##
## openpkg-build.pl -- create build scripts from package index
##
## Copyright (c) 2000-2002 Cable & Wireless Deutschland GmbH
## Copyright (c) 2000-2002 The OpenPKG Project
## Copyright (c) 2000-2002 Ralf S. Engelschall
##
## 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_i $opt_D $opt_p $opt_q/;
getopts('R:r:f:uUaAzZP:N:E:iD:p:q');
##########################################################################
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{''};
while () {
if (/^\s*\[([^\]]*)\]/) {
$env{$1} = { } unless $env{$1};
$env = $env{$1};
} elsif (my($opt,$val) = /^\-([RfruUaAzPN])\s*(.*?)\s*$/) {
$val = 1 unless defined $val;
$env->{$opt} = $val;
}
}
close(FH);
}
die "openpkg:build:USAGE: $0 [-R rpm] [-r repository] [-f index.rdf] [-uUzZiq] [-P priv-cmd] [-N non-priv-cmd] [-p platform] [-Dwith ...] [-Ename ...] ( [-aA] | patternlist )\n"
unless $#ARGV >= 0 || ($#ARGV == -1 && ($opt_a || $opt_A));
##########################################################################
sub conditional ($$) {
my($cond,$with) = @_;
my(@s,$res);
return 1 if $cond eq '';
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';
}
}
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,$RPM_PRIV,$RPM_NPRIV,$CURL,$PROG);
$RPM = $opt_R || $env{''}->{opt}->{'R'} || '@l_prefix@/bin/rpm';
$RPM = (`which $RPM` =~ m{^(/.*)})[0];
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;";
}
}
$RPM_PRIV = ($opt_P ? $opt_P." ".$RPM : $RPM);
$RPM_NPRIV = ($opt_N ? $opt_N." ".$RPM : $RPM);
$CURL = $RPM;
$CURL =~ s/\/bin\/rpm$/\/lib\/openpkg\/curl/
or die "FATAL: cannot deduce curl path from $RPM\n";
($PROG) = $0 =~ /(?:.*\/)?(.*)/;
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) = @_;
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 "$t->{version}-$t->{release}";
}
sub vsn ($) {
my($t) = @_;
return "$t->{name}-$t->{version}-$t->{release}";
}
##########################################################################
sub get_config ()
{
my($c,@q);
$c = `$RPM_NPRIV --eval '%{_rpmdir} %{_rpmfilename} %{_target_os} %{_target_cpu} %{_target_platform} %{_prefix}'`;
chomp($c);
(@q) = split(/\s+/,$c);
$q[1] =~ s/%{OS}/$q[2]/;
$q[1] =~ s/%{ARCH}/$q[3]/;
return {
rpmdir => $q[0],
template => $q[1],
platform => $q[4],
prefix => $q[5]
};
}
sub get_release () {
my($rel,$url);
($rel) =`$RPM_NPRIV -qi openpkg` =~ /Version:\s*(\S+)/m;
if ($rel =~ /^\d+$/) {
print "# $PROG current($rel)\n";
print "# using '$RPM_NPRIV' (build) and '$RPM_PRIV' (install)\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 get_installed () {
my(%map);
my(@l) = `$RPM_NPRIV --provides -qa`;
foreach (@l) {
/^(\S+)\s*(?:=\s*([^\s\-]+)-([^\s\+]+)(\+PROXY)?)?$/;
push(@{$map{$1}->{"$2-$3"}}, {
name => $1,
version => (defined $2 ? $2 : '*'),
release => (defined $3 ? $3 : '*'),
PROXY => $4
});
}
return \%map;
}
sub revdep ($$$) {
my($rev,$t,$name) = @_;
return 1 if $name eq $t->{name};
foreach (@{$rev->{$_}}) {
return 1 if revdep($rev,$t,$_->{name});
}
return -1;
}
sub get_revdep ($) {
my($env) = @_;
my($i) = $env->{'installed'};
my($r) = $env->{'repository'};
my($pkg, %rev);
my(@vers,$t,@names);
print "# computing reverse dependencies\n";
foreach $pkg (keys %$i) {
unless ($r->{$pkg}) {
print "# ATTENTION: $pkg has no upgrade path\n";
next;
}
@vers = get_versions($r->{$pkg}, sub { 1; });
foreach (@vers) {
foreach $t (@{$r->{$pkg}->{$_}}) {
next unless $i->{$t->{name}};
next unless $t->{depends};
@names = grep { $_ ne '' }
map { /^(\S+)/ }
@{$t->{depends}};
next unless @names;
push @{$rev{$_}}, $t foreach @names;
}
}
}
foreach $pkg (keys %rev) {
$rev{$pkg} = [
sort {
revdep(\%rev, $b, $a->{name});
} @{$rev{$pkg}}
];
}
return \%rev;
}
sub parse_options ($) {
my($l) = @_;
$l = [ split(/\n+/, $l) ] unless ref $l;
my(%with) = map { /--define\s*'(\S+)\s+(\S+?)'/ } @$l;
return unless %with;
return \%with;
}
sub override_options ($$) {
my($old, $new) = @_;
while (my ($k,$v) = each %$new) {
$old->{$k} = $v if exists $old->{$k};
}
}
sub get_with ($;$) {
my($t,$fn) = @_;
my(@l,%with);
unless ($t->{OPTIONS}) {
if (defined $fn) {
@l = `$RPM_NPRIV -qi -p $fn`;
} else {
@l = `$RPM_NPRIV -qi $t->{name}`;
}
$t->{OPTIONS} = parse_options(\@l);
}
return $t->{OPTIONS};
}
sub relurl ($$$) {
my($url,$fn,$suburl) = @_;
my($subfn,$submap);
unless ($suburl =~ /^\w+:\/\// || $suburl =~ /^\//) {
if (defined $fn) {
$subfn = $fn;
$subfn =~ s/\/[^\/]*$//;
$subfn .= '/' unless $subfn =~ /\/$/;
$subfn .= $suburl;
$suburl = $subfn;
} else {
$subfn = $url;
$subfn =~ s/\/[^\/]*$//;
$subfn .= '/' unless $subfn =~ /\/$/;
$suburl = "$subfn$suburl";
$subfn = undef;
}
}
return ($suburl, $subfn);
}
sub xel($) {
my($a) = @_;
my($l) = $a->[0];
return '' if ref $l;
return $l;
}
sub get_index ($$$) {
my($url,$fn,$with) = @_;
my($ua,$req,$res,$rdf);
my($bzip2,$path);
my(%map,@include);
my($fetch);
$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";
eval {
require XML::Simple;
};
if ($@) {
print "# using simple text parser\n";
my($section);
my($name,$version);
my($href,$release,$desc);
my(@prereq,@bprereq);
my(@provides,@conflicts);
my($platform,$prefix);
my($rec);
my($tag,$cond,$body);
my($useit);
while () {
s/>/>/g;
s/</([^<]*)/;
$useit = conditional($cond,$with);
if ($tag eq 'Description') {
$section = 'description';
} elsif ($tag eq '/Description') {
$section = undef;
} elsif ($section eq 'description') {
$desc .= $_;
} elsif ($tag eq 'PreReq') {
$section = 'prereq' if $useit;
} elsif ($tag eq '/PreReq') {
$section = undef;
} elsif ($tag eq 'BuildPreReq') {
$section = 'bprereq' if $useit;
} elsif ($tag eq '/BuildPreReq') {
$section = undef;
} elsif ($tag eq 'Provides') {
$section = 'provides' if $useit;
} elsif ($tag eq '/Provides') {
$section = undef;
} elsif ($tag eq 'Conflicts') {
$section = 'conflicts' if $useit;
} elsif ($tag eq '/Conflicts') {
$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') {
if ($section eq 'prereq') {
push(@prereq, $body);
} elsif ($section eq 'bprereq') {
push(@bprereq, $body);
} elsif ($section eq 'provides') {
push(@provides, $body);
} elsif ($section eq 'conflicts') {
push(@conflicts, $body);
}
} elsif ($tag eq '/rdf:Description') {
if (defined $href &&
defined $name &&
defined $version &&
defined $release) {
@provides = map {
/(\S+)\s*(?:=\s*(\S+?)\-(\S+))?$/;
{
name => $1,
version => $2,
release => $3
}
} @provides;
unless (grep($_->{name} eq $name, @provides)) {
push(@provides, {
name => $name,
version => $version,
release => $release
});
}
$rec = {
href => (relurl($url, undef, $href))[0],
name => $name,
version => $version,
release => $release,
depends => [ @bprereq ],
keeps => [ @prereq ],
conflicts => [ @conflicts ],
desc => $desc,
platform => $platform,
prefix => $prefix
};
$rec->{OPTIONS} = parse_options($rec->{desc});
foreach (@provides) {
push(@{$map{$_->{name}}->{vs($_)}}, $rec);
}
}
$href = undef;
}
}
} else {
print "# using XML parser\n";
my($xml) = XML::Simple::XMLin(\*RFH, forcearray => 1);
my($desc) = $xml->{'Repository'}->[0]->{'rdf:Description'};
my($sub) = $xml->{'Repository'}->[0]->{'Repository'};
my($provides,@provides,$rec);
my($href,$name,$version,$release);
foreach (@$desc) {
$href = $_->{'href'};
$name = xel($_->{'Name'});
$version = xel($_->{'Version'});
$release = xel($_->{'Release'});
next unless defined $href &&
defined $name &&
defined $version &&
defined $release;
$provides = $_->{'Provides'}->[0]->{'rdf:bag'}->[0]->{'rdf:li'};
@provides = map {
/(\S+)\s*(?:=\s*(\S+?)\-(\S+))?$/;
{
name => $1,
version => $2,
release => $3
}
} @$provides;
unless (grep($_->{name} eq $name, @provides)) {
push(@provides, {
name => $name,
version => $version,
release => $release
});
}
$rec = {
href => (relurl($url, undef, $href))[0],
name => $name,
version => $version,
release => $release,
platform => xel($_->{'Platform'}),
prefix => xel($_->{'Prefixes'}),
depends =>
( $_->{'BuildPreReq'}->[0]->{'rdf:bag'}->[0]->{'rdf:li'}
|| [] ),
keeps =>
( $_->{'PreReq'}->[0]->{'rdf:bag'}->[0]->{'rdf:li'}
|| [] ),
desc => xel($_->{'Description'})
};
$rec->{OPTIONS} = parse_options($rec->{desc});
foreach (@provides) {
push(@{$map{$_->{name}}->{vs($_)}}, $rec);
}
}
if ($sub) {
@include = map { $_->{href} } @$sub;
}
}
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,$with);
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;
}
#
# there can be multiple sources for a target release
#
sub chose_source ($$@) {
my($env, $name, $vmap, @vers) = @_;
my(@recs,@nrecs,$rec);
return unless @vers;
@recs = grep {
$env->{sourceonly} ? (
!(defined $_->{'platform'})
) : (
!(defined $_->{'platform'}) || (
defined $_->{'prefix'} &&
$_->{'platform'} eq $env->{config}->{platform} &&
$_->{'prefix'} eq $env->{config}->{prefix}
)
)
} map { @{$vmap->{$_}} } @vers;
return unless @recs;
if (scalar(@recs) > 1) {
@nrecs = grep {
$env->{built}->{$_->{name}} ||
$env->{installed}->{$_->{name}}
} @recs;
@recs = @nrecs if @nrecs;
}
if (scalar(@recs) > 1 && !$env->{sourceonly}) {
@nrecs = grep {
defined $_->{'platform'}
} @recs;
@recs = @nrecs if @nrecs;
}
if (scalar(@recs) > 1) {
print "# ambigous sources for $name\n";
my($i) = 0;
foreach (@recs) {
print "# $i: ".vsn($_)." = $_->{href}\n";
$i++;
}
die "ERROR: ambigous dependency\n";
} else {
if ($env->{upgrade}) {
$rec = $recs[-1];
} else {
$rec = $recs[0];
}
}
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];
}
#
# see wether target has conflicts in map
#
sub target_conflicts ($$) {
my($target, $map) = @_;
my($t);
foreach (@{$target->{conflicts}}) {
$t = find_target($_, $map);
return $t if $t;
}
return;
}
#
# retrieve build dependencies for target in map
#
sub target_depends ($$) {
my($target, $map) = @_;
my($vmap,$vers);
die "FATAL: ",vsn($target)," not in depend map\n"
unless
( $vmap = $map->{$target->{name}} ) &&
( defined $target->{version} ) &&
( $vers = $vmap->{vs($target)} ) &&
@$vers;
return $vers->[0]->{depends};
}
#
# retrieve runtime dependencies for target in map
#
sub target_keeps ($$) {
my($target, $map) = @_;
my($vmap,$vers);
die "FATAL: ",vsn($target)," not in keep map\n"
unless
( $vmap = $map->{$target->{name}} ) &&
( defined $target->{version} ) &&
( $vers = $vmap->{vs($target)} ) &&
@$vers;
return $vers->[0]->{keeps};
}
#
# test wether target could be upgraded
#
sub target_newer ($$) {
my($target, $map) = @_;
my($vs) = vs($target);
my($vmap) = $map->{$target->{name}};
return 1 unless $vmap;
return !grep { vcmp($vs, $_) <= 0; } keys %$vmap;
}
#
# check wether installed package matches
# build options
#
sub target_suitable ($$) {
my($target, $with) = @_;
my($iwith);
my($k,$v);
$iwith = $target->{OPTIONS};
while (($k,$v) = each %$with) {
if (exists $iwith->{$k}) {
return 0 if $iwith->{$k} ne $with->{$k};
}
}
return 1;
}
#
# report options that are not used for
#
sub warn_about_options ($$) {
my($target, $with) = @_;
my($iwith) = $target->{OPTIONS};
my($k,$v);
return unless defined $iwith;
while (($k,$v) = each %$with) {
if (!exists $iwith->{$k}) {
print "# ATTENTION: $target->{name} ignores option '$k'\n";
}
}
}
#
# locate target for a dependency
#
sub dep2target ($$) {
my($dep, $env) = @_;
my($name,@vers);
my($i,$r,$b,$cond,$version);
my($t);
$dep =~ s/(\S+)\s*//;
$name = $1;
$i = $env->{installed}->{$name};
$r = $env->{repository}->{$name};
$b = $env->{built}->{$name};
return unless $i || $r || $b;
if ($dep =~ /^>=\s*(\S+)$/) {
$version = $1;
$cond = sub { vcmp($_[0],$version) >= 0; };
} elsif ($dep =~ /^=\s*(\S+)$/) {
$version = $1;
$cond = sub { vcmp($_[0],$version) == 0; };
} elsif ($dep =~ /^\s*$/) {
$cond = sub { 1; };
} else {
die "FATAL: don't know how to handle PreReq: $name $dep\n";
}
if ($i && (@vers = get_versions($i, $cond))) {
foreach (@vers) {
$t = $i->{$_}->[0];
if (get_with($t), target_suitable($t, $env->{with})) {
if (!$env->{upgrade}) {
return ($t, 1);
}
}
}
}
if ($b && (@vers = get_versions($b, $cond))) {
return ($b->{$vers[0]}->[0], 1);
}
return (chose_source($env, $name, $r, get_versions($r, $cond)), 0);
}
sub make_dep ($$$$$) {
my($target,$depth,$env,$list,$blist) = @_;
my($d,$k,%d,%k,$t,$old);
if (target_exists($target, $env->{built})) {
print "# $target->{name} is already in list\n";
return;
}
if ($t = target_conflicts($target, $env->{installed})) {
print "# $target->{name} conflicts with ",vsn($t),"\n";
return;
}
if ($t = target_conflicts($target, $env->{built})) {
print "# $target->{name} conflicts with ",vsn($t),"\n";
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;
}
get_with($t);
if ($target->{REBUILD}) {
print "# rebuilding $target->{name} (dependency)\n";
} elsif ($env->{zero}) {
print "# rebuilding $target->{name} (zero)\n";
} elsif (target_newer($target, $env->{installed})) {
print "# rebuilding $target->{name} (upgrade)\n";
} elsif (!target_suitable($t, $env->{with})) {
print "# rebuilding $target->{name} (parameter mismatch)\n";
} else {
print "# $target->{name} is already installed\n";
return;
}
# use options from installed base
override_options($target->{OPTIONS}, $t->{OPTIONS});
# remember this is a rebuild for a proxy package
$target->{PROXY} = $t->{PROXY};
$target->{REBUILD} = 1;
}
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);
$d = target_depends($target, $env->{repository});
$k = target_keeps($target, $env->{repository});
#
# recurse over dependencies
#
if (@$d || @$k) {
%d = map { $_ => 1 } @$d, @$k;
%k = map { $_ => 1 } @$k;
foreach (keys %d) {
# old index misses a OpenPKG provider in the index... skip it
next if $_ eq 'OpenPKG';
($t,$old) = dep2target($_, $env);
if ($t) {
if ($old) {
print "# $target->{name} uses ".vsn($t)." for $_\n";
next;
}
# record which targets to keep in blist
if ($k{$_}) {
push(@$blist,$t);
print "# $target->{name} installs ".vsn($t)." for $_\n";
} else {
print "# $target->{name} requires ".vsn($t)." for $_\n";
}
make_dep($t,$depth+1,$env,$list,$blist);
} else {
die "FATAL: $target->{name} searches for a frood called '$_'\n";
}
}
}
print "# adding ".vsn($target)." to list\n";
push(@$list, $target);
if (!$env->{quick} &&
$target->{name} ne 'openpkg' &&
$target->{REBUILD}) {
unless ($env->{revdep}) {
$env->{revdep} = get_revdep($env);
}
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($t,$depth+1,$env,$list,$blist);
}
}
}
sub remove_list ($$$) {
my($targets, $keeps, $installed) = @_;
my(%keep);
%keep = map { $_ => 1 } @$keeps;
return [ grep {
!$keep{$_} && !$installed->{$_->{name}}->{vs($_)};
} @$targets
];
}
sub build_list ($$) {
my($pattern, $env) = @_;
my(@goals,@targets,@keeps,$bonly,$t);
my($name,$r,$i,@vers);
my(@todo);
if (defined $pattern) {
@todo = ();
foreach (split(/\s+/,$pattern)) {
next unless /\S/;
if (s/\*+$//) {
push @todo, '^'.quotemeta($_).'';
} else {
push @todo, '^'.quotemeta($_).'$';
}
}
$pattern = join('|',@todo);
@todo = grep(/$pattern/, keys %{$env->{repository}});
} else {
@todo = grep {
my($n) = $_;
(ref $env->{installed}->{$n}) &&
grep { $_ ne '-' } keys %{$env->{installed}->{$n}}
} keys %{$env->{repository}};
}
#
# chose sources for goals from repository
#
foreach $name (@todo) {
$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};
# if (@vers = get_versions($i, sub { 1; })) {
# $t = chose_source($env, $name, $i, @vers);
# }
#}
unless ($t) {
$r = $env->{repository}->{$name};
if (@vers = get_versions($r, sub { 1; })) {
$t = chose_source($env, $name, $r, @vers);
}
}
die "FATAL: no known source found for '$name'\n" unless $t;
warn_about_options($t, $env->{with});
push(@goals, $t);
}
return unless @goals;
@targets = ();
@keeps = @goals;
foreach $t (@goals) {
print "# recursing over dependencies for ".vsn($t)."\n";
make_dep($t,0,$env,\@targets,\@keeps);
}
$bonly = remove_list(\@targets, \@keeps, $env->{installed});
return (\@targets, $bonly);
}
#######################################################################
sub target2rpm ($$) {
my($target,$c) = @_;
my($tmpl) = $c->{template};
$tmpl =~ s/%{NAME}/$target->{name}/;
$tmpl =~ s/%{VERSION}/$target->{version}/;
$tmpl =~ s/%{RELEASE}/$target->{release}/;
return $c->{rpmdir}.'/'.$tmpl;
}
#######################################################################
sub binary_target ($$) {
my($t, $fn) = @_;
my(%target) = %$t;
get_with(\%target, $fn);
return \%target;
}
sub find_proxy ($$) {
my($t,$bpkg) = @_;
my(@l) = `$RPM_NPRIV -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/(\.[^-]+-[^-]+-)[^-]+\.rpm$/$1*.rpm/;
return (glob("$prefix/RPM/PKG/$bpkg"))[0];
}
sub make_defines ($$) {
my($old, $new) = @_;
my($with);
#
# override old parameters with new parameters
# drop new parameters that do not exist in old set
#
# if there is no old set at all (which happens if there
# is no template and no installed package), just use the
# new parameters and assume these are useful.
#
if ($old) {
$old = { %$old };
override_options($old, $new);
} else {
$old = $new;
}
#
# convert parameters to --define command line options
# skip parameter templates from index
#
$with = join(' ',map { "--define '$_ $old->{$_}'" }
grep { $old->{$_} !~ /^%/ } keys %$old);
$with = ' '.$with if $with ne '';
return $with;
}
sub print_list1 ($$$@$) {
my($list,$c,$uncond,$with,$ignore) = @_;
my($spkg,$bpkg,$ppkg);
my($opt);
my($cmd1, $cmd2, $mark);
$mark = '::::';
foreach (@$list) {
$spkg = $_->{href};
$bpkg = target2rpm($_, $c);
#
# rebuild binary package IF
#
# 'unconditional' option
# 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 || $_->{REBUILD} ||
!target_suitable(binary_target($_, $bpkg),$with)) {
$opt = make_defines($_->{OPTIONS}, $with);
#
# 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";
$cmd1 = "$RPM_NPRIV$opt --makeproxy -o $bpkg $ppkg";
} elsif (defined $_->{platform}) {
$cmd1 = "$CURL -q -s -o $bpkg $spkg";
} else {
$cmd1 = "$RPM_NPRIV$opt --rebuild $spkg";
}
}
#
# if package exist force rpm to copy over new files
# better than erasing everything and losing configuration
# files
#
$opt = $_->{REBUILD} ? ' --force' : '';
$cmd2 = "$RPM_PRIV$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";
}
}
sub print_list2 ($$) {
my($list,$c) = @_;
my($pkg);
foreach (@$list) {
$pkg = "$_->{name}-$_->{version}-$_->{release}";
print "$RPM_PRIV -e $pkg\n";
}
}
#######################################################################
my($config,$url,$repository,$installed,$list,$bonly);
my($pattern,%with,%exclude);
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);
$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,\%with);
($list,$bonly) = build_list($pattern, {
config => $config,
installed => $installed,
repository => $repository,
built => {},
revdep => undef,
with => \%with,
exclude => \%exclude,
upgrade => ($opt_a || $opt_U),
zero => ($opt_z || $opt_Z),
quick => $opt_q,
sourceonly => (
$opt_u ||
$opt_U ||
$opt_z ||
$opt_Z ||
scalar(%with) > 0 )
});
die "FATAL: cannot find package\n" unless defined $list;
print_list1($list,$config,$opt_a || $opt_u || $opt_U,\%with,$opt_i);
print_list2($bonly,$config);