Thu, 04 Oct 2012 20:30:05 +0200
Correct out of date build configuration, porting to Solaris 11 network
link infrastructure and new libpcap logic. This additionally allows for
device drivers in subdirectories of /dev. Correct packaged nmap
personalities and signatures to work out of the box. Finally, hack
arpd logic to properly close sockets and quit on TERM by repeating
signaling in the run command script. Sadly, all this fails to correct
the run time behaviour of honeyd which fails to bind to the IP layer.
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/>/>/g;
968 s/</</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 };
1068 }
1069 if ($section eq 'provides') {
1070 push(@provides, $body) if (!defined($usecond));
1071 } elsif ($section ne '') {
1072 push(@{$bags->{"$usecond"}->{$section}}, $body);
1073 }
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; });
1099 }
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" . $@;
1121 }
1122 foreach (@provides) {
1123 push(@{$map->{$_->{name}}->{vs($_)}}, $rec);
1124 }
1125 }
1126 # prepare to recognize next package
1127 $href = undef;
1128 }
1129 }
1131 # return contained XML/RDF indices
1132 return \@include;
1133 }
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'}};
1175 }
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; });
1198 }
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".$@;
1221 }
1222 foreach (@provides) {
1223 push(@{$map->{$_->{name}}->{vs($_)}}, $rec);
1224 }
1225 }
1227 # determine contained XML/RDF indices
1228 if ($sub) {
1229 @include = map {
1230 goodpf($_->{platform}, $pfmatch)
1231 ? ( $_->{href} )
1232 : ( )
1233 } @$sub;
1234 }
1236 # return contained XML/RDF indices
1237 return \@include;
1238 }
1240 # convert XML parser output to dependency records
1241 sub depend_list ($) {
1242 my ($dl) = @_;
1243 foreach (@$dl) {
1244 $_->{value} = parse_depends($_->{value});
1245 }
1246 return $dl;
1247 }
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 };
1261 }
1262 }
1263 return \@out;
1264 }
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 };
1280 }
1281 }
1282 }
1283 return \@out;
1284 }
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;
1292 }
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|$)/);
1299 }
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;
1319 }
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 );
1337 }
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).'$';
1361 }
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}});
1376 }
1377 return \@todo;
1378 }
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");
1404 }
1405 }
1406 }
1407 }
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 ];
1413 }
1414 }
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);
1429 }
1430 }
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 ];
1436 }
1437 }
1438 $opt = parse_options(\@l);
1439 }
1440 $t->{OPTIONS} = $opt;
1441 }
1442 return $opt;
1443 }
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};
1455 }
1456 }
1457 }
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);
1466 }
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 };
1476 }
1478 # filter out package relevant options
1479 sub name_with ($$) {
1480 filter_name_with($_[0], $_[1], 1);
1481 }
1483 # filter out package specific options
1484 sub name_only_with ($$) {
1485 filter_name_with($_[0], $_[1], 0);
1486 }
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);
1511 }
1512 }
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;
1518 }
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'});
1535 }
1536 return \@out;
1537 }
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 );
1548 }
1550 # see whether target has conflicts
1551 sub target_conflicts ($$) {
1552 my ($target, $env) = @_;
1553 return target_attribute($target, $env, 'conflicts');
1554 }
1556 # retrieve build dependencies for target
1557 sub target_depends ($$) {
1558 my ($target, $env) = @_;
1559 return target_attribute($target, $env, 'depends');
1560 }
1562 # retrieve runtime dependencies for target
1563 sub target_keeps ($$) {
1564 my ($target, $env) = @_;
1565 return target_attribute($target, $env, 'keeps');
1566 }
1568 # retrieve source list for target
1569 sub target_source ($$) {
1570 my ($target, $env) = @_;
1571 return target_attribute($target, $env, 'source');
1572 }
1574 # retrieve nosource list for target
1575 sub target_nosource ($$) {
1576 my ($target, $env) = @_;
1577 return target_attribute($target, $env, 'nosource');
1578 }
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;
1589 }
1590 return;
1591 }
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;
1600 }
1601 }
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}} = $_;
1610 }
1611 }
1612 return %out;
1613 }
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);
1627 }
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});
1635 }
1636 }
1637 return 1;
1638 }
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';
1653 }
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';
1659 }
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';
1665 }
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';
1671 }
1673 # keep installed target
1674 return;
1675 }
1677 # check if target record describes a source package
1678 sub is_source ($) {
1679 my ($t) = @_;
1680 return !(defined $t->{'prefix'});
1681 }
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;
1713 }
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);
1723 }
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);
1731 }
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);
1740 }
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);
1748 }
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++;
1759 }
1760 return;
1761 }
1762 }
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;
1774 }
1775 @recs = @nrecs if (@nrecs);
1776 }
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;
1785 }
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";
1816 }
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);
1829 }
1830 }
1831 }
1832 }
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);
1839 }
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);
1852 }
1853 }
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);
1860 }
1862 return;
1863 }
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;
1882 }
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;
1900 }
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} : $_);
1909 }
1910 $name = $name{$_};
1911 if (not $dep{$name}{$t->{name}}) {
1912 $dep{$name}{$t->{name}} = 1;
1913 push(@{$dlist{$name}}, $t2);
1914 }
1915 }
1916 }
1917 }
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 ];
1928 }
1930 # return hash of package reverse dependencies
1931 return \%rev;
1932 }
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);
1962 }
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);
1983 }
1985 # return final results
1986 return \@out;
1987 }
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";
2000 }
2001 }
2002 }
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];
2013 }
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;
2031 }
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;
2041 }
2042 }
2044 return 0 if ($conflict);
2046 print "# ", vsn($target), " adds option $opt = $val\n";
2047 $with->{$opt} = $val;
2048 return 1;
2049 }
2051 # remember fatal error
2052 sub pusherr ($$$) {
2053 my ($env, $target, $mess) = @_;
2054 print "# $mess\n";
2055 push(@{$env->{fatal}}, vsn($target).": $mess\n");
2056 }
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;
2068 }
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;
2076 }
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;
2084 }
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;
2091 }
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;
2112 }
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);
2123 }
2125 if (exists($env->{exclude}->{$target->{name}})) {
2126 die "openpkg:build:FATAL: target ".vsn($target)." is forbidden\n";
2127 }
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";
2150 }
2151 next;
2152 }
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;
2159 }
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";
2165 }
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;
2172 }
2173 }
2174 if (not $conflict) {
2175 foreach $t (@deps) {
2176 make_dep($target, $t, $depth+1, $env, $list, $blist, $clist); # RECURSION
2177 }
2178 }
2179 }
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";
2194 }
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);
2206 }
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
2216 }
2217 }
2218 }
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; });
2249 }
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";
2261 }
2262 }
2263 }
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);
2272 }
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);
2283 }
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";
2301 }
2302 }
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;
2315 }
2316 return (wantarray ? @{$out} : join(//, @{$out}));
2317 }
2319 # print dependency list
2320 sub print_deps ($) {
2321 my ($list) = @_;
2323 print join("\n", sort map { vsn($_) } @$list), "\n";
2324 }
2326 # print dependency map
2327 sub print_map ($$$$$) {
2328 my ($installed, $repository, $list, $bonly, $clist) = @_;
2329 my (%dep);
2331 foreach (@$bonly) {
2332 $_->{status} = 'TEMP';
2333 }
2334 foreach (reverse(@$list)) {
2335 printf("%-35s %-8s %s\n",
2336 $_->{WHO} ? vsn($_->{WHO}) : "GOAL",
2337 $_->{WHY} ? $_->{WHY} : '???',
2338 vsn($_)
2339 );
2340 }
2341 }
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 };
2360 }
2361 foreach (@$bonly) {
2362 next if (not $_->{release} =~ m/\S/);
2363 $map{$_->{name}} = {
2364 rel => "$_->{version}-$_->{release}",
2365 status => 'TEMP'
2366 };
2367 }
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);
2378 }
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);
2387 }
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);
2401 }
2402 }
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';
2408 }
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;
2421 }
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;
2455 }
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;
2467 }
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];
2481 }
2483 # indent text to form a block
2484 sub indent ($) {
2485 my ($txt) = @_;
2486 $txt =~ s/^/ /gm;
2487 return $txt;
2488 }
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";
2513 }
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;
2550 }
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);
2554 }
2555 } else {
2556 $cmd1 = '';
2557 if ($spkg =~ m|^\.?/|) {
2558 $cmd1 .= npriv($config->{"rpm"} . "$opt --rebuild $spkg $err\n");
2559 }
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);
2566 }
2567 }
2568 }
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";
2574 }
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";
2584 }
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";
2588 }
2589 }
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");
2601 }
2602 }