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:
parent
08992ab6bc
commit
1dc3f5355a
5 changed files with 134 additions and 55 deletions
|
|
@ -1,11 +1,13 @@
|
||||||
|
with builtins;
|
||||||
|
|
||||||
lockFileStr: rootSrc: rootSubdir:
|
lockFileStr: rootSrc: rootSubdir:
|
||||||
|
|
||||||
let
|
let
|
||||||
|
|
||||||
lockFile = builtins.fromJSON lockFileStr;
|
lockFile = fromJSON lockFileStr;
|
||||||
|
|
||||||
allNodes =
|
allNodes =
|
||||||
builtins.mapAttrs
|
mapAttrs
|
||||||
(key: node:
|
(key: node:
|
||||||
let
|
let
|
||||||
|
|
||||||
|
|
@ -16,9 +18,55 @@ let
|
||||||
|
|
||||||
subdir = if key == lockFile.root then rootSubdir else node.locked.dir or "";
|
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})
|
(inputName: inputSpec: allNodes.${resolveInput inputSpec})
|
||||||
(node.inputs or {});
|
(node.inputs or {});
|
||||||
|
|
||||||
|
|
@ -26,7 +74,7 @@ let
|
||||||
# either a node name, or a 'follows' path from the root
|
# either a node name, or a 'follows' path from the root
|
||||||
# node.
|
# node.
|
||||||
resolveInput = inputSpec:
|
resolveInput = inputSpec:
|
||||||
if builtins.isList inputSpec
|
if isList inputSpec
|
||||||
then getInputByPath lockFile.root inputSpec
|
then getInputByPath lockFile.root inputSpec
|
||||||
else inputSpec;
|
else inputSpec;
|
||||||
|
|
||||||
|
|
@ -38,15 +86,15 @@ let
|
||||||
else
|
else
|
||||||
getInputByPath
|
getInputByPath
|
||||||
# Since this could be a 'follows' input, call resolveInput.
|
# Since this could be a 'follows' input, call resolveInput.
|
||||||
(resolveInput lockFile.nodes.${nodeName}.inputs.${builtins.head path})
|
(resolveInput lockFile.nodes.${nodeName}.inputs.${head path})
|
||||||
(builtins.tail path);
|
(tail path);
|
||||||
|
|
||||||
outputs = flake.outputs (inputs // { self = result; });
|
outputs = flake.outputs (inputs // { self = result; });
|
||||||
|
|
||||||
result = outputs // sourceInfo // { inherit inputs; inherit outputs; inherit sourceInfo; };
|
result = outputs // sourceInfo // { inherit inputs; inherit outputs; inherit sourceInfo; };
|
||||||
in
|
in
|
||||||
if node.flake or true then
|
if node.flake or true then
|
||||||
assert builtins.isFunction flake.outputs;
|
assert isFunction flake.outputs;
|
||||||
result
|
result
|
||||||
else
|
else
|
||||||
sourceInfo
|
sourceInfo
|
||||||
|
|
|
||||||
|
|
@ -175,11 +175,8 @@ static Flake getFlake(
|
||||||
auto [sourceInfo, resolvedRef, lockedRef] = fetchOrSubstituteTree(
|
auto [sourceInfo, resolvedRef, lockedRef] = fetchOrSubstituteTree(
|
||||||
state, originalRef, allowLookup, flakeCache);
|
state, originalRef, allowLookup, flakeCache);
|
||||||
|
|
||||||
// Guard against symlink attacks.
|
|
||||||
auto flakeFile = canonPath(sourceInfo.actualPath + "/" + lockedRef.subdir + "/flake.nix");
|
auto flakeFile = canonPath(sourceInfo.actualPath + "/" + lockedRef.subdir + "/flake.nix");
|
||||||
if (!isInDir(flakeFile, sourceInfo.actualPath))
|
auto tomlFile = canonPath(sourceInfo.actualPath + "/" + lockedRef.subdir + "/nix.toml");
|
||||||
throw Error("'flake.nix' file of flake '%s' escapes from '%s'",
|
|
||||||
lockedRef, state.store->printStorePath(sourceInfo.storePath));
|
|
||||||
|
|
||||||
Flake flake {
|
Flake flake {
|
||||||
.originalRef = originalRef,
|
.originalRef = originalRef,
|
||||||
|
|
@ -188,8 +185,14 @@ static Flake getFlake(
|
||||||
.sourceInfo = std::make_shared<fetchers::Tree>(std::move(sourceInfo))
|
.sourceInfo = std::make_shared<fetchers::Tree>(std::move(sourceInfo))
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!pathExists(flakeFile))
|
auto sInputs = state.symbols.create("inputs");
|
||||||
throw Error("source tree referenced by '%s' does not contain a '%s/flake.nix' file", lockedRef, lockedRef.subdir);
|
auto sOutputs = state.symbols.create("outputs");
|
||||||
|
|
||||||
|
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));
|
||||||
|
|
||||||
Value vInfo;
|
Value vInfo;
|
||||||
state.evalFile(flakeFile, vInfo, true); // FIXME: symlink attack
|
state.evalFile(flakeFile, vInfo, true); // FIXME: symlink attack
|
||||||
|
|
@ -206,13 +209,9 @@ static Flake getFlake(
|
||||||
flake.description = description->value->string.s;
|
flake.description = description->value->string.s;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto sInputs = state.symbols.create("inputs");
|
|
||||||
|
|
||||||
if (auto inputs = vInfo.attrs->get(sInputs))
|
if (auto inputs = vInfo.attrs->get(sInputs))
|
||||||
flake.inputs = parseFlakeInputs(state, inputs->value, *inputs->pos);
|
flake.inputs = parseFlakeInputs(state, inputs->value, *inputs->pos);
|
||||||
|
|
||||||
auto sOutputs = state.symbols.create("outputs");
|
|
||||||
|
|
||||||
if (auto outputs = vInfo.attrs->get(sOutputs)) {
|
if (auto outputs = vInfo.attrs->get(sOutputs)) {
|
||||||
expectType(state, tLambda, *outputs->value, *outputs->pos);
|
expectType(state, tLambda, *outputs->value, *outputs->pos);
|
||||||
|
|
||||||
|
|
@ -237,6 +236,34 @@ static Flake getFlake(
|
||||||
lockedRef, attr.name, *attr.pos);
|
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;
|
return flake;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -117,8 +117,8 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
|
||||||
if (!S_ISDIR(lstat(path).st_mode))
|
if (!S_ISDIR(lstat(path).st_mode))
|
||||||
throw BadURL("path '%s' is not a flake (because it's not a directory)", path);
|
throw BadURL("path '%s' is not a flake (because it's not a directory)", path);
|
||||||
|
|
||||||
if (!allowMissing && !pathExists(path + "/flake.nix"))
|
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' file)", path);
|
throw BadURL("path '%s' is not a flake (because it doesn't contain a 'flake.nix' or 'nix.toml' file)", path);
|
||||||
|
|
||||||
auto flakeRoot = path;
|
auto flakeRoot = path;
|
||||||
std::string subdir;
|
std::string subdir;
|
||||||
|
|
|
||||||
|
|
@ -41,4 +41,6 @@ void prim_importNative(EvalState & state, const Pos & pos, Value * * args, Value
|
||||||
/* 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);
|
||||||
|
|
||||||
|
void prim_fromTOML(EvalState & state, const Pos & pos, Value * * args, Value & v);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
namespace nix {
|
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;
|
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) {
|
visit = [&](Value & v, std::shared_ptr<base> t) {
|
||||||
|
|
||||||
|
// FIXME: set attribute positions
|
||||||
|
|
||||||
if (auto t2 = t->as_table()) {
|
if (auto t2 = t->as_table()) {
|
||||||
|
|
||||||
size_t size = 0;
|
size_t size = 0;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue