nix-prefetch-url: Rewrite in C++
This commit is contained in:
parent
bdc4a0b54d
commit
bec3c31608
6 changed files with 141 additions and 133 deletions
1
Makefile
1
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 \
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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'};
|
|
@ -202,6 +202,7 @@ public:
|
|||
AutoDelete(const Path & p, bool recursive = true);
|
||||
~AutoDelete();
|
||||
void cancel();
|
||||
operator Path() const { return path; }
|
||||
};
|
||||
|
||||
|
||||
|
|
7
src/nix-prefetch-url/local.mk
Normal file
7
src/nix-prefetch-url/local.mk
Normal file
|
@ -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
|
132
src/nix-prefetch-url/nix-prefetch-url.cc
Normal file
132
src/nix-prefetch-url/nix-prefetch-url.cc
Normal file
|
@ -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;
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue