## ## openpkg-build.pl -- create build scripts from package index ## Copyright (c) 2000-2003 The OpenPKG Project ## Copyright (c) 2000-2003 Ralf S. Engelschall ## Copyright (c) 2000-2003 Cable & Wireless ## ## Permission to use, copy, modify, and distribute this software for ## any purpose with or without fee is hereby granted, provided that ## the above copyright notice and this permission notice appear in all ## copies. ## ## THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED ## WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF ## MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ## IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR ## CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF ## USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ## ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, ## OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT ## OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ## SUCH DAMAGE. ## require 5; $|=1; # autoflush use strict; use vars qw/ $opt_R $opt_r $opt_f $opt_u $opt_U $opt_a $opt_A $opt_z $opt_Z $opt_P $opt_N $opt_E $opt_H $opt_i $opt_D $opt_p $opt_q $opt_s $opt_S $opt_X $opt_M $opt_L $opt_W $opt_K $opt_e $opt_b $opt_B $opt_g/; my $getopts = 'R:r:f:uUaAzZP:N:EH:iD:p:qsSXMLWKebBg'; getopts($getopts); ########################################################################## sub getopts ($) { my($opts) = @_; my(%optf) = map { /(\w)/; $1 => $_ } $opts =~ /(\w:|\w)/g; my(%opts,@argv,$optarg); foreach (@ARGV) { if (@argv) { push @argv, $_; } elsif (defined $optarg) { if (exists $opts{$optarg}) { $opts{$optarg} .= " $_"; } else { $opts{$optarg} = $_; } $optarg = undef; } elsif (!/^[-]/) { push @argv, $_; } else { while (/^\-(\w)(.*)/) { if (exists $optf{$1}) { if (length($optf{$1}) > 1) { if ($2 ne '') { if (exists $opts{$1}) { $opts{$1} .= " $2"; } else { $opts{$1} = $2; } } else { $optarg = $1; } last; } else { $opts{$1} = 1; } } else { warn "warning: unknown option $_\n"; } $_ = "-$2"; } } } if (defined $optarg) { warn "warning: option $optarg requires an argument\n"; } foreach (keys %opts) { eval '$opt_'.$_.' = "'.quotemeta($opts{$_}).'";'; } @ARGV = @argv; } my(%env) = ( '' => { } ); if (open(FH, "< $ENV{'HOME'}/.openpkg/build")) { my($env) = $env{''}; my($go) = $getopts; $go =~ s/[^a-zA-Z]//g; while () { if (/^\s*\[([^\]]*)\]/) { $env{$1} = { } unless $env{$1}; $env = $env{$1}; } elsif (my($opt,$val) = /^\-([$go])\s*(.*?)\s*$/) { $val = 1 unless defined $val; if (exists $env->{$opt}) { $env->{$opt} .= " $val"; } else { $env->{$opt} = $val; } } } close(FH); } die "openpkg:build:USAGE: $0 [-R rpm] [-r repository] [-f index.rdf] [-uUzZiqsSXMLWKebBg] [-P priv-cmd] [-N non-priv-cmd] [-p platform] [-Dwith ...] [-Ename ...] [-Hname ...] ( [-aA] | patternlist )\n" unless $#ARGV >= 0 || ($#ARGV == -1 && ($opt_a || $opt_A)); ########################################################################## # # evaluate a condition attribute from an option set # sub conditional ($$) { my($cond,$with) = @_; my(@s,$res); return 1 if $cond eq '' || !defined $with; foreach (split(/\s+/,$cond)) { if ($_ eq '+') { die "FATAL: stack underflow in: $cond\n" if scalar(@s)<2; my($a) = pop @s; my($b) = pop @s; push @s, $a && $b; } elsif ($_ eq '|') { die "FATAL: stack underflow in: $cond\n" if scalar(@s)<2; my($a) = pop @s; my($b) = pop @s; push @s, $a || $b; } elsif ($_ eq '!') { die "FATAL: stack underflow in: $cond\n" if scalar(@s)<1; my($a) = pop @s; push @s, !$a; } else { push @s, ($with->{$_} eq 'yes') ? 1 : 0; } } die "FATAL: stack underflow in: $cond\n" if scalar(@s)<1; $res = pop @s; die "FATAL: stack not empty in: $cond\n" if scalar(@s)>0; return $res; } ########################################################################## my($RPM,$CURL,$PROG); $RPM = $opt_R || $env{''}->{'R'} || '@l_prefix@/bin/rpm'; $RPM = (`which $RPM` =~ m{^(/.*)})[0] if ($RPM !~ m|^/|); die "FATAL: cannot locate rpm in path\n" unless $RPM =~ m{^/}; # augment command line parameters foreach my $env (sort { $a cmp $b } grep { $RPM =~ /^\Q$_\E/ } keys %env) { while (my($opt,$val) = each %{$env{$env}}) { eval "\$opt_$opt = '$val' unless defined \$opt_$opt;"; } } $CURL = $RPM; $CURL =~ s/\/bin\/rpm$/\/lib\/openpkg\/curl/ or die "FATAL: cannot deduce curl path from $RPM\n"; ($PROG) = $0 =~ /(?:.*\/)?(.*)/; sub cmd ($$) { my($w,$s) = @_; if (!defined $w) { return $s; } elsif ($w =~ /^-(.*)/) { return "$1 \"$s\""; } else { return "$w $s"; } } sub priv ($) { cmd($opt_P,$_[0]); } sub npriv ($) { cmd($opt_N,$_[0]); } sub run ($) { my($c) = cmd($opt_N,$_[0]); `$c` } sub version_cmp ($$) { my($a,$b) = @_; my(@a,@b,$c); my($ax,$bx); @a = split(/\./, $a); @b = split(/\./, $b); while (@a && @b) { if ($a[0] =~ /^\d+$/ && $b[0] =~ /^\d+$/) { $c = $a[0] <=> $b[0]; } elsif ((($a,$ax) = $a[0] =~ /^(\d+)(.*)$/) && (($b,$bx) = $b[0] =~ /^(\d+)(.*)$/)) { $c = $a <=> $b; $c = $ax cmp $bx unless $c; } else { $c = $a[0] cmp $b[0]; } return $c if $c; shift @a; shift @b; } $c = scalar(@a) <=> scalar(@b); return $c; } sub release_cmp ($$) { my($a,$b) = @_; return $a cmp $b; } sub vcmp ($$) { my($a,$b) = @_; return 0 if $a eq $b; my($av,$ar) = $a =~ /^(.*?)(?:\-([\d\.]+))?$/; my($bv,$br) = $b =~ /^(.*?)(?:\-([\d\.]+))?$/; my($c); if ((defined $ar) && (defined $br)) { $c = release_cmp($ar,$br); return $c if $c; } if ((defined $av) && (defined $bv)) { $c = version_cmp($av,$bv); return $c if $c; } return 0; } sub vs ($) { my($t) = @_; return defined $t->{release} ? "$t->{version}-$t->{release}" : $t->{version}; } sub vsn ($) { my($t) = @_; return "$t->{name}-".vs($t); } ########################################################################## sub get_config () { my($c,@q,@g); $c = run("$RPM --eval '%{_rpmdir} %{_rpmfilename} %{_target_os} %{_target_cpu} %{_prefix}'"); chomp($c); (@q) = split(/\s+/,$c); $q[1] =~ s/%{OS}/$q[2]/; $q[1] =~ s/%{ARCH}/$q[3]/; $c = run("$RPM --showrc"); @g = $c =~ /\%\{l_tool_locate\s+([^\s\}]+)/g; return { rpmdir => $q[0], template => $q[1], platform => '', prefix => $q[4], optreg => '(?:'.join('|', map { "\Quse_$_\E" } @g).')' }; } sub get_release () { my($rel,$url); ($rel) = run("$RPM -qi openpkg") =~ /Version:\s*(\S+)/m; if ($rel =~ /^\d+$/) { print "# $PROG current($rel)\n"; $url = "ftp://ftp.openpkg.org/current/"; } elsif ($rel =~ /^(\d+\.\d+)/) { $rel = $1; print "# $PROG release($rel)\n"; $url = "ftp://ftp.openpkg.org/release/$rel/"; } else { die "FATAL: don't know how to handle this release\n"; } return $url; } sub parse_provides ($) { my($s) = @_; my($nam,$val,$pre,$with,$pxy,$ver,$rel); ($nam,$val) = $s =~ /^(\S+)\s*(?:=\s*(\S*?))?$/; # # build options are encoded as a Requirement # :: = # # since the value is interpreted as a version number # you can only do equality tests # if (($pre,$with) = $nam =~ /^(\S+?)::(\S*)$/) { $val =~ s/(?:\%([0-9a-fA-F][0-9a-fA-F]))/chr(hex($1))/eg; ($ver,$rel,$pxy) = ($val, undef, undef); } else { ($ver,$rel,$pxy) = $val =~ /^([^\s\-]+)-([^\s\+]+)(\+PROXY)?$/; } return { name => $nam, # the full name of the resource version => $ver, # the version (or value) release => $rel, # and release number proxy => $pxy, # wether the resource is a PROXY resource prefix => $pre, # the packagename (if resource is an option) with => $with # the buildoption (if resource is an option) }; } sub parse_depends ($) { my($dep) = @_; my($name, $op, $val); if (ref $dep) { # # dependency from new index stored as a node # # content of the node is the name # certain attributes denote the comparison operator # the value of such an attribute is the comparison operand # # the operator (and operand) are optional and there can # only be one # $name = $dep->{content}; $op = undef; $op = 'equ' if exists $dep->{equ}; $op = 'geq' if exists $dep->{geq}; $op = 'leq' if exists $dep->{leq}; $op = 'gt' if exists $dep->{gt}; $op = 'lt' if exists $dep->{lt}; if (defined $op) { $val = $dep->{$op}; } } elsif ($dep =~ /\S/) { # # dependency from old index stored as text string # # "name operator operand" # or # "name" # ($name,$op,$val) = $dep =~ /(\S+)\s*(?:(\S+)\s*(\S+))?\s*$/; if (defined $op) { $op = { '==' => 'equ', '=' => 'equ', '>=' => 'geq', '=>' => 'geq', '<=' => 'leq', '=<' => 'leq', '>' => 'gt', '<' => 'lt' }->{$op}; unless (defined $op) { print "# don't know how to handle dependency: $dep\n"; return; } } } return { name => $name, op => $op, val => $val }; } sub depends2provides ($) { my($dep) = @_; my($ver,$rel,$pxy,$pre,$with); ($ver,$rel,$pxy) = $dep->{val} =~ /^([^\s\-]+)-([^\s\+]+)(\+PROXY)?$/; ($pre,$with) = $dep->{name} =~ /^(\S+?)::(\S*)$/; return { name => $dep->{name}, version => (defined $ver ? $ver : $dep->{val}), release => $rel, proxy => $pxy, prefix => $pre, with => $with } } # # convert parser output to dependency records # sub depend_list ($) { my($dl) = @_; foreach (@$dl) { $_->{value} = parse_depends($_->{value}); } return $dl; } # # retrieve the local installed base # # for packages that provide option resources (packagename::buildoption) # the options are parsed into the OPTIONS hash # # other packages will query options on demand # sub get_installed () { my(%map); my(@l,$p); my($nam,$val,%options); my($vs,$rec,@list); my($name,$version,$release); my($req); @l = run("$RPM --provides -qa"); @list = (); foreach (@l) { next unless $p = parse_provides($_); # is this an option ? if (defined $p->{with}) { $options{$p->{prefix}}->{$p->{with}} = $p->{version}; push @list, $p; next; } # is this a virtual target ? $vs = vs($p); if ($vs eq '') { push @list,$p; next; } $name = $p->{name}; $version = defined $p->{version} ? $p->{version} : '*'; $release = defined $p->{release} ? $p->{release} : '*'; push(@list, { name => $name, version => $version, release => $release }); # create target record $rec = { name => $name, version => $version, release => $release, PROXY => $p->{proxy}, depends => [], keeps => [] }; foreach (@list) { push @{$map{$_->{name}}->{vs($_)}}, $rec; } @list = (); } if (@list) { print "# ATTENTION: ",scalar(@list)," froods found\n" } # # options are provided for a package # apply them to all instances of the package # foreach $nam (keys %options) { foreach $val (keys %{$map{$nam}}) { foreach (@{$map{$nam}->{$val}}) { $_->{OPTIONS} = $options{$nam}; } } } @l = run("$RPM --qf '%{NAME} %{VERSION} %{RELEASE}[ .%{REQUIRENAME} .%{REQUIREFLAGS:depflags} .%{REQUIREVERSION}]\n' -qa"); @list = (); foreach (@l) { ($name,$version,$release,$req) = /^(\S+)\s+(\S+)\s+(\S+)\s*(.*?)\s*$/; while ($req =~ /\.(\S+)\s+\.(\S*)\s+\.(\S*)/g) { $p = parse_depends("$1 $2 $3"); next if $p->{name} =~ /^rpmlib\(/; $vs = vs({ version => $version, release => $release}); $p = { cond => '', value => $p }; foreach $rec (@{$map{$name}->{$vs}}) { push @{$rec->{depends}}, $p; push @{$rec->{keeps}}, $p; } } } if (@list) { print "# ATTENTION: ",scalar(@list)," fnords found\n" } return \%map; } # # compute reverse dependency map # # sub get_revdep ($$) { my($env, $i) = @_; my($r) = $env->{'repository'}; my($pkg, %dep, %dlist, %rev); my(@vers,$t,$t1,$t2,$with,$name,$vmap); my($d,$k,%d,$old,%name,%pkg); print "# computing reverse dependencies\n"; foreach $pkg (keys %$i) { $vmap = $r->{$pkg}; unless ($vmap) { print "# ATTENTION: $pkg has no upgrade path\n"; next; } # # get forward dependencies from installed packages # # dep{a}{b} is true if b depends directly on a # dlist{a} is list of packages that depend on a # @vers = get_versions($i->{$pkg}, sub { 1; }); foreach (@vers) { foreach $t (@{$i->{$pkg}->{$_}}) { $with = get_with($t); $d = target_attribute($t, $env, 'depends', $with); $k = target_attribute($t, $env, 'keeps', $with); next unless @$d || @$k; %d = unique_map($d,$k); # resolve package unless (exists $pkg{$pkg}) { ($t2,$old) = dep2target({ name => $pkg }, $env); $t2 = undef if $old; $pkg{$pkg} = undef; } $t2 = $pkg{$pkg}; next unless $t2; foreach (keys %d) { next if $_ eq 'OpenPKG'; # resolve target unless (exists $name{$_}) { ($t1,$old) = dep2target($d{$_}, $env); $name{$_} = $t1 ? $t1->{name} : $_; } $name = $name{$_}; unless ($dep{$name}{$t->{name}}) { $dep{$name}{$t->{name}} = 1; push @{$dlist{$name}}, $t2; } } } } } # # sort reverse dependencies # foreach $pkg (keys %dep) { $rev{$pkg} = [ sort { $dep{$b->{name}}{$a->{name}} || -$dep{$a->{name}}{$b->{name}} || $a->{name} cmp $b->{name} } @{$dlist{$pkg}} ]; } return \%rev; } # # parse option from rpm output # sub parse_options ($) { my($l) = @_; $l = join("\n", @$l) if ref $l; return {} if ($l !~ m/(--define|\%option\s+)/s); my $with = {}; $l =~ s/--define\s*'(\S+)\s+(\S+?)'/$with->{$1} = $2, ''/sge; # before openpkg-20021230 $l =~ s/\%option\s+(\S+)\s+(\S+)/$with->{$1} = $2, ''/sge; # after openpkg-20021230 return $with; } # # parse option from rpm provides list # sub parse_provideslist ($) { my($l) = @_; my($p); my($nam,$val,%opts); foreach (@$l) { $p = parse_provides($_); next unless defined $p->{with} && defined $p->{prefix}; $opts{$p->{with}} = $p->{version} } return \%opts; } # # copy options from new to old # where option already exists in old or option key # matches regular expression # sub override_options ($$$) { my($old, $new, $reg) = @_; foreach my $k (keys %$new) { if ((exists $old->{$k} && $old->{$k} ne $new->{$k}) || $k =~ /^$reg$/) { $old->{$k} = $new->{$k}; } } } # # pull in OPTIONS for a package or an RPM file # sub get_with ($;$) { my($t,$fn) = @_; my(@l,%with); my($optmap,$opt); if ($t->{OPTIONS}) { $opt = $t->{OPTIONS}; } else { if (defined $fn) { @l = run("$RPM -q --provides -p $fn"); } else { @l = run("$RPM -q --provides $t->{name}"); } $opt = parse_provideslist(\@l); if (scalar(keys %$opt) == 0) { if (defined $fn) { @l = run("$RPM -qi -p $fn"); } else { @l = run("$RPM -qi $t->{name}"); } $opt = parse_options(\@l); } $t->{OPTIONS} = $opt; } return $opt; } # # compute absolute paths # # (url, fn) point to a base document # the location is the file path fn if fn is # defined, otherwise it is url. # # augment the pointer with suburl # # suburl can be an absolute url # then the new pointer is (suburl, undef) # # suburl can be a absolute file path # then the new pointer is (suburl, suburl) # # suburl can be a relative path # then it augments url or fn accordingly # sub relurl ($$$) { my($url,$fn,$suburl) = @_; my($subfn); if ($suburl =~ /^\w+:\/\//) { # NOP } elsif ($suburl =~ /^\//) { $subfn = $suburl; } else { if (defined $fn) { $subfn = $fn; $subfn =~ s/(\/)?\/*[^\/]*$/$1$suburl/; $suburl = $subfn; } else { $subfn = $url; $subfn =~ s/(\/)?\/*[^\/]*$/$1$suburl/; $suburl = $subfn; $subfn = undef; } } return ($suburl, $subfn); } # # return node value from XML parser # sub xel($) { my($a) = @_; my($l) = $a->[0]; return '' if ref $l; return $l; } # # convert conditional XML Bag into flat list # sub xwith ($) { my($bags) = @_; my($bag,$li,$el); my(@out); foreach $bag (@$bags) { foreach $li (@{$bag->{'rdf:bag'}}) { $el = $li->{'resource'} || $li->{'rdf:li'}; foreach (@$el) { push @out, { cond => $bag->{'cond'}, value => $_ }; } } } return \@out; } # # convert simple parser Bag into flat list # sub swith ($$) { my($bags,$name) = @_; my($cond); my(@out); foreach $cond (keys %$bags) { foreach (@{$bags->{$cond}->{$name}}) { push @out, { cond => $cond, value => $_ }; } } return \@out; } sub goodpf ($$) { my($l,$p) = @_; return 1 if $l eq ''; return $l =~ /(?:^|\s)\Q$p\E(?:\s|$)/; } sub simple_text_parser ($$$$$) { my($fh,$url,$map,$pfmatch,$installed) = @_; my(@include); my($section); my($name,$version); my($href,$release,$desc,$bags); my(%options,@provides); my($platform,$prefix); my($rec); my($tag,$cond,$attrname,$attrval,$body); my($usecond); my($options); print "# using simple text parser\n"; while (<$fh>) { s/>/>/g; s/</ (.*?) (?:<\/\1>)? $ }mx; if ($tag eq 'Description') { $usecond = $cond; $section = 'description'; } elsif ($tag eq '/Description') { $usecond = $cond; $section = undef; } elsif ($section eq 'description') { $desc .= $_; } elsif ($tag eq 'PreReq') { $usecond = $cond; $section = 'prereq'; } elsif ($tag eq '/PreReq') { $usecond = undef; $section = undef; } elsif ($tag eq 'BuildPreReq') { $usecond = $cond; $section = 'bprereq'; } elsif ($tag eq '/BuildPreReq') { $usecond = undef; $section = undef; } elsif ($tag eq 'Provides') { $usecond = $cond; $section = 'provides'; } elsif ($tag eq '/Provides') { $usecond = undef; $section = undef; } elsif ($tag eq 'Conflicts') { $usecond = $cond; $section = 'conflicts'; } elsif ($tag eq '/Conflicts') { $usecond = undef; $section = undef; } elsif ($tag eq 'NoSource') { $usecond = $cond; $section = 'nosource'; } elsif ($tag eq '/NoSource') { $usecond = undef; $section = undef; } elsif ($tag eq 'Source') { $usecond = $cond; $section = 'source'; } elsif ($tag eq '/Source') { $usecond = undef; $section = undef; } elsif ($tag eq 'Name') { $name = $body; } elsif ($tag eq 'Version') { $version = $body; } elsif ($tag eq 'Release') { $release = $body; } elsif ($tag eq 'Platform') { $platform = $body; } elsif ($tag eq 'Prefixes') { $prefix = $body; } elsif ($tag eq 'rdf:li' || $tag eq 'resource') { if (defined $attrname) { $body = { $attrname => $attrval, content => $body }; } if ($section eq 'provides') { push @provides, $body if !defined $usecond; } elsif ($section ne '') { push @{$bags->{"$usecond"}->{$section}}, $body; } } elsif ($tag eq '/rdf:Description') { if (defined $href && defined $name && defined $version && defined $release) { @provides = map { depends2provides(parse_depends($_)) } @provides; %options = map { ( $_->{with} => $_->{version} ) } grep { defined $_->{with} } @provides; push(@provides, { name => $name, version => $version, release => $release }); $options = %options ? { %options } : parse_options($desc); if ($options) { my(@t) = get_targets($installed->{$name},sub { 1; }); } eval { $rec = { href => (relurl($url, undef, $href))[0], name => $name, version => $version, release => $release, depends => depend_list(swith($bags,'bprereq')), keeps => depend_list(swith($bags,'prereq')), conflicts => swith($bags,'conflicts'), source => swith($bags,'source'), nosource => swith($bags,'nosource'), desc => $desc, platform => $platform, prefix => $prefix, OPTIONS => $options, DEFOPTS => { %$options } }; }; if ($@) { die "ERROR: when reading entry '$name'\n".$@; } foreach (@provides) { push(@{$map->{$_->{name}}->{vs($_)}}, $rec); } } $href = undef; } } return \@include; } sub xml_parser ($$$$$) { my($fh, $url, $map, $pfmatch, $installed) = @_; my(@include); my($xml,$rep,$sub); my(@provides,%options,$rec); my($href,$name,$version,$release,$desc); my($options); print "# using XML parser\n"; $xml = XML::Simple::XMLin($fh, forcearray => 1); $rep = $xml->{'Repository'}->[0]->{'rdf:Description'}; $sub = $xml->{'Repository'}->[0]->{'Repository'}; foreach (@$rep) { $href = $_->{'href'}; $name = xel($_->{'Name'}); $version = xel($_->{'Version'}); $release = xel($_->{'Release'}); next unless defined $href && defined $name && defined $version && defined $release; @provides = (); if ($_->{'Provides'}) { @provides = map { $_ = $_->{'rdf:bag'}->[0]; $_ = $_->{'rdf:li'} ? $_->{'rdf:li'} : $_->{'resource'}; @$_; } grep { !exists $_->{'cond'} } @{$_->{'Provides'}}; } @provides = map { depends2provides(parse_depends($_)) } @provides; %options = map { ( $_->{with} => $_->{version} ) } grep { defined $_->{with} } @provides; push(@provides, { name => $name, version => $version, release => $release }); $desc = xel($_->{'Description'}); $options = %options ? { %options } : parse_options($desc); if ($options) { my(@t) = get_targets($installed->{$name},sub { 1; }); } eval { $rec = { href => (relurl($url, undef, $href))[0], name => $name, version => $version, release => $release, platform => xel($_->{'Platform'}), prefix => xel($_->{'Prefixes'}), depends => depend_list(xwith($_->{'BuildPreReq'})), keeps => depend_list(xwith($_->{'PreReq'})), conflicts => xwith($_->{'Conflicts'}), source => xwith($_->{'Source'}), nosource => xwith($_->{'NoSource'}), desc => $desc, OPTIONS => $options, DEFOPTS => { %$options } }; }; if ($@) { die "ERROR: when reading entry '$name'\n".$@; } foreach (@provides) { push(@{$map->{$_->{name}}->{vs($_)}}, $rec); } } if ($sub) { @include = map { goodpf($_->{platform},$pfmatch) ? ( $_->{href} ) : ( ) } @$sub; } return \@include; } sub open_index ($$) { my($url, $fn) = @_; my($fetch,$bzip2,$path); $fetch = defined $fn ? $fn : $url; $bzip2 = $RPM; $bzip2 =~ s/bin\/rpm$/lib\/openpkg\/bzip2/ or die "FATAL: cannot deduce bzip2 path from $RPM\n"; $fetch !~ /\.bz2$/ || -x $bzip2 or die "FATAL: $bzip2 not found\n"; if ($fetch =~ /^\w+:/) { # looks like URL scheme print "# curling index $fetch\n"; if ($fetch =~ /\.bz2$/) { $path = "$CURL -q -s -o - \"$fetch\" | $bzip2 -dc |"; } else { $path = "$CURL -q -s -o - \"$fetch\" |"; } } else { print "# reading index file $fn\n"; if ($fetch =~ /\.bz2$/) { $path = "$bzip2 -dc $fetch |"; } else { $path = "< $fetch"; } } open(RFH, $path) or die "FATAL: cannot open '$fetch' ($!)\n"; } # # fetch index from file or URL # recursively fetch sub-indexes # sub get_index ($$$$$) { my($url,$fn,$noxml,$pfmatch,$installed) = @_; my(%map,$include); my($parser); open_index($url,$fn); unless ($noxml) { eval { require XML::Simple; }; $noxml = 1 if $@; } $parser = $noxml ? \&simple_text_parser : \&xml_parser; $include = $parser->(\*RFH, $url, \%map, $pfmatch, $installed); close(RFH) or die "FATAL: an I/O error occured\n"; # # cannot do real recursions on file handles, so we simply append # all sub-RDFs, the result is flattend into a big hash anyway # foreach (@$include) { my($submap); my($suburl,$subfn) = relurl($url,$fn,$_); $submap = get_index($suburl,$subfn,$noxml,$pfmatch,$installed); while (my($name,$vmap) = each %$submap) { while (my($vs,$recs) = each %$vmap) { push @{$map{$name}->{$vs}}, @$recs; } } } return \%map; } ############################################################################ # # grep all versions of a name that # satisfy a condition # sub get_versions ($$) { my($relmap, $cond) = @_; return grep { $cond->($_); } sort { vcmp($a,$b); } keys %$relmap; } # # fetch targets of a name that # satisfies a condition # sub get_targets ($$) { my($relmap, $cond) = @_; return map { @{$relmap->{$_}} } get_versions($relmap, $cond); } # # check if target record describes a source package # sub is_source ($) { my($t) = @_; return !(defined $t->{'prefix'}); } # # there can be multiple sources for a target release # sub chose_source ($$$$$) { my($env, $name, $select, $vmap, $cond) = @_; my(@vers,@recs,@nrecs,$rec,%nam); # # resolve name into a list of versions # for virtual targets this resolves to a list # of real targets that provide the virtual target # @vers = get_versions($vmap, sub { 1; }); return unless @vers; # # filter out binary targets that are not usuable # @recs = map { $_->[1] } grep { my($v,$t) = @$_; is_source($t) || ( !$env->{sourceonly} && $t->{'platform'} eq $env->{config}->{platform} && $t->{'prefix'} eq $env->{config}->{prefix} && $cond->($v) ) } map { my($v) = $_; my($l) = $vmap->{$_}; map { [ $v, $_ ] } @$l; } @vers; return unless @recs; # # limit list to exact matches if provided by -e # if (defined $select) { @recs = grep { vsn($_) =~ /^\Q$select\E/ } @recs; } # # try to resolve ambiguity against installed targets # and targets previously selected # if (scalar(@recs) > 1) { @nrecs = grep { $env->{built}->{$_->{name}} || $env->{installed}->{$_->{name}} } @recs; @recs = @nrecs if @nrecs; } # # try to resolve ambiguity against hints # if ($env->{hint}) { @nrecs = grep { exists $env->{hint}->{$_->{name}} } @recs; @recs = @nrecs if @nrecs; } # # try to resolve ambiguity against targets that match # the exact name # if (scalar(@recs) > 1) { @nrecs = grep { $name eq $_->{name} } @recs; @recs = @nrecs if @nrecs; } # # try to resolve ambiguity by preferring binaries # if (scalar(@recs) > 1 && !$env->{sourceonly}) { @nrecs = grep { defined $_->{'platform'} } @recs; @recs = @nrecs if @nrecs; } # # if we still have non-unique targets, complain # if (scalar(@recs) > 1) { %nam = map { $_->{name} => 1 } @recs; if (scalar(keys %nam) > 1) { print "# ambigous sources for $name\n"; my($i) = 0; foreach (@recs) { print "# $i: ".vsn($_)." = $_->{href}\n"; $i++; } return; } } # # prefer full-source packages # if (scalar(@recs) > 1) { @nrecs = grep { ! $_->{nosource} || ! @{$_->{nosource}} } @recs; unless (@nrecs) { @nrecs = grep { $_->{href} !~ /\.nosrc.rpm$/ } @recs; } @recs = @nrecs if @nrecs; } # # nothing left -> exit # if (scalar(@recs) == 0) { return; } # # chose last (= max version) in list of targets # $rec = $recs[-1]; print "# source for $name is ".vsn($rec)."\n"; return $rec; } # # see wether target is in map # sub target_exists ($$) { my($target, $map) = @_; my($vmap) = $map->{$target->{name}}; return unless $vmap; return !defined $target->{version} || defined $vmap->{vs($target)}; } # # find target in map # sub find_target ($$) { my($name, $map) = @_; my($vmap) = $map->{$name}; my(@vs); return unless $vmap; @vs = sort { vcmp($b,$a) } keys %$vmap; return $vmap->{$vs[0]}->[-1]; } # # lookup target in map # sub target_lookup ($$) { my($target, $map) = @_; my($vmap,$vers); $vmap = $map->{$target->{name}}; return unless $vmap; $vers = $vmap->{vs($target)}; return unless $vers && @$vers; return $vers->[0]; } # # retrieve conditional target attributes in map # sub target_attribute ($$$;$) { my($target, $env, $attr, $with) = @_; my($optreg) = $env->{config}->{optreg}; my($name,@out); return unless $target; $name = $target->{name}; my($mywith) = $with ? $with : get_with($target); override_options($mywith, name_with($name, $env->{with}), $optreg); foreach (@{$target->{$attr}}) { next unless conditional($_->{'cond'}, $mywith); push @out, $_->{'value'}; } return \@out; } # # see wether target has conflicts # sub target_conflicts ($$) { my($target, $env) = @_; return target_attribute($target, $env, 'conflicts'); } # # retrieve build dependencies for target # sub target_depends ($$) { my($target, $env) = @_; return target_attribute($target, $env, 'depends'); } # # retrieve runtime dependencies for target # sub target_keeps ($$) { my($target, $env) = @_; return target_attribute($target, $env, 'keeps'); } # # retrieve source list for target # sub target_source ($$) { my($target, $env) = @_; return target_attribute($target, $env, 'source'); } # # retrieve nosource list for target # sub target_nosource ($$) { my($target, $env) = @_; return target_attribute($target, $env, 'nosource'); } # # check wether target conflicts against map # sub target_has_conflicts ($$$) { my($target, $map, $env) = @_; my($conflicts, $t); $conflicts = target_conflicts($target, $env); foreach (@$conflicts) { my($t) = find_target($_, $map); return $t if $t; } return; } # # strip doubles from depend/keep lists # and a return a map name => depend/keep # sub unique_map { my(%out); foreach (@_) { foreach (@$_) { $out{$_->{name}} = $_; } } return %out; } # # determine wether target should be rebuild # sub target_better ($$$) { my($env, $target, $map) = @_; my($vs) = vs($target); my($vmap) = $map->{$target->{name}}; # # rebuild if target isn't installed # return 'new' unless $vmap; # # if -e then # always update if installed version is different from repository # if ($env->{exact} && !grep { vcmp($vs, $_) == 0; } keys %$vmap) { return 'exact'; } # # if target is goal # always update if installed version is older than repository # if ($target->{GOAL} && !grep { vcmp($vs, $_) <= 0; } keys %$vmap) { return 'goal'; } # # if -U then # always update if installed version is older than repository # if ($env->{upgrade} && !grep { vcmp($vs, $_) <= 0; } keys %$vmap) { return 'upgrade'; } # # if -z/-Z then # always update if installed version is equal or older than repository if ($env->{zero} && grep { vcmp($vs, $_) >= 0; } keys %$vmap) { return 'zero'; } # keep installed target return; } # # filter package options # sub filter_name_with ($$$) { my($name, $with, $global) = @_; my(@keys); if ($global) { push(@keys, grep { !/::/ } keys %$with); } push(@keys, grep { /::/ } keys %$with); return { map { my($k) = $_; $k !~ /::/ || $k =~ s/^\Q$name\E::// ? ( $k => $with->{$_} ) : ( ) } @keys }; } # # filter out package relevant options # sub name_with ($$) { filter_name_with($_[0],$_[1],1); } # # filter out package specific options # sub name_only_with ($$) { filter_name_with($_[0],$_[1],0); } # # check wether installed package matches # build options # # if default = 1 then options which are not # required must be identical to the DEFOPTS. # sub target_suitable ($$$) { my($target, $with, $default) = @_; my($iwith,$dwith); my($k,$v); if ($target->{GOAL}) { $with = name_with($target->{name}, $with); } else { $with = name_only_with($target->{name}, $with); } $iwith = $target->{OPTIONS}; $dwith = $target->{DEFOPTS}; while (($k,$v) = each %$iwith) { if (exists $with->{$k}) { return 0 if $iwith->{$k} ne $with->{$k}; } elsif ($default) { return 0 if $iwith->{$k} ne $dwith->{$k}; } } return 1; } # # record target status # sub target_setstatus ($$$) { my($target, $status, $pri) = @_; if ($pri > $target->{STATUSPRI}) { $target->{STATUSPRI} = $pri; $target->{STATUS} = $status; } } # # report options that are not used for # sub warn_about_options ($$$) { my($target, $with, $c) = @_; my($iwith) = $target->{OPTIONS}; my($k,$v); return unless defined $iwith; $with = name_with($target->{name}, $with); while (($k,$v) = each %$with) { unless ($k =~ /^$c->{optreg}$/ || exists $iwith->{$k}) { print "# ATTENTION: $target->{name} ignores option '$k'\n"; } } } # # add dependency as build option # sub depend_option ($$$) { my($target,$dep,$env) = @_; my($with,$opt,$relmap,@t,$t); my($pro) = depends2provides($dep); my($conflict) = 0; return 1 unless defined $pro->{with}; my($val) = defined $pro->{version} ? $pro->{version} : 'yes'; $with = $env->{with}; $opt = $pro->{prefix}.'::'.$pro->{with}; if (defined $with->{$opt} && $with->{$opt} ne $val) { print "# ",vsn($target), " has conflicting requirement $opt = $with->{$opt} != $val\n"; $conflict = 1; } $relmap = $env->{built}->{$pro->{prefix}} || $env->{installed}->{$pro->{prefix}}; @t = get_targets($relmap, sub { 1; }); foreach $t (@t) { $with = $t->{OPTIONS}; $opt = $pro->{with}; if (defined $with->{$opt} && $with->{$opt} ne $val) { print "# ",vsn($t), " has conflicting requirement $opt = $with->{$opt} != $val\n"; $conflict = 1; } } return 0 if $conflict; print "# ",vsn($target)," adds option $opt = $val\n"; $with->{$opt} = $val; return 1; } ############################################################################ # # LOGIC # # # locate target for a dependency # sub dep2target ($$) { my($dep, $env) = @_; my($name,$op,@vers); my($i,$r,$b,$cond,$version); my($t,$tdef,$why); ($name, $op, $version) = ($dep->{name}, $dep->{op}, $dep->{val}); $i = $env->{installed}->{$name}; $r = $env->{repository}->{$name}; $b = $env->{built}->{$name}; return unless $i || $r || $b; if (!defined $op) { $cond = sub { 1; }; } elsif ($op eq 'geq') { $cond = sub { vcmp($_[0],$version) >= 0; }; } elsif ($op eq 'leq') { $cond = sub { vcmp($_[0],$version) <= 0; }; } elsif ($op eq 'gt') { $cond = sub { vcmp($_[0],$version) > 0; }; } elsif ($op eq 'lt') { $cond = sub { vcmp($_[0],$version) < 0; }; } elsif ($op eq 'equ') { $cond = sub { vcmp($_[0],$version) == 0; }; } else { die "FATAL: internal error in dep2target\n"; } $tdef = undef; # # search installed target that matches requirement # use it if we are not upgrading (no -U and no -z/-Z) # if ($i && (@vers = get_versions($i, $cond))) { foreach (@vers) { $t = $i->{$_}->[0]; next unless $t && get_with($t); if (target_suitable($t, $env->{with}, 0)) { $tdef = $t; unless ($env->{upgrade} || $env->{zero}) { return ($t, 1); } } } } # # search target in current build list that matches requirement # use it if it exists # if ($b && (@vers = get_versions($b, $cond))) { $t = $b->{$vers[0]}->[0]; return ($t, 1); } # # search target in repository and install it, if it is newer # than corresponding installed versions # avoid repository packages that would install 'new' (i.e. # are not an upgrade of an existing package) # $t = chose_source($env, $name, undef, $r, $cond); if ($t) { if (!$tdef || ( ($why = target_better($env, $t, $env->{installed})) && $why ne 'new' )) { return ($t, 0); } } # # if nothing is suitable in repository then fall back to # anything we already have installed but that we skipped # above to look for upgrades. # if ($tdef) { return ($tdef, 1); } return; } # # # sub make_dep ($$$$$$$) { my($who,$target,$depth,$env,$list,$blist,$clist) = @_; my($d,$k,%d,%k,$t,$old); my(@deps,$conflict,$why); if (target_exists($target, $env->{built})) { print "# $target->{name} is already in list\n"; return; } if ($t = target_has_conflicts($target, $env->{installed}, $env)) { target_setstatus($target,'CONFLICT',4); push(@$clist,$target); pusherr($env,$target,"$target->{name} conflicts with ".vsn($t)); return; } if ($t = target_has_conflicts($target, $env->{built}, $env)) { target_setstatus($target,'CONFLICT',4); push(@$clist,$target); pusherr($env,$target,"$target->{name} conflicts with ".vsn($t)); return; } # # see if a target is already installed and requires a rebuild # if ($t = find_target($target->{name}, $env->{installed})) { if (exists $env->{exclude}->{$target->{name}}) { print "# excluding $target->{name} (no upgrade allowed)\n"; return; } # pull in options if ($target->{REBUILD}) { target_setstatus($target,'DEPEND',1); print "# rebuilding $target->{name} (dependency)\n"; } elsif ($env->{zero}) { target_setstatus($target,'ZERO',1); print "# rebuilding $target->{name} (zero)\n"; } elsif (vs($target) ne vs($t)) { target_setstatus($target,'UPDATE',3); print "# rebuilding $target->{name} (update)\n"; } elsif (!target_suitable($t, $env->{with}, 0)) { target_setstatus($target,'MISMATCH',2); print "# rebuilding $target->{name} (parameter mismatch)\n"; } elsif ($env->{goals} && $target->{GOAL}) { target_setstatus($target,'GOAL',3); print "# rebuilding $target->{name} (goal)\n"; } else { print "# $target->{name} is already installed\n"; return; } # use options from installed base override_options(get_with($target), get_with($t), $env->{config}->{optreg}); # remember this is a rebuild for a proxy package $target->{PROXY} = $t->{PROXY}; $target->{REBUILD} = 1; } else { print "# creating $target->{name}\n"; target_setstatus($target,'ADD',3); } if (exists $env->{exclude}->{$target->{name}}) { die "FATAL: target ".vsn($target)." is forbidden\n"; } # mark this as a target before reverse dependencies trigger # it again push(@{$env->{built}->{$target->{name}}->{vs($target)}}, $target); $target->{LIMBO} = 1; $d = target_depends($target, $env); $k = target_keeps($target, $env); # # recurse over dependencies # if (@$d || @$k) { %d = unique_map($d, $k); %k = unique_map($k); @deps = (); $conflict = 0; foreach (keys %d) { # old index misses a OpenPKG provider in the index... skip it next if $_ eq 'OpenPKG'; ($t,$old) = dep2target($d{$_}, $env); if ($t) { if ($old) { print "# $target->{name} uses ".vsn($t)." for $_\n"; if ($t->{LIMBO}) { print "# ATTENTION: ".vsn($t)." is in LIMBO\n"; } next; } unless (depend_option($t, $d{$_}, $env)) { push @$clist, $target; pusherr($env,$target,"$target->{name} has conflicting requirement"); target_setstatus($target,'UNDEF',4); $conflict = 1; next; } if ($k{$_}) { push @$blist,$t; print "# $target->{name} installs ".vsn($t)." for $_\n"; } else { print "# $target->{name} requires ".vsn($t)." for $_\n"; } push @deps, $t; } else { push @$clist, $target; pusherr($env,$target,"$target->{name} searches a frood called '$_'"); target_setstatus($target,'UNDEF',4); $conflict = 1; } } unless ($conflict) { foreach $t (@deps) { make_dep($target,$t,$depth+1,$env,$list,$blist,$clist); } } } print "# adding ".vsn($target)." to list\n"; $target->{WHO} = $who; $target->{WHY} = $target->{STATUS}; push(@$list, $target); # remember new options override_options(get_with($target), name_with($target->{name}, $env->{with}), ''); # moan about non-source packages foreach (@{target_nosource($target,$env)}) { my($p) = target_source($target,$env)->[$_]; $p =~ s/.*\///; print "# ATTENTION: unpackaged source $_: $p\n"; } $target->{LIMBO} = 0; # # a dependency could not be resolved, don't bother with reverse # dependencies for this target # return if $conflict; if (!$env->{quick} && $target->{name} ne 'openpkg' ) { unless ($env->{revdep}) { $env->{revdep} = get_revdep($env, $env->{installed}); } foreach $t (@{$env->{revdep}->{$target->{name}}}) { # this is a rebuild, triggering further revdeps $t->{REBUILD} = 1; # this is a rebuild, keep this installed push(@$blist, $t); print "# rebuilding revdep ".vsn($t)."\n"; make_dep($target,$t,$depth+1,$env,$list,$blist,$clist); } } } # # grep environment for packages that match a pattern # sub search_pattern ($$) { my($pattern, $env) = @_; my(@todo); # # handle various patterns # if (defined $pattern) { @todo = map { my($p) = $_; my($s); $s = $1 if $p =~ s/(,[^\s,]+)$//; if ($p =~ s/\*+$//) { $p = '^'.quotemeta($p).''; } else { $p = '^'.quotemeta($p).'$'; } map { "$_$s" } grep { /$p/ } keys %{$env->{repository}} } split(/\s+/,$pattern); } else { # # undefined pattern means -a option that selects # all packages from repository that are installed # @todo = grep { my($n) = $_; (ref $env->{installed}->{$n}) && grep { $_ ne '-' } keys %{$env->{installed}->{$n}} } keys %{$env->{repository}}; } return \@todo; } # # generate build lists for targets matched by pattern # # all input and output is passed in 'env' hash # sub build_list ($$) { my($pattern, $env) = @_; my(@goals,@targets,@keeps,@conflicts,@bonly,$t); my($name,$select,$r,$i); my($todo,%keep); $todo = search_pattern($pattern, $env); # # chose sources for goals from repository # foreach $name (@$todo) { $select = undef; $select = $1 if $name =~ s/,([^\s,]+)$//; $t = undef; # # keeping installed packages for goals is ugly # -> we currently do not support installed source RPMs # -> source RPMs might already have expired from repository # # consequence: # -> goals are always upgraded to repository versions # #unless ($env->{upgrade}) { # $i = $env->{installed}->{$name}; # $t = chose_source($env, $name, $select, $i, sub { 1; }); #} unless ($t) { $r = $env->{repository}->{$name}; $t = chose_source($env, $name, $select, $r, sub { 1; }); } if ($t) { warn_about_options($t, $env->{with}, $env->{config}); $t->{GOAL} = 1; push @goals, $t; } else { if ($env->{status}) { print "# dropping goal '$name'\n"; } else { die "FATAL: cannot find source for '$name'\n"; } } } return unless @goals; @targets = (); @keeps = @goals; foreach $t (@goals) { print "# recursing over dependencies for ".vsn($t)."\n"; make_dep(undef,$t,0,$env,\@targets,\@keeps,\@conflicts); } %keep = map { $_ => 1 } @keeps; @bonly = reverse grep { !$keep{$_} && !$env->{installed}->{$_->{name}} } @targets; return (\@targets, \@bonly, \@conflicts); } sub build_deps ($$) { my($pattern, $env) = @_; my($todo,@list,$list,@out); $todo = search_pattern($pattern, $env); # # unfold target names into real targets # @list = map { map { map { $_->{name} } @$_ } values %{$env->{repository}->{$_}} } @$todo; # # also add target name # push @list, @$todo; # # strip duplicates # @list = keys %{ { map { $_ => 1 } @list } }; # # cache reverse dependencies # unless ($env->{revdep}) { $env->{revdep} = get_revdep($env, $env->{repository}); } # # map targets into list of dependency names # @list = map { $env->{revdep}->{$_} ? ( @{$env->{revdep}->{$_}} ) : ( ) } @list; # # recurse over dependencies # foreach (@list) { # avoiding cycles next if $env->{builddeps}->{$_->{name}}; $env->{builddeps}->{$_->{name}} = 1; push @out, $_; $list = build_deps($_->{name}, $env); push @out, @$list; } return \@out; } ####################################################################### # # OUTPUT # # # remember fatal error # sub pusherr ($$$) { my($env,$target,$mess) = @_; print "# $mess\n"; push @{$env->{fatal}}, vsn($target).": $mess\n"; } # # compute path to binary RPM from rpm config and target data # sub target2rpm ($$) { my($target,$c) = @_; my($tmpl) = $c->{template}; my($popt) = $target->{PROXY} ? '+PROXY' : ''; $tmpl =~ s/%{NAME}/$target->{name}/; $tmpl =~ s/%{VERSION}/$target->{version}/; $tmpl =~ s/%{RELEASE}/$target->{release}$popt/; return $c->{rpmdir}.'/'.$tmpl; } # # compute new target based on old target augmented with options from # a binary RPM file # sub binary_target ($$) { my($t, $fn) = @_; my(%target) = %$t; # pull in options from binary RPM file delete $target{'OPTIONS'}; get_with(\%target, $fn); return \%target; } # # return path to master package for a proxy package # sub find_proxy ($$) { my($t,$bpkg) = @_; my(@l) = run("$RPM -ql $t->{name}"); my($link) = (grep { $_ =~ /\/\.prefix-$t->{name}$/ } @l)[0]; return unless defined $link; chomp $link; my($prefix) = readlink($link); return unless defined $prefix; $bpkg =~ s/.*\///; $bpkg =~ s/\+PROXY(\.[^-]+-[^-]+)-[^-]+\.rpm$/$1-*.rpm/; return (glob("$prefix/RPM/PKG/$bpkg"))[0]; } # # merge parameters from installed package # with new parameter set and global parameters # from configuration # # then map the result to --define command line arguments # suitable for rpm # sub make_defines ($$$$) { my($old, $new, $def, $c) = @_; my($with); $old = {} unless $old; $def = {} unless $def; # # override old parameters with new parameters # drop new parameters that do not exist in old set # $old = { %$old }; override_options($old, $new, $c->{optreg}); # # convert parameters to --define command line options # skip parameter templates from index # skip parameters that are identical to defaults # $with = join(' ',map { "--define '$_ $old->{$_}'" } sort grep { $old->{$_} =~ /\S/ && $old->{$_} !~ /^%/ && $old->{$_} ne $def->{$_} } keys %$old); $with = ' '.$with if $with ne ''; return $with; } # # print commands from package build list # # c -> configuration to derive paths from # uncond -> always do the --rebuild # with -> parameter set passed to build tool # ignore -> generate script that does not stop on error # usebin -> build-time check to skip rebuild when binary exists # allbin -> usebin also for goals # sub print_list1 ($$$$$$$) { my($list,$c,$uncond,$with,$ignore,$usebin,$allbin) = @_; my($spkg,$bpkg,$ppkg); my($mywith, $opt); my($cmd1, $cmd2, $mark); $mark = '::::'; foreach (@$list) { $spkg = $_->{href}; unless ($spkg =~ /\S/) { die "FATAL: internal error, ",vsn($_)," without source URL\n"; } $bpkg = target2rpm($_, $c); $mywith = # # rebuild binary package IF # # 'unconditional' option # OR target is tagged as rebuilding # OR there is no binary package # OR dependency check found that installed package is not suitable # OR existing binary package doesn't satisfy wanted options # $cmd1 = undef; if ($uncond || !-f $bpkg || !target_suitable(binary_target($_, $bpkg), $with, 1)) { $opt = make_defines($_->{OPTIONS}, $with, $_->{DEFOPTS}, $c); # # proxy packages are rebuilt from their maste # hierachy # # someone preferred a binary from the repository # just copy it to the local store # if ($_->{PROXY}) { $ppkg = find_proxy($_,$bpkg) or die "FATAL: proxy package ",vsn($_)," does not exist\n"; # # rpm doesn't support additional parameters to the # mkproxy script # $cmd1 = npriv("$RPM$opt --makeproxy $ppkg -- -o $bpkg"); # $cmd1 = "( cd $c->{rpmdir} && ". npriv("$RPM$opt --makeproxy $ppkg"). " )"; } elsif (defined $_->{prefix}) { $cmd1 = npriv("$CURL -q -s -o $bpkg $spkg"); } else { $cmd1 = npriv("$RPM$opt --rebuild $spkg"); } } # # wrap build command with build-time check for existing # binary target # if (defined $cmd1 && ( $allbin || ($usebin && !$_->{GOAL}) ) ) { $cmd1 = "if test ! -f $bpkg ; then $cmd1 ; fi"; } # # if package exist force rpm to copy over new files # better than erasing everything and losing configuration # files # $opt = $_->{REBUILD} ? ' --force' : ''; $cmd2 = priv("$RPM$opt -Uvh $bpkg"); if ($ignore) { $cmd2 = "$cmd1 && \\\n$cmd2" if defined $cmd1; } else { if (defined $cmd1) { $cmd2 = "$cmd1 || exit \$?\n$cmd2 || exit \$?" } else { $cmd2 = "$cmd2 || exit \$?"; } } print "echo $mark $spkg $mark\n$cmd2\necho $mark $spkg = \$? $mark\n"; } } # # print commands for the temporary package list # # temporary packages are only used for building other packages # and are removed when everything is done # sub print_list2 ($$) { my($list,$c) = @_; my($pkg); foreach (@$list) { $pkg = "$_->{name}-$_->{version}-$_->{release}"; print priv("$RPM -e $pkg\n"); } } # # instead of printing a command list, print a status map # that shows all packages and how the build process would # change their status # sub print_status ($$$$$) { my($installed,$repository,$list,$bonly,$clist) = @_; my(%bonly) = map { $_ => 1 } @$bonly; my(%map,$n,@names,$t); my($old,$tag,$new); foreach (@$list, @$clist) { next unless $_->{release} =~ /\S/; $map{$_->{name}} = { rel => "$_->{version}-$_->{release}", status => $_->{STATUS} }; } foreach (@$bonly) { next unless $_->{release} =~ /\S/; $map{$_->{name}} = { rel => "$_->{version}-$_->{release}", status => 'TEMP' }; } @names = keys %map; foreach $n (keys %$installed) { next if $n =~ /::/; next if exists $map{$n}; next unless grep { $_ ne '' } keys %{$installed->{$n}}; $map{$n}->{'status'} = 'OK'; push @names,$n; } foreach $n (keys %$repository) { next if $n =~ /::/; next if exists $map{$n}; next unless grep { $_ ne '' } keys %{$repository->{$n}}; $t = find_target($n, $repository); $map{$n}->{'status'} = 'NEW'; $map{$n}->{'rel'} = vs($t); push @names,$n; } foreach $n (sort @names) { $old = join ',', map { "$n-$_" } sort grep { $_ ne '-' } keys %{$installed->{$n}}; $old = $n if $old eq ''; $tag = $map{$n}->{status}; $new = defined $map{$n}->{rel} ? " $n-$map{$n}->{rel}" : ''; printf "%-35s %-8s%s\n", $old, $tag, $new; } } # # print dependency map # sub print_map ($$$$$) { my($installed,$repository,$list,$bonly,$clist) = @_; my(%dep); foreach (@$bonly) { $_->{status} = 'TEMP'; } foreach (reverse @$list) { printf "%-35s %-8s %s\n", $_->{WHO} ? vsn($_->{WHO}) : "GOAL", $_->{WHY} ? $_->{WHY} : '???', vsn($_); } } # # print dependency list # sub print_deps ($) { my($list) = @_; print join("\n", sort map { vsn($_) } @$list),"\n"; } ####################################################################### my($config,$url,$repository,$installed,$env,$list,$bonly,$clist); my($pattern,%with,%exclude,%hint); if ($opt_a) { $pattern = undef; } else { $pattern = join(' ', @ARGV); } if ($opt_A) { $pattern = '*'; } %with = map { /([^\s=]+)(?:\=(\S+))?/ ? ($1 => (defined $2 ? $2 : 'yes')) : () } split(/\s+/, $opt_D); %exclude = map { $_ => 1 } split(/\s+/, $opt_E); %hint = map { $_ => 1 } split(/\s+/, $opt_H); $config = get_config(); if (defined $opt_p) { $config->{platform} = $opt_p; } if (defined $opt_r) { $url = $opt_r; $url .= '/' unless $url =~ /\/$/; } else { $url = get_release(); } # if we read the index from a file we can no longer deduce # repository paths from index paths. For now lets assume # that everything is below SRC/ to be compatible with # existing file indexes. if (defined $opt_f && !defined $opt_r) { $url .= 'SRC/'; } $installed = $opt_Z ? {} : get_installed(); $repository = get_index( $url.'00INDEX.rdf', $opt_f, $opt_X, $config->{platform}, $installed); $env = { config => $config, installed => $installed, repository => $repository, built => {}, revdep => undef, with => \%with, exclude => \%exclude, hint => \%hint, upgrade => ($opt_a || $opt_U), zero => ($opt_z || $opt_Z), exact => $opt_e, quick => $opt_q, status => ($opt_s || $opt_S), fatal => [], goals => $opt_g, sourceonly => ($opt_u || $opt_U || $opt_z || $opt_Z) }; if ($opt_L) { ($list) = build_deps($pattern, $env); print_deps($list); } else { ($list,$bonly,$clist) = build_list($pattern, $env); die "FATAL: cannot find package\n" unless defined $list; if ($opt_M) { print_map($installed,$repository,$list,$bonly,$clist); } elsif ($opt_S) { print_status($installed,$repository,$list,$bonly,$clist); } elsif ($opt_s) { print_status($installed,{},$list,$bonly,$clist); } else { if (@{$env->{fatal}}) { die "FATAL: errors occured while building:\n", @{$env->{fatal}}, "\n"; } print_list1($list, $config, $opt_a || $opt_u || $opt_U, $env->{with}, $opt_i, $opt_b, $opt_B); print_list2($bonly,$config) unless $opt_K; } }