diff --git a/Makefile b/Makefile index fe2e88a99..3a204de88 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 39e1df611..3fb47676f 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 6effbe208..000000000 --- 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 <' -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 b2fb59d6f..a05a4cb88 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 000000000..3e7735406 --- /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 000000000..bd52df1c7 --- /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 + +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 ", "."), 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 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; + }); +}