aboutsummaryrefslogtreecommitdiff
path: root/scripts/generate-patches.pl.in
diff options
context:
space:
mode:
authorEelco Dolstra <e.dolstra@tudelft.nl>2005-02-01 09:54:56 +0000
committerEelco Dolstra <e.dolstra@tudelft.nl>2005-02-01 09:54:56 +0000
commita6b65fd5e107416588a6572a88518d8816abcb12 (patch)
treeb1a5df197e55b7a14cf31edae2de06d8a773c099 /scripts/generate-patches.pl.in
parent06b44242860d62e12f9abe018c6f974ceaa30bb9 (diff)
* Get rid of hardcoded paths.
Diffstat (limited to 'scripts/generate-patches.pl.in')
-rwxr-xr-xscripts/generate-patches.pl.in285
1 files changed, 285 insertions, 0 deletions
diff --git a/scripts/generate-patches.pl.in b/scripts/generate-patches.pl.in
new file mode 100755
index 000000000..cf9e24141
--- /dev/null
+++ b/scripts/generate-patches.pl.in
@@ -0,0 +1,285 @@
+#! @perl@ -w -I@libexecdir@/nix
+
+use strict;
+use POSIX qw(tmpnam);
+use readmanifest;
+
+die unless scalar @ARGV == 5;
+
+my $cacheDir = $ARGV[0];
+my $patchesDir = $ARGV[1];
+my $patchesURL = $ARGV[2];
+my $srcDir = $ARGV[3];
+my $dstDir = $ARGV[4];
+
+my $tmpdir;
+do { $tmpdir = tmpnam(); }
+until mkdir $tmpdir, 0777;
+
+print "TEMP = $tmpdir\n";
+
+#END { rmdir $tmpdir; }
+
+my %srcNarFiles;
+my %srcPatches;
+my %srcSuccessors;
+
+my %dstNarFiles;
+my %dstPatches;
+my %dstSuccessors;
+
+readManifest "$srcDir/MANIFEST",
+ \%srcNarFiles, \%srcPatches, \%srcSuccessors;
+
+readManifest "$dstDir/MANIFEST",
+ \%dstNarFiles, \%dstPatches, \%dstSuccessors;
+
+
+sub findOutputPaths {
+ my $narFiles = shift;
+ my $successors = shift;
+
+ my %outPaths;
+
+ foreach my $p (keys %{$narFiles}) {
+
+ # Ignore store expressions.
+ next if ($p =~ /\.store$/);
+
+ # Ignore builders (too much ambiguity -- they're all called
+ # `builder.sh').
+ next if ($p =~ /\.sh$/);
+ next if ($p =~ /\.patch$/);
+
+ # Don't bother including tar files etc.
+ next if ($p =~ /\.tar\.(gz|bz2)$/ || $p =~ /\.zip$/ || $p =~ /\.bin$/);
+
+ $outPaths{$p} = 1;
+ }
+
+ return %outPaths;
+}
+
+print "finding src output paths...\n";
+my %srcOutPaths = findOutputPaths \%srcNarFiles, \%srcSuccessors;
+
+print "finding dst output paths...\n";
+my %dstOutPaths = findOutputPaths \%dstNarFiles, \%dstSuccessors;
+
+
+sub getNameVersion {
+ my $p = shift;
+ $p =~ /\/[0-9a-f]+((?:-[a-zA-Z][^\/-]*)+)([^\/]*)$/;
+ my $name = $1;
+ my $version = $2;
+ $name =~ s/^-//;
+ $version =~ s/^-//;
+ return ($name, $version);
+}
+
+
+# A quick hack to get a measure of the `distance' between two
+# versions: it's just the position of the first character that differs
+# (or 999 if they are the same).
+sub versionDiff {
+ my $s = shift;
+ my $t = shift;
+ my $i;
+ return 999 if $s eq $t;
+ for ($i = 0; $i < length $s; $i++) {
+ return $i if $i >= length $t or
+ substr($s, $i, 1) ne substr($t, $i, 1);
+ }
+ return $i;
+}
+
+
+sub getNarBz2 {
+ my $narFiles = shift;
+ my $storePath = shift;
+
+ my $narFileList = $$narFiles{$storePath};
+ die "missing store expression $storePath" unless defined $narFileList;
+
+ my $narFile = @{$narFileList}[0];
+ die unless defined $narFile;
+
+ $narFile->{url} =~ /\/([^\/]+)$/;
+ die unless defined $1;
+ return "$cacheDir/$1";
+}
+
+
+sub containsPatch {
+ my $patches = shift;
+ my $storePath = shift;
+ my $basePath = shift;
+ my $patchList = $$patches{$storePath};
+ return 0 if !defined $patchList;
+ my $found = 0;
+ foreach my $patch (@{$patchList}) {
+ # !!! baseHash might differ
+ return 1 if $patch->{basePath} eq $basePath;
+ }
+ return 0;
+}
+
+
+# For each output path in the destination, see if we need to / can
+# create a patch.
+
+print "creating patches...\n";
+
+foreach my $p (keys %dstOutPaths) {
+
+ # If exactly the same path already exists in the source, skip it.
+ next if defined $srcOutPaths{$p};
+
+# print " $p\n";
+
+ # If not, then we should find the path in the source that is
+ # `most' likely to be present on a system that wants to install
+ # this path.
+
+ (my $name, my $version) = getNameVersion $p;
+
+ my @closest = ();
+ my $closestVersion;
+ my $minDist = -1; # actually, larger means closer
+
+ # Find all source paths with the same name.
+
+ foreach my $q (keys %srcOutPaths) {
+ (my $name2, my $version2) = getNameVersion $q;
+ if ($name eq $name2) {
+ my $dist = versionDiff $version, $version2;
+ if ($dist > $minDist) {
+ $minDist = $dist;
+ @closest = ($q);
+ $closestVersion = $version2;
+ } elsif ($dist == $minDist) {
+ push @closest, $q;
+ }
+ }
+ }
+
+ if (scalar(@closest) == 0) {
+ print " NO BASE: $p\n";
+ next;
+ }
+
+ foreach my $closest (@closest) {
+
+ # Generate a patch between $closest and $p.
+ print " $p <- $closest\n";
+
+ # If the patch already exists, skip it.
+ if (containsPatch(\%srcPatches, $p, $closest) ||
+ containsPatch(\%dstPatches, $p, $closest))
+ {
+ print " skipping, already exists\n";
+ next;
+ }
+
+# next;
+
+ my $srcNarBz2 = getNarBz2 \%srcNarFiles, $closest;
+ my $dstNarBz2 = getNarBz2 \%dstNarFiles, $p;
+
+ system("@bunzip2@ < $srcNarBz2 > $tmpdir/A") == 0
+ or die "cannot unpack $srcNarBz2";
+
+ system("@bunzip2@ < $dstNarBz2 > $tmpdir/B") == 0
+ or die "cannot unpack $dstNarBz2";
+
+ system("@libexecdir@/bspatch $tmpdir/A $tmpdir/B $tmpdir/DIFF") == 0
+ or die "cannot compute binary diff";
+
+ my $baseHash = `@bindir@/nix-hash --flat $tmpdir/A` or die;
+ chomp $baseHash;
+
+ my $narHash = `@bindir@/nix-hash --flat $tmpdir/B` or die;
+ chomp $narHash;
+
+ my $narDiffHash = `@bindir@/nix-hash --flat $tmpdir/DIFF` or die;
+ chomp $narDiffHash;
+
+ my $narDiffSize = (stat "$tmpdir/DIFF")[7];
+ my $dstNarBz2Size = (stat $dstNarBz2)[7];
+
+ if ($narDiffSize >= $dstNarBz2Size) {
+ print " rejecting; patch bigger than full archive\n";
+ next;
+ }
+
+ my $finalName =
+ "$narDiffHash-$name-$closestVersion-to-$version.nar-bsdiff";
+
+ print " size $narDiffSize; full size $dstNarBz2Size\n";
+
+ if (-e "$patchesDir/$finalName") {
+ print " not copying, already exists\n";
+ }
+
+ else {
+
+ system("cp '$tmpdir/DIFF' '$patchesDir/$finalName.tmp'") == 0
+ or die "cannot copy diff";
+
+ rename("$patchesDir/$finalName.tmp", "$patchesDir/$finalName")
+ or die "cannot rename $patchesDir/$finalName.tmp";
+
+ }
+
+ # Add the patch to the manifest.
+ addPatch \%dstPatches, $p,
+ { url => "$patchesURL/$finalName", hash => $narDiffHash
+ , size => $narDiffSize
+ , basePath => $closest, baseHash => $baseHash
+ , narHash => $narHash, patchType => "nar-bsdiff"
+ };
+ }
+}
+
+
+# Add in any potentially useful patches in the source (namely, those
+# patches that produce either paths in the destination or paths that
+# can be used as the base for other useful patches).
+
+my $changed;
+do {
+ # !!! we repeat this to reach the transitive closure; inefficient
+ $changed = 0;
+
+ foreach my $p (keys %srcPatches) {
+ my $patchList = $srcPatches{$p};
+
+ my $include = 0;
+
+ # Is path $p included in the destination? If so, include
+ # patches that produce it.
+ $include = 1 if (defined $dstNarFiles{$p});
+
+ # Is path $p a path that serves as a base for paths in the
+ # destination? If so, include patches that produce it.
+ foreach my $q (keys %dstPatches) {
+ foreach my $patch (@{$dstPatches{$q}}) {
+ # !!! check baseHash
+ $include = 1 if ($p eq $patch->{basePath});
+ }
+ }
+
+ if ($include) {
+ foreach my $patch (@{$patchList}) {
+ $changed = 1 if addPatch \%dstPatches, $p, $patch;
+ }
+ }
+
+ }
+
+} while $changed;
+
+
+# Rewrite the manifest of the destination (with the new patches).
+writeManifest "$dstDir/MANIFEST",
+ \%dstNarFiles, \%dstPatches, \%dstSuccessors;