diff -r 71503088f51b -r f880f219c566 openpkg/build.pl
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/openpkg/build.pl Tue Jul 31 12:23:42 2012 +0200
@@ -0,0 +1,2603 @@
+##
+## build.pl -- OpenPKG Package Building and Installing
+## Copyright (c) 2000-2012 OpenPKG GmbH
+##
+## This software is property of the OpenPKG GmbH, DE MUC HRB 160208.
+## All rights reserved. Licenses which grant limited permission to use,
+## copy, modify and distribute this software are available from the
+## OpenPKG GmbH.
+##
+## THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
+## WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+## MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+## IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR
+## CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+## USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+## ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+## OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+## OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+## SUCH DAMAGE.
+##
+
+#############################################################################
+##
+## MAIN PROCEDURE
+##
+#############################################################################
+
+require 5;
+#use strict;
+my (
+ $opt_h,
+ $opt_R, $opt_r, $opt_f, $opt_u, $opt_U, $opt_a, $opt_A,
+ $opt_z, $opt_Z, $opt_P, $opt_N, $opt_E, $opt_H, $opt_i,
+ $opt_D, $opt_p, $opt_q, $opt_s, $opt_S, $opt_X, $opt_M,
+ $opt_L, $opt_W, $opt_K, $opt_e, $opt_b, $opt_B, $opt_g,
+ $opt_k
+);
+
+# global context variables
+my $prg = "openpkg build";
+my %env = ('' => {});
+
+##
+## OPTION PARSING
+##
+
+# parse command line options
+my $getopts = 'hR:r:f:uUaAzZP:N:E:H:iD:p:qsSXMLWKebBgk';
+getopts($getopts);
+
+# parse configuration script options
+if (open(FH, "<$ENV{'HOME'}/.openpkg/build")) {
+ my ($env) = $env{''};
+ my ($go) = $getopts;
+ $go =~ s/[^a-zA-Z]//g;
+ while (my $line = ) {
+ if ($line =~ m/^\s*\[([^\]]*)\]/) {
+ $env{$1} = {} unless ($env{$1});
+ $env = $env{$1};
+ } elsif (my ($opt, $val) = ($line =~ m/^\-([$go])\s*(.*?)\s*$/)) {
+ $val = 1 unless (defined($val));
+ if (exists($env->{$opt})) {
+ $env->{$opt} .= " " . $val;
+ } else {
+ $env->{$opt} = $val;
+ }
+ }
+ }
+ close(FH);
+}
+
+# usage sanity check and usage help
+sub usage {
+ my ($rc) = @_;
+ my $usage =
+ "openpkg:build:USAGE: $prg [options] [pattern ...]\n" .
+ " -a operate on all installed packages\n" .
+ " -A operate on all repository packages\n" .
+ " -R path to \"openpkg rpm\" command\n" .
+ " -r URL to package repository directory\n" .
+ " -f URL to package repository index file\n" .
+ " -u ignore local binary RPMs\n" .
+ " -U upgrade all selected packages including dependencies\n" .
+ " -z rebuild from zero all selected installed packages\n" .
+ " -Z rebuild from zero all selected available packages\n" .
+ " -i ignore errors in the generated script\n" .
+ " -q ignore all reverse dependencies\n" .
+ " -s generate status map instead of shell script\n" .
+ " -S generate status map instead of shell script (including new)\n" .
+ " -X use external XML/RDF parser instead of internal one\n" .
+ " -M generate short dependency map instead of shell script\n" .
+ " -L generate list of packages in repository depending on target\n" .
+ " -W include dependencies as if all build options are enabled\n" .
+ " -K keep temporarily installed packages\n" .
+ " -k keep temporarily downloaded packages\n" .
+ " -e rebuild exact version of a package from repository\n" .
+ " -b build-time check existing binaries for dependencies only\n" .
+ " -B build-time check existing binaries for dependencies and target\n" .
+ " -g rebuild packages even when most recent version is installed\n" .
+ " -P command prefix for privileged commands\n" .
+ " -N command prefix for non-privileged commands\n" .
+ " -p match platform against repository index for binary packages\n" .
+ " -E exclude package\n" .
+ " -H hint about packages to resolve ambiquity\n" .
+ " -D [=] set build option for packages\n";
+ if ($rc == 0) {
+ print STDOUT $usage;
+ }
+ else {
+ print STDERR $usage;
+ }
+ exit($rc);
+}
+if ($opt_h) {
+ usage(0);
+}
+if (not ( ($#ARGV >= 0 && !($opt_a || $opt_A))
+ || ($#ARGV == -1 && ($opt_a || $opt_A)))) {
+ usage(1);
+};
+
+# determine RPM run-time information
+my $config = rpm_runtime_info();
+
+# override command line options with configuration script options
+# now that the effectively used OpenPKG RPM command is known
+foreach my $env (sort { $a cmp $b } grep {
+ $config->{"rpm"} =~ m/^\Q$_\E/ # compatibility
+ or $config->{"prefix"} =~ m/^\Q$_\E/ # regular
+} keys %env) {
+ while (my ($opt, $val) = each(%{$env{$env}})) {
+ eval "\$opt_$opt = '$val' unless defined \$opt_$opt;";
+ }
+}
+
+##
+## OPTION POST-PROCESSING
+##
+
+my ($url, $repository, $installed, $env, $list, $bonly, $clist);
+my ($pattern, %with, %exclude, %hint);
+
+# determine package goal pattern
+if ($opt_a) {
+ $pattern = undef;
+} else {
+ $pattern = join(' ', @ARGV);
+}
+if ($opt_A) {
+ $pattern = '*';
+}
+
+# parse build options
+%with = map {
+ m/([^\s=]+)(?:\=(\S+))?/
+ ? ($1 => (defined($2) ? $2 : 'yes'))
+ : ()
+} split(/\s+/, $opt_D);
+
+# split accumulated option values
+%exclude = map { $_ => 1 } split(/\s+/, $opt_E);
+%hint = map { $_ => 1 } split(/\s+/, $opt_H);
+
+if (defined($opt_p)) {
+ $config->{platform} = $opt_p;
+}
+
+# determine RPM package repository information
+if (defined $opt_r) {
+ $url = $opt_r;
+ $url .= '/' unless $url =~ m/\/$/;
+} else {
+ $url = rpm_release_url();
+}
+# if we read the index from a file we can no longer deduce
+# repository paths from index paths. For now lets assume
+# that everything is below SRC/ to be compatible with
+# existing file indexes.
+if (defined($opt_f) and not defined($opt_r)) {
+ $url .= 'SRC/';
+}
+
+# determine information about INSTALLED packages (virtual and regular),
+# including their options, provides and requirements
+my $installed = get_installed();
+
+# SPECIAL CASE post-processing for
+# -Z (ignore installed packages)
+# -a (operate for all installed packages)
+if ($opt_a and $opt_Z) {
+ # This allows one to correctly upgrade an existing OpenPKG
+ # instance to a newer major version by querying all installed
+ # packages and their options (-a) but then ignore them (-Z) during
+ # the later processing and instead perform more or less a fresh
+ # rebuild from scratch. This ensures that during the process the
+ # installed packages are effectively picked up as dependencies
+ # only after they in turn were already updated.
+ foreach my $package (keys(%{$installed})) {
+ next if ($package =~ m/::/);
+ if (exists($installed->{$package}->{""})) {
+ # virtual package
+ $hint{$installed->{$package}->{""}->[0]->{"name"}} = 1
+ if (exists($installed->{$package}->{""}->[0]->{"name"}));
+ }
+ else {
+ # regular package
+ $pattern .= " $package";
+ foreach my $version (keys(%{$installed->{$package}})) {
+ foreach my $rec (@{$installed->{$package}->{$version}}) {
+ if (defined($rec->{"OPTIONS"})) {
+ my $options = $rec->{"OPTIONS"};
+ foreach my $option (keys(%{$options})) {
+ $with{$package."::".$option} = $options->{$option};
+ }
+ }
+ }
+ }
+ }
+ }
+}
+if ($opt_Z) {
+ $installed = {};
+}
+
+# determine information about AVAILABLE packages
+# by fetching and parsing a package repository XML/RDF index
+$repository = get_index(
+ $url . '00INDEX.rdf',
+ $opt_f,
+ $opt_X,
+ $config->{platform},
+ $installed
+);
+
+# assemble together all determined environment information
+$env = {
+ config => $config,
+ installed => $installed,
+ repository => $repository,
+ built => {},
+ revdep => undef,
+ with => \%with,
+ exclude => \%exclude,
+ hint => \%hint,
+ upgrade => ($opt_a || $opt_U),
+ zero => ($opt_z || $opt_Z),
+ exact => $opt_e,
+ quick => ($opt_q || $opt_z || $opt_Z),
+ status => ($opt_s || $opt_S),
+ fatal => [],
+ goals => $opt_g,
+ sourceonly => ($opt_u || $opt_U || $opt_z || $opt_Z)
+};
+
+##
+## PERFORM REQUESTED OPERATION
+##
+
+if ($opt_L) {
+ # case 1: calculate dependencies only and
+ # print packages depending on target
+ ($list) = build_deps($pattern, $env);
+ print_deps($list);
+} else {
+ # case 2: calculate build commands and
+ # print results in different formats
+ ($list, $bonly, $clist) = build_list($pattern, $env);
+ die "openpkg:build:FATAL: cannot find package\n" if (not defined($list));
+ if ($opt_M) {
+ print_map($installed, $repository, $list, $bonly, $clist);
+ } elsif ($opt_S) {
+ print_status($installed, $repository, $list, $bonly, $clist);
+ } elsif ($opt_s) {
+ print_status($installed, {}, $list, $bonly, $clist);
+ } else {
+ if (@{$env->{fatal}}) {
+ die "openpkg:build:FATAL: errors occured while building:\n", @{$env->{fatal}}, "\n";
+ }
+ print_list1($list, $config, $opt_a || $opt_u || $opt_U, $env->{with}, $opt_i, $opt_b, $opt_B);
+ print_list2($bonly, $config) if (not $opt_K);
+ }
+}
+
+# die gracefully
+exit(0);
+
+#############################################################################
+##
+## FUNCTIONS: PARSING & RUN-TIME INFORMATION
+##
+#############################################################################
+
+# home-brewn getopt(3) style option parser
+sub getopts ($) {
+ my ($opts) = @_;
+ my (%optf) = map { m/(\w)/; $1 => $_ } $opts =~ m/(\w:|\w)/g;
+ my (%opts, @argv, $optarg);
+
+ foreach (@ARGV) {
+ if (@argv) {
+ push @argv, $_;
+ } elsif (defined $optarg) {
+ if (exists $opts{$optarg}) {
+ $opts{$optarg} .= " $_";
+ } else {
+ $opts{$optarg} = $_;
+ }
+ $optarg = undef;
+ } elsif (!/^[-]/) {
+ push @argv, $_;
+ } else {
+ while (/^\-(\w)(.*)/) {
+ if (exists $optf{$1}) {
+ if (length($optf{$1}) > 1) {
+ if ($2 ne '') {
+ if (exists $opts{$1}) {
+ $opts{$1} .= " $2";
+ } else {
+ $opts{$1} = $2;
+ }
+ } else {
+ $optarg = $1;
+ }
+ last;
+ } else {
+ $opts{$1} = 1;
+ }
+ } else {
+ warn "openpkg:build:WARNING: unknown option $_\n";
+ }
+ $_ = "-$2";
+ }
+ }
+ }
+ if (defined $optarg) {
+ warn "openpkg:build:WARNING: option $optarg requires an argument\n";
+ }
+ foreach (keys %opts) {
+ eval '$opt_'.$_.' = "'.quotemeta($opts{$_}).'";';
+ }
+ @ARGV = @argv;
+}
+
+# determine RPM run-time information
+sub rpm_runtime_info () {
+ # determine OpenPKG instance prefix via
+ # 1. the environment of the "openpkg build" framework
+ # 2. the installation path of the script
+ # 3. the installation path of the Perl interpreter
+ # 4. the path of the "openpkg" command in $PATH
+ my $l_prefix = $ENV{'OPENPKG_PREFIX'};
+ if (not $l_prefix) {
+ ($l_prefix) = ($0 =~ m/^(.+)\/lib(exec)?\/openpkg(-tools)?\/build(\.pl)?$/);
+ }
+ if (not $l_prefix) {
+ ($l_prefix) = ($^X =~ m/^(.+)\/bin\/perl.*$/);
+ }
+ if (not $l_prefix) {
+ $l_prefix = (`(which openpkg) 2>/dev/null` =~ m/^(.+)\/bin\/openpkg$/);
+ }
+ if (not -x "$l_prefix/bin/openpkg") {
+ die "openpkg:build:FATAL: cannot determine OpenPKG instance prefix";
+ }
+ print "# operating with OpenPKG instance $l_prefix\n";
+
+ # determine OpenPKG RPM command
+ my $rpm = $opt_R || $env{''}->{'R'} ||
+ ((-f "$l_prefix/bin/openpkg" && -f "$l_prefix/libexec/openpkg/rpm") ?
+ "$l_prefix/bin/openpkg rpm" : "$l_prefix/bin/rpm");
+ $rpm = (`(which $rpm) 2>/dev/null` =~ m{^(/.*)})[0] if ($rpm !~ m|^/|);
+ die "openpkg:build:FATAL: cannot locate OpenPKG RPM in path" unless ($rpm =~ m{^/});
+ print "# operating with OpenPKG RPM $rpm\n";
+
+ # determine additional tools
+ my $mkp = "$l_prefix/bin/openpkg makeproxy";
+ my $rel = "$l_prefix/bin/openpkg release";
+ my $bzip2 = $rpm;
+ $bzip2 =~ s/\/bin\/openpkg rpm$/\/lib\/openpkg\/bzip2/;
+ my $curl = $rpm;
+ $curl =~ s/\/bin\/openpkg rpm$/\/lib\/openpkg\/curl/;
+ $curl = "$l_prefix/bin/openpkg curl" if (system("$l_prefix/bin/openpkg curl file://$l_prefix/etc/openpkg/platform >/dev/null 2>&1") == 0);
+
+ # expand RPM macros holding information
+ my $c = run("$rpm --eval '%{_rpmdir} %{_rpmfilename} %{_target_os} %{_target_cpu} %{_srcrpmdir}'");
+
+ # parse and post-process information
+ chomp($c);
+ my (@q) = split(/\s+/, $c);
+ $q[1] =~ s/%{OS}/$q[2]/;
+ $q[1] =~ s/%{ARCH}/$q[3]/;
+
+ # expand RPM rc information about tools
+ $c = run("$rpm --showrc");
+ my @g = ($c =~ m/\%\{l_tool_locate\s+([^\s\}]+)/g);
+
+ # return accumulated information
+ return {
+ rpm => $rpm,
+ mkp => $mkp,
+ rel => $rel,
+ bzip2 => $bzip2,
+ curl => $curl,
+ rpmdir => $q[0],
+ srcrpmdir=> $q[4],
+ template => $q[1],
+ platform => '',
+ prefix => $l_prefix,
+ optreg => '(?:'. join('|', map { "\Quse_$_\E" } @g) .')'
+ };
+}
+
+# determine RPM release URL
+sub rpm_release_url ($$) {
+ my ($rel, $url);
+
+ # determine the release URL the newer way
+ $url = run("(".$config->{"rel"}." --fmt='%u') 2>/dev/null || true") || "";
+ $url =~ s/^\s+//s;
+ $url =~ s/\s+$//s;
+
+ # use a local jumpstart RDF
+ if (-f $config->{"prefix"}."/etc/openpkg/build.rdf") {
+ $url = "file://".$config->{"prefix"}."/etc/openpkg/build.rdf"
+ }
+
+ return $url;
+}
+
+#############################################################################
+##
+## FUNCTIONS: VERSION STRING HANDLING
+##
+#############################################################################
+
+# compare two package versions
+# - "openpkg rpm":
+# splits according to !isalnum(3) ([a-zA-Z0-9])
+# and between isdigit(3) ([0-9]) and isalpha(3) ([a-zA-Z])
+# - "openpkg build" (this):
+# splits on "." characters
+sub vcmp_version ($$) {
+ my ($a, $b) = @_;
+ my (@a, @b, $c);
+ my ($ax, $bx);
+
+ # split according to dots
+ @a = split(/\./, $a);
+ @b = split(/\./, $b);
+
+ # compare as long as components exist
+ while (@a && @b) {
+ if ($a[0] =~ m/^\d+$/ && $b[0] =~ m/^\d+$/) {
+ # numerical comparison
+ $c = $a[0] <=> $b[0];
+ } elsif ((($a, $ax) = $a[0] =~ m/^(\d+)(.*)$/) &&
+ (($b, $bx) = $b[0] =~ m/^(\d+)(.*)$/)) {
+ # numerical comparison for prefix,
+ # string comparison for remainder
+ $c = $a <=> $b;
+ $c = $ax cmp $bx unless ($c);
+ } else {
+ # string comparison
+ $c = $a[0] cmp $b[0];
+ }
+
+ # stop comparison if components already mismatched
+ return $c if ($c != 0);
+
+ # else reduce by one component level
+ shift(@a);
+ shift(@b);
+ }
+
+ # finally compare number of remaining components
+ # (in case one is more specific)
+ $c = (scalar(@a) <=> scalar(@b));
+ return $c;
+}
+
+# compare two package releases
+# - "openpkg rpm":
+# uses "vcmp_version" semantics 1:1 again
+# - "openpkg build" (this):
+# uses "vcmp_version" semantics 1:1 again (>= 20060719)
+# trivial string comparison (<= 20060719)
+sub vcmp_release ($$) {
+ my ($a, $b) = @_;
+
+ return vcmp_version($a, $b);
+}
+
+# compare two package "version" or "version-release" strings
+# - "openpkg rpm":
+# compares "epoch", "version", "release" (in this order)
+# - "openpkg build" (this):
+# compares "release", "version", (in this order)
+sub vcmp ($$) {
+ my ($a, $b) = @_;
+ my ($c);
+
+ # short-circuit comparison for simple case
+ return 0 if ($a eq $b);
+
+ # split into "version" and "release"
+ my ($av, $ar) = ($a =~ m/^(.*?)(?:\-([\d\.]+))?$/);
+ my ($bv, $br) = ($b =~ m/^(.*?)(?:\-([\d\.]+))?$/);
+
+ # compare "release"
+ if (defined($ar) and defined($br)) {
+ $c = vcmp_release($ar, $br);
+ return $c if ($c); # short-circuit
+ }
+
+ # compare "version"
+ if (defined($av) && defined($bv)) {
+ $c = vcmp_version($av, $bv);
+ return $c if ($c); # short-circuit
+ }
+
+ # equality
+ return 0;
+}
+
+# create "version" or "version-release" string
+# from a provide object (see parse_provides)
+sub vs ($) {
+ my ($t) = @_;
+ return (
+ defined($t->{release})
+ ? "$t->{version}-$t->{release}"
+ : $t->{version}
+ );
+}
+
+# create "name-version" or "name-version-release" string
+# from a provide object (see parse_provides)
+sub vsn ($) {
+ my ($t) = @_;
+ return "$t->{name}-".vs($t);
+}
+
+#############################################################################
+##
+## FUNCTIONS: INSTALLATION INFORMATION
+##
+#############################################################################
+
+# parse OpenPKG RPM 'provide' string
+# "" (virtual package)
+# " = -" (regular package)
+# "::