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