diff options
Diffstat (limited to 'third_party/nix/src/nix-prefetch-url')
-rw-r--r-- | third_party/nix/src/nix-prefetch-url/nix-prefetch-url.cc | 253 |
1 files changed, 253 insertions, 0 deletions
diff --git a/third_party/nix/src/nix-prefetch-url/nix-prefetch-url.cc b/third_party/nix/src/nix-prefetch-url/nix-prefetch-url.cc new file mode 100644 index 000000000000..b61a38a7f193 --- /dev/null +++ b/third_party/nix/src/nix-prefetch-url/nix-prefetch-url.cc @@ -0,0 +1,253 @@ +#include <iostream> + +#include <absl/strings/match.h> +#include <fcntl.h> +#include <glog/logging.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "libexpr/attr-path.hh" +#include "libexpr/common-eval-args.hh" +#include "libexpr/eval-inline.hh" +#include "libexpr/eval.hh" +#include "libmain/shared.hh" +#include "libstore/download.hh" +#include "libstore/store-api.hh" +#include "libutil/finally.hh" +#include "libutil/hash.hh" +#include "nix/legacy.hh" + +using namespace nix; + +/* If ‘uri’ starts with ‘mirror://’, then resolve it using the list of + mirrors defined in Nixpkgs. */ +std::string resolveMirrorUri(EvalState& state, std::string uri) { + if (std::string(uri, 0, 9) != "mirror://") { + return uri; + } + + std::string s(uri, 9); + auto p = s.find('/'); + if (p == std::string::npos) { + throw Error("invalid mirror URI"); + } + std::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->second.value); + + if (mirrorList->second.value->listSize() < 1) { + throw Error(format("mirror URI '%1%' did not expand to anything") % uri); + } + + std::string mirror = state.forceString(*(*mirrorList->second.value->list)[0]); + return mirror + (absl::EndsWith(mirror, "/") ? "" : "/") + + std::string(s, p + 1); +} + +static int _main(int argc, char** argv) { + { + HashType ht = htSHA256; + std::vector<std::string> args; + bool printPath = getEnv("PRINT_PATH").has_value(); + bool fromExpr = false; + std::string attrPath; + bool unpack = false; + std::string name; + + struct MyArgs : LegacyArgs, MixEvalArgs { + using LegacyArgs::LegacyArgs; + }; + + MyArgs myArgs(baseNameOf(argv[0]), + [&](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") { + std::string s = getArg(*arg, arg, end); + ht = parseHashType(s); + if (ht == htUnknown) { + throw UsageError(format("unknown hash type '%1%'") % s); + } + } else if (*arg == "--print-path") { + printPath = true; + } else if (*arg == "--attr" || *arg == "-A") { + fromExpr = true; + attrPath = getArg(*arg, arg, end); + } else if (*arg == "--unpack") { + unpack = true; + } else if (*arg == "--name") { + name = getArg(*arg, arg, end); + } else if (*arg != "" && arg->at(0) == '-') { + return false; + } else { + args.push_back(*arg); + } + return true; + }); + + myArgs.parseCmdline(argvToStrings(argc, argv)); + + if (args.size() > 2) { + throw UsageError("too many arguments"); + } + + auto store = openStore(); + auto state = std::make_unique<EvalState>(myArgs.searchPath, store); + + std::unique_ptr<Bindings> autoArgs = myArgs.getAutoArgs(*state); + + /* If -A is given, get the URI from the specified Nix + expression. */ + std::string uri; + if (!fromExpr) { + if (args.empty()) { + throw UsageError("you must specify a URI"); + } + uri = args[0]; + } else { + Path path = + resolveExprPath(lookupFileArg(*state, args.empty() ? "." : args[0])); + Value vRoot; + state->evalFile(path, vRoot); + Value& v(*findAlongAttrPath(*state, attrPath, autoArgs.get(), vRoot)); + state->forceAttrs(v); + + /* Extract the URI. */ + auto attr = v.attrs->find(state->symbols.Create("urls")); + if (attr == v.attrs->end()) { + throw Error("attribute set does not contain a 'urls' attribute"); + } + state->forceList(*attr->second.value); + if (attr->second.value->listSize() < 1) { + throw Error("'urls' list is empty"); + } + uri = state->forceString(*(*attr->second.value->list)[0]); + + /* Extract the hash mode. */ + attr = v.attrs->find(state->symbols.Create("outputHashMode")); + if (attr == v.attrs->end()) { + LOG(WARNING) << "this does not look like a fetchurl call"; + } else { + unpack = state->forceString(*attr->second.value) == "recursive"; + } + + /* Extract the name. */ + if (name.empty()) { + attr = v.attrs->find(state->symbols.Create("name")); + if (attr != v.attrs->end()) { + name = state->forceString(*attr->second.value); + } + } + } + + /* Figure out a name in the Nix store. */ + if (name.empty()) { + 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; + Hash expectedHash(ht); + Path storePath; + if (args.size() == 2) { + auto expectedHash_ = Hash::deserialize(args[1], ht); + expectedHash = Hash::unwrap_throw(expectedHash); + storePath = store->makeFixedOutputPath(unpack, expectedHash, name); + if (store->isValidPath(storePath)) { + hash = expectedHash; + } else { + storePath.clear(); + } + } + + if (storePath.empty()) { + auto actualUri = resolveMirrorUri(*state, uri); + + AutoDelete tmpDir(createTempDir(), true); + Path tmpFile = Path(tmpDir) + "/tmp"; + + /* 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()); + + DownloadRequest req(actualUri); + req.decompress = false; + getDownloader()->download(std::move(req), sink); + } + + /* Optionally unpack the file. */ + if (unpack) { + LOG(INFO) << "unpacking..."; + Path unpacked = Path(tmpDir) + "/unpacked"; + createDirs(unpacked); + if (absl::EndsWith(baseNameOf(uri), ".zip")) { + runProgram("unzip", true, {"-qq", tmpFile, "-d", unpacked}); + } else { + // FIXME: this requires GNU tar for decompression. + runProgram("tar", true, {"xf", tmpFile, "-C", unpacked}); + } + + /* 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; + } + } + + /* FIXME: inefficient; addToStore() will also hash + this. */ + hash = unpack ? hashPath(ht, tmpFile).first : hashFile(ht, tmpFile); + + if (expectedHash != Hash(ht) && expectedHash != hash) { + throw Error(format("hash mismatch for '%1%'") % uri); + } + + /* 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); + + assert(storePath == store->makeFixedOutputPath(unpack, hash, name)); + } + + if (!printPath) { + LOG(INFO) << "path is '" << storePath << "'"; + } + + std::cout << printHash16or32(hash) << std::endl; + if (printPath) { + std::cout << storePath << std::endl; + } + + return 0; + } +} + +static RegisterLegacyCommand s1("nix-prefetch-url", _main); |