lib.evalModules: add graph attribute (#403839)
This commit is contained in:
@@ -116,6 +116,16 @@ A nominal type marker, always `"configuration"`.
|
||||
|
||||
The [`class` argument](#module-system-lib-evalModules-param-class).
|
||||
|
||||
#### `graph` {#module-system-lib-evalModules-return-value-graph}
|
||||
|
||||
Represents all the modules that took part in the evaluation.
|
||||
It is a list of `ModuleGraph` where `ModuleGraph` is defined as an attribute set with the following attributes:
|
||||
|
||||
- `key`: `string` for the purpose of module deduplication and `disabledModules`
|
||||
- `file`: `string` for the purpose of error messages and warnings
|
||||
- `imports`: `[ ModuleGraph ]`
|
||||
- `disabled`: `bool`
|
||||
|
||||
## Module arguments {#module-system-module-arguments}
|
||||
|
||||
Module arguments are the attribute values passed to modules when they are evaluated.
|
||||
|
||||
@@ -487,6 +487,9 @@
|
||||
"module-system-lib-evalModules-return-value-_configurationClass": [
|
||||
"index.html#module-system-lib-evalModules-return-value-_configurationClass"
|
||||
],
|
||||
"module-system-lib-evalModules-return-value-graph": [
|
||||
"index.html#module-system-lib-evalModules-return-value-graph"
|
||||
],
|
||||
"part-stdenv": [
|
||||
"index.html#part-stdenv"
|
||||
],
|
||||
|
||||
+71
-44
@@ -245,25 +245,26 @@ let
|
||||
};
|
||||
};
|
||||
|
||||
merged =
|
||||
let
|
||||
collected =
|
||||
collectModules class (specialArgs.modulesPath or "") (regularModules ++ [ internalModule ])
|
||||
(
|
||||
{
|
||||
inherit
|
||||
lib
|
||||
options
|
||||
specialArgs
|
||||
;
|
||||
_class = class;
|
||||
_prefix = prefix;
|
||||
config = addErrorContext "if you get an infinite recursion here, you probably reference `config` in `imports`. If you are trying to achieve a conditional import behavior dependent on `config`, consider importing unconditionally, and using `mkEnableOption` and `mkIf` to control its effect." config;
|
||||
}
|
||||
// specialArgs
|
||||
);
|
||||
in
|
||||
mergeModules prefix (reverseList collected);
|
||||
# This function takes an empty attrset as an argument.
|
||||
# It could theoretically be replaced with its body,
|
||||
# but such a binding is avoided to allow for earlier grabage collection.
|
||||
doCollect =
|
||||
{ }:
|
||||
collectModules class (specialArgs.modulesPath or "") (regularModules ++ [ internalModule ]) (
|
||||
{
|
||||
inherit
|
||||
lib
|
||||
options
|
||||
specialArgs
|
||||
;
|
||||
_class = class;
|
||||
_prefix = prefix;
|
||||
config = addErrorContext "if you get an infinite recursion here, you probably reference `config` in `imports`. If you are trying to achieve a conditional import behavior dependent on `config`, consider importing unconditionally, and using `mkEnableOption` and `mkIf` to control its effect." config;
|
||||
}
|
||||
// specialArgs
|
||||
);
|
||||
|
||||
merged = mergeModules prefix (reverseList (doCollect { }).modules);
|
||||
|
||||
options = merged.matchedOptions;
|
||||
|
||||
@@ -359,12 +360,13 @@ let
|
||||
options = checked options;
|
||||
config = checked (removeAttrs config [ "_module" ]);
|
||||
_module = checked (config._module);
|
||||
inherit (doCollect { }) graph;
|
||||
inherit extendModules type class;
|
||||
};
|
||||
in
|
||||
result;
|
||||
|
||||
# collectModules :: (class: String) -> (modulesPath: String) -> (modules: [ Module ]) -> (args: Attrs) -> [ Module ]
|
||||
# collectModules :: (class: String) -> (modulesPath: String) -> (modules: [ Module ]) -> (args: Attrs) -> ModulesTree
|
||||
#
|
||||
# Collects all modules recursively through `import` statements, filtering out
|
||||
# all modules in disabledModules.
|
||||
@@ -424,8 +426,37 @@ let
|
||||
else
|
||||
m: m;
|
||||
|
||||
# isDisabled :: String -> [ { disabled, file } ] -> StructuredModule -> bool
|
||||
#
|
||||
# Figures out whether a `StructuredModule` is disabled.
|
||||
isDisabled =
|
||||
modulesPath: disabledList:
|
||||
let
|
||||
moduleKey =
|
||||
file: m:
|
||||
if isString m then
|
||||
if substring 0 1 m == "/" then m else toString modulesPath + "/" + m
|
||||
|
||||
else if isConvertibleWithToString m then
|
||||
if m ? key && m.key != toString m then
|
||||
throw "Module `${file}` contains a disabledModules item that is an attribute set that can be converted to a string (${toString m}) but also has a `.key` attribute (${m.key}) with a different value. This makes it ambiguous which module should be disabled."
|
||||
else
|
||||
toString m
|
||||
|
||||
else if m ? key then
|
||||
m.key
|
||||
|
||||
else if isAttrs m then
|
||||
throw "Module `${file}` contains a disabledModules item that is an attribute set, presumably a module, that does not have a `key` attribute. This means that the module system doesn't have any means to identify the module that should be disabled. Make sure that you've put the correct value in disabledModules: a string path relative to modulesPath, a path value, or an attribute set with a `key` attribute."
|
||||
else
|
||||
throw "Each disabledModules item must be a path, string, or a attribute set with a key attribute, or a value supported by toString. However, one of the disabledModules items in `${toString file}` is none of that, but is of type ${typeOf m}.";
|
||||
|
||||
disabledKeys = concatMap ({ file, disabled }: map (moduleKey file) disabled) disabledList;
|
||||
in
|
||||
structuredModule: elem structuredModule.key disabledKeys;
|
||||
|
||||
/**
|
||||
Collects all modules recursively into the form
|
||||
Collects all modules recursively into a `[ StructuredModule ]` and a list of disabled modules:
|
||||
|
||||
{
|
||||
disabled = [ <list of disabled modules> ];
|
||||
@@ -493,36 +524,32 @@ let
|
||||
modulesPath:
|
||||
{ disabled, modules }:
|
||||
let
|
||||
moduleKey =
|
||||
file: m:
|
||||
if isString m then
|
||||
if substring 0 1 m == "/" then m else toString modulesPath + "/" + m
|
||||
|
||||
else if isConvertibleWithToString m then
|
||||
if m ? key && m.key != toString m then
|
||||
throw "Module `${file}` contains a disabledModules item that is an attribute set that can be converted to a string (${toString m}) but also has a `.key` attribute (${m.key}) with a different value. This makes it ambiguous which module should be disabled."
|
||||
else
|
||||
toString m
|
||||
|
||||
else if m ? key then
|
||||
m.key
|
||||
|
||||
else if isAttrs m then
|
||||
throw "Module `${file}` contains a disabledModules item that is an attribute set, presumably a module, that does not have a `key` attribute. This means that the module system doesn't have any means to identify the module that should be disabled. Make sure that you've put the correct value in disabledModules: a string path relative to modulesPath, a path value, or an attribute set with a `key` attribute."
|
||||
else
|
||||
throw "Each disabledModules item must be a path, string, or a attribute set with a key attribute, or a value supported by toString. However, one of the disabledModules items in `${toString file}` is none of that, but is of type ${typeOf m}.";
|
||||
|
||||
disabledKeys = concatMap ({ file, disabled }: map (moduleKey file) disabled) disabled;
|
||||
keyFilter = filter (attrs: !elem attrs.key disabledKeys);
|
||||
keyFilter = filter (attrs: !isDisabled modulesPath disabled attrs);
|
||||
in
|
||||
map (attrs: attrs.module) (genericClosure {
|
||||
startSet = keyFilter modules;
|
||||
operator = attrs: keyFilter attrs.modules;
|
||||
});
|
||||
|
||||
toGraph =
|
||||
modulesPath:
|
||||
{ disabled, modules }:
|
||||
let
|
||||
isDisabledModule = isDisabled modulesPath disabled;
|
||||
|
||||
toModuleGraph = structuredModule: {
|
||||
disabled = isDisabledModule structuredModule;
|
||||
inherit (structuredModule) key;
|
||||
file = structuredModule.module._file;
|
||||
imports = map toModuleGraph structuredModule.modules;
|
||||
};
|
||||
in
|
||||
map toModuleGraph (filter (x: x.key != "lib/modules.nix") modules);
|
||||
in
|
||||
modulesPath: initialModules: args:
|
||||
filterModules modulesPath (collectStructuredModules unknownModule "" initialModules args);
|
||||
modulesPath: initialModules: args: {
|
||||
modules = filterModules modulesPath (collectStructuredModules unknownModule "" initialModules args);
|
||||
graph = toGraph modulesPath (collectStructuredModules unknownModule "" initialModules args);
|
||||
};
|
||||
|
||||
/**
|
||||
Wrap a module with a default location for reporting errors.
|
||||
|
||||
+22
-1
@@ -20,6 +20,10 @@ cd "$DIR"/modules
|
||||
pass=0
|
||||
fail=0
|
||||
|
||||
local-nix-instantiate() {
|
||||
nix-instantiate --timeout 1 --eval-only --show-trace --read-write-mode --json "$@"
|
||||
}
|
||||
|
||||
# loc
|
||||
# prints the location of the call of to the function that calls it
|
||||
# loc n
|
||||
@@ -55,7 +59,7 @@ evalConfig() {
|
||||
local attr=$1
|
||||
shift
|
||||
local script="import ./default.nix { modules = [ $* ];}"
|
||||
nix-instantiate --timeout 1 -E "$script" -A "$attr" --eval-only --show-trace --read-write-mode --json
|
||||
local-nix-instantiate -E "$script" -A "$attr"
|
||||
}
|
||||
|
||||
reportFailure() {
|
||||
@@ -106,6 +110,20 @@ globalErrorLogCheck() {
|
||||
}
|
||||
}
|
||||
|
||||
checkExpression() {
|
||||
local path=$1
|
||||
local output
|
||||
{
|
||||
output="$(local-nix-instantiate --strict "$path" 2>&1)" && ((++pass))
|
||||
} || {
|
||||
logStartFailure
|
||||
echo "$output"
|
||||
((++fail))
|
||||
logFailure
|
||||
logEndFailure
|
||||
}
|
||||
}
|
||||
|
||||
checkConfigError() {
|
||||
local errorContains=$1
|
||||
local err=""
|
||||
@@ -337,6 +355,9 @@ checkConfigOutput '^12$' config.value ./declare-coerced-value-unsound.nix
|
||||
checkConfigError 'A definition for option .* is not of type .*. Definition values:\n\s*- In .*: "1000"' config.value ./declare-coerced-value-unsound.nix ./define-value-string-bigint.nix
|
||||
checkConfigError 'toInt: Could not convert .* to int' config.value ./declare-coerced-value-unsound.nix ./define-value-string-arbitrary.nix
|
||||
|
||||
# Check `graph` attribute
|
||||
checkExpression './graph/test.nix'
|
||||
|
||||
# Check mkAliasOptionModule.
|
||||
checkConfigOutput '^true$' config.enable ./alias-with-priority.nix
|
||||
checkConfigOutput '^true$' config.enableAlias ./alias-with-priority.nix
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
imports = [
|
||||
{
|
||||
imports = [ { } ];
|
||||
}
|
||||
];
|
||||
disabledModules = [ ./b.nix ];
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
args: {
|
||||
imports = [ { key = "explicit-key"; } ];
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
let
|
||||
lib = import ../../..;
|
||||
|
||||
evaluation = lib.evalModules {
|
||||
modules = [
|
||||
{ }
|
||||
(args: { })
|
||||
./a.nix
|
||||
./b.nix
|
||||
];
|
||||
};
|
||||
|
||||
actual = evaluation.graph;
|
||||
|
||||
expected = [
|
||||
{
|
||||
key = ":anon-1";
|
||||
file = "<unknown-file>";
|
||||
imports = [ ];
|
||||
disabled = false;
|
||||
}
|
||||
{
|
||||
key = ":anon-2";
|
||||
file = "<unknown-file>";
|
||||
imports = [ ];
|
||||
disabled = false;
|
||||
}
|
||||
{
|
||||
key = toString ./a.nix;
|
||||
file = toString ./a.nix;
|
||||
imports = [
|
||||
{
|
||||
key = "${toString ./a.nix}:anon-1";
|
||||
file = toString ./a.nix;
|
||||
imports = [
|
||||
{
|
||||
key = "${toString ./a.nix}:anon-1:anon-1";
|
||||
file = toString ./a.nix;
|
||||
imports = [ ];
|
||||
disabled = false;
|
||||
}
|
||||
];
|
||||
disabled = false;
|
||||
}
|
||||
];
|
||||
disabled = false;
|
||||
}
|
||||
{
|
||||
key = toString ./b.nix;
|
||||
file = toString ./b.nix;
|
||||
imports = [
|
||||
{
|
||||
key = "explicit-key";
|
||||
file = toString ./b.nix;
|
||||
imports = [ ];
|
||||
disabled = false;
|
||||
}
|
||||
];
|
||||
disabled = true;
|
||||
}
|
||||
];
|
||||
in
|
||||
assert actual == expected;
|
||||
null
|
||||
Reference in New Issue
Block a user