force enable DO_CUMULATE
[platform/upstream/build.git] / mkbaselibs
1 #!/usr/bin/perl -w
2
3 use POSIX;
4 use strict;
5 use File::Temp qw/tempfile tempdir/;
6
7 # See: http://www.rpm.org/max-rpm/s1-rpm-file-format-rpm-file-format.html#S3-RPM-FILE-FORMAT-HEADER-TAG-LISTING
8 # cf http://search.cpan.org/~davecross/Parse-RPM-Spec-0.01/lib/Parse/RPM/Spec.pm
9 my %STAG = (
10         "NAME"          => 1000,
11         "VERSION"       => 1001,
12         "RELEASE"       => 1002,
13         "EPOCH"         => 1003,
14         "SERIAL"        => 1003,
15         "SUMMARY"       => 1004,
16         "DESCRIPTION"   => 1005,
17         "BUILDTIME"     => 1006,
18         "BUILDHOST"     => 1007,
19         "INSTALLTIME"   => 1008,
20         "SIZE"          => 1009,
21         "DISTRIBUTION"  => 1010,
22         "VENDOR"        => 1011,
23         "GIF"           => 1012,
24         "XPM"           => 1013,
25         "LICENSE"       => 1014,
26         "COPYRIGHT"     => 1014,
27         "PACKAGER"      => 1015,
28         "GROUP"         => 1016,
29         "SOURCE"        => 1018,
30         "PATCH"         => 1019,
31         "URL"           => 1020,
32         "OS"            => 1021,
33         "ARCH"          => 1022,
34         "PREIN"         => 1023,
35         "POSTIN"        => 1024,
36         "PREUN"         => 1025,
37         "POSTUN"        => 1026,
38         "OLDFILENAMES"  => 1027,
39         "FILESIZES"     => 1028,
40         "FILESTATES"    => 1029,
41         "FILEMODES"     => 1030,
42         "FILERDEVS"     => 1033,
43         "FILEMTIMES"    => 1034,
44         "FILEMD5S"      => 1035,
45         "FILELINKTOS"   => 1036,
46         "FILEFLAGS"     => 1037,
47         "FILEUSERNAME"  => 1039,
48         "FILEGROUPNAME" => 1040,
49         "ICON"          => 1043,
50         "SOURCERPM"     => 1044,
51         "FILEVERIFYFLAGS"       => 1045,
52         "ARCHIVESIZE"   => 1046,
53         "PROVIDENAME"   => 1047,
54         "PROVIDES"      => 1047,
55         "REQUIREFLAGS"  => 1048,
56         "REQUIRENAME"   => 1049,
57         "REQUIREVERSION"        => 1050,
58         "NOSOURCE"      => 1051,
59         "NOPATCH"       => 1052,
60         "CONFLICTFLAGS" => 1053,
61         "CONFLICTNAME"  => 1054,
62         "CONFLICTVERSION"       => 1055,
63         "EXCLUDEARCH"   => 1059,
64         "EXCLUDEOS"     => 1060,
65         "EXCLUSIVEARCH" => 1061,
66         "EXCLUSIVEOS"   => 1062,
67         "RPMVERSION"    => 1064,
68         "TRIGGERSCRIPTS"        => 1065,
69         "TRIGGERNAME"   => 1066,
70         "TRIGGERVERSION"        => 1067,
71         "TRIGGERFLAGS"  => 1068,
72         "TRIGGERINDEX"  => 1069,
73         "VERIFYSCRIPT"  => 1079,
74         "CHANGELOGTIME" => 1080,
75         "CHANGELOGNAME" => 1081,
76         "CHANGELOGTEXT" => 1082,
77         "PREINPROG"     => 1085,
78         "POSTINPROG"    => 1086,
79         "PREUNPROG"     => 1087,
80         "POSTUNPROG"    => 1088,
81         "BUILDARCHS"    => 1089,
82         "OBSOLETENAME"  => 1090,
83         "OBSOLETES"     => 1090,
84         "VERIFYSCRIPTPROG"      => 1091,
85         "TRIGGERSCRIPTPROG"     => 1092,
86         "COOKIE"        => 1094,
87         "FILEDEVICES"   => 1095,
88         "FILEINODES"    => 1096,
89         "FILELANGS"     => 1097,
90         "PREFIXES"      => 1098,
91         "INSTPREFIXES"  => 1099,
92         "SOURCEPACKAGE" => 1106,
93         "PROVIDEFLAGS"  => 1112,
94         "PROVIDEVERSION"        => 1113,
95         "OBSOLETEFLAGS" => 1114,
96         "OBSOLETEVERSION"       => 1115,
97         "DIRINDEXES"    => 1116,
98         "BASENAMES"     => 1117,
99         "DIRNAMES"      => 1118,
100         "OPTFLAGS"      => 1122,
101         "DISTURL"       => 1123,
102         "PAYLOADFORMAT" => 1124,
103         "PAYLOADCOMPRESSOR"     => 1125,
104         "PAYLOADFLAGS"  => 1126,
105         "INSTALLCOLOR"  => 1127,
106         "INSTALLTID"    => 1128,
107         "REMOVETID"     => 1129,
108         "RHNPLATFORM"   => 1131,
109         "PLATFORM"      => 1132,
110         "PATCHESNAME"   => 1133,
111         "PATCHESFLAGS"  => 1134,
112         "PATCHESVERSION"        => 1135,
113         "CACHECTIME"    => 1136,
114         "CACHEPKGPATH"  => 1137,
115         "CACHEPKGSIZE"  => 1138,
116         "CACHEPKGMTIME" => 1139,
117         "FILECOLORS"    => 1140,
118         "FILECLASS"     => 1141,
119         "CLASSDICT"     => 1142,
120         "FILEDEPENDSX"  => 1143,
121         "FILEDEPENDSN"  => 1144,
122         "DEPENDSDICT"   => 1145,
123         "SOURCEPKGID"   => 1146,
124         "PRETRANS"      => 1151,
125         "POSTTRANS"     => 1152,
126         "PRETRANSPROG"  => 1153,
127         "POSTTRANSPROG" => 1154,
128         "DISTTAG"       => 1155,
129         "SUGGESTSNAME"  => 1156,
130         "SUGGESTSVERSION"       => 1157,
131         "SUGGESTSFLAGS" => 1158,
132         "ENHANCESNAME"  => 1159,
133         "ENHANCESVERSION"       => 1160,
134         "ENHANCESFLAGS" => 1161,
135         "PRIORITY"      => 1162,
136         "CVSID"         => 1163,
137         "VCS"           => 5034,
138 );
139
140 # do not mix numeric tags with symbolic tags.
141 # special symbolic tag 'FILENAME' exists.
142
143 # This function seems to take a set of tags and populates a global
144 # hash-table (%res) with data obtained by doing a binary unpack() on
145 # the raw package
146 # http://www.rpm.org/max-rpm/s1-rpm-file-format-rpm-file-format.html
147
148 sub rpmq_many {
149   my $rpm = shift;
150   my @stags = @_;
151
152   my $need_filenames = grep { $_ eq 'FILENAMES' } @stags;
153   push @stags, 'BASENAMES', 'DIRNAMES', 'DIRINDEXES', 'OLDFILENAMES' if $need_filenames;
154   @stags = grep { $_ ne 'FILENAMES' } @stags if $need_filenames;
155   my %stags = map {0+($STAG{$_} or $_) => $_} @stags;
156
157   my ($magic, $sigtype, $headmagic, $cnt, $cntdata, $lead, $head, $index, $data, $tag, $type, $offset, $count);
158
159   local *RPM;
160   if (ref($rpm) eq 'ARRAY') {
161     ($headmagic, $cnt, $cntdata) = unpack('N@8NN', $rpm->[0]);
162     if ($headmagic != 0x8eade801) {
163       warn("Bad rpm\n");
164       return ();
165     }
166     if (length($rpm->[0]) < 16 + $cnt * 16 + $cntdata) {
167       warn("Bad rpm\n");
168       return ();
169     }
170     $index = substr($rpm->[0], 16, $cnt * 16);
171     $data = substr($rpm->[0], 16 + $cnt * 16, $cntdata);
172   } else {
173     return () unless open(RPM, "<$rpm");
174     if (read(RPM, $lead, 96) != 96) {
175       warn("Bad rpm $rpm\n");
176       close RPM;
177       return ();
178     }
179     ($magic, $sigtype) = unpack('N@78n', $lead);
180     if ($magic != 0xedabeedb || $sigtype != 5) {
181       warn("Bad rpm $rpm\n");
182       close RPM;
183       return ();
184     }
185     if (read(RPM, $head, 16) != 16) {
186       warn("Bad rpm $rpm\n");
187       close RPM;
188       return ();
189     }
190     ($headmagic, $cnt, $cntdata) = unpack('N@8NN', $head);
191     if ($headmagic != 0x8eade801) {
192       warn("Bad rpm $rpm\n");
193       close RPM;
194       return ();
195     }
196     if (read(RPM, $index, $cnt * 16) != $cnt * 16) {
197       warn("Bad rpm $rpm\n");
198       close RPM;
199       return ();
200     }
201     $cntdata = ($cntdata + 7) & ~7;
202     if (read(RPM, $data, $cntdata) != $cntdata) {
203       warn("Bad rpm $rpm\n");
204       close RPM;
205       return ();
206     }
207   }
208
209   my %res = ();
210
211   if (ref($rpm) eq 'ARRAY' && @stags && @$rpm > 1) {
212     my %res2 = &rpmq_many([ $rpm->[1] ], @stags);
213     %res = (%res, %res2);
214     return %res;
215   }
216
217   if (ref($rpm) ne 'ARRAY' && @stags) {
218     if (read(RPM, $head, 16) != 16) {
219       warn("Bad rpm $rpm\n");
220       close RPM;
221       return ();
222     }
223     ($headmagic, $cnt, $cntdata) = unpack('N@8NN', $head);
224     if ($headmagic != 0x8eade801) {
225       warn("Bad rpm $rpm\n");
226       close RPM;
227       return ();
228     }
229     if (read(RPM, $index, $cnt * 16) != $cnt * 16) {
230       warn("Bad rpm $rpm\n");
231       close RPM;
232       return ();
233     }
234     if (read(RPM, $data, $cntdata) != $cntdata) {
235       warn("Bad rpm $rpm\n");
236       close RPM;
237       return ();
238     }
239   }
240   close RPM if ref($rpm) ne 'ARRAY';
241
242   return %res unless @stags;    # nothing to do
243
244   while($cnt-- > 0) {
245     ($tag, $type, $offset, $count, $index) = unpack('N4a*', $index);
246     $tag = 0+$tag;
247     if ($stags{$tag}) {
248       eval {
249         my $otag = $stags{$tag};
250         if ($type == 0) {
251           $res{$otag} = [ '' ];
252         } elsif ($type == 1) {
253           $res{$otag} = [ unpack("\@${offset}c$count", $data) ];
254         } elsif ($type == 2) {
255           $res{$otag} = [ unpack("\@${offset}c$count", $data) ];
256         } elsif ($type == 3) {
257           $res{$otag} = [ unpack("\@${offset}n$count", $data) ];
258         } elsif ($type == 4) {
259           $res{$otag} = [ unpack("\@${offset}N$count", $data) ];
260         } elsif ($type == 5) {
261           $res{$otag} = [ undef ];
262         } elsif ($type == 6) {
263           $res{$otag} = [ unpack("\@${offset}Z*", $data) ];
264         } elsif ($type == 7) {
265           $res{$otag} = [ unpack("\@${offset}a$count", $data) ];
266         } elsif ($type == 8 || $type == 9) {
267           my $d = unpack("\@${offset}a*", $data);
268           my @res = split("\0", $d, $count + 1);
269           $res{$otag} = [ splice @res, 0, $count ];
270         } else {
271           $res{$otag} = [ undef ];
272         }
273       };
274       if ($@) {
275         warn("Bad rpm $rpm: $@\n");
276         return ();
277       }
278     }
279   }
280
281   if ($need_filenames) {
282     if ($res{'OLDFILENAMES'}) {
283       $res{'FILENAMES'} = [ @{$res{'OLDFILENAMES'}} ];
284     } else {
285       my $i = 0;
286       $res{'FILENAMES'} = [ map {"$res{'DIRNAMES'}->[$res{'DIRINDEXES'}->[$i++]]$_"} @{$res{'BASENAMES'}} ];
287     }
288   }
289   return %res;
290 }
291
292 sub rpmq_add_flagsvers {
293   my $res = shift;
294   my $name = shift;
295   my $flags = shift;
296   my $vers = shift;
297
298   return unless $res;
299   my @flags = @{$res->{$flags} || []};
300   my @vers = @{$res->{$vers} || []};
301   for (@{$res->{$name}}) {
302     if (@flags && ($flags[0] & 0xe) && @vers) {
303       $_ .= ' ';
304       $_ .= '<' if $flags[0] & 2;
305       $_ .= '>' if $flags[0] & 4;
306       $_ .= '=' if $flags[0] & 8;
307       $_ .= " $vers[0]";
308     }
309     shift @flags;
310     shift @vers;
311   }
312 }
313
314 my @preamble = qw{
315   Name Version Release VCS Epoch Summary Copyright License Distribution
316   Disturl Vendor Group Packager Url Icon Prefixes
317 };
318
319 my $rpm;
320 my $arch;
321
322 my $config = '';
323
324 my $targettype;
325 my $targetarch;
326 my $prefix;
327 my $extension;
328 my $configdir;
329 my $targetname;
330 my $legacyversion;
331
332 my @baselib;
333 my @config;
334
335 my @provides;
336 my @obsoletes;
337 my @requires;
338 my @prerequires;
339 my @conflicts;
340 my @recommends;
341 my @supplements;
342 my @suggests;
343
344 my @prein;
345 my @postin;
346 my @preun;
347 my @postun;
348 my $autoreqprov;
349
350 my $verbose;
351 my %target_matched;
352 my @filesystem;
353
354 # Used for each package by
355 sub parse_config {
356   my $target = shift;
357   my $pkgname = shift;
358   my $pkgver = shift;
359
360   my $pkghasmatched;
361
362   my $pkgmatches = 1;
363   $prefix = '';
364   $legacyversion = '';
365   $extension = '';
366   $configdir = '';
367   $targetname = '';
368   ($targetarch, $targettype) = split(':', $target, 2);
369   @baselib = ();
370   @config = ();
371   @provides = ();
372   @obsoletes = ();
373   @requires = ();
374   @recommends = ();
375   @supplements = ();
376   @suggests = ();
377   @prerequires = ();
378   @conflicts = ();
379   @prein = ();
380   @postin = ();
381   @preun = ();
382   @postun = ();
383   $autoreqprov = 'on';
384   my $match1 = '';
385
386   for (split("\n", $config)) {
387     s/^\s+//;
388     s/\s+$//;
389     next if $_ eq '' || $_ =~ /^#/;
390
391     s/\<targettype\>/$targettype/g;
392     s/\<targetarch\>/$targetarch/g;
393     s/\<name\>/$pkgname/g;
394     s/\<version\>/$pkgver/g;
395     s/\<prefix\>/$prefix/g;
396     s/\<extension\>/$extension/g;
397     s/\<configdir\>/$configdir/g;
398     s/\<match1\>/$match1/g;
399
400     if (/^arch\s+/) {
401       next unless s/^arch\s+\Q$arch\E\s+//;
402     }
403     next if /^targets\s+/;
404     if (/\s+package\s+[-+_a-zA-Z0-9]+$/) {
405       $pkgmatches = 0;  # XXX: hack
406     }
407     if (/\s+package\s+\/[-+_a-zA-Z0-9]+\/$/) {
408       $pkgmatches = 0;  # XXX: hack
409     }
410     if (/^targettype\s+/) {
411       next unless s/^targettype\s+\Q$targettype\E\s+//;
412     }
413     if (/^targetarch\s+/) {
414       next unless s/^targetarch\s+\Q$targetarch\E\s+//;
415     }
416     if (/^prefix\s+(.*?)$/) { $prefix = $1; next; }
417     if (/^legacyversion\s+(.*?)$/) { $legacyversion = $1; next; }
418     if (/^extension\s+(.*?)$/) { $extension = $1; next; }
419     if (/^configdir\s+(.*?)$/) { $configdir= $1; next; }
420     if (/^targetname\s+(.*?)$/) { $targetname = $1; next; }
421
422     $_ = "baselib $_" if /^[\+\-\"]/;
423     $_ = "package $_" if /^[-+_a-zA-Z0-9]+$/;
424     if (/^package\s+\/(.*?)\/$/) {
425       my $pm = $1;
426       $pkgmatches = $pkgname =~ /$pm/;
427       $match1 = $1 if defined $1;
428       $pkghasmatched |= $pkgmatches if $pkgname =~ /-debuginfo$/ && $target_matched{$target};
429       next;
430     }
431     if (/^package\s+(.*?)$/) {
432       $pkgmatches = $1 eq $pkgname;
433       $pkghasmatched |= $pkgmatches;
434       next;
435     }
436     next unless $pkgmatches;
437     return 0 if $_ eq 'block!';
438     if (/^provides\s+(.*?)$/) { push @provides, $1; next; }
439     if (/^requires\s+(.*?)$/) { push @requires, $1; next; }
440     if (/^recommends\s+(.*?)$/) { push @recommends, $1; next; }
441     if (/^supplements\s+(.*?)$/) { push @supplements, $1; next; }
442     if (/^suggests\s+(.*?)$/) { push @suggests, $1; next; }
443     if (/^prereq\s+(.*?)$/) { push @prerequires, $1; next; }
444     if (/^obsoletes\s+(.*?)$/) { push @obsoletes, $1; next; }
445     if (/^conflicts\s+(.*?)$/) { push @conflicts, $1; next; }
446     if (/^baselib\s+(.*?)$/) { push @baselib, $1; next; }
447     if (/^config\s+(.*?)$/) { push @config, $1; next; }
448     if (/^pre(in)?\s+(.*?)$/) { push @prein, $2; next; }
449     if (/^post(in)?\s+(.*?)$/) { push @postin, $2; next; }
450     if (/^preun\s+(.*?)$/) { push @preun, $1; next; }
451     if (/^postun\s+(.*?)$/) { push @preun, $1; next; }
452     if (/^autoreqprov\s+(.*?)$/) {$autoreqprov = $1; next; }
453     die("bad line: $_\n");
454   }
455   return $pkghasmatched;
456 }
457
458 sub read_config {
459   my $cfname = shift;
460   local *F;
461   open(F, "<$cfname") || die("$cfname: $!\n");
462   my @cf = <F>;
463   close F;
464   $config .= join('', @cf);
465   $config .= "\npackage __does_not_match__\n";
466 }
467
468 sub get_targets {
469   my $architecture = shift;
470   my $conf = shift;
471   my %targets;
472   for (split("\n", $conf)) {
473     if (/^arch\s+/) {
474       next unless s/^arch\s+\Q$architecture\E\s+//;
475     }
476     if (/^targets\s+(.*?)$/) {
477       $targets{$_} = 1 for split(' ', $1);
478     }
479   }
480   my @targets = sort keys %targets;
481   return @targets;
482 }
483
484 # Packages listed in config file
485 sub get_pkgnames {
486   my %rpms;
487   for (split("\n", $config)) {
488     if (/^(.*\s+)?package\s+([-+_a-zA-Z0-9]+)\s*$/) {  # eg : arch ppc package libnuma-devel
489       $rpms{$2} = 1;
490     } elsif (/^\s*([-+_a-zA-Z0-9]+)\s*$/) { # eg: readline-devel
491       $rpms{$1} = 1;
492     }
493   }
494   return sort keys %rpms;
495 }
496
497 # Packages listed in config file - debian variant (can have "." in package names)
498 sub get_debpkgnames {
499   my %debs;
500   for (split("\n", $config)) {
501     if (/^(.*\s+)?package\s+([-+_a-zA-Z0-9.]+)\s*$/) {  # eg : arch ppc package libnuma-devel
502       $debs{$2} = 1;
503     } elsif (/^\s*([-+_a-zA-Z0-9.]+)\s*$/) { # eg: readline-devel
504       $debs{$1} = 1;
505     }
506   }
507   return sort keys %debs;
508 }
509
510 sub handle_rpms {
511  for $rpm (@_) {
512
513   my @stags = map {uc($_)} @preamble;
514   push @stags, 'DESCRIPTION';
515   push @stags, 'FILENAMES', 'FILEMODES', 'FILEUSERNAME', 'FILEGROUPNAME', 'FILEFLAGS', 'FILEVERIFYFLAGS';
516   push @stags, 'CHANGELOGTIME', 'CHANGELOGNAME', 'CHANGELOGTEXT';
517   push @stags, 'ARCH', 'SOURCERPM', 'RPMVERSION';
518   push @stags, 'BUILDTIME';
519   my %res = rpmq_many($rpm, @stags);
520   die("$rpm: bad rpm\n") unless $res{'NAME'};
521
522   my $rname = $res{'NAME'}->[0];
523   my $sname = $res{'SOURCERPM'}->[0];
524   die("$rpm is a sourcerpm\n") unless $sname;
525   die("bad sourcerpm: $sname\n") unless $sname =~ /^(.*)-([^-]+)-([^-]+)\.(no)?src\.rpm$/;
526   $sname = $1;
527   my $sversion = $2;
528   my $srelease = $3;
529
530   $arch = $res{'ARCH'}->[0];
531   my @targets = get_targets($arch, $config);
532   if (!@targets) {
533     print "no targets for arch $arch, nothing to do\n";
534     exit(0);
535   }
536   for my $target (@targets) {
537
538     next unless parse_config($target, $res{'NAME'}->[0], $res{'VERSION'}->[0]);
539     die("targetname not set\n") unless $targetname;
540     $target_matched{$target} = 1;
541
542     my %ghosts;
543     my @rpmfiles = @{$res{'FILENAMES'}};
544     my @ff = @{$res{'FILEFLAGS'}};
545     for (@rpmfiles) {
546       $ghosts{$_} = 1 if $ff[0] & (1 << 6);
547       shift @ff;
548     }
549     my %files;
550     my %cfiles;
551     my %moves;
552     my %symlinks;
553     for my $r (@baselib) {
554       my $rr = substr($r, 1);
555       if (substr($r, 0, 1) eq '+') {
556         if ($rr =~ /^(.*?)\s*->\s*(.*?)$/) {
557           if (grep {$_ eq $1} @rpmfiles) {
558             $files{$1} = 1;
559             $moves{$1} = $2;
560           }
561         } else {
562           for (grep {/$rr/} @rpmfiles) {
563             $files{$_} = 1;
564             delete $moves{$_};
565           }
566         }
567       } elsif (substr($r, 0, 1) eq '-') {
568         delete $files{$_} for grep {/$rr/} keys %files;
569       } elsif (substr($r, 0, 1) eq '"') {
570         $rr =~ s/\"$//;
571         if ($rr =~ /^(.*?)\s*->\s*(.*?)$/) {
572           $symlinks{$1} = $2;
573         } else {
574           die("bad baselib string rule: $r\n");
575         }
576       } else {
577         die("bad baselib rule: $r\n");
578       }
579     }
580     if ($configdir) {
581       for my $r (@config) {
582         my $rr = substr($r, 1);
583         if (substr($r, 0, 1) eq '+') {
584           $cfiles{$_} = 1 for grep {/$rr/} grep {!$ghosts{$_}} @rpmfiles;
585         } elsif (substr($r, 0, 1) eq '-') {
586           delete $cfiles{$_} for grep {/$rr/} keys %cfiles;
587         } else {
588           die("bad config rule: $r\n");
589         }
590       }
591     }
592     $files{$_} = 1 for keys %cfiles;
593
594     if (!%files) {
595       print "$rname($target): empty filelist, skipping rpm\n";
596       next;
597     }
598
599     my $i = 0;
600     for (@{$res{'FILENAMES'}}) {
601       $files{$_} = $i if $files{$_};
602       $i++;
603     }
604
605     my %cpiodirs;
606     for (keys %files) {
607       next if $cfiles{$_} || $moves{$_};
608       my $fn = $_;
609       next unless $fn =~ s/\/[^\/]+$//;
610       $cpiodirs{$fn} = 1;
611     }
612
613     my %alldirs;
614     for (keys %files) {
615       next if $cfiles{$_};
616       my $fn = $_;
617       if ($moves{$fn}) {
618         $fn = $moves{$fn};
619         next unless $fn =~ s/\/[^\/]+$//;
620         $alldirs{$fn} = 1;
621       } else {
622         next unless $fn =~ s/\/[^\/]+$//;
623         $alldirs{"$prefix$fn"} = 1;
624       }
625     }
626     $alldirs{$_} = 1 for keys %symlinks;
627     $alldirs{$configdir} = 1 if %cfiles;
628     my $ad;
629     for $ad (keys %alldirs) {
630       $alldirs{$ad} = 1 while $ad =~ s/\/[^\/]+$//;
631     }
632     for (keys %files) {
633       next if $cfiles{$_};
634       my $fn = $_;
635       if ($moves{$fn}) {
636         delete $alldirs{$moves{$fn}};
637       } else {
638         delete $alldirs{"$prefix$fn"};
639       }
640     }
641     $ad = $prefix;
642     delete $alldirs{$ad};
643     delete $alldirs{$ad} while $ad =~ s/\/[^\/]+$//;
644     delete $alldirs{$_} for @filesystem;
645
646     print "$rname($target): writing specfile...\n";
647     my ($fh, $specfile) = tempfile(SUFFIX => ".spec");
648     open(SPEC, ">&=", $fh) || die("open: $!\n");
649     for my $p (@preamble) {
650       my $pt = uc($p);
651       next unless $res{$pt};
652       my $d = $res{$pt}->[0];
653       $d =~ s/%/%%/g;
654       if ($p eq 'Name') {
655         print SPEC "Name: $sname\n";
656         next;
657       }
658       if ($p eq 'Version') {
659         print SPEC "Version: $sversion\n";
660         next;
661       }
662       if ($p eq 'Release') {
663         print SPEC "Release: $srelease\n";
664         next;
665       }
666       if ($p eq 'Disturl') {
667         print SPEC "%define disturl $d\n";
668         next;
669       }
670       print SPEC "$p: $d\n";
671     }
672     print SPEC "Source: $rpm\n";
673     print SPEC "NoSource: 0\n" if $res{'SOURCERPM'}->[0] =~ /\.nosrc\.rpm$/;
674     print SPEC "BuildRoot: %{_tmppath}/baselibs-%{name}-%{version}-build\n";
675     print SPEC "%define _target_cpu $targetarch\n";
676     print SPEC "%define __os_install_post %{nil}\n";
677     print SPEC "%description\nUnneeded main package. Ignore.\n\n";
678     print SPEC "%package -n $targetname\n";
679     for my $p (@preamble) {
680       next if $p eq 'Name' || $p eq 'Disturl';
681       my $pt = uc($p);
682       next unless $res{$pt};
683       my $d = $res{$pt}->[0];
684       $d =~ s/%/%%/g;
685       if ($pt eq 'VERSION' && $legacyversion) {
686         $d = $legacyversion;
687       } elsif ($pt eq 'RELEASE' && $legacyversion) {
688         my @bt = localtime($res{'BUILDTIME'}->[0]);
689         $bt[5] += 1900;
690         $bt[4] += 1;
691         $d = sprintf("%04d%02d%02d%02d%02d\n", @bt[5,4,3,2,1]);
692       }
693       print SPEC "$p: $d\n";
694     }
695     print SPEC "Autoreqprov: $autoreqprov\n";
696
697     for my $ar ([\@provides, 'provides'],
698                 [\@prerequires, 'prereq'],
699                 [\@requires, 'requires'],
700                 [\@recommends, 'recommends'],
701                 [\@supplements, 'supplements'],
702                 [\@obsoletes, 'obsoletes'],
703                 [\@conflicts, 'conflicts']) {
704         my @a = @{$ar->[0]};
705         my @na = ();
706         for (@a) {
707           if (substr($_, 0, 1) eq '"') {
708             die("bad $ar->[1] rule: $_\n") unless /^\"(.*)\"$/;
709             push @na, $1;
710           } elsif (substr($_, 0, 1) eq '-') {
711             my $ra = substr($_, 1);
712             @na = grep {!/$ra/} @na;
713           } else {
714             die("bad $ar->[1] rule: $_\n");
715           }
716         }
717       print SPEC ucfirst($ar->[1]).": $_\n" for @na;
718     }
719     my $cpiopre = '';
720     $cpiopre = './' if $res{'RPMVERSION'}->[0] !~ /^3/;
721     my $d = $res{'DESCRIPTION'}->[0];
722     $d =~ s/%/%%/g;
723     if ($legacyversion) {
724       $d = "This rpm was re-packaged from $res{'NAME'}->[0]-$res{'VERSION'}->[0]-$res{'RELEASE'}->[0]\n\n$d";
725     }
726     print SPEC "\n%description -n $targetname\n";
727     print SPEC "$d\n";
728     print SPEC "%prep\n";
729     print SPEC "%build\n";
730     print SPEC "%install\n";
731     print SPEC "rm -rf \$RPM_BUILD_ROOT\n";
732     print SPEC "mkdir \$RPM_BUILD_ROOT\n";
733     print SPEC "cd \$RPM_BUILD_ROOT\n";
734     my @cfl = grep {!$cfiles{$_} && !$moves{$_}} sort keys %files;
735     if (@cfl) {
736       if ($prefix ne '') {
737         print SPEC "mkdir -p \$RPM_BUILD_ROOT$prefix\n";
738         print SPEC "pushd \$RPM_BUILD_ROOT$prefix\n";
739       }
740       print SPEC "cat <<EOFL >.filelist\n";
741       print SPEC "$_\n" for map {$cpiopre.substr($_, 1)} @cfl;
742       print SPEC "EOFL\n";
743       print SPEC "mkdir -p \$RPM_BUILD_ROOT$prefix$_\n" for sort keys %cpiodirs;
744       print SPEC "rpm2cpio $rpm | cpio -i -d -v -E .filelist\n";
745       print SPEC "rm .filelist\n";
746       if (%ghosts) {
747         for my $fn (grep {$ghosts{$_}} @cfl) {
748           my $fnm = $fn;
749           $fnm = '.' unless $fnm =~ s/\/[^\/]+$//;
750           print SPEC "mkdir -p \$RPM_BUILD_ROOT$prefix$fnm\n";
751           print SPEC "touch \$RPM_BUILD_ROOT$prefix$fn\n";
752         }
753       }
754       if ($prefix ne '') {
755         print SPEC "popd\n";
756       }
757     }
758     if (%cfiles || %moves) {
759       print SPEC "mkdir -p .cfiles\n";
760       print SPEC "pushd .cfiles\n";
761       print SPEC "cat <<EOFL >.filelist\n";
762       print SPEC "$_\n" for map {$cpiopre.substr($_, 1)} grep {$cfiles{$_} || $moves{$_}} sort keys %files;
763       print SPEC "EOFL\n";
764       print SPEC "rpm2cpio $rpm | cpio -i -d -v -E .filelist\n";
765       print SPEC "popd\n";
766       if (%cfiles) {
767         print SPEC "mkdir -p \$RPM_BUILD_ROOT$configdir\n";
768         print SPEC "mv .cfiles$_ \$RPM_BUILD_ROOT$configdir\n" for sort keys %cfiles;
769       }
770       for my $fn (sort keys %moves) {
771         my $fnm = $moves{$fn};
772         $fnm = '.' unless $fnm =~ s/\/[^\/]+$//;
773         print SPEC "mkdir -p \$RPM_BUILD_ROOT$fnm\n";
774         print SPEC "mv .cfiles$fn \$RPM_BUILD_ROOT$moves{$fn}\n";
775       }
776       print SPEC "rm -rf .cfiles\n";
777     }
778     for my $fn (sort keys %symlinks) {
779       my $fnm = $fn;
780       $fnm = '.' unless $fnm =~ s/\/[^\/]+$//;
781       print SPEC "mkdir -p \$RPM_BUILD_ROOT$fnm\n";
782       print SPEC "ln -s $symlinks{$fn} \$RPM_BUILD_ROOT$fn\n";
783     }
784     if ($prefix ne '' && grep {/\.so.*$/} @cfl) {
785       @postin = () if @postin == 1 && $postin[0] =~ /^\"-p.*ldconfig/;
786       unshift @postin, "\"/sbin/ldconfig -r $prefix\"";
787     }
788
789     if (@prein) {
790       print SPEC "%pre -n $targetname";
791       print SPEC $prein[0] =~ /^\"-p/ ? " " : "\n";
792       for (@prein) {
793         die("bad prein rule: $_\n") unless /^\"(.*)\"$/;
794         print SPEC "$1\n";
795       }
796     }
797     if (@postin) {
798       print SPEC "%post -n $targetname";
799       print SPEC $postin[0] =~ /^\"-p/ ? " " : "\n";
800       for (@postin) {
801         die("bad postin rule: $_\n") unless /^\"(.*)\"$/;
802         print SPEC "$1\n";
803       }
804     }
805     if (@preun) {
806       print SPEC "%preun -n $targetname";
807       print SPEC $preun[0] =~ /^\"-p/ ? " " : "\n";
808       for (@preun) {
809         die("bad preun rule: $_\n") unless /^\"(.*)\"$/;
810         print SPEC "$1\n";
811       }
812     }
813     if (@postun) {
814       print SPEC "%postun -n $targetname";
815       print SPEC $postun[0] =~ /^\"-p/ ? " " : "\n";
816       for (@postun) {
817         die("bad postun rule: $_\n") unless /^\"(.*)\"$/;
818         print SPEC "$1\n";
819       }
820     }
821
822     print SPEC "\n%clean\n";
823     print SPEC "\nrm -rf \$RPM_BUILD_ROOT\n\n";
824     print SPEC "%files -n $targetname\n";
825     for my $file (sort keys %alldirs) {
826       print SPEC "%dir %attr(0755,root,root) $file\n";
827     }
828     for my $file (keys %files) {
829       my $fi = $files{$file};
830       my $fm = $res{'FILEMODES'}->[$fi];
831       my $fv = $res{'FILEVERIFYFLAGS'}->[$fi];
832       my $ff = $res{'FILEFLAGS'}->[$fi];
833       if (POSIX::S_ISDIR($fm)) {
834         print SPEC "%dir ";
835       }
836       if ($ff & ((1 << 3) | (1 << 4))) {
837         print SPEC "%config(missingok noreplace) ";
838       } elsif ($ff & (1 << 3)) {
839         print SPEC "%config(missingok) ";
840       } elsif ($ff & (1 << 4)) {
841         print SPEC "%config(noreplace) ";
842       } elsif ($ff & (1 << 0)) {
843         print SPEC "%config ";
844       }
845       print SPEC "%doc " if $ff & (1 << 1);
846       print SPEC "%ghost " if $ff & (1 << 6);
847       print SPEC "%license " if $ff & (1 << 7);
848       print SPEC "%readme " if $ff & (1 << 8);
849       if ($fv != 4294967295) {
850         print SPEC "%verify(";
851         if ($fv & 2147483648) {
852           print SPEC "not ";
853           $fv ^= 4294967295;
854         }
855         print SPEC "md5 " if $fv & (1 << 0);
856         print SPEC "size " if $fv & (1 << 1);
857         print SPEC "link " if $fv & (1 << 2);
858         print SPEC "user " if $fv & (1 << 3);
859         print SPEC "group " if $fv & (1 << 4);
860         print SPEC "mtime " if $fv & (1 << 5);
861         print SPEC "mode " if $fv & (1 << 6);
862         print SPEC "rdev " if $fv & (1 << 7);
863         print SPEC ") ";
864       }
865       #sigh, no POSIX::S_ISLNK ...
866       if (($fm & 0170000) == 0120000) {
867         printf SPEC "%%attr(-,%s,%s) ", $res{'FILEUSERNAME'}->[$fi], $res{'FILEGROUPNAME'}->[$fi];
868       } else {
869         printf SPEC "%%attr(%03o,%s,%s) ", $fm & 07777, $res{'FILEUSERNAME'}->[$fi], $res{'FILEGROUPNAME'}->[$fi];
870       }
871       if ($cfiles{$file}) {
872         my $fn = $file;
873         $fn =~ s/.*\///;
874         print SPEC "$configdir/$fn\n";
875       } else {
876         if ($moves{$file}) {
877           print SPEC "$moves{$file}\n";
878         } else {
879           print SPEC "$prefix$file\n";
880         }
881       }
882     }
883     for (keys %symlinks) {
884       printf SPEC "%%attr(-,root,root) $_\n";
885     }
886
887     if ($res{'CHANGELOGTEXT'}) {
888       print SPEC "\n%changelog -n $targetname\n";
889       my @ct = @{$res{'CHANGELOGTIME'}};
890       my @cn = @{$res{'CHANGELOGNAME'}};
891       my @wdays = qw{Sun Mon Tue Wed Thu Fri Sat};
892       my @months = qw{Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec};
893       for my $cc (@{$res{'CHANGELOGTEXT'}}) {
894         my @lt = localtime($ct[0]);
895         my $cc2 = $cc;
896         my $cn2 = $cn[0];
897         $cc2 =~ s/%/%%/g;
898         $cn2 =~ s/%/%%/g;
899         printf SPEC "* %s %s %02d %04d %s\n%s\n", $wdays[$lt[6]], $months[$lt[4]], $lt[3], 1900 + $lt[5], $cn2, $cc2;
900         shift @ct;
901         shift @cn;
902       }
903     }
904
905     close(SPEC) || die("$specfile: $!\n");
906     print "$rname($target): running build...\n";
907     if (system("rpmbuild -bb $specfile".($verbose ? '' : '>/dev/null 2>&1'))) {
908       print "rpmbuild failed: $?\n";
909       print "re-running in verbose mode:\n";
910       system("rpmbuild -bb $specfile 2>&1");
911       exit(1);
912     }
913     unlink($specfile);
914   }
915  }
916 }
917
918 ################################################################
919
920 sub handle_debs {
921
922   eval {
923     require Parse::DebControl;
924   };
925   if ($@){
926     print "mkbaselibs needs the perl module Parse::DebControl\n".
927       "Error. baselibs-deb.conf specified but mkbaselibs can't run\n".
928         "Please ensure that 'osc meta prjconf' contains the following line:\n".
929           "  Support: libparse-debcontrol-perl\n";
930     return;
931   };
932
933
934   # for each deb:
935   #  look in the config file to see if we should be doing anything
936   #
937   #  Unpack the deb control data using dpkg-deb
938   #  for each target
939   #   Unpack the deb control data *and* file data using dpkg-deb
940   #   process the config file for this package modifying control and moving files
941   #   repackage the target deb
942
943   for my $deb (@_) {
944     # http://www.debian.org/doc/debian-policy/ch-controlfields.html#s-binarycontrolfiles
945     # unpack the outer loop control file - this gives us eg: the arch
946     my $base = tempdir() || die("tempdir: $!\n");
947     system "dpkg -e $deb  ${base}/DEBIAN" || die "dpkg -e failed on $deb";
948     my $controlParser = new Parse::DebControl;
949     $controlParser->DEBUG();
950     my $keys = $controlParser->parse_file("${base}/DEBIAN/control");
951 #    print Dumper($keys);
952     # DebControl supports multiple paragraphs of control data but
953     # debian/control in a .deb only has one (whereas a debian/control
954     # in a build root contains many)
955     # So extract the ref to the first one.
956     my %control = %{@{$keys}[0]};
957
958     # Validate this is a binary deb and get the control data
959     my $d_name = $control{'Package'};
960     my $d_version = $control{'Version'};
961
962     $arch = $control{'Architecture'};  # set global $arch
963
964     # examine the
965     #   arch <arch> targets <target_arch>[:<target_type>] [<target_arch>[:<target_type>]...]
966     # line and get a list of target_arch-es
967     my @targets = get_targets($arch, $config);
968     if (!@targets) {
969       print "no targets for arch $arch, nothing to do\n";
970       return; # there may be more debs to handle
971     }
972
973     for my $target (@targets) {
974       next unless parse_config($target, $d_name, $d_version);
975       die("targetname not set\n") unless $targetname;  # set in the global_conf
976       $target_matched{$target} = 1;
977
978       my $baseTarget = "${base}/$target";
979       # Unpack a .deb to work on. We have to do this each time as we
980       # manipulate the unpacked files.
981       system "mkdir ${base}/$target";
982       system "dpkg -e $deb  ${baseTarget}/DEBIAN" || die "dpkg -e failed on $deb";
983       # Note that extracting to $prefix does the clever move to /lib-x86/ or whatever
984       system "dpkg -x $deb  ${baseTarget}/$prefix" || die "dpkg -x failed on $deb";
985
986       # Reset the control data
987       $keys = $controlParser->parse_file("${baseTarget}/DEBIAN/control");
988       %control = %{@{$keys}[0]};
989
990       # Force the architecture
991       $control{'Architecture'} = $targetarch;
992
993       # Currently this script does not manipulate any files
994       # If needed they are all unpacked in ${baseTarget}
995
996       # we don't need a dsc/spec file.. all done by just moving files around
997       # and running dpkg -b ${base} $NEW_DEB
998       #
999       # my $dscfile = "/usr/src/packages/DSCS/mkbaselibs$$.dsc";
1000
1001       print "$d_name($target): writing dscfile...\n";
1002       # We can Use Parse::DebControl write_file to create the new control file
1003       # just modify tags in there
1004
1005       # We'll use requires -> Depends:
1006       map s/^"(.*)"$/$1/, @requires;  # remove leading/trailing "s
1007       $control{"Depends"} = @requires ? join(", ", @requires) : "";  # join array if exists or reset it to ""
1008
1009       map s/^"(.*)"$/$1/, @prerequires;
1010       $control{"Pre-Depends"} = @prerequires ? join(", ", @prerequires) : "";
1011
1012       map s/^"(.*)"$/$1/, @provides;
1013       $control{"Provides"} = @provides ? join(", ", @provides) : "";
1014
1015       map s/^"(.*)"$/$1/, @recommends;
1016       $control{"Recommends"} = @recommends ? join(", ", @recommends) : "";
1017
1018       map s/^"(.*)"$/$1/, @suggests;
1019       $control{"Suggests"} = @suggests ? join(", ", @suggests) : "";
1020
1021       map s/^"(.*)"$/$1/, @obsoletes;
1022       $control{"Replaces"} = @obsoletes ? join(", ", @obsoletes) : "";
1023
1024       map s/^"(.*)"$/$1/, @conflicts;
1025       $control{"Conflicts"} = @conflicts ? join(", ", @conflicts) : "";
1026
1027       map s/^"(.*)"$/$1/, @supplements;
1028       $control{"Enhances"} = @supplements ? join(", ", @supplements) : "";
1029
1030
1031       # Tidy up the various control files.
1032       # the md5sums are regenerated by dpkg-deb when building
1033       foreach my $c_file ( qw(conffiles postins postrm preinst prerm) ) {
1034         unlink "${baseTarget}/DEBIAN/$c_file";
1035       }
1036       # Create them if needed
1037       if (@prein) {
1038         map s/^"(.*)"$/$1/, @prein;  # remove leading/trailing "s
1039         open(my $SCRIPT, ">${baseTarget}/DEBIAN/preinst");
1040         print $SCRIPT join("\n", @prein) ;
1041         chmod(0755, $SCRIPT);
1042         close($SCRIPT);
1043       }
1044       if (@postin) {
1045         map s/^"(.*)"$/$1/, @postin;
1046         open(my $SCRIPT, ">${baseTarget}/DEBIAN/postinst");
1047         print $SCRIPT join("\n", @postin) ;
1048         chmod(0755, $SCRIPT);
1049         close($SCRIPT);
1050       }
1051       if (@preun) {
1052         map s/^"(.*)"$/$1/, @preun;
1053         open(my $SCRIPT, ">${baseTarget}/DEBIAN/prerm");
1054         print $SCRIPT join("\n", @preun) ;
1055         chmod(0755, $SCRIPT);
1056         close($SCRIPT);
1057       }
1058       if (@postun) {
1059         map s/^"(.*)"$/$1/, @postun;
1060         open(my $SCRIPT, ">${baseTarget}/DEBIAN/postrm");
1061         print $SCRIPT join("\n", @postun) ;
1062         chmod(0755, $SCRIPT);
1063         close($SCRIPT);
1064       }
1065
1066       # Don't forget to rename the package - or it will replace/uninstall the /-based one
1067       $control{"Package"} = "${d_name}-${targettype}";
1068
1069       $controlParser->write_file("${baseTarget}/DEBIAN/control", \%control, {clobberFile => 1, addNewline=>1 } );
1070       system "dpkg -b ${baseTarget} /usr/src/packages/DEBS/${d_name}-${targettype}_${d_version}_${targetarch}.deb" || die "dpkg -b failed on $deb";
1071       system "rm -rf ${baseTarget}";
1072     }
1073     system "rm -rf ${base}";
1074   }
1075 }
1076
1077 # args is a list of full pathnames to rpm/deb files
1078 die("Usage: mkbaselibs <rpms>\n") unless @ARGV;
1079
1080 if ($ARGV[0] eq '-v') {
1081   $verbose = 1;
1082   shift @ARGV;
1083 }
1084 while ($ARGV[0] eq '-c') {
1085   shift @ARGV;
1086   read_config($ARGV[0]);
1087   shift @ARGV;
1088 }
1089
1090 my %goodpkgs = map {$_ => 1} get_pkgnames();  # These are packages named in the config file
1091 my @pkgs = @ARGV;
1092 my @rpms;
1093 my @debugrpms;
1094 for my $rpm (@pkgs) {
1095   my $rpmn = $rpm;
1096   unless (-f $rpm) {
1097     warn ("$rpm does not exist, skipping\n");
1098     next;
1099   }
1100   next if $rpm =~ /\.(no)?src\.rpm$/;  # ignore source rpms
1101   next if $rpm =~ /\.spm$/;
1102   $rpmn =~ s/.*\///;   # Remove leading path info
1103   $rpmn =~ s/-[^-]+-[^-]+\.[^\.]+\.rpm$/\.rpm/; # remove all version info
1104   $rpmn =~ s/\.rpm$//; # remove extension
1105   push @rpms, $rpm if $goodpkgs{$rpmn};
1106   if ($rpmn =~ s/-debuginfo$//) {
1107       push @debugrpms, $rpm if $goodpkgs{$rpmn};
1108   }
1109 }
1110 for (@rpms) {
1111     die("$_: need absolute path to package\n") unless /^\//;
1112 }
1113
1114 my %debs_to_process = map {$_ => 1} get_debpkgnames();  # These are packages named in the config file
1115 my @debs;
1116 for my $deb (@pkgs) {
1117   my $debn = $deb;
1118   next unless $debn =~ /\.deb$/;
1119   $debn =~ s/.*\///;   # Remove leading path info
1120   $debn =~ s/_[^_]+_[^_]+\.deb$//; # remove all version info and extension
1121   push @debs, $deb if $debs_to_process{$debn};
1122   print "ignoring $deb as $debn not in baselibs.conf\n" if !$debs_to_process{$debn};
1123 }
1124 for (@debs) {
1125     die("$_: need absolute path to package\n") unless /^\//;
1126 }
1127
1128 exit 0 unless @rpms or @debs;
1129
1130 if (@rpms) {
1131     @filesystem = split("\n", `rpm -ql filesystem 2>/dev/null`);
1132     die("filesystem rpm is not installed\n") unless @filesystem;
1133
1134     handle_rpms(@rpms);
1135     handle_rpms(@debugrpms);
1136 }
1137
1138 if (@debs) {
1139     handle_debs(@debs);
1140 }