diff options
Diffstat (limited to 'src/libexpr/primops')
-rw-r--r-- | src/libexpr/primops/context.cc | 13 | ||||
-rw-r--r-- | src/libexpr/primops/fetchClosure.cc | 161 | ||||
-rw-r--r-- | src/libexpr/primops/fetchMercurial.cc | 2 | ||||
-rw-r--r-- | src/libexpr/primops/fetchTree.cc | 32 | ||||
-rw-r--r-- | src/libexpr/primops/fromTOML.cc | 2 |
5 files changed, 192 insertions, 18 deletions
diff --git a/src/libexpr/primops/context.cc b/src/libexpr/primops/context.cc index 654251c23..cc74c7f58 100644 --- a/src/libexpr/primops/context.cc +++ b/src/libexpr/primops/context.cc @@ -1,5 +1,6 @@ #include "primops.hh" #include "eval-inline.hh" +#include "derivations.hh" #include "store-api.hh" namespace nix { @@ -37,7 +38,7 @@ static void prim_unsafeDiscardOutputDependency(EvalState & state, const Pos & po PathSet context2; for (auto & p : context) - context2.insert(p.at(0) == '=' ? string(p, 1) : p); + context2.insert(p.at(0) == '=' ? std::string(p, 1) : p); v.mkString(*s, context2); } @@ -76,14 +77,14 @@ static void prim_getContext(EvalState & state, const Pos & pos, Value * * args, auto contextInfos = std::map<Path, ContextInfo>(); for (const auto & p : context) { Path drv; - string output; + std::string output; const Path * path = &p; if (p.at(0) == '=') { - drv = string(p, 1); + drv = std::string(p, 1); path = &drv; } else if (p.at(0) == '!') { - std::pair<string, string> ctx = decodeContext(p); - drv = ctx.first; + NixStringContextElem ctx = decodeContext(*state.store, p); + drv = state.store->printStorePath(ctx.first); output = ctx.second; path = &drv; } @@ -166,7 +167,7 @@ static void prim_appendContext(EvalState & state, const Pos & pos, Value * * arg .errPos = *i.pos }); } - context.insert("=" + string(i.name)); + context.insert("=" + std::string(i.name)); } } diff --git a/src/libexpr/primops/fetchClosure.cc b/src/libexpr/primops/fetchClosure.cc new file mode 100644 index 000000000..821eba698 --- /dev/null +++ b/src/libexpr/primops/fetchClosure.cc @@ -0,0 +1,161 @@ +#include "primops.hh" +#include "store-api.hh" +#include "make-content-addressed.hh" +#include "url.hh" + +namespace nix { + +static void prim_fetchClosure(EvalState & state, const Pos & pos, Value * * args, Value & v) +{ + state.forceAttrs(*args[0], pos); + + std::optional<std::string> fromStoreUrl; + std::optional<StorePath> fromPath; + bool toCA = false; + std::optional<StorePath> toPath; + + for (auto & attr : *args[0]->attrs) { + if (attr.name == "fromPath") { + PathSet context; + fromPath = state.coerceToStorePath(*attr.pos, *attr.value, context); + } + + else if (attr.name == "toPath") { + state.forceValue(*attr.value, *attr.pos); + toCA = true; + if (attr.value->type() != nString || attr.value->string.s != std::string("")) { + PathSet context; + toPath = state.coerceToStorePath(*attr.pos, *attr.value, context); + } + } + + else if (attr.name == "fromStore") + fromStoreUrl = state.forceStringNoCtx(*attr.value, *attr.pos); + + else + throw Error({ + .msg = hintfmt("attribute '%s' isn't supported in call to 'fetchClosure'", attr.name), + .errPos = pos + }); + } + + if (!fromPath) + throw Error({ + .msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "fromPath"), + .errPos = pos + }); + + if (!fromStoreUrl) + throw Error({ + .msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "fromStore"), + .errPos = pos + }); + + auto parsedURL = parseURL(*fromStoreUrl); + + if (parsedURL.scheme != "http" && + parsedURL.scheme != "https" && + !(getEnv("_NIX_IN_TEST").has_value() && parsedURL.scheme == "file")) + throw Error({ + .msg = hintfmt("'fetchClosure' only supports http:// and https:// stores"), + .errPos = pos + }); + + if (!parsedURL.query.empty()) + throw Error({ + .msg = hintfmt("'fetchClosure' does not support URL query parameters (in '%s')", *fromStoreUrl), + .errPos = pos + }); + + auto fromStore = openStore(parsedURL.to_string()); + + if (toCA) { + if (!toPath || !state.store->isValidPath(*toPath)) { + auto remappings = makeContentAddressed(*fromStore, *state.store, { *fromPath }); + auto i = remappings.find(*fromPath); + assert(i != remappings.end()); + if (toPath && *toPath != i->second) + throw Error({ + .msg = hintfmt("rewriting '%s' to content-addressed form yielded '%s', while '%s' was expected", + state.store->printStorePath(*fromPath), + state.store->printStorePath(i->second), + state.store->printStorePath(*toPath)), + .errPos = pos + }); + if (!toPath) + throw Error({ + .msg = hintfmt( + "rewriting '%s' to content-addressed form yielded '%s'; " + "please set this in the 'toPath' attribute passed to 'fetchClosure'", + state.store->printStorePath(*fromPath), + state.store->printStorePath(i->second)), + .errPos = pos + }); + } + } else { + if (!state.store->isValidPath(*fromPath)) + copyClosure(*fromStore, *state.store, RealisedPath::Set { *fromPath }); + toPath = fromPath; + } + + /* In pure mode, require a CA path. */ + if (evalSettings.pureEval) { + auto info = state.store->queryPathInfo(*toPath); + if (!info->isContentAddressed(*state.store)) + throw Error({ + .msg = hintfmt("in pure mode, 'fetchClosure' requires a content-addressed path, which '%s' isn't", + state.store->printStorePath(*toPath)), + .errPos = pos + }); + } + + auto toPathS = state.store->printStorePath(*toPath); + v.mkString(toPathS, {toPathS}); +} + +static RegisterPrimOp primop_fetchClosure({ + .name = "__fetchClosure", + .args = {"args"}, + .doc = R"( + Fetch a Nix store closure from a binary cache, rewriting it into + content-addressed form. For example, + + ```nix + builtins.fetchClosure { + fromStore = "https://cache.nixos.org"; + fromPath = /nix/store/r2jd6ygnmirm2g803mksqqjm4y39yi6i-git-2.33.1; + toPath = /nix/store/ldbhlwhh39wha58rm61bkiiwm6j7211j-git-2.33.1; + } + ``` + + fetches `/nix/store/r2jd...` from the specified binary cache, + and rewrites it into the content-addressed store path + `/nix/store/ldbh...`. + + If `fromPath` is already content-addressed, or if you are + allowing impure evaluation (`--impure`), then `toPath` may be + omitted. + + To find out the correct value for `toPath` given a `fromPath`, + you can use `nix store make-content-addressed`: + + ```console + # nix store make-content-addressed --from https://cache.nixos.org /nix/store/r2jd6ygnmirm2g803mksqqjm4y39yi6i-git-2.33.1 + rewrote '/nix/store/r2jd6ygnmirm2g803mksqqjm4y39yi6i-git-2.33.1' to '/nix/store/ldbhlwhh39wha58rm61bkiiwm6j7211j-git-2.33.1' + ``` + + This function is similar to `builtins.storePath` in that it + allows you to use a previously built store path in a Nix + expression. However, it is more reproducible because it requires + specifying a binary cache from which the path can be fetched. + Also, requiring a content-addressed final store path avoids the + need for users to configure binary cache public keys. + + This function is only available if you enable the experimental + feature `fetch-closure`. + )", + .fun = prim_fetchClosure, + .experimentalFeature = Xp::FetchClosure, +}); + +} diff --git a/src/libexpr/primops/fetchMercurial.cc b/src/libexpr/primops/fetchMercurial.cc index c4e1a7bf0..b7f715859 100644 --- a/src/libexpr/primops/fetchMercurial.cc +++ b/src/libexpr/primops/fetchMercurial.cc @@ -62,7 +62,7 @@ static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * ar fetchers::Attrs attrs; attrs.insert_or_assign("type", "hg"); attrs.insert_or_assign("url", url.find("://") != std::string::npos ? url : "file://" + url); - attrs.insert_or_assign("name", string(name)); + attrs.insert_or_assign("name", std::string(name)); if (ref) attrs.insert_or_assign("ref", *ref); if (rev) attrs.insert_or_assign("rev", rev->gitRev()); auto input = fetchers::Input::fromAttrs(std::move(attrs)); diff --git a/src/libexpr/primops/fetchTree.cc b/src/libexpr/primops/fetchTree.cc index c9fcbb8f5..bae0fb1e4 100644 --- a/src/libexpr/primops/fetchTree.cc +++ b/src/libexpr/primops/fetchTree.cc @@ -19,7 +19,7 @@ void emitTreeAttrs( bool emptyRevFallback, bool forceDirty) { - assert(input.isImmutable()); + assert(input.isLocked()); auto attrs = state.buildBindings(8); @@ -166,8 +166,8 @@ static void fetchTree( if (!evalSettings.pureEval && !input.isDirect()) input = lookupInRegistries(state.store, input).first; - if (evalSettings.pureEval && !input.isImmutable()) - state.debug_throw(EvalError("in pure evaluation mode, 'fetchTree' requires an immutable input, at %s", pos)); + if (evalSettings.pureEval && !input.isLocked()) + state.debug_throw(EvalError("in pure evaluation mode, 'fetchTree' requires a locked input, at %s", pos)); auto [tree, input2] = input.fetch(state.store); @@ -186,7 +186,7 @@ static void prim_fetchTree(EvalState & state, const Pos & pos, Value * * args, V static RegisterPrimOp primop_fetchTree("fetchTree", 1, prim_fetchTree); static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v, - const string & who, bool unpack, std::string name) + const std::string & who, bool unpack, std::string name) { std::optional<std::string> url; std::optional<Hash> expectedHash; @@ -198,7 +198,7 @@ static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v, state.forceAttrs(*args[0], pos); for (auto & attr : *args[0]->attrs) { - string n(attr.name); + std::string n(attr.name); if (n == "url") url = state.forceStringNoCtx(*attr.value, *attr.pos); else if (n == "sha256") @@ -230,6 +230,21 @@ static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v, if (evalSettings.pureEval && !expectedHash) state.debug_throw(EvalError("in pure evaluation mode, '%s' requires a 'sha256' argument", who)); + // early exit if pinned and already in the store + if (expectedHash && expectedHash->type == htSHA256) { + auto expectedPath = + unpack + ? state.store->makeFixedOutputPath(FileIngestionMethod::Recursive, *expectedHash, name, {}) + : state.store->makeFixedOutputPath(FileIngestionMethod::Flat, *expectedHash, name, {}); + + if (state.store->isValidPath(expectedPath)) { + state.allowAndSetStorePathString(expectedPath, v); + return; + } + } + + // TODO: fetching may fail, yet the path may be substitutable. + // https://github.com/NixOS/nix/issues/4313 auto storePath = unpack ? fetchers::downloadTarball(state.store, *url, name, (bool) expectedHash).first.storePath @@ -244,10 +259,7 @@ static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v, *url, expectedHash->to_string(Base32, true), hash.to_string(Base32, true))); } - state.allowPath(storePath); - - auto path = state.store->printStorePath(storePath); - v.mkString(path, PathSet({path})); + state.allowAndSetStorePathString(storePath, v); } static void prim_fetchurl(EvalState & state, const Pos & pos, Value * * args, Value & v) @@ -317,7 +329,7 @@ static RegisterPrimOp primop_fetchTarball({ .fun = prim_fetchTarball, }); -static void prim_fetchGit(EvalState &state, const Pos &pos, Value **args, Value &v) +static void prim_fetchGit(EvalState & state, const Pos & pos, Value * * args, Value & v) { fetchTree(state, pos, args, v, "git", FetchTreeParams { .emptyRevFallback = true, .allowNameArgument = true }); } diff --git a/src/libexpr/primops/fromTOML.cc b/src/libexpr/primops/fromTOML.cc index c0e858b61..dd4280030 100644 --- a/src/libexpr/primops/fromTOML.cc +++ b/src/libexpr/primops/fromTOML.cc @@ -9,7 +9,7 @@ static void prim_fromTOML(EvalState & state, const Pos & pos, Value * * args, Va { auto toml = state.forceStringNoCtx(*args[0], pos); - std::istringstream tomlStream(string{toml}); + std::istringstream tomlStream(std::string{toml}); std::function<void(Value &, toml::value)> visit; |