1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-11-20 09:19:36 +01:00

Support flakes in TOML format

So instead of a 'flake.nix', flakes can now contain a 'nix.toml' file
like this:

  description = "My own Hello World"

  [inputs]
  configs.url = "github:tweag/nix-ux/configs?dir=configs"

  [my-hello]
  extends = [ "configs#hello" ]
  doc = '''
    A specialized version of the Hello package!
  '''
  who = "Springfield"

'my-hello' defines an output named 'modules.my-hello', which can be
built as follows:

  $ nix build /path/to/flake#my-hello

  $ ./result/bin/hello
  Hello Springfield
This commit is contained in:
Eelco Dolstra 2020-09-23 14:57:47 +02:00
parent 08992ab6bc
commit 1dc3f5355a
5 changed files with 134 additions and 55 deletions

View file

@ -1,11 +1,13 @@
with builtins;
lockFileStr: rootSrc: rootSubdir:
let
lockFile = builtins.fromJSON lockFileStr;
lockFile = fromJSON lockFileStr;
allNodes =
builtins.mapAttrs
mapAttrs
(key: node:
let
@ -16,9 +18,55 @@ let
subdir = if key == lockFile.root then rootSubdir else node.locked.dir or "";
flake = import (sourceInfo + (if subdir != "" then "/" else "") + subdir + "/flake.nix");
flakeDir = sourceInfo + (if subdir != "" then "/" else "") + subdir;
inputs = builtins.mapAttrs
flake =
if pathExists (flakeDir + "/flake.nix")
then import (flakeDir + "/flake.nix")
else if pathExists (flakeDir + "/nix.toml")
then
# Convert nix.toml to a flake containing a 'modules'
# output.
let
toml = fromTOML (readFile (flakeDir + "/nix.toml"));
in {
inputs = toml.inputs or {};
outputs = inputs: {
modules =
listToAttrs (
map (moduleName:
let
m = toml.${moduleName};
in {
name = moduleName;
value = module {
extends =
map (flakeRef:
let
tokens = match ''(.*)#(.*)'' flakeRef;
in
assert tokens != null;
inputs.${elemAt tokens 0}.modules.${elemAt tokens 1}
) (m.extends or []);
config = { config }: listToAttrs (map
(optionName:
{ name = optionName;
value = m.${optionName};
}
)
(filter
(n: n != "extends" && n != "doc")
(attrNames m)));
};
})
(filter
(n: isAttrs toml.${n} && n != "inputs")
(attrNames toml)));
};
}
else throw "flake does not contain a 'flake.nix' or 'nix.toml'";
inputs = mapAttrs
(inputName: inputSpec: allNodes.${resolveInput inputSpec})
(node.inputs or {});
@ -26,7 +74,7 @@ let
# either a node name, or a 'follows' path from the root
# node.
resolveInput = inputSpec:
if builtins.isList inputSpec
if isList inputSpec
then getInputByPath lockFile.root inputSpec
else inputSpec;
@ -38,15 +86,15 @@ let
else
getInputByPath
# Since this could be a 'follows' input, call resolveInput.
(resolveInput lockFile.nodes.${nodeName}.inputs.${builtins.head path})
(builtins.tail path);
(resolveInput lockFile.nodes.${nodeName}.inputs.${head path})
(tail path);
outputs = flake.outputs (inputs // { self = result; });
result = outputs // sourceInfo // { inherit inputs; inherit outputs; inherit sourceInfo; };
in
if node.flake or true then
assert builtins.isFunction flake.outputs;
assert isFunction flake.outputs;
result
else
sourceInfo

View file

@ -175,11 +175,8 @@ static Flake getFlake(
auto [sourceInfo, resolvedRef, lockedRef] = fetchOrSubstituteTree(
state, originalRef, allowLookup, flakeCache);
// Guard against symlink attacks.
auto flakeFile = canonPath(sourceInfo.actualPath + "/" + lockedRef.subdir + "/flake.nix");
if (!isInDir(flakeFile, sourceInfo.actualPath))
throw Error("'flake.nix' file of flake '%s' escapes from '%s'",
lockedRef, state.store->printStorePath(sourceInfo.storePath));
auto tomlFile = canonPath(sourceInfo.actualPath + "/" + lockedRef.subdir + "/nix.toml");
Flake flake {
.originalRef = originalRef,
@ -188,55 +185,85 @@ static Flake getFlake(
.sourceInfo = std::make_shared<fetchers::Tree>(std::move(sourceInfo))
};
if (!pathExists(flakeFile))
throw Error("source tree referenced by '%s' does not contain a '%s/flake.nix' file", lockedRef, lockedRef.subdir);
Value vInfo;
state.evalFile(flakeFile, vInfo, true); // FIXME: symlink attack
expectType(state, tAttrs, vInfo, Pos(foFile, state.symbols.create(flakeFile), 0, 0));
auto sEdition = state.symbols.create("edition"); // FIXME: remove soon
if (vInfo.attrs->get(sEdition))
warn("flake '%s' has deprecated attribute 'edition'", lockedRef);
if (auto description = vInfo.attrs->get(state.sDescription)) {
expectType(state, tString, *description->value, *description->pos);
flake.description = description->value->string.s;
}
auto sInputs = state.symbols.create("inputs");
if (auto inputs = vInfo.attrs->get(sInputs))
flake.inputs = parseFlakeInputs(state, inputs->value, *inputs->pos);
auto sOutputs = state.symbols.create("outputs");
if (auto outputs = vInfo.attrs->get(sOutputs)) {
expectType(state, tLambda, *outputs->value, *outputs->pos);
if (pathExists(flakeFile)) {
// Guard against symlink attacks.
if (!isInDir(flakeFile, sourceInfo.actualPath))
throw Error("'flake.nix' file of flake '%s' escapes from '%s'",
lockedRef, state.store->printStorePath(sourceInfo.storePath));
if (outputs->value->lambda.fun->matchAttrs) {
for (auto & formal : outputs->value->lambda.fun->formals->formals) {
if (formal.name != state.sSelf)
flake.inputs.emplace(formal.name, FlakeInput {
.ref = parseFlakeRef(formal.name)
});
}
Value vInfo;
state.evalFile(flakeFile, vInfo, true); // FIXME: symlink attack
expectType(state, tAttrs, vInfo, Pos(foFile, state.symbols.create(flakeFile), 0, 0));
auto sEdition = state.symbols.create("edition"); // FIXME: remove soon
if (vInfo.attrs->get(sEdition))
warn("flake '%s' has deprecated attribute 'edition'", lockedRef);
if (auto description = vInfo.attrs->get(state.sDescription)) {
expectType(state, tString, *description->value, *description->pos);
flake.description = description->value->string.s;
}
} else
throw Error("flake '%s' lacks attribute 'outputs'", lockedRef);
if (auto inputs = vInfo.attrs->get(sInputs))
flake.inputs = parseFlakeInputs(state, inputs->value, *inputs->pos);
if (auto outputs = vInfo.attrs->get(sOutputs)) {
expectType(state, tLambda, *outputs->value, *outputs->pos);
if (outputs->value->lambda.fun->matchAttrs) {
for (auto & formal : outputs->value->lambda.fun->formals->formals) {
if (formal.name != state.sSelf)
flake.inputs.emplace(formal.name, FlakeInput {
.ref = parseFlakeRef(formal.name)
});
}
}
} else
throw Error("flake '%s' lacks attribute 'outputs'", lockedRef);
for (auto & attr : *vInfo.attrs) {
if (attr.name != sEdition &&
attr.name != state.sDescription &&
attr.name != sInputs &&
attr.name != sOutputs)
throw Error("flake '%s' has an unsupported attribute '%s', at %s",
lockedRef, attr.name, *attr.pos);
}
for (auto & attr : *vInfo.attrs) {
if (attr.name != sEdition &&
attr.name != state.sDescription &&
attr.name != sInputs &&
attr.name != sOutputs)
throw Error("flake '%s' has an unsupported attribute '%s', at %s",
lockedRef, attr.name, *attr.pos);
}
else if (pathExists(tomlFile)) {
// Guard against symlink attacks.
if (!isInDir(tomlFile, sourceInfo.actualPath))
throw Error("'nix.toml' file of flake '%s' escapes from '%s'",
lockedRef, state.store->printStorePath(sourceInfo.storePath));
auto vToml = state.allocValue();
mkString(*vToml, readFile(tomlFile));
auto vFlake = state.allocValue();
prim_fromTOML(state, noPos, &vToml, *vFlake);
state.forceAttrs(*vFlake);
if (auto description = vFlake->attrs->get(state.sDescription)) {
expectType(state, tString, *description->value, *description->pos);
flake.description = description->value->string.s;
}
if (auto inputs = vFlake->attrs->get(sInputs))
flake.inputs = parseFlakeInputs(state, inputs->value, *inputs->pos);
// FIXME: complain about unknown attributes.
}
else
throw Error("source tree referenced by '%1%' does not contain a '%2%/flake.nix' or '%2%/nix.toml' file %3%", lockedRef, lockedRef.subdir, flakeFile);
return flake;
}

View file

@ -117,8 +117,8 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
if (!S_ISDIR(lstat(path).st_mode))
throw BadURL("path '%s' is not a flake (because it's not a directory)", path);
if (!allowMissing && !pathExists(path + "/flake.nix"))
throw BadURL("path '%s' is not a flake (because it doesn't contain a 'flake.nix' file)", path);
if (!allowMissing && !(pathExists(path + "/flake.nix") || pathExists(path + "/nix.toml")))
throw BadURL("path '%s' is not a flake (because it doesn't contain a 'flake.nix' or 'nix.toml' file)", path);
auto flakeRoot = path;
std::string subdir;

View file

@ -41,4 +41,6 @@ void prim_importNative(EvalState & state, const Pos & pos, Value * * args, Value
/* Execute a program and parse its output */
void prim_exec(EvalState & state, const Pos & pos, Value * * args, Value & v);
void prim_fromTOML(EvalState & state, const Pos & pos, Value * * args, Value & v);
}

View file

@ -5,7 +5,7 @@
namespace nix {
static void prim_fromTOML(EvalState & state, const Pos & pos, Value * * args, Value & v)
void prim_fromTOML(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
using namespace cpptoml;
@ -17,6 +17,8 @@ static void prim_fromTOML(EvalState & state, const Pos & pos, Value * * args, Va
visit = [&](Value & v, std::shared_ptr<base> t) {
// FIXME: set attribute positions
if (auto t2 = t->as_table()) {
size_t size = 0;