aboutsummaryrefslogtreecommitdiffstats
path: root/URPM
diff options
context:
space:
mode:
Diffstat (limited to 'URPM')
-rw-r--r--URPM/.perl_checker1
-rw-r--r--URPM/Build.pm528
-rw-r--r--URPM/Query.pm40
-rw-r--r--URPM/Resolve.pm2003
-rw-r--r--URPM/Signature.pm91
5 files changed, 2663 insertions, 0 deletions
diff --git a/URPM/.perl_checker b/URPM/.perl_checker
new file mode 100644
index 0000000..202e053
--- /dev/null
+++ b/URPM/.perl_checker
@@ -0,0 +1 @@
+Basedir ..
diff --git a/URPM/Build.pm b/URPM/Build.pm
new file mode 100644
index 0000000..f298707
--- /dev/null
+++ b/URPM/Build.pm
@@ -0,0 +1,528 @@
+package URPM;
+
+# $Id: Build.pm 270395 2010-07-30 00:55:59Z nanardon $
+
+use strict;
+use warnings;
+
+sub _get_tmp_dir () {
+ my $t = $ENV{TMPDIR};
+ $t && -w $t or $t = '/tmp';
+ "$t/.build_hdlist";
+}
+
+# DEPRECATED. ONLY USED BY MKCD
+#
+#- prepare build of an hdlist from a list of files.
+#- it can be used to start computing depslist.
+#- parameters are :
+#- rpms : array of all rpm file name to parse (mandatory)
+#- dir : directory which will contain headers (defaults to /tmp/.build_hdlist)
+#- callback : perl code to be called for each package read (defaults pack_header)
+#- clean : bool to clean cache before (default no).
+#- packing : bool to create info (default is weird)
+#
+# deprecated
+sub parse_rpms_build_headers {
+ my ($urpm, %options) = @_;
+ my ($dir, %cache, @headers);
+
+ #- check for mandatory options.
+ if (@{$options{rpms} || []} > 0) {
+ #- build a working directory which will hold rpm headers.
+ $dir = $options{dir} || _get_tmp_dir();
+ $options{clean} and system($ENV{LD_LOADER} ? $ENV{LD_LOADER} : @{[]}, "rm", "-rf", $dir);
+ -d $dir or mkdir $dir, 0755 or die "cannot create directory $dir\n";
+
+ #- examine cache if it contains any headers which will be much faster to read
+ #- than parsing rpm file directly.
+ unless ($options{clean}) {
+ my $dirh;
+ opendir $dirh, $dir;
+ while (defined (my $file = readdir $dirh)) {
+ my ($fullname, $filename) = $file =~ /(.+?-[^:\-]+-[^:\-]+\.[^:\-\.]+)(?::(\S+))?$/ or next;
+ my @stat = stat "$dir/$file";
+ $cache{$filename || $fullname} = {
+ file => $file,
+ size => $stat[7],
+ 'time' => $stat[9],
+ };
+ }
+ closedir $dirh;
+ }
+
+ foreach (@{$options{rpms}}) {
+ my ($key) = m!([^/]*)\.rpm$! or next; #- get rpm filename.
+ my ($id, $filename);
+
+ if ($cache{$key} && $cache{$key}{time} > 0 && $cache{$key}{time} >= (stat $_)[9]) {
+ ($id, undef) = $urpm->parse_hdlist("$dir/$cache{$key}{file}", packing => $options{packing}, keep_all_tags => $options{keep_all_tags});
+ unless (defined $id) {
+ if ($options{dontdie}) {
+ print STDERR "bad header $dir/$cache{$key}{file}\n";
+ next;
+ } else {
+ die "bad header $dir/$cache{$key}{file}\n";
+ }
+ }
+
+ $options{callback} and $options{callback}->($urpm, $id, %options, (file => $_));
+
+ $filename = $cache{$key}{file};
+ } else {
+ ($id, undef) = $urpm->parse_rpm($_, keep_all_tags => $options{keep_all_tags});
+ unless (defined $id) {
+ if ($options{dontdie}) {
+ print STDERR "bad rpm $_\n";
+ next;
+ } else {
+ die "bad rpm $_\n";
+ }
+ }
+
+ my $pkg = $urpm->{depslist}[$id];
+
+ $filename = $pkg->fullname;
+
+ unless (-s "$dir/$filename") {
+ open my $fh, ">$dir/$filename" or die "unable to open $dir/$filename for writing\n";
+ $pkg->build_header(fileno $fh);
+ close $fh;
+ }
+ -s "$dir/$filename" or unlink("$dir/$filename"), die "can create header $dir/$filename\n";
+
+ #- make smart use of memory (no need to keep header in memory now).
+ if ($options{callback}) {
+ $options{callback}->($urpm, $id, %options, (file => $_));
+ } else {
+ $pkg->pack_header;
+ }
+
+ # Olivier Thauvin <thauvin@aerov.jussieu.fr>
+ # isn't this code better, but maybe it will break some tools:
+ # $options{callback}->($urpm, $id, %options, (file => $_)) if ($options{callback});
+ # $pkg->pack_header;
+ }
+
+ #- keep track of header associated (to avoid rereading rpm filename directly
+ #- if rereading has been made neccessary).
+ push @headers, $filename;
+ }
+ }
+ @headers;
+}
+
+# DEPRECATED. ONLY USED BY MKCD
+#
+#- allow rereading of hdlist and clean.
+sub unresolved_provides_clean {
+ my ($urpm) = @_;
+ $urpm->{depslist} = [];
+ $urpm->{provides}{$_} = undef foreach keys %{$urpm->{provides} || {}};
+}
+
+# DEPRECATED. ONLY USED BY MKCD
+#
+#- read a list of headers (typically when building an hdlist when provides have
+#- been cleaned).
+#- parameters are :
+#- headers : array containing all headers filenames to parse (mandatory)
+#- dir : directory which contains headers (defaults to /tmp/.build_hdlist)
+#- callback : perl code to be called for each package read (defaults to pack_header)
+sub parse_headers {
+ my ($urpm, %options) = @_;
+ my ($dir, $start, $id);
+
+ $dir = $options{dir} || _get_tmp_dir();
+ -d $dir or die "no directory $dir\n";
+
+ $start = @{$urpm->{depslist} || []};
+ foreach (@{$options{headers} || []}) {
+ #- make smart use of memory (no need to keep header in memory now).
+ ($id, undef) = $urpm->parse_hdlist("$dir/$_", packing => !$options{callback});
+ defined $id or die "bad header $dir/$_\n";
+ $options{callback} and $options{callback}->($urpm, $id, %options);
+ }
+ defined $id ? ($start, $id) : @{[]};
+}
+
+# DEPRECATED. ONLY USED BY MKCD
+#- compute dependencies, result in stored in info values of urpm.
+#- operations are incremental, it is possible to read just one hdlist, compute
+#- dependencies and read another hdlist, and again.
+#- parameters are :
+#- callback : callback to relocate reference to package id.
+sub compute_deps {
+ my ($urpm, %options) = @_;
+ my %propagated_weight = (
+ basesystem => 10000,
+ msec => 20000,
+ filesystem => 50000,
+ );
+ my ($locales_weight, $step_weight, $fixed_weight) = (-5000, 10000, $propagated_weight{basesystem});
+
+ #- avoid recomputing already present infos, take care not to modify
+ #- existing entries, as the array here is used instead of values of infos.
+ my $start = @{$urpm->{deps} ||= []};
+ my $end = $#{$urpm->{depslist} || []};
+
+ #- check if something has to be done.
+ $start > $end and return;
+
+ #- keep track of prereqs.
+ my %prereqs;
+
+ #- take into account in which hdlist a package has been found.
+ #- this can be done by an incremental take into account generation
+ #- of depslist.ordered part corresponding to the hdlist.
+ #- compute closed requires, do not take into account choices.
+ foreach ($start .. $end) {
+ my $pkg = $urpm->{depslist}[$_];
+
+ my %required_packages;
+ my @required_packages;
+ my %requires;
+
+ foreach ($pkg->requires) {
+ my ($n, $prereq) = /^([^\s\[]*)(\[\*\])?/;
+ $requires{$n} = $prereq && 1;
+ }
+ my @requires = keys %requires;
+
+ while (my $req = shift @requires) {
+ $req =~ /^basesystem/ and next; #- never need to requires basesystem directly as always required! what a speed up!
+ my $treq = (
+ $req =~ /^\d+$/ ? [ $req ]
+ : $urpm->{provides}{$req} ? [ keys %{$urpm->{provides}{$req}} ]
+ : [ ($req !~ /NOTFOUND_/ ? "NOTFOUND_" : "") . $req ]
+ );
+ if (@$treq > 1) {
+ #- this is a choice, no closure need to be done here.
+ push @required_packages, $treq;
+ } else {
+ #- this could be nothing if the provides is a file not found.
+ #- and this has been fixed above.
+ foreach (@$treq) {
+ my $pkg_ = /^\d+$/ && $urpm->{depslist}[$_];
+ exists $required_packages{$_} and $pkg_ = undef;
+ $required_packages{$_} ||= $requires{$req}; $pkg_ or next;
+ foreach ($pkg_->requires_nosense) {
+ exists $requires{$_} or push @requires, $_;
+ $requires{$_} ||= $requires{$req};
+ }
+ }
+ }
+ }
+ #- examine choice to remove those which are not mandatory.
+ foreach (@required_packages) {
+ unless (grep { exists $required_packages{$_} } @$_) {
+ $required_packages{join '|', sort { $a <=> $b } @$_} = undef;
+ }
+ }
+
+ #- store a short representation of requires.
+ $urpm->{requires}[$_] = join ' ', keys %required_packages;
+ foreach my $d (keys %required_packages) {
+ $required_packages{$d} or next;
+ $prereqs{$d}{$_} = undef;
+ }
+ }
+
+ #- expand choices and closure again.
+ my %ordered;
+ foreach ($start .. $end) {
+ my @requires = $_;
+ my ($dep, %requires);
+ while (defined ($dep = shift @requires)) {
+ exists $requires{$dep} || /^[^\d\|]*$/ and next;
+ foreach ($dep, split ' ', (defined $urpm->{deps}[$dep] ? $urpm->{deps}[$dep] : $urpm->{requires}[$dep])) {
+ if (/\|/) {
+ push @requires, split /\|/, $_;
+ } else {
+ /^\d+$/ and $requires{$_} = undef;
+ }
+ }
+ }
+
+ my $pkg = $urpm->{depslist}[$_];
+ my $delta = 1 + $propagated_weight{$pkg->name};
+ foreach (keys %requires) {
+ $ordered{$_} += $delta;
+ }
+ }
+
+ #- some package should be sorted at the beginning.
+ foreach (qw(basesystem msec rpm locales filesystem setup glibc sash bash libtermcap2 termcap readline ldconfig)) {
+ foreach (keys %{$urpm->{provides}{$_} || {}}) {
+ /^\d+$/ and $ordered{$_} = $fixed_weight;
+ }
+ /locales/ and $locales_weight += $fixed_weight;
+ $fixed_weight += $step_weight;
+ }
+ foreach ($start .. $end) {
+ my $pkg = $urpm->{depslist}[$_];
+
+ $pkg->name =~ /locales-[a-zA-Z]/ and $ordered{$_} = $locales_weight;
+ }
+
+ #- compute base flag, consists of packages which are required without
+ #- choices of basesystem and are ALWAYS installed. these packages can
+ #- safely be removed from requires of others packages.
+ foreach (qw(basesystem glibc kernel)) {
+ foreach (keys %{$urpm->{provides}{$_} || {}}) {
+ foreach ($_, split ' ', (defined $urpm->{deps}[$_] ? $urpm->{deps}[$_] : $urpm->{requires}[$_])) {
+ /^\d+$/ and $urpm->{depslist}[$_] and $urpm->{depslist}[$_]->set_flag_base(1);
+ }
+ }
+ }
+
+ #- give an id to each packages, start from number of package already
+ #- registered in depslist.
+ my %remap_ids; @remap_ids{sort {
+ exists $prereqs{$b}{$a} && ! exists $prereqs{$a}{$b} ? 1 :
+ $ordered{$b} <=> $ordered{$a} or do {
+ my ($na, $nb) = map { $urpm->{depslist}[$_]->name } ($a, $b);
+ my ($sa, $sb) = map { /^lib(.*)/ ? $1 : '' } ($na, $nb);
+ $sa && $sb ? $sa cmp $sb : $sa ? -1 : $sb ? 1 : $na cmp $nb;
+ } } ($start .. $end)} = ($start .. $end);
+
+ #- now it is possible to clean ordered and prereqs.
+ %ordered = %prereqs = ();
+
+ #- recompute requires to use packages id, drop any base packages or
+ #- reference of a package to itself.
+ my @depslist;
+ foreach ($start .. $end) {
+ my $pkg = $urpm->{depslist}[$_];
+
+ #- set new id.
+ $pkg->set_id($remap_ids{$_});
+
+ my ($id, $base, %requires_id, %not_founds);
+ foreach (split ' ', $urpm->{requires}[$_]) {
+ if (/\|/) {
+ #- all choices are grouped together at the end of requires,
+ #- this allow computation of dropable choices.
+ my ($to_drop, @choices_base_id, @choices_id);
+ foreach (split /\|/, $_) {
+ my ($id, $base) = (exists($remap_ids{$_}) ? $remap_ids{$_} : $_, $urpm->{depslist}[$_]->flag_base);
+ $base and push @choices_base_id, $id;
+ $base &&= ! $pkg->flag_base;
+ $to_drop ||= $id == $pkg->id || exists $requires_id{$id} || $base;
+ push @choices_id, $id;
+ }
+
+ #- package can safely be dropped as it will be selected in requires directly.
+ $to_drop and next;
+
+ #- if a base package is in a list, keep it instead of the choice.
+ if (@choices_base_id) {
+ @choices_id = @choices_base_id;
+ $base = 1;
+ }
+ if (@choices_id == 1) {
+ $id = $choices_id[0];
+ } else {
+ my $choices_key = join '|', sort { $a <=> $b } @choices_id;
+ $requires_id{$choices_key} = undef;
+ next;
+ }
+ } elsif (/^\d+$/) {
+ ($id, $base) = (exists($remap_ids{$_}) ? $remap_ids{$_} : $_, $urpm->{depslist}[$_]->flag_base);
+ } else {
+ $not_founds{$_} = undef;
+ next;
+ }
+
+ #- select individual package from choices or defined package.
+ $base &&= ! $pkg->flag_base;
+ $base || $id == $pkg->id or $requires_id{$id} = undef;
+ }
+ #- be smart with memory usage.
+ delete $urpm->{requires}[$_];
+ $urpm->{deps}[$remap_ids{$_}] = join ' ', ((sort { ($a =~ /^(\d+)/)[0] <=> ($b =~ /^(\d+)/)[0] } keys %requires_id),
+ keys %not_founds);
+ $depslist[$remap_ids{$_}-$start] = $pkg;
+ }
+
+ #- remap all provides ids for new package position and update depslist.
+ delete $urpm->{requires};
+ @{$urpm->{depslist}}[$start .. $end] = @depslist;
+ foreach my $h (values %{$urpm->{provides}}) {
+ my %provided;
+ foreach (keys %{$h || {}}) {
+ $provided{exists($remap_ids{$_}) ? $remap_ids{$_} : $_} = delete $h->{$_};
+ }
+ $h = \%provided;
+ }
+ $options{callback} and $options{callback}->($urpm, \%remap_ids, %options);
+
+ ($start, $end);
+}
+
+# DEPRECATED. ONLY USED BY MKCD
+#
+#- build an hdlist from existing depslist, from start to end inclusive.
+#- parameters are :
+#- hdlist : hdlist file to use.
+#- dir : directory which contains headers (defaults to /tmp/.build_hdlist)
+#- start : index of first package (defaults to first index of depslist).
+#- end : index of last package (defaults to last index of depslist).
+#- idlist : id list of rpm to compute (defaults is start .. end)
+#- ratio : compression ratio (default 4).
+#- split : split ratio (default 400kb, see MDV::Packdrakeng).
+sub build_hdlist {
+ my ($urpm, %options) = @_;
+ my ($dir, $ratio, @idlist);
+
+ $dir = $options{dir} || _get_tmp_dir();
+ -d $dir or die "no directory $dir\n";
+
+ @idlist = $urpm->build_listid($options{start}, $options{end}, $options{idlist});
+
+ #- compression ratio are not very high, sample for cooker
+ #- gives the following (main only and cache fed up):
+ #- ratio compression_time size
+ #- 9 21.5 sec 8.10Mb -> good for installation CD
+ #- 6 10.7 sec 8.15Mb
+ #- 5 9.5 sec 8.20Mb
+ #- 4 8.6 sec 8.30Mb -> good for urpmi
+ #- 3 7.6 sec 8.60Mb
+ $ratio = $options{ratio} || 4;
+
+ require MDV::Packdrakeng;
+ my $pack = MDV::Packdrakeng->new(
+ archive => $options{hdlist},
+ compress => "gzip",
+ uncompress => "gzip -d",
+ block_size => $options{split},
+ comp_level => $ratio,
+ ) or die "Can't create archive";
+ foreach my $pkg (@{$urpm->{depslist}}[@idlist]) {
+ my $filename = $pkg->fullname;
+ -s "$dir/$filename" or die "bad header $dir/$filename\n";
+ $pack->add($dir, $filename);
+ }
+}
+
+#- build synthesis file.
+#- used by genhdlist2 and mkcd
+#-
+#- parameters are :
+#- synthesis : synthesis file to create (mandatory if fd not given).
+#- fd : file descriptor (mandatory if synthesis not given).
+#- start : index of first package (defaults to first index of depslist).
+#- end : index of last package (defaults to last index of depslist).
+#- idlist : id list of rpm to compute (defaults is start .. end)
+#- ratio : compression ratio (default 9).
+#- filter : program to filter through (default is 'gzip -$ratio').
+#- returns true on success
+sub build_synthesis {
+ my ($urpm, %options) = @_;
+ my ($ratio, $filter, @idlist);
+
+ @idlist = $urpm->build_listid($options{start}, $options{end}, $options{idlist});
+
+ $ratio = $options{ratio} || 9;
+ $filter = $options{filter} ? $options{filter} : "gzip -$ratio";
+ $options{synthesis} || defined $options{fd} or die "invalid parameters given";
+
+ #- first pass: traverse provides to find files provided.
+ my %provided_files;
+ foreach (keys %{$urpm->{provides}}) {
+ m!^/! or next;
+ foreach my $id (keys %{$urpm->{provides}{$_} || {}}) {
+ push @{$provided_files{$id} ||= []}, $_;
+ }
+ }
+
+
+ #- second pass: write each info including files provided.
+ $options{synthesis} and open my $fh, "| " . ($ENV{LD_LOADER} || '') . " $filter >'$options{synthesis}'";
+ foreach (@idlist) {
+ my $pkg = $urpm->{depslist}[$_];
+ my %files;
+
+ if ($provided_files{$_}) {
+ @files{@{$provided_files{$_}}} = undef;
+ delete @files{$pkg->provides_nosense};
+ }
+
+ $pkg->build_info($options{synthesis} ? fileno $fh : $options{fd}, join('@', keys %files));
+ }
+ close $fh; # returns true on success
+}
+
+# DEPRECATED. ONLY USED BY MKCD
+#- write depslist.ordered file according to info in params.
+#- parameters are :
+#- depslist : depslist.ordered file to create.
+#- provides : provides file to create.
+#- compss : compss file to create.
+sub build_base_files {
+ my ($urpm, %options) = @_;
+
+ if ($options{depslist}) {
+ open my $fh, ">", $options{depslist} or die "Can't write to $options{depslist}: $!\n";
+ foreach (0 .. $#{$urpm->{depslist}}) {
+ my $pkg = $urpm->{depslist}[$_];
+
+ printf $fh ("%s-%s-%s.%s%s %s %s\n", $pkg->fullname,
+ ($pkg->epoch ? ':' . $pkg->epoch : ''), $pkg->size || 0, $urpm->{deps}[$_]);
+ }
+ close $fh;
+ }
+
+ if ($options{provides}) {
+ open my $fh, ">", $options{provides} or die "Can't write to $options{provides}: $!\n";
+ while (my ($k, $v) = each %{$urpm->{provides}}) {
+ printf $fh "%s\n", join '@', $k, map { scalar $urpm->{depslist}[$_]->fullname } keys %{$v || {}};
+ }
+ close $fh;
+ }
+
+ if ($options{compss}) {
+ my %p;
+
+ open my $fh, ">", $options{compss} or die "Can't write to $options{compss}: $!\n";
+ foreach (@{$urpm->{depslist}}) {
+ $_->group or next;
+ push @{$p{$_->group} ||= []}, $_->name;
+ }
+ foreach (sort keys %p) {
+ print $fh $_, "\n";
+ foreach (@{$p{$_}}) {
+ print $fh "\t", $_, "\n";
+ }
+ print $fh "\n";
+ }
+ close $fh;
+ }
+
+ 1;
+}
+
+our $MAKEDELTARPM = '/usr/bin/makedeltarpm';
+
+#- make_delta_rpm($old_rpm_file, $new_rpm_file)
+# Creates a delta rpm in the current directory.
+
+# DEPRECATED. UNUSED
+sub make_delta_rpm ($$) {
+ @_ == 2 or return 0;
+ -e $_[0] && -e $_[1] && -x $MAKEDELTARPM or return 0;
+ my @id;
+ my $urpm = new URPM;
+ foreach my $i (0, 1) {
+ ($id[$i]) = $urpm->parse_rpm($_[$i]);
+ defined $id[$i] or return 0;
+ }
+ my $oldpkg = $urpm->{depslist}[$id[0]];
+ my $newpkg = $urpm->{depslist}[$id[1]];
+ $oldpkg->arch eq $newpkg->arch or return 0;
+ #- construct filename of the deltarpm
+ my $patchrpm = $oldpkg->name . '-' . $oldpkg->version . '-' . $oldpkg->release . '_' . $newpkg->version . '-' . $newpkg->release . '.' . $oldpkg->arch . '.delta.rpm';
+ !system($MAKEDELTARPM, @_, $patchrpm);
+}
+
+1;
diff --git a/URPM/Query.pm b/URPM/Query.pm
new file mode 100644
index 0000000..14256a4
--- /dev/null
+++ b/URPM/Query.pm
@@ -0,0 +1,40 @@
+package URPM;
+
+use strict;
+use warnings;
+
+# Olivier Thauvin <thauvin@aerov.jussieu.fr>
+# This package extend URPM functions to permit
+# URPM low level query on rpm header
+# $Id: Query.pm 270395 2010-07-30 00:55:59Z nanardon $
+
+# tag2id
+# INPUT array of rpm tag name
+# Return an array of ID tag
+
+sub tag2id {
+ my @l = @_;
+ my %taglist = URPM::list_rpm_tag();
+ map { $taglist{uc($_)} || undef } @l;
+}
+
+sub query_pkg {
+ my (undef, $pkg, $query) = @_;
+ my @tags = map {
+ [ $pkg->get_tag(tag2id($_)) ]
+ } $query =~ m/\%\{([^{}]*)\}*/g;
+
+ $query =~ s/\%\{[^{}]*\}/%s/g;
+ $query =~ s/\\n/\n/g;
+ $query =~ s/\\t/\t/g;
+ my ($max, @res) = 0;
+
+ foreach (@tags) { $max < $#{$_} and $max = $#{$_} };
+
+ foreach my $i (0 .. $max) {
+ push(@res, sprintf($query, map { ${$_}[ $#{$_} < $i ? $#{$_} : $i ] } @tags));
+ }
+ @res
+}
+
+1;
diff --git a/URPM/Resolve.pm b/URPM/Resolve.pm
new file mode 100644
index 0000000..b82b45b
--- /dev/null
+++ b/URPM/Resolve.pm
@@ -0,0 +1,2003 @@
+package URPM;
+#package URPM::Resolve;
+#use URPM;
+
+# $Id: Resolve.pm 270395 2010-07-30 00:55:59Z nanardon $
+
+use strict;
+use warnings;
+use Config;
+
+
+#- a few functions from MDK::Common copied here:
+sub listlength { scalar @_ }
+sub min { my $n = shift; $_ < $n and $n = $_ foreach @_; $n }
+sub uniq { my %l; $l{$_} = 1 foreach @_; grep { delete $l{$_} } @_ }
+sub find(&@) {
+ my $f = shift;
+ $f->($_) and return $_ foreach @_;
+ undef;
+}
+
+#- property2name* functions below parse things like "mageia-release[>= 1]"
+#- which is the format returned by URPM.xs for ->requires, ->provides, ->conflicts...
+sub property2name {
+ $_[0] =~ /^([^\s\[]*)/ && $1;
+}
+sub property2name_range {
+ $_[0] =~ /^([^\s\[]*)(?:\[\*\])?\[?([^\s\]]*\s*[^\s\]]*)/;
+}
+sub property2name_op_version {
+ $_[0] =~ /^([^\s\[]*)(?:\[\*\])?\s*\[?([^\s\]]*)\s*([^\s\]]*)/;
+}
+
+#- wrappers around $state (cf "The $state object" in "perldoc URPM")
+sub packages_to_remove {
+ my ($state) = @_;
+ grep {
+ $state->{rejected}{$_}{removed} && !$state->{rejected}{$_}{obsoleted};
+ } keys %{$state->{rejected} || {}};
+}
+sub removed_or_obsoleted_packages {
+ my ($state) = @_;
+ grep {
+ $state->{rejected}{$_}{removed} || $state->{rejected}{$_}{obsoleted};
+ } keys %{$state->{rejected} || {}};
+}
+
+#- Find candidates packages from a require string (or id).
+#- Takes care of choices using the '|' separator.
+#- (nb: see also find_required_package())
+#-
+#- side-effects: none
+sub find_candidate_packages_ {
+ my ($urpm, $id_prop, $o_rejected) = @_;
+ my @packages;
+
+ foreach (split /\|/, $id_prop) {
+ if (/^\d+$/) {
+ my $pkg = $urpm->{depslist}[$_];
+ $pkg->flag_skip and next;
+ $pkg->arch eq 'src' || $pkg->is_arch_compat or next;
+ $o_rejected && exists $o_rejected->{$pkg->fullname} and next;
+ push @packages, $pkg;
+ } elsif (my $name = property2name($_)) {
+ my $property = $_;
+ foreach (keys %{$urpm->{provides}{$name} || {}}) {
+ my $pkg = $urpm->{depslist}[$_];
+ $pkg->flag_skip and next;
+ $pkg->is_arch_compat or next;
+ $o_rejected && exists $o_rejected->{$pkg->fullname} and next;
+ #- check if at least one provide of the package overlap the property.
+ !$urpm->{provides}{$name}{$_} || $pkg->provides_overlap($property, 1)
+ and push @packages, $pkg;
+ }
+ }
+ }
+ @packages;
+}
+
+#- deprecated, use find_candidate_packages_() directly
+#-
+#- side-effects: none
+sub find_candidate_packages {
+ my ($urpm, $id_prop, $o_rejected) = @_;
+
+ my %packages;
+ foreach (find_candidate_packages_($urpm, $id_prop, $o_rejected)) {
+ push @{$packages{$_->name}}, $_;
+ }
+ \%packages;
+}
+
+#- returns the "arch" of package $n in rpm db
+sub get_installed_arch {
+ my ($db, $n) = @_;
+ my $arch;
+ $db->traverse_tag('name', [ $n ], sub { $arch = $_[0]->arch });
+ $arch;
+}
+
+#- is "strict-arch" wanted? (cf "man urpmi")
+#- since it's slower we only force it on bi-arch
+sub strict_arch {
+ my ($urpm) = @_;
+ defined $urpm->{options}{'strict-arch'} ? $urpm->{options}{'strict-arch'} : $Config{archname} =~ /x86_64|sparc64|ppc64/;
+}
+my %installed_arch;
+
+#- checks wether $pkg could be installed under strict-arch policy
+#- (ie check wether $pkg->name with different arch is not installed)
+#-
+#- side-effects: none (but uses a cache)
+sub strict_arch_check_installed {
+ my ($db, $pkg) = @_;
+ if ($pkg->arch ne 'src' && $pkg->arch ne 'noarch') {
+ my $n = $pkg->name;
+ defined $installed_arch{$n} or $installed_arch{$n} = get_installed_arch($db, $n);
+ if ($installed_arch{$n} && $installed_arch{$n} ne 'noarch') {
+ $pkg->arch eq $installed_arch{$n} or return;
+ }
+ }
+ 1;
+}
+
+#- check wether $installed_pkg and $pkg have same arch
+#- (except for src/noarch of course)
+#-
+#- side-effects: none
+sub strict_arch_check {
+ my ($installed_pkg, $pkg) = @_;
+ if ($pkg->arch ne 'src' && $pkg->arch ne 'noarch') {
+ if ($installed_pkg->arch ne 'noarch') {
+ $pkg->arch eq $installed_pkg->arch or return;
+ }
+ }
+ 1;
+}
+
+#- is $pkg->name installed?
+#-
+#- side-effects: none
+sub is_package_installed {
+ my ($db, $pkg) = @_;
+
+ my $found;
+ $db->traverse_tag('name', [ $pkg->name ], sub {
+ my ($p) = @_;
+ $found ||= $p->fullname eq $pkg->fullname;
+ });
+ $found;
+}
+
+sub _is_selected_or_installed {
+ my ($urpm, $db, $name) = @_;
+
+ (grep { $_->flag_available } $urpm->packages_providing($name)) > 0 ||
+ $db->traverse_tag('name', [ $name ], undef) > 0;
+}
+
+#- finds $pkg "provides" that matches $provide_name, and returns the version provided
+#- eg: $pkg provides "a = 3", $provide_name is "a > 1", returns "3"
+sub provided_version_that_overlaps {
+ my ($pkg, $provide_name) = @_;
+
+ my $version;
+ foreach my $property ($pkg->provides) {
+ my ($n, undef, $v) = property2name_op_version($property) or next;
+ $n eq $provide_name or next;
+
+ if ($version) {
+ $version = $v if URPM::rpmvercmp($v, $version) > 0;
+ } else {
+ $version = $v;
+ }
+ }
+ $version;
+}
+
+#- deprecated function, use find_required_package()
+sub find_chosen_packages { &find_required_package }
+
+#- find the package (or packages) to install matching $id_prop
+#- returns (list ref of matches, list ref of preferred matches)
+#- (see also find_candidate_packages_())
+#-
+#- side-effects: flag_install, flag_upgrade (and strict_arch_check_installed cache)
+sub find_required_package {
+ my ($urpm, $db, $state, $id_prop) = @_;
+ my (%packages, %provided_version);
+ my $strict_arch = strict_arch($urpm);
+
+ my $may_add_to_packages = sub {
+ my ($pkg) = @_;
+
+ if (my $p = $packages{$pkg->name}) {
+ $pkg->flag_requested > $p->flag_requested ||
+ $pkg->flag_requested == $p->flag_requested && $pkg->compare_pkg($p) > 0 and $packages{$pkg->name} = $pkg;
+ } else {
+ $packages{$pkg->name} = $pkg;
+ }
+ };
+
+ #- search for possible packages, try to be as fast as possible, backtrack can be longer.
+ foreach (split /\|/, $id_prop) {
+ if (/^\d+$/) {
+ my $pkg = $urpm->{depslist}[$_];
+ $pkg->arch eq 'src' || $pkg->is_arch_compat or next;
+ $pkg->flag_skip || $state->{rejected}{$pkg->fullname} and next;
+ #- determine if this package is better than a possibly previously chosen package.
+ $pkg->flag_selected || exists $state->{selected}{$pkg->id} and return [$pkg];
+ !$strict_arch || strict_arch_check_installed($db, $pkg) or next;
+ $may_add_to_packages->($pkg);
+ } elsif (my $name = property2name($_)) {
+ my $property = $_;
+ foreach (keys %{$urpm->{provides}{$name} || {}}) {
+ my $pkg = $urpm->{depslist}[$_];
+ $pkg->is_arch_compat or next;
+ $pkg->flag_skip || $state->{rejected}{$pkg->fullname} and next;
+ #- check if at least one provide of the package overlaps the property
+ if (!$urpm->{provides}{$name}{$_} || $pkg->provides_overlap($property)) {
+ #- determine if this package is better than a possibly previously chosen package.
+ $pkg->flag_selected || exists $state->{selected}{$pkg->id} and return [$pkg];
+ !$strict_arch || strict_arch_check_installed($db, $pkg) or next;
+ $provided_version{$pkg} = provided_version_that_overlaps($pkg, $name);
+ $may_add_to_packages->($pkg);
+ }
+ }
+ }
+ }
+ my @packages = values %packages;
+
+ if (@packages > 1) {
+ #- packages should be preferred if one of their provides is referenced
+ #- in the "requested" hash, or if the package itself is requested (or
+ #- required).
+ #- If there is no preference, choose the first one by default (higher
+ #- probability of being chosen) and ask the user.
+ #- Packages with more compatibles architectures are always preferred.
+ #- Puts the results in @chosen. Other are left unordered.
+ foreach my $pkg (@packages) {
+ _set_flag_installed_and_upgrade_if_no_newer($db, $pkg);
+ }
+
+ if (my @kernel_source = _find_required_package__kernel_source($urpm, $db, \@packages)) {
+ $urpm->{debug_URPM}("packageCallbackChoices: kernel source chosen " . join(",", map { $_->name } @kernel_source) . " in " . join(",", map { $_->name } @packages)) if $urpm->{debug_URPM};
+ return \@kernel_source, \@kernel_source;
+ }
+ if (my @kmod = _find_required_package__kmod($urpm, $db, \@packages)) {
+ $urpm->{debug_URPM}("packageCallbackChoices: kmod packages " . join(",", map { $_->name } @kmod) . " in " . join(",", map { $_->name } @packages)) if $urpm->{debug_URPM};
+ return \@kmod, \@kmod;
+ }
+
+ _find_required_package__sort($urpm, $db, \@packages, \%provided_version);
+ } else {
+ \@packages;
+ }
+}
+
+# nb: _set_flag_installed_and_upgrade_if_no_newer must be done on $packages
+sub _find_required_package__sort {
+ my ($urpm, $db, $packages, $provided_version) = @_;
+
+ my ($best, @other) = sort {
+ $a->[1] <=> $b->[1] #- we want the lowest (ie preferred arch)
+ || $b->[2] <=> $a->[2]; #- and the higher score
+ } map {
+ my $score = 0;
+ $score += 2 if $_->flag_requested;
+ $score += $_->flag_upgrade ? 1 : -1 if $_->flag_installed;
+ [ $_, $_->is_arch_compat, $score ];
+ } @$packages;
+
+ my @chosen_with_score = ($best, grep { $_->[1] == $best->[1] && $_->[2] == $best->[2] } @other);
+ my @chosen = map { $_->[0] } @chosen_with_score;
+
+ #- return immediately if there is only one chosen package
+ if (@chosen == 1) { return \@chosen }
+
+ #- if several packages were selected to match a requested installation,
+ #- and if --more-choices wasn't given, trim the choices to the first one.
+ if (!$urpm->{options}{morechoices} && $chosen_with_score[0][2] == 3) {
+ return [ $chosen[0] ];
+ }
+
+ if ($urpm->{media}) {
+ @chosen_with_score = sort {
+ $a->[2] != $b->[2] ?
+ $a->[0]->id <=> $b->[0]->id :
+ $b->[1] <=> $a->[1] || $b->[0]->compare_pkg($a->[0]);
+ } map { [ $_, _score_for_locales($urpm, $db, $_), pkg2media($urpm->{media}, $_) ] } @chosen;
+ } else {
+ # obsolete code which should not happen, kept just in case
+ $urpm->{debug_URPM}("can't sort choices by media") if $urpm->{debug_URPM};
+ @chosen_with_score = sort {
+ $b->[1] <=> $a->[1] ||
+ $b->[0]->compare_pkg($a->[0]) || $a->[0]->id <=> $b->[0]->id;
+ } map { [ $_, _score_for_locales($urpm, $db, $_) ] } @chosen;
+ }
+ if (!$urpm->{options}{morechoices}) {
+ if (my @valid_locales = grep { $_->[1] } @chosen_with_score) {
+ #- get rid of invalid locales
+ @chosen_with_score = @valid_locales;
+ }
+ }
+ # propose to select all packages for installed locales
+ my @prefered = grep { $_->[1] == 3 } @chosen_with_score;
+
+ @chosen = map { $_->[0] } @chosen_with_score;
+ if (%$provided_version) {
+ # highest provided version first
+ # (nb: this sort overrules the sort on media (cf ->id above))
+ @chosen = sort { URPM::rpmvercmp($provided_version->{$b} || 0, $provided_version->{$a} || 0) } @chosen;
+ }
+ \@chosen, [ map { $_->[0] } @prefered ];
+}
+
+#- prefer the pkgs corresponding to installed/selected kernels
+sub _find_required_package__kernel_source {
+ my ($urpm, $db, $choices) = @_;
+
+ $choices->[0]->name =~ /^kernel-(.*source-|.*-devel-)/ or return;
+
+ grep {
+ if ($_->name =~ /^kernel-.*source-stripped-(.*)/) {
+ my $version = quotemeta($1);
+ find {
+ $_->name =~ /-$version$/ && ($_->flag_installed || $_->flag_selected);
+ } $urpm->packages_providing('kernel');
+ } elsif ($_->name =~ /(kernel-.*)-devel-(.*)/) {
+ my $kernel = "$1-$2";
+ _is_selected_or_installed($urpm, $db, $kernel);
+ } elsif ($_->name =~ /^kernel-.*source-/) {
+ #- hopefully we don't have a media with kernel-source but not kernel-source-stripped nor kernel-.*-devel
+ 0;
+ } else {
+ $urpm->{debug_URPM}("unknown kernel-source package " . $_->fullname) if $urpm->{debug_URPM};
+ 0;
+ }
+ } @$choices;
+}
+
+#- prefer the pkgs corresponding to installed/selected kernels
+sub _find_required_package__kmod {
+ my ($urpm, $db, $choices) = @_;
+
+ $choices->[0]->name =~ /^dkms-|-kernel-2\./ or return;
+
+ grep {
+ if (my ($_name, $version, $flavor, $release) = $_->name =~ /(.*)-kernel-(2\..*)-(.*)-(.*)/) {
+ my $kernel = "kernel-$flavor-$version-$release";
+ _is_selected_or_installed($urpm, $db, $kernel);
+ } elsif ($_->name =~ /^dkms-/) {
+ 0; # we prefer precompiled dkms
+ } else {
+ $urpm->{debug_URPM}("unknown kmod package " . $_->fullname) if $urpm->{debug_URPM};
+ 0;
+ }
+ } @$choices;
+}
+
+#- Packages that require locales-xxx when the corresponding locales are
+#- already installed should be preferred over packages that require locales
+#- which are not installed.
+#-
+#- eg: locales-fr & locales-de are installed,
+#- prefer firefox-fr & firefox-de which respectively require locales-fr & locales-de
+sub _score_for_locales {
+ my ($urpm, $db, $pkg) = @_;
+
+ my @r = $pkg->requires_nosense;
+
+ if (my ($specific_locales) = grep { /locales-(?!en)/ } @r) {
+ if (_is_selected_or_installed($urpm, $db, $specific_locales)) {
+ 3; # good locale
+ } else {
+ 0; # bad locale
+ }
+ } elsif (grep { /locales-en/ } @r) {
+ 2; #
+ } else {
+ 1;
+ }
+}
+
+#- side-effects: $properties, $choices
+#- + those of backtrack_selected ($state->{backtrack}, $state->{rejected}, $state->{selected}, $state->{whatrequires}, flag_requested, flag_required)
+sub _choose_required {
+ my ($urpm, $db, $state, $dep, $properties, $choices, $diff_provides, %options) = @_;
+
+ #- take the best choice possible.
+ my ($chosen, $prefered) = find_required_package($urpm, $db, $state, $dep->{required});
+
+ #- If no choice is found, this means that nothing can be possibly selected
+ #- according to $dep, so we need to retry the selection, allowing all
+ #- packages that conflict or anything similar to see which strategy can be
+ #- tried. Backtracking is used to avoid trying multiple times the same
+ #- packages. If multiple packages are possible and properties is not
+ #- empty, postpone the choice for a later time as one of the packages
+ #- may be selected for another reason. Otherwise simply ask the user which
+ #- one to choose; else take the first one available.
+ if (!@$chosen) {
+ $urpm->{debug_URPM}("no packages match " . _dep_to_name($urpm, $dep) . " (it is either in skip.list or already rejected)") if $urpm->{debug_URPM};
+ unshift @$properties, backtrack_selected($urpm, $db, $state, $dep, $diff_provides, %options);
+ return; #- backtrack code choose to continue with same package or completely new strategy.
+ } elsif (@$chosen > 1) {
+ if (@$properties) {
+ unshift @$choices, $dep;
+ return;
+ } elsif ($options{callback_choices}) {
+ my @l = grep { ref $_ } $options{callback_choices}->($urpm, $db, $state, $chosen, _dep_to_name($urpm, $dep), $prefered);
+ $urpm->{debug_URPM}("replacing " . _dep_to_name($urpm, $dep) . " with " .
+ join(' ', map { $_->name } @l)) if $urpm->{debug_URPM};
+ unshift @$properties, map {
+ +{
+ required => $_->id,
+ _choices => $dep->{required},
+ exists $dep->{from} ? (from => $dep->{from}) : @{[]},
+ exists $dep->{requested} ? (requested => $dep->{requested}) : @{[]},
+ };
+ } @l;
+ return; #- always redo according to choices.
+ }
+ }
+
+
+ #- now do the real work, select the package.
+ my $pkg = shift @$chosen;
+ if ($urpm->{debug_URPM} && $pkg->name ne _dep_to_name($urpm, $dep)) {
+ $urpm->{debug_URPM}("chosen " . $pkg->fullname . " for " . _dep_to_name($urpm, $dep));
+ @$chosen and $urpm->{debug_URPM}(" (it could also have chosen " . join(' ', map { scalar $_->fullname } @$chosen));
+ }
+
+ $pkg;
+}
+
+sub pkg2media {
+ my ($mediums, $p) = @_;
+ my $id = $p->id;
+ #- || 0 to avoid undef, but is it normal to have undef ?
+ find { $id >= ($_->{start} || 0) && $id <= ($_->{end} || 0) } @$mediums;
+}
+
+sub whatrequires {
+ my ($urpm, $state, $property_name) = @_;
+
+ map { $urpm->{depslist}[$_] } whatrequires_id($state, $property_name);
+}
+sub whatrequires_id {
+ my ($state, $property_name) = @_;
+
+ keys %{$state->{whatrequires}{$property_name} || {}};
+}
+
+#- return unresolved requires of a package (a new one or an existing one).
+#-
+#- side-effects: none (but uses a $state->{cached_installed})
+sub unsatisfied_requires {
+ my ($urpm, $db, $state, $pkg, %options) = @_;
+ my %unsatisfied;
+
+ #- all requires should be satisfied according to selected packages or installed packages,
+ #- or the package itself.
+ REQUIRES: foreach my $prop ($pkg->requires) {
+ my ($n, $s) = property2name_range($prop) or next;
+
+ if (defined $options{name} && $n ne $options{name}) {
+ #- allow filtering on a given name (to speed up some search).
+ } elsif (exists $unsatisfied{$prop}) {
+ #- avoid recomputing the same all the time.
+ } else {
+ #- check for installed packages in the installed cache.
+ foreach (keys %{$state->{cached_installed}{$n} || {}}) {
+ exists $state->{rejected}{$_} and next;
+ next REQUIRES;
+ }
+
+ #- check on the selected package if a provide is satisfying the resolution (need to do the ops).
+ foreach (grep { exists $state->{selected}{$_} } keys %{$urpm->{provides}{$n} || {}}) {
+ my $p = $urpm->{depslist}[$_];
+ !$urpm->{provides}{$n}{$_} || $p->provides_overlap($prop, 1) and next REQUIRES;
+ }
+
+ #- check if the package itself provides what is necessary.
+ $pkg->provides_overlap($prop) and next REQUIRES;
+
+ #- check on installed system if a package which is not obsoleted is satisfying the require.
+ my $satisfied = 0;
+ if ($n =~ m!^/!) {
+ $db->traverse_tag('path', [ $n ], sub {
+ my ($p) = @_;
+ exists $state->{rejected}{$p->fullname} and return;
+ $state->{cached_installed}{$n}{$p->fullname} = undef;
+ ++$satisfied;
+ });
+ } else {
+ $db->traverse_tag('whatprovides', [ $n ], sub {
+ my ($p) = @_;
+ exists $state->{rejected}{$p->fullname} and return;
+ foreach ($p->provides) {
+ if (my ($pn, $ps) = property2name_range($_)) {
+ $ps or $state->{cached_installed}{$pn}{$p->fullname} = undef;
+ $pn eq $n or next;
+ URPM::ranges_overlap($ps, $s, 1) and ++$satisfied;
+ }
+ }
+ });
+ }
+ #- if nothing can be done, the require should be resolved.
+ $satisfied or $unsatisfied{$prop} = undef;
+ }
+ }
+
+ keys %unsatisfied;
+}
+
+#- this function is "suggests vs requires" safe:
+#- 'whatrequires' will give both requires & suggests, but unsatisfied_requires
+#- will check $p->requires and so filter out suggests
+
+#- side-effects: only those done by $do
+sub with_db_unsatisfied_requires {
+ my ($urpm, $db, $state, $name, $do) = @_;
+
+ $db->traverse_tag('whatrequires', [ $name ], sub {
+ my ($p) = @_;
+ if (my @l = unsatisfied_requires($urpm, $db, $state, $p, name => $name)) {
+ $urpm->{debug_URPM}("installed " . $p->fullname . " is conflicting because of unsatisfied @l") if $urpm->{debug_URPM};
+ $do->($p, @l);
+ }
+ });
+}
+
+#- side-effects: only those done by $do
+sub with_state_unsatisfied_requires {
+ my ($urpm, $db, $state, $name, $do) = @_;
+
+ foreach (whatrequires_id($state, $name)) {
+ $state->{selected}{$_} or next;
+ my $p = $urpm->{depslist}[$_];
+ if (my @l = unsatisfied_requires($urpm, $db, $state, $p, name => $name)) {
+ $urpm->{debug_URPM}("selected " . $p->fullname . " is conflicting because of unsatisfied @l") if $urpm->{debug_URPM};
+ $do->($p, @l);
+ }
+ }
+}
+
+sub with_any_unsatisfied_requires {
+ my ($urpm, $db, $state, $name, $do) = @_;
+ with_db_unsatisfied_requires($urpm, $db, $state, $name, sub { my ($p, @l) = @_; $do->($p, 0, @l)});
+ with_state_unsatisfied_requires($urpm, $db, $state, $name, sub { my ($p, @l) = @_; $do->($p, 1, @l)});
+}
+
+
+# used when a require is not available
+#
+#- side-effects: $state->{backtrack}, $state->{selected}
+#- + those of disable_selected_and_unrequested_dependencies ($state->{whatrequires}, flag_requested, flag_required)
+#- + those of _set_rejected_from ($state->{rejected})
+#- + those of set_rejected_and_compute_diff_provides ($state->{rejected}, $diff_provides_h)
+#- + those of _add_rejected_backtrack ($state->{rejected})
+sub backtrack_selected {
+ my ($urpm, $db, $state, $dep, $diff_provides, %options) = @_;
+
+ if (defined $dep->{required}) {
+ #- avoid deadlock here...
+ if (!exists $state->{backtrack}{deadlock}{$dep->{required}}) {
+ $state->{backtrack}{deadlock}{$dep->{required}} = undef;
+
+ #- search for all possible packages, first is to try the selection, then if it is
+ #- impossible, backtrack the origin.
+ my @packages = find_candidate_packages_($urpm, $dep->{required});
+
+ foreach (@packages) {
+ #- avoid dead loop.
+ exists $state->{backtrack}{selected}{$_->id} and next;
+ #- a package if found is problably rejected or there is a problem.
+ if ($state->{rejected}{$_->fullname}) {
+ #- keep in mind a backtrack has happening here...
+ exists $dep->{promote} and _add_rejected_backtrack($state, $_, { promote => [ $dep->{promote} ] });
+
+ my $closure = $state->{rejected}{$_->fullname}{closure} || {};
+ foreach my $p (grep { exists $closure->{$_}{avoid} } keys %$closure) {
+ _add_rejected_backtrack($state, $_, { conflicts => [ $p ] })
+ }
+ #- backtrack callback should return a strictly positive value if the selection of the new
+ #- package is prefered over the currently selected package.
+ next;
+ }
+ $state->{backtrack}{selected}{$_->id} = undef;
+
+ #- in such case, we need to drop the problem caused so that rejected condition is removed.
+ #- if this is not possible, the next backtrack on the same package will be refused above.
+ my @l = map { $urpm->search($_, strict_fullname => 1) }
+ keys %{($state->{rejected}{$_->fullname} || {})->{closure}};
+
+ disable_selected_and_unrequested_dependencies($urpm, $db, $state, @l);
+
+ return { required => $_->id,
+ exists $dep->{from} ? (from => $dep->{from}) : @{[]},
+ exists $dep->{requested} ? (requested => $dep->{requested}) : @{[]},
+ exists $dep->{promote} ? (promote => $dep->{promote}) : @{[]},
+ exists $dep->{psel} ? (psel => $dep->{psel}) : @{[]},
+ };
+ }
+ }
+ }
+
+ if (defined $dep->{from}) {
+ if ($options{nodeps}) {
+ #- try to keep unsatisfied dependencies in requested.
+ if ($dep->{required} && exists $state->{selected}{$dep->{from}->id}) {
+ push @{$state->{selected}{$dep->{from}->id}{unsatisfied}}, $dep->{required};
+ }
+ } else {
+ #- at this point, dep cannot be resolved, this means we need to disable
+ #- all selection tree, re-enabling removed and obsoleted packages as well.
+ unless (exists $state->{rejected}{$dep->{from}->fullname}) {
+ #- package is not currently rejected, compute the closure now.
+ my @l = disable_selected_and_unrequested_dependencies($urpm, $db, $state, $dep->{from});
+ foreach (@l) {
+ #- disable all these packages in order to avoid selecting them again.
+ _set_rejected_from($state, $_, $dep->{from});
+ }
+ }
+ #- the package is already rejected, we assume we can add another reason here!
+ $urpm->{debug_URPM}("adding a reason to already rejected package " . $dep->{from}->fullname . ": unsatisfied " . $dep->{required}) if $urpm->{debug_URPM};
+
+ _add_rejected_backtrack($state, $dep->{from}, { unsatisfied => [ $dep->{required} ] });
+ }
+ }
+
+ my @properties;
+ if (defined $dep->{psel}) {
+ if ($options{keep}) {
+ backtrack_selected_psel_keep($urpm, $db, $state, $dep->{psel}, $dep->{keep});
+
+ #- the package is already rejected, we assume we can add another reason here!
+ defined $dep->{promote} and _add_rejected_backtrack($state, $dep->{psel}, { promote => [ $dep->{promote} ] });
+ } else {
+ #- the backtrack need to examine diff_provides promotion on $n.
+ with_db_unsatisfied_requires($urpm, $db, $state, $dep->{promote}, sub {
+ my ($p, @unsatisfied) = @_;
+ my %diff_provides_h;
+ set_rejected_and_compute_diff_provides($urpm, $state, \%diff_provides_h, {
+ rejected_pkg => $p, removed => 1,
+ from => $dep->{psel},
+ why => { unsatisfied => \@unsatisfied }
+ });
+ push @$diff_provides, map { +{ name => $_, pkg => $dep->{psel} } } keys %diff_provides_h;
+ });
+ with_state_unsatisfied_requires($urpm, $db, $state, $dep->{promote}, sub {
+ my ($p) = @_;
+ _set_rejected_from($state, $p, $dep->{psel});
+ disable_selected_and_unrequested_dependencies($urpm, $db, $state, $p);
+ });
+ }
+ }
+
+ #- some packages may have been removed because of selection of this one.
+ #- the rejected flags should have been cleaned by disable_selected above.
+ @properties;
+}
+
+#- side-effects:
+#- + those of _set_rejected_from ($state->{rejected})
+#- + those of _add_rejected_backtrack ($state->{rejected})
+#- + those of disable_selected_and_unrequested_dependencies ($state->{selected}, $state->{whatrequires}, flag_requested, flag_required)
+sub backtrack_selected_psel_keep {
+ my ($urpm, $db, $state, $psel, $keep) = @_;
+
+ #- we shouldn't try to remove packages, so psel which leads to this need to be unselected.
+ unless (exists $state->{rejected}{$psel->fullname}) {
+ #- package is not currently rejected, compute the closure now.
+ my @l = disable_selected_and_unrequested_dependencies($urpm, $db, $state, $psel);
+ foreach (@l) {
+ #- disable all these packages in order to avoid selecting them again.
+ _set_rejected_from($state, $_, $psel);
+ }
+ }
+ #- to simplify, a reference to list or standalone elements may be set in keep.
+ $keep and _add_rejected_backtrack($state, $psel, { keep => $keep });
+}
+
+#- side-effects: $state->{rejected}
+sub _remove_all_rejected_from {
+ my ($state, $from_fullname) = @_;
+
+ grep {
+ _remove_rejected_from($state, $_, $from_fullname);
+ } keys %{$state->{rejected}};
+}
+
+#- side-effects: $state->{rejected}
+sub _remove_rejected_from {
+ my ($state, $fullname, $from_fullname) = @_;
+
+ my $rv = $state->{rejected}{$fullname} or return;
+
+ foreach (qw(removed obsoleted)) {
+ if (exists $rv->{$_} && exists $rv->{$_}{$from_fullname}) {
+ delete $rv->{$_}{$from_fullname};
+ delete $rv->{$_} if !%{$rv->{$_}};
+ }
+ }
+
+ exists $rv->{closure}{$from_fullname} or return;
+ delete $rv->{closure}{$from_fullname};
+ if (%{$rv->{closure}}) {
+ 0;
+ } else {
+ delete $state->{rejected}{$fullname};
+ 1;
+ }
+}
+
+#- side-effects: $state->{rejected}
+sub _add_rejected_backtrack {
+ my ($state, $pkg, $backtrack) = @_;
+
+ my $bt = $state->{rejected}{$pkg->fullname}{backtrack} ||= {};
+
+ foreach (keys %$backtrack) {
+ push @{$bt->{$_}}, @{$backtrack->{$_}};
+ }
+}
+
+#- useful to reject packages in advance
+#- eg when selecting "a" which conflict with "b", ensure we won't select "b"
+#- but it's somewhat dangerous because it's sometimes called on installed packages,
+#- and in that case, a real resolve_rejected_ must be done
+#- (that's why set_rejected ignores the effect of _set_rejected_from)
+#-
+#- side-effects: $state->{rejected}
+sub _set_rejected_from {
+ my ($state, $pkg, $from_pkg) = @_;
+
+ $pkg->fullname ne $from_pkg->fullname or return;
+
+ $state->{rejected}{$pkg->fullname}{closure}{$from_pkg->fullname}{avoid} ||= undef;
+}
+
+#- side-effects: $state->{rejected}
+sub _set_rejected_old_package {
+ my ($state, $pkg, $new_pkg) = @_;
+
+ if ($pkg->fullname eq $new_pkg->fullname) {
+ $state->{rejected_already_installed}{$pkg->id} = $pkg;
+ } else {
+ push @{$state->{rejected}{$pkg->fullname}{backtrack}{keep}}, scalar $new_pkg->fullname;
+ }
+}
+
+#- side-effects: $state->{rejected}
+sub set_rejected {
+ my ($urpm, $state, $rdep) = @_;
+
+ my $fullname = $rdep->{rejected_pkg}->fullname;
+ my $rv = $state->{rejected}{$fullname} ||= {};
+
+ my $newly_rejected = !exists $rv->{size};
+
+ if ($newly_rejected) {
+ $urpm->{debug_URPM}("set_rejected: $fullname") if $urpm->{debug_URPM};
+ #- keep track of size of package which are finally removed.
+ $rv->{size} = $rdep->{rejected_pkg}->size;
+ }
+
+ #- keep track of what causes closure.
+ if ($rdep->{from}) {
+ my $closure = $rv->{closure}{scalar $rdep->{from}->fullname} ||= {};
+ if (my $l = delete $rdep->{why}{unsatisfied}) {
+ my $unsatisfied = $closure->{unsatisfied} ||= [];
+ @$unsatisfied = uniq(@$unsatisfied, @$l);
+ }
+ $closure->{$_} = $rdep->{why}{$_} foreach keys %{$rdep->{why}};
+ }
+
+ #- set removed and obsoleted level.
+ foreach (qw(removed obsoleted)) {
+ if ($rdep->{$_}) {
+ if ($rdep->{from}) {
+ $rv->{$_}{scalar $rdep->{from}->fullname} = undef;
+ } else {
+ $rv->{$_}{asked} = undef;
+ }
+ }
+ }
+
+ $newly_rejected;
+}
+
+#- side-effects:
+#- + those of set_rejected ($state->{rejected})
+#- + those of _compute_diff_provides_of_removed_pkg ($diff_provides_h)
+sub set_rejected_and_compute_diff_provides {
+ my ($urpm, $state, $diff_provides_h, $rdep) = @_;
+
+ my $newly_rejected = set_rejected($urpm, $state, $rdep);
+
+ #- no need to compute diff_provides if package was already rejected
+ $newly_rejected or return;
+
+ _compute_diff_provides_of_removed_pkg($urpm, $state, $diff_provides_h, $rdep->{rejected_pkg});
+}
+
+#- see resolve_rejected_ below
+sub resolve_rejected {
+ my ($urpm, $db, $state, $pkg, %rdep) = @_;
+ $rdep{rejected_pkg} = $pkg;
+ resolve_rejected_($urpm, $db, $state, $rdep{unsatisfied}, \%rdep);
+}
+
+#- close rejected (as urpme previously) for package to be removable without error.
+#-
+#- side-effects: $properties
+#- + those of set_rejected ($state->{rejected})
+sub resolve_rejected_ {
+ my ($urpm, $db, $state, $properties, $rdep) = @_;
+
+ $urpm->{debug_URPM}("resolve_rejected: " . $rdep->{rejected_pkg}->fullname) if $urpm->{debug_URPM};
+
+ #- check if the package has already been asked to be rejected (removed or obsoleted).
+ #- this means only add the new reason and return.
+ my $newly_rejected = set_rejected($urpm, $state, $rdep);
+
+ $newly_rejected or return;
+
+ my @pkgs_todo = $rdep->{rejected_pkg};
+
+ while (my $cp = shift @pkgs_todo) {
+ #- close what requires this property, but check with selected package requiring old properties.
+ foreach my $n ($cp->provides_nosense) {
+ foreach my $pkg (whatrequires($urpm, $state, $n)) {
+ if (my @l = unsatisfied_requires($urpm, $db, $state, $pkg, name => $n)) {
+ #- a selected package requires something that is no more available
+ #- and should be tried to be re-selected if possible.
+ if ($properties) {
+ push @$properties, map {
+ { required => $_, rejected => scalar $pkg->fullname }; # rejected is only there for debugging purpose (??)
+ } @l;
+ }
+ }
+ }
+ with_db_unsatisfied_requires($urpm, $db, $state, $n, sub {
+ my ($p, @unsatisfied) = @_;
+
+ my $newly_rejected = set_rejected($urpm, $state, {
+ rejected_pkg => $p,
+ from => $rdep->{rejected_pkg},
+ why => { unsatisfied => \@unsatisfied },
+ obsoleted => $rdep->{obsoleted},
+ removed => $rdep->{removed},
+ });
+
+ #- continue the closure unless already examined.
+ $newly_rejected or return;
+
+ $p->pack_header; #- need to pack else package is no longer visible...
+ push @pkgs_todo, $p;
+ });
+ }
+ }
+}
+
+# see resolve_requested__no_suggests below for information about usage
+sub resolve_requested {
+ my ($urpm, $db, $state, $requested, %options) = @_;
+
+ my @selected = resolve_requested__no_suggests($urpm, $db, $state, $requested, %options);
+
+ if (!$options{no_suggests}) {
+ my @todo = @selected;
+ while (@todo) {
+ my $pkg = shift @todo;
+ my %suggests = map { $_ => 1 } $pkg->suggests or next;
+
+ #- do not install a package that has already been suggested
+ $db->traverse_tag('name', [ $pkg->name ], sub {
+ my ($p) = @_;
+ delete $suggests{$_} foreach $p->suggests;
+ });
+
+ # workaround: if you do "urpmi virtual_pkg" and one virtual_pkg is already installed,
+ # it will ask anyway for the other choices
+ foreach my $suggest (keys %suggests) {
+ $db->traverse_tag('whatprovides', [ $suggest ], sub {
+ delete $suggests{$suggest};
+ });
+ }
+
+ %suggests or next;
+
+ $urpm->{debug_URPM}("requested " . join(', ', keys %suggests) . " suggested by " . $pkg->fullname) if $urpm->{debug_URPM};
+
+ my %new_requested = map { $_ => undef } keys %suggests;
+ my @new_selected = resolve_requested__no_suggests_($urpm, $db, $state, \%new_requested, %options);
+ $state->{selected}{$_->id}{suggested} = 1 foreach @new_selected;
+ push @selected, @new_selected;
+ push @todo, @new_selected;
+ }
+ }
+ @selected;
+}
+
+#- Resolve dependencies of requested packages; keep resolution state to
+#- speed up process.
+#- A requested package is marked to be installed; once done, an upgrade flag or
+#- an installed flag is set according to the needs of the installation of this
+#- package.
+#- Other required packages will have a required flag set along with an upgrade
+#- flag or an installed flag.
+#- Base flag should always be "installed" or "upgraded".
+#- The following options are recognized :
+#- callback_choices : subroutine to be called to ask the user to choose
+#- between several possible packages. Returns an array of URPM::Package
+#- objects, or an empty list eventually.
+#- keep :
+#- nodeps :
+#-
+#- side-effects: flag_requested
+#- + those of resolve_requested__no_suggests_
+sub resolve_requested__no_suggests {
+ my ($urpm, $db, $state, $requested, %options) = @_;
+
+ foreach (keys %$requested) {
+ #- keep track of requested packages by propating the flag.
+ foreach (find_candidate_packages_($urpm, $_)) {
+ $_->set_flag_requested;
+ }
+ }
+
+ resolve_requested__no_suggests_($urpm, $db, $state, $requested, %options);
+}
+
+# same as resolve_requested__no_suggests, but do not modify requested_flag
+#-
+#- side-effects: $state->{selected}, flag_required, flag_installed, flag_upgrade
+#- + those of backtrack_selected (flag_requested, $state->{rejected}, $state->{whatrequires}, $state->{backtrack})
+#- + those of _unselect_package_deprecated_by (flag_requested, $state->{rejected}, $state->{whatrequires}, $state->{oldpackage}, $state->{unselected_uninstalled})
+#- + those of _handle_conflicts ($state->{rejected})
+#- + those of _handle_conflict ($state->{rejected})
+#- + those of backtrack_selected_psel_keep (flag_requested, $state->{whatrequires})
+#- + those of _handle_diff_provides (flag_requested, $state->{rejected}, $state->{whatrequires})
+#- + those of _no_more_recent_installed_and_providing ($state->{rejected})
+sub resolve_requested__no_suggests_ {
+ my ($urpm, $db, $state, $requested, %options) = @_;
+
+ my @properties = map {
+ { required => $_, requested => $requested->{$_} };
+ } keys %$requested;
+
+ my (@diff_provides, @selected, @choices);
+
+ #- for each dep property evaluated, examine which package will be obsoleted on $db,
+ #- then examine provides that will be removed (which need to be satisfied by another
+ #- package present or by a new package to upgrade), then requires not satisfied and
+ #- finally conflicts that will force a new upgrade or a remove.
+ do {
+ while (my $dep = shift @properties) {
+ #- we need to avoid selecting packages if the source has been disabled.
+ if (exists $dep->{from} && !$urpm->{keep_unrequested_dependencies}) {
+ exists $state->{selected}{$dep->{from}->id} or next;
+ }
+
+ my $pkg = _choose_required($urpm, $db, $state, $dep, \@properties, \@choices, \@diff_provides, %options) or next;
+
+ !$pkg || exists $state->{selected}{$pkg->id} and next;
+
+ if ($pkg->arch eq 'src') {
+ $pkg->set_flag_upgrade;
+ } else {
+ _set_flag_installed_and_upgrade_if_no_newer($db, $pkg);
+
+ if ($pkg->flag_installed && !$pkg->flag_upgrade) {
+ _no_more_recent_installed_and_providing($urpm, $db, $state, $pkg, $dep->{required}) or next;
+ }
+ }
+
+ _handle_conflicts_with_selected($urpm, $db, $state, $pkg, $dep, \@properties, \@diff_provides, %options) or next;
+
+ $urpm->{debug_URPM}("selecting " . $pkg->fullname) if $urpm->{debug_URPM};
+
+ #- keep in mind the package has be selected, remove the entry in requested input hash,
+ #- this means required dependencies have undef value in selected hash.
+ #- requested flag is set only for requested package where value is not false.
+ push @selected, $pkg;
+ $state->{selected}{$pkg->id} = { exists $dep->{requested} ? (requested => $dep->{requested}) : @{[]},
+ exists $dep->{from} ? (from => $dep->{from}) : @{[]},
+ exists $dep->{promote} ? (promote => $dep->{promote}) : @{[]},
+ exists $dep->{psel} ? (psel => $dep->{psel}) : @{[]},
+ $pkg->flag_disable_obsolete ? (install => 1) : @{[]},
+ };
+
+ $pkg->set_flag_required;
+
+ #- check if the package is not already installed before trying to use it, compute
+ #- obsoleted packages too. This is valable only for non source packages.
+ my %diff_provides_h;
+ if ($pkg->arch ne 'src' && !$pkg->flag_disable_obsolete) {
+ _unselect_package_deprecated_by($urpm, $db, $state, \%diff_provides_h, $pkg);
+ }
+
+ #- all requires should be satisfied according to selected package, or installed packages.
+ if (my @l = unsatisfied_requires($urpm, $db, $state, $pkg)) {
+ $urpm->{debug_URPM}("requiring " . join(',', sort @l) . " for " . $pkg->fullname) if $urpm->{debug_URPM};
+ unshift @properties, map { +{ required => $_, from => $pkg,
+ exists $dep->{promote} ? (promote => $dep->{promote}) : @{[]},
+ exists $dep->{psel} ? (psel => $dep->{psel}) : @{[]},
+ } } @l;
+ }
+
+ #- keep in mind what is requiring each item (for unselect to work).
+ foreach ($pkg->requires_nosense) {
+ $state->{whatrequires}{$_}{$pkg->id} = undef;
+ }
+
+ #- cancel flag if this package should be cancelled but too late (typically keep options).
+ my @keep;
+
+ _handle_conflicts($urpm, $db, $state, $pkg, \@properties, \%diff_provides_h, $options{keep} && \@keep);
+
+ #- examine if an existing package does not conflict with this one.
+ $db->traverse_tag('whatconflicts', [ $pkg->provides_nosense ], sub {
+ @keep and return;
+ my ($p) = @_;
+ foreach my $property ($p->conflicts) {
+ if ($pkg->provides_overlap($property)) {
+ _handle_conflict($urpm, $state, $pkg, $p, $property, $property, \@properties, \%diff_provides_h, $options{keep} && \@keep);
+ }
+ }
+ });
+
+ #- keep existing package and therefore cancel current one.
+ if (@keep) {
+ backtrack_selected_psel_keep($urpm, $db, $state, $pkg, \@keep);
+ }
+
+ push @diff_provides, map { +{ name => $_, pkg => $pkg } } keys %diff_provides_h;
+ }
+ if (my $diff = shift @diff_provides) {
+ _handle_diff_provides($urpm, $db, $state, \@properties, \@diff_provides, $diff->{name}, $diff->{pkg}, %options);
+ } elsif (my $dep = shift @choices) {
+ push @properties, $dep;
+ }
+ } while @diff_provides || @properties || @choices;
+
+ #- return what has been selected by this call (not all selected hash which may be not empty
+ #- previously. avoid returning rejected packages which weren't selectable.
+ grep { exists $state->{selected}{$_->id} } @selected;
+}
+
+#- pre-disables packages that $pkg has conflict entries for, and
+#- unselects $pkg if such a package is already selected
+#- side-effects:
+#- + those of _set_rejected_from ($state->{rejected})
+#- + those of _remove_all_rejected_from ($state->{rejected})
+#- + those of backtrack_selected ($state->{backtrack}, $state->{rejected}, $state->{selected}, $state->{whatrequires}, flag_requested, flag_required)
+sub _handle_conflicts_with_selected {
+ my ($urpm, $db, $state, $pkg, $dep, $properties, $diff_provides, %options) = @_;
+ foreach ($pkg->conflicts) {
+ if (my ($n, $o, $v) = property2name_op_version($_)) {
+ foreach my $p ($urpm->packages_providing($n)) {
+ $pkg == $p and next;
+ $p->provides_overlap($_) or next;
+ if (exists $state->{selected}{$p->id}) {
+ $urpm->{debug_URPM}($pkg->fullname . " conflicts with already selected package " . $p->fullname) if $urpm->{debug_URPM};
+ _remove_all_rejected_from($state, $pkg);
+ _set_rejected_from($state, $pkg, $p);
+ unshift @$properties, backtrack_selected($urpm, $db, $state, $dep, $diff_provides, %options);
+ return;
+ }
+ _set_rejected_from($state, $p, $pkg);
+ }
+ }
+ }
+ 1;
+}
+
+#- side-effects:
+#- + those of set_rejected_and_compute_diff_provides ($state->{rejected}, $diff_provides_h)
+#- + those of _handle_conflict ($properties, $keep, $diff_provides_h)
+sub _handle_conflicts {
+ my ($urpm, $db, $state, $pkg, $properties, $diff_provides_h, $keep) = @_;
+
+ #- examine conflicts, an existing package conflicting with this selection should
+ #- be upgraded to a new version which will be safe, else it should be removed.
+ foreach ($pkg->conflicts) {
+ $keep && @$keep and last;
+ if (my ($file) = m!^(/[^\s\[]*)!) {
+ $db->traverse_tag('path', [ $file ], sub {
+ $keep && @$keep and return;
+ my ($p) = @_;
+ if ($keep) {
+ push @$keep, scalar $p->fullname;
+ } else {
+ #- all these package should be removed.
+ set_rejected_and_compute_diff_provides($urpm, $state, $diff_provides_h, {
+ rejected_pkg => $p, removed => 1,
+ from => $pkg,
+ why => { conflicts => $file },
+ });
+ }
+ });
+ } elsif (my $name = property2name($_)) {
+ my $property = $_;
+ $db->traverse_tag('whatprovides', [ $name ], sub {
+ $keep && @$keep and return;
+ my ($p) = @_;
+ if ($p->provides_overlap($property)) {
+ _handle_conflict($urpm, $state, $pkg, $p, $property, scalar($pkg->fullname), $properties, $diff_provides_h, $keep);
+ }
+ });
+ }
+ }
+}
+
+#- side-effects:
+#- + those of _unselect_package_deprecated_by_property (flag_requested, flag_required, $state->{selected}, $state->{rejected}, $state->{whatrequires}, $state->{oldpackage}, $state->{unselected_uninstalled})
+sub _unselect_package_deprecated_by {
+ my ($urpm, $db, $state, $diff_provides_h, $pkg) = @_;
+
+ _unselect_package_deprecated_by_property($urpm, $db, $state, $pkg, $diff_provides_h, $pkg->name, '<', $pkg->epoch . ":" . $pkg->version . "-" . $pkg->release);
+
+ foreach ($pkg->obsoletes) {
+ my ($n, $o, $v) = property2name_op_version($_) or next;
+
+ #- ignore if this package obsoletes itself
+ #- otherwise this can cause havoc if: to_install=v3, installed=v2, v3 obsoletes < v2
+ if ($n ne $pkg->name) {
+ _unselect_package_deprecated_by_property($urpm, $db, $state, $pkg, $diff_provides_h, $n, $o, $v);
+ }
+ }
+}
+
+#- side-effects: $state->{oldpackage}, $state->{unselected_uninstalled}
+#- + those of set_rejected ($state->{rejected})
+#- + those of _set_rejected_from ($state->{rejected})
+#- + those of disable_selected (flag_requested, flag_required, $state->{selected}, $state->{rejected}, $state->{whatrequires})
+sub _unselect_package_deprecated_by_property {
+ my ($urpm, $db, $state, $pkg, $diff_provides_h, $n, $o, $v) = @_;
+
+ #- populate avoided entries according to what is selected.
+ foreach my $p ($urpm->packages_providing($n)) {
+ if ($p->name eq $pkg->name) {
+ #- all packages with the same name should now be avoided except when chosen.
+ } else {
+ #- in case of obsoletes, keep track of what should be avoided
+ #- but only if package name equals the obsolete name.
+ $p->name eq $n && (!$o || eval($p->compare($v) . $o . 0)) or next;
+ }
+ #- these packages are not yet selected, if they happen to be selected,
+ #- they must first be unselected.
+ _set_rejected_from($state, $p, $pkg);
+ }
+
+ #- examine rpm db too (but only according to package names as a fix in rpm itself)
+ $db->traverse_tag('name', [ $n ], sub {
+ my ($p) = @_;
+
+ #- without an operator, anything (with the same name) is matched.
+ #- with an operator, check package EVR with the obsoletes EVR.
+ #- $satisfied is true if installed package has version newer or equal.
+ my $comparison = $p->compare($v);
+ my $satisfied = !$o || eval($comparison . $o . 0);
+
+ my $obsoleted;
+ if ($p->name eq $pkg->name) {
+ #- all packages older than the current one are obsoleted,
+ #- the others are simply removed (the result is the same).
+ if ($o && $comparison > 0) {
+ #- installed package is newer
+ #- remove this package from the list of packages to install,
+ #- unless urpmi was invoked with --allow-force (in which
+ #- case rpm could be invoked with --oldpackage)
+ if (!$urpm->{options}{'allow-force'}) {
+ #- since the originally requested packages (or other
+ #- non-installed ones) could be unselected by the following
+ #- operation, remember them, to warn the user
+ $state->{unselected_uninstalled} = [ grep {
+ !$_->flag_installed;
+ } disable_selected($urpm, $db, $state, $pkg) ];
+
+ return;
+ }
+ } elsif ($satisfied) {
+ $obsoleted = 1;
+ }
+ } elsif ($satisfied) {
+ $obsoleted = 1;
+ } else {
+ return;
+ }
+
+ set_rejected_and_compute_diff_provides($urpm, $state, $diff_provides_h, {
+ rejected_pkg => $p,
+ obsoleted => $obsoleted, removed => !$obsoleted,
+ from => $pkg, why => $obsoleted ? undef : { old_requested => 1 },
+ });
+ $obsoleted or ++$state->{oldpackage};
+ });
+}
+
+#- side-effects: $diff_provides
+sub _compute_diff_provides_of_removed_pkg {
+ my ($urpm, $state, $diff_provides_h, $p) = @_;
+
+ foreach ($p->provides) {
+ #- check differential provides between obsoleted package and newer one.
+ my ($pn, $ps) = property2name_range($_) or next;
+
+ my $not_provided = 1;
+ foreach (grep { exists $state->{selected}{$_} }
+ keys %{$urpm->{provides}{$pn} || {}}) {
+ my $pp = $urpm->{depslist}[$_];
+ foreach ($pp->provides) {
+ my ($ppn, $pps) = property2name_range($_) or next;
+ $ppn eq $pn && $pps eq $ps
+ and $not_provided = 0;
+ }
+ }
+ $not_provided and $diff_provides_h->{$pn} = undef;
+ }
+}
+
+#- side-effects: none
+sub _find_packages_obsoleting {
+ my ($urpm, $state, $p) = @_;
+
+ grep {
+ !$_->flag_skip
+ && $_->is_arch_compat
+ && !exists $state->{rejected}{$_->fullname}
+ && $_->obsoletes_overlap($p->name . " == " . $p->epoch . ":" . $p->version . "-" . $p->release)
+ && $_->fullname ne $p->fullname
+ && (!strict_arch($urpm) || strict_arch_check($p, $_));
+ } $urpm->packages_obsoleting($p->name);
+}
+
+#- side-effects: $properties
+#- + those of backtrack_selected_psel_keep ($state->{rejected}, $state->{selected}, $state->{whatrequires}, flag_requested, flag_required)
+#- + those of resolve_rejected_ ($state->{rejected}, $properties)
+#- + those of disable_selected_and_unrequested_dependencies (flag_requested, flag_required, $state->{selected}, $state->{whatrequires}, $state->{rejected})
+#- + those of _set_rejected_from ($state->{rejected})
+sub _handle_diff_provides {
+ my ($urpm, $db, $state, $properties, $diff_provides, $n, $pkg, %options) = @_;
+
+ with_any_unsatisfied_requires($urpm, $db, $state, $n, sub {
+ my ($p, $from_state, @unsatisfied) = @_;
+
+ #- try if upgrading the package will be satisfying all the requires...
+ #- there is no need to avoid promoting epoch as the package examined is not
+ #- already installed.
+ my @packages = find_candidate_packages_($urpm, $p->name, $state->{rejected});
+ @packages =
+ grep { ($_->name eq $p->name ? $p->compare_pkg($_) < 0 :
+ $_->obsoletes_overlap($p->name . " == " . $p->epoch . ":" . $p->version . "-" . $p->release))
+ && (!strict_arch($urpm) || strict_arch_check($p, $_))
+ } @packages;
+
+ if (!@packages) {
+ @packages = _find_packages_obsoleting($urpm, $state, $p);
+ }
+
+ if (@packages) {
+ my $best = join('|', map { $_->id } @packages);
+ $urpm->{debug_URPM}("promoting " . $urpm->{depslist}[$best]->fullname . " because of conflict above") if $urpm->{debug_URPM};
+ push @$properties, { required => $best, promote => $n, psel => $pkg };
+ } else {
+ #- no package have been found, we may need to remove the package examined unless
+ #- there exists enough packages that provided the unsatisfied requires.
+ my @best;
+ foreach (@unsatisfied) {
+ my @packages = find_candidate_packages_($urpm, $_, $state->{rejected});
+ if (@packages = grep { $_->fullname ne $p->fullname } @packages) {
+ push @best, join('|', map { $_->id } @packages);
+ }
+ }
+
+ if (@best == @unsatisfied) {
+ $urpm->{debug_URPM}("promoting " . join(' ', _ids_to_fullnames($urpm, @best)) . " because of conflict above") if $urpm->{debug_URPM};
+ push @$properties, map { +{ required => $_, promote => $n, psel => $pkg } } @best;
+ } else {
+ if ($from_state) {
+ disable_selected_and_unrequested_dependencies($urpm, $db, $state, $p);
+ _set_rejected_from($state, $p, $pkg);
+ } elsif ($options{keep}) {
+ backtrack_selected_psel_keep($urpm, $db, $state, $pkg, [ scalar $p->fullname ]);
+ } else {
+ my %diff_provides_h;
+ set_rejected_and_compute_diff_provides($urpm, $state, \%diff_provides_h, {
+ rejected_pkg => $p, removed => 1,
+ from => $pkg,
+ why => { unsatisfied => \@unsatisfied },
+ });
+ push @$diff_provides, map { +{ name => $_, pkg => $pkg } } keys %diff_provides_h;
+ }
+ }
+ }
+ });
+}
+
+#- side-effects: $properties, $keep
+#- + those of set_rejected_and_compute_diff_provides ($state->{rejected}, $diff_provides_h)
+sub _handle_conflict {
+ my ($urpm, $state, $pkg, $p, $property, $reason, $properties, $diff_provides_h, $keep) = @_;
+
+ $urpm->{debug_URPM}("installed package " . $p->fullname . " is conflicting with " . $pkg->fullname . " (Conflicts: $property)") if $urpm->{debug_URPM};
+
+ #- the existing package will conflict with the selection; check
+ #- whether a newer version will be ok, else ask to remove the old.
+ my $need_deps = $p->name . " > " . ($p->epoch ? $p->epoch . ":" : "") .
+ $p->version . "-" . $p->release;
+ my @packages = grep { $_->name eq $p->name } find_candidate_packages_($urpm, $need_deps, $state->{rejected});
+ @packages = grep { ! $_->provides_overlap($property) } @packages;
+
+ if (!@packages) {
+ @packages = _find_packages_obsoleting($urpm, $state, $p);
+ @packages = grep { ! $_->provides_overlap($property) } @packages;
+ }
+
+ if (@packages) {
+ my $best = join('|', map { $_->id } @packages);
+ $urpm->{debug_URPM}("promoting " . join('|', map { scalar $_->fullname } @packages) . " because of conflict above") if $urpm->{debug_URPM};
+ unshift @$properties, { required => $best, promote_conflicts => $reason };
+ } else {
+ if ($keep) {
+ push @$keep, scalar $p->fullname;
+ } else {
+ #- no package has been found, we need to remove the package examined.
+ set_rejected_and_compute_diff_provides($urpm, $state, $diff_provides_h, {
+ rejected_pkg => $p, removed => 1,
+ from => $pkg,
+ why => { conflicts => $reason },
+ });
+ }
+ }
+}
+
+#- side-effects: none
+sub _dep_to_name {
+ my ($urpm, $dep) = @_;
+ join('|', map { _id_to_name($urpm, $_) } split('\|', $dep->{required}));
+}
+#- side-effects: none
+sub _id_to_name {
+ my ($urpm, $id_prop) = @_;
+ if ($id_prop =~ /^\d+/) {
+ my $pkg = $urpm->{depslist}[$id_prop];
+ $pkg && $pkg->name;
+ } else {
+ $id_prop;
+ }
+}
+#- side-effects: none
+sub _ids_to_names {
+ my $urpm = shift;
+
+ map { $urpm->{depslist}[$_]->name } @_;
+}
+#- side-effects: none
+sub _ids_to_fullnames {
+ my $urpm = shift;
+
+ map { scalar $urpm->{depslist}[$_]->fullname } @_;
+}
+
+#- side-effects: flag_installed, flag_upgrade
+sub _set_flag_installed_and_upgrade_if_no_newer {
+ my ($db, $pkg) = @_;
+
+ !$pkg->flag_upgrade && !$pkg->flag_installed or return;
+
+ my $upgrade = 1;
+ $db->traverse_tag('name', [ $pkg->name ], sub {
+ my ($p) = @_;
+ $pkg->set_flag_installed;
+ $upgrade &&= $pkg->compare_pkg($p) > 0;
+ });
+ $pkg->set_flag_upgrade($upgrade);
+}
+
+#- side-effects:
+#- + those of _set_rejected_old_package ($state->{rejected})
+sub _no_more_recent_installed_and_providing {
+ my ($urpm, $db, $state, $pkg, $required) = @_;
+
+ my $allow = 1;
+ $db->traverse_tag('name', [ $pkg->name ], sub {
+ my ($p) = @_;
+ #- allow if a less recent package is installed,
+ if ($allow && $pkg->compare_pkg($p) <= 0) {
+ if ($required =~ /^\d+/ || $p->provides_overlap($required)) {
+ $urpm->{debug_URPM}("not selecting " . $pkg->fullname . " since the more recent " . $p->fullname . " is installed") if $urpm->{debug_URPM};
+ _set_rejected_old_package($state, $pkg, $p);
+ $allow = 0;
+ } else {
+ $urpm->{debug_URPM}("the more recent " . $p->fullname .
+ " is installed, but does not provide $required whereas " .
+ $pkg->fullname . " does") if $urpm->{debug_URPM};
+ }
+ }
+ });
+ $allow;
+}
+
+#- do the opposite of the resolve_requested:
+#- unselect a package and extend to any package not requested that is no
+#- longer needed by any other package.
+#- return the packages that have been deselected.
+#-
+#- side-effects: flag_requested, flag_required, $state->{selected}, $state->{whatrequires}
+#- + those of _remove_all_rejected_from ($state->{rejected})
+sub disable_selected {
+ my ($urpm, $db, $state, @pkgs_todo) = @_;
+ my @unselected;
+
+ #- iterate over package needing unrequested one.
+ while (my $pkg = shift @pkgs_todo) {
+ exists $state->{selected}{$pkg->id} or next;
+
+ #- keep a trace of what is deselected.
+ push @unselected, $pkg;
+
+ #- perform a closure on rejected packages (removed, obsoleted or avoided).
+ my @rejected_todo = scalar $pkg->fullname;
+ while (my $fullname = shift @rejected_todo) {
+ push @rejected_todo, _remove_all_rejected_from($state, $fullname);
+ }
+
+ #- the package being examined has to be unselected.
+ $urpm->{debug_URPM}("unselecting " . $pkg->fullname) if $urpm->{debug_URPM};
+ $pkg->set_flag_requested(0);
+ $pkg->set_flag_required(0);
+ delete $state->{selected}{$pkg->id};
+
+ #- determine package that requires properties no longer available, so that they need to be
+ #- unselected too.
+ foreach my $n ($pkg->provides_nosense) {
+ foreach my $p (whatrequires($urpm, $state, $n)) {
+ exists $state->{selected}{$p->id} or next;
+ if (unsatisfied_requires($urpm, $db, $state, $p, name => $n)) {
+ #- this package has broken dependencies and is selected.
+ push @pkgs_todo, $p;
+ }
+ }
+ }
+
+ #- clean whatrequires hash.
+ foreach ($pkg->requires_nosense) {
+ delete $state->{whatrequires}{$_}{$pkg->id};
+ %{$state->{whatrequires}{$_}} or delete $state->{whatrequires}{$_};
+ }
+ }
+
+ #- return all unselected packages.
+ @unselected;
+}
+
+#- determine dependencies that can safely been removed and are not requested
+#- return the packages that have been deselected.
+#-
+#- side-effects:
+#- + those of disable_selected (flag_requested, flag_required, $state->{selected}, $state->{whatrequires}, $state->{rejected})
+sub disable_selected_and_unrequested_dependencies {
+ my ($urpm, $db, $state, @pkgs_todo) = @_;
+ my @all_unselected;
+
+ #- disable selected packages, then extend unselection to all required packages
+ #- no longer needed and not requested.
+ while (my @unselected = disable_selected($urpm, $db, $state, @pkgs_todo)) {
+ my %required;
+
+ #- keep in the packages that had to be unselected.
+ @all_unselected or push @all_unselected, @unselected;
+
+ if ($urpm->{keep_unrequested_dependencies}) {
+ last;
+ }
+
+ #- search for unrequested required packages.
+ foreach (@unselected) {
+ foreach ($_->requires_nosense) {
+ foreach my $pkg (grep { $_ } $urpm->packages_providing($_)) {
+ $state->{selected}{$pkg->id} or next;
+ $state->{selected}{$pkg->id}{psel} && $state->{selected}{$state->{selected}{$pkg->id}{psel}->id} and next;
+ $pkg->flag_requested and next;
+ $required{$pkg->id} = undef;
+ }
+ }
+ }
+
+ #- check required packages are not needed by another selected package.
+ foreach (keys %required) {
+ my $pkg = $urpm->{depslist}[$_] or next;
+ foreach ($pkg->provides_nosense) {
+ foreach my $p_id (whatrequires_id($state, $_)) {
+ exists $required{$p_id} and next;
+ $state->{selected}{$p_id} and $required{$pkg->id} = 1;
+ }
+ }
+ }
+
+ #- now required values still undefined indicates packages than can be removed.
+ @pkgs_todo = map { $urpm->{depslist}[$_] } grep { !$required{$_} } keys %required;
+ }
+
+ @all_unselected;
+}
+
+#- compute selected size by removing any removed or obsoleted package.
+#-
+#- side-effects: none
+sub selected_size {
+ my ($urpm, $state) = @_;
+ my ($size) = _selected_size_filesize($urpm, $state, 0);
+ $size;
+}
+#- side-effects: none
+sub selected_size_filesize {
+ my ($urpm, $state) = @_;
+ _selected_size_filesize($urpm, $state, 1);
+}
+#- side-effects: none
+sub _selected_size_filesize {
+ my ($urpm, $state, $compute_filesize) = @_;
+ my ($size, $filesize, $bad_filesize);
+
+ foreach (keys %{$state->{selected} || {}}) {
+ my $pkg = $urpm->{depslist}[$_];
+ $size += $pkg->size;
+ $compute_filesize or next;
+
+ if (my $n = $pkg->filesize) {
+ $filesize += $n;
+ } elsif (!$bad_filesize) {
+ $urpm->{debug} and $urpm->{debug}("no filesize for package " . $pkg->fullname);
+ $bad_filesize = 1;
+ }
+ }
+
+ foreach (values %{$state->{rejected} || {}}) {
+ $_->{removed} || $_->{obsoleted} or next;
+ $size -= $_->{size};
+ }
+
+ foreach (@{$state->{orphans_to_remove} || []}) {
+ $size -= $_->size;
+ }
+
+ $size, $bad_filesize ? 0 : $filesize;
+}
+
+#- compute installed flags for all packages in depslist.
+#-
+#- side-effects: flag_upgrade, flag_installed
+sub compute_installed_flags {
+ my ($urpm, $db) = @_;
+
+ #- first pass to initialize flags installed and upgrade for all packages.
+ foreach (@{$urpm->{depslist}}) {
+ $_->is_arch_compat or next;
+ $_->flag_upgrade || $_->flag_installed or $_->set_flag_upgrade;
+ }
+
+ #- second pass to set installed flag and clean upgrade flag according to installed packages.
+ $db->traverse(sub {
+ my ($p) = @_;
+ #- compute flags.
+ foreach my $pkg ($urpm->packages_providing($p->name)) {
+ next if !defined $pkg;
+ $pkg->is_arch_compat && $pkg->name eq $p->name or next;
+ #- compute only installed and upgrade flags.
+ $pkg->set_flag_installed; #- there is at least one package installed (whatever its version).
+ $pkg->flag_upgrade and $pkg->set_flag_upgrade($pkg->compare_pkg($p) > 0);
+ }
+ });
+}
+
+#- side-effects: flag_skip, flag_disable_obsolete
+sub compute_flag {
+ my ($urpm, $pkg, %options) = @_;
+ foreach (qw(skip disable_obsolete)) {
+ if ($options{$_} && !$pkg->flag($_)) {
+ $pkg->set_flag($_, 1);
+ $options{callback} and $options{callback}->($urpm, $pkg, %options);
+ }
+ }
+}
+
+#- Adds packages flags according to an array containing packages names.
+#- $val is an array reference (as returned by get_packages_list) containing
+#- package names, or a regular expression matching against the fullname, if
+#- enclosed in slashes.
+#- %options :
+#- callback : sub to be called for each package where the flag is set
+#- skip : if true, set the 'skip' flag
+#- disable_obsolete : if true, set the 'disable_obsolete' flag
+#-
+#- side-effects:
+#- + those of compute_flag (flag_skip, flag_disable_obsolete)
+sub compute_flags {
+ my ($urpm, $val, %options) = @_;
+ if (ref $val eq 'HASH') { $val = [ keys %$val ] } #- compatibility with urpmi <= 4.5-13mdk
+ my @regex;
+
+ #- unless a regular expression is given, search in provides
+ foreach my $name (@$val) {
+ if ($name =~ m,^/(.*)/$,) {
+ push @regex, $1;
+ } else {
+ foreach my $pkg ($urpm->packages_providing($name)) {
+ compute_flag($urpm, $pkg, %options);
+ }
+ }
+ }
+
+ #- now search packages which fullname match given regexps
+ if (@regex) {
+ #- very costly :-(
+ foreach my $pkg (@{$urpm->{depslist}}) {
+ if (grep { $pkg->fullname =~ /$_/ } @regex) {
+ compute_flag($urpm, $pkg, %options);
+ }
+ }
+ }
+}
+
+#- side-effects: none
+sub _choose_best_pkg {
+ my ($urpm, $pkg_installed, @pkgs) = @_;
+
+ _choose_best_pkg_($urpm, $pkg_installed, grep {
+ $_->compare_pkg($pkg_installed) > 0;
+ } @pkgs);
+}
+
+#- side-effects: none
+sub _choose_best_pkg_ {
+ my ($urpm, $pkg_installed, @pkgs) = @_;
+
+ my $best;
+ foreach my $pkg (grep {
+ !strict_arch($urpm) || strict_arch_check($pkg_installed, $_);
+ } @pkgs) {
+ if (!$best || ($pkg->compare_pkg($best) || $pkg->id < $best->id) > 0) {
+ $best = $pkg;
+ }
+ }
+ $best;
+}
+
+#- side-effects: none
+sub _choose_bests_obsolete {
+ my ($urpm, $db, $pkg_installed, @pkgs) = @_;
+
+ _set_flag_installed_and_upgrade_if_no_newer($db, $_) foreach @pkgs;
+
+ my %by_name;
+ push @{$by_name{$_->name}}, $_ foreach grep { $_->flag_upgrade } @pkgs;
+
+ map { _choose_best_pkg_($urpm, $pkg_installed, @$_) } values %by_name;
+}
+
+#- select packages to upgrade, according to package already registered.
+#- by default, only takes best package and its obsoleted and compute
+#- all installed or upgrade flag.
+#- (used for --auto-select)
+#-
+#- side-effects: $requisted, flag_installed, flag_upgrade
+sub request_packages_to_upgrade {
+ my ($urpm, $db, $state, $requested, %options) = @_;
+
+ my %by_name;
+
+ #- now we can examine all existing packages to find packages to upgrade.
+ $db->traverse(sub {
+ my ($pkg_installed) = @_;
+ my $name = $pkg_installed->name;
+ my $pkg;
+ if (exists $by_name{$name}) {
+ if (my $p = $by_name{$name}) {
+ #- here a pkg with the same name is installed twice
+ if ($p->compare_pkg($pkg_installed) > 0) {
+ #- we selected $p, and it is still a valid choice
+ $pkg = $p;
+ } else {
+ #- $p is no good since $pkg_installed is higher version,
+ }
+ }
+ } elsif ($pkg = _choose_best_pkg($urpm, $pkg_installed, $urpm->packages_by_name($name))) {
+ #- first try with package using the same name.
+ $pkg->set_flag_installed;
+ $pkg->set_flag_upgrade;
+ }
+ if (my @pkgs = _choose_bests_obsolete($urpm, $db, $pkg_installed, _find_packages_obsoleting($urpm, $state, $pkg_installed))) {
+ if (@pkgs == 1) {
+ $pkg and $urpm->{debug_URPM}("auto-select: prefering " . $pkgs[0]->fullname . " obsoleting " . $pkg_installed->fullname . " over " . $pkg->fullname) if $urpm->{debug_URPM};
+ $pkg = $pkgs[0];
+ } elsif (@pkgs > 1) {
+ $urpm->{debug_URPM}("auto-select: multiple packages (" . join(' ', map { scalar $_->fullname } @pkgs) . ") obsoleting " . $pkg_installed->fullname) if $urpm->{debug_URPM};
+ $pkg = undef;
+ }
+ }
+ if ($pkg && $options{idlist} && !grep { $pkg->id == $_ } @{$options{idlist}}) {
+ $urpm->{debug_URPM}("not auto-selecting $pkg->fullname because it's not in search medias") if $urpm->{debug_URPM};
+ $pkg = undef;
+ }
+
+ $pkg and $urpm->{debug_URPM}("auto-select: adding " . $pkg->fullname . " replacing " . $pkg_installed->fullname) if $urpm->{debug_URPM};
+
+ $by_name{$name} = $pkg;
+ });
+
+ foreach my $pkg (values %by_name) {
+ $pkg or next;
+ $pkg->set_flag_upgrade;
+ $requested->{$pkg->id} = $options{requested};
+ }
+
+ $requested;
+}
+
+#- side-effects: none
+sub _sort_by_dependencies_get_graph {
+ my ($urpm, $state, $l) = @_;
+ my %edges;
+ foreach my $id (@$l) {
+ my $pkg = $urpm->{depslist}[$id];
+ my @provides = map { whatrequires_id($state, $_) } $pkg->provides_nosense;
+ if (my $from = $state->{selected}{$id}{from}) {
+ unshift @provides, $from->id;
+ }
+ $edges{$id} = [ uniq(@provides) ];
+ }
+ \%edges;
+}
+
+#- side-effects: none
+sub reverse_multi_hash {
+ my ($h) = @_;
+ my %r;
+ my ($k, $v);
+ while (($k, $v) = each %$h) {
+ push @{$r{$_}}, $k foreach @$v;
+ }
+ \%r;
+}
+
+sub _merge_2_groups {
+ my ($groups, $l1, $l2) = @_;
+ my $l = [ @$l1, @$l2 ];
+ $groups->{$_} = $l foreach @$l;
+ $l;
+}
+sub _add_group {
+ my ($groups, $group) = @_;
+
+ my ($main, @other) = uniq(grep { $_ } map { $groups->{$_} } @$group);
+ $main ||= [];
+ if (@other) {
+ $main = _merge_2_groups($groups, $main, $_) foreach @other;
+ }
+ foreach (grep { !$groups->{$_} } @$group) {
+ $groups->{$_} ||= $main;
+ push @$main, $_;
+ my @l_ = uniq(@$main);
+ @l_ == @$main or die '';
+ }
+ # warn "# groups: ", join(' ', map { join('+', @$_) } uniq(values %$groups)), "\n";
+}
+
+#- nb: this handles $nodes list not containing all $nodes that can be seen in $edges
+#-
+#- side-effects: none
+sub sort_graph {
+ my ($nodes, $edges) = @_;
+
+ #require Data::Dumper;
+ #warn Data::Dumper::Dumper($nodes, $edges);
+
+ my %nodes_h = map { $_ => 1 } @$nodes;
+ my (%loops, %added, @sorted);
+
+ my $recurse; $recurse = sub {
+ my ($id, @ids) = @_;
+# warn "# recurse $id @ids\n";
+
+ my $loop_ahead;
+ foreach my $p_id (@{$edges->{$id}}) {
+ if ($p_id == $id) {
+ # don't care
+ } elsif (exists $added{$p_id}) {
+ # already done
+ } elsif (grep { $_ == $p_id } @ids) {
+ my $begin = 1;
+ my @l = grep { $begin &&= $_ != $p_id } @ids;
+ $loop_ahead = 1;
+ _add_group(\%loops, [ $p_id, $id, @l ]);
+ } elsif ($loops{$p_id}) {
+ my $take;
+ if (my @l = grep { $take ||= $loops{$_} && $loops{$_} == $loops{$p_id} } reverse @ids) {
+ $loop_ahead = 1;
+# warn "# loop to existing one $p_id, $id, @l\n";
+ _add_group(\%loops, [ $p_id, $id, @l ]);
+ }
+ } else {
+ $recurse->($p_id, $id, @ids);
+ #- we would need to compute loop_ahead. we will do it below only once, and if not already set
+ }
+ }
+ if (!$loop_ahead && $loops{$id} && grep { exists $loops{$_} && $loops{$_} == $loops{$id} } @ids) {
+ $loop_ahead = 1;
+ }
+
+ if (!$loop_ahead) {
+ #- it's now a leaf or a loop we're done with
+ my @toadd = $loops{$id} ? @{$loops{$id}} : $id;
+ $added{$_} = undef foreach @toadd;
+# warn "# adding ", join('+', @toadd), " for $id\n";
+ push @sorted, [ uniq(grep { $nodes_h{$_} } @toadd) ];
+ }
+ };
+ !exists $added{$_} and $recurse->($_) foreach @$nodes;
+
+# warn "# result: ", join(' ', map { join('+', @$_) } @sorted), "\n";
+
+ check_graph_is_sorted(\@sorted, $nodes, $edges) or die "sort_graph failed";
+
+ @sorted;
+}
+
+#- side-effects: none
+sub check_graph_is_sorted {
+ my ($sorted, $nodes, $edges) = @_;
+
+ my $i = 1;
+ my %nb;
+ foreach (@$sorted) {
+ $nb{$_} = $i foreach @$_;
+ $i++;
+ }
+ my $nb_errors = 0;
+ my $error = sub { $nb_errors++; warn "error: $_[0]\n" };
+
+ foreach my $id (@$nodes) {
+ $nb{$id} or $error->("missing $id in sort_graph list");
+ }
+ foreach my $id (keys %$edges) {
+ my $id_i = $nb{$id} or next;
+ foreach my $req (@{$edges->{$id}}) {
+ my $req_i = $nb{$req} or next;
+ $req_i <= $id_i or $error->("$req should be before $id ($req_i $id_i)");
+ }
+ }
+ $nb_errors == 0;
+}
+
+
+#- side-effects: none
+sub _sort_by_dependencies__add_obsolete_edges {
+ my ($urpm, $state, $l, $requires) = @_;
+
+ my @obsoletes = grep { $_->{obsoleted} } values %{$state->{rejected}} or return;
+ my @groups = grep { @$_ > 1 } map { [ keys %{$_->{closure}} ] } @obsoletes;
+ my %groups;
+ foreach my $group (@groups) {
+ _add_group(\%groups, $group);
+ foreach (@$group) {
+ my $rej = $state->{rejected}{$_} or next;
+ _add_group(\%groups, [ $_, keys %{$rej->{closure}} ]);
+ }
+ }
+
+ my %fullnames = map { scalar($urpm->{depslist}[$_]->fullname) => $_ } @$l;
+ foreach my $group (uniq(values %groups)) {
+ my @group = grep { defined $_ } map { $fullnames{$_} } @$group;
+ foreach (@group) {
+ @{$requires->{$_}} = uniq(@{$requires->{$_}}, @group);
+ }
+ }
+}
+
+#- side-effects: none
+sub sort_by_dependencies {
+ my ($urpm, $state, @list_unsorted) = @_;
+ @list_unsorted = sort { $a <=> $b } @list_unsorted; # sort by ids to be more reproductable
+ $urpm->{debug_URPM}("getting graph of dependencies for sorting") if $urpm->{debug_URPM};
+ my $edges = _sort_by_dependencies_get_graph($urpm, $state, \@list_unsorted);
+ my $requires = reverse_multi_hash($edges);
+
+ _sort_by_dependencies__add_obsolete_edges($urpm, $state, \@list_unsorted, $requires);
+
+ $urpm->{debug_URPM}("sorting graph of dependencies") if $urpm->{debug_URPM};
+ sort_graph(\@list_unsorted, $requires);
+}
+
+sub sorted_rpms_to_string {
+ my ($urpm, @sorted) = @_;
+
+ "rpms sorted by dependencies:\n" . join("\n", map {
+ join('+', _ids_to_names($urpm, @$_));
+ } @sorted);
+}
+
+#- build transaction set for given selection
+#- options: start, end, idlist, split_length, keep
+#-
+#- side-effects: $state->{transaction}, $state->{transaction_state}
+sub build_transaction_set {
+ my ($urpm, $db, $state, %options) = @_;
+
+ #- clean transaction set.
+ $state->{transaction} = [];
+
+ my %selected_id;
+ @selected_id{$urpm->build_listid($options{start}, $options{end}, $options{idlist})} = ();
+
+ if ($options{split_length}) {
+ #- first step consists of sorting packages according to dependencies.
+ my @sorted = sort_by_dependencies($urpm, $state,
+ keys(%selected_id) > 0 ?
+ (grep { exists($selected_id{$_}) } keys %{$state->{selected}}) :
+ keys %{$state->{selected}});
+ $urpm->{debug_URPM}(sorted_rpms_to_string($urpm, @sorted)) if $urpm->{debug_URPM};
+
+ #- second step consists of re-applying resolve_requested in the same
+ #- order computed in first step and to update a list of packages to
+ #- install, to upgrade and to remove.
+ my %examined;
+ my @todo = @sorted;
+ while (@todo) {
+ my @ids;
+ while (@todo && @ids < $options{split_length}) {
+ my $l = shift @todo;
+ push @ids, @$l;
+ }
+ my %requested = map { $_ => undef } @ids;
+
+ resolve_requested__no_suggests_($urpm,
+ $db, $state->{transaction_state} ||= {},
+ \%requested,
+ defined $options{start} ? (start => $options{start}) : @{[]},
+ defined $options{end} ? (end => $options{end}) : @{[]},
+ keep => $options{keep},
+ );
+
+ my @upgrade = grep { ! exists $examined{$_} } keys %{$state->{transaction_state}{selected}};
+ my @remove = grep { ! exists $examined{$_} } packages_to_remove($state->{transaction_state});
+
+ @upgrade || @remove or next;
+
+ if (my @bad_remove = grep { !$state->{rejected}{$_}{removed} || $state->{rejected}{$_}{obsoleted} } @remove) {
+ $urpm->{error}(sorted_rpms_to_string($urpm, @sorted)) if $urpm->{error};
+ $urpm->{error}('transaction is too small: ' . join(' ', @bad_remove) . ' is rejected but it should not (current transaction: ' . join(' ', _ids_to_fullnames($urpm, @upgrade)) . ', requested: ' . join('+', _ids_to_fullnames($urpm, @ids)) . ')') if $urpm->{error};
+ $state->{transaction} = [];
+ last;
+ }
+
+ $urpm->{debug_URPM}(sprintf('transaction valid: remove=%s update=%s',
+ join(',', @remove),
+ join(',', _ids_to_names($urpm, @upgrade)))) if $urpm->{debug_URPM};
+
+ $examined{$_} = undef foreach @upgrade, @remove;
+ push @{$state->{transaction}}, { upgrade => \@upgrade, remove => \@remove };
+ }
+
+ #- check that the transaction set has been correctly created.
+ #- (ie that no other package was removed)
+ if (keys(%{$state->{selected}}) == keys(%{$state->{transaction_state}{selected}}) &&
+ listlength(packages_to_remove($state)) == listlength(packages_to_remove($state->{transaction_state}))
+ ) {
+ foreach (keys(%{$state->{selected}})) {
+ exists $state->{transaction_state}{selected}{$_} and next;
+ $urpm->{error}('using one big transaction') if $urpm->{error};
+ $state->{transaction} = []; last;
+ }
+ foreach (packages_to_remove($state)) {
+ $state->{transaction_state}{rejected}{$_}{removed} &&
+ !$state->{transaction_state}{rejected}{$_}{obsoleted} and next;
+ $urpm->{error}('using one big transaction') if $urpm->{error};
+ $state->{transaction} = []; last;
+ }
+ }
+ }
+
+ #- fallback if something can be selected but nothing has been allowed in transaction list.
+ if (%{$state->{selected} || {}} && !@{$state->{transaction}}) {
+ $urpm->{debug_URPM}('using one big transaction') if $urpm->{debug_URPM};
+ push @{$state->{transaction}}, {
+ upgrade => [ keys %{$state->{selected}} ],
+ remove => [ packages_to_remove($state) ],
+ };
+ }
+
+ if ($state->{orphans_to_remove}) {
+ my @l = map { scalar $_->fullname } @{$state->{orphans_to_remove}};
+ push @{$state->{transaction}}, { remove => \@l };
+ }
+
+ $state->{transaction};
+}
+
+1;
diff --git a/URPM/Signature.pm b/URPM/Signature.pm
new file mode 100644
index 0000000..003af07
--- /dev/null
+++ b/URPM/Signature.pm
@@ -0,0 +1,91 @@
+package URPM;
+
+use strict;
+use warnings;
+
+#- parse from rpmlib db.
+#-
+#- side-effects: $urpm
+sub parse_pubkeys {
+ my ($urpm, %options) = @_;
+
+ my $db = $options{db};
+ $db ||= URPM::DB::open($options{root}) or die "Can't open RPM DB, aborting\n";
+ my @keys = parse_pubkeys_($db);
+
+ $urpm->{keys}{$_->{id}} = $_ foreach @keys;
+}
+
+#- side-effects: none
+sub parse_pubkeys_ {
+ my ($db) = @_;
+
+ my ($block, $content);
+ my %keys;
+
+ $db->traverse_tag('name', [ 'gpg-pubkey' ], sub {
+ my ($p) = @_;
+ # the first blank separates the PEM headers from key data, this
+ # flags we found it:
+ my $found_blank = 0;
+ foreach (split "\n", $p->description) {
+ if ($block) {
+ if (/^$/ and not $found_blank) {
+ # All content until now were the encapsulated pem
+ # headers...
+ $content = '';
+ $found_blank = 1;
+ }
+ elsif (/^-----END PGP PUBLIC KEY BLOCK-----$/) {
+ $keys{$p->version} = {
+ $p->summary =~ /^gpg\((.*)\)$/ ? (name => $1) : @{[]},
+ id => $p->version,
+ content => $content,
+ block => $p->description,
+ };
+ $block = undef;
+ $content = '';
+ }
+ else {
+ $content .= $_;
+ }
+ }
+ $block ||= /^-----BEGIN PGP PUBLIC KEY BLOCK-----$/;
+ }
+ });
+
+ values %keys;
+}
+
+#- obsoleted
+sub import_needed_pubkeys {
+ warn "import_needed_pubkeys prototype has changed, please give a file directly\n";
+ return;
+}
+
+#- import pubkeys only if it is needed.
+sub import_needed_pubkeys_from_file {
+ my ($db, $pubkey_file, $o_callback) = @_;
+
+ my @keys = parse_pubkeys_($db);
+
+ my $keyid = substr get_gpg_fingerprint($pubkey_file), 8;
+ my ($kv) = grep { (hex($keyid) == hex($_->{id})) } @keys;
+ my $imported;
+ if (!$kv) {
+ if (!import_pubkey_file($db, $pubkey_file)) {
+ #$urpm->{debug_URPM}("Couldn't import public key from ".$pubkey_file) if $urpm->{debug_URPM};
+ $imported = 0;
+ } else {
+ $imported = 1;
+ }
+ @keys = parse_pubkeys_($db);
+ ($kv) = grep { (hex($keyid) == hex($_->{id})) } @keys;
+ }
+
+ #- let the caller know about what has been found.
+ #- this is an error if the key is not found.
+ $o_callback and $o_callback->($kv?$kv->{id}:undef, $imported);
+}
+
+1;