* Allow string concatenations involving derivations, e.g.,
configureFlags = "--with-freetype2-library="
      + freetype + "/lib";
			
			
This commit is contained in:
		
							parent
							
								
									cce31b739c
								
							
						
					
					
						commit
						6cecad2be0
					
				
					 8 changed files with 130 additions and 18 deletions
				
			
		|  | @ -149,6 +149,103 @@ ATermList evalList(EvalState & state, Expr e) | |||
| } | ||||
| 
 | ||||
| 
 | ||||
| /* String concatenation and context nodes: in order to allow users to
 | ||||
|    write things like | ||||
| 
 | ||||
|      "--with-freetype2-library=" + freetype + "/lib" | ||||
| 
 | ||||
|    where `freetype' is a derivation, we automatically coerce | ||||
|    derivations into their output path (e.g., | ||||
|    /nix/store/hashcode-freetype) in concatenations.  However, if we do | ||||
|    this naively, we could introduce an undeclared dependency: when the | ||||
|    string is used in another derivation, that derivation would not | ||||
|    have an explicitly dependency on `freetype' in its inputDrvs | ||||
|    field.  Thus `freetype' would not necessarily be built. | ||||
| 
 | ||||
|    To prevent this, we wrap the string resulting from the | ||||
|    concatenation in a *context node*, like this: | ||||
| 
 | ||||
|      Context([freetype], | ||||
|        Str("--with-freetype2-library=/nix/store/hashcode-freetype/lib")) | ||||
| 
 | ||||
|    Thus the context is the list of all derivations used in the | ||||
|    computation of a value.  These contexts are propagated through | ||||
|    further concatenations.  In processBinding() in primops.cc, context | ||||
|    nodes are unwrapped and added to inputDrvs. | ||||
| 
 | ||||
|    !!! Should the ordering of the context list have a canonical form? | ||||
| 
 | ||||
|    !!! Contexts are not currently recognised in most places in the | ||||
|    evaluator. */ | ||||
| 
 | ||||
| 
 | ||||
| /* Coerce a value to a string, keeping track of contexts. */ | ||||
| string coerceToStringWithContext(EvalState & state, | ||||
|     ATermList & context, Expr e, bool & isPath) | ||||
| { | ||||
|     isPath = false; | ||||
|      | ||||
|     e = evalExpr(state, e); | ||||
| 
 | ||||
|     ATermList es; | ||||
|     ATerm e2; | ||||
|     if (matchContext(e, es, e2)) { | ||||
|         e = e2; | ||||
|         context = ATconcat(es, context); | ||||
|     } | ||||
|      | ||||
|     ATerm s; | ||||
|     if (matchStr(e, s) || matchUri(e, s)) | ||||
|         return aterm2String(s); | ||||
|      | ||||
|     if (matchPath(e, s)) { | ||||
|         isPath = true; | ||||
|         return aterm2String(s); | ||||
|     } | ||||
| 
 | ||||
|     if (matchAttrs(e, es)) { | ||||
|         ATermMap attrs; | ||||
|         queryAllAttrs(e, attrs, false); | ||||
| 
 | ||||
|         Expr a = attrs.get("type"); | ||||
|         if (a && evalString(state, a) == "derivation") { | ||||
|             a = attrs.get("outPath"); | ||||
|             if (!a) throw Error("output path missing from derivation"); | ||||
|             context = ATinsert(context, e); | ||||
|             return evalPath(state, a); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     throw Error("cannot coerce value to string"); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /* Wrap an expression in a context if the context is not empty. */ | ||||
| Expr wrapInContext(ATermList context, Expr e) | ||||
| { | ||||
|     return context == ATempty ? e : makeContext(context, e); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static ATerm concatStrings(EvalState & state, const ATermVector & args) | ||||
| { | ||||
|     ATermList context = ATempty; | ||||
|     ostringstream s; | ||||
|     bool isPath; | ||||
| 
 | ||||
|     for (ATermVector::const_iterator i = args.begin(); i != args.end(); ++i) { | ||||
|         bool isPath2; | ||||
|         s << coerceToStringWithContext(state, context, *i, isPath2); | ||||
|         if (i == args.begin()) isPath = isPath2; | ||||
|     } | ||||
| 
 | ||||
|     Expr result = isPath | ||||
|         ? makePath(toATerm(canonPath(s.str()))) | ||||
|         : makeStr(toATerm(s.str())); | ||||
|     return wrapInContext(context, result); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| Expr evalExpr2(EvalState & state, Expr e) | ||||
| { | ||||
|     Expr e1, e2, e3, e4; | ||||
|  | @ -167,7 +264,8 @@ Expr evalExpr2(EvalState & state, Expr e) | |||
|         sym == symFunction1 || | ||||
|         sym == symAttrs || | ||||
|         sym == symList || | ||||
|         sym == symPrimOp) | ||||
|         sym == symPrimOp || | ||||
|         sym == symContext) | ||||
|         return e; | ||||
|      | ||||
|     /* The `Closed' constructor is just a way to prevent substitutions
 | ||||
|  | @ -338,16 +436,10 @@ Expr evalExpr2(EvalState & state, Expr e) | |||
| 
 | ||||
|     /* String or path concatenation. */ | ||||
|     if (matchOpPlus(e, e1, e2)) { | ||||
|         e1 = evalExpr(state, e1); | ||||
|         e2 = evalExpr(state, e2); | ||||
|         ATerm s1, s2; | ||||
|         if (matchStr(e1, s1) && matchStr(e2, s2)) | ||||
|             return makeStr(toATerm( | ||||
|                 (string) aterm2String(s1) + (string) aterm2String(s2))); | ||||
|         else if (matchPath(e1, s1) && matchPath(e2, s2)) | ||||
|             return makePath(toATerm(canonPath( | ||||
|                 (string) aterm2String(s1) + "/" + (string) aterm2String(s2)))); | ||||
|         else throw Error("wrong argument types in `+' operator"); | ||||
|         ATermVector args; | ||||
|         args.push_back(e1); | ||||
|         args.push_back(e2); | ||||
|         return concatStrings(state, args); | ||||
|     } | ||||
| 
 | ||||
|     /* List concatenation. */ | ||||
|  |  | |||
|  | @ -59,6 +59,11 @@ bool evalBool(EvalState & state, Expr e); | |||
| ATermList evalList(EvalState & state, Expr e); | ||||
| ATerm coerceToString(Expr e); | ||||
| 
 | ||||
| /* Contexts. */ | ||||
| string coerceToStringWithContext(EvalState & state, | ||||
|     ATermList & context, Expr e, bool & isPath); | ||||
| Expr wrapInContext(ATermList context, Expr e); | ||||
| 
 | ||||
| /* Print statistics. */ | ||||
| void printEvalStats(EvalState & state); | ||||
| 
 | ||||
|  |  | |||
|  | @ -45,6 +45,10 @@ MetaInfo DrvInfo::queryMetaInfo(EvalState & state) const | |||
| } | ||||
| 
 | ||||
| 
 | ||||
| /* Cache for already evaluated derivations.  Usually putting ATerms in
 | ||||
|    a STL container is unsafe (they're not scanning for GC roots), but | ||||
|    here it doesn't matter; everything in this set is reachable from | ||||
|    the stack as well. */ | ||||
| typedef set<Expr> Exprs; | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -35,6 +35,7 @@ Closed | Expr | Expr | | |||
| Rec | ATermList ATermList | Expr | | ||||
| Bool | ATerm | Expr | | ||||
| Null | | Expr | | ||||
| Context | ATermList Expr | Expr | | ||||
| 
 | ||||
| Bind | string Expr Pos | ATerm | | ||||
| Bind | string Expr | ATerm | Bind2 | ||||
|  |  | |||
|  | @ -109,6 +109,14 @@ static void processBinding(EvalState & state, Expr e, Derivation & drv, | |||
|     int n; | ||||
|     Expr e1, e2; | ||||
| 
 | ||||
|     if (matchContext(e, es, e2)) { | ||||
|         e = e2; | ||||
|         for (ATermIterator i(es); i; ++i) { | ||||
|             Strings dummy; | ||||
|             processBinding(state, *i, drv, dummy); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (matchStr(e, s)) ss.push_back(aterm2String(s)); | ||||
|     else if (matchUri(e, s)) ss.push_back(aterm2String(s)); | ||||
|     else if (e == eTrue) ss.push_back("1"); | ||||
|  | @ -408,9 +416,10 @@ ATerm coerceToString(Expr e) | |||
| /* Convert the argument (which can be a path or a uri) to a string. */ | ||||
| static Expr primToString(EvalState & state, const ATermVector & args) | ||||
| { | ||||
|     ATerm s = coerceToString(evalExpr(state, args[0])); | ||||
|     if (!s) throw Error("cannot coerce value to string"); | ||||
|     return makeStr(s); | ||||
|     ATermList context = ATempty; | ||||
|     bool dummy; | ||||
|     string s = coerceToStringWithContext(state, context, args[0], dummy); | ||||
|     return wrapInContext(context, makeStr(toATerm(s))); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -18,8 +18,9 @@ let { | |||
|     name = "dependencies"; | ||||
|     system = "@system@"; | ||||
|     builder = "@shell@"; | ||||
|     args = ["-e" "-x" ./dependencies.builder0.sh]; | ||||
|     inherit input1 input2; | ||||
|     args = ["-e" "-x" (./dependencies.builder0.sh  + "/FOOBAR/../.")]; | ||||
|     input1 = input1 + "/."; | ||||
|     inherit input2; | ||||
|   }; | ||||
| 
 | ||||
| } | ||||
|  | @ -1 +1 @@ | |||
| Str("foobar/a/b/c/d") | ||||
| Str("foobar/a/b/c/d/foo/xyzzy/foo.txt/../foo/x/y") | ||||
|  |  | |||
|  | @ -1 +1 @@ | |||
| "foo" + "bar" + toString (/a/b + /c/d) | ||||
| "foo" + "bar" + toString (/a/b + /c/d) + (/foo/bar + "/../xyzzy/." + "/foo.txt") + ("/../foo" + /x/y) | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue