about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/libexpr/eval.cc2
-rw-r--r--src/libexpr/eval.hh2
-rw-r--r--src/libexpr/primops.cc127
-rw-r--r--src/libstore/build.cc57
4 files changed, 130 insertions, 58 deletions
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index 5f9f5bac1ed6..d418ab4e43aa 100644
--- a/src/libexpr/eval.cc
+++ b/src/libexpr/eval.cc
@@ -291,6 +291,8 @@ EvalState::EvalState(const Strings & _searchPath, ref<Store> store)
     , sToString(symbols.create("__toString"))
     , sRight(symbols.create("right"))
     , sWrong(symbols.create("wrong"))
+    , sStructuredAttrs(symbols.create("__structuredAttrs"))
+    , sBuilder(symbols.create("builder"))
     , store(store)
     , baseEnv(allocEnv(128))
     , staticBaseEnv(false, 0)
diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh
index 195cb0db3acc..46d5a1cc866c 100644
--- a/src/libexpr/eval.hh
+++ b/src/libexpr/eval.hh
@@ -68,7 +68,7 @@ public:
     const Symbol sWith, sOutPath, sDrvPath, sType, sMeta, sName, sValue,
         sSystem, sOverrides, sOutputs, sOutputName, sIgnoreNulls,
         sFile, sLine, sColumn, sFunctor, sToString,
-        sRight, sWrong;
+        sRight, sWrong, sStructuredAttrs, sBuilder;
     Symbol sDerivationNix;
 
     /* If set, force copying files to the Nix store even if they
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index d8dbedcaf89e..5a570cefb2fa 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -8,6 +8,7 @@
 #include "names.hh"
 #include "store-api.hh"
 #include "util.hh"
+#include "json.hh"
 #include "value-to-json.hh"
 #include "value-to-xml.hh"
 #include "primops.hh"
@@ -474,6 +475,13 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
         throw;
     }
 
+    /* Check whether attributes should be passed as a JSON file. */
+    std::ostringstream jsonBuf;
+    std::unique_ptr<JSONObject> jsonObject;
+    attr = args[0]->attrs->find(state.sStructuredAttrs);
+    if (attr != args[0]->attrs->end() && state.forceBool(*attr->value, pos))
+        jsonObject = std::make_unique<JSONObject>(jsonBuf);
+
     /* Check whether null attributes should be ignored. */
     bool ignoreNulls = false;
     attr = args[0]->attrs->find(state.sIgnoreNulls);
@@ -491,24 +499,48 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
     StringSet outputs;
     outputs.insert("out");
 
-    for (auto & i : *args[0]->attrs) {
-        if (i.name == state.sIgnoreNulls) continue;
-        string key = i.name;
+    for (auto & i : args[0]->attrs->lexicographicOrder()) {
+        if (i->name == state.sIgnoreNulls) continue;
+        string key = i->name;
         Activity act(*logger, lvlVomit, format("processing attribute ‘%1%’") % key);
 
+        auto handleHashMode = [&](const std::string & s) {
+            if (s == "recursive") outputHashRecursive = true;
+            else if (s == "flat") outputHashRecursive = false;
+            else throw EvalError("invalid value ‘%s’ for ‘outputHashMode’ attribute, at %s", s, posDrvName);
+        };
+
+        auto handleOutputs = [&](const Strings & ss) {
+            outputs.clear();
+            for (auto & j : ss) {
+                if (outputs.find(j) != outputs.end())
+                    throw EvalError(format("duplicate derivation output ‘%1%’, at %2%") % j % posDrvName);
+                /* !!! Check whether j is a valid attribute
+                   name. */
+                /* Derivations cannot be named ‘drv’, because
+                   then we'd have an attribute ‘drvPath’ in
+                   the resulting set. */
+                if (j == "drv")
+                    throw EvalError(format("invalid derivation output name ‘drv’, at %1%") % posDrvName);
+                outputs.insert(j);
+            }
+            if (outputs.empty())
+                throw EvalError(format("derivation cannot have an empty set of outputs, at %1%") % posDrvName);
+        };
+
         try {
 
             if (ignoreNulls) {
-                state.forceValue(*i.value);
-                if (i.value->type == tNull) continue;
+                state.forceValue(*i->value);
+                if (i->value->type == tNull) continue;
             }
 
             /* The `args' attribute is special: it supplies the
                command-line arguments to the builder. */
             if (key == "args") {
-                state.forceList(*i.value, pos);
-                for (unsigned int n = 0; n < i.value->listSize(); ++n) {
-                    string s = state.coerceToString(posDrvName, *i.value->listElems()[n], context, true);
+                state.forceList(*i->value, pos);
+                for (unsigned int n = 0; n < i->value->listSize(); ++n) {
+                    string s = state.coerceToString(posDrvName, *i->value->listElems()[n], context, true);
                     drv.args.push_back(s);
                 }
             }
@@ -516,39 +548,51 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
             /* All other attributes are passed to the builder through
                the environment. */
             else {
-                string s = state.coerceToString(posDrvName, *i.value, context, true);
-                drv.env[key] = s;
-                if (key == "builder") drv.builder = s;
-                else if (i.name == state.sSystem) drv.platform = s;
-                else if (i.name == state.sName) {
-                    drvName = s;
-                    printMsg(lvlVomit, format("derivation name is ‘%1%’") % drvName);
-                }
-                else if (key == "outputHash") outputHash = s;
-                else if (key == "outputHashAlgo") outputHashAlgo = s;
-                else if (key == "outputHashMode") {
-                    if (s == "recursive") outputHashRecursive = true;
-                    else if (s == "flat") outputHashRecursive = false;
-                    else throw EvalError(format("invalid value ‘%1%’ for ‘outputHashMode’ attribute, at %2%") % s % posDrvName);
-                }
-                else if (key == "outputs") {
-                    Strings tmp = tokenizeString<Strings>(s);
-                    outputs.clear();
-                    for (auto & j : tmp) {
-                        if (outputs.find(j) != outputs.end())
-                            throw EvalError(format("duplicate derivation output ‘%1%’, at %2%") % j % posDrvName);
-                        /* !!! Check whether j is a valid attribute
-                           name. */
-                        /* Derivations cannot be named ‘drv’, because
-                           then we'd have an attribute ‘drvPath’ in
-                           the resulting set. */
-                        if (j == "drv")
-                            throw EvalError(format("invalid derivation output name ‘drv’, at %1%") % posDrvName);
-                        outputs.insert(j);
+
+                if (jsonObject) {
+
+                    if (i->name == state.sStructuredAttrs) continue;
+
+                    auto placeholder(jsonObject->placeholder(key));
+                    printValueAsJSON(state, true, *i->value, placeholder, context);
+
+                    if (i->name == state.sBuilder)
+                        drv.builder = state.forceString(*i->value, context, posDrvName);
+                    else if (i->name == state.sSystem)
+                        drv.platform = state.forceStringNoCtx(*i->value, posDrvName);
+                    else if (i->name == state.sName)
+                        drvName = state.forceStringNoCtx(*i->value, posDrvName);
+                    else if (key == "outputHash")
+                        outputHash = state.forceStringNoCtx(*i->value, posDrvName);
+                    else if (key == "outputHashAlgo")
+                        outputHashAlgo = state.forceStringNoCtx(*i->value, posDrvName);
+                    else if (key == "outputHashMode")
+                        handleHashMode(state.forceStringNoCtx(*i->value, posDrvName));
+                    else if (key == "outputs") {
+                        /* Require ‘outputs’ to be a list of strings. */
+                        state.forceList(*i->value, posDrvName);
+                        Strings ss;
+                        for (unsigned int n = 0; n < i->value->listSize(); ++n)
+                            ss.emplace_back(state.forceStringNoCtx(*i->value->listElems()[n], posDrvName));
+                        handleOutputs(ss);
+                    }
+
+                } else {
+                    auto s = state.coerceToString(posDrvName, *i->value, context, true);
+                    drv.env.emplace(key, s);
+                    if (i->name == state.sBuilder) drv.builder = s;
+                    else if (i->name == state.sSystem) drv.platform = s;
+                    else if (i->name == state.sName) {
+                        drvName = s;
+                        printMsg(lvlVomit, format("derivation name is ‘%1%’") % drvName);
                     }
-                    if (outputs.empty())
-                        throw EvalError(format("derivation cannot have an empty set of outputs, at %1%") % posDrvName);
+                    else if (key == "outputHash") outputHash = s;
+                    else if (key == "outputHashAlgo") outputHashAlgo = s;
+                    else if (key == "outputHashMode") handleHashMode(s);
+                    else if (key == "outputs")
+                        handleOutputs(tokenizeString<Strings>(s));
                 }
+
             }
 
         } catch (Error & e) {
@@ -558,6 +602,11 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
         }
     }
 
+    if (jsonObject) {
+        jsonObject.reset();
+        drv.env.emplace("__json", jsonBuf.str());
+    }
+
     /* Everything in the context of the strings in the derivation
        attributes should be added as dependencies of the resulting
        derivation. */
diff --git a/src/libstore/build.cc b/src/libstore/build.cc
index 6250de13cbb2..d76c8d1727fc 100644
--- a/src/libstore/build.cc
+++ b/src/libstore/build.cc
@@ -865,6 +865,9 @@ private:
     /* Fill in the environment for the builder. */
     void initEnv();
 
+    /* Write a JSON file containing the derivation attributes. */
+    void writeStructuredAttrs();
+
     /* Make a file owned by the builder. */
     void chownToBuilder(const Path & path);
 
@@ -1726,13 +1729,15 @@ void DerivationGoal::startBuilder()
     tmpDirInSandbox = useChroot ? canonPath("/tmp", true) + "/nix-build-" + drvName + "-0" : tmpDir;
     chownToBuilder(tmpDir);
 
-    /* Construct the environment passed to the builder. */
-    initEnv();
-
     /* Substitute output placeholders with the actual output paths. */
     for (auto & output : drv->outputs)
         inputRewrites[hashPlaceholder(output.first)] = output.second.path;
 
+    /* Construct the environment passed to the builder. */
+    initEnv();
+
+    writeStructuredAttrs();
+
     /* Handle exportReferencesGraph(), if set. */
     doExportReferencesGraph();
 
@@ -2148,22 +2153,29 @@ void DerivationGoal::initEnv()
     /* The maximum number of cores to utilize for parallel building. */
     env["NIX_BUILD_CORES"] = (format("%d") % settings.buildCores).str();
 
-    /* Add all bindings specified in the derivation via the
-       environments, except those listed in the passAsFile
-       attribute. Those are passed as file names pointing to
-       temporary files containing the contents. */
-    StringSet passAsFile = tokenizeString<StringSet>(get(drv->env, "passAsFile"));
-    int fileNr = 0;
-    for (auto & i : drv->env) {
-        if (passAsFile.find(i.first) == passAsFile.end()) {
-            env[i.first] = i.second;
-        } else {
-            string fn = ".attr-" + std::to_string(fileNr++);
-            Path p = tmpDir + "/" + fn;
-            writeFile(p, i.second);
-            chownToBuilder(p);
-            env[i.first + "Path"] = tmpDirInSandbox + "/" + fn;
+    /* In non-structured mode, add all bindings specified in the
+       derivation via the environments, except those listed in the
+       passAsFile attribute. Those are passed as file names pointing
+       to temporary files containing the contents. Note that
+       passAsFile is ignored in structure mode because it's not
+       needed (attributes are not passed through the environment, so
+       there is no size constraint). */
+    if (!drv->env.count("__json")) {
+
+        StringSet passAsFile = tokenizeString<StringSet>(get(drv->env, "passAsFile"));
+        int fileNr = 0;
+        for (auto & i : drv->env) {
+            if (passAsFile.find(i.first) == passAsFile.end()) {
+                env[i.first] = i.second;
+            } else {
+                string fn = ".attr-" + std::to_string(fileNr++);
+                Path p = tmpDir + "/" + fn;
+                writeFile(p, i.second);
+                chownToBuilder(p);
+                env[i.first + "Path"] = tmpDirInSandbox + "/" + fn;
+            }
         }
+
     }
 
     /* For convenience, set an environment pointing to the top build
@@ -2201,6 +2213,15 @@ void DerivationGoal::initEnv()
 }
 
 
+void DerivationGoal::writeStructuredAttrs()
+{
+    auto json = drv->env.find("__json");
+    if (json == drv->env.end()) return;
+
+    writeFile(tmpDir + "/.attrs.json", rewriteStrings(json->second, inputRewrites));
+}
+
+
 void DerivationGoal::chownToBuilder(const Path & path)
 {
     if (!buildUser) return;