about summary refs log blame commit diff
path: root/corepkgs/nix-pull/download.pl
blob: 5c7b33a996f5ad73d050bd9b382d2453177bf86a (plain) (tree)

























































































































































































































                                                                              
#! /usr/bin/perl -w -I/home/eelco/Dev/nix/scripts

use strict;
use readmanifest;

my $manifestDir = "/home/eelco/Dev/nix/patch/test";


# Check the arguments.
die unless scalar @ARGV == 1;
my $targetPath = $ARGV[0];


# Load all manifests.
my %narFiles;
my %patches;
my %successors;

for my $manifest (glob "$manifestDir/*.nixmanifest") {
    print STDERR "reading $manifest\n";
    readManifest $manifest, \%narFiles, \%patches, \%successors;
}


# Build a graph of all store paths that might contribute to the
# construction of $targetPath, and the special node "start".  The
# edges are either patch operations, or downloads of full NAR files.
# The latter edges only occur between "start" and a store path.

my %graph;

$graph{"start"} = {d => 0, pred => undef, edges => []};

my @queue = ();
my $queueFront = 0;
my %done;

sub addToQueue {
    my $v = shift;
    return if defined $done{$v};
    $done{$v} = 1;
    push @queue, $v;
}

sub addNode {
    my $u = shift;
    $graph{$u} = {d => 999999999999, pred => undef, edges => []}
        unless defined $graph{$u};
}

sub addEdge {
    my $u = shift;
    my $v = shift;
    my $w = shift;
    my $type = shift;
    my $info = shift;
    addNode $u;
    push @{$graph{$u}->{edges}},
        {weight => $w, start => $u, end => $v, type => $type, info => $info};
    my $n = scalar @{$graph{$u}->{edges}};
}

addToQueue $targetPath;

while ($queueFront < scalar @queue) {
    my $u = $queue[$queueFront++];
    print "$u\n";

    addNode $u;

    # If the path already exists, it has distance 0 from the "start"
    # node.
    system "nix-store --isvalid '$u' 2> /dev/null";
    if ($? == 0) {
        addEdge "start", $u, 0, "present", undef;
    }

    else {

        # Add patch edges.
        my $patchList = $patches{$u};
        foreach my $patch (@{$patchList}) {
            # !!! this should be cached
            my $hash = `nix-hash "$patch->{basePath}"`;
            chomp $hash;
            print "  MY HASH is $hash\n";
            if ($hash ne $patch->{baseHash}) {
                print "  REJECTING PATCH from $patch->{basePath}\n";
                next;
            }
            print "  PATCH from $patch->{basePath}\n";
            addToQueue $patch->{basePath};
            addEdge $patch->{basePath}, $u, $patch->{size}, "patch", $patch;
        }

        # Add NAR file edges to the start node.
        my $narFileList = $narFiles{$u};
        foreach my $narFile (@{$narFileList}) {
            print "  NAR from $narFile->{url}\n";
            addEdge "start", $u, $narFile->{size}, "narfile", $narFile;
        }

    }
}


# Run Dijkstra's shortest path algorithm to determine the shortest
# sequence of download and/or patch actions that will produce
# $targetPath.

sub byDistance { # sort by distance, reversed
    return -($graph{$a}->{d} <=> $graph{$b}->{d});
}

my @todo = keys %graph;

while (scalar @todo > 0) {

    # Remove the closest element from the todo list.
    @todo = sort byDistance @todo;
    my $u = pop @todo;

    my $u_ = $graph{$u};

    print "IN $u $u_->{d}\n";

    foreach my $edge (@{$u_->{edges}}) {
        my $v_ = $graph{$edge->{end}};
        if ($v_->{d} > $u_->{d} + $edge->{weight}) {
            $v_->{d} = $u_->{d} + $edge->{weight};
            # Store the edge; to edge->start is actually the
            # predecessor.
            $v_->{pred} = $edge; 
            print "  RELAX $edge->{end} $v_->{d}\n";
        }
    }
}


# Retrieve the shortest path from "start" to $targetPath.
my @path = ();
my $cur = $targetPath;
die "don't know how to produce $targetPath\n"
    unless defined $graph{$targetPath}->{pred};
while ($cur ne "start") {
    push @path, $graph{$cur}->{pred};
    $cur = $graph{$cur}->{pred}->{start};
}


# Traverse the shortest path, perform the actions described by the
# edges.
my $curStep = 1;
my $maxStep = scalar @path;

sub downloadFile {
    my $url = shift;
    my $hash = shift;
    $ENV{"PRINT_PATH"} = 1;
    $ENV{"QUIET"} = 1;
    my ($hash2, $path) = `nix-prefetch-url '$url' '$hash'`;
    chomp $hash2;
    chomp $path;
    die "hash mismatch" if $hash ne $hash2;
    return $path;
}

while (scalar @path > 0) {
    my $edge = pop @path;
    my $u = $edge->{start};
    my $v = $edge->{end};

    print "\n*** Step $curStep/$maxStep: ";
    $curStep++;

    if ($edge->{type} eq "present") {
        print "using already present path `$v'\n";
    }

    elsif ($edge->{type} eq "patch") {
        my $patch = $edge->{info};
        print "applying patch `$patch->{url}' to `$u' to create `$v'\n";

        # Download the patch.
        print "  downloading patch...\n";
        my $patchPath = downloadFile "$patch->{url}", "$patch->{hash}";

        # Turn the base path into a NAR archive, to which we can
        # actually apply the patch.
        print "  packing base path...\n";
        system "nix-store --dump $patch->{basePath} > /tmp/nar";
        die "cannot dump `$patch->{basePath}'" if ($? != 0);

        # Apply the patch.
        print "  applying patch...\n";
        system "bspatch /tmp/nar /tmp/nar2 $patchPath";
        die "cannot apply patch `$patchPath' to /tmp/nar" if ($? != 0);

        # Unpack the resulting NAR archive into the target path.
        print "  unpacking patched archive...\n";
        system "nix-store --restore $targetPath < /tmp/nar2";
        die "cannot unpack /tmp/nar2 into `$targetPath'" if ($? != 0);
    }

    elsif ($edge->{type} eq "narfile") {
        my $narFile = $edge->{info};
        print "downloading `$narFile->{url}' into `$v'\n";

        # Download the archive.
        print "  downloading archive...\n";
        my $narFilePath = downloadFile "$narFile->{url}", "$narFile->{hash}";

        # Unpack the archive into the target path.
        print "  unpacking archive...\n";
        system "bunzip2 < '$narFilePath' | nix-store --restore '$targetPath'";
        die "cannot unpack `$narFilePath' into `$targetPath'" if ($? != 0);
    }
}