Merge pull request #2628 from shlevy/context-introspection
Context introspection
This commit is contained in:
commit
7a7ec22298
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -81,6 +81,9 @@ perl/Makefile.config
|
||||||
/tests/common.sh
|
/tests/common.sh
|
||||||
/tests/dummy
|
/tests/dummy
|
||||||
/tests/result*
|
/tests/result*
|
||||||
|
/tests/restricted-innocent
|
||||||
|
/tests/shell
|
||||||
|
/tests/shell.drv
|
||||||
|
|
||||||
# /tests/lang/
|
# /tests/lang/
|
||||||
/tests/lang/*.out
|
/tests/lang/*.out
|
||||||
|
|
|
@ -316,6 +316,9 @@ private:
|
||||||
/* Return a string representing the type of the value `v'. */
|
/* Return a string representing the type of the value `v'. */
|
||||||
string showType(const 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". */
|
/* If `path' refers to a directory, then append "/default.nix". */
|
||||||
Path resolveExprPath(Path path);
|
Path resolveExprPath(Path path);
|
||||||
|
|
|
@ -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>’. */
|
/* Handle derivation outputs of the form ‘!<name>!<path>’. */
|
||||||
else if (path.at(0) == '!') {
|
else if (path.at(0) == '!') {
|
||||||
std::pair<string, string> ctx = decodeContext(path);
|
std::pair<string, string> ctx = decodeContext(path);
|
||||||
drv.inputDrvs[ctx.first].insert(ctx.second);
|
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. */
|
/* Otherwise it's a source file. */
|
||||||
else
|
else
|
||||||
drv.inputSrcs.insert(path);
|
drv.inputSrcs.insert(path);
|
||||||
|
@ -1004,13 +995,8 @@ static void prim_toFile(EvalState & state, const Pos & pos, Value * * args, Valu
|
||||||
PathSet refs;
|
PathSet refs;
|
||||||
|
|
||||||
for (auto path : context) {
|
for (auto path : context) {
|
||||||
if (path.at(0) == '=') path = string(path, 1);
|
if (path.at(0) != '/')
|
||||||
if (isDerivation(path)) {
|
throw EvalError(format("in 'toFile': the file '%1%' cannot refer to derivation outputs, at %2%") % name % pos);
|
||||||
/* 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);
|
|
||||||
}
|
|
||||||
refs.insert(path);
|
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. */
|
/* Return the cryptographic hash of a string in base-16. */
|
||||||
static void prim_hashString(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
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("toString", 1, prim_toString);
|
||||||
addPrimOp("__substring", 3, prim_substring);
|
addPrimOp("__substring", 3, prim_substring);
|
||||||
addPrimOp("__stringLength", 1, prim_stringLength);
|
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("__hashString", 2, prim_hashString);
|
||||||
addPrimOp("__match", 2, prim_match);
|
addPrimOp("__match", 2, prim_match);
|
||||||
addPrimOp("__split", 2, prim_split);
|
addPrimOp("__split", 2, prim_split);
|
||||||
|
|
187
src/libexpr/primops/context.cc
Normal file
187
src/libexpr/primops/context.cc
Normal file
|
@ -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);
|
||||||
|
|
||||||
|
}
|
1
tests/lang/eval-okay-context-introspection.exp
Normal file
1
tests/lang/eval-okay-context-introspection.exp
Normal file
|
@ -0,0 +1 @@
|
||||||
|
true
|
24
tests/lang/eval-okay-context-introspection.nix
Normal file
24
tests/lang/eval-okay-context-introspection.nix
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
let
|
||||||
|
drv = derivation {
|
||||||
|
name = "fail";
|
||||||
|
builder = "/bin/false";
|
||||||
|
system = "x86_64-linux";
|
||||||
|
outputs = [ "out" "foo" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
path = "${./eval-okay-context-introspection.nix}";
|
||||||
|
|
||||||
|
desired-context = {
|
||||||
|
"${builtins.unsafeDiscardStringContext path}" = {
|
||||||
|
path = true;
|
||||||
|
};
|
||||||
|
"${builtins.unsafeDiscardStringContext drv.drvPath}" = {
|
||||||
|
outputs = [ "foo" "out" ];
|
||||||
|
allOutputs = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
legit-context = builtins.getContext "${path}${drv.outPath}${drv.foo.outPath}${drv.drvPath}";
|
||||||
|
|
||||||
|
constructed-context = builtins.getContext (builtins.appendContext "" desired-context);
|
||||||
|
in legit-context == constructed-context
|
Loading…
Reference in a new issue