185 lines
5.4 KiB
Perl
185 lines
5.4 KiB
Perl
|
use strict;
|
||
|
use XML::Simple;
|
||
|
use List::Util qw(min);
|
||
|
|
||
|
my @packagesFiles = ();
|
||
|
my @urlPrefixes = ();
|
||
|
|
||
|
# rpm-closure.pl (<package-file> <url-prefix>)+ <toplevel-pkg>+
|
||
|
|
||
|
while(-f $ARGV[0]) {
|
||
|
my $packagesFile = shift @ARGV;
|
||
|
my $urlPrefix = shift @ARGV;
|
||
|
push(@packagesFiles, $packagesFile);
|
||
|
push(@urlPrefixes, $urlPrefix);
|
||
|
}
|
||
|
|
||
|
|
||
|
sub rpmvercmp {
|
||
|
my ($version1, $version2) = @_;
|
||
|
my @vercmps1 = split /\./, $version1;
|
||
|
my @vercmps2 = split /\./, $version2;
|
||
|
my $l1 = scalar(@vercmps1);
|
||
|
my $l2 = scalar(@vercmps2);
|
||
|
my $l = min($l1, $l2);
|
||
|
|
||
|
for(my $i=0; $i<$l; $i++) {
|
||
|
my $v1 = $vercmps1[$i];
|
||
|
my $v2 = $vercmps2[$i];
|
||
|
|
||
|
if($v1 =~ /^[0-9]*$/ && $v2 =~ /^[0-9]*$/) {
|
||
|
if ( int($v1) > int($v2) ) {
|
||
|
return 1;
|
||
|
}
|
||
|
elsif ( int($v1) < int($v2) ) {
|
||
|
return -1;
|
||
|
}
|
||
|
} else {
|
||
|
if ( $v1 gt $v2 ) {
|
||
|
return 1;
|
||
|
}
|
||
|
elsif ( $v1 lt $v2 ) {
|
||
|
return -1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if($l1 == $l2) {
|
||
|
return 0;
|
||
|
} elsif ($l1 > $l2) {
|
||
|
return 1;
|
||
|
} elsif ($l1 < $l2) {
|
||
|
return -1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
my @toplevelPkgs = @ARGV;
|
||
|
|
||
|
my @archs = split ' ', ($ENV{'archs'} or "");
|
||
|
|
||
|
my %pkgs;
|
||
|
for (my $i = 0; $i < scalar(@packagesFiles); $i++) {
|
||
|
my $packagesFile = $packagesFiles[$i];
|
||
|
print STDERR "parsing packages in $packagesFile...\n";
|
||
|
|
||
|
my $xml = XMLin($packagesFile, ForceArray => ['package', 'rpm:entry', 'file'], KeyAttr => []) or die;
|
||
|
|
||
|
print STDERR "$packagesFile contains $xml->{packages} packages\n";
|
||
|
|
||
|
foreach my $pkg (@{$xml->{'package'}}) {
|
||
|
if (scalar @archs > 0) {
|
||
|
my $arch = $pkg->{arch};
|
||
|
my $found = 0;
|
||
|
foreach my $a (@archs) { $found = 1 if $arch eq $a; }
|
||
|
next if !$found;
|
||
|
}
|
||
|
if (defined $pkgs{$pkg->{name}}) {
|
||
|
my $earlierPkg = $pkgs{$pkg->{name}};
|
||
|
print STDERR "WARNING: duplicate occurrence of package $pkg->{name}\n";
|
||
|
# <version epoch="0" ver="1.28.0" rel="2.el6"/>
|
||
|
my $cmp = rpmvercmp($pkg->{'version'}->{ver}, $earlierPkg->{'version'}->{ver});
|
||
|
if ($cmp > 0 || ($cmp == 0 && rpmvercmp($pkg->{'version'}->{rel}, $earlierPkg->{'version'}->{rel})>0)) {
|
||
|
print STDERR "WARNING: replaced package $pkg->{name} (".$earlierPkg->{'version'}->{ver}." ".$earlierPkg->{'version'}->{rel}.") with newer one (".$pkg->{'version'}->{ver}." ".$pkg->{'version'}->{rel}.")\n";
|
||
|
$pkg->{urlPrefix} = $urlPrefixes[$i];
|
||
|
$pkgs{$pkg->{name}} = $pkg;
|
||
|
}
|
||
|
next;
|
||
|
}
|
||
|
$pkg->{urlPrefix} = $urlPrefixes[$i];
|
||
|
$pkgs{$pkg->{name}} = $pkg;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
my %provides;
|
||
|
PKG: foreach my $pkgName (sort(keys %pkgs)) {
|
||
|
#print STDERR "looking at $pkgName\n";
|
||
|
my $pkg = $pkgs{$pkgName};
|
||
|
|
||
|
# Skip packages that conflict with a required package.
|
||
|
my $conflicts = $pkg->{format}->{'rpm:conflicts'}->{'rpm:entry'} // [];
|
||
|
foreach my $conflict (@{$conflicts}) {
|
||
|
next if $conflict->{flags} // "" eq "LT" || $conflict->{flags} // "" eq "LE";
|
||
|
#print STDERR " $pkgName conflicts with $conflict->{name}\n";
|
||
|
if (grep { $_ eq $conflict->{name} } @toplevelPkgs) {
|
||
|
print STDERR "skipping package $pkgName because it conflicts with a required package\n";
|
||
|
next PKG;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
my $provides = $pkg->{format}->{'rpm:provides'}->{'rpm:entry'} or die;
|
||
|
foreach my $req (@{$provides}) {
|
||
|
#print STDERR " $pkgName provides $req->{name}\n";
|
||
|
#die "multiple provides for $req->{name}" if defined $provides{$req->{name}};
|
||
|
$provides{$req->{name}} = $pkgName;
|
||
|
}
|
||
|
|
||
|
if (defined $pkg->{format}->{file}) {
|
||
|
foreach my $file (@{$pkg->{format}->{file}}) {
|
||
|
#print STDERR " provides file $file\n";
|
||
|
$provides{$file} = $pkgName;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
my %donePkgs;
|
||
|
my @needed = ();
|
||
|
|
||
|
sub closePackage {
|
||
|
my $pkgName = shift;
|
||
|
|
||
|
return if defined $donePkgs{$pkgName};
|
||
|
$donePkgs{$pkgName} = 1;
|
||
|
|
||
|
print STDERR ">>> $pkgName\n";
|
||
|
|
||
|
my $pkg = $pkgs{$pkgName} or die "package $pkgName doesn't exist";
|
||
|
|
||
|
my $requires = $pkg->{format}->{'rpm:requires'}->{'rpm:entry'} || [];
|
||
|
|
||
|
my @deps = ();
|
||
|
foreach my $req (@{$requires}) {
|
||
|
next if $req->{name} =~ /^rpmlib\(/;
|
||
|
#print STDERR " needs $req->{name}\n";
|
||
|
my $provider = $provides{$req->{name}};
|
||
|
if (!defined $provider) {
|
||
|
print STDERR " WARNING: no provider for $req->{name}\n";
|
||
|
next;
|
||
|
}
|
||
|
#print STDERR " satisfied by $provider\n";
|
||
|
push @deps, $provider;
|
||
|
}
|
||
|
|
||
|
closePackage($_) foreach @deps;
|
||
|
|
||
|
push @needed, $pkgName;
|
||
|
}
|
||
|
|
||
|
|
||
|
foreach my $pkgName (@toplevelPkgs) {
|
||
|
closePackage $pkgName;
|
||
|
}
|
||
|
|
||
|
|
||
|
# Generate the output Nix expression.
|
||
|
print "# This is a generated file. Do not modify!\n";
|
||
|
print "# Following are the RPM packages constituting the closure of: @toplevelPkgs\n\n";
|
||
|
print "{fetchurl}:\n\n";
|
||
|
print "[\n\n";
|
||
|
|
||
|
foreach my $pkgName (@needed) {
|
||
|
my $pkg = $pkgs{$pkgName};
|
||
|
print " (fetchurl {\n";
|
||
|
print " url = $pkg->{urlPrefix}/$pkg->{location}->{href};\n";
|
||
|
if ($pkg->{checksum}->{type} eq "sha") {
|
||
|
print " sha1 = \"$pkg->{checksum}->{content}\";\n";
|
||
|
} elsif ($pkg->{checksum}->{type} eq "sha256") {
|
||
|
print " sha256 = \"$pkg->{checksum}->{content}\";\n";
|
||
|
} else {
|
||
|
die "unsupported hash type";
|
||
|
}
|
||
|
print " })\n";
|
||
|
print "\n";
|
||
|
}
|
||
|
|
||
|
print "]\n";
|