#!/usr/bin/perl -w ################################################################ # # Copyright (c) 1995-2014 SUSE Linux Products GmbH # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or 3 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program (see the file COPYING); if not, write to the # Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA # ################################################################ use POSIX; use strict; use File::Temp qw/tempfile tempdir/; # See: http://www.rpm.org/max-rpm/s1-rpm-file-format-rpm-file-format.html#S3-RPM-FILE-FORMAT-HEADER-TAG-LISTING # cf http://search.cpan.org/~davecross/Parse-RPM-Spec-0.01/lib/Parse/RPM/Spec.pm my %STAG = ( "NAME" => 1000, "VERSION" => 1001, "RELEASE" => 1002, "EPOCH" => 1003, "SERIAL" => 1003, "SUMMARY" => 1004, "DESCRIPTION" => 1005, "BUILDTIME" => 1006, "BUILDHOST" => 1007, "INSTALLTIME" => 1008, "SIZE" => 1009, "DISTRIBUTION" => 1010, "VENDOR" => 1011, "GIF" => 1012, "XPM" => 1013, "LICENSE" => 1014, "COPYRIGHT" => 1014, "PACKAGER" => 1015, "GROUP" => 1016, "SOURCE" => 1018, "PATCH" => 1019, "URL" => 1020, "OS" => 1021, "ARCH" => 1022, "PREIN" => 1023, "POSTIN" => 1024, "PREUN" => 1025, "POSTUN" => 1026, "OLDFILENAMES" => 1027, "FILESIZES" => 1028, "FILESTATES" => 1029, "FILEMODES" => 1030, "FILERDEVS" => 1033, "FILEMTIMES" => 1034, "FILEMD5S" => 1035, "FILELINKTOS" => 1036, "FILEFLAGS" => 1037, "FILEUSERNAME" => 1039, "FILEGROUPNAME" => 1040, "ICON" => 1043, "SOURCERPM" => 1044, "FILEVERIFYFLAGS" => 1045, "ARCHIVESIZE" => 1046, "PROVIDENAME" => 1047, "PROVIDES" => 1047, "REQUIREFLAGS" => 1048, "REQUIRENAME" => 1049, "REQUIREVERSION" => 1050, "NOSOURCE" => 1051, "NOPATCH" => 1052, "CONFLICTFLAGS" => 1053, "CONFLICTNAME" => 1054, "CONFLICTVERSION" => 1055, "EXCLUDEARCH" => 1059, "EXCLUDEOS" => 1060, "EXCLUSIVEARCH" => 1061, "EXCLUSIVEOS" => 1062, "RPMVERSION" => 1064, "TRIGGERSCRIPTS" => 1065, "TRIGGERNAME" => 1066, "TRIGGERVERSION" => 1067, "TRIGGERFLAGS" => 1068, "TRIGGERINDEX" => 1069, "VERIFYSCRIPT" => 1079, "CHANGELOGTIME" => 1080, "CHANGELOGNAME" => 1081, "CHANGELOGTEXT" => 1082, "PREINPROG" => 1085, "POSTINPROG" => 1086, "PREUNPROG" => 1087, "POSTUNPROG" => 1088, "BUILDARCHS" => 1089, "OBSOLETENAME" => 1090, "OBSOLETES" => 1090, "VERIFYSCRIPTPROG" => 1091, "TRIGGERSCRIPTPROG" => 1092, "COOKIE" => 1094, "FILEDEVICES" => 1095, "FILEINODES" => 1096, "FILELANGS" => 1097, "PREFIXES" => 1098, "INSTPREFIXES" => 1099, "SOURCEPACKAGE" => 1106, "PROVIDEFLAGS" => 1112, "PROVIDEVERSION" => 1113, "OBSOLETEFLAGS" => 1114, "OBSOLETEVERSION" => 1115, "DIRINDEXES" => 1116, "BASENAMES" => 1117, "DIRNAMES" => 1118, "OPTFLAGS" => 1122, "DISTURL" => 1123, "PAYLOADFORMAT" => 1124, "PAYLOADCOMPRESSOR" => 1125, "PAYLOADFLAGS" => 1126, "INSTALLCOLOR" => 1127, "INSTALLTID" => 1128, "REMOVETID" => 1129, "RHNPLATFORM" => 1131, "PLATFORM" => 1132, "PATCHESNAME" => 1133, "PATCHESFLAGS" => 1134, "PATCHESVERSION" => 1135, "CACHECTIME" => 1136, "CACHEPKGPATH" => 1137, "CACHEPKGSIZE" => 1138, "CACHEPKGMTIME" => 1139, "FILECOLORS" => 1140, "FILECLASS" => 1141, "CLASSDICT" => 1142, "FILEDEPENDSX" => 1143, "FILEDEPENDSN" => 1144, "DEPENDSDICT" => 1145, "SOURCEPKGID" => 1146, "PRETRANS" => 1151, "POSTTRANS" => 1152, "PRETRANSPROG" => 1153, "POSTTRANSPROG" => 1154, "DISTTAG" => 1155, "SUGGESTSNAME" => 1156, "SUGGESTSVERSION" => 1157, "SUGGESTSFLAGS" => 1158, "ENHANCESNAME" => 1159, "ENHANCESVERSION" => 1160, "ENHANCESFLAGS" => 1161, "PRIORITY" => 1162, "CVSID" => 1163, ); # do not mix numeric tags with symbolic tags. # special symbolic tag 'FILENAME' exists. # This function seems to take a set of tags and populates a global # hash-table (%res) with data obtained by doing a binary unpack() on # the raw package # http://www.rpm.org/max-rpm/s1-rpm-file-format-rpm-file-format.html sub rpmq_many { my $rpm = shift; my @stags = @_; my $need_filenames = grep { $_ eq 'FILENAMES' } @stags; push @stags, 'BASENAMES', 'DIRNAMES', 'DIRINDEXES', 'OLDFILENAMES' if $need_filenames; @stags = grep { $_ ne 'FILENAMES' } @stags if $need_filenames; my %stags = map {0+($STAG{$_} or $_) => $_} @stags; my ($magic, $sigtype, $headmagic, $cnt, $cntdata, $lead, $head, $index, $data, $tag, $type, $offset, $count); local *RPM; if (ref($rpm) eq 'ARRAY') { ($headmagic, $cnt, $cntdata) = unpack('N@8NN', $rpm->[0]); if ($headmagic != 0x8eade801) { warn("Bad rpm\n"); return (); } if (length($rpm->[0]) < 16 + $cnt * 16 + $cntdata) { warn("Bad rpm\n"); return (); } $index = substr($rpm->[0], 16, $cnt * 16); $data = substr($rpm->[0], 16 + $cnt * 16, $cntdata); } else { return () unless open(RPM, "<$rpm"); if (read(RPM, $lead, 96) != 96) { warn("Bad rpm $rpm\n"); close RPM; return (); } ($magic, $sigtype) = unpack('N@78n', $lead); if ($magic != 0xedabeedb || $sigtype != 5) { warn("Bad rpm $rpm\n"); close RPM; return (); } if (read(RPM, $head, 16) != 16) { warn("Bad rpm $rpm\n"); close RPM; return (); } ($headmagic, $cnt, $cntdata) = unpack('N@8NN', $head); if ($headmagic != 0x8eade801) { warn("Bad rpm $rpm\n"); close RPM; return (); } if (read(RPM, $index, $cnt * 16) != $cnt * 16) { warn("Bad rpm $rpm\n"); close RPM; return (); } $cntdata = ($cntdata + 7) & ~7; if (read(RPM, $data, $cntdata) != $cntdata) { warn("Bad rpm $rpm\n"); close RPM; return (); } } my %res = (); if (ref($rpm) eq 'ARRAY' && @stags && @$rpm > 1) { my %res2 = &rpmq_many([ $rpm->[1] ], @stags); %res = (%res, %res2); return %res; } if (ref($rpm) ne 'ARRAY' && @stags) { if (read(RPM, $head, 16) != 16) { warn("Bad rpm $rpm\n"); close RPM; return (); } ($headmagic, $cnt, $cntdata) = unpack('N@8NN', $head); if ($headmagic != 0x8eade801) { warn("Bad rpm $rpm\n"); close RPM; return (); } if (read(RPM, $index, $cnt * 16) != $cnt * 16) { warn("Bad rpm $rpm\n"); close RPM; return (); } if (read(RPM, $data, $cntdata) != $cntdata) { warn("Bad rpm $rpm\n"); close RPM; return (); } } close RPM if ref($rpm) ne 'ARRAY'; return %res unless @stags; # nothing to do while($cnt-- > 0) { ($tag, $type, $offset, $count, $index) = unpack('N4a*', $index); $tag = 0+$tag; if ($stags{$tag}) { eval { my $otag = $stags{$tag}; if ($type == 0) { $res{$otag} = [ '' ]; } elsif ($type == 1) { $res{$otag} = [ unpack("\@${offset}c$count", $data) ]; } elsif ($type == 2) { $res{$otag} = [ unpack("\@${offset}c$count", $data) ]; } elsif ($type == 3) { $res{$otag} = [ unpack("\@${offset}n$count", $data) ]; } elsif ($type == 4) { $res{$otag} = [ unpack("\@${offset}N$count", $data) ]; } elsif ($type == 5) { $res{$otag} = [ undef ]; } elsif ($type == 6) { $res{$otag} = [ unpack("\@${offset}Z*", $data) ]; } elsif ($type == 7) { $res{$otag} = [ unpack("\@${offset}a$count", $data) ]; } elsif ($type == 8 || $type == 9) { my $d = unpack("\@${offset}a*", $data); my @res = split("\0", $d, $count + 1); $res{$otag} = [ splice @res, 0, $count ]; } else { $res{$otag} = [ undef ]; } }; if ($@) { warn("Bad rpm $rpm: $@\n"); return (); } } } if ($need_filenames) { if ($res{'OLDFILENAMES'}) { $res{'FILENAMES'} = [ @{$res{'OLDFILENAMES'}} ]; } else { my $i = 0; $res{'FILENAMES'} = [ map {"$res{'DIRNAMES'}->[$res{'DIRINDEXES'}->[$i++]]$_"} @{$res{'BASENAMES'}} ]; } } return %res; } sub rpmq_add_flagsvers { my $res = shift; my $name = shift; my $flags = shift; my $vers = shift; return unless $res; my @flags = @{$res->{$flags} || []}; my @vers = @{$res->{$vers} || []}; for (@{$res->{$name}}) { if (@flags && ($flags[0] & 0xe) && @vers) { $_ .= ' '; $_ .= '<' if $flags[0] & 2; $_ .= '>' if $flags[0] & 4; $_ .= '=' if $flags[0] & 8; $_ .= " $vers[0]"; } shift @flags; shift @vers; } } my @preamble = qw{ Name Version Release Epoch Summary Copyright License Distribution Disturl Vendor Group Packager Url Icon Prefixes }; my $rpm; my $arch; my $config = ''; my $targettype; my $targetarch; my $prefix; my $extension; my $configdir; my $targetname; my $legacyversion; my @baselib; my @config; my @provides; my @obsoletes; my @requires; my @prerequires; my @conflicts; my @recommends; my @supplements; my @suggests; my @prein; my @postin; my @preun; my @postun; my $autoreqprov; my $verbose; my %target_matched; my @filesystem; # Used for each package by sub parse_config { my $target = shift; my $pkgname = shift; my $pkgver = shift; my $pkghasmatched; my $pkgmatches = 1; $prefix = ''; $legacyversion = ''; $extension = ''; $configdir = ''; $targetname = ''; ($targetarch, $targettype) = split(':', $target, 2); @baselib = (); @config = (); @provides = (); @obsoletes = (); @requires = (); @recommends = (); @supplements = (); @suggests = (); @prerequires = (); @conflicts = (); @prein = (); @postin = (); @preun = (); @postun = (); $autoreqprov = 'on'; my $match1 = ''; for (split("\n", $config)) { s/^\s+//; s/\s+$//; next if $_ eq '' || $_ =~ /^#/; s/\/$targettype/g; s/\/$targetarch/g; s/\/$pkgname/g; s/\/$pkgver/g; s/\/$prefix/g; s/\/$extension/g; s/\/$configdir/g; s/\/$match1/g; if (/^arch\s+/) { next unless s/^arch\s+\Q$arch\E\s+//; } next if /^targets\s+/; if (/\s+package\s+[-+_a-zA-Z0-9]+$/) { $pkgmatches = 0; # XXX: hack } if (/\s+package\s+\/[-+_a-zA-Z0-9]+\/$/) { $pkgmatches = 0; # XXX: hack } if (/^targettype\s+/) { next unless s/^targettype\s+\Q$targettype\E\s+//; } if (/^targetarch\s+/) { next unless s/^targetarch\s+\Q$targetarch\E\s+//; } if (/^prefix\s+(.*?)$/) { $prefix = $1; next; } if (/^legacyversion\s+(.*?)$/) { $legacyversion = $1; next; } if (/^extension\s+(.*?)$/) { $extension = $1; next; } if (/^configdir\s+(.*?)$/) { $configdir= $1; next; } if (/^targetname\s+(.*?)$/) { $targetname = $1; next; } $_ = "baselib $_" if /^[\+\-\"]/; $_ = "package $_" if /^[-+_a-zA-Z0-9]+$/; if (/^package\s+\/(.*?)\/$/) { my $pm = $1; $pkgmatches = $pkgname =~ /$pm/; $match1 = $1 if defined $1; $pkghasmatched |= $pkgmatches if $pkgname =~ /-debuginfo$/ && $target_matched{$target}; next; } if (/^package\s+(.*?)$/) { $pkgmatches = $1 eq $pkgname; $pkghasmatched |= $pkgmatches; next; } next unless $pkgmatches; return 0 if $_ eq 'block!'; if (/^provides\s+(.*?)$/) { push @provides, $1; next; } if (/^requires\s+(.*?)$/) { push @requires, $1; next; } if (/^recommends\s+(.*?)$/) { push @recommends, $1; next; } if (/^supplements\s+(.*?)$/) { push @supplements, $1; next; } if (/^suggests\s+(.*?)$/) { push @suggests, $1; next; } if (/^prereq\s+(.*?)$/) { push @prerequires, $1; next; } if (/^obsoletes\s+(.*?)$/) { push @obsoletes, $1; next; } if (/^conflicts\s+(.*?)$/) { push @conflicts, $1; next; } if (/^baselib\s+(.*?)$/) { push @baselib, $1; next; } if (/^config\s+(.*?)$/) { push @config, $1; next; } if (/^pre(in)?\s+(.*?)$/) { push @prein, $2; next; } if (/^post(in)?\s+(.*?)$/) { push @postin, $2; next; } if (/^preun\s+(.*?)$/) { push @preun, $1; next; } if (/^postun\s+(.*?)$/) { push @preun, $1; next; } if (/^autoreqprov\s+(.*?)$/) {$autoreqprov = $1; next; } die("bad line: $_\n"); } return $pkghasmatched; } sub read_config { my $cfname = shift; local *F; open(F, "<$cfname") || die("$cfname: $!\n"); my @cf = ; close F; $config .= join('', @cf); $config .= "\npackage __does_not_match__\n"; } sub get_targets { my $architecture = shift; my $conf = shift; my %targets; for (split("\n", $conf)) { if (/^arch\s+/) { next unless s/^arch\s+\Q$architecture\E\s+//; } if (/^targets\s+(.*?)$/) { $targets{$_} = 1 for split(' ', $1); } } my @targets = sort keys %targets; return @targets; } # Packages listed in config file sub get_pkgnames { my %rpms; for (split("\n", $config)) { if (/^(.*\s+)?package\s+([-+_a-zA-Z0-9]+)\s*$/) { # eg : arch ppc package libnuma-devel $rpms{$2} = 1; } elsif (/^\s*([-+_a-zA-Z0-9]+)\s*$/) { # eg: readline-devel $rpms{$1} = 1; } } return sort keys %rpms; } # Packages listed in config file - debian variant (can have "." in package names) sub get_debpkgnames { my %debs; for (split("\n", $config)) { if (/^(.*\s+)?package\s+([-+_a-zA-Z0-9.]+)\s*$/) { # eg : arch ppc package libnuma-devel $debs{$2} = 1; } elsif (/^\s*([-+_a-zA-Z0-9.]+)\s*$/) { # eg: readline-devel $debs{$1} = 1; } } return sort keys %debs; } sub handle_rpms { for $rpm (@_) { my @stags = map {uc($_)} @preamble; push @stags, 'DESCRIPTION'; push @stags, 'FILENAMES', 'FILEMODES', 'FILEUSERNAME', 'FILEGROUPNAME', 'FILEFLAGS', 'FILEVERIFYFLAGS'; push @stags, 'CHANGELOGTIME', 'CHANGELOGNAME', 'CHANGELOGTEXT'; push @stags, 'ARCH', 'SOURCERPM', 'RPMVERSION'; push @stags, 'BUILDTIME'; my %res = rpmq_many($rpm, @stags); die("$rpm: bad rpm\n") unless $res{'NAME'}; my $rname = $res{'NAME'}->[0]; my $sname = $res{'SOURCERPM'}->[0]; die("$rpm is a sourcerpm\n") unless $sname; die("bad sourcerpm: $sname\n") unless $sname =~ /^(.*)-([^-]+)-([^-]+)\.(no)?src\.rpm$/; $sname = $1; my $sversion = $2; my $srelease = $3; $arch = $res{'ARCH'}->[0]; my @targets = get_targets($arch, $config); if (!@targets) { print "no targets for arch $arch, skipping $rname\n"; next; } for my $target (@targets) { next unless parse_config($target, $res{'NAME'}->[0], $res{'VERSION'}->[0]); die("targetname not set\n") unless $targetname; $target_matched{$target} = 1; my %ghosts; my @rpmfiles = @{$res{'FILENAMES'}}; my @ff = @{$res{'FILEFLAGS'}}; for (@rpmfiles) { $ghosts{$_} = 1 if $ff[0] & (1 << 6); shift @ff; } my %files; my %cfiles; my %moves; my %symlinks; for my $r (@baselib) { my $rr = substr($r, 1); if (substr($r, 0, 1) eq '+') { if ($rr =~ /^(.*?)\s*->\s*(.*?)$/) { if (grep {$_ eq $1} @rpmfiles) { $files{$1} = 1; $moves{$1} = $2; } } else { for (grep {/$rr/} @rpmfiles) { $files{$_} = 1; delete $moves{$_}; } } } elsif (substr($r, 0, 1) eq '-') { delete $files{$_} for grep {/$rr/} keys %files; } elsif (substr($r, 0, 1) eq '"') { $rr =~ s/\"$//; if ($rr =~ /^(.*?)\s*->\s*(.*?)$/) { $symlinks{$1} = $2; } else { die("bad baselib string rule: $r\n"); } } else { die("bad baselib rule: $r\n"); } } if ($configdir) { for my $r (@config) { my $rr = substr($r, 1); if (substr($r, 0, 1) eq '+') { $cfiles{$_} = 1 for grep {/$rr/} grep {!$ghosts{$_}} @rpmfiles; } elsif (substr($r, 0, 1) eq '-') { delete $cfiles{$_} for grep {/$rr/} keys %cfiles; } else { die("bad config rule: $r\n"); } } } $files{$_} = 1 for keys %cfiles; if (!%files) { print "$rname($target): empty filelist, skipping rpm\n"; next; } my $i = 0; for (@{$res{'FILENAMES'}}) { $files{$_} = $i if $files{$_}; $i++; } my %cpiodirs; for (keys %files) { next if $cfiles{$_} || $moves{$_}; my $fn = $_; next unless $fn =~ s/\/[^\/]+$//; $cpiodirs{$fn} = 1; } my %alldirs; for (keys %files) { next if $cfiles{$_}; my $fn = $_; if ($moves{$fn}) { $fn = $moves{$fn}; next unless $fn =~ s/\/[^\/]+$//; $alldirs{$fn} = 1; } else { next unless $fn =~ s/\/[^\/]+$//; $alldirs{"$prefix$fn"} = 1; } } $alldirs{$_} = 1 for keys %symlinks; $alldirs{$configdir} = 1 if %cfiles; my $ad; for $ad (keys %alldirs) { $alldirs{$ad} = 1 while $ad =~ s/\/[^\/]+$//; } for (keys %files) { next if $cfiles{$_}; my $fn = $_; if ($moves{$fn}) { delete $alldirs{$moves{$fn}}; } else { delete $alldirs{"$prefix$fn"}; } } delete $alldirs{$_} for keys %symlinks; $ad = $prefix; delete $alldirs{$ad}; delete $alldirs{$ad} while $ad =~ s/\/[^\/]+$//; delete $alldirs{$_} for @filesystem; print "$rname($target): writing specfile...\n"; my ($fh, $specfile) = tempfile(SUFFIX => ".spec"); open(SPEC, ">&=", $fh) || die("open: $!\n"); for my $p (@preamble) { my $pt = uc($p); next unless $res{$pt}; my $d = $res{$pt}->[0]; $d =~ s/%/%%/g; if ($p eq 'Name') { print SPEC "Name: $sname\n"; next; } if ($p eq 'Version') { print SPEC "Version: $sversion\n"; next; } if ($p eq 'Release') { print SPEC "Release: $srelease\n"; next; } if ($p eq 'Disturl') { print SPEC "%define disturl $d\n"; next; } print SPEC "$p: $d\n"; } print SPEC "Source: $rpm\n"; print SPEC "NoSource: 0\n" if $res{'SOURCERPM'}->[0] =~ /\.nosrc\.rpm$/; print SPEC "BuildRoot: %{_tmppath}/baselibs-%{name}-%{version}-build\n"; print SPEC "%define _target_cpu $targetarch\n"; print SPEC "%define __os_install_post %{nil}\n"; print SPEC "%description\nUnneeded main package. Ignore.\n\n"; print SPEC "%package -n $targetname\n"; for my $p (@preamble) { next if $p eq 'Name' || $p eq 'Disturl'; my $pt = uc($p); next unless $res{$pt}; my $d = $res{$pt}->[0]; $d =~ s/%/%%/g; if ($pt eq 'VERSION' && $legacyversion) { $d = $legacyversion; } elsif ($pt eq 'RELEASE' && $legacyversion) { my @bt = localtime($res{'BUILDTIME'}->[0]); $bt[5] += 1900; $bt[4] += 1; $d = sprintf("%04d%02d%02d%02d%02d\n", @bt[5,4,3,2,1]); } print SPEC "$p: $d\n"; } print SPEC "Autoreqprov: $autoreqprov\n"; for my $ar ([\@provides, 'provides'], [\@prerequires, 'prereq'], [\@requires, 'requires'], [\@recommends, 'recommends'], [\@supplements, 'supplements'], [\@obsoletes, 'obsoletes'], [\@conflicts, 'conflicts']) { my @a = @{$ar->[0]}; my @na = (); for (@a) { if (substr($_, 0, 1) eq '"') { die("bad $ar->[1] rule: $_\n") unless /^\"(.*)\"$/; push @na, $1; } elsif (substr($_, 0, 1) eq '-') { my $ra = substr($_, 1); @na = grep {!/$ra/} @na; } else { die("bad $ar->[1] rule: $_\n"); } } print SPEC ucfirst($ar->[1]).": $_\n" for @na; } my $cpiopre = ''; $cpiopre = './' if $res{'RPMVERSION'}->[0] !~ /^3/; my $d = $res{'DESCRIPTION'}->[0]; $d =~ s/%/%%/g; if ($legacyversion) { $d = "This rpm was re-packaged from $res{'NAME'}->[0]-$res{'VERSION'}->[0]-$res{'RELEASE'}->[0]\n\n$d"; } print SPEC "\n%description -n $targetname\n"; print SPEC "$d\n"; print SPEC "%prep\n"; print SPEC "%build\n"; print SPEC "%install\n"; print SPEC "rm -rf \$RPM_BUILD_ROOT\n"; print SPEC "mkdir \$RPM_BUILD_ROOT\n"; print SPEC "cd \$RPM_BUILD_ROOT\n"; my @cfl = grep {!$cfiles{$_} && !$moves{$_}} sort keys %files; if (@cfl) { if ($prefix ne '') { print SPEC "mkdir -p \$RPM_BUILD_ROOT$prefix\n"; print SPEC "pushd \$RPM_BUILD_ROOT$prefix\n"; } print SPEC "cat <.filelist\n"; print SPEC "$_\n" for map {$cpiopre.substr($_, 1)} @cfl; print SPEC "EOFL\n"; print SPEC "mkdir -p \$RPM_BUILD_ROOT$prefix$_\n" for sort keys %cpiodirs; print SPEC "rpm2cpio $rpm | cpio -i -d -v -E .filelist\n"; print SPEC "rm .filelist\n"; if (%ghosts) { for my $fn (grep {$ghosts{$_}} @cfl) { my $fnm = $fn; $fnm = '.' unless $fnm =~ s/\/[^\/]+$//; print SPEC "mkdir -p \$RPM_BUILD_ROOT$prefix$fnm\n"; print SPEC "touch \$RPM_BUILD_ROOT$prefix$fn\n"; } } if ($prefix ne '') { print SPEC "popd\n"; } } if (%cfiles || %moves) { print SPEC "mkdir -p .cfiles\n"; print SPEC "pushd .cfiles\n"; print SPEC "cat <.filelist\n"; print SPEC "$_\n" for map {$cpiopre.substr($_, 1)} grep {$cfiles{$_} || $moves{$_}} sort keys %files; print SPEC "EOFL\n"; print SPEC "rpm2cpio $rpm | cpio -i -d -v -E .filelist\n"; print SPEC "popd\n"; if (%cfiles) { print SPEC "mkdir -p \$RPM_BUILD_ROOT$configdir\n"; print SPEC "mv .cfiles$_ \$RPM_BUILD_ROOT$configdir\n" for sort keys %cfiles; } for my $fn (sort keys %moves) { my $fnm = $moves{$fn}; $fnm = '.' unless $fnm =~ s/\/[^\/]+$//; print SPEC "mkdir -p \$RPM_BUILD_ROOT$fnm\n"; print SPEC "mv .cfiles$fn \$RPM_BUILD_ROOT$moves{$fn}\n"; } print SPEC "rm -rf .cfiles\n"; } for my $fn (sort keys %symlinks) { my $fnm = $fn; $fnm = '.' unless $fnm =~ s/\/[^\/]+$//; print SPEC "mkdir -p \$RPM_BUILD_ROOT$fnm\n"; print SPEC "ln -s $symlinks{$fn} \$RPM_BUILD_ROOT$fn\n"; } if ($prefix ne '' && grep {/\.so.*$/} @cfl) { @postin = () if @postin == 1 && $postin[0] =~ /^\"-p.*ldconfig/; unshift @postin, "\"/sbin/ldconfig -r $prefix\""; } if (@prein) { print SPEC "%pre -n $targetname"; print SPEC $prein[0] =~ /^\"-p/ ? " " : "\n"; for (@prein) { die("bad prein rule: $_\n") unless /^\"(.*)\"$/; print SPEC "$1\n"; } } if (@postin) { print SPEC "%post -n $targetname"; print SPEC $postin[0] =~ /^\"-p/ ? " " : "\n"; for (@postin) { die("bad postin rule: $_\n") unless /^\"(.*)\"$/; print SPEC "$1\n"; } } if (@preun) { print SPEC "%preun -n $targetname"; print SPEC $preun[0] =~ /^\"-p/ ? " " : "\n"; for (@preun) { die("bad preun rule: $_\n") unless /^\"(.*)\"$/; print SPEC "$1\n"; } } if (@postun) { print SPEC "%postun -n $targetname"; print SPEC $postun[0] =~ /^\"-p/ ? " " : "\n"; for (@postun) { die("bad postun rule: $_\n") unless /^\"(.*)\"$/; print SPEC "$1\n"; } } print SPEC "\n%clean\n"; print SPEC "\nrm -rf \$RPM_BUILD_ROOT\n\n"; print SPEC "%files -n $targetname\n"; for my $file (sort keys %alldirs) { print SPEC "%dir %attr(0755,root,root) $file\n"; } for my $file (keys %files) { my $fi = $files{$file}; my $fm = $res{'FILEMODES'}->[$fi]; my $fv = $res{'FILEVERIFYFLAGS'}->[$fi]; my $ff = $res{'FILEFLAGS'}->[$fi]; if (POSIX::S_ISDIR($fm)) { print SPEC "%dir "; } if ($ff & ((1 << 3) | (1 << 4))) { print SPEC "%config(missingok noreplace) "; } elsif ($ff & (1 << 3)) { print SPEC "%config(missingok) "; } elsif ($ff & (1 << 4)) { print SPEC "%config(noreplace) "; } elsif ($ff & (1 << 0)) { print SPEC "%config "; } print SPEC "%doc " if $ff & (1 << 1); print SPEC "%ghost " if $ff & (1 << 6); print SPEC "%license " if $ff & (1 << 7); print SPEC "%readme " if $ff & (1 << 8); if ($fv != 4294967295) { print SPEC "%verify("; if ($fv & 2147483648) { print SPEC "not "; $fv ^= 4294967295; } print SPEC "md5 " if $fv & (1 << 0); print SPEC "size " if $fv & (1 << 1); print SPEC "link " if $fv & (1 << 2); print SPEC "user " if $fv & (1 << 3); print SPEC "group " if $fv & (1 << 4); print SPEC "mtime " if $fv & (1 << 5); print SPEC "mode " if $fv & (1 << 6); print SPEC "rdev " if $fv & (1 << 7); print SPEC ") "; } #sigh, no POSIX::S_ISLNK ... if (($fm & 0170000) == 0120000) { printf SPEC "%%attr(-,%s,%s) ", $res{'FILEUSERNAME'}->[$fi], $res{'FILEGROUPNAME'}->[$fi]; } else { printf SPEC "%%attr(%03o,%s,%s) ", $fm & 07777, $res{'FILEUSERNAME'}->[$fi], $res{'FILEGROUPNAME'}->[$fi]; } if ($cfiles{$file}) { my $fn = $file; $fn =~ s/.*\///; print SPEC "$configdir/$fn\n"; } else { if ($moves{$file}) { print SPEC "$moves{$file}\n"; } else { print SPEC "$prefix$file\n"; } } } for (keys %symlinks) { printf SPEC "%%attr(-,root,root) $_\n"; } if ($res{'CHANGELOGTEXT'}) { print SPEC "\n%changelog -n $targetname\n"; my @ct = @{$res{'CHANGELOGTIME'}}; my @cn = @{$res{'CHANGELOGNAME'}}; my @wdays = qw{Sun Mon Tue Wed Thu Fri Sat}; my @months = qw{Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec}; for my $cc (@{$res{'CHANGELOGTEXT'}}) { my @lt = localtime($ct[0]); my $cc2 = $cc; my $cn2 = $cn[0]; $cc2 =~ s/%/%%/g; $cn2 =~ s/%/%%/g; printf SPEC "* %s %s %02d %04d %s\n%s\n", $wdays[$lt[6]], $months[$lt[4]], $lt[3], 1900 + $lt[5], $cn2, $cc2; shift @ct; shift @cn; } } close(SPEC) || die("$specfile: $!\n"); print "$rname($target): running build...\n"; if (system("rpmbuild -bb $specfile".($verbose ? '' : '>/dev/null 2>&1'))) { print "rpmbuild failed: $?\n"; print "re-running in verbose mode:\n"; system("rpmbuild -bb $specfile 2>&1"); exit(1); } unlink($specfile); } } } ################################################################ sub handle_debs { eval { require Parse::DebControl; }; if ($@){ print "mkbaselibs needs the perl module Parse::DebControl\n". "Error. baselibs-deb.conf specified but mkbaselibs can't run\n". "Please ensure that 'osc meta prjconf' contains the following line:\n". " Support: libparse-debcontrol-perl\n"; return; }; # for each deb: # look in the config file to see if we should be doing anything # # Unpack the deb control data using dpkg-deb # for each target # Unpack the deb control data *and* file data using dpkg-deb # process the config file for this package modifying control and moving files # repackage the target deb for my $deb (@_) { # http://www.debian.org/doc/debian-policy/ch-controlfields.html#s-binarycontrolfiles # unpack the outer loop control file - this gives us eg: the arch my $base = tempdir() || die("tempdir: $!\n"); system "dpkg -e $deb ${base}/DEBIAN" || die "dpkg -e failed on $deb"; my $controlParser = new Parse::DebControl; $controlParser->DEBUG(); my $keys = $controlParser->parse_file("${base}/DEBIAN/control"); # print Dumper($keys); # DebControl supports multiple paragraphs of control data but # debian/control in a .deb only has one (whereas a debian/control # in a build root contains many) # So extract the ref to the first one. my %control = %{@{$keys}[0]}; # Validate this is a binary deb and get the control data my $d_name = $control{'Package'}; my $d_version = $control{'Version'}; $arch = $control{'Architecture'}; # set global $arch # examine the # arch targets [:] [[:]...] # line and get a list of target_arch-es my @targets = get_targets($arch, $config); if (!@targets) { print "no targets for arch $arch, skipping $d_name\n"; next; # there may be more debs to handle } for my $target (@targets) { next unless parse_config($target, $d_name, $d_version); die("targetname not set\n") unless $targetname; # set in the global_conf $target_matched{$target} = 1; my $baseTarget = "${base}/$target"; # Unpack a .deb to work on. We have to do this each time as we # manipulate the unpacked files. system "mkdir ${base}/$target"; system "dpkg -e $deb ${baseTarget}/DEBIAN" || die "dpkg -e failed on $deb"; # Note that extracting to $prefix does the clever move to /lib-x86/ or whatever system "dpkg -x $deb ${baseTarget}/$prefix" || die "dpkg -x failed on $deb"; # Reset the control data $keys = $controlParser->parse_file("${baseTarget}/DEBIAN/control"); %control = %{@{$keys}[0]}; # Force the architecture $control{'Architecture'} = $targetarch; # Currently this script does not manipulate any files # If needed they are all unpacked in ${baseTarget} # we don't need a dsc/spec file.. all done by just moving files around # and running dpkg -b ${base} $NEW_DEB # # my $dscfile = "/usr/src/packages/DSCS/mkbaselibs$$.dsc"; print "$d_name($target): writing dscfile...\n"; # We can Use Parse::DebControl write_file to create the new control file # just modify tags in there # We'll use requires -> Depends: map s/^"(.*)"$/$1/, @requires; # remove leading/trailing "s $control{"Depends"} = @requires ? join(", ", @requires) : ""; # join array if exists or reset it to "" map s/^"(.*)"$/$1/, @prerequires; $control{"Pre-Depends"} = @prerequires ? join(", ", @prerequires) : ""; map s/^"(.*)"$/$1/, @provides; $control{"Provides"} = @provides ? join(", ", @provides) : ""; map s/^"(.*)"$/$1/, @recommends; $control{"Recommends"} = @recommends ? join(", ", @recommends) : ""; map s/^"(.*)"$/$1/, @suggests; $control{"Suggests"} = @suggests ? join(", ", @suggests) : ""; map s/^"(.*)"$/$1/, @obsoletes; $control{"Replaces"} = @obsoletes ? join(", ", @obsoletes) : ""; map s/^"(.*)"$/$1/, @conflicts; $control{"Conflicts"} = @conflicts ? join(", ", @conflicts) : ""; map s/^"(.*)"$/$1/, @supplements; $control{"Enhances"} = @supplements ? join(", ", @supplements) : ""; # Tidy up the various control files. # the md5sums are regenerated by dpkg-deb when building foreach my $c_file ( qw(conffiles postins postrm preinst prerm) ) { unlink "${baseTarget}/DEBIAN/$c_file"; } # Create them if needed if (@prein) { map s/^"(.*)"$/$1/, @prein; # remove leading/trailing "s open(my $SCRIPT, ">${baseTarget}/DEBIAN/preinst"); print $SCRIPT join("\n", @prein) ; chmod(0755, $SCRIPT); close($SCRIPT); } if (@postin) { map s/^"(.*)"$/$1/, @postin; open(my $SCRIPT, ">${baseTarget}/DEBIAN/postinst"); print $SCRIPT join("\n", @postin) ; chmod(0755, $SCRIPT); close($SCRIPT); } if (@preun) { map s/^"(.*)"$/$1/, @preun; open(my $SCRIPT, ">${baseTarget}/DEBIAN/prerm"); print $SCRIPT join("\n", @preun) ; chmod(0755, $SCRIPT); close($SCRIPT); } if (@postun) { map s/^"(.*)"$/$1/, @postun; open(my $SCRIPT, ">${baseTarget}/DEBIAN/postrm"); print $SCRIPT join("\n", @postun) ; chmod(0755, $SCRIPT); close($SCRIPT); } # Don't forget to rename the package - or it will replace/uninstall the /-based one $control{"Package"} = "${d_name}-${targettype}"; $controlParser->write_file("${baseTarget}/DEBIAN/control", \%control, {clobberFile => 1, addNewline=>1 } ); system "dpkg -b ${baseTarget} /usr/src/packages/DEBS/${d_name}-${targettype}_${d_version}_${targetarch}.deb" || die "dpkg -b failed on $deb"; system "rm -rf ${baseTarget}"; } system "rm -rf ${base}"; } } # args is a list of full pathnames to rpm/deb files die("Usage: mkbaselibs \n") unless @ARGV; if ($ARGV[0] eq '-v') { $verbose = 1; shift @ARGV; } while ($ARGV[0] eq '-c') { shift @ARGV; read_config($ARGV[0]); shift @ARGV; } my %goodpkgs = map {$_ => 1} get_pkgnames(); # These are packages named in the config file my @pkgs = @ARGV; my @rpms; my @debugrpms; for my $rpm (@pkgs) { my $rpmn = $rpm; unless (-f $rpm) { warn ("$rpm does not exist, skipping\n"); next; } next if $rpm =~ /\.(no)?src\.rpm$/; # ignore source rpms next if $rpm =~ /\.spm$/; $rpmn =~ s/.*\///; # Remove leading path info $rpmn =~ s/-[^-]+-[^-]+\.[^\.]+\.rpm$/\.rpm/; # remove all version info $rpmn =~ s/\.rpm$//; # remove extension push @rpms, $rpm if $goodpkgs{$rpmn}; if ($rpmn =~ s/-debuginfo$//) { push @debugrpms, $rpm if $goodpkgs{$rpmn}; } } for (@rpms) { die("$_: need absolute path to package\n") unless /^\//; } my %debs_to_process = map {$_ => 1} get_debpkgnames(); # These are packages named in the config file my @debs; for my $deb (@pkgs) { my $debn = $deb; next unless $debn =~ /\.deb$/; $debn =~ s/.*\///; # Remove leading path info $debn =~ s/_[^_]+_[^_]+\.deb$//; # remove all version info and extension push @debs, $deb if $debs_to_process{$debn}; print "ignoring $deb as $debn not in baselibs.conf\n" if !$debs_to_process{$debn}; } for (@debs) { die("$_: need absolute path to package\n") unless /^\//; } exit 0 unless @rpms or @debs; if (@rpms) { @filesystem = split("\n", `rpm -ql filesystem 2>/dev/null`); die("filesystem rpm is not installed\n") unless @filesystem; handle_rpms(@rpms); handle_rpms(@debugrpms); } if (@debs) { handle_debs(@debs); }