aboutsummaryrefslogtreecommitdiff
path: root/src/libexpr/primops
diff options
context:
space:
mode:
Diffstat (limited to 'src/libexpr/primops')
-rw-r--r--src/libexpr/primops/context.cc52
-rw-r--r--src/libexpr/primops/fetchClosure.cc163
-rw-r--r--src/libexpr/primops/fetchMercurial.cc16
-rw-r--r--src/libexpr/primops/fetchTree.cc52
-rw-r--r--src/libexpr/primops/fromTOML.cc4
5 files changed, 226 insertions, 61 deletions
diff --git a/src/libexpr/primops/context.cc b/src/libexpr/primops/context.cc
index 3701bd442..979136984 100644
--- a/src/libexpr/primops/context.cc
+++ b/src/libexpr/primops/context.cc
@@ -1,10 +1,11 @@
#include "primops.hh"
#include "eval-inline.hh"
+#include "derivations.hh"
#include "store-api.hh"
namespace nix {
-static void prim_unsafeDiscardStringContext(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_unsafeDiscardStringContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
PathSet context;
auto s = state.coerceToString(pos, *args[0], context);
@@ -14,7 +15,7 @@ static void prim_unsafeDiscardStringContext(EvalState & state, const Pos & pos,
static RegisterPrimOp primop_unsafeDiscardStringContext("__unsafeDiscardStringContext", 1, prim_unsafeDiscardStringContext);
-static void prim_hasContext(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_hasContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
PathSet context;
state.forceString(*args[0], context, pos);
@@ -30,7 +31,7 @@ static RegisterPrimOp primop_hasContext("__hasContext", 1, prim_hasContext);
source-only deployment). This primop marks the string context so
that builtins.derivation adds the path to drv.inputSrcs rather than
drv.inputDrvs. */
-static void prim_unsafeDiscardOutputDependency(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_unsafeDiscardOutputDependency(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
PathSet context;
auto s = state.coerceToString(pos, *args[0], context);
@@ -64,7 +65,7 @@ static RegisterPrimOp primop_unsafeDiscardOutputDependency("__unsafeDiscardOutpu
Note that for a given path any combination of the above attributes
may be present.
*/
-static void prim_getContext(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_getContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
struct ContextInfo {
bool path = false;
@@ -82,8 +83,8 @@ static void prim_getContext(EvalState & state, const Pos & pos, Value * * args,
drv = std::string(p, 1);
path = &drv;
} else if (p.at(0) == '!') {
- std::pair<std::string, std::string> ctx = decodeContext(p);
- drv = ctx.first;
+ NixStringContextElem ctx = decodeContext(*state.store, p);
+ drv = state.store->printStorePath(ctx.first);
output = ctx.second;
path = &drv;
}
@@ -133,7 +134,7 @@ static RegisterPrimOp primop_getContext("__getContext", 1, prim_getContext);
See the commentary above unsafeGetContext for details of the
context representation.
*/
-static void prim_appendContext(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
PathSet context;
auto orig = state.forceString(*args[0], context, pos);
@@ -143,45 +144,46 @@ static void prim_appendContext(EvalState & state, const Pos & pos, Value * * arg
auto sPath = state.symbols.create("path");
auto sAllOutputs = state.symbols.create("allOutputs");
for (auto & i : *args[1]->attrs) {
- if (!state.store->isStorePath(i.name))
+ const auto & name = state.symbols[i.name];
+ if (!state.store->isStorePath(name))
throw EvalError({
- .msg = hintfmt("Context key '%s' is not a store path", i.name),
- .errPos = *i.pos
+ .msg = hintfmt("Context key '%s' is not a store path", name),
+ .errPos = state.positions[i.pos]
});
if (!settings.readOnlyMode)
- state.store->ensurePath(state.store->parseStorePath(i.name));
- state.forceAttrs(*i.value, *i.pos);
+ state.store->ensurePath(state.store->parseStorePath(name));
+ state.forceAttrs(*i.value, i.pos);
auto iter = i.value->attrs->find(sPath);
if (iter != i.value->attrs->end()) {
- if (state.forceBool(*iter->value, *iter->pos))
- context.insert(i.name);
+ if (state.forceBool(*iter->value, iter->pos))
+ context.emplace(name);
}
iter = i.value->attrs->find(sAllOutputs);
if (iter != i.value->attrs->end()) {
- if (state.forceBool(*iter->value, *iter->pos)) {
- if (!isDerivation(i.name)) {
+ if (state.forceBool(*iter->value, iter->pos)) {
+ if (!isDerivation(name)) {
throw EvalError({
- .msg = hintfmt("Tried to add all-outputs context of %s, which is not a derivation, to a string", i.name),
- .errPos = *i.pos
+ .msg = hintfmt("Tried to add all-outputs context of %s, which is not a derivation, to a string", name),
+ .errPos = state.positions[i.pos]
});
}
- context.insert("=" + std::string(i.name));
+ context.insert(concatStrings("=", name));
}
}
iter = i.value->attrs->find(state.sOutputs);
if (iter != i.value->attrs->end()) {
- state.forceList(*iter->value, *iter->pos);
- if (iter->value->listSize() && !isDerivation(i.name)) {
+ state.forceList(*iter->value, iter->pos);
+ if (iter->value->listSize() && !isDerivation(name)) {
throw EvalError({
- .msg = hintfmt("Tried to add derivation output context of %s, which is not a derivation, to a string", i.name),
- .errPos = *i.pos
+ .msg = hintfmt("Tried to add derivation output context of %s, which is not a derivation, to a string", name),
+ .errPos = state.positions[i.pos]
});
}
for (auto elem : iter->value->listItems()) {
- auto name = state.forceStringNoCtx(*elem, *iter->pos);
- context.insert(concatStrings("!", name, "!", i.name));
+ auto outputName = state.forceStringNoCtx(*elem, iter->pos);
+ context.insert(concatStrings("!", outputName, "!", name));
}
}
}
diff --git a/src/libexpr/primops/fetchClosure.cc b/src/libexpr/primops/fetchClosure.cc
new file mode 100644
index 000000000..662c9652e
--- /dev/null
+++ b/src/libexpr/primops/fetchClosure.cc
@@ -0,0 +1,163 @@
+#include "primops.hh"
+#include "store-api.hh"
+#include "make-content-addressed.hh"
+#include "url.hh"
+
+namespace nix {
+
+static void prim_fetchClosure(EvalState & state, const PosIdx 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) {
+ const auto & attrName = state.symbols[attr.name];
+
+ if (attrName == "fromPath") {
+ PathSet context;
+ fromPath = state.coerceToStorePath(attr.pos, *attr.value, context);
+ }
+
+ else if (attrName == "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 (attrName == "fromStore")
+ fromStoreUrl = state.forceStringNoCtx(*attr.value, attr.pos);
+
+ else
+ throw Error({
+ .msg = hintfmt("attribute '%s' isn't supported in call to 'fetchClosure'", attrName),
+ .errPos = state.positions[pos]
+ });
+ }
+
+ if (!fromPath)
+ throw Error({
+ .msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "fromPath"),
+ .errPos = state.positions[pos]
+ });
+
+ if (!fromStoreUrl)
+ throw Error({
+ .msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "fromStore"),
+ .errPos = state.positions[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 = state.positions[pos]
+ });
+
+ if (!parsedURL.query.empty())
+ throw Error({
+ .msg = hintfmt("'fetchClosure' does not support URL query parameters (in '%s')", *fromStoreUrl),
+ .errPos = state.positions[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 = state.positions[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 = state.positions[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 = state.positions[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 b7f715859..249c0934e 100644
--- a/src/libexpr/primops/fetchMercurial.cc
+++ b/src/libexpr/primops/fetchMercurial.cc
@@ -7,7 +7,7 @@
namespace nix {
-static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
std::string url;
std::optional<Hash> rev;
@@ -22,31 +22,31 @@ static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * ar
state.forceAttrs(*args[0], pos);
for (auto & attr : *args[0]->attrs) {
- std::string_view n(attr.name);
+ std::string_view n(state.symbols[attr.name]);
if (n == "url")
- url = state.coerceToString(*attr.pos, *attr.value, context, false, false).toOwned();
+ url = state.coerceToString(attr.pos, *attr.value, context, false, false).toOwned();
else if (n == "rev") {
// Ugly: unlike fetchGit, here the "rev" attribute can
// be both a revision or a branch/tag name.
- auto value = state.forceStringNoCtx(*attr.value, *attr.pos);
+ auto value = state.forceStringNoCtx(*attr.value, attr.pos);
if (std::regex_match(value.begin(), value.end(), revRegex))
rev = Hash::parseAny(value, htSHA1);
else
ref = value;
}
else if (n == "name")
- name = state.forceStringNoCtx(*attr.value, *attr.pos);
+ name = state.forceStringNoCtx(*attr.value, attr.pos);
else
throw EvalError({
- .msg = hintfmt("unsupported argument '%s' to 'fetchMercurial'", attr.name),
- .errPos = *attr.pos
+ .msg = hintfmt("unsupported argument '%s' to 'fetchMercurial'", state.symbols[attr.name]),
+ .errPos = state.positions[attr.pos]
});
}
if (url.empty())
throw EvalError({
.msg = hintfmt("'url' argument required"),
- .errPos = pos
+ .errPos = state.positions[pos]
});
} else
diff --git a/src/libexpr/primops/fetchTree.cc b/src/libexpr/primops/fetchTree.cc
index 9c2da2178..d7c3c9918 100644
--- a/src/libexpr/primops/fetchTree.cc
+++ b/src/libexpr/primops/fetchTree.cc
@@ -90,7 +90,7 @@ struct FetchTreeParams {
static void fetchTree(
EvalState & state,
- const Pos & pos,
+ const PosIdx pos,
Value * * args,
Value & v,
std::optional<std::string> type,
@@ -110,43 +110,43 @@ static void fetchTree(
if (type)
throw Error({
.msg = hintfmt("unexpected attribute 'type'"),
- .errPos = pos
+ .errPos = state.positions[pos]
});
- type = state.forceStringNoCtx(*aType->value, *aType->pos);
+ type = state.forceStringNoCtx(*aType->value, aType->pos);
} else if (!type)
throw Error({
.msg = hintfmt("attribute 'type' is missing in call to 'fetchTree'"),
- .errPos = pos
+ .errPos = state.positions[pos]
});
attrs.emplace("type", type.value());
for (auto & attr : *args[0]->attrs) {
if (attr.name == state.sType) continue;
- state.forceValue(*attr.value, *attr.pos);
+ state.forceValue(*attr.value, attr.pos);
if (attr.value->type() == nPath || attr.value->type() == nString) {
- auto s = state.coerceToString(*attr.pos, *attr.value, context, false, false).toOwned();
- attrs.emplace(attr.name,
- attr.name == "url"
+ auto s = state.coerceToString(attr.pos, *attr.value, context, false, false).toOwned();
+ attrs.emplace(state.symbols[attr.name],
+ state.symbols[attr.name] == "url"
? type == "git"
? fixURIForGit(s, state)
: fixURI(s, state)
: s);
}
else if (attr.value->type() == nBool)
- attrs.emplace(attr.name, Explicit<bool>{attr.value->boolean});
+ attrs.emplace(state.symbols[attr.name], Explicit<bool>{attr.value->boolean});
else if (attr.value->type() == nInt)
- attrs.emplace(attr.name, uint64_t(attr.value->integer));
+ attrs.emplace(state.symbols[attr.name], uint64_t(attr.value->integer));
else
throw TypeError("fetchTree argument '%s' is %s while a string, Boolean or integer is expected",
- attr.name, showType(*attr.value));
+ state.symbols[attr.name], showType(*attr.value));
}
if (!params.allowNameArgument)
if (auto nameIter = attrs.find("name"); nameIter != attrs.end())
throw Error({
- .msg = hintfmt("attribute 'name' isn’t supported in call to 'fetchTree'"),
- .errPos = pos
+ .msg = hintfmt("attribute 'name' isn't supported in call to 'fetchTree'"),
+ .errPos = state.positions[pos]
});
input = fetchers::Input::fromAttrs(std::move(attrs));
@@ -167,7 +167,7 @@ static void fetchTree(
input = lookupInRegistries(state.store, input).first;
if (evalSettings.pureEval && !input.isLocked())
- throw Error("in pure evaluation mode, 'fetchTree' requires a locked input, at %s", pos);
+ throw Error("in pure evaluation mode, 'fetchTree' requires a locked input, at %s", state.positions[pos]);
auto [tree, input2] = input.fetch(state.store);
@@ -176,7 +176,7 @@ static void fetchTree(
emitTreeAttrs(state, tree, input2, v, params.emptyRevFallback, false);
}
-static void prim_fetchTree(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_fetchTree(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
settings.requireExperimentalFeature(Xp::Flakes);
fetchTree(state, pos, args, v, std::nullopt, FetchTreeParams { .allowNameArgument = false });
@@ -185,7 +185,7 @@ static void prim_fetchTree(EvalState & state, const Pos & pos, Value * * args, V
// FIXME: document
static RegisterPrimOp primop_fetchTree("fetchTree", 1, prim_fetchTree);
-static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v,
+static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v,
const std::string & who, bool unpack, std::string name)
{
std::optional<std::string> url;
@@ -198,24 +198,24 @@ static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v,
state.forceAttrs(*args[0], pos);
for (auto & attr : *args[0]->attrs) {
- std::string n(attr.name);
+ std::string_view n(state.symbols[attr.name]);
if (n == "url")
- url = state.forceStringNoCtx(*attr.value, *attr.pos);
+ url = state.forceStringNoCtx(*attr.value, attr.pos);
else if (n == "sha256")
- expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, *attr.pos), htSHA256);
+ expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, attr.pos), htSHA256);
else if (n == "name")
- name = state.forceStringNoCtx(*attr.value, *attr.pos);
+ name = state.forceStringNoCtx(*attr.value, attr.pos);
else
throw EvalError({
- .msg = hintfmt("unsupported argument '%s' to '%s'", attr.name, who),
- .errPos = *attr.pos
+ .msg = hintfmt("unsupported argument '%s' to '%s'", n, who),
+ .errPos = state.positions[attr.pos]
});
}
if (!url)
throw EvalError({
.msg = hintfmt("'url' argument required"),
- .errPos = pos
+ .errPos = state.positions[pos]
});
} else
url = state.forceStringNoCtx(*args[0], pos);
@@ -262,7 +262,7 @@ static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v,
state.allowAndSetStorePathString(storePath, v);
}
-static void prim_fetchurl(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_fetchurl(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
fetch(state, pos, args, v, "fetchurl", false, "");
}
@@ -278,7 +278,7 @@ static RegisterPrimOp primop_fetchurl({
.fun = prim_fetchurl,
});
-static void prim_fetchTarball(EvalState & state, const Pos & pos, Value * * args, Value & v)
+static void prim_fetchTarball(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
fetch(state, pos, args, v, "fetchTarball", true, "source");
}
@@ -329,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 PosIdx 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 dd4280030..9753e2ac9 100644
--- a/src/libexpr/primops/fromTOML.cc
+++ b/src/libexpr/primops/fromTOML.cc
@@ -5,7 +5,7 @@
namespace nix {
-static void prim_fromTOML(EvalState & state, const Pos & pos, Value * * args, Value & val)
+static void prim_fromTOML(EvalState & state, const PosIdx pos, Value * * args, Value & val)
{
auto toml = state.forceStringNoCtx(*args[0], pos);
@@ -73,7 +73,7 @@ static void prim_fromTOML(EvalState & state, const Pos & pos, Value * * args, Va
} catch (std::exception & e) { // TODO: toml::syntax_error
throw EvalError({
.msg = hintfmt("while parsing a TOML string: %s", e.what()),
- .errPos = pos
+ .errPos = state.positions[pos]
});
}
}