forked from lix-project/lix
Move calling flakes into a Nix helper function (call-flake.nix)
This commit is contained in:
parent
5a1514adb8
commit
73769b28e3
22
corepkgs/call-flake.nix
Normal file
22
corepkgs/call-flake.nix
Normal file
|
@ -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
|
|
@ -3,7 +3,8 @@ corepkgs_FILES = \
|
||||||
unpack-channel.nix \
|
unpack-channel.nix \
|
||||||
derivation.nix \
|
derivation.nix \
|
||||||
fetchurl.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)))
|
$(foreach file,config.nix $(corepkgs_FILES),$(eval $(call install-data-in,$(d)/$(file),$(datadir)/nix/corepkgs)))
|
||||||
|
|
||||||
|
|
|
@ -6,10 +6,6 @@
|
||||||
#include "fetchers/fetchers.hh"
|
#include "fetchers/fetchers.hh"
|
||||||
#include "finally.hh"
|
#include "finally.hh"
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <ctime>
|
|
||||||
#include <iomanip>
|
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
using namespace flake;
|
using namespace flake;
|
||||||
|
@ -158,7 +154,8 @@ static FlakeInput parseFlakeInput(EvalState & state,
|
||||||
if (attr.value->type == tString)
|
if (attr.value->type == tString)
|
||||||
attrs.emplace(attr.name, attr.value->string.s);
|
attrs.emplace(attr.name, attr.value->string.s);
|
||||||
else
|
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) {
|
} catch (Error & e) {
|
||||||
e.addPrefix(fmt("in flake attribute '%s' at '%s':\n", attr.name, *attr.pos));
|
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) };
|
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,
|
void callFlake(EvalState & state,
|
||||||
const Flake & flake,
|
const Flake & flake,
|
||||||
const LockedInputs & lockedInputs,
|
const LockedInputs & lockedInputs,
|
||||||
Value & vResFinal)
|
Value & vRes)
|
||||||
{
|
{
|
||||||
auto & vRes = *state.allocValue();
|
auto vCallFlake = state.allocValue();
|
||||||
auto & vInputs = *state.allocValue();
|
auto vLocks = state.allocValue();
|
||||||
|
auto vRootSrc = state.allocValue();
|
||||||
|
auto vTmp = state.allocValue();
|
||||||
|
|
||||||
state.mkAttrs(vInputs, flake.inputs.size() + 1);
|
mkString(*vLocks, lockedInputs.to_string());
|
||||||
|
|
||||||
for (auto & [inputId, input] : flake.inputs) {
|
emitTreeAttrs(state, *flake.sourceInfo, flake.lockedRef.input, *vRootSrc);
|
||||||
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.evalFile(canonPath(settings.nixDataDir + "/nix/corepkgs/call-flake.nix", true), *vCallFlake);
|
||||||
state.mkAttrs(vSourceInfo, 8);
|
state.callFunction(*vCallFlake, *vLocks, *vTmp, noPos);
|
||||||
emitSourceInfoAttrs(state, flake.lockedRef, *flake.sourceInfo, vSourceInfo);
|
state.callFunction(*vTmp, *vRootSrc, vRes, noPos);
|
||||||
vSourceInfo.attrs->sort();
|
|
||||||
|
|
||||||
vInputs.attrs->push_back(Attr(state.sSelf, &vRes));
|
|
||||||
|
|
||||||
vInputs.attrs->sort();
|
|
||||||
|
|
||||||
/* 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);
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
vResFinal = vRes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void callFlake(EvalState & state,
|
void callFlake(EvalState & state,
|
||||||
|
@ -767,7 +644,6 @@ void callFlake(EvalState & state,
|
||||||
callFlake(state, lockedFlake.flake, lockedFlake.lockFile, v);
|
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)
|
static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
callFlake(state,
|
callFlake(state,
|
||||||
|
|
|
@ -107,4 +107,10 @@ void callFlake(
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void emitTreeAttrs(
|
||||||
|
EvalState & state,
|
||||||
|
const fetchers::Tree & tree,
|
||||||
|
std::shared_ptr<const fetchers::Input> input,
|
||||||
|
Value & v);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,6 +123,11 @@ nlohmann::json LockedInputs::toJson() const
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string LockedInputs::to_string() const
|
||||||
|
{
|
||||||
|
return toJson().dump(2);
|
||||||
|
}
|
||||||
|
|
||||||
bool LockedInputs::isImmutable() const
|
bool LockedInputs::isImmutable() const
|
||||||
{
|
{
|
||||||
for (auto & i : inputs)
|
for (auto & i : inputs)
|
||||||
|
|
|
@ -27,6 +27,8 @@ struct LockedInputs
|
||||||
|
|
||||||
nlohmann::json toJson() const;
|
nlohmann::json toJson() const;
|
||||||
|
|
||||||
|
std::string to_string() const;
|
||||||
|
|
||||||
bool isImmutable() const;
|
bool isImmutable() const;
|
||||||
|
|
||||||
std::optional<LockedInput *> findInput(const InputPath & path);
|
std::optional<LockedInput *> findInput(const InputPath & path);
|
||||||
|
|
|
@ -20,6 +20,7 @@ struct RegisterPrimOp
|
||||||
them. */
|
them. */
|
||||||
/* Load a ValueInitializer from a DSO and return whatever it initializes */
|
/* Load a ValueInitializer from a DSO and return whatever it initializes */
|
||||||
void prim_importNative(EvalState & state, const Pos & pos, Value * * args, Value & v);
|
void prim_importNative(EvalState & state, const Pos & pos, Value * * args, Value & v);
|
||||||
|
|
||||||
/* Execute a program and parse its output */
|
/* Execute a program and parse its output */
|
||||||
void prim_exec(EvalState & state, const Pos & pos, Value * * args, Value & v);
|
void prim_exec(EvalState & state, const Pos & pos, Value * * args, Value & v);
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,45 @@
|
||||||
#include "fetchers/fetchers.hh"
|
#include "fetchers/fetchers.hh"
|
||||||
#include "fetchers/registry.hh"
|
#include "fetchers/registry.hh"
|
||||||
|
|
||||||
|
#include <ctime>
|
||||||
|
#include <iomanip>
|
||||||
|
|
||||||
namespace nix {
|
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)
|
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;
|
std::shared_ptr<const fetchers::Input> input;
|
||||||
PathSet context;
|
PathSet context;
|
||||||
|
@ -25,7 +59,8 @@ static void prim_fetchTree(EvalState & state, const Pos & pos, Value * * args, V
|
||||||
if (attr.value->type == tString)
|
if (attr.value->type == tString)
|
||||||
attrs.emplace(attr.name, attr.value->string.s);
|
attrs.emplace(attr.name, attr.value->string.s);
|
||||||
else
|
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"))
|
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);
|
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)
|
if (state.allowedPaths)
|
||||||
state.allowedPaths->insert(tree.actualPath);
|
state.allowedPaths->insert(tree.actualPath);
|
||||||
|
|
||||||
|
emitTreeAttrs(state, tree, input2, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
static RegisterPrimOp r("fetchTree", 1, prim_fetchTree);
|
static RegisterPrimOp r("fetchTree", 1, prim_fetchTree);
|
||||||
|
|
|
@ -27,10 +27,10 @@ test_tarball() {
|
||||||
|
|
||||||
nix-build -o $TEST_ROOT/result -E "import (fetchTarball file://$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 flakes -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 flakes -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 flakes -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 { 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 '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
|
nix-instantiate --eval -E 'with <fnord/xyzzy>; 1 + 2' -I fnord=file://no-such-tarball$ext
|
||||||
|
|
Loading…
Reference in a new issue