Merge pull request #2628 from shlevy/context-introspection
Context introspection
This commit is contained in:
		
						commit
						7a7ec22298
					
				
					 6 changed files with 220 additions and 54 deletions
				
			
		
							
								
								
									
										3
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -81,6 +81,9 @@ perl/Makefile.config
 | 
			
		|||
/tests/common.sh
 | 
			
		||||
/tests/dummy
 | 
			
		||||
/tests/result*
 | 
			
		||||
/tests/restricted-innocent
 | 
			
		||||
/tests/shell
 | 
			
		||||
/tests/shell.drv
 | 
			
		||||
 | 
			
		||||
# /tests/lang/
 | 
			
		||||
/tests/lang/*.out
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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) != '~')
 | 
			
		||||
        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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										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…
	
	Add table
		Add a link
		
	
		Reference in a new issue