aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorEelco Dolstra <edolstra@gmail.com>2019-02-12 17:57:35 +0100
committerGitHub <noreply@github.com>2019-02-12 17:57:35 +0100
commit7a7ec2229834aa294b3e09df7f514b7134287ec2 (patch)
tree4c704e904b9ed21536ceb39632e61d7c78b2917b /src
parent01d07b1e92c298f729a73705907b2987da9a4d0c (diff)
parentb30be6b450f872f8be6dc8afa28f4b030fa8d1d1 (diff)
Merge pull request #2628 from shlevy/context-introspection
Context introspection
Diffstat (limited to 'src')
-rw-r--r--src/libexpr/eval.hh3
-rw-r--r--src/libexpr/primops.cc56
-rw-r--r--src/libexpr/primops/context.cc187
3 files changed, 192 insertions, 54 deletions
diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh
index d0f298e16..9fe387891 100644
--- a/src/libexpr/eval.hh
+++ b/src/libexpr/eval.hh
@@ -316,6 +316,9 @@ private:
/* Return a string representing the type of the value `v'. */
string showType(const Value & v);
+/* Decode a context string ‘!<name>!<path>’ into a pair <path,
+ name>. */
+std::pair<string, string> decodeContext(const string & s);
/* If `path' refers to a directory, then append "/default.nix". */
Path resolveExprPath(Path path);
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index 60698f740..0da9f702f 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -687,21 +687,12 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
}
}
- /* See prim_unsafeDiscardOutputDependency. */
- else if (path.at(0) == '~')
- drv.inputSrcs.insert(string(path, 1));
-
/* Handle derivation outputs of the form ‘!<name>!<path>’. */
else if (path.at(0) == '!') {
std::pair<string, string> ctx = decodeContext(path);
drv.inputDrvs[ctx.first].insert(ctx.second);
}
- /* Handle derivation contexts returned by
- ‘builtins.storePath’. */
- else if (isDerivation(path))
- drv.inputDrvs[path] = state.store->queryDerivationOutputNames(path);
-
/* Otherwise it's a source file. */
else
drv.inputSrcs.insert(path);
@@ -1004,13 +995,8 @@ static void prim_toFile(EvalState & state, const Pos & pos, Value * * args, Valu
PathSet refs;
for (auto path : context) {
- if (path.at(0) == '=') path = string(path, 1);
- if (isDerivation(path)) {
- /* See prim_unsafeDiscardOutputDependency. */
- if (path.at(0) != '~')
- throw EvalError(format("in 'toFile': the file '%1%' cannot refer to derivation outputs, at %2%") % name % pos);
- path = string(path, 1);
- }
+ if (path.at(0) != '/')
+ throw EvalError(format("in 'toFile': the file '%1%' cannot refer to derivation outputs, at %2%") % name % pos);
refs.insert(path);
}
@@ -1794,41 +1780,6 @@ static void prim_stringLength(EvalState & state, const Pos & pos, Value * * args
}
-static void prim_unsafeDiscardStringContext(EvalState & state, const Pos & pos, Value * * args, Value & v)
-{
- PathSet context;
- string s = state.coerceToString(pos, *args[0], context);
- mkString(v, s, PathSet());
-}
-
-
-static void prim_hasContext(EvalState & state, const Pos & pos, Value * * args, Value & v)
-{
- PathSet context;
- state.forceString(*args[0], context, pos);
- mkBool(v, !context.empty());
-}
-
-
-/* Sometimes we want to pass a derivation path (i.e. pkg.drvPath) to a
- builder without causing the derivation to be built (for instance,
- in the derivation that builds NARs in nix-push, when doing
- 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)
-{
- PathSet context;
- string s = state.coerceToString(pos, *args[0], context);
-
- PathSet context2;
- for (auto & p : context)
- context2.insert(p.at(0) == '=' ? "~" + string(p, 1) : p);
-
- mkString(v, s, context2);
-}
-
-
/* Return the cryptographic hash of a string in base-16. */
static void prim_hashString(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
@@ -2299,9 +2250,6 @@ void EvalState::createBaseEnv()
addPrimOp("toString", 1, prim_toString);
addPrimOp("__substring", 3, prim_substring);
addPrimOp("__stringLength", 1, prim_stringLength);
- addPrimOp("__hasContext", 1, prim_hasContext);
- addPrimOp("__unsafeDiscardStringContext", 1, prim_unsafeDiscardStringContext);
- addPrimOp("__unsafeDiscardOutputDependency", 1, prim_unsafeDiscardOutputDependency);
addPrimOp("__hashString", 2, prim_hashString);
addPrimOp("__match", 2, prim_match);
addPrimOp("__split", 2, prim_split);
diff --git a/src/libexpr/primops/context.cc b/src/libexpr/primops/context.cc
new file mode 100644
index 000000000..2d79739ea
--- /dev/null
+++ b/src/libexpr/primops/context.cc
@@ -0,0 +1,187 @@
+#include "primops.hh"
+#include "eval-inline.hh"
+#include "derivations.hh"
+
+namespace nix {
+
+static void prim_unsafeDiscardStringContext(EvalState & state, const Pos & pos, Value * * args, Value & v)
+{
+ PathSet context;
+ string s = state.coerceToString(pos, *args[0], context);
+ mkString(v, s, PathSet());
+}
+
+static RegisterPrimOp r1("__unsafeDiscardStringContext", 1, prim_unsafeDiscardStringContext);
+
+
+static void prim_hasContext(EvalState & state, const Pos & pos, Value * * args, Value & v)
+{
+ PathSet context;
+ state.forceString(*args[0], context, pos);
+ mkBool(v, !context.empty());
+}
+
+static RegisterPrimOp r2("__hasContext", 1, prim_hasContext);
+
+
+/* Sometimes we want to pass a derivation path (i.e. pkg.drvPath) to a
+ builder without causing the derivation to be built (for instance,
+ in the derivation that builds NARs in nix-push, when doing
+ 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)
+{
+ PathSet context;
+ string s = state.coerceToString(pos, *args[0], context);
+
+ PathSet context2;
+ for (auto & p : context)
+ context2.insert(p.at(0) == '=' ? string(p, 1) : p);
+
+ mkString(v, s, context2);
+}
+
+static RegisterPrimOp r3("__unsafeDiscardOutputDependency", 1, prim_unsafeDiscardOutputDependency);
+
+
+/* Extract the context of a string as a structured Nix value.
+
+ The context is represented as an attribute set whose keys are the
+ paths in the context set and whose values are attribute sets with
+ the following keys:
+ path: True if the relevant path is in the context as a plain store
+ path (i.e. the kind of context you get when interpolating
+ a Nix path (e.g. ./.) into a string). False if missing.
+ allOutputs: True if the relevant path is a derivation and it is
+ in the context as a drv file with all of its outputs
+ (i.e. the kind of context you get when referencing
+ .drvPath of some derivation). False if missing.
+ outputs: If a non-empty list, the relevant path is a derivation
+ and the provided outputs are referenced in the context
+ (i.e. the kind of context you get when referencing
+ .outPath of some derivation). Empty list if missing.
+ 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)
+{
+ struct ContextInfo {
+ bool path = false;
+ bool allOutputs = false;
+ Strings outputs;
+ };
+ PathSet context;
+ state.forceString(*args[0], context, pos);
+ auto contextInfos = std::map<Path, ContextInfo>();
+ for (const auto & p : context) {
+ Path drv;
+ string output;
+ const Path * path = &p;
+ if (p.at(0) == '=') {
+ drv = string(p, 1);
+ path = &drv;
+ } else if (p.at(0) == '!') {
+ std::pair<string, string> ctx = decodeContext(p);
+ drv = ctx.first;
+ output = ctx.second;
+ path = &drv;
+ }
+ auto isPath = drv.empty();
+ auto isAllOutputs = (!drv.empty()) && output.empty();
+
+ auto iter = contextInfos.find(*path);
+ if (iter == contextInfos.end()) {
+ contextInfos.emplace(*path, ContextInfo{isPath, isAllOutputs, output.empty() ? Strings{} : Strings{std::move(output)}});
+ } else {
+ if (isPath)
+ iter->second.path = true;
+ else if (isAllOutputs)
+ iter->second.allOutputs = true;
+ else
+ iter->second.outputs.emplace_back(std::move(output));
+ }
+ }
+
+ state.mkAttrs(v, contextInfos.size());
+
+ auto sPath = state.symbols.create("path");
+ auto sAllOutputs = state.symbols.create("allOutputs");
+ for (const auto & info : contextInfos) {
+ auto & infoVal = *state.allocAttr(v, state.symbols.create(info.first));
+ state.mkAttrs(infoVal, 3);
+ if (info.second.path)
+ mkBool(*state.allocAttr(infoVal, sPath), true);
+ if (info.second.allOutputs)
+ mkBool(*state.allocAttr(infoVal, sAllOutputs), true);
+ if (!info.second.outputs.empty()) {
+ auto & outputsVal = *state.allocAttr(infoVal, state.sOutputs);
+ state.mkList(outputsVal, info.second.outputs.size());
+ size_t i = 0;
+ for (const auto & output : info.second.outputs) {
+ mkString(*(outputsVal.listElems()[i++] = state.allocValue()), output);
+ }
+ }
+ infoVal.attrs->sort();
+ }
+ v.attrs->sort();
+}
+
+static RegisterPrimOp r4("__getContext", 1, prim_getContext);
+
+
+/* Append the given context to a given string.
+
+ See the commentary above unsafeGetContext for details of the
+ context representation.
+*/
+static void prim_appendContext(EvalState & state, const Pos & pos, Value * * args, Value & v)
+{
+ PathSet context;
+ auto orig = state.forceString(*args[0], context, pos);
+
+ state.forceAttrs(*args[1], pos);
+
+ auto sPath = state.symbols.create("path");
+ auto sAllOutputs = state.symbols.create("allOutputs");
+ for (auto & i : *args[1]->attrs) {
+ if (!state.store->isStorePath(i.name))
+ throw EvalError("Context key '%s' is not a store path, at %s", i.name, i.pos);
+ if (!settings.readOnlyMode)
+ state.store->ensurePath(i.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);
+ }
+
+ iter = i.value->attrs->find(sAllOutputs);
+ if (iter != i.value->attrs->end()) {
+ if (state.forceBool(*iter->value, *iter->pos)) {
+ if (!isDerivation(i.name)) {
+ throw EvalError("Tried to add all-outputs context of %s, which is not a derivation, to a string, at %s", i.name, i.pos);
+ }
+ context.insert("=" + string(i.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)) {
+ throw EvalError("Tried to add derivation output context of %s, which is not a derivation, to a string, at %s", i.name, i.pos);
+ }
+ for (unsigned int n = 0; n < iter->value->listSize(); ++n) {
+ auto name = state.forceStringNoCtx(*iter->value->listElems()[n], *iter->pos);
+ context.insert("!" + name + "!" + string(i.name));
+ }
+ }
+ }
+
+ mkString(v, orig, context);
+}
+
+static RegisterPrimOp r5("__appendContext", 2, prim_appendContext);
+
+}