openpkg/build.pl

Mon, 28 Jan 2013 17:37:18 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Mon, 28 Jan 2013 17:37:18 +0100
changeset 758
a2c6460cfb16
permissions
-rw-r--r--

Correct socket error reporting improvement with IPv6 portable code,
after helpful recommendation by Saúl Ibarra Corretgé on OSips devlist.

     1 ##
     2 ##  build.pl -- OpenPKG Package Building and Installing
     3 ##  Copyright (c) 2000-2012 OpenPKG GmbH <http://openpkg.com/>
     4 ##
     5 ##  This software is property of the OpenPKG GmbH, DE MUC HRB 160208.
     6 ##  All rights reserved. Licenses which grant limited permission to use,
     7 ##  copy, modify and distribute this software are available from the
     8 ##  OpenPKG GmbH.
     9 ##
    10 ##  THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
    11 ##  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
    12 ##  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
    13 ##  IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR
    14 ##  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    15 ##  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    16 ##  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
    17 ##  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
    18 ##  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
    19 ##  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
    20 ##  OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
    21 ##  SUCH DAMAGE.
    22 ##
    24 #############################################################################
    25 ##
    26 ##  MAIN PROCEDURE
    27 ##
    28 #############################################################################
    30 require 5;
    31 #use strict;
    32 my (
    33    $opt_h,
    34    $opt_R, $opt_r, $opt_f, $opt_u, $opt_U, $opt_a, $opt_A,
    35    $opt_z, $opt_Z, $opt_P, $opt_N, $opt_E, $opt_H, $opt_i,
    36    $opt_D, $opt_p, $opt_q, $opt_s, $opt_S, $opt_X, $opt_M,
    37    $opt_L, $opt_W, $opt_K, $opt_e, $opt_b, $opt_B, $opt_g,
    38    $opt_k
    39 );
    41 #   global context variables
    42 my $prg = "openpkg build";
    43 my %env = ('' => {});
    45 ##
    46 ##  OPTION PARSING
    47 ##
    49 #   parse command line options
    50 my $getopts = 'hR:r:f:uUaAzZP:N:E:H:iD:p:qsSXMLWKebBgk';
    51 getopts($getopts);
    53 #   parse configuration script options
    54 if (open(FH, "<$ENV{'HOME'}/.openpkg/build")) {
    55     my ($env) = $env{''};
    56     my ($go) = $getopts;
    57     $go =~ s/[^a-zA-Z]//g;
    58     while (my $line = <FH>) {
    59         if ($line =~ m/^\s*\[([^\]]*)\]/) {
    60             $env{$1} = {} unless ($env{$1});
    61             $env = $env{$1};
    62         } elsif (my ($opt, $val) = ($line =~ m/^\-([$go])\s*(.*?)\s*$/)) {
    63             $val = 1 unless (defined($val));
    64             if (exists($env->{$opt})) {
    65                 $env->{$opt} .= " " . $val;
    66             } else {
    67                 $env->{$opt} = $val;
    68             }
    69         }
    70     }
    71     close(FH);
    72 }
    74 #   usage sanity check and usage help
    75 sub usage {
    76     my ($rc) = @_;
    77     my $usage =
    78         "openpkg:build:USAGE: $prg [options] [pattern ...]\n" .
    79         " -a                operate on all installed packages\n" .
    80         " -A                operate on all repository packages\n" .
    81         " -R <rpm>          path to \"openpkg rpm\" command\n" .
    82         " -r <repository>   URL to package repository directory\n" .
    83         " -f <index.rdf>    URL to package repository index file\n" .
    84         " -u                ignore local binary RPMs\n" .
    85         " -U                upgrade all selected packages including dependencies\n" .
    86         " -z                rebuild from zero all selected installed packages\n" .
    87         " -Z                rebuild from zero all selected available packages\n" .
    88         " -i                ignore errors in the generated script\n" .
    89         " -q                ignore all reverse dependencies\n" .
    90         " -s                generate status map instead of shell script\n" .
    91         " -S                generate status map instead of shell script (including new)\n" .
    92         " -X                use external XML/RDF parser instead of internal one\n" .
    93         " -M                generate short dependency map instead of shell script\n" .
    94         " -L                generate list of packages in repository depending on target\n" .
    95         " -W                include dependencies as if all build options are enabled\n" .
    96         " -K                keep temporarily installed packages\n" .
    97         " -k                keep temporarily downloaded packages\n" .
    98         " -e                rebuild exact version of a package from repository\n" .
    99         " -b                build-time check existing binaries for dependencies only\n" .
   100         " -B                build-time check existing binaries for dependencies and target\n" .
   101         " -g                rebuild packages even when most recent version is installed\n" .
   102         " -P <priv-cmd>     command prefix for privileged commands\n" .
   103         " -N <non-priv-cmd> command prefix for non-privileged commands\n" .
   104         " -p <platform>     match platform against repository index for binary packages\n" .
   105         " -E <name>         exclude package\n" .
   106         " -H <name>         hint about packages to resolve ambiquity\n" .
   107         " -D <name>[=<val>] set build option for packages\n";
   108     if ($rc == 0) {
   109         print STDOUT $usage;
   110     }
   111     else {
   112         print STDERR $usage;
   113     }
   114     exit($rc);
   115 }
   116 if ($opt_h) {
   117     usage(0);
   118 }
   119 if (not (   ($#ARGV >= 0  && !($opt_a || $opt_A))
   120          || ($#ARGV == -1 &&  ($opt_a || $opt_A)))) {
   121     usage(1);
   122 };
   124 #   determine RPM run-time information
   125 my $config = rpm_runtime_info();
   127 #   override command line options with configuration script options
   128 #   now that the effectively used OpenPKG RPM command is known
   129 foreach my $env (sort { $a cmp $b } grep {
   130        $config->{"rpm"}    =~ m/^\Q$_\E/ # compatibility
   131     or $config->{"prefix"} =~ m/^\Q$_\E/ # regular
   132 } keys %env) {
   133     while (my ($opt, $val) = each(%{$env{$env}})) {
   134         eval "\$opt_$opt = '$val' unless defined \$opt_$opt;";
   135     }
   136 }
   138 ##
   139 ##  OPTION POST-PROCESSING
   140 ##
   142 my ($url, $repository, $installed, $env, $list, $bonly, $clist);
   143 my ($pattern, %with, %exclude, %hint);
   145 #   determine package goal pattern
   146 if ($opt_a) {
   147     $pattern = undef;
   148 } else {
   149     $pattern = join(' ', @ARGV);
   150 }
   151 if ($opt_A) {
   152     $pattern = '*';
   153 }
   155 #   parse build options
   156 %with = map {
   157       m/([^\s=]+)(?:\=(\S+))?/
   158     ? ($1 => (defined($2) ? $2 : 'yes'))
   159     : ()
   160 } split(/\s+/, $opt_D);
   162 #   split accumulated option values
   163 %exclude = map { $_ => 1 } split(/\s+/, $opt_E);
   164 %hint    = map { $_ => 1 } split(/\s+/, $opt_H);
   166 if (defined($opt_p)) {
   167     $config->{platform} = $opt_p;
   168 }
   170 #   determine RPM package repository information
   171 if (defined $opt_r) {
   172     $url = $opt_r;
   173     $url .= '/' unless $url =~ m/\/$/;
   174 } else {
   175     $url = rpm_release_url();
   176 }
   177 #   if we read the index from a file we can no longer deduce
   178 #   repository paths from index paths. For now lets assume
   179 #   that everything is below SRC/ to be compatible with
   180 #   existing file indexes.
   181 if (defined($opt_f) and not defined($opt_r)) {
   182     $url .= 'SRC/';
   183 }
   185 #   determine information about INSTALLED packages (virtual and regular),
   186 #   including their options, provides and requirements
   187 my $installed = get_installed();
   189 #   SPECIAL CASE post-processing for
   190 #   -Z (ignore installed packages)
   191 #   -a (operate for all installed packages)
   192 if ($opt_a and $opt_Z) {
   193     #   This allows one to correctly upgrade an existing OpenPKG
   194     #   instance to a newer major version by querying all installed
   195     #   packages and their options (-a) but then ignore them (-Z) during
   196     #   the later processing and instead perform more or less a fresh
   197     #   rebuild from scratch. This ensures that during the process the
   198     #   installed packages are effectively picked up as dependencies
   199     #   only after they in turn were already updated.
   200     foreach my $package (keys(%{$installed})) {
   201         next if ($package =~ m/::/);
   202         if (exists($installed->{$package}->{""})) {
   203             #   virtual package
   204             $hint{$installed->{$package}->{""}->[0]->{"name"}} = 1
   205                 if (exists($installed->{$package}->{""}->[0]->{"name"}));
   206         }
   207         else {
   208             #   regular package
   209             $pattern .= " $package";
   210             foreach my $version (keys(%{$installed->{$package}})) {
   211                 foreach my $rec (@{$installed->{$package}->{$version}}) {
   212                     if (defined($rec->{"OPTIONS"})) {
   213                         my $options = $rec->{"OPTIONS"};
   214                         foreach my $option (keys(%{$options})) {
   215                             $with{$package."::".$option} = $options->{$option};
   216                         }
   217                     }
   218                 }
   219             }
   220         }
   221     }
   222 }
   223 if ($opt_Z) {
   224     $installed = {};
   225 }
   227 #   determine information about AVAILABLE packages
   228 #   by fetching and parsing a package repository XML/RDF index
   229 $repository = get_index(
   230     $url . '00INDEX.rdf',
   231     $opt_f,
   232     $opt_X,
   233     $config->{platform},
   234     $installed
   235 );
   237 #   assemble together all determined environment information
   238 $env = {
   239     config     => $config,
   240     installed  => $installed,
   241     repository => $repository,
   242     built      => {},
   243     revdep     => undef,
   244     with       => \%with,
   245     exclude    => \%exclude,
   246     hint       => \%hint,
   247     upgrade    => ($opt_a || $opt_U),
   248     zero       => ($opt_z || $opt_Z),
   249     exact      => $opt_e,
   250     quick      => ($opt_q || $opt_z || $opt_Z),
   251     status     => ($opt_s || $opt_S),
   252     fatal      => [],
   253     goals      => $opt_g,
   254     sourceonly => ($opt_u || $opt_U || $opt_z || $opt_Z)
   255 };
   257 ##
   258 ##  PERFORM REQUESTED OPERATION
   259 ##
   261 if ($opt_L) {
   262     #   case 1: calculate dependencies only and
   263     #           print packages depending on target
   264     ($list) = build_deps($pattern, $env);
   265     print_deps($list);
   266 } else {
   267     #   case 2: calculate build commands and
   268     #           print results in different formats
   269     ($list, $bonly, $clist) = build_list($pattern, $env);
   270     die "openpkg:build:FATAL: cannot find package\n" if (not defined($list));
   271     if ($opt_M) {
   272         print_map($installed, $repository, $list, $bonly, $clist);
   273     } elsif ($opt_S) {
   274         print_status($installed, $repository, $list, $bonly, $clist);
   275     } elsif ($opt_s) {
   276         print_status($installed, {}, $list, $bonly, $clist);
   277     } else {
   278         if (@{$env->{fatal}}) {
   279             die "openpkg:build:FATAL: errors occured while building:\n", @{$env->{fatal}}, "\n";
   280         }
   281         print_list1($list, $config, $opt_a || $opt_u || $opt_U, $env->{with}, $opt_i, $opt_b, $opt_B);
   282         print_list2($bonly, $config) if (not $opt_K);
   283     }
   284 }
   286 #   die gracefully
   287 exit(0);
   289 #############################################################################
   290 ##
   291 ##  FUNCTIONS: PARSING & RUN-TIME INFORMATION
   292 ##
   293 #############################################################################
   295 #   home-brewn getopt(3) style option parser
   296 sub getopts ($) {
   297     my ($opts) = @_;
   298     my (%optf) = map { m/(\w)/; $1 => $_ } $opts =~ m/(\w:|\w)/g;
   299     my (%opts, @argv, $optarg);
   301     foreach (@ARGV) {
   302         if (@argv) {
   303             push @argv, $_;
   304         } elsif (defined $optarg) {
   305             if (exists $opts{$optarg}) {
   306                 $opts{$optarg} .= " $_";
   307             } else {
   308                 $opts{$optarg} = $_;
   309             }
   310             $optarg = undef;
   311         } elsif (!/^[-]/) {
   312             push @argv, $_;
   313         } else {
   314             while (/^\-(\w)(.*)/) {
   315                 if (exists $optf{$1}) {
   316                     if (length($optf{$1}) > 1) {
   317                         if ($2 ne '') {
   318                             if (exists $opts{$1}) {
   319                                 $opts{$1} .= " $2";
   320                             } else {
   321                                 $opts{$1} = $2;
   322                             }
   323                         } else {
   324                             $optarg = $1;
   325                         }
   326                         last;
   327                     } else {
   328                         $opts{$1} = 1;
   329                     }
   330                 } else {
   331                     warn "openpkg:build:WARNING: unknown option $_\n";
   332                 }
   333                 $_ = "-$2";
   334             }
   335         }
   336     }
   337     if (defined $optarg) {
   338         warn "openpkg:build:WARNING: option $optarg requires an argument\n";
   339     }
   340     foreach (keys %opts) {
   341         eval '$opt_'.$_.' = "'.quotemeta($opts{$_}).'";';
   342     }
   343     @ARGV = @argv;
   344 }
   346 #   determine RPM run-time information
   347 sub rpm_runtime_info () {
   348     #   determine OpenPKG instance prefix via
   349     #   1. the environment of the "openpkg build" framework
   350     #   2. the installation path of the script
   351     #   3. the installation path of the Perl interpreter
   352     #   4. the path of the "openpkg" command in $PATH
   353     my $l_prefix = $ENV{'OPENPKG_PREFIX'};
   354     if (not $l_prefix) {
   355         ($l_prefix) = ($0 =~ m/^(.+)\/lib(exec)?\/openpkg(-tools)?\/build(\.pl)?$/);
   356     }
   357     if (not $l_prefix) {
   358         ($l_prefix) = ($^X =~ m/^(.+)\/bin\/perl.*$/);
   359     }
   360     if (not $l_prefix) {
   361         $l_prefix = (`(which openpkg) 2>/dev/null` =~ m/^(.+)\/bin\/openpkg$/);
   362     }
   363     if (not -x "$l_prefix/bin/openpkg") {
   364         die "openpkg:build:FATAL: cannot determine OpenPKG instance prefix";
   365     }
   366     print "# operating with OpenPKG instance $l_prefix\n";
   368     #   determine OpenPKG RPM command
   369     my $rpm = $opt_R || $env{''}->{'R'} ||
   370         ((-f "$l_prefix/bin/openpkg" && -f "$l_prefix/libexec/openpkg/rpm") ?
   371         "$l_prefix/bin/openpkg rpm" : "$l_prefix/bin/rpm");
   372     $rpm = (`(which $rpm) 2>/dev/null` =~ m{^(/.*)})[0] if ($rpm !~ m|^/|);
   373     die "openpkg:build:FATAL: cannot locate OpenPKG RPM in path" unless ($rpm =~ m{^/});
   374     print "# operating with OpenPKG RPM $rpm\n";
   376     #   determine additional tools
   377     my $mkp = "$l_prefix/bin/openpkg makeproxy";
   378     my $rel = "$l_prefix/bin/openpkg release";
   379     my $bzip2 = $rpm;
   380     $bzip2 =~ s/\/bin\/openpkg rpm$/\/lib\/openpkg\/bzip2/;
   381     my $curl = $rpm;
   382     $curl =~ s/\/bin\/openpkg rpm$/\/lib\/openpkg\/curl/;
   383     $curl = "$l_prefix/bin/openpkg curl" if (system("$l_prefix/bin/openpkg curl file://$l_prefix/etc/openpkg/platform >/dev/null 2>&1") == 0);
   385     #   expand RPM macros holding information
   386     my $c = run("$rpm --eval '%{_rpmdir} %{_rpmfilename} %{_target_os} %{_target_cpu} %{_srcrpmdir}'");
   388     #   parse and post-process information
   389     chomp($c);
   390     my (@q) = split(/\s+/, $c);
   391     $q[1] =~ s/%{OS}/$q[2]/;
   392     $q[1] =~ s/%{ARCH}/$q[3]/;
   394     #   expand RPM rc information about tools
   395     $c = run("$rpm --showrc");
   396     my @g = ($c =~ m/\%\{l_tool_locate\s+([^\s\}]+)/g);
   398     #   return accumulated information
   399     return {
   400         rpm      => $rpm,
   401         mkp      => $mkp,
   402         rel      => $rel,
   403         bzip2    => $bzip2,
   404         curl     => $curl,
   405         rpmdir   => $q[0],
   406         srcrpmdir=> $q[4],
   407         template => $q[1],
   408         platform => '',
   409         prefix   => $l_prefix,
   410         optreg   => '(?:'. join('|', map { "\Quse_$_\E" } @g) .')'
   411     };
   412 }
   414 #   determine RPM release URL
   415 sub rpm_release_url ($$) {
   416     my ($rel, $url);
   418     #   determine the release URL the newer way
   419     $url = run("(".$config->{"rel"}." --fmt='%u') 2>/dev/null || true") || "";
   420     $url =~ s/^\s+//s;
   421     $url =~ s/\s+$//s;
   423     #   use a local jumpstart RDF
   424     if (-f $config->{"prefix"}."/etc/openpkg/build.rdf") {
   425         $url = "file://".$config->{"prefix"}."/etc/openpkg/build.rdf"
   426     }
   428     return $url;
   429 }
   431 #############################################################################
   432 ##
   433 ##  FUNCTIONS: VERSION STRING HANDLING
   434 ##
   435 #############################################################################
   437 #   compare two package versions
   438 #   - "openpkg rpm":
   439 #     splits according to !isalnum(3) ([a-zA-Z0-9])
   440 #     and between isdigit(3) ([0-9]) and isalpha(3) ([a-zA-Z])
   441 #   - "openpkg build" (this):
   442 #     splits on "." characters
   443 sub vcmp_version ($$) {
   444     my ($a, $b) = @_;
   445     my (@a, @b, $c);
   446     my ($ax, $bx);
   448     #   split according to dots
   449     @a = split(/\./, $a);
   450     @b = split(/\./, $b);
   452     #   compare as long as components exist
   453     while (@a && @b) {
   454         if ($a[0] =~ m/^\d+$/ && $b[0] =~ m/^\d+$/) {
   455             #   numerical comparison
   456             $c = $a[0] <=> $b[0];
   457         } elsif ((($a, $ax) = $a[0] =~ m/^(\d+)(.*)$/) &&
   458                  (($b, $bx) = $b[0] =~ m/^(\d+)(.*)$/)) {
   459             #   numerical comparison for prefix,
   460             #   string comparison for remainder
   461             $c = $a <=> $b;
   462             $c = $ax cmp $bx unless ($c);
   463         } else {
   464             #   string comparison
   465             $c = $a[0] cmp $b[0];
   466         }
   468         #   stop comparison if components already mismatched
   469         return $c if ($c != 0);
   471         #   else reduce by one component level
   472         shift(@a);
   473         shift(@b);
   474     }
   476     #   finally compare number of remaining components
   477     #   (in case one is more specific)
   478     $c = (scalar(@a) <=> scalar(@b));
   479     return $c;
   480 }
   482 #   compare two package releases
   483 #   - "openpkg rpm":
   484 #     uses "vcmp_version" semantics 1:1 again
   485 #   - "openpkg build" (this):
   486 #     uses "vcmp_version" semantics 1:1 again (>= 20060719)
   487 #     trivial string comparison               (<= 20060719)
   488 sub vcmp_release ($$) {
   489     my ($a, $b) = @_;
   491     return vcmp_version($a, $b);
   492 }
   494 #   compare two package "version" or "version-release" strings
   495 #   - "openpkg rpm":
   496 #     compares "epoch", "version", "release" (in this order)
   497 #   - "openpkg build" (this):
   498 #     compares "release", "version", (in this order)
   499 sub vcmp ($$) {
   500     my ($a, $b) = @_;
   501     my ($c);
   503     #   short-circuit comparison for simple case
   504     return 0 if ($a eq $b);
   506     #   split into "version" and "release"
   507     my ($av, $ar) = ($a =~ m/^(.*?)(?:\-([\d\.]+))?$/);
   508     my ($bv, $br) = ($b =~ m/^(.*?)(?:\-([\d\.]+))?$/);
   510     #   compare "release"
   511     if (defined($ar) and defined($br)) {
   512         $c = vcmp_release($ar, $br);
   513         return $c if ($c); # short-circuit
   514     }
   516     #   compare "version"
   517     if (defined($av) && defined($bv)) {
   518         $c = vcmp_version($av, $bv);
   519         return $c if ($c); # short-circuit
   520     }
   522     #   equality
   523     return 0;
   524 }
   526 #   create "version" or "version-release" string
   527 #   from a provide object (see parse_provides)
   528 sub vs ($) {
   529     my ($t) = @_;
   530     return (
   531           defined($t->{release})
   532         ? "$t->{version}-$t->{release}"
   533         : $t->{version}
   534     );
   535 }
   537 #   create "name-version" or "name-version-release" string
   538 #   from a provide object (see parse_provides)
   539 sub vsn ($) {
   540     my ($t) = @_;
   541     return "$t->{name}-".vs($t);
   542 }
   544 #############################################################################
   545 ##
   546 ##  FUNCTIONS: INSTALLATION INFORMATION
   547 ##
   548 #############################################################################
   550 #   parse OpenPKG RPM 'provide' string
   551 #   "<virtual-name>"               (virtual package)
   552 #   "<name> = <version>-<release>" (regular package)
   553 #   "<name>::<option> = <value>"   (regular package build option)
   554 sub parse_provides ($) {
   555     my ($s) = @_;
   556     my ($nam, $val, $pre, $with, $pxy, $ver, $rel);
   558     ($nam, $val) = ($s =~ m/^([^\s\(]+(?:\([^\)]*\))?)\s*(?:=\s*(\S*?))?$/);
   559     if (($pre, $with) = ($nam =~ m/^(\S+?)::(\S*)$/)) {
   560         #   build option
   561         $val =~ s/(?:\%([0-9a-fA-F][0-9a-fA-F]))/chr(hex($1))/eg; # hex decode
   562         ($ver, $rel, $pxy) = ($val, undef, undef);
   563     } else {
   564         #   virtual or real package
   565         ($ver, $rel, $pxy) = ($val =~ m/^([^\s\-]+)-([^\s\+]+)(\+PROXY)?$/);
   566     }
   568     #   return accumulated information
   569     return {
   570         name    => $nam,     # the full name of the resource
   571         version => $ver,     # the version        (or value)
   572         release => $rel,     # and release number (or undef)
   573         proxy   => $pxy,     # whether the resource is a PROXY resource (or undef)
   574         prefix  => $pre,     # the packagename (if resource is an option)
   575         with    => $with     # the buildoption (if resource is an option)
   576     };
   577 }
   579 #   parse option from RPM 'provides' list
   580 sub parse_provideslist ($) {
   581     my ($l) = @_;
   582     my ($p);
   583     my ($nam, $val, %opts);
   585     foreach (@$l) {
   586         $p = parse_provides($_);
   587         next if (not (defined $p->{with} && defined $p->{prefix}));
   588         $opts{$p->{with}} = $p->{version};
   589     }
   590     return \%opts;
   591 }
   593 #   translate dependency object into provides object
   594 sub depends2provides ($) {
   595     my ($dep) = @_;
   596     my ($ver, $rel, $pxy, $pre, $with);
   598     ($ver, $rel, $pxy) = ($dep->{val} =~ m/^([^\s\-]+)-([^\s\+]+)(\+PROXY)?$/);
   599     ($pre, $with) = ($dep->{name} =~ m/^(\S+?)::(\S*)$/);
   601     return {
   602         name    => $dep->{name},
   603         version => (defined $ver ? $ver : $dep->{val}),
   604         release => $rel,
   605         proxy   => $pxy,
   606         prefix  => $pre,
   607         with    => $with
   608     }
   609 }
   611 #   parse OpenPKG RPM 'require' string
   612 #   "<virtual-name>"                              (virtual package)
   613 #   "<name> =|<|<=|>|>= <version>[-<release>]"    (regular package)
   614 #   "<name>::<option> =|<|<=|>|>= <value>"        (regular package build option)
   615 sub parse_depends ($) {
   616     my ($dep) = @_;
   617     my ($name, $op, $val);
   619     if (ref($dep)) {
   620         #   dependency from new index stored as a node
   621         #   - content of the node is the name
   622         #   - certain attributes denote the comparison operator
   623         #   - the value of such an attribute is the comparison operand
   624         #   - the operator (and operand) are optional and there can only be one
   625         $name = $dep->{content};
   626         $op = undef;
   627         $op = 'equ' if (exists($dep->{equ}));
   628         $op = 'geq' if (exists($dep->{geq}));
   629         $op = 'leq' if (exists($dep->{leq}));
   630         $op = 'gt'  if (exists($dep->{gt}));
   631         $op = 'lt'  if (exists($dep->{lt}));
   632         if (defined($op)) {
   633             $val = $dep->{$op};
   634         }
   635     } elsif ($dep =~ m/\S/) {
   636         #   dependency from old index stored as text string
   637         #   "name operator operand" or "name"
   638         ($name, $op, $val) = ($dep =~ m/(\S+)\s*(?:(\S+)\s*(\S+))?\s*$/);
   639         if (defined($op)) {
   640             $op = {
   641                 '=='  => 'equ', '='   => 'equ',
   642                 '>='  => 'geq', '=>'  => 'geq',
   643                 '<='  => 'leq', '=<'  => 'leq',
   644                 '>'   => 'gt',  '<'   => 'lt'
   645             }->{$op};
   646             if (not defined($op)) {
   647                 print "# don't know how to handle dependency: $dep (invalid operator)\n";
   648                 return;
   649             }
   650         }
   651     }
   652     return {
   653         name => $name,
   654         op   => $op,
   655         val  => $val
   656     };
   657 }
   659 #   retrieve the local installed package base.
   660 #   for packages that provide option resources (packagename::buildoption)
   661 #   the options are parsed into the OPTIONS hash.
   662 #   other packages will query options on demand.
   663 sub get_installed () {
   664     my (%map);
   665     my (@l, $p);
   666     my ($nam, $val, %options);
   667     my ($vs, $rec, @list);
   668     my ($name, $version, $release);
   669     my ($req);
   671     #   generated total result:
   672     #   $map = {
   673     #       #   regular package
   674     #       "<package-name>" <foo> => {
   675     #            "<version>-<release>" <1.2.3-20060622> => [
   676     #                <<1>{
   677     #                    "name"    => $name,
   678     #                    "version" => $version,
   679     #                    "release" => $release,
   680     #                    "PROXY"   => $proxy,
   681     #                    "depends" => [
   682     #                          <<3>>{
   683     #                              "cond"  => '',
   684     #                              "value" => {
   685     #                                  name => $name,
   686     #                                  op   => $op,
   687     #                                  val  => $val
   688     #                              },
   689     #                          },
   690     #                          ...
   691     #                    ],
   692     #                    "keeps" => [
   693     #                          \<<3>
   694     #                          ...
   695     #                    ],
   696     #                    "OPTIONS" => {
   697     #                        "<option>" => "<value>",
   698     #                        "<option>" => "<value>",
   699     #                        ...
   700     #                    },
   701     #                },
   702     #                ...
   703     #            ],
   704     #       },
   705     #       #   build option
   706     #       "<package-name>::<option>" <foo::with_baz> => {
   707     #            "<value>" <yes> => [
   708     #                \<<1>>
   709     #                ...
   710     #            ],
   711     #       },
   712     #       #   virtual package
   713     #       "<package-name>" <BAR> => {
   714     #            "" => [
   715     #                \<<1>>,
   716     #                ...
   717     #            ],
   718     #       },
   719     #       ...
   720     #   };
   722     #   query and parse all provides of all packages
   723     #   HINT: We assume(!) that OpenPKG RPM outputs "provides" in order:
   724     #         1. virtual package & build option
   725     #         2. regular package
   726     #   FIXME: The better long-term solution for all this fiddling would be something like:
   727     #          "openpkg rpm -qa -qf '%{NAME} %{VERSION} %{RELEASE}[ .%{PROVIDENAME} .%{PROVIDEFLAGS:depflags} .%{PROVIDEVERSION}]\\n'"
   728     @l = run($config->{"rpm"}. " --provides -qa");
   729     @list = ();
   730     foreach (@l) {
   731         #   parse into provide object
   732         $p = parse_provides($_) or next;
   734         #   short-circuit processing for RPM special case
   735         next if ($p->{name} =~ m/^gpg\(/);
   737         #   is this an option?
   738         if (defined($p->{with})) {
   739             $options{$p->{prefix}}->{$p->{with}} = $p->{version};
   740             push(@list, $p);
   741             next;
   742         }
   744         #   is this a virtual target?
   745         $vs = vs($p);
   746         if ($vs eq '') {
   747             push(@list, $p);
   748             next;
   749         }
   751         #   assemble package details
   752         $name    = $p->{name};
   753         $version = defined($p->{version}) ? $p->{version} : '*';
   754         $release = defined($p->{release}) ? $p->{release} : '*';
   755         push(@list, {
   756             name    => $name,
   757             version => $version,
   758             release => $release
   759         });
   761         #   create target record
   762         $rec = {
   763             name       => $name,
   764             version    => $version,
   765             release    => $release,
   766             PROXY      => $p->{proxy},
   767             depends    => [],
   768             keeps      => []
   769         };
   770         foreach (@list) {
   771             push(@{$map{$_->{name}}->{vs($_)}}, $rec);
   772         }
   774         #   remove assembled details
   775         @list = ();
   776     }
   777     if (@list) {
   778         print "# ATTENTION: ", scalar(@list), " froods (unassignable RPM 'provides') left\n";
   779     }
   781     #   options are provided for a package,
   782     #   apply them to all instances of the package
   783     #   FIXME: duplicate copying because record exists multiple times (but harmless)
   784     #   FIXME: merges all "provides" of all package instances together -- which might be wrong
   785     foreach $nam (keys(%options)) {
   786         foreach $val (keys(%{$map{$nam}})) {
   787             foreach (@{$map{$nam}->{$val}}) {
   788                 $_->{OPTIONS} = $options{$nam};
   789             }
   790         }
   791     }
   793     #   query all 'requires' of all installed packages
   794     #   to determine the package dependencies
   795     @l = run($config->{"rpm"} . " --qf '%{NAME}:::%{VERSION}:::%{RELEASE}[ :::%{REQUIRENAME}:::%{REQUIREFLAGS:depflags}:::%{REQUIREVERSION}:::]\\n' -qa");
   796     @list = ();
   797     foreach (@l) {
   798         ($name, $version, $release, $req) = m/^([^:]+):::([^:]+):::([^:]+)(.*?)$/;
   799         next if ($name eq 'gpg-pubkey');
   800         $release =~ s/\+PROXY$//;
   801         #   for each requirement triple...
   802         while ($req =~ m/\s+:::(.+?):::\s*(.*?)\s*:::(.*?):::/g) {
   803             $p = parse_depends("$1 $2 $3");
   804             next if ($p->{name} =~ m/^(rpmlib|gpg)\(/);
   805             $vs = vs({ version => $version, release => $release });
   806             $p = { cond => '', value => $p };
   807             foreach $rec (@{$map{$name}->{$vs}}) {
   808                 push(@{$rec->{depends}}, $p);
   809                 push(@{$rec->{keeps}},   $p);
   810             }
   811         }
   812     }
   813     if (@list) {
   814         print "# ATTENTION: ",scalar(@list)," fnords (unassignable RPM 'requires') left\n";
   815     }
   817     #   return final result
   818     return \%map;
   819 }
   821 #############################################################################
   822 ##
   823 ##  FUNCTIONS: REPOSITORY INDEX INFORMATION
   824 ##
   825 #############################################################################
   827 #   fetch XML/RDF index from file or URL
   828 #   (recursively fetches sub-indexes, too)
   829 sub get_index ($$$$$) {
   830     my ($url, $fn, $xml, $pfmatch, $installed) = @_;
   831     my (%map, $include);
   832     my ($fetch, $bzip2, $path);
   833     my ($parser);
   835     #   determine command/path to fetch/open index
   836     $bzip2 = $config->{"bzip2"};
   837     $fetch = defined($fn) ? $fn : $url;
   838     $fetch !~ m/\.bz2$/ || -x $bzip2
   839         or die "openpkg:build:FATAL: $bzip2 not found\n";
   840     if ($fetch =~ m/^\w+:/) {
   841         #   looks like URL scheme
   842         print "# fetching XML/RDF index from URL $fetch\n";
   843         $path = $config->{"curl"} . " -s -o - \"$fetch\" |";
   844         $path .= "$bzip2 -dc |" if ($fetch =~ m/\.bz2$/);
   845     } else {
   846         print "# reading XML/RDF index from file $fetch\n";
   847         if ($fetch =~ m/\.bz2$/) {
   848             $path = "$bzip2 -dc $fetch |";
   849         } else {
   850             $path = "<$fetch";
   851         }
   852     }
   854     #   open index
   855     open(RFH, $path) or
   856         die "openpkg:build:FATAL: cannot open '$fetch' ($!)\n";
   858     #   if XML parser can be used, try to lazy-load it
   859     if ($xml) {
   860         eval { require XML::Simple; };
   861         $xml = 0 if ($@);
   862     }
   864     #   determine and run XML parser
   865     #   (returns contained index includes)
   866     $parser = ($xml ? \&xml_parser : \&simple_text_parser);
   867     $include = $parser->(\*RFH, $url, \%map, $pfmatch, $installed);
   869     #   close index
   870     close(RFH)
   871         or die "openpkg:build:FATAL: an I/O error occured\n";
   873     #   cannot do real recursions on file handles, so we simply append
   874     #   (instead of inserting at the correct position) all sub-RDFs, as
   875     #   the result is flattend into a big hash anyway
   876     foreach (@$include) {
   877         my ($submap);
   878         my ($suburl, $subfn) = relurl($url, $fn, $_);
   879         $submap = get_index($suburl, $subfn, $xml, $pfmatch, $installed); # RECURSION
   880         while (my ($name, $vmap) = each(%$submap)) {
   881             while (my ($vs, $recs) = each(%$vmap)) {
   882                 push(@{$map{$name}->{$vs}}, @$recs);
   883             }
   884         }
   885     }
   887     #   return final result
   888     #   $map = {
   889     #       <package-name> => {
   890     #           "<version>-<release>" => {
   891     #                href      => ...,
   892     #                name      => ...,
   893     #                version   => ...,
   894     #                release   => ...,
   895     #                platform  => ...,
   896     #                prefix    => ...,
   897     #                depends   => [ ... ],
   898     #                keeps     => [ ... ],
   899     #                conflicts => [ ... ],
   900     #                source    => ...,
   901     #                nosource  => ...,
   902     #                desc      => $desc,
   903     #                OPTIONS   => $options,
   904     #                DEFOPTS   => { %$options },
   905     #           };
   906     #       };
   907     #   };
   908     return \%map;
   909 }
   911 #   compute absolute paths
   912 #   - (url, fn) point to a base document
   913 #     the location is the file path fn if fn is
   914 #     defined, otherwise it is url.
   915 #   - augment the pointer with suburl
   916 #   - suburl can be an absolute url
   917 #     then the new pointer is (suburl, undef)
   918 #   - suburl can be a absolute file path
   919 #     then the new pointer is (suburl, suburl)
   920 #   - suburl can be a relative path
   921 #     then it augments url or fn accordingly
   922 sub relurl ($$$) {
   923     my ($url, $fn, $suburl) = @_;
   924     my ($subfn);
   926     if ($suburl =~ m/^\w+:\/\//) {
   927         # NOP
   928     } elsif ($suburl =~ m/^\//) {
   929         $subfn = $suburl;
   930     } else {
   931         if (defined($fn)) {
   932             $subfn = $fn;
   933             $subfn =~ s/(\/)?\/*[^\/]*$/$1$suburl/;
   934             $suburl = $subfn;
   935         } else {
   936             $subfn = $url;
   937             $subfn =~ s/(\/)?\/*[^\/]*$/$1$suburl/;
   938             $suburl = $subfn;
   939             $subfn  = undef;
   940         }
   941     }
   942     1 while ($suburl =~ s/\/\.\//\//s);
   943     1 while ($suburl =~ s/\/[^\/]+\/\.\.\//\//s);
   944     return ($suburl, $subfn);
   945 }
   947 #   XML/RDF parser (simple way)
   948 sub simple_text_parser ($$$$$) {
   949     my ($fh, $url, $map, $pfmatch, $installed) = @_;
   950     my (@include);
   951     my ($section);
   952     my ($name, $version);
   953     my ($href, $release, $desc, $bags);
   954     my (%options, @provides);
   955     my ($platform, $prefix);
   956     my ($rec);
   957     my ($tag, $cond, $attrname, $attrval, $body);
   958     my ($usecond);
   959     my ($options);
   961     print "# using internal XML/RDF parser\n";
   963     #   read XML/RDF line-wise as we know that our
   964     #   OpenPKG XML/RDF indices follow a strict formatting
   965     while (<$fh>) {
   966         #   unescape some XML entities
   967         s/&gt;/>/g;
   968         s/&lt;/</g;
   970         if (!(defined($href)) && m/<rdf:Description.*?href="([^"]*)"/) {
   971             #   start of new package description
   972             $href      = $1;
   973             $section   = undef;
   974             $name      = undef;
   975             $release   = undef;
   976             $desc      = '';
   977             $platform  = undef;
   978             $prefix    = undef;
   979             $bags      = {};
   980             @provides  = ();
   981         }
   983         if (!(defined($href)) && m/<Repository.*?href="([^"]*)"(?:\s*platform="([^"]*)")?/) {
   984             #   external XML/RDF index reference for particular binary platform
   985             if (goodpf($2, $pfmatch)) {
   986                 push(@include, $1);
   987             }
   988             next;
   989         }
   991         #   skip content unless referenced piece was found
   992         next if (not defined($href));
   994         #   parse XML/RDF element into components
   995         ($tag, $cond, $attrname, $attrval, $body) = m{
   996             <                      # start delimiter
   997             (\/?[\w:]+)            # begin element name
   998             \s*                    # optional space before attributes
   999             (?:cond="([^"]+)")?    # known attribute
  1000             (?:(\w+)="([^"]+)")?   # unknown attribute
  1001             >                      # end delimiter
  1002             (.*?)                  # optional element body
  1003             (?:<\/\1>)?            # optional end tag
  1004             $                      # end of string
  1005         }mx;
  1007         #   recognize the various XML/RDF elements
  1008         if ($tag eq 'Description') {
  1009             $usecond = $cond;
  1010             $section = 'description';
  1011         } elsif ($tag eq '/Description') {
  1012             $usecond = $cond;
  1013             $section = undef;
  1014         } elsif ($section eq 'description') {
  1015             $desc .= $_;
  1016         } elsif ($tag eq 'PreReq') {
  1017             $usecond = $cond;
  1018             $section = 'prereq';
  1019         } elsif ($tag eq '/PreReq') {
  1020             $usecond = undef;
  1021             $section = undef;
  1022         } elsif ($tag eq 'BuildPreReq') {
  1023             $usecond = $cond;
  1024             $section = 'bprereq';
  1025         } elsif ($tag eq '/BuildPreReq') {
  1026             $usecond = undef;
  1027             $section = undef;
  1028         } elsif ($tag eq 'Provides') {
  1029             $usecond = $cond;
  1030             $section = 'provides';
  1031         } elsif ($tag eq '/Provides') {
  1032             $usecond = undef;
  1033             $section = undef;
  1034         } elsif ($tag eq 'Conflicts') {
  1035             $usecond = $cond;
  1036             $section = 'conflicts';
  1037         } elsif ($tag eq '/Conflicts') {
  1038             $usecond = undef;
  1039             $section = undef;
  1040         } elsif ($tag eq 'NoSource') {
  1041             $usecond = $cond;
  1042             $section = 'nosource';
  1043         } elsif ($tag eq '/NoSource') {
  1044             $usecond = undef;
  1045             $section = undef;
  1046         } elsif ($tag eq 'Source') {
  1047             $usecond = $cond;
  1048             $section = 'source';
  1049         } elsif ($tag eq '/Source') {
  1050             $usecond = undef;
  1051             $section = undef;
  1052         } elsif ($tag eq 'Name') {
  1053             $name = $body;
  1054         } elsif ($tag eq 'Version') {
  1055             $version = $body;
  1056         } elsif ($tag eq 'Release') {
  1057             $release = $body;
  1058         } elsif ($tag eq 'Platform') {
  1059             $platform = $body;
  1060         } elsif ($tag eq 'Prefixes') {
  1061             $prefix = $body;
  1062         } elsif ($tag eq 'rdf:li' || $tag eq 'resource') {
  1063             if (defined($attrname)) {
  1064                 $body = {
  1065                     $attrname => $attrval,
  1066                     content   => $body
  1067                 };
  1069             if ($section eq 'provides') {
  1070                 push(@provides, $body) if (!defined($usecond));
  1071             } elsif ($section ne '') {
  1072                 push(@{$bags->{"$usecond"}->{$section}}, $body);
  1074         } elsif ($tag eq '/rdf:Description') {
  1075             if (   defined($href)
  1076                 && defined($name)
  1077                 && defined($version)
  1078                 && defined($release)) {
  1079                 #   process the accumulated package information
  1080                 @provides = map {
  1081                     depends2provides(parse_depends($_))
  1082                 } @provides;
  1083                 %options = map {
  1084                     ($_->{with} => $_->{version})
  1085                 } grep {
  1086                     defined($_->{with})
  1087                 } @provides;
  1088                 push(@provides, {
  1089                     name    => $name,
  1090                     version => $version,
  1091                     release => $release
  1092                 });
  1093                 $options =
  1094                       %options
  1095                     ? { %options }
  1096                     : parse_options($desc);
  1097                 if ($options) {
  1098                     my (@t) = get_targets($installed->{$name}, sub { 1; });
  1100                 #   store accumulated package information
  1101                 eval {
  1102                     $rec = {
  1103                         href      => (relurl($url, undef, $href))[0],
  1104                         name      => $name,
  1105                         version   => $version,
  1106                         release   => $release,
  1107                         depends   => depend_list(swith($bags, 'bprereq')),
  1108                         keeps     => depend_list(swith($bags, 'prereq')),
  1109                         conflicts => swith($bags, 'conflicts'),
  1110                         source    => swith($bags, 'source'),
  1111                         nosource  => swith($bags, 'nosource'),
  1112                         desc      => $desc,
  1113                         platform  => $platform,
  1114                         prefix    => $prefix,
  1115                         OPTIONS   => $options,
  1116                         DEFOPTS   => { %$options }
  1117                     };
  1118                 };
  1119                 if ($@) {
  1120                     die "openpkg:build:FATAL: when reading entry '$name':\n" . $@;
  1122                 foreach (@provides) {
  1123                     push(@{$map->{$_->{name}}->{vs($_)}}, $rec);
  1126             #   prepare to recognize next package
  1127             $href = undef;
  1131     #   return contained XML/RDF indices
  1132     return \@include;
  1135 #   XML/RDF parser (usual way)
  1136 sub xml_parser ($$$$$) {
  1137     my ($fh, $url, $map, $pfmatch, $installed) = @_;
  1138     my (@include);
  1139     my ($xml, $rep, $sub);
  1140     my (@provides, %options, $rec);
  1141     my ($href, $name, $version, $release, $desc);
  1142     my ($options);
  1144     print "# using external XML/RDF parser\n";
  1146     #   parse XML/RDF with XML::Simple parser
  1147     $xml = XML::Simple::XMLin($fh, forcearray => 1);
  1148     $rep = $xml->{'Repository'}->[0]->{'rdf:Description'};
  1149     $sub = $xml->{'Repository'}->[0]->{'Repository'};
  1151     #   iterate over all package descriptions
  1152     foreach (@$rep) {
  1153         #   fetch package information
  1154         $href    = $_->{'href'};
  1155         $name    = xel($_->{'Name'});
  1156         $version = xel($_->{'Version'});
  1157         $release = xel($_->{'Release'});
  1158         next if (not (
  1159                defined($href)
  1160             && defined($name)
  1161             && defined($version)
  1162             && defined($release)
  1163         ));
  1165         #   determine package "provides"
  1166         @provides = ();
  1167         if ($_->{'Provides'}) {
  1168             @provides = map {
  1169                 $_ = $_->{'rdf:bag'}->[0];
  1170                 $_ = $_->{'rdf:li'} ? $_->{'rdf:li'} : $_->{'resource'};
  1171                 @$_;
  1172             } grep {
  1173                 !exists $_->{'cond'}
  1174             } @{$_->{'Provides'}};
  1176         @provides = map {
  1177             depends2provides(parse_depends($_))
  1178         } @provides;
  1179         %options = map {
  1180             ($_->{with} => $_->{version})
  1181         } grep {
  1182             defined $_->{with}
  1183         } @provides;
  1184         push(@provides, {
  1185             name    => $name,
  1186             version => $version,
  1187             release => $release
  1188         });
  1190         #   determine targets
  1191         $desc = xel($_->{'Description'});
  1192         $options =
  1193               %options
  1194             ? { %options }
  1195             : parse_options($desc);
  1196         if ($options) {
  1197             my (@t) = get_targets($installed->{$name}, sub { 1; });
  1200         #   store accumulated package information
  1201         eval {
  1202             $rec = {
  1203                 href      => (relurl($url, undef, $href))[0],
  1204                 name      => $name,
  1205                 version   => $version,
  1206                 release   => $release,
  1207                 platform  => xel($_->{'Platform'}),
  1208                 prefix    => xel($_->{'Prefixes'}),
  1209                 depends   => depend_list(xwith($_->{'BuildPreReq'})),
  1210                 keeps     => depend_list(xwith($_->{'PreReq'})),
  1211                 conflicts => xwith($_->{'Conflicts'}),
  1212                 source    => xwith($_->{'Source'}),
  1213                 nosource  => xwith($_->{'NoSource'}),
  1214                 desc      => $desc,
  1215                 OPTIONS   => $options,
  1216                 DEFOPTS   => { %$options }
  1217             };
  1218         };
  1219         if ($@) {
  1220             die "openpkg:build:FATAL: when reading entry '$name'\n".$@;
  1222         foreach (@provides) {
  1223             push(@{$map->{$_->{name}}->{vs($_)}}, $rec);
  1227     #   determine contained XML/RDF indices
  1228     if ($sub) {
  1229         @include = map {
  1230             goodpf($_->{platform}, $pfmatch)
  1231             ? ( $_->{href} )
  1232             : ( )
  1233         } @$sub;
  1236     #   return contained XML/RDF indices
  1237     return \@include;
  1240 #   convert XML parser output to dependency records
  1241 sub depend_list ($) {
  1242     my ($dl) = @_;
  1243     foreach (@$dl) {
  1244         $_->{value} = parse_depends($_->{value});
  1246     return $dl;
  1249 #   convert simple XML parser Bag into flat list
  1250 sub swith ($$) {
  1251     my ($bags,$name) = @_;
  1252     my ($cond);
  1253     my (@out);
  1255     foreach $cond (keys %$bags) {
  1256         foreach (@{$bags->{$cond}->{$name}}) {
  1257             push @out, {
  1258                 cond  => $cond,
  1259                 value => $_
  1260             };
  1263     return \@out;
  1266 #   convert (conditional) XML/RDF Bag into flat list
  1267 sub xwith ($) {
  1268     my ($bags) = @_;
  1269     my ($bag, $li, $el);
  1270     my (@out);
  1272     foreach $bag (@$bags) {
  1273         foreach $li (@{$bag->{'rdf:bag'}}) {
  1274             $el = $li->{'resource'} || $li->{'rdf:li'};
  1275             foreach (@$el) {
  1276                 push @out, {
  1277                     cond   => $bag->{'cond'},
  1278                     value  => $_
  1279                 };
  1283     return \@out;
  1286 #   return node value from XML parser
  1287 sub xel($) {
  1288     my ($a) = @_;
  1289     my ($l) = $a->[0];
  1290     return '' if ref($l);
  1291     return $l;
  1294 #   is the platform a good one?
  1295 sub goodpf ($$) {
  1296     my ($l, $p) = @_;
  1297     return 1 if $l eq '';
  1298     return ($l =~ m/(?:^|\s)\Q$p\E(?:\s|$)/);
  1302 #############################################################################
  1303 ##
  1304 ##  FUNCTIONS: HELPER FUNCTIONS FOR XML PARSING & DEPENDENCY PROCESSING
  1305 ##
  1306 #############################################################################
  1308 #   parse option from RPM output
  1309 #   < "%option with_foo bar"
  1310 #   > $with{"with_foo"} = "bar"
  1311 sub parse_options ($) {
  1312     my ($l) = @_;
  1313     $l = join("\n", @$l) if (ref($l));
  1314     return {} if ($l !~ m/(--define|\%option\s+)/s);
  1315     my $with = {};
  1316     $l =~ s/--define\s*'(\S+)\s+(\S+?)'/$with->{$1} = $2, ''/sge;  # before openpkg-20021230
  1317     $l =~ s/\%option\s+(\S+)\s+(\S+)/$with->{$1} = $2, ''/sge;     # after  openpkg-20021230
  1318     return $with;
  1321 #   fetch targets of a name that satisfy a condition and sort by target
  1322 #   version. Input is a hash of versions(?) on which the condition has
  1323 #   to be true and which points to an array of records with package
  1324 #   version information. Output is the list of version sorted package
  1325 #   version information records.
  1326 sub get_targets ($$) {
  1327     my ($relmap, $cond) = @_;
  1328     return (
  1329         sort {
  1330             vcmp(vs($a), vs($b));
  1331         } map {
  1332             @{$relmap->{$_}}
  1333         } grep {
  1334             $cond->($_);
  1335         } keys %$relmap
  1336    );
  1339 #############################################################################
  1340 ##
  1341 ##  FUNCTIONS: DEPENDENCY PROCESSING
  1342 ##
  1343 #############################################################################
  1345 #   search environment for packages that match a pattern
  1346 sub search_pattern ($$) {
  1347     my ($pattern, $env) = @_;
  1348     my (@todo);
  1350     if (defined($pattern)) {
  1351         #   explicitly given package pattern
  1352         @todo = map {
  1353             my ($p) = $_;
  1354             my ($s, $iswildcard);
  1355             $s = $1 if ($p =~ s/(,[^\s,]+)$//);
  1356             if ($p =~ s/\*+$//) {
  1357                 $p = '^'.quotemeta($p).'';
  1358                 $iswildcard = 1;
  1359             } else {
  1360                 $p = '^'.quotemeta($p).'$';
  1362             map { "$_$s" }
  1363                 grep { m/$p/ && !($iswildcard && exists($env->{exclude}->{$_})) }
  1364                     keys %{$env->{repository}}
  1365         } split(/\s+/, $pattern);
  1366     } else {
  1367         #   undefined pattern means "-a" option that selects
  1368         #   all packages from repository that are installed
  1369         #   and not explicitly excluded on command line
  1370         @todo = grep {
  1371             my ($n) = $_;
  1372                (ref($env->{installed}->{$n}))
  1373             && !exists($env->{exclude}->{$n})
  1374             && grep { $_ ne '-' } keys %{$env->{installed}->{$n}}
  1375         } keys(%{$env->{repository}});
  1377     return \@todo;
  1380 #   pull in OPTIONS for a package or an RPM file
  1381 my $get_with_cache = {};
  1382 sub get_with ($;$) {
  1383     my ($t, $fn) = @_;
  1384     my (@l, %with);
  1385     my ($optmap, $opt);
  1387     if ($t->{OPTIONS}) {
  1388         $opt = $t->{OPTIONS};
  1389     } else {
  1390         if (defined($fn)) {
  1391             @l = run($config->{"rpm"} . " -q --provides -p $fn");
  1392         } else {
  1393             if (not exists($get_with_cache->{-provides})) {
  1394                 #   pre-cache the "provides" query for all(!) packages at once for speedup
  1395                 my @c = run($config->{"rpm"} . " -qa --qf " .
  1396                     '\'%{NAME}[ :::%{PROVIDENAME}:::%{PROVIDEFLAGS:depflags}:::%{PROVIDEVERSION}:::]\n\'');
  1397                 $get_with_cache->{-provides} = {};
  1398                 foreach my $c (@c) {
  1399                     if ($c =~ m/^(\S+)(.*)$/s) {
  1400                         my ($name, $provides) = ($1, $2);
  1401                         while ($provides =~ m/\s+:::(.+?):::\s*(.*?)\s*:::(.*?):::/g) {
  1402                             $get_with_cache->{-provides}->{$name} = [] if (not exists($get_with_cache->{-provides}->{$name}));
  1403                             push(@{$get_with_cache->{-provides}->{$name}}, "$1 $2 $3");
  1408             @l = $get_with_cache->{-provides}->{$t->{name}};
  1409             if (not @l) {
  1410                 #   (should not happen in practice, but anyway)
  1411                 @l = run($config->{"rpm"} . " -q --provides $t->{name}");
  1412                 $get_with_cache->{-provides}->{$t->{name}} = [ @l ];
  1415         $opt = parse_provideslist(\@l);
  1416         if (scalar(keys(%$opt)) == 0) {
  1417             if (defined($fn)) {
  1418                 @l = run($config->{"rpm"} . " -qi -p $fn");
  1419             } else {
  1420                 if (not exists($get_with_cache->{-infos})) {
  1421                     #   pre-cache the "infos" query for all(!) packages at once for speedup
  1422                     my @c = run($config->{"rpm"} . " -qi -a");
  1423                     my $p = "";
  1424                     $get_with_cache->{-infos} = {};
  1425                     foreach my $c (@c) {
  1426                         $p = $1 if ($c =~ m/^Name:\s+(\S+)/s);
  1427                         $get_with_cache->{-infos}->{$p} = [] if (not exists($get_with_cache->{-infos}->{$p}));
  1428                         push(@{$get_with_cache->{-infos}->{$p}}, $c);
  1431                 @l = $get_with_cache->{-infos}->{$t->{name}};
  1432                 if (not @l) {
  1433                     #   (should not happen in practice, but anyway)
  1434                     @l = run($config->{"rpm"} . " -qi $t->{name}");
  1435                     $get_with_cache->{-infos}->{$t->{name}} = [ @l ];
  1438             $opt = parse_options(\@l);
  1440         $t->{OPTIONS} = $opt;
  1442     return $opt;
  1445 #   copy options from new to old
  1446 #   where option already exists in old or option key
  1447 #   matches regular expression
  1448 sub override_options ($$$) {
  1449     my ($old, $new, $reg) = @_;
  1450     my ($k);
  1452     foreach $k (keys(%$new)) {
  1453         if ((exists($old->{$k}) && $old->{$k} ne $new->{$k}) || $k =~ m/^$reg$/) {
  1454             $old->{$k} = $new->{$k};
  1459 #   filter package options
  1460 sub filter_name_with ($$$) {
  1461     my ($name, $with, $global) = @_;
  1462     my (@keys);
  1464     if ($global) {
  1465         push(@keys, grep { !/::/ } keys %$with);
  1467     push(@keys, grep { m/::/ } keys %$with);
  1468     return {
  1469         map {
  1470             my ($k) = $_;
  1471               $k !~ m/::/ || $k =~ s/^\Q$name\E:://
  1472             ? ( $k => $with->{$_} )
  1473             : ( )
  1474         } @keys
  1475     };
  1478 #   filter out package relevant options
  1479 sub name_with ($$) {
  1480     filter_name_with($_[0], $_[1], 1);
  1483 #   filter out package specific options
  1484 sub name_only_with ($$) {
  1485     filter_name_with($_[0], $_[1], 0);
  1488 #   evaluate a condition attribute from an option set
  1489 sub conditional ($$) {
  1490     my ($cond, $with) = @_;
  1491     my (@s, $res);
  1493     return 1 if ($cond eq '' || !defined($with));
  1494     foreach (split(/\s+/,$cond)) {
  1495         if ($_ eq '+') {
  1496             die "openpkg:build:FATAL: stack underflow in: $cond\n" if scalar(@s) < 2;
  1497             my ($a) = pop(@s);
  1498             my ($b) = pop(@s);
  1499             push(@s, $a && $b);
  1500         } elsif ($_ eq '|') {
  1501             die "openpkg:build:FATAL: stack underflow in: $cond\n" if scalar(@s) < 2;
  1502             my ($a) = pop(@s);
  1503             my ($b) = pop(@s);
  1504             push(@s, $a || $b);
  1505         } elsif ($_ eq '!') {
  1506             die "openpkg:build:FATAL: stack underflow in: $cond\n" if scalar(@s) < 1;
  1507             my ($a) = pop(@s);
  1508             push(@s, !$a);
  1509         } else {
  1510             push(@s, ($with->{$_} eq 'yes') ? 1 : 0);
  1513     die "openpkg:build:FATAL: stack underflow in: $cond\n" if scalar(@s) < 1;
  1514     $res = pop(@s);
  1516     die "openpkg:build:FATAL: stack not empty in: $cond\n" if scalar(@s) > 0;
  1517     return $res;
  1520 #   retrieve conditional target attributes in map
  1521 sub target_attribute ($$$;$) {
  1522     my ($target, $env, $attr, $with) = @_;
  1523     my ($optreg) = $env->{config}->{optreg};
  1524     my ($name, @out);
  1526     return if (not $target);
  1527     $name = $target->{name};
  1529     my ($mywith) = ($with ? $with : get_with($target));
  1530     override_options($mywith, name_with($name, $env->{with}), $optreg);
  1532     foreach (@{$target->{$attr}}) {
  1533         next if (not conditional($_->{'cond'}, $mywith));
  1534         push(@out, $_->{'value'});
  1536     return \@out;
  1539 #   see whether target is in map
  1540 sub target_exists ($$) {
  1541     my ($target, $map) = @_;
  1542     my ($vmap) = $map->{$target->{name}};
  1543     return if (not $vmap);
  1544     return (
  1545            !defined $target->{version}
  1546         || defined $vmap->{vs($target)}
  1547     );
  1550 #   see whether target has conflicts
  1551 sub target_conflicts ($$) {
  1552     my ($target, $env) = @_;
  1553     return target_attribute($target, $env, 'conflicts');
  1556 #   retrieve build dependencies for target
  1557 sub target_depends ($$) {
  1558     my ($target, $env) = @_;
  1559     return target_attribute($target, $env, 'depends');
  1562 #   retrieve runtime dependencies for target
  1563 sub target_keeps ($$) {
  1564     my ($target, $env) = @_;
  1565     return target_attribute($target, $env, 'keeps');
  1568 #   retrieve source list for target
  1569 sub target_source ($$) {
  1570     my ($target, $env) = @_;
  1571     return target_attribute($target, $env, 'source');
  1574 #   retrieve nosource list for target
  1575 sub target_nosource ($$) {
  1576     my ($target, $env) = @_;
  1577     return target_attribute($target, $env, 'nosource');
  1580 #   check whether target conflicts against map
  1581 sub target_has_conflicts ($$$) {
  1582     my ($target, $map, $env) = @_;
  1583     my ($conflicts, $t);
  1585     $conflicts = target_conflicts($target, $env);
  1586     foreach (@$conflicts) {
  1587         my ($t) = find_target($_, $map, 0);
  1588         return $t if $t;
  1590     return;
  1593 #   record target status
  1594 sub target_setstatus ($$$) {
  1595     my ($target, $status, $pri) = @_;
  1597     if ($pri > $target->{STATUSPRI}) {
  1598         $target->{STATUSPRI} = $pri;
  1599         $target->{STATUS}    = $status;
  1603 #   strip doubles from depend/keep lists
  1604 #   and return a map name => depend/keep
  1605 sub unique_map {
  1606     my (%out);
  1607     foreach (@_) {
  1608         foreach (@$_) {
  1609             $out{$_->{name}} = $_;
  1612     return %out;
  1615 #   check whether installed package matches build options. If default
  1616 #   = 1 then options which are not required must be identical to the
  1617 #   DEFOPTS.
  1618 sub target_suitable ($$$) {
  1619     my ($target, $with, $default) = @_;
  1620     my ($iwith, $dwith);
  1621     my ($k, $v);
  1623     if ($target->{GOAL}) {
  1624         $with = name_with($target->{name}, $with);
  1625     } else {
  1626         $with = name_only_with($target->{name}, $with);
  1628     $iwith = $target->{OPTIONS};
  1629     $dwith = $target->{DEFOPTS};
  1630     while (($k,$v) = each(%$iwith)) {
  1631         if (exists($with->{$k})) {
  1632             return 0 if ($iwith->{$k} ne $with->{$k});
  1633         } elsif ($default) {
  1634             return 0 if ($iwith->{$k} ne $dwith->{$k});
  1637     return 1;
  1640 #   determine whether target should be rebuild
  1641 sub target_better ($$$) {
  1642     my ($env, $target, $map) = @_;
  1643     my ($vs) = vs($target);
  1644     my ($vmap) = $map->{$target->{name}};
  1646     #   rebuild if target isn't installed
  1647     return 'new' unless $vmap;
  1649     #   if "-e" then
  1650     #   always update if installed version is different from repository
  1651     if ($env->{exact} && !grep { vcmp($vs, $_) == 0; } keys(%$vmap)) {
  1652         return 'exact';
  1655     #   if target is goal
  1656     #   always update if installed version is older than repository
  1657     if ($target->{GOAL} && !grep { vcmp($vs, $_) <= 0; } keys(%$vmap)) {
  1658         return 'goal';
  1661     #   if -U then
  1662     #   always update if installed version is older than repository
  1663     if ($env->{upgrade} && !grep { vcmp($vs, $_) <= 0; } keys(%$vmap)) {
  1664         return 'upgrade';
  1667     #   if -z/-Z then
  1668     #   always update if installed version is equal or older than repository
  1669     if ($env->{zero} && grep { vcmp($vs, $_) >= 0; } keys(%$vmap)) {
  1670         return 'zero';
  1673     #   keep installed target
  1674     return;
  1677 #   check if target record describes a source package
  1678 sub is_source ($) {
  1679     my ($t) = @_;
  1680     return !(defined $t->{'prefix'});
  1683 #   there can be multiple sources for a target release
  1684 sub chose_source ($$$$$) {
  1685     my ($env, $name, $select, $vmap, $cond) = @_;
  1686     my (@targ, @recs, @nrecs, $rec, %nam);
  1688     #   resolve name into a list of versions
  1689     #   for virtual targets this resolves to a list
  1690     #   of real targets that provide the virtual target
  1691     @targ = get_targets($vmap, sub { 1; });
  1692     return unless @targ;
  1694     #   find usable binary targets add all source targets
  1695     @recs = (
  1696         ( grep {
  1697                !$env->{sourceonly}
  1698             && !is_source($_)
  1699             && $_->{'platform'} eq $env->{config}->{platform}
  1700             && $_->{'prefix'} eq $env->{config}->{prefix}
  1701         } get_targets($vmap, $cond) ),
  1702         ( grep {
  1703             is_source($_)
  1704         } @targ )
  1705     );
  1706     return if (not @recs);
  1708     #   limit list to exact matches if provided by "-e"
  1709     if (defined($select)) {
  1710         @recs = grep {
  1711             vsn($_) =~ m/^\Q$select\E/
  1712         } @recs;
  1715     #   try to resolve ambiguity against installed targets
  1716     #   and targets previously selected
  1717     if (scalar(@recs) > 1) {
  1718         @nrecs = grep {
  1719                $env->{built}->{$_->{name}}
  1720             || $env->{installed}->{$_->{name}}
  1721         } @recs;
  1722         @recs = @nrecs if (@nrecs);
  1725     #   try to resolve ambiguity against hints
  1726     if ($env->{hint}) {
  1727         @nrecs = grep {
  1728             exists($env->{hint}->{$_->{name}})
  1729         } @recs;
  1730         @recs = @nrecs if (@nrecs);
  1733     #   try to resolve ambiguity against targets that match
  1734     #   the exact name
  1735     if (scalar(@recs) > 1) {
  1736         @nrecs = grep {
  1737             $name eq $_->{name}
  1738         } @recs;
  1739         @recs = @nrecs if (@nrecs);
  1742     #   try to resolve ambiguity by preferring binaries
  1743     if (scalar(@recs) > 1 && !$env->{sourceonly}) {
  1744         @nrecs = grep {
  1745             defined($_->{'platform'})
  1746         } @recs;
  1747         @recs = @nrecs if (@nrecs);
  1750     #   if we still have non-unique targets, complain
  1751     if (scalar(@recs) > 1) {
  1752         %nam = map { $_->{name} => 1 } @recs;
  1753         if (scalar(keys(%nam)) > 1) {
  1754             print "# ambigous sources for $name\n";
  1755             my ($i) = 0;
  1756             foreach (@recs) {
  1757                 print "# $i: ".vsn($_)." = $_->{href}\n";
  1758                 $i++;
  1760             return;
  1764     #   prefer full-source packages
  1765     if (scalar(@recs) > 1) {
  1766         @nrecs = grep {
  1767                ! $_->{nosource}
  1768             || ! @{$_->{nosource}}
  1769         } @recs;
  1770         unless (@nrecs) {
  1771             @nrecs = grep {
  1772                 $_->{href} !~ m/\.nosrc.rpm$/
  1773             } @recs;
  1775         @recs = @nrecs if (@nrecs);
  1778     #   nothing left -> exit
  1779     return if (scalar(@recs) == 0);
  1781     #   chose last (= max version) in list of targets
  1782     $rec = $recs[-1];
  1783     print "# source for $name is ".vsn($rec)."\n";
  1784     return $rec;
  1787 #   locate target for a dependency
  1788 sub dep2target ($$$) {
  1789     my ($dep, $env, $source) = @_;
  1790     my ($name, $op, @targ);
  1791     my ($i, $r, $b, $cond, $version);
  1792     my ($t, $tdef, $why);
  1794     ($name, $op, $version) = ($dep->{name}, $dep->{op}, $dep->{val});
  1796     $i = $env->{installed}->{$name};
  1797     $r = $env->{repository}->{$name};
  1798     $b = $env->{built}->{$name};
  1800     return if (not ($i || $r || $b));
  1802     if (!defined($op)) {
  1803         $cond = sub { 1; };
  1804     } elsif ($op eq 'geq') {
  1805         $cond = sub { vcmp($_[0],$version) >= 0; };
  1806     } elsif ($op eq 'leq') {
  1807         $cond = sub { vcmp($_[0],$version) <= 0; };
  1808     } elsif ($op eq 'gt') {
  1809         $cond = sub { vcmp($_[0],$version) >  0; };
  1810     } elsif ($op eq 'lt') {
  1811         $cond = sub { vcmp($_[0],$version) <  0; };
  1812     } elsif ($op eq 'equ') {
  1813         $cond = sub { vcmp($_[0],$version) == 0; };
  1814     } else {
  1815         die "openpkg:build:FATAL: internal error in dep2target\n";
  1818     $tdef = undef;
  1820     #   search installed target that matches requirement
  1821     #   use it if we are not upgrading (no -U and no -z/-Z)
  1822     if ($i && (@targ = get_targets($i, $cond))) {
  1823         foreach $t (@targ) {
  1824             get_with($t);
  1825             if (target_suitable($t, $env->{with}, 0)) {
  1826                 $tdef = $t;
  1827                 if (not ($env->{upgrade} || $env->{zero} || $source)) {
  1828                     return ($t, 1);
  1834     #   search target in current build list that matches requirement
  1835     #   use it if it exists
  1836     if ($b && (@targ = get_targets($b, $cond))) {
  1837         $t = $targ[0];
  1838         return ($t, 1);
  1841     #   search target in repository and install it, if it is newer than
  1842     #   corresponding installed versions avoid repository packages that
  1843     #   would install 'new' (i.e. are not an upgrade of an existing
  1844     #   package)
  1845     $t = chose_source($env, $name, undef, $r, $cond);
  1846     if ($t) {
  1847         if (!$tdef || (
  1848             ($why = target_better($env, $t, $env->{installed})) &&
  1849             $why ne 'new'
  1850             ) || $source) {
  1851             return ($t, 0);
  1855     #   if nothing is suitable in repository then fall back to
  1856     #   anything we already have installed but that we skipped
  1857     #   above to look for upgrades.
  1858     if ($tdef) {
  1859         return ($tdef, 1);
  1862     return;
  1865 #   compute reverse dependency map
  1866 sub get_revdep ($) {
  1867     my ($env) = @_;
  1868     my ($i) = $env->{'installed'};
  1869     my ($r) = $env->{'repository'};
  1870     my ($pkg, %dep, %dlist, %rev);
  1871     my (@targ, $t, $t1, $t2, $with, $name, $vmap);
  1872     my ($d, $k, %d, $old, %name, %pkg);
  1874     print "# computing reverse dependencies\n";
  1876     #   iterate over all installed packages
  1877     foreach $pkg (keys(%$i)) {
  1878         $vmap = $r->{$pkg};
  1879         if (not $vmap) {
  1880             print "# ATTENTION: $pkg has no upgrade path (installed package not found in repository)\n";
  1881             next;
  1884         #   get forward dependencies from installed packages
  1885         #   dep{a}{b} is true if b depends directly on a
  1886         #   dlist{a}  is list of packages that depend on a
  1887         @targ = get_targets($i->{$pkg}, sub { 1; });
  1888         foreach $t (@targ) {
  1889             $with = get_with($t);
  1890             $d = target_attribute($t, $env, 'depends', $with);
  1891             $k = target_attribute($t, $env, 'keeps', $with);
  1892             next if (not (@$d || @$k));
  1893             %d = unique_map($d,$k);
  1895             #   resolve package
  1896             if (not exists($pkg{$pkg})) {
  1897                 ($t2, $old) = dep2target({ name => $pkg }, $env, 1);
  1898                 $t2 = undef if ($old);
  1899                 $pkg{$pkg} = $t2;
  1901             $t2 = $pkg{$pkg};
  1902             next if (not $t2);
  1903             foreach (keys(%d)) {
  1904                 next if ($_ eq 'OpenPKG');
  1905                 #   resolve target
  1906                 unless (exists($name{$_})) {
  1907                     ($t1, $old) = dep2target($d{$_}, $env, 0);
  1908                     $name{$_} = ($t1 ? $t1->{name} : $_);
  1910                 $name = $name{$_};
  1911                 if (not $dep{$name}{$t->{name}}) {
  1912                     $dep{$name}{$t->{name}} = 1;
  1913                     push(@{$dlist{$name}}, $t2);
  1919     #   sort reverse dependencies
  1920     foreach $pkg (keys(%dep)) {
  1921         $rev{$pkg} = [
  1922             sort {
  1923                    $dep{$b->{name}}{$a->{name}}
  1924                 || -$dep{$a->{name}}{$b->{name}}
  1925                 || $a->{name} cmp $b->{name}
  1926             } @{$dlist{$pkg}}
  1927         ];
  1930     #   return hash of package reverse dependencies
  1931     return \%rev;
  1934 #   build package dependencies
  1935 #   (all input and output is passed in 'env' hash)
  1936 sub build_deps ($$) {
  1937     my ($pattern, $env) = @_;
  1938     my ($todo, @list, $list, @out);
  1940     #   determine all packages which match the pattern
  1941     $todo = search_pattern($pattern, $env);
  1943     #   unfold target names into all(!) real targets names
  1944     @list =
  1945     map {
  1946         map {
  1947             map {
  1948                 $_->{name}
  1949             } @$_
  1950         } values(%{$env->{repository}->{$_}})
  1951     } @$todo;
  1953     #   also add target name
  1954     push @list, @$todo;
  1956     #   strip duplicate names
  1957     @list = keys %{ { map { $_ => 1 } @list } };
  1959     #   cache reverse dependencies
  1960     if (not $env->{revdep}) {
  1961         $env->{revdep} = get_revdep($env);
  1964     #   map targets into list of dependency names
  1965     @list = map {
  1966           $env->{revdep}->{$_}
  1967         ? (@{$env->{revdep}->{$_}})
  1968         : ()
  1969     } @list;
  1971     #   iterate over dependencies
  1972     foreach (@list) {
  1973         #   avoiding cycles
  1974         next if ($env->{builddeps}->{$_->{name}});
  1975         $env->{builddeps}->{$_->{name}} = 1;
  1977         #   generate result
  1978         push(@out, $_);
  1980         #   generate result recursively
  1981         $list = build_deps($_->{name}, $env); # RECURSION
  1982         push(@out, @$list);
  1985     #   return final results
  1986     return \@out;
  1989 #   report options that are not used for
  1990 sub warn_about_options ($$$) {
  1991     my ($target, $with, $c) = @_;
  1992     my ($iwith) = $target->{OPTIONS};
  1993     my ($k, $v);
  1995     return if (not defined($iwith));
  1996     $with = name_with($target->{name}, $with);
  1997     while (($k,$v) = each %$with) {
  1998         if (not ($k =~ m/^$c->{optreg}$/ || exists $iwith->{$k})) {
  1999             print "# ATTENTION: $target->{name} ignores option '$k'\n";
  2004 #   find target in map
  2005 sub find_target ($$$) {
  2006     my ($name, $map, $pos) = @_;
  2007     my ($vmap) = $map->{$name};
  2008     my (@vs);
  2010     return if (not $vmap);
  2011     @vs = sort { vcmp($b,$a) } keys(%$vmap);
  2012     return $vmap->{$vs[$pos]}->[-1];
  2015 #   add dependency as build option
  2016 sub depend_option ($$$) {
  2017     my ($target, $dep, $env) = @_;
  2018     my ($with, $opt, $relmap, @t, $t);
  2019     my ($pro) = depends2provides($dep);
  2020     my ($conflict) = 0;
  2022     return 1 if (not defined($pro->{with}));
  2024     my ($val) = defined($pro->{version}) ? $pro->{version} : 'yes';
  2026     $with = $env->{with};
  2027     $opt  = $pro->{prefix}.'::'.$pro->{with};
  2028     if (defined($with->{$opt}) && $with->{$opt} ne $val) {
  2029         print "# ", vsn($target), " has conflicting requirement $opt = $with->{$opt} != $val\n";
  2030         $conflict = 1;
  2033     $relmap = $env->{built}->{$pro->{prefix}};
  2034     @t = get_targets($relmap, sub { 1; });
  2035     foreach $t (@t) {
  2036         $with = $t->{OPTIONS};
  2037         $opt  = $pro->{with};
  2038         if (defined($with->{$opt}) && $with->{$opt} ne $val) {
  2039             print "# ", vsn($t), " has conflicting requirement $opt = $with->{$opt} != $val\n";
  2040             $conflict = 1;
  2044     return 0 if ($conflict);
  2046     print "# ", vsn($target), " adds option $opt = $val\n";
  2047     $with->{$opt} = $val;
  2048     return 1;
  2051 #   remember fatal error
  2052 sub pusherr ($$$) {
  2053     my ($env, $target, $mess) = @_;
  2054     print "# $mess\n";
  2055     push(@{$env->{fatal}}, vsn($target).": $mess\n");
  2058 #   generate dependencies
  2059 sub make_dep ($$$$$$$) {
  2060     my ($who, $target, $depth, $env, $list, $blist, $clist) = @_;
  2061     my ($d, $k, %d, %k, $t, $old);
  2062     my (@deps, $conflict, $why);
  2064     #   check whether target is already in list of to be build packages
  2065     if (target_exists($target, $env->{built})) {
  2066         print "# $target->{name} is already in list\n";
  2067         return;
  2070     #   check whether target is in conflict with already installed package
  2071     if ($t = target_has_conflicts($target, $env->{installed}, $env)) {
  2072         target_setstatus($target, 'CONFLICT', 4);
  2073         push(@$clist, $target);
  2074         pusherr($env, $target, "$target->{name} conflicts with ".vsn($t));
  2075         return;
  2078     #   check whether target is in conflict with to be build package
  2079     if ($t = target_has_conflicts($target, $env->{built}, $env)) {
  2080         target_setstatus($target, 'CONFLICT', 4);
  2081         push(@$clist, $target);
  2082         pusherr($env, $target, "$target->{name} conflicts with ".vsn($t));
  2083         return;
  2086     #   see if a target is already installed and requires a rebuild
  2087     if ($t = find_target($target->{name}, $env->{installed}, -1)) {
  2088         if (exists($env->{exclude}->{$target->{name}})) {
  2089             print "# excluding $target->{name} (no upgrade allowed)\n";
  2090             return;
  2093         #   pull in options (for status report)
  2094         if ($target->{REBUILD}) {
  2095             target_setstatus($target, 'DEPEND', 1);
  2096             print "# rebuilding $target->{name} (dependency)\n";
  2097         } elsif ($env->{zero}) {
  2098             target_setstatus($target, 'ZERO', 1);
  2099             print "# rebuilding $target->{name} (zero)\n";
  2100         } elsif (vs($target) ne vs($t)) {
  2101             target_setstatus($target, 'UPDATE', 3);
  2102             print "# rebuilding $target->{name} (update)\n";
  2103         } elsif (!target_suitable($t, $env->{with}, 0)) {
  2104             target_setstatus($target, 'MISMATCH', 2);
  2105             print "# rebuilding $target->{name} (parameter mismatch)\n";
  2106         } elsif ($env->{goals} && $target->{GOAL}) {
  2107             target_setstatus($target, 'GOAL', 3);
  2108             print "# rebuilding $target->{name} (goal)\n";
  2109         } else {
  2110             print "# $target->{name} is already installed\n";
  2111             return;
  2114         #   use options from installed base
  2115         override_options(get_with($target), get_with($t), $env->{config}->{optreg});
  2117         #   remember this is a rebuild for a proxy package
  2118         $target->{PROXY} = $t->{PROXY};
  2119         $target->{REBUILD} = 1;
  2120     } else {
  2121         print "# creating $target->{name}\n";
  2122         target_setstatus($target, 'ADD', 3);
  2125     if (exists($env->{exclude}->{$target->{name}})) {
  2126         die "openpkg:build:FATAL: target ".vsn($target)." is forbidden\n";
  2129     #   mark this as a target before reverse dependencies trigger it again
  2130     push(@{$env->{built}->{$target->{name}}->{vs($target)}}, $target);
  2131     $target->{LIMBO} = 1;
  2133     #   recurse over dependencies
  2134     $d = target_depends($target, $env);
  2135     $k = target_keeps($target, $env);
  2136     if (@$d || @$k) {
  2137         %d = unique_map($d, $k);
  2138         %k = unique_map($k);
  2139         @deps     = ();
  2140         $conflict = 0;
  2141         foreach (keys %d) {
  2142             #   old index misses a OpenPKG provider in the index... skip it
  2143             next if ($_ eq 'OpenPKG');
  2144             ($t, $old) = dep2target($d{$_}, $env, 0);
  2145             if ($t) {
  2146                 if ($old) {
  2147                     print "# $target->{name} uses ".vsn($t)." for $_\n";
  2148                     if ($t->{LIMBO}) {
  2149                         print "# ATTENTION: ".vsn($t)." is in LIMBO\n";
  2151                     next;
  2153                 if (not depend_option($t, $d{$_}, $env)) {
  2154                     push(@$clist, $target);
  2155                     pusherr($env, $target, "$target->{name} has conflicting requirement");
  2156                     target_setstatus($target, 'UNDEF', 4);
  2157                     $conflict = 1;
  2158                     next;
  2160                 if ($k{$_}) {
  2161                     push(@$blist, $t);
  2162                     print "# $target->{name} installs ".vsn($t)." for $_\n";
  2163                 } else {
  2164                     print "# $target->{name} requires ".vsn($t)." for $_\n";
  2166                 push(@deps, $t);
  2167             } else {
  2168                 push(@$clist, $target);
  2169                 pusherr($env, $target, "$target->{name} searches a frood called '$_'");
  2170                 target_setstatus($target, 'UNDEF', 4);
  2171                 $conflict = 1;
  2174         if (not $conflict) {
  2175             foreach $t (@deps) {
  2176                 make_dep($target, $t, $depth+1, $env, $list, $blist, $clist); # RECURSION
  2181     print "# adding ".vsn($target)." to list\n";
  2182     $target->{WHO} = $who;
  2183     $target->{WHY} = $target->{STATUS};
  2184     push(@$list, $target);
  2186     #   remember new options
  2187     override_options(get_with($target), name_with($target->{name}, $env->{with}), '');
  2189     #   moan about non-source packages
  2190     foreach (@{target_nosource($target, $env)}) {
  2191         my ($p) = target_source($target, $env)->[$_];
  2192         $p =~ s/.*\///;
  2193         print "# ATTENTION: unpackaged source $_: $p\n";
  2196     #   cleanup limbo
  2197     $target->{LIMBO} = 0;
  2199     #   a dependency could not be resolved, don't bother with reverse
  2200     #   dependencies for this target
  2201     return if ($conflict);
  2203     if (!$env->{quick} && $target->{name} ne 'openpkg' ) {
  2204         if (not $env->{revdep}) {
  2205             $env->{revdep} = get_revdep($env);
  2207         foreach $t (@{$env->{revdep}->{$target->{name}}}) {
  2208             #   this is a rebuild, triggering further revdeps
  2209             $t->{REBUILD} = 1;
  2211             #   this is a rebuild, keep this installed
  2212             push(@$blist, $t);
  2214             print "# rebuilding reverse dependency ".vsn($t)."\n";
  2215             make_dep($target, $t, $depth+1, $env, $list, $blist, $clist); # RECURSION
  2220 #   generate build lists for targets matched by pattern
  2221 #   (all input and output is passed in 'env' hash)
  2222 sub build_list ($$) {
  2223     my ($pattern, $env) = @_;
  2224     my (@goals, @targets, @keeps, @conflicts, @bonly, $t);
  2225     my ($name, $select, $r, $i);
  2226     my ($todo, %keep);
  2228     #   determine all packages which match the pattern
  2229     $todo = search_pattern($pattern, $env);
  2231     #   chose sources for goals from repository
  2232     foreach $name (@$todo) {
  2233         $select = undef;
  2234         $select = $1 if ($name =~ s/,([^\s,]+)$//);
  2235         $t = undef;
  2237         #   keeping installed packages for goals is ugly
  2238         #   - we currently do not support installed source RPMs
  2239         #   - source RPMs might already have expired from repository
  2240         #   consequence:
  2241         #   - goals are always upgraded to repository versions
  2242         #if (not $env->{upgrade}) {
  2243         #    $i = $env->{installed}->{$name};
  2244         #    $t = chose_source($env, $name, $select, $i, sub { 1; });
  2245         #}
  2246         if (not $t) {
  2247             $r = $env->{repository}->{$name};
  2248             $t = chose_source($env, $name, $select, $r, sub { 1; });
  2251         if ($t) {
  2252             warn_about_options($t, $env->{with}, $env->{config});
  2253             $t->{GOAL} = 1;
  2254             push @goals, $t;
  2255         } else {
  2256             #   error
  2257             if ($env->{status}) {
  2258                 print "# dropping goal '$name'\n";
  2259             } else {
  2260                 die "openpkg:build:FATAL: cannot find source for '$name'\n";
  2264     return if (not @goals);
  2266     #   recurse over dependencies
  2267     @targets = ();
  2268     @keeps   = @goals;
  2269     foreach $t (@goals) {
  2270         print "# recursing over dependencies for ".vsn($t)."\n";
  2271         make_dep(undef, $t, 0, $env, \@targets, \@keeps, \@conflicts);
  2274     #   determine "binary only" packages which should be not kept
  2275     #   as they were not installed and are used temporarily only.
  2276     %keep = map { $_ => 1 } @keeps;
  2277     @bonly = reverse grep {
  2278         !$keep{$_} && !$env->{installed}->{$_->{name}}
  2279     } @targets;
  2281     #   return results
  2282     return (\@targets, \@bonly, \@conflicts);
  2286 #############################################################################
  2287 ##
  2288 ##  FUNCTIONS: RESULT PRINTING
  2289 ##
  2290 #############################################################################
  2292 #   determine execution command
  2293 sub cmd ($$) {
  2294     my ($w,$s) = @_;
  2295     if (!defined($w)) {
  2296         return $s;
  2297     } elsif ($w =~ m/^-(.*)/) {
  2298         return "$1 \"$s\"";
  2299     } else {
  2300         return "$w $s";
  2303 sub priv  ($) { cmd($opt_P, $_[0]); }
  2304 sub npriv ($) { cmd($opt_N, $_[0]); }
  2306 #   execute a command
  2307 my $run_cache = {};
  2308 sub run ($) {
  2309     my $cmd = cmd($opt_N, $_[0]);
  2310     my $out = $run_cache->{$cmd};
  2311     if (not defined($out)) {
  2312         my @out = `$cmd`;
  2313         $out = [ @out ];
  2314         $run_cache->{$cmd} = $out;
  2316     return (wantarray ? @{$out} : join(//, @{$out}));
  2319 #   print dependency list
  2320 sub print_deps ($) {
  2321     my ($list) = @_;
  2323     print join("\n", sort map { vsn($_) } @$list), "\n";
  2326 #   print dependency map
  2327 sub print_map ($$$$$) {
  2328     my ($installed, $repository, $list, $bonly, $clist) = @_;
  2329     my (%dep);
  2331     foreach (@$bonly) {
  2332         $_->{status} = 'TEMP';
  2334     foreach (reverse(@$list)) {
  2335         printf("%-35s %-8s %s\n",
  2336             $_->{WHO} ? vsn($_->{WHO}) : "GOAL",
  2337             $_->{WHY} ? $_->{WHY} : '???',
  2338             vsn($_)
  2339         );
  2343 #   instead of printing a command list, print a status map
  2344 #   that shows all packages and how the build process would
  2345 #   change their status
  2346 sub print_status ($$$$$) {
  2347     my ($installed, $repository, $list, $bonly, $clist) = @_;
  2348     my (%bonly) = map { $_ => 1 } @$bonly;
  2349     my (%map, $n, @names, $t);
  2350     my ($old, $tag, $new);
  2352     #   augment map with additional information
  2353     #   about conflicting and binary only (temporary) packages
  2354     foreach (@$list, @$clist) {
  2355         next if (not $_->{release} =~ m/\S/);
  2356         $map{$_->{name}} = {
  2357             rel    => "$_->{version}-$_->{release}",
  2358             status => $_->{STATUS}
  2359         };
  2361     foreach (@$bonly) {
  2362         next if (not $_->{release} =~ m/\S/);
  2363         $map{$_->{name}} = {
  2364             rel    => "$_->{version}-$_->{release}",
  2365             status => 'TEMP'
  2366         };
  2369     #   augment map with additional information
  2370     #   about up-to-date and new packages
  2371     @names = keys(%map);
  2372     foreach $n (keys(%$installed)) {
  2373         next if ($n =~ m/::/);
  2374         next if (exists($map{$n}));
  2375         next if (not (grep { $_ ne '' } keys(%{$installed->{$n}})));
  2376         $map{$n}->{'status'} = 'OK';
  2377         push(@names, $n);
  2379     foreach $n (keys(%$repository)) {
  2380         next if ($n =~ m/::/);
  2381         next if (exists($map{$n}));
  2382         next if (not (grep { $_ ne '' } keys(%{$repository->{$n}})));
  2383         $t = find_target($n, $repository, 0);
  2384         $map{$n}->{'status'} = 'NEW';
  2385         $map{$n}->{'rel'}    = vs($t);
  2386         push(@names, $n);
  2389     #   generate status output
  2390     foreach $n (sort(@names)) {
  2391         $old = join(',',
  2392             map { "$n-$_" }
  2393             sort
  2394             grep { $_ ne '-' }
  2395             keys(%{$installed->{$n}})
  2396         );
  2397         $old = $n if ($old eq '');
  2398         $tag = $map{$n}->{status};
  2399         $new = defined($map{$n}->{rel}) ? " $n-$map{$n}->{rel}" : '';
  2400         printf("%-35s %-8s%s\n", $old, $tag, $new);
  2404 #   compute path to source RPM from rpm config and target data
  2405 sub target2srcrpm ($$) {
  2406     my ($target, $c) = @_;
  2407     return $c->{srcrpmdir}.'/'.$target->{name}.'-'.$target->{version}.'-'.$target->{release}.'.src.rpm';
  2410 #   compute path to binary RPM from rpm config and target data
  2411 sub target2rpm ($$) {
  2412     my ($target, $c) = @_;
  2413     my ($tmpl) = $c->{template};
  2414     my ($popt) = $target->{PROXY} ? '+PROXY' : '';
  2416     $tmpl =~ s/%{NAME}/$target->{name}/;
  2417     $tmpl =~ s/%{VERSION}/$target->{version}/;
  2418     $tmpl =~ s/%{RELEASE}/$target->{release}$popt/;
  2420     return $c->{rpmdir}.'/'.$tmpl;
  2423 #   merge parameters from installed package
  2424 #   with new parameter set and global parameters
  2425 #   from configuration
  2426 #   then map the result to --define command line arguments
  2427 #   suitable for rpm
  2428 sub make_defines ($$$$) {
  2429     my ($old, $new, $def, $c) = @_;
  2430     my ($with);
  2432     $old = {} unless $old;
  2433     $def = {} unless $def;
  2435     #   override old parameters with new parameters
  2436     #   drop new parameters that do not exist in old set
  2437     $old = { %$old };
  2438     override_options($old, $new, $c->{optreg});
  2440     #   convert parameters to --define command line options
  2441     #   skip parameter templates from index
  2442     #   skip parameters that are identical to defaults
  2443     $with = join(' ',
  2444         map { "--define '$_ $old->{$_}'" }
  2445         sort grep {
  2446             $old->{$_} =~ m/\S/ &&
  2447             $old->{$_} !~ m/^%/ &&
  2448             $old->{$_} ne $def->{$_}
  2449         } keys %$old
  2450     );
  2452     $with = ' '.$with if ($with ne '');
  2454     return $with;
  2457 #   compute new target based on old target augmented with options from
  2458 #   a binary RPM file
  2459 sub binary_target ($$) {
  2460     my ($t, $fn) = @_;
  2461     my (%target) = %$t;
  2463     #   pull in options from binary RPM file
  2464     delete $target{'OPTIONS'};
  2465     get_with(\%target, $fn);
  2466     return \%target;
  2469 #   return path to master package for a proxy package
  2470 sub find_proxy ($$) {
  2471     my ($t, $bpkg) = @_;
  2472     my (@l) = run($config->{"rpm"} . " -ql $t->{name}");
  2473     my ($link) = (grep { $_ =~ m/\/\.prefix-$t->{name}$/ } @l)[0];
  2474     return if (not defined($link));
  2475     chomp $link;
  2476     my ($prefix) = readlink($link);
  2477     return if (not defined($prefix));
  2478     $bpkg =~ s/.*\///;
  2479     $bpkg =~ s/\+PROXY(\.[^-]+-[^-]+)-[^-]+\.rpm$/$1-*.rpm/;
  2480     return (glob("$prefix/RPM/PKG/$bpkg"))[0];
  2483 #   indent text to form a block
  2484 sub indent ($) {
  2485     my ($txt) = @_;
  2486     $txt =~ s/^/    /gm;
  2487     return $txt;
  2490 #   print commands from package build list
  2491 #   c      -> configuration to derive paths from
  2492 #   uncond -> always do the --rebuild
  2493 #   with   -> parameter set passed to build tool
  2494 #   ignore -> generate script that does not stop on error
  2495 #   usebin -> build-time check to skip rebuild when binary exists
  2496 #   allbin -> usebin also for goals
  2497 sub print_list1 ($$$$$$$) {
  2498     my ($list, $c, $uncond, $with, $ignore, $usebin, $allbin) = @_;
  2499     my ($pkg, $spkg, $bpkg, $uvhpkg, $ppkg);
  2500     my ($opt);
  2501     my ($cmd1, $cmd2, $mark);
  2502     my ($cmd3, $srcpkg);
  2504     $mark = '::::';
  2506     my $err;
  2507     if ($ignore) { $err = "|| true" } else { $err = "|| exit \$?" };
  2508     foreach (@$list) {
  2509         $pkg = $_->{name};
  2510         $spkg = $_->{href};
  2511         unless ($spkg =~ m/\S/) {
  2512             die "openpkg:build:FATAL: internal error, ",vsn($_)," without source URL\n";
  2514         $bpkg = target2rpm($_, $c); $uvhpkg = $bpkg;
  2515         $srcpkg = target2srcrpm($_, $c);
  2516         $cmd3 = '';
  2518         #   rebuild binary package IF
  2519         #   'unconditional' option
  2520         #   OR target is tagged as rebuilding
  2521         #   OR there is no binary package
  2522         #   OR dependency check found that installed package is not suitable
  2523         #   OR existing binary package doesn't satisfy wanted options
  2524         $cmd1 = undef;
  2525         if (   $uncond
  2526             || !-f $bpkg
  2527             || !target_suitable(binary_target($_, $bpkg), $with, 1)) {
  2529             $opt = make_defines($_->{OPTIONS}, $with,
  2530                                 $_->{DEFOPTS}, $c);
  2532             #   proxy packages are rebuilt from their maste
  2533             #   hierachy
  2534             #   someone preferred a binary from the repository
  2535             #   just copy it to the local store
  2536             if ($_->{PROXY}) {
  2537                 $ppkg = find_proxy($_,$bpkg) or
  2538                 die "openpkg:build:FATAL: proxy package ",vsn($_)," does not exist\n";
  2540                 #   rpm doesn't support additional parameters to the
  2541                 #   mkproxy script
  2542                 #   $cmd1 = npriv($config->{"mkp"} . " $ppkg -- -o $bpkg");
  2543                 $cmd1 = "( cd $c->{rpmdir} && ".
  2544                         npriv($config->{"mkp"} . " $ppkg").
  2545                         " )";
  2546             } elsif (defined $_->{prefix}) {
  2547                 $cmd1 = '';
  2548                 if ($spkg =~ m|^\.?/|) {
  2549                     $uvhpkg = $spkg;
  2551                 else {
  2552                     $cmd1 .= npriv($config->{"curl"} . " -# -o $bpkg $spkg $err\n");
  2553                     $cmd3 = npriv("rm -f $bpkg >/dev/null 2>&1 $err\n") unless ($opt_k);
  2555             } else {
  2556                 $cmd1 = '';
  2557                 if ($spkg =~ m|^\.?/|) {
  2558                     $cmd1 .= npriv($config->{"rpm"} . "$opt --rebuild $spkg $err\n");
  2560                 else {
  2561                     $cmd1 .= "if test ! -f $srcpkg; then\n";
  2562                     $cmd1 .= indent(npriv($config->{"curl"} . " -# -o $srcpkg $spkg $err\n"));
  2563                     $cmd1 .= "fi\n";
  2564                     $cmd1 .= npriv($config->{"rpm"} . "$opt --rebuild $srcpkg $err\n");
  2565                     $cmd1 .= npriv("rm -f $srcpkg >/dev/null 2>&1 $err\n") unless ($opt_k);
  2570         #   wrap build command with build-time check for existing
  2571         #   binary target
  2572         if (defined($cmd1) && ($allbin || ($usebin && !$_->{GOAL}))) {
  2573             $cmd1 = "if test ! -f $uvhpkg; then\n".indent($cmd1)."fi\n";
  2576         #   if package exist force rpm to copy over new files
  2577         #   better than erasing everything and losing configuration
  2578         #   files
  2579         $opt = ($_->{REBUILD} || ($allbin || ($usebin && !$_->{GOAL}))) ? ' --force' : '';
  2580         $cmd2 = '';
  2581         $cmd2 .= priv($config->{"rpm"} . "$opt -Uvh $uvhpkg $err\n");
  2582         if ($allbin || ($usebin && !$_->{GOAL})) {
  2583             $cmd2 = "if test \".`".$config->{"rpm"}." -q --qf '\%{SIGMD5}' $pkg`\" != \".`".$config->{"rpm"}." -qp --qf '\%{SIGMD5}' $uvhpkg`\"; then\n".indent($cmd2)."fi\n";
  2585         $cmd2 = $cmd1.$cmd2 if ($cmd1);
  2586         $cmd2 = $cmd2.$cmd3 if ($cmd3);
  2587         print "echo $mark $spkg $mark\n".$cmd2."echo $mark $spkg = \$? $mark\n";
  2591 #   print commands for the temporary package list
  2592 #   temporary packages are only used for building other packages
  2593 #   and are removed when everything is done
  2594 sub print_list2 ($$) {
  2595     my ($list, $c) = @_;
  2596     my ($pkg);
  2598     foreach (@$list) {
  2599         $pkg = "$_->{name}-$_->{version}-$_->{release}";
  2600         print priv($config->{"rpm"} . " -e $pkg\n");

mercurial