about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/libexpr/eval.cc114
-rw-r--r--src/libexpr/eval.hh5
-rw-r--r--src/libexpr/get-drvs.cc4
-rw-r--r--src/libexpr/nixexpr-ast.def1
-rw-r--r--src/libexpr/primops.cc15
-rw-r--r--tests/dependencies.nix.in5
-rw-r--r--tests/lang/eval-okay-string.exp2
-rw-r--r--tests/lang/eval-okay-string.nix2
8 files changed, 130 insertions, 18 deletions
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index fc51590b20..1c2aafd91f 100644
--- a/src/libexpr/eval.cc
+++ b/src/libexpr/eval.cc
@@ -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. */
diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh
index 8070f4884c..54a612b367 100644
--- a/src/libexpr/eval.hh
+++ b/src/libexpr/eval.hh
@@ -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);
 
diff --git a/src/libexpr/get-drvs.cc b/src/libexpr/get-drvs.cc
index 78edbd392d..b101f2da30 100644
--- a/src/libexpr/get-drvs.cc
+++ b/src/libexpr/get-drvs.cc
@@ -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;
 
 
diff --git a/src/libexpr/nixexpr-ast.def b/src/libexpr/nixexpr-ast.def
index b384ff7c08..fab560c99f 100644
--- a/src/libexpr/nixexpr-ast.def
+++ b/src/libexpr/nixexpr-ast.def
@@ -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
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index 3a291e007f..8935b147e5 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -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)));
 }
 
 
diff --git a/tests/dependencies.nix.in b/tests/dependencies.nix.in
index 920564955a..aec9ec5b5f 100644
--- a/tests/dependencies.nix.in
+++ b/tests/dependencies.nix.in
@@ -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;
   };
 
 }
\ No newline at end of file
diff --git a/tests/lang/eval-okay-string.exp b/tests/lang/eval-okay-string.exp
index 07741d1605..9c3f56c3f3 100644
--- a/tests/lang/eval-okay-string.exp
+++ b/tests/lang/eval-okay-string.exp
@@ -1 +1 @@
-Str("foobar/a/b/c/d")
+Str("foobar/a/b/c/d/foo/xyzzy/foo.txt/../foo/x/y")
diff --git a/tests/lang/eval-okay-string.nix b/tests/lang/eval-okay-string.nix
index 19b60497a5..b5280a0cd1 100644
--- a/tests/lang/eval-okay-string.nix
+++ b/tests/lang/eval-okay-string.nix
@@ -1 +1 @@
-"foo" + "bar" + toString (/a/b + /c/d)
+"foo" + "bar" + toString (/a/b + /c/d) + (/foo/bar + "/../xyzzy/." + "/foo.txt") + ("/../foo" + /x/y)