lib.evalModules: add graph attribute

Co-authored-by: Ali Jamadi <jamadi1377@gmail.com>
This commit is contained in:
Shahar "Dawn" Or 2025-07-04 23:40:53 -06:00
parent 2c1ec14d12
commit 5186921ded
7 changed files with 150 additions and 23 deletions

View File

@ -116,6 +116,16 @@ A nominal type marker, always `"configuration"`.
The [`class` argument](#module-system-lib-evalModules-param-class). 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 {#module-system-module-arguments}
Module arguments are the attribute values passed to modules when they are evaluated. Module arguments are the attribute values passed to modules when they are evaluated.

View File

@ -487,6 +487,9 @@
"module-system-lib-evalModules-return-value-_configurationClass": [ "module-system-lib-evalModules-return-value-_configurationClass": [
"index.html#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": [ "part-stdenv": [
"index.html#part-stdenv" "index.html#part-stdenv"
], ],

View File

@ -245,11 +245,12 @@ let
}; };
}; };
merged = # This function takes an empty attrset as an argument.
let # It could theoretically be replaced with its body,
collected = # but such a binding is avoided to allow for earlier grabage collection.
collectModules class (specialArgs.modulesPath or "") (regularModules ++ [ internalModule ]) doCollect =
( { }:
collectModules class (specialArgs.modulesPath or "") (regularModules ++ [ internalModule ]) (
{ {
inherit inherit
lib lib
@ -262,8 +263,8 @@ let
} }
// specialArgs // specialArgs
); );
in
mergeModules prefix (reverseList collected); merged = mergeModules prefix (reverseList (doCollect { }).modules);
options = merged.matchedOptions; options = merged.matchedOptions;
@ -359,12 +360,13 @@ let
options = checked options; options = checked options;
config = checked (removeAttrs config [ "_module" ]); config = checked (removeAttrs config [ "_module" ]);
_module = checked (config._module); _module = checked (config._module);
inherit (doCollect { }) graph;
inherit extendModules type class; inherit extendModules type class;
}; };
in in
result; 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 # Collects all modules recursively through `import` statements, filtering out
# all modules in disabledModules. # all modules in disabledModules.
@ -529,9 +531,25 @@ let
operator = attrs: keyFilter attrs.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 in
modulesPath: initialModules: args: map toModuleGraph (filter (x: x.key != "lib/modules.nix") modules);
filterModules modulesPath (collectStructuredModules unknownModule "" initialModules args); in
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. Wrap a module with a default location for reporting errors.

View File

@ -20,6 +20,10 @@ cd "$DIR"/modules
pass=0 pass=0
fail=0 fail=0
local-nix-instantiate() {
nix-instantiate --timeout 1 --eval-only --show-trace --read-write-mode --json "$@"
}
# loc # loc
# prints the location of the call of to the function that calls it # prints the location of the call of to the function that calls it
# loc n # loc n
@ -55,7 +59,7 @@ evalConfig() {
local attr=$1 local attr=$1
shift shift
local script="import ./default.nix { modules = [ $* ];}" 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() { 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() { checkConfigError() {
local errorContains=$1 local errorContains=$1
local err="" 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 '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 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. # Check mkAliasOptionModule.
checkConfigOutput '^true$' config.enable ./alias-with-priority.nix checkConfigOutput '^true$' config.enable ./alias-with-priority.nix
checkConfigOutput '^true$' config.enableAlias ./alias-with-priority.nix checkConfigOutput '^true$' config.enableAlias ./alias-with-priority.nix

View File

@ -0,0 +1,8 @@
{
imports = [
{
imports = [ { } ];
}
];
disabledModules = [ ./b.nix ];
}

View File

@ -0,0 +1,3 @@
args: {
imports = [ { key = "explicit-key"; } ];
}

View File

@ -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