* 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…
Reference in a new issue