lib.evalModules: add graph attribute

Co-authored-by: Ali Jamadi <jamadi1377@gmail.com>

Changed files
+150 -23
doc
lib
tests
modules
+10
doc/module-system/module-system.chapter.md
···
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.
+3
doc/redirects.json
···
"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"
],
+40 -22
lib/modules.nix
···
};
};
-
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;
···
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.
···
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
lib/tests/modules.sh
···
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
···
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() {
···
fi
return 1
}
+
}
+
+
checkExpression() {
+
local path=$1
+
local output
+
{
+
output="$(local-nix-instantiate --strict "$path" 2>&1)" && ((++pass))
+
} || {
+
logStartFailure
+
echo "$output"
+
((++fail))
+
logFailure
+
logEndFailure
+
}
}
checkConfigError() {
···
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
+8
lib/tests/modules/graph/a.nix
···
+
{
+
imports = [
+
{
+
imports = [ { } ];
+
}
+
];
+
disabledModules = [ ./b.nix ];
+
}
+3
lib/tests/modules/graph/b.nix
···
+
args: {
+
imports = [ { key = "explicit-key"; } ];
+
}
+64
lib/tests/modules/graph/test.nix
···
+
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