Add builtins.getContext.
This can be very helpful when debugging, as well as enabling complex black magic like surgically removing a single dependency from a string's context.
This commit is contained in:
		
							parent
							
								
									087be7281a
								
							
						
					
					
						commit
						1d757292d0
					
				
					 5 changed files with 158 additions and 38 deletions
				
			
		|  | @ -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); | ||||||
|  |  | ||||||
|  | @ -1780,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) | ||||||
| { | { | ||||||
|  | @ -2285,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); | ||||||
|  |  | ||||||
							
								
								
									
										132
									
								
								src/libexpr/primops/context.cc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								src/libexpr/primops/context.cc
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,132 @@ | ||||||
|  | #include "primops.hh" | ||||||
|  | #include "eval-inline.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, but at least one must be set to something other | ||||||
|  |    than the default. | ||||||
|  | */ | ||||||
|  | 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); | ||||||
|  | 
 | ||||||
|  | } | ||||||
							
								
								
									
										1
									
								
								tests/lang/eval-okay-context-introspection.exp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								tests/lang/eval-okay-context-introspection.exp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | ||||||
|  | true | ||||||
							
								
								
									
										22
									
								
								tests/lang/eval-okay-context-introspection.nix
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								tests/lang/eval-okay-context-introspection.nix
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,22 @@ | ||||||
|  | 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 = "${path}${drv.outPath}${drv.foo.outPath}${drv.drvPath}"; | ||||||
|  | in builtins.getContext legit-context == desired-context | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue