2015-10-01 17:47:43 +03:00
|
|
|
|
#include "hash.hh"
|
|
|
|
|
#include "shared.hh"
|
|
|
|
|
#include "download.hh"
|
|
|
|
|
#include "store-api.hh"
|
|
|
|
|
#include "eval.hh"
|
|
|
|
|
#include "eval-inline.hh"
|
2017-10-24 13:45:11 +03:00
|
|
|
|
#include "common-eval-args.hh"
|
2015-10-01 19:07:56 +03:00
|
|
|
|
#include "attr-path.hh"
|
2018-10-20 09:11:22 +03:00
|
|
|
|
#include "finally.hh"
|
2020-03-30 15:29:29 +03:00
|
|
|
|
#include "../nix/legacy.hh"
|
|
|
|
|
#include "../nix/progress-bar.hh"
|
2019-09-11 16:25:43 +03:00
|
|
|
|
#include "tarfile.hh"
|
2015-10-01 17:47:43 +03:00
|
|
|
|
|
|
|
|
|
#include <iostream>
|
|
|
|
|
|
2018-07-12 19:44:37 +03:00
|
|
|
|
#include <sys/types.h>
|
|
|
|
|
#include <sys/stat.h>
|
|
|
|
|
#include <fcntl.h>
|
|
|
|
|
|
2015-10-01 17:47:43 +03:00
|
|
|
|
using namespace nix;
|
|
|
|
|
|
|
|
|
|
|
2016-11-26 01:37:43 +02:00
|
|
|
|
/* If ‘uri’ starts with ‘mirror://’, then resolve it using the list of
|
2015-10-01 17:47:43 +03:00
|
|
|
|
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())
|
2017-07-30 14:27:57 +03:00
|
|
|
|
throw Error(format("unknown mirror name '%1%'") % mirrorName);
|
2015-10-01 17:47:43 +03:00
|
|
|
|
state.forceList(*mirrorList->value);
|
|
|
|
|
|
|
|
|
|
if (mirrorList->value->listSize() < 1)
|
2017-07-30 14:27:57 +03:00
|
|
|
|
throw Error(format("mirror URI '%1%' did not expand to anything") % uri);
|
2015-10-01 17:47:43 +03:00
|
|
|
|
|
|
|
|
|
string mirror = state.forceString(*mirrorList->value->listElems()[0]);
|
|
|
|
|
return mirror + (hasSuffix(mirror, "/") ? "" : "/") + string(s, p + 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2018-10-26 12:35:46 +03:00
|
|
|
|
static int _main(int argc, char * * argv)
|
2015-10-01 17:47:43 +03:00
|
|
|
|
{
|
2018-10-26 12:35:46 +03:00
|
|
|
|
{
|
2015-10-01 17:47:43 +03:00
|
|
|
|
HashType ht = htSHA256;
|
|
|
|
|
std::vector<string> args;
|
2020-01-05 01:41:18 +02:00
|
|
|
|
bool printPath = getEnv("PRINT_PATH") == "1";
|
2015-10-01 19:07:56 +03:00
|
|
|
|
bool fromExpr = false;
|
|
|
|
|
string attrPath;
|
2015-10-07 15:40:10 +03:00
|
|
|
|
bool unpack = false;
|
2015-10-07 15:47:39 +03:00
|
|
|
|
string name;
|
2015-10-01 17:47:43 +03:00
|
|
|
|
|
2017-10-24 13:45:11 +03:00
|
|
|
|
struct MyArgs : LegacyArgs, MixEvalArgs
|
|
|
|
|
{
|
|
|
|
|
using LegacyArgs::LegacyArgs;
|
|
|
|
|
};
|
|
|
|
|
|
2019-12-05 20:11:09 +02:00
|
|
|
|
MyArgs myArgs(std::string(baseNameOf(argv[0])), [&](Strings::iterator & arg, const Strings::iterator & end) {
|
2015-10-01 17:47:43 +03:00
|
|
|
|
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)
|
2017-07-30 14:27:57 +03:00
|
|
|
|
throw UsageError(format("unknown hash type '%1%'") % s);
|
2015-10-01 17:47:43 +03:00
|
|
|
|
}
|
2015-10-01 17:53:07 +03:00
|
|
|
|
else if (*arg == "--print-path")
|
|
|
|
|
printPath = true;
|
2015-10-01 19:07:56 +03:00
|
|
|
|
else if (*arg == "--attr" || *arg == "-A") {
|
|
|
|
|
fromExpr = true;
|
|
|
|
|
attrPath = getArg(*arg, arg, end);
|
|
|
|
|
}
|
2015-10-07 15:40:10 +03:00
|
|
|
|
else if (*arg == "--unpack")
|
|
|
|
|
unpack = true;
|
2015-10-07 15:55:33 +03:00
|
|
|
|
else if (*arg == "--name")
|
|
|
|
|
name = getArg(*arg, arg, end);
|
2015-10-01 17:47:43 +03:00
|
|
|
|
else if (*arg != "" && arg->at(0) == '-')
|
|
|
|
|
return false;
|
|
|
|
|
else
|
|
|
|
|
args.push_back(*arg);
|
|
|
|
|
return true;
|
|
|
|
|
});
|
|
|
|
|
|
2017-10-24 13:45:11 +03:00
|
|
|
|
myArgs.parseCmdline(argvToStrings(argc, argv));
|
|
|
|
|
|
2018-02-08 18:26:18 +02:00
|
|
|
|
initPlugins();
|
|
|
|
|
|
2015-10-01 19:07:56 +03:00
|
|
|
|
if (args.size() > 2)
|
|
|
|
|
throw UsageError("too many arguments");
|
2015-10-01 17:47:43 +03:00
|
|
|
|
|
2018-10-20 09:11:22 +03:00
|
|
|
|
Finally f([]() { stopProgressBar(); });
|
|
|
|
|
|
|
|
|
|
if (isatty(STDERR_FILENO))
|
|
|
|
|
startProgressBar();
|
|
|
|
|
|
Eliminate the "store" global variable
Also, move a few free-standing functions into StoreAPI and Derivation.
Also, introduce a non-nullable smart pointer, ref<T>, which is just a
wrapper around std::shared_ptr ensuring that the pointer is never
null. (For reference-counted values, this is better than passing a
"T&", because the latter doesn't maintain the refcount. Usually, the
caller will have a shared_ptr keeping the value alive, but that's not
always the case, e.g., when passing a reference to a std::thread via
std::bind.)
2016-02-04 15:28:26 +02:00
|
|
|
|
auto store = openStore();
|
2018-06-12 18:26:36 +03:00
|
|
|
|
auto state = std::make_unique<EvalState>(myArgs.searchPath, store);
|
2015-10-01 17:47:43 +03:00
|
|
|
|
|
2018-06-12 18:26:36 +03:00
|
|
|
|
Bindings & autoArgs = *myArgs.getAutoArgs(*state);
|
2015-10-01 19:07:56 +03:00
|
|
|
|
|
|
|
|
|
/* If -A is given, get the URI from the specified Nix
|
|
|
|
|
expression. */
|
|
|
|
|
string uri;
|
|
|
|
|
if (!fromExpr) {
|
|
|
|
|
if (args.empty())
|
|
|
|
|
throw UsageError("you must specify a URI");
|
|
|
|
|
uri = args[0];
|
|
|
|
|
} else {
|
2018-06-12 18:26:36 +03:00
|
|
|
|
Path path = resolveExprPath(lookupFileArg(*state, args.empty() ? "." : args[0]));
|
2015-10-01 19:07:56 +03:00
|
|
|
|
Value vRoot;
|
2018-06-12 18:26:36 +03:00
|
|
|
|
state->evalFile(path, vRoot);
|
2020-02-07 15:08:24 +02:00
|
|
|
|
Value & v(*findAlongAttrPath(*state, attrPath, autoArgs, vRoot).first);
|
2018-06-12 18:26:36 +03:00
|
|
|
|
state->forceAttrs(v);
|
2015-10-07 15:40:10 +03:00
|
|
|
|
|
|
|
|
|
/* Extract the URI. */
|
2018-06-12 18:26:36 +03:00
|
|
|
|
auto attr = v.attrs->find(state->symbols.create("urls"));
|
2015-10-07 15:40:10 +03:00
|
|
|
|
if (attr == v.attrs->end())
|
2017-07-30 14:27:57 +03:00
|
|
|
|
throw Error("attribute set does not contain a 'urls' attribute");
|
2018-06-12 18:26:36 +03:00
|
|
|
|
state->forceList(*attr->value);
|
2015-10-07 15:40:10 +03:00
|
|
|
|
if (attr->value->listSize() < 1)
|
2017-07-30 14:27:57 +03:00
|
|
|
|
throw Error("'urls' list is empty");
|
2018-06-12 18:26:36 +03:00
|
|
|
|
uri = state->forceString(*attr->value->listElems()[0]);
|
2015-10-07 15:40:10 +03:00
|
|
|
|
|
|
|
|
|
/* Extract the hash mode. */
|
2018-06-12 18:26:36 +03:00
|
|
|
|
attr = v.attrs->find(state->symbols.create("outputHashMode"));
|
2015-10-07 15:40:10 +03:00
|
|
|
|
if (attr == v.attrs->end())
|
2016-09-21 17:11:01 +03:00
|
|
|
|
printInfo("warning: this does not look like a fetchurl call");
|
2015-10-07 15:40:10 +03:00
|
|
|
|
else
|
2018-06-12 18:26:36 +03:00
|
|
|
|
unpack = state->forceString(*attr->value) == "recursive";
|
2015-10-07 15:47:39 +03:00
|
|
|
|
|
|
|
|
|
/* Extract the name. */
|
2015-10-07 15:55:33 +03:00
|
|
|
|
if (name.empty()) {
|
2018-06-12 18:26:36 +03:00
|
|
|
|
attr = v.attrs->find(state->symbols.create("name"));
|
2015-10-07 15:55:33 +03:00
|
|
|
|
if (attr != v.attrs->end())
|
2018-06-12 18:26:36 +03:00
|
|
|
|
name = state->forceString(*attr->value);
|
2015-10-07 15:55:33 +03:00
|
|
|
|
}
|
2015-10-01 19:07:56 +03:00
|
|
|
|
}
|
|
|
|
|
|
2015-10-01 17:47:43 +03:00
|
|
|
|
/* Figure out a name in the Nix store. */
|
2015-10-07 15:47:39 +03:00
|
|
|
|
if (name.empty())
|
|
|
|
|
name = baseNameOf(uri);
|
2015-10-01 17:47:43 +03:00
|
|
|
|
if (name.empty())
|
2017-07-30 14:27:57 +03:00
|
|
|
|
throw Error(format("cannot figure out file name for '%1%'") % uri);
|
2015-10-01 17:47:43 +03:00
|
|
|
|
|
|
|
|
|
/* If an expected hash is given, the file may already exist in
|
|
|
|
|
the store. */
|
|
|
|
|
Hash hash, expectedHash(ht);
|
2019-12-05 20:11:09 +02:00
|
|
|
|
std::optional<StorePath> storePath;
|
2015-10-01 17:47:43 +03:00
|
|
|
|
if (args.size() == 2) {
|
2017-07-04 15:47:59 +03:00
|
|
|
|
expectedHash = Hash(args[1], ht);
|
2016-07-26 22:25:52 +03:00
|
|
|
|
storePath = store->makeFixedOutputPath(unpack, expectedHash, name);
|
2019-12-05 20:11:09 +02:00
|
|
|
|
if (store->isValidPath(*storePath))
|
2015-10-01 17:47:43 +03:00
|
|
|
|
hash = expectedHash;
|
|
|
|
|
else
|
2019-12-05 20:11:09 +02:00
|
|
|
|
storePath.reset();
|
2015-10-01 17:47:43 +03:00
|
|
|
|
}
|
|
|
|
|
|
2019-12-05 20:11:09 +02:00
|
|
|
|
if (!storePath) {
|
2015-10-01 17:47:43 +03:00
|
|
|
|
|
2018-06-12 18:26:36 +03:00
|
|
|
|
auto actualUri = resolveMirrorUri(*state, uri);
|
2015-10-01 17:47:43 +03:00
|
|
|
|
|
|
|
|
|
AutoDelete tmpDir(createTempDir(), true);
|
|
|
|
|
Path tmpFile = (Path) tmpDir + "/tmp";
|
2018-07-12 19:44:37 +03:00
|
|
|
|
|
|
|
|
|
/* Download the file. */
|
|
|
|
|
{
|
|
|
|
|
AutoCloseFD fd = open(tmpFile.c_str(), O_WRONLY | O_CREAT | O_EXCL, 0600);
|
|
|
|
|
if (!fd) throw SysError("creating temporary file '%s'", tmpFile);
|
|
|
|
|
|
|
|
|
|
FdSink sink(fd.get());
|
|
|
|
|
|
2020-04-06 14:30:45 +03:00
|
|
|
|
DataTransferRequest req(actualUri);
|
2018-07-12 19:44:37 +03:00
|
|
|
|
req.decompress = false;
|
|
|
|
|
getDownloader()->download(std::move(req), sink);
|
|
|
|
|
}
|
2015-10-01 17:47:43 +03:00
|
|
|
|
|
2015-10-07 15:40:10 +03:00
|
|
|
|
/* Optionally unpack the file. */
|
|
|
|
|
if (unpack) {
|
2016-09-21 17:11:01 +03:00
|
|
|
|
printInfo("unpacking...");
|
2015-10-07 15:40:10 +03:00
|
|
|
|
Path unpacked = (Path) tmpDir + "/unpacked";
|
|
|
|
|
createDirs(unpacked);
|
2019-12-07 17:35:14 +02:00
|
|
|
|
unpackTarfile(tmpFile, unpacked);
|
2015-10-07 15:40:10 +03:00
|
|
|
|
|
|
|
|
|
/* If the archive unpacks to a single file/directory, then use
|
|
|
|
|
that as the top-level. */
|
|
|
|
|
auto entries = readDirectory(unpacked);
|
|
|
|
|
if (entries.size() == 1)
|
|
|
|
|
tmpFile = unpacked + "/" + entries[0].name;
|
|
|
|
|
else
|
|
|
|
|
tmpFile = unpacked;
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-01 17:47:43 +03:00
|
|
|
|
/* FIXME: inefficient; addToStore() will also hash
|
|
|
|
|
this. */
|
2018-07-12 19:44:37 +03:00
|
|
|
|
hash = unpack ? hashPath(ht, tmpFile).first : hashFile(ht, tmpFile);
|
2015-10-01 17:47:43 +03:00
|
|
|
|
|
|
|
|
|
if (expectedHash != Hash(ht) && expectedHash != hash)
|
2017-07-30 14:27:57 +03:00
|
|
|
|
throw Error(format("hash mismatch for '%1%'") % uri);
|
2015-10-01 17:47:43 +03:00
|
|
|
|
|
2015-10-07 15:40:10 +03:00
|
|
|
|
/* 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. */
|
|
|
|
|
storePath = store->addToStore(name, tmpFile, unpack, ht);
|
|
|
|
|
|
2019-12-05 20:11:09 +02:00
|
|
|
|
assert(*storePath == store->makeFixedOutputPath(unpack, hash, name));
|
2015-10-01 17:47:43 +03:00
|
|
|
|
}
|
|
|
|
|
|
2018-11-09 11:34:12 +02:00
|
|
|
|
stopProgressBar();
|
|
|
|
|
|
2015-10-01 17:53:07 +03:00
|
|
|
|
if (!printPath)
|
2019-12-05 20:11:09 +02:00
|
|
|
|
printInfo("path is '%s'", store->printStorePath(*storePath));
|
2015-10-01 17:47:43 +03:00
|
|
|
|
|
|
|
|
|
std::cout << printHash16or32(hash) << std::endl;
|
2015-10-01 17:53:07 +03:00
|
|
|
|
if (printPath)
|
2019-12-05 20:11:09 +02:00
|
|
|
|
std::cout << store->printStorePath(*storePath) << std::endl;
|
2018-10-26 12:35:46 +03:00
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
2015-10-01 17:47:43 +03:00
|
|
|
|
}
|
2018-10-26 12:35:46 +03:00
|
|
|
|
|
|
|
|
|
static RegisterLegacyCommand s1("nix-prefetch-url", _main);
|