* 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 evalExpr2(EvalState & state, Expr e) | ||||||
| { | { | ||||||
|     Expr e1, e2, e3, e4; |     Expr e1, e2, e3, e4; | ||||||
|  | @ -167,7 +264,8 @@ Expr evalExpr2(EvalState & state, Expr e) | ||||||
|         sym == symFunction1 || |         sym == symFunction1 || | ||||||
|         sym == symAttrs || |         sym == symAttrs || | ||||||
|         sym == symList || |         sym == symList || | ||||||
|         sym == symPrimOp) |         sym == symPrimOp || | ||||||
|  |         sym == symContext) | ||||||
|         return e; |         return e; | ||||||
|      |      | ||||||
|     /* The `Closed' constructor is just a way to prevent substitutions
 |     /* The `Closed' constructor is just a way to prevent substitutions
 | ||||||
|  | @ -338,16 +436,10 @@ Expr evalExpr2(EvalState & state, Expr e) | ||||||
| 
 | 
 | ||||||
|     /* String or path concatenation. */ |     /* String or path concatenation. */ | ||||||
|     if (matchOpPlus(e, e1, e2)) { |     if (matchOpPlus(e, e1, e2)) { | ||||||
|         e1 = evalExpr(state, e1); |         ATermVector args; | ||||||
|         e2 = evalExpr(state, e2); |         args.push_back(e1); | ||||||
|         ATerm s1, s2; |         args.push_back(e2); | ||||||
|         if (matchStr(e1, s1) && matchStr(e2, s2)) |         return concatStrings(state, args); | ||||||
|             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"); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /* List concatenation. */ |     /* List concatenation. */ | ||||||
|  |  | ||||||
|  | @ -59,6 +59,11 @@ bool evalBool(EvalState & state, Expr e); | ||||||
| ATermList evalList(EvalState & state, Expr e); | ATermList evalList(EvalState & state, Expr e); | ||||||
| ATerm coerceToString(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. */ | /* Print statistics. */ | ||||||
| void printEvalStats(EvalState & state); | 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; | typedef set<Expr> Exprs; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -35,6 +35,7 @@ Closed | Expr | Expr | | ||||||
| Rec | ATermList ATermList | Expr | | Rec | ATermList ATermList | Expr | | ||||||
| Bool | ATerm | Expr | | Bool | ATerm | Expr | | ||||||
| Null | | Expr | | Null | | Expr | | ||||||
|  | Context | ATermList Expr | Expr | | ||||||
| 
 | 
 | ||||||
| Bind | string Expr Pos | ATerm | | Bind | string Expr Pos | ATerm | | ||||||
| Bind | string Expr | ATerm | Bind2 | Bind | string Expr | ATerm | Bind2 | ||||||
|  |  | ||||||
|  | @ -109,6 +109,14 @@ static void processBinding(EvalState & state, Expr e, Derivation & drv, | ||||||
|     int n; |     int n; | ||||||
|     Expr e1, e2; |     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)); |     if (matchStr(e, s)) ss.push_back(aterm2String(s)); | ||||||
|     else if (matchUri(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"); |     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. */ | /* Convert the argument (which can be a path or a uri) to a string. */ | ||||||
| static Expr primToString(EvalState & state, const ATermVector & args) | static Expr primToString(EvalState & state, const ATermVector & args) | ||||||
| { | { | ||||||
|     ATerm s = coerceToString(evalExpr(state, args[0])); |     ATermList context = ATempty; | ||||||
|     if (!s) throw Error("cannot coerce value to string"); |     bool dummy; | ||||||
|     return makeStr(s); |     string s = coerceToStringWithContext(state, context, args[0], dummy); | ||||||
|  |     return wrapInContext(context, makeStr(toATerm(s))); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -18,8 +18,9 @@ let { | ||||||
|     name = "dependencies"; |     name = "dependencies"; | ||||||
|     system = "@system@"; |     system = "@system@"; | ||||||
|     builder = "@shell@"; |     builder = "@shell@"; | ||||||
|     args = ["-e" "-x" ./dependencies.builder0.sh]; |     args = ["-e" "-x" (./dependencies.builder0.sh  + "/FOOBAR/../.")]; | ||||||
|     inherit input1 input2; |     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