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:
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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,8 +185,14 @@ 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);
|
||||
auto sInputs = state.symbols.create("inputs");
|
||||
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;
|
||||
state.evalFile(flakeFile, vInfo, true); // FIXME: symlink attack
|
||||
|
|
@ -206,13 +209,9 @@ static Flake getFlake(
|
|||
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);
|
||||
|
||||
|
|
@ -237,6 +236,34 @@ static Flake getFlake(
|
|||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue