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