Hello World

File mkbaselibs of Package build

#!/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, $pkgname, $pkgver, $pkgrel, $pkgepoch) = @_;

  my $pkgevr = $pkgepoch ? "$pkgepoch:$pkgver-$pkgrel" : "$pkgver-$pkgrel";
  my $pkghasmatched;

  my $pkgmatches = 1;
  my $packageseen = 0;
  $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 '' || $_ =~ /^#/;

    if ($_ eq 'end_of_config') {
      $pkgmatches = 0;
      $packageseen = 0;
      next;
    }

    s/\<targettype\>/$targettype/g;
    s/\<targetarch\>/$targetarch/g;
    s/\<name\>/$pkgname/g;
    s/\<version\>/$pkgver/g;
    s/\<release\>/$pkgrel/g;
    s/\<evr\>/$pkgevr/g;
    s/\<prefix\>/$prefix/g;
    s/\<extension\>/$extension/g;
    s/\<configdir\>/$configdir/g;
    s/\<match1\>/$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 for targettype/targetarch conditional
      $packageseen = 1;
    }
    if (/\s+package\s+\/[-+_a-zA-Z0-9]+\/$/) {
      $pkgmatches = 0;	# XXX: hack
      $packageseen = 1;
    }
    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 if $pkgmatches || !$packageseen;
      next;
    }

    $_ = "baselib $_" if /^[\+\-\"]/;
    $_ = "package $_" if /^[-+_a-zA-Z0-9]+$/;
    if (/^package\s+\/(.*?)\/$/) {
      my $pm = $1;
      $packageseen = 1;
      $pkgmatches = $pkgname =~ /$pm/;
      $match1 = defined($1) ? $1 : '' if $pkgmatches;
      $pkghasmatched |= $pkgmatches if $pkgname =~ /-debuginfo$/ && $target_matched{$target};
      next;
    }
    if (/^package\s+(.*?)$/) {
      $packageseen = 1;
      $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 = <F>;
  close F;
  $config .= join('', @cf);
  # add end of config marker to reset package matching
  $config .= "\nend_of_config\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], $res{'RELEASE'}->[0], ($res{'EPOCH'} || [])->[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*(.*?)$/) {
	  $rr = $1;
	  my $mrr = $2;
	  if ($mrr =~ /\$[1-9]/) {
	    for my $f (grep {/$rr/} @rpmfiles) {
	      $files{$f} = 1;
	      $moves{$f} = $mrr;
	      my @s = $f =~ /$rr/;
	      $moves{$f} =~ s/\$([1-9])/$s[$1 - 1]/g;
	    }
	  } else {
	    if (grep {$_ eq $rr} @rpmfiles) {
	      $files{$rr} = 1;
	      $moves{$rr} = $mrr;
	    }
	  }
	} 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 <<EOFL >.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 <<EOFL >.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 <arch> targets <target_arch>[:<target_type>] [<target_arch>[:<target_type>]...]
    # 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}";
  }
}

while (@ARGV) {
  if ($ARGV[0] eq '-v') {
    $verbose = 1;
    shift @ARGV;
  } elsif ($ARGV[0] eq '-c') {
    shift @ARGV;
    read_config($ARGV[0]);
    shift @ARGV;
  } else {
    last;
  }
}

# args is a list of full pathnames to rpm/deb files
die("Usage: mkbaselibs [-v] [-c config] <rpms>\n") unless @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;
  }
  my @rpmfiles = `rpm -qp --queryformat "[%{FILENAMES}\n]" $rpm`;
  if (!@rpmfiles) {
    warn ("$rpm is empty, 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$/;
  my @debfiles = `dpkg --contents $deb`;
  if (!@debfiles) {
    warn ("$deb is empty, skipping\n");
    next;
  }
  $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);
}