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'. */ | ||||
| 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); | ||||
|  |  | |||
|  | @ -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. */ | ||||
| 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("__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); | ||||
|  |  | |||
							
								
								
									
										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