|
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 ## |
|
23 |
|
24 ############################################################################# |
|
25 ## |
|
26 ## MAIN PROCEDURE |
|
27 ## |
|
28 ############################################################################# |
|
29 |
|
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 ); |
|
40 |
|
41 # global context variables |
|
42 my $prg = "openpkg build"; |
|
43 my %env = ('' => {}); |
|
44 |
|
45 ## |
|
46 ## OPTION PARSING |
|
47 ## |
|
48 |
|
49 # parse command line options |
|
50 my $getopts = 'hR:r:f:uUaAzZP:N:E:H:iD:p:qsSXMLWKebBgk'; |
|
51 getopts($getopts); |
|
52 |
|
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 } |
|
73 |
|
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 }; |
|
123 |
|
124 # determine RPM run-time information |
|
125 my $config = rpm_runtime_info(); |
|
126 |
|
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 } |
|
137 |
|
138 ## |
|
139 ## OPTION POST-PROCESSING |
|
140 ## |
|
141 |
|
142 my ($url, $repository, $installed, $env, $list, $bonly, $clist); |
|
143 my ($pattern, %with, %exclude, %hint); |
|
144 |
|
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 } |
|
154 |
|
155 # parse build options |
|
156 %with = map { |
|
157 m/([^\s=]+)(?:\=(\S+))?/ |
|
158 ? ($1 => (defined($2) ? $2 : 'yes')) |
|
159 : () |
|
160 } split(/\s+/, $opt_D); |
|
161 |
|
162 # split accumulated option values |
|
163 %exclude = map { $_ => 1 } split(/\s+/, $opt_E); |
|
164 %hint = map { $_ => 1 } split(/\s+/, $opt_H); |
|
165 |
|
166 if (defined($opt_p)) { |
|
167 $config->{platform} = $opt_p; |
|
168 } |
|
169 |
|
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 } |
|
184 |
|
185 # determine information about INSTALLED packages (virtual and regular), |
|
186 # including their options, provides and requirements |
|
187 my $installed = get_installed(); |
|
188 |
|
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 } |
|
226 |
|
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 ); |
|
236 |
|
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 }; |
|
256 |
|
257 ## |
|
258 ## PERFORM REQUESTED OPERATION |
|
259 ## |
|
260 |
|
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 } |
|
285 |
|
286 # die gracefully |
|
287 exit(0); |
|
288 |
|
289 ############################################################################# |
|
290 ## |
|
291 ## FUNCTIONS: PARSING & RUN-TIME INFORMATION |
|
292 ## |
|
293 ############################################################################# |
|
294 |
|
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); |
|
300 |
|
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 } |
|
345 |
|
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"; |
|
367 |
|
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"; |
|
375 |
|
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); |
|
384 |
|
385 # expand RPM macros holding information |
|
386 my $c = run("$rpm --eval '%{_rpmdir} %{_rpmfilename} %{_target_os} %{_target_cpu} %{_srcrpmdir}'"); |
|
387 |
|
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]/; |
|
393 |
|
394 # expand RPM rc information about tools |
|
395 $c = run("$rpm --showrc"); |
|
396 my @g = ($c =~ m/\%\{l_tool_locate\s+([^\s\}]+)/g); |
|
397 |
|
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 } |
|
413 |
|
414 # determine RPM release URL |
|
415 sub rpm_release_url ($$) { |
|
416 my ($rel, $url); |
|
417 |
|
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; |
|
422 |
|
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 } |
|
427 |
|
428 return $url; |
|
429 } |
|
430 |
|
431 ############################################################################# |
|
432 ## |
|
433 ## FUNCTIONS: VERSION STRING HANDLING |
|
434 ## |
|
435 ############################################################################# |
|
436 |
|
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); |
|
447 |
|
448 # split according to dots |
|
449 @a = split(/\./, $a); |
|
450 @b = split(/\./, $b); |
|
451 |
|
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 } |
|
467 |
|
468 # stop comparison if components already mismatched |
|
469 return $c if ($c != 0); |
|
470 |
|
471 # else reduce by one component level |
|
472 shift(@a); |
|
473 shift(@b); |
|
474 } |
|
475 |
|
476 # finally compare number of remaining components |
|
477 # (in case one is more specific) |
|
478 $c = (scalar(@a) <=> scalar(@b)); |
|
479 return $c; |
|
480 } |
|
481 |
|
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) = @_; |
|
490 |
|
491 return vcmp_version($a, $b); |
|
492 } |
|
493 |
|
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); |
|
502 |
|
503 # short-circuit comparison for simple case |
|
504 return 0 if ($a eq $b); |
|
505 |
|
506 # split into "version" and "release" |
|
507 my ($av, $ar) = ($a =~ m/^(.*?)(?:\-([\d\.]+))?$/); |
|
508 my ($bv, $br) = ($b =~ m/^(.*?)(?:\-([\d\.]+))?$/); |
|
509 |
|
510 # compare "release" |
|
511 if (defined($ar) and defined($br)) { |
|
512 $c = vcmp_release($ar, $br); |
|
513 return $c if ($c); # short-circuit |
|
514 } |
|
515 |
|
516 # compare "version" |
|
517 if (defined($av) && defined($bv)) { |
|
518 $c = vcmp_version($av, $bv); |
|
519 return $c if ($c); # short-circuit |
|
520 } |
|
521 |
|
522 # equality |
|
523 return 0; |
|
524 } |
|
525 |
|
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 } |
|
536 |
|
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 } |
|
543 |
|
544 ############################################################################# |
|
545 ## |
|
546 ## FUNCTIONS: INSTALLATION INFORMATION |
|
547 ## |
|
548 ############################################################################# |
|
549 |
|
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); |
|
557 |
|
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 } |
|
567 |
|
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 } |
|
578 |
|
579 # parse option from RPM 'provides' list |
|
580 sub parse_provideslist ($) { |
|
581 my ($l) = @_; |
|
582 my ($p); |
|
583 my ($nam, $val, %opts); |
|
584 |
|
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 } |
|
592 |
|
593 # translate dependency object into provides object |
|
594 sub depends2provides ($) { |
|
595 my ($dep) = @_; |
|
596 my ($ver, $rel, $pxy, $pre, $with); |
|
597 |
|
598 ($ver, $rel, $pxy) = ($dep->{val} =~ m/^([^\s\-]+)-([^\s\+]+)(\+PROXY)?$/); |
|
599 ($pre, $with) = ($dep->{name} =~ m/^(\S+?)::(\S*)$/); |
|
600 |
|
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 } |
|
610 |
|
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); |
|
618 |
|
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 } |
|
658 |
|
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); |
|
670 |
|
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 # }; |
|
721 |
|
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; |
|
733 |
|
734 # short-circuit processing for RPM special case |
|
735 next if ($p->{name} =~ m/^gpg\(/); |
|
736 |
|
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 } |
|
743 |
|
744 # is this a virtual target? |
|
745 $vs = vs($p); |
|
746 if ($vs eq '') { |
|
747 push(@list, $p); |
|
748 next; |
|
749 } |
|
750 |
|
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 }); |
|
760 |
|
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 } |
|
773 |
|
774 # remove assembled details |
|
775 @list = (); |
|
776 } |
|
777 if (@list) { |
|
778 print "# ATTENTION: ", scalar(@list), " froods (unassignable RPM 'provides') left\n"; |
|
779 } |
|
780 |
|
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 } |
|
792 |
|
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 } |
|
816 |
|
817 # return final result |
|
818 return \%map; |
|
819 } |
|
820 |
|
821 ############################################################################# |
|
822 ## |
|
823 ## FUNCTIONS: REPOSITORY INDEX INFORMATION |
|
824 ## |
|
825 ############################################################################# |
|
826 |
|
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); |
|
834 |
|
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 } |
|
853 |
|
854 # open index |
|
855 open(RFH, $path) or |
|
856 die "openpkg:build:FATAL: cannot open '$fetch' ($!)\n"; |
|
857 |
|
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 } |
|
863 |
|
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); |
|
868 |
|
869 # close index |
|
870 close(RFH) |
|
871 or die "openpkg:build:FATAL: an I/O error occured\n"; |
|
872 |
|
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 } |
|
886 |
|
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 } |
|
910 |
|
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); |
|
925 |
|
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 } |
|
946 |
|
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); |
|
960 |
|
961 print "# using internal XML/RDF parser\n"; |
|
962 |
|
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; |
|
969 |
|
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 } |
|
982 |
|
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 } |
|
990 |
|
991 # skip content unless referenced piece was found |
|
992 next if (not defined($href)); |
|
993 |
|
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; |
|
1006 |
|
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 } |
|
1130 |
|
1131 # return contained XML/RDF indices |
|
1132 return \@include; |
|
1133 } |
|
1134 |
|
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); |
|
1143 |
|
1144 print "# using external XML/RDF parser\n"; |
|
1145 |
|
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'}; |
|
1150 |
|
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 )); |
|
1164 |
|
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 }); |
|
1189 |
|
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 } |
|
1199 |
|
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 } |
|
1226 |
|
1227 # determine contained XML/RDF indices |
|
1228 if ($sub) { |
|
1229 @include = map { |
|
1230 goodpf($_->{platform}, $pfmatch) |
|
1231 ? ( $_->{href} ) |
|
1232 : ( ) |
|
1233 } @$sub; |
|
1234 } |
|
1235 |
|
1236 # return contained XML/RDF indices |
|
1237 return \@include; |
|
1238 } |
|
1239 |
|
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 } |
|
1248 |
|
1249 # convert simple XML parser Bag into flat list |
|
1250 sub swith ($$) { |
|
1251 my ($bags,$name) = @_; |
|
1252 my ($cond); |
|
1253 my (@out); |
|
1254 |
|
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 } |
|
1265 |
|
1266 # convert (conditional) XML/RDF Bag into flat list |
|
1267 sub xwith ($) { |
|
1268 my ($bags) = @_; |
|
1269 my ($bag, $li, $el); |
|
1270 my (@out); |
|
1271 |
|
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 } |
|
1285 |
|
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 } |
|
1293 |
|
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 } |
|
1300 |
|
1301 |
|
1302 ############################################################################# |
|
1303 ## |
|
1304 ## FUNCTIONS: HELPER FUNCTIONS FOR XML PARSING & DEPENDENCY PROCESSING |
|
1305 ## |
|
1306 ############################################################################# |
|
1307 |
|
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 } |
|
1320 |
|
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 } |
|
1338 |
|
1339 ############################################################################# |
|
1340 ## |
|
1341 ## FUNCTIONS: DEPENDENCY PROCESSING |
|
1342 ## |
|
1343 ############################################################################# |
|
1344 |
|
1345 # search environment for packages that match a pattern |
|
1346 sub search_pattern ($$) { |
|
1347 my ($pattern, $env) = @_; |
|
1348 my (@todo); |
|
1349 |
|
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 } |
|
1379 |
|
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); |
|
1386 |
|
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 } |
|
1444 |
|
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); |
|
1451 |
|
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 } |
|
1458 |
|
1459 # filter package options |
|
1460 sub filter_name_with ($$$) { |
|
1461 my ($name, $with, $global) = @_; |
|
1462 my (@keys); |
|
1463 |
|
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 } |
|
1477 |
|
1478 # filter out package relevant options |
|
1479 sub name_with ($$) { |
|
1480 filter_name_with($_[0], $_[1], 1); |
|
1481 } |
|
1482 |
|
1483 # filter out package specific options |
|
1484 sub name_only_with ($$) { |
|
1485 filter_name_with($_[0], $_[1], 0); |
|
1486 } |
|
1487 |
|
1488 # evaluate a condition attribute from an option set |
|
1489 sub conditional ($$) { |
|
1490 my ($cond, $with) = @_; |
|
1491 my (@s, $res); |
|
1492 |
|
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); |
|
1515 |
|
1516 die "openpkg:build:FATAL: stack not empty in: $cond\n" if scalar(@s) > 0; |
|
1517 return $res; |
|
1518 } |
|
1519 |
|
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); |
|
1525 |
|
1526 return if (not $target); |
|
1527 $name = $target->{name}; |
|
1528 |
|
1529 my ($mywith) = ($with ? $with : get_with($target)); |
|
1530 override_options($mywith, name_with($name, $env->{with}), $optreg); |
|
1531 |
|
1532 foreach (@{$target->{$attr}}) { |
|
1533 next if (not conditional($_->{'cond'}, $mywith)); |
|
1534 push(@out, $_->{'value'}); |
|
1535 } |
|
1536 return \@out; |
|
1537 } |
|
1538 |
|
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 } |
|
1549 |
|
1550 # see whether target has conflicts |
|
1551 sub target_conflicts ($$) { |
|
1552 my ($target, $env) = @_; |
|
1553 return target_attribute($target, $env, 'conflicts'); |
|
1554 } |
|
1555 |
|
1556 # retrieve build dependencies for target |
|
1557 sub target_depends ($$) { |
|
1558 my ($target, $env) = @_; |
|
1559 return target_attribute($target, $env, 'depends'); |
|
1560 } |
|
1561 |
|
1562 # retrieve runtime dependencies for target |
|
1563 sub target_keeps ($$) { |
|
1564 my ($target, $env) = @_; |
|
1565 return target_attribute($target, $env, 'keeps'); |
|
1566 } |
|
1567 |
|
1568 # retrieve source list for target |
|
1569 sub target_source ($$) { |
|
1570 my ($target, $env) = @_; |
|
1571 return target_attribute($target, $env, 'source'); |
|
1572 } |
|
1573 |
|
1574 # retrieve nosource list for target |
|
1575 sub target_nosource ($$) { |
|
1576 my ($target, $env) = @_; |
|
1577 return target_attribute($target, $env, 'nosource'); |
|
1578 } |
|
1579 |
|
1580 # check whether target conflicts against map |
|
1581 sub target_has_conflicts ($$$) { |
|
1582 my ($target, $map, $env) = @_; |
|
1583 my ($conflicts, $t); |
|
1584 |
|
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 } |
|
1592 |
|
1593 # record target status |
|
1594 sub target_setstatus ($$$) { |
|
1595 my ($target, $status, $pri) = @_; |
|
1596 |
|
1597 if ($pri > $target->{STATUSPRI}) { |
|
1598 $target->{STATUSPRI} = $pri; |
|
1599 $target->{STATUS} = $status; |
|
1600 } |
|
1601 } |
|
1602 |
|
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 } |
|
1614 |
|
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); |
|
1622 |
|
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 } |
|
1639 |
|
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}}; |
|
1645 |
|
1646 # rebuild if target isn't installed |
|
1647 return 'new' unless $vmap; |
|
1648 |
|
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 } |
|
1654 |
|
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 } |
|
1660 |
|
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 } |
|
1666 |
|
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 } |
|
1672 |
|
1673 # keep installed target |
|
1674 return; |
|
1675 } |
|
1676 |
|
1677 # check if target record describes a source package |
|
1678 sub is_source ($) { |
|
1679 my ($t) = @_; |
|
1680 return !(defined $t->{'prefix'}); |
|
1681 } |
|
1682 |
|
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); |
|
1687 |
|
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; |
|
1693 |
|
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); |
|
1707 |
|
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 } |
|
1714 |
|
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 } |
|
1724 |
|
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 } |
|
1732 |
|
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 } |
|
1741 |
|
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 } |
|
1749 |
|
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 } |
|
1763 |
|
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 } |
|
1777 |
|
1778 # nothing left -> exit |
|
1779 return if (scalar(@recs) == 0); |
|
1780 |
|
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 } |
|
1786 |
|
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); |
|
1793 |
|
1794 ($name, $op, $version) = ($dep->{name}, $dep->{op}, $dep->{val}); |
|
1795 |
|
1796 $i = $env->{installed}->{$name}; |
|
1797 $r = $env->{repository}->{$name}; |
|
1798 $b = $env->{built}->{$name}; |
|
1799 |
|
1800 return if (not ($i || $r || $b)); |
|
1801 |
|
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 } |
|
1817 |
|
1818 $tdef = undef; |
|
1819 |
|
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 } |
|
1833 |
|
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 } |
|
1840 |
|
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 } |
|
1854 |
|
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 } |
|
1861 |
|
1862 return; |
|
1863 } |
|
1864 |
|
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); |
|
1873 |
|
1874 print "# computing reverse dependencies\n"; |
|
1875 |
|
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 } |
|
1883 |
|
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); |
|
1894 |
|
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 } |
|
1918 |
|
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 } |
|
1929 |
|
1930 # return hash of package reverse dependencies |
|
1931 return \%rev; |
|
1932 } |
|
1933 |
|
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); |
|
1939 |
|
1940 # determine all packages which match the pattern |
|
1941 $todo = search_pattern($pattern, $env); |
|
1942 |
|
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; |
|
1952 |
|
1953 # also add target name |
|
1954 push @list, @$todo; |
|
1955 |
|
1956 # strip duplicate names |
|
1957 @list = keys %{ { map { $_ => 1 } @list } }; |
|
1958 |
|
1959 # cache reverse dependencies |
|
1960 if (not $env->{revdep}) { |
|
1961 $env->{revdep} = get_revdep($env); |
|
1962 } |
|
1963 |
|
1964 # map targets into list of dependency names |
|
1965 @list = map { |
|
1966 $env->{revdep}->{$_} |
|
1967 ? (@{$env->{revdep}->{$_}}) |
|
1968 : () |
|
1969 } @list; |
|
1970 |
|
1971 # iterate over dependencies |
|
1972 foreach (@list) { |
|
1973 # avoiding cycles |
|
1974 next if ($env->{builddeps}->{$_->{name}}); |
|
1975 $env->{builddeps}->{$_->{name}} = 1; |
|
1976 |
|
1977 # generate result |
|
1978 push(@out, $_); |
|
1979 |
|
1980 # generate result recursively |
|
1981 $list = build_deps($_->{name}, $env); # RECURSION |
|
1982 push(@out, @$list); |
|
1983 } |
|
1984 |
|
1985 # return final results |
|
1986 return \@out; |
|
1987 } |
|
1988 |
|
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); |
|
1994 |
|
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 } |
|
2003 |
|
2004 # find target in map |
|
2005 sub find_target ($$$) { |
|
2006 my ($name, $map, $pos) = @_; |
|
2007 my ($vmap) = $map->{$name}; |
|
2008 my (@vs); |
|
2009 |
|
2010 return if (not $vmap); |
|
2011 @vs = sort { vcmp($b,$a) } keys(%$vmap); |
|
2012 return $vmap->{$vs[$pos]}->[-1]; |
|
2013 } |
|
2014 |
|
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; |
|
2021 |
|
2022 return 1 if (not defined($pro->{with})); |
|
2023 |
|
2024 my ($val) = defined($pro->{version}) ? $pro->{version} : 'yes'; |
|
2025 |
|
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 } |
|
2032 |
|
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 } |
|
2043 |
|
2044 return 0 if ($conflict); |
|
2045 |
|
2046 print "# ", vsn($target), " adds option $opt = $val\n"; |
|
2047 $with->{$opt} = $val; |
|
2048 return 1; |
|
2049 } |
|
2050 |
|
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 } |
|
2057 |
|
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); |
|
2063 |
|
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 } |
|
2069 |
|
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 } |
|
2077 |
|
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 } |
|
2085 |
|
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 } |
|
2092 |
|
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 } |
|
2113 |
|
2114 # use options from installed base |
|
2115 override_options(get_with($target), get_with($t), $env->{config}->{optreg}); |
|
2116 |
|
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 } |
|
2124 |
|
2125 if (exists($env->{exclude}->{$target->{name}})) { |
|
2126 die "openpkg:build:FATAL: target ".vsn($target)." is forbidden\n"; |
|
2127 } |
|
2128 |
|
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; |
|
2132 |
|
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 } |
|
2180 |
|
2181 print "# adding ".vsn($target)." to list\n"; |
|
2182 $target->{WHO} = $who; |
|
2183 $target->{WHY} = $target->{STATUS}; |
|
2184 push(@$list, $target); |
|
2185 |
|
2186 # remember new options |
|
2187 override_options(get_with($target), name_with($target->{name}, $env->{with}), ''); |
|
2188 |
|
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 } |
|
2195 |
|
2196 # cleanup limbo |
|
2197 $target->{LIMBO} = 0; |
|
2198 |
|
2199 # a dependency could not be resolved, don't bother with reverse |
|
2200 # dependencies for this target |
|
2201 return if ($conflict); |
|
2202 |
|
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; |
|
2210 |
|
2211 # this is a rebuild, keep this installed |
|
2212 push(@$blist, $t); |
|
2213 |
|
2214 print "# rebuilding reverse dependency ".vsn($t)."\n"; |
|
2215 make_dep($target, $t, $depth+1, $env, $list, $blist, $clist); # RECURSION |
|
2216 } |
|
2217 } |
|
2218 } |
|
2219 |
|
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); |
|
2227 |
|
2228 # determine all packages which match the pattern |
|
2229 $todo = search_pattern($pattern, $env); |
|
2230 |
|
2231 # chose sources for goals from repository |
|
2232 foreach $name (@$todo) { |
|
2233 $select = undef; |
|
2234 $select = $1 if ($name =~ s/,([^\s,]+)$//); |
|
2235 $t = undef; |
|
2236 |
|
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 } |
|
2250 |
|
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); |
|
2265 |
|
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 } |
|
2273 |
|
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; |
|
2280 |
|
2281 # return results |
|
2282 return (\@targets, \@bonly, \@conflicts); |
|
2283 } |
|
2284 |
|
2285 |
|
2286 ############################################################################# |
|
2287 ## |
|
2288 ## FUNCTIONS: RESULT PRINTING |
|
2289 ## |
|
2290 ############################################################################# |
|
2291 |
|
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]); } |
|
2305 |
|
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 } |
|
2318 |
|
2319 # print dependency list |
|
2320 sub print_deps ($) { |
|
2321 my ($list) = @_; |
|
2322 |
|
2323 print join("\n", sort map { vsn($_) } @$list), "\n"; |
|
2324 } |
|
2325 |
|
2326 # print dependency map |
|
2327 sub print_map ($$$$$) { |
|
2328 my ($installed, $repository, $list, $bonly, $clist) = @_; |
|
2329 my (%dep); |
|
2330 |
|
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 } |
|
2342 |
|
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); |
|
2351 |
|
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 } |
|
2368 |
|
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 } |
|
2388 |
|
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 } |
|
2403 |
|
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 } |
|
2409 |
|
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' : ''; |
|
2415 |
|
2416 $tmpl =~ s/%{NAME}/$target->{name}/; |
|
2417 $tmpl =~ s/%{VERSION}/$target->{version}/; |
|
2418 $tmpl =~ s/%{RELEASE}/$target->{release}$popt/; |
|
2419 |
|
2420 return $c->{rpmdir}.'/'.$tmpl; |
|
2421 } |
|
2422 |
|
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); |
|
2431 |
|
2432 $old = {} unless $old; |
|
2433 $def = {} unless $def; |
|
2434 |
|
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}); |
|
2439 |
|
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 ); |
|
2451 |
|
2452 $with = ' '.$with if ($with ne ''); |
|
2453 |
|
2454 return $with; |
|
2455 } |
|
2456 |
|
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; |
|
2462 |
|
2463 # pull in options from binary RPM file |
|
2464 delete $target{'OPTIONS'}; |
|
2465 get_with(\%target, $fn); |
|
2466 return \%target; |
|
2467 } |
|
2468 |
|
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 } |
|
2482 |
|
2483 # indent text to form a block |
|
2484 sub indent ($) { |
|
2485 my ($txt) = @_; |
|
2486 $txt =~ s/^/ /gm; |
|
2487 return $txt; |
|
2488 } |
|
2489 |
|
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); |
|
2503 |
|
2504 $mark = '::::'; |
|
2505 |
|
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 = ''; |
|
2517 |
|
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)) { |
|
2528 |
|
2529 $opt = make_defines($_->{OPTIONS}, $with, |
|
2530 $_->{DEFOPTS}, $c); |
|
2531 |
|
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"; |
|
2539 |
|
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 } |
|
2569 |
|
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 } |
|
2575 |
|
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 } |
|
2590 |
|
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); |
|
2597 |
|
2598 foreach (@$list) { |
|
2599 $pkg = "$_->{name}-$_->{version}-$_->{release}"; |
|
2600 print priv($config->{"rpm"} . " -e $pkg\n"); |
|
2601 } |
|
2602 } |
|
2603 |