aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEelco Dolstra <edolstra@gmail.com>2020-03-09 15:28:41 +0100
committerEelco Dolstra <edolstra@gmail.com>2020-03-09 15:28:41 +0100
commit73769b28e376cf1fb3e7248bafaa17c35527925d (patch)
tree7f3a1e8fd1f00df7d5cecbfee8ab31ed5ad72991
parent5a1514adb8dcec78c03f949868ae35215b9d62f9 (diff)
Move calling flakes into a Nix helper function (call-flake.nix)
-rw-r--r--corepkgs/call-flake.nix22
-rw-r--r--corepkgs/local.mk3
-rw-r--r--src/libexpr/flake/flake.cc148
-rw-r--r--src/libexpr/flake/flake.hh6
-rw-r--r--src/libexpr/flake/lockfile.cc5
-rw-r--r--src/libexpr/flake/lockfile.hh2
-rw-r--r--src/libexpr/primops.hh1
-rw-r--r--src/libexpr/primops/fetchTree.cc52
-rw-r--r--tests/tarball.sh8
9 files changed, 93 insertions, 154 deletions
diff --git a/corepkgs/call-flake.nix b/corepkgs/call-flake.nix
new file mode 100644
index 000000000..29ff41040
--- /dev/null
+++ b/corepkgs/call-flake.nix
@@ -0,0 +1,22 @@
+locks: rootSrc:
+
+let
+
+ callFlake = sourceInfo: locks:
+ let
+ flake = import (sourceInfo + "/flake.nix");
+
+ inputs = builtins.mapAttrs (n: v:
+ if v.flake or true
+ then callFlake (fetchTree v.locked) v.inputs
+ else fetchTree v.locked) locks;
+
+ outputs = flake.outputs (inputs // { self = result; });
+
+ result = outputs // sourceInfo // { inherit inputs; inherit outputs; inherit sourceInfo; };
+ in
+ assert flake.edition == 201909;
+
+ result;
+
+in callFlake rootSrc (builtins.fromJSON locks).inputs
diff --git a/corepkgs/local.mk b/corepkgs/local.mk
index 67306e50d..fb44e7c3e 100644
--- a/corepkgs/local.mk
+++ b/corepkgs/local.mk
@@ -3,7 +3,8 @@ corepkgs_FILES = \
unpack-channel.nix \
derivation.nix \
fetchurl.nix \
- imported-drv-to-derivation.nix
+ imported-drv-to-derivation.nix \
+ call-flake.nix
$(foreach file,config.nix $(corepkgs_FILES),$(eval $(call install-data-in,$(d)/$(file),$(datadir)/nix/corepkgs)))
diff --git a/src/libexpr/flake/flake.cc b/src/libexpr/flake/flake.cc
index 4fa125f1b..eac7d026d 100644
--- a/src/libexpr/flake/flake.cc
+++ b/src/libexpr/flake/flake.cc
@@ -6,10 +6,6 @@
#include "fetchers/fetchers.hh"
#include "finally.hh"
-#include <iostream>
-#include <ctime>
-#include <iomanip>
-
namespace nix {
using namespace flake;
@@ -158,7 +154,8 @@ static FlakeInput parseFlakeInput(EvalState & state,
if (attr.value->type == tString)
attrs.emplace(attr.name, attr.value->string.s);
else
- throw Error("unsupported attribute type");
+ throw TypeError("flake input attribute '%s' is %s while a string is expected",
+ attr.name, showType(*attr.value));
}
} catch (Error & e) {
e.addPrefix(fmt("in flake attribute '%s' at '%s':\n", attr.name, *attr.pos));
@@ -621,143 +618,23 @@ LockedFlake lockFlake(
return LockedFlake { .flake = std::move(flake), .lockFile = std::move(newLockFile) };
}
-static void emitSourceInfoAttrs(
- EvalState & state,
- const FlakeRef & flakeRef,
- const fetchers::Tree & sourceInfo,
- Value & vAttrs)
-{
- assert(state.store->isValidPath(sourceInfo.storePath));
- auto pathS = state.store->printStorePath(sourceInfo.storePath);
- mkString(*state.allocAttr(vAttrs, state.sOutPath), pathS, {pathS});
-
- assert(sourceInfo.info.narHash);
- mkString(*state.allocAttr(vAttrs, state.symbols.create("narHash")),
- sourceInfo.info.narHash.to_string(SRI));
-
- if (auto rev = flakeRef.input->getRev()) {
- mkString(*state.allocAttr(vAttrs, state.symbols.create("rev")),
- rev->gitRev());
- mkString(*state.allocAttr(vAttrs, state.symbols.create("shortRev")),
- rev->gitShortRev());
- }
-
- if (sourceInfo.info.revCount)
- mkInt(*state.allocAttr(vAttrs, state.symbols.create("revCount")), *sourceInfo.info.revCount);
-
- if (sourceInfo.info.lastModified)
- mkString(*state.allocAttr(vAttrs, state.symbols.create("lastModified")),
- fmt("%s", std::put_time(std::gmtime(&*sourceInfo.info.lastModified), "%Y%m%d%H%M%S")));
-}
-
-struct LazyInput
-{
- bool isFlake;
- LockedInput lockedInput;
-};
-
-/* Helper primop to make callFlake (below) fetch/call its inputs
- lazily. Note that this primop cannot be called by user code since
- it doesn't appear in 'builtins'. */
-static void prim_callFlake(EvalState & state, const Pos & pos, Value * * args, Value & v)
-{
- auto lazyInput = (LazyInput *) args[0]->attrs;
-
- if (lazyInput->isFlake) {
- FlakeCache flakeCache;
- auto flake = getFlake(state, lazyInput->lockedInput.lockedRef, lazyInput->lockedInput.info, false, flakeCache);
-
- if (flake.sourceInfo->info.narHash != lazyInput->lockedInput.info.narHash)
- throw Error("the content hash of flake '%s' (%s) doesn't match the hash recorded in the referring lock file (%s)",
- lazyInput->lockedInput.lockedRef,
- flake.sourceInfo->info.narHash.to_string(SRI),
- lazyInput->lockedInput.info.narHash.to_string(SRI));
-
- // FIXME: check all the other attributes in lockedInput.info
- // once we've dropped support for lock file version 4.
-
- assert(flake.sourceInfo->storePath == lazyInput->lockedInput.computeStorePath(*state.store));
-
- callFlake(state, flake, lazyInput->lockedInput, v);
- } else {
- FlakeCache flakeCache;
- auto [sourceInfo, lockedRef] = fetchOrSubstituteTree(
- state, lazyInput->lockedInput.lockedRef, {}, false, flakeCache);
-
- if (sourceInfo.info.narHash != lazyInput->lockedInput.info.narHash)
- throw Error("the content hash of repository '%s' (%s) doesn't match the hash recorded in the referring lock file (%s)",
- lazyInput->lockedInput.lockedRef,
- sourceInfo.info.narHash.to_string(SRI),
- lazyInput->lockedInput.info.narHash.to_string(SRI));
-
- // FIXME: check all the other attributes in lockedInput.info
- // once we've dropped support for lock file version 4.
-
- assert(sourceInfo.storePath == lazyInput->lockedInput.computeStorePath(*state.store));
-
- state.mkAttrs(v, 8);
-
- assert(state.store->isValidPath(sourceInfo.storePath));
-
- auto pathS = state.store->printStorePath(sourceInfo.storePath);
-
- mkString(*state.allocAttr(v, state.sOutPath), pathS, {pathS});
-
- emitSourceInfoAttrs(state, lockedRef, sourceInfo, v);
-
- v.attrs->sort();
- }
-}
-
void callFlake(EvalState & state,
const Flake & flake,
const LockedInputs & lockedInputs,
- Value & vResFinal)
+ Value & vRes)
{
- auto & vRes = *state.allocValue();
- auto & vInputs = *state.allocValue();
-
- state.mkAttrs(vInputs, flake.inputs.size() + 1);
-
- for (auto & [inputId, input] : flake.inputs) {
- auto vFlake = state.allocAttr(vInputs, inputId);
- auto vPrimOp = state.allocValue();
- static auto primOp = new PrimOp(prim_callFlake, 1, state.symbols.create("callFlake"));
- vPrimOp->type = tPrimOp;
- vPrimOp->primOp = primOp;
- auto vArg = state.allocValue();
- vArg->type = tNull;
- auto lockedInput = lockedInputs.inputs.find(inputId);
- assert(lockedInput != lockedInputs.inputs.end());
- // FIXME: leak
- vArg->attrs = (Bindings *) new LazyInput{input.isFlake, lockedInput->second};
- mkApp(*vFlake, *vPrimOp, *vArg);
- }
-
- auto & vSourceInfo = *state.allocValue();
- state.mkAttrs(vSourceInfo, 8);
- emitSourceInfoAttrs(state, flake.lockedRef, *flake.sourceInfo, vSourceInfo);
- vSourceInfo.attrs->sort();
-
- vInputs.attrs->push_back(Attr(state.sSelf, &vRes));
-
- vInputs.attrs->sort();
+ auto vCallFlake = state.allocValue();
+ auto vLocks = state.allocValue();
+ auto vRootSrc = state.allocValue();
+ auto vTmp = state.allocValue();
- /* For convenience, put the outputs directly in the result, so you
- can refer to an output of an input as 'inputs.foo.bar' rather
- than 'inputs.foo.outputs.bar'. */
- auto vCall = *state.allocValue();
- state.eval(state.parseExprFromString(
- "outputsFun: inputs: sourceInfo: let outputs = outputsFun inputs; in "
- "outputs // sourceInfo // { inherit inputs; inherit outputs; inherit sourceInfo; }", "/"), vCall);
+ mkString(*vLocks, lockedInputs.to_string());
- auto vCall2 = *state.allocValue();
- auto vCall3 = *state.allocValue();
- state.callFunction(vCall, *flake.vOutputs, vCall2, noPos);
- state.callFunction(vCall2, vInputs, vCall3, noPos);
- state.callFunction(vCall3, vSourceInfo, vRes, noPos);
+ emitTreeAttrs(state, *flake.sourceInfo, flake.lockedRef.input, *vRootSrc);
- vResFinal = vRes;
+ state.evalFile(canonPath(settings.nixDataDir + "/nix/corepkgs/call-flake.nix", true), *vCallFlake);
+ state.callFunction(*vCallFlake, *vLocks, *vTmp, noPos);
+ state.callFunction(*vTmp, *vRootSrc, vRes, noPos);
}
void callFlake(EvalState & state,
@@ -767,7 +644,6 @@ void callFlake(EvalState & state,
callFlake(state, lockedFlake.flake, lockedFlake.lockFile, v);
}
-// This function is exposed to be used in nix files.
static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
callFlake(state,
diff --git a/src/libexpr/flake/flake.hh b/src/libexpr/flake/flake.hh
index 7c71f3383..e5c6c1bb0 100644
--- a/src/libexpr/flake/flake.hh
+++ b/src/libexpr/flake/flake.hh
@@ -107,4 +107,10 @@ void callFlake(
}
+void emitTreeAttrs(
+ EvalState & state,
+ const fetchers::Tree & tree,
+ std::shared_ptr<const fetchers::Input> input,
+ Value & v);
+
}
diff --git a/src/libexpr/flake/lockfile.cc b/src/libexpr/flake/lockfile.cc
index 0a1795a16..fdbba44de 100644
--- a/src/libexpr/flake/lockfile.cc
+++ b/src/libexpr/flake/lockfile.cc
@@ -123,6 +123,11 @@ nlohmann::json LockedInputs::toJson() const
return json;
}
+std::string LockedInputs::to_string() const
+{
+ return toJson().dump(2);
+}
+
bool LockedInputs::isImmutable() const
{
for (auto & i : inputs)
diff --git a/src/libexpr/flake/lockfile.hh b/src/libexpr/flake/lockfile.hh
index 51649df3d..82cbffd19 100644
--- a/src/libexpr/flake/lockfile.hh
+++ b/src/libexpr/flake/lockfile.hh
@@ -27,6 +27,8 @@ struct LockedInputs
nlohmann::json toJson() const;
+ std::string to_string() const;
+
bool isImmutable() const;
std::optional<LockedInput *> findInput(const InputPath & path);
diff --git a/src/libexpr/primops.hh b/src/libexpr/primops.hh
index c790b30f6..05d0792ef 100644
--- a/src/libexpr/primops.hh
+++ b/src/libexpr/primops.hh
@@ -20,6 +20,7 @@ struct RegisterPrimOp
them. */
/* Load a ValueInitializer from a DSO and return whatever it initializes */
void prim_importNative(EvalState & state, const Pos & pos, Value * * args, Value & v);
+
/* Execute a program and parse its output */
void prim_exec(EvalState & state, const Pos & pos, Value * * args, Value & v);
diff --git a/src/libexpr/primops/fetchTree.cc b/src/libexpr/primops/fetchTree.cc
index 60bd2ed11..66994c823 100644
--- a/src/libexpr/primops/fetchTree.cc
+++ b/src/libexpr/primops/fetchTree.cc
@@ -4,11 +4,45 @@
#include "fetchers/fetchers.hh"
#include "fetchers/registry.hh"
+#include <ctime>
+#include <iomanip>
+
namespace nix {
+void emitTreeAttrs(
+ EvalState & state,
+ const fetchers::Tree & tree,
+ std::shared_ptr<const fetchers::Input> input,
+ Value & v)
+{
+ state.mkAttrs(v, 8);
+
+ auto storePath = state.store->printStorePath(tree.storePath);
+
+ mkString(*state.allocAttr(v, state.sOutPath), storePath, PathSet({storePath}));
+
+ assert(tree.info.narHash);
+ mkString(*state.allocAttr(v, state.symbols.create("narHash")),
+ tree.info.narHash.to_string(SRI));
+
+ if (input->getRev()) {
+ mkString(*state.allocAttr(v, state.symbols.create("rev")), input->getRev()->gitRev());
+ mkString(*state.allocAttr(v, state.symbols.create("shortRev")), input->getRev()->gitShortRev());
+ }
+
+ if (tree.info.revCount)
+ mkInt(*state.allocAttr(v, state.symbols.create("revCount")), *tree.info.revCount);
+
+ if (tree.info.lastModified)
+ mkString(*state.allocAttr(v, state.symbols.create("lastModified")),
+ fmt("%s", std::put_time(std::gmtime(&*tree.info.lastModified), "%Y%m%d%H%M%S")));
+
+ v.attrs->sort();
+}
+
static void prim_fetchTree(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
- settings.requireExperimentalFeature("fetch-tree");
+ settings.requireExperimentalFeature("flakes");
std::shared_ptr<const fetchers::Input> input;
PathSet context;
@@ -25,7 +59,8 @@ static void prim_fetchTree(EvalState & state, const Pos & pos, Value * * args, V
if (attr.value->type == tString)
attrs.emplace(attr.name, attr.value->string.s);
else
- throw Error("unsupported attribute type");
+ throw TypeError("fetchTree argument '%s' is %s while a string is expected",
+ attr.name, showType(*attr.value));
}
if (!attrs.count("type"))
@@ -43,19 +78,10 @@ static void prim_fetchTree(EvalState & state, const Pos & pos, Value * * args, V
auto [tree, input2] = input->fetchTree(state.store);
- state.mkAttrs(v, 8);
- auto storePath = state.store->printStorePath(tree.storePath);
- mkString(*state.allocAttr(v, state.sOutPath), storePath, PathSet({storePath}));
- if (input2->getRev()) {
- mkString(*state.allocAttr(v, state.symbols.create("rev")), input2->getRev()->gitRev());
- mkString(*state.allocAttr(v, state.symbols.create("shortRev")), input2->getRev()->gitShortRev());
- }
- if (tree.info.revCount)
- mkInt(*state.allocAttr(v, state.symbols.create("revCount")), *tree.info.revCount);
- v.attrs->sort();
-
if (state.allowedPaths)
state.allowedPaths->insert(tree.actualPath);
+
+ emitTreeAttrs(state, tree, input2, v);
}
static RegisterPrimOp r("fetchTree", 1, prim_fetchTree);
diff --git a/tests/tarball.sh b/tests/tarball.sh
index 55ed3e318..b3ec16d40 100644
--- a/tests/tarball.sh
+++ b/tests/tarball.sh
@@ -27,10 +27,10 @@ test_tarball() {
nix-build -o $TEST_ROOT/result -E "import (fetchTarball file://$tarball)"
- nix-build --experimental-features fetch-tree -o $TEST_ROOT/result -E "import (fetchTree file://$tarball)"
- nix-build --experimental-features fetch-tree -o $TEST_ROOT/result -E "import (fetchTree { type = \"tarball\"; url = file://$tarball; })"
- nix-build --experimental-features fetch-tree -o $TEST_ROOT/result -E "import (fetchTree { type = \"tarball\"; url = file://$tarball; narHash = \"$hash\"; })"
- nix-build --experimental-features fetch-tree -o $TEST_ROOT/result -E "import (fetchTree { type = \"tarball\"; url = file://$tarball; narHash = \"sha256-xdKv2pq/IiwLSnBBJXW8hNowI4MrdZfW+SYqDQs7Tzc=\"; })" 2>&1 | grep 'NAR hash mismatch in input'
+ nix-build --experimental-features flakes -o $TEST_ROOT/result -E "import (fetchTree file://$tarball)"
+ nix-build --experimental-features flakes -o $TEST_ROOT/result -E "import (fetchTree { type = \"tarball\"; url = file://$tarball; })"
+ nix-build --experimental-features flakes -o $TEST_ROOT/result -E "import (fetchTree { type = \"tarball\"; url = file://$tarball; narHash = \"$hash\"; })"
+ nix-build --experimental-features flakes -o $TEST_ROOT/result -E "import (fetchTree { type = \"tarball\"; url = file://$tarball; narHash = \"sha256-xdKv2pq/IiwLSnBBJXW8hNowI4MrdZfW+SYqDQs7Tzc=\"; })" 2>&1 | grep 'NAR hash mismatch in input'
nix-instantiate --eval -E '1 + 2' -I fnord=file://no-such-tarball.tar$ext
nix-instantiate --eval -E 'with <fnord/xyzzy>; 1 + 2' -I fnord=file://no-such-tarball$ext