about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEelco Dolstra <eelco.dolstra@logicblox.com>2015-10-01T14·47+0200
committerEelco Dolstra <eelco.dolstra@logicblox.com>2015-10-01T14·47+0200
commitbec3c3160881fcedf226080f13739aee81adae1a (patch)
tree6f3b9ab73916cd821a77324c34279826423a3640
parentbdc4a0b54d54146448061dd9a248212f98a9f801 (diff)
nix-prefetch-url: Rewrite in C++
-rw-r--r--Makefile1
-rw-r--r--scripts/local.mk1
-rwxr-xr-xscripts/nix-prefetch-url.in132
-rw-r--r--src/libutil/util.hh1
-rw-r--r--src/nix-prefetch-url/local.mk7
-rw-r--r--src/nix-prefetch-url/nix-prefetch-url.cc132
6 files changed, 141 insertions, 133 deletions
diff --git a/Makefile b/Makefile
index fe2e88a995..3a204de888 100644
--- a/Makefile
+++ b/Makefile
@@ -13,6 +13,7 @@ makefiles = \
   src/nix-collect-garbage/local.mk \
   src/download-via-ssh/local.mk \
   src/nix-log2xml/local.mk \
+  src/nix-prefetch-url/local.mk \
   src/bsdiff-4.3/local.mk \
   perl/local.mk \
   scripts/local.mk \
diff --git a/scripts/local.mk b/scripts/local.mk
index 39e1df611c..3fb47676fa 100644
--- a/scripts/local.mk
+++ b/scripts/local.mk
@@ -4,7 +4,6 @@ nix_bin_scripts := \
   $(d)/nix-copy-closure \
   $(d)/nix-generate-patches \
   $(d)/nix-install-package \
-  $(d)/nix-prefetch-url \
   $(d)/nix-pull \
   $(d)/nix-push
 
diff --git a/scripts/nix-prefetch-url.in b/scripts/nix-prefetch-url.in
deleted file mode 100755
index 6effbe2081..0000000000
--- a/scripts/nix-prefetch-url.in
+++ /dev/null
@@ -1,132 +0,0 @@
-#! @perl@ -w @perlFlags@
-
-use utf8;
-use strict;
-use File::Basename;
-use File::stat;
-use Nix::Store;
-use Nix::Config;
-use Nix::Utils;
-
-binmode STDERR, ":encoding(utf8)";
-
-
-my $hashType = $ENV{'NIX_HASH_ALGO'} || "sha256"; # obsolete
-my $cacheDir = $ENV{'NIX_DOWNLOAD_CACHE'};
-
-my @args;
-my $arg;
-while ($arg = shift) {
-    if ($arg eq "--help") {
-        exec "man nix-prefetch-url" or die;
-    } elsif ($arg eq "--type") {
-        $hashType = shift;
-        die "$0: ‘$arg’ requires an argument\n" unless defined $hashType;
-    } elsif (substr($arg, 0, 1) eq "-") {
-        die "$0: unknown flag ‘$arg’\n";
-    } else {
-        push @args, $arg;
-    }
-}
-
-my $url = $args[0];
-my $expHash = $args[1];
-
-
-if (!defined $url || $url eq "") {
-    print STDERR <<EOF
-Usage: nix-prefetch-url URL [EXPECTED-HASH]
-EOF
-    ;
-    exit 1;
-}
-
-my $tmpDir = mkTempDir("nix-prefetch-url");
-
-# Hack to support the mirror:// scheme from Nixpkgs.
-if ($url =~ /^mirror:\/\//) {
-    system("$Nix::Config::binDir/nix-build '<nixpkgs>' -A resolveMirrorURLs --argstr url '$url' -o $tmpDir/urls > /dev/null") == 0
-        or die "$0: nix-build failed; maybe \$NIX_PATH is not set properly\n";
-    my @expanded = split ' ', readFile("$tmpDir/urls");
-    die "$0: cannot resolve ‘$url’" unless scalar @expanded > 0;
-    print STDERR "$url expands to $expanded[0]\n";
-    $url = $expanded[0];
-}
-
-# Handle escaped characters in the URI.  `+', `=' and `?' are the only
-# characters that are valid in Nix store path names but have a special
-# meaning in URIs.
-my $name = basename $url;
-die "cannot figure out file name for ‘$url’\n" if $name eq ""; 
-$name =~ s/%2b/+/g;
-$name =~ s/%3d/=/g;
-$name =~ s/%3f/?/g;
-
-my $finalPath;
-my $hash;
-
-# If the hash was given, a file with that hash may already be in the
-# store.
-if (defined $expHash) {
-    $finalPath = makeFixedOutputPath(0, $hashType, $expHash, $name);
-    if (isValidPath($finalPath)) { $hash = $expHash; } else { $finalPath = undef; }
-}
-
-# If we don't know the hash or a file with that hash doesn't exist,
-# download the file and add it to the store.
-if (!defined $finalPath) {
-
-    my $tmpFile = "$tmpDir/$name";
-    
-    # Optionally do timestamp-based caching of the download.
-    # Actually, the only thing that we cache in $NIX_DOWNLOAD_CACHE is
-    # the hash and the timestamp of the file at $url.  The caching of
-    # the file *contents* is done in Nix store, where it can be
-    # garbage-collected independently.
-    my ($cachedTimestampFN, $cachedHashFN, @cacheFlags);
-    if (defined $cacheDir) {
-        my $urlHash = hashString("sha256", 1, $url);
-        writeFile "$cacheDir/$urlHash.url", $url;
-        $cachedHashFN = "$cacheDir/$urlHash.$hashType";
-        $cachedTimestampFN = "$cacheDir/$urlHash.stamp";
-        @cacheFlags = ("--time-cond", $cachedTimestampFN) if -f $cachedHashFN && -f $cachedTimestampFN;
-    }
-    
-    # Perform the download.
-    my @curlFlags = ("curl", $url, "-o", $tmpFile, "--fail", "--location", "--max-redirs", "20", "--disable-epsv", "--cookie-jar", "$tmpDir/cookies", "--remote-time", (split " ", ($ENV{NIX_CURL_FLAGS} || "")));
-    (system $Nix::Config::curl @curlFlags, @cacheFlags) == 0 or die "$0: download of ‘$url’ failed\n";
-
-    if (defined $cacheDir && ! -e $tmpFile) {
-        # Curl didn't create $tmpFile, so apparently there's no newer
-        # file on the server.
-        $hash = readFile $cachedHashFN or die;
-        $finalPath = makeFixedOutputPath(0, $hashType, $hash, $name);
-        unless (isValidPath $finalPath) {
-            print STDERR "cached contents of ‘$url’ disappeared, redownloading...\n";
-            $finalPath = undef;
-            (system $Nix::Config::curl @curlFlags) == 0 or die "$0: download of ‘$url’ failed\n";
-        }
-    }
-
-    if (!defined $finalPath) {
-        
-        # Compute the hash.
-        $hash = hashFile($hashType, $hashType ne "md5", $tmpFile);
-
-        if (defined $cacheDir) {
-            writeFile $cachedHashFN, $hash;
-            my $st = stat($tmpFile) or die;
-            open STAMP, ">$cachedTimestampFN" or die; close STAMP;
-            utime($st->atime, $st->mtime, $cachedTimestampFN) or die;
-        }
-    
-        # Add the downloaded file to the Nix store.
-        $finalPath = addToStore($tmpFile, 0, $hashType);
-    }
-
-    die "$0: hash mismatch for ‘$url’\n" if defined $expHash && $expHash ne $hash;
-}
-
-print STDERR "path is ‘$finalPath’\n" unless $ENV{'QUIET'};
-print "$hash\n";
-print "$finalPath\n" if $ENV{'PRINT_PATH'};
diff --git a/src/libutil/util.hh b/src/libutil/util.hh
index b2fb59d6f2..a05a4cb880 100644
--- a/src/libutil/util.hh
+++ b/src/libutil/util.hh
@@ -202,6 +202,7 @@ public:
     AutoDelete(const Path & p, bool recursive = true);
     ~AutoDelete();
     void cancel();
+    operator Path() const { return path; }
 };
 
 
diff --git a/src/nix-prefetch-url/local.mk b/src/nix-prefetch-url/local.mk
new file mode 100644
index 0000000000..3e7735406a
--- /dev/null
+++ b/src/nix-prefetch-url/local.mk
@@ -0,0 +1,7 @@
+programs += nix-prefetch-url
+
+nix-prefetch-url_DIR := $(d)
+
+nix-prefetch-url_SOURCES := $(d)/nix-prefetch-url.cc
+
+nix-prefetch-url_LIBS = libmain libexpr libstore libutil libformat
diff --git a/src/nix-prefetch-url/nix-prefetch-url.cc b/src/nix-prefetch-url/nix-prefetch-url.cc
new file mode 100644
index 0000000000..bd52df1c74
--- /dev/null
+++ b/src/nix-prefetch-url/nix-prefetch-url.cc
@@ -0,0 +1,132 @@
+#include "hash.hh"
+#include "shared.hh"
+#include "download.hh"
+#include "store-api.hh"
+#include "eval.hh"
+#include "eval-inline.hh"
+#include "common-opts.hh"
+
+#include <iostream>
+
+using namespace nix;
+
+
+/* If ‘uri’ starts with ‘mirror://’, then resolve it using the list of
+   mirrors defined in Nixpkgs. */
+string resolveMirrorUri(EvalState & state, string uri)
+{
+    if (string(uri, 0, 9) != "mirror://") return uri;
+
+    string s(uri, 9);
+    auto p = s.find('/');
+    if (p == string::npos) throw Error("invalid mirror URI");
+    string mirrorName(s, 0, p);
+
+    Value vMirrors;
+    state.eval(state.parseExprFromString("import <nixpkgs/pkgs/build-support/fetchurl/mirrors.nix>", "."), vMirrors);
+    state.forceAttrs(vMirrors);
+
+    auto mirrorList = vMirrors.attrs->find(state.symbols.create(mirrorName));
+    if (mirrorList == vMirrors.attrs->end())
+        throw Error(format("unknown mirror name ‘%1%’") % mirrorName);
+    state.forceList(*mirrorList->value);
+
+    if (mirrorList->value->listSize() < 1)
+        throw Error(format("mirror URI ‘%1%’ did not expand to anything") % uri);
+
+    string mirror = state.forceString(*mirrorList->value->listElems()[0]);
+    return mirror + (hasSuffix(mirror, "/") ? "" : "/") + string(s, p + 1);
+}
+
+
+int main(int argc, char * * argv)
+{
+    return handleExceptions(argv[0], [&]() {
+        initNix();
+        initGC();
+
+        HashType ht = htSHA256;
+        std::vector<string> args;
+        Strings searchPath;
+
+        parseCmdLine(argc, argv, [&](Strings::iterator & arg, const Strings::iterator & end) {
+            if (*arg == "--help")
+                showManPage("nix-prefetch-url");
+            else if (*arg == "--version")
+                printVersion("nix-prefetch-url");
+            else if (*arg == "--type") {
+                string s = getArg(*arg, arg, end);
+                ht = parseHashType(s);
+                if (ht == htUnknown)
+                    throw UsageError(format("unknown hash type ‘%1%’") % s);
+            }
+            else if (parseSearchPathArg(arg, end, searchPath))
+                ;
+            else if (*arg != "" && arg->at(0) == '-')
+                return false;
+            else
+                args.push_back(*arg);
+            return true;
+        });
+
+        if (args.size() < 1 || args.size() > 2)
+            throw UsageError("nix-prefetch-url expects one argument");
+
+        store = openStore();
+
+        EvalState state(searchPath);
+
+        /* Figure out a name in the Nix store. */
+        auto uri = args[0];
+        auto name = baseNameOf(uri);
+        if (name.empty())
+            throw Error(format("cannot figure out file name for ‘%1%’") % uri);
+
+        /* If an expected hash is given, the file may already exist in
+           the store. */
+        Hash hash, expectedHash(ht);
+        Path storePath;
+        if (args.size() == 2) {
+            expectedHash = parseHash16or32(ht, args[1]);
+            storePath = makeFixedOutputPath(false, ht, expectedHash, name);
+            if (store->isValidPath(storePath))
+                hash = expectedHash;
+            else
+                storePath.clear();
+        }
+
+        if (storePath.empty()) {
+
+            auto actualUri = resolveMirrorUri(state, uri);
+
+            if (uri != actualUri)
+                printMsg(lvlInfo, format("‘%1%’ expands to ‘%2%’") % uri % actualUri);
+
+            /* Download the file. */
+            auto result = downloadFile(actualUri);
+
+            /* Copy the file to the Nix store. FIXME: if RemoteStore
+               implemented addToStoreFromDump() and downloadFile()
+               supported a sink, we could stream the download directly
+               into the Nix store. */
+            AutoDelete tmpDir(createTempDir(), true);
+            Path tmpFile = (Path) tmpDir + "/tmp";
+            writeFile(tmpFile, result.data);
+
+            /* FIXME: inefficient; addToStore() will also hash
+               this. */
+            hash = hashString(ht, result.data);
+
+            if (expectedHash != Hash(ht) && expectedHash != hash)
+                throw Error(format("hash mismatch for ‘%1%’") % uri);
+
+            storePath = store->addToStore(name, tmpFile, false, ht);
+        }
+
+        printMsg(lvlInfo, format("path is ‘%1%’") % storePath);
+
+        std::cout << printHash16or32(hash) << std::endl;
+        if (getEnv("PRINT_PATH") != "")
+            std::cout << storePath << std::endl;
+    });
+}