Skip to content

Commit 0a3bd26

Browse files
committed
https://github.com/NixOS/nixpkgs/pull/403839
1 parent 2bbc5bb commit 0a3bd26

File tree

7 files changed

+181
-45
lines changed

7 files changed

+181
-45
lines changed

doc/module-system/module-system.chapter.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,16 @@ A nominal type marker, always `"configuration"`.
116116

117117
The [`class` argument](#module-system-lib-evalModules-param-class).
118118

119+
#### `graph` {#module-system-lib-evalModules-return-value-graph}
120+
121+
Represents all the modules that took part in the evaluation.
122+
It is a list of `ModuleGraph` where `ModuleGraph` is defined as an attribute set with the following attributes:
123+
124+
- `key`: `string` for the purpose of module deduplication and `disabledModules`
125+
- `file`: `string` for the purpose of error messages and warnings
126+
- `imports`: `[ ModuleGraph ]`
127+
- `disabled`: `bool`
128+
119129
## Module arguments {#module-system-module-arguments}
120130

121131
Module arguments are the attribute values passed to modules when they are evaluated.

doc/redirects.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,9 @@
487487
"module-system-lib-evalModules-return-value-_configurationClass": [
488488
"index.html#module-system-lib-evalModules-return-value-_configurationClass"
489489
],
490+
"module-system-lib-evalModules-return-value-graph": [
491+
"index.html#module-system-lib-evalModules-return-value-graph"
492+
],
490493
"part-stdenv": [
491494
"index.html#part-stdenv"
492495
],

lib/modules.nix

Lines changed: 71 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -245,25 +245,26 @@ let
245245
};
246246
};
247247

248-
merged =
249-
let
250-
collected =
251-
collectModules class (specialArgs.modulesPath or "") (regularModules ++ [ internalModule ])
252-
(
253-
{
254-
inherit
255-
lib
256-
options
257-
specialArgs
258-
;
259-
_class = class;
260-
_prefix = prefix;
261-
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;
262-
}
263-
// specialArgs
264-
);
265-
in
266-
mergeModules prefix (reverseList collected);
248+
# This function takes an empty attrset as an argument.
249+
# It could theoretically be replaced with its body,
250+
# but such a binding is avoided to allow for earlier grabage collection.
251+
doCollect =
252+
{ }:
253+
collectModules class (specialArgs.modulesPath or "") (regularModules ++ [ internalModule ]) (
254+
{
255+
inherit
256+
lib
257+
options
258+
specialArgs
259+
;
260+
_class = class;
261+
_prefix = prefix;
262+
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;
263+
}
264+
// specialArgs
265+
);
266+
267+
merged = mergeModules prefix (reverseList (doCollect { }).modules);
267268

268269
options = merged.matchedOptions;
269270

@@ -359,12 +360,13 @@ let
359360
options = checked options;
360361
config = checked (removeAttrs config [ "_module" ]);
361362
_module = checked (config._module);
363+
inherit (doCollect { }) graph;
362364
inherit extendModules type class;
363365
};
364366
in
365367
result;
366368

367-
# collectModules :: (class: String) -> (modulesPath: String) -> (modules: [ Module ]) -> (args: Attrs) -> [ Module ]
369+
# collectModules :: (class: String) -> (modulesPath: String) -> (modules: [ Module ]) -> (args: Attrs) -> ModulesTree
368370
#
369371
# Collects all modules recursively through `import` statements, filtering out
370372
# all modules in disabledModules.
@@ -424,8 +426,37 @@ let
424426
else
425427
m: m;
426428

429+
# isDisabled :: String -> [ { disabled, file } ] -> StructuredModule -> bool
430+
#
431+
# Figures out whether a `StructuredModule` is disabled.
432+
isDisabled =
433+
modulesPath: disabledList:
434+
let
435+
moduleKey =
436+
file: m:
437+
if isString m then
438+
if substring 0 1 m == "/" then m else toString modulesPath + "/" + m
439+
440+
else if isConvertibleWithToString m then
441+
if m ? key && m.key != toString m then
442+
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."
443+
else
444+
toString m
445+
446+
else if m ? key then
447+
m.key
448+
449+
else if isAttrs m then
450+
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."
451+
else
452+
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}.";
453+
454+
disabledKeys = concatMap ({ file, disabled }: map (moduleKey file) disabled) disabledList;
455+
in
456+
structuredModule: elem structuredModule.key disabledKeys;
457+
427458
/**
428-
Collects all modules recursively into the form
459+
Collects all modules recursively into a `[ StructuredModule ]` and a list of disabled modules:
429460
430461
{
431462
disabled = [ <list of disabled modules> ];
@@ -493,36 +524,32 @@ let
493524
modulesPath:
494525
{ disabled, modules }:
495526
let
496-
moduleKey =
497-
file: m:
498-
if isString m then
499-
if substring 0 1 m == "/" then m else toString modulesPath + "/" + m
500-
501-
else if isConvertibleWithToString m then
502-
if m ? key && m.key != toString m then
503-
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."
504-
else
505-
toString m
506-
507-
else if m ? key then
508-
m.key
509-
510-
else if isAttrs m then
511-
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."
512-
else
513-
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}.";
514-
515-
disabledKeys = concatMap ({ file, disabled }: map (moduleKey file) disabled) disabled;
516-
keyFilter = filter (attrs: !elem attrs.key disabledKeys);
527+
keyFilter = filter (attrs: !isDisabled modulesPath disabled attrs);
517528
in
518529
map (attrs: attrs.module) (genericClosure {
519530
startSet = keyFilter modules;
520531
operator = attrs: keyFilter attrs.modules;
521532
});
522533

534+
toGraph =
535+
modulesPath:
536+
{ disabled, modules }:
537+
let
538+
isDisabledModule = isDisabled modulesPath disabled;
539+
540+
toModuleGraph = structuredModule: {
541+
disabled = isDisabledModule structuredModule;
542+
inherit (structuredModule) key;
543+
file = structuredModule.module._file;
544+
imports = map toModuleGraph structuredModule.modules;
545+
};
546+
in
547+
map toModuleGraph (filter (x: x.key != "lib/modules.nix") modules);
523548
in
524-
modulesPath: initialModules: args:
525-
filterModules modulesPath (collectStructuredModules unknownModule "" initialModules args);
549+
modulesPath: initialModules: args: {
550+
modules = filterModules modulesPath (collectStructuredModules unknownModule "" initialModules args);
551+
graph = toGraph modulesPath (collectStructuredModules unknownModule "" initialModules args);
552+
};
526553

527554
/**
528555
Wrap a module with a default location for reporting errors.

lib/tests/modules.sh

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ cd "$DIR"/modules
2020
pass=0
2121
fail=0
2222

23+
local-nix-instantiate() {
24+
nix-instantiate --timeout 1 --eval-only --show-trace --read-write-mode --json "$@"
25+
}
26+
2327
# loc
2428
# prints the location of the call of to the function that calls it
2529
# loc n
@@ -55,7 +59,7 @@ evalConfig() {
5559
local attr=$1
5660
shift
5761
local script="import ./default.nix { modules = [ $* ];}"
58-
nix-instantiate --timeout 1 -E "$script" -A "$attr" --eval-only --show-trace --read-write-mode --json
62+
local-nix-instantiate -E "$script" -A "$attr"
5963
}
6064

6165
reportFailure() {
@@ -106,6 +110,20 @@ globalErrorLogCheck() {
106110
}
107111
}
108112

113+
checkExpression() {
114+
local path=$1
115+
local output
116+
{
117+
output="$(local-nix-instantiate --strict "$path" 2>&1)" && ((++pass))
118+
} || {
119+
logStartFailure
120+
echo "$output"
121+
((++fail))
122+
logFailure
123+
logEndFailure
124+
}
125+
}
126+
109127
checkConfigError() {
110128
local errorContains=$1
111129
local err=""
@@ -337,6 +355,9 @@ checkConfigOutput '^12$' config.value ./declare-coerced-value-unsound.nix
337355
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
338356
checkConfigError 'toInt: Could not convert .* to int' config.value ./declare-coerced-value-unsound.nix ./define-value-string-arbitrary.nix
339357

358+
# Check `graph` attribute
359+
checkExpression './graph/test.nix'
360+
340361
# Check mkAliasOptionModule.
341362
checkConfigOutput '^true$' config.enable ./alias-with-priority.nix
342363
checkConfigOutput '^true$' config.enableAlias ./alias-with-priority.nix

lib/tests/modules/graph/a.nix

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
imports = [
3+
{
4+
imports = [ { } ];
5+
}
6+
];
7+
disabledModules = [ ./b.nix ];
8+
}

lib/tests/modules/graph/b.nix

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
args: {
2+
imports = [ { key = "explicit-key"; } ];
3+
}

lib/tests/modules/graph/test.nix

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
let
2+
lib = import ../../..;
3+
4+
evaluation = lib.evalModules {
5+
modules = [
6+
{ }
7+
(args: { })
8+
./a.nix
9+
./b.nix
10+
];
11+
};
12+
13+
actual = evaluation.graph;
14+
15+
expected = [
16+
{
17+
key = ":anon-1";
18+
file = "<unknown-file>";
19+
imports = [ ];
20+
disabled = false;
21+
}
22+
{
23+
key = ":anon-2";
24+
file = "<unknown-file>";
25+
imports = [ ];
26+
disabled = false;
27+
}
28+
{
29+
key = toString ./a.nix;
30+
file = toString ./a.nix;
31+
imports = [
32+
{
33+
key = "${toString ./a.nix}:anon-1";
34+
file = toString ./a.nix;
35+
imports = [
36+
{
37+
key = "${toString ./a.nix}:anon-1:anon-1";
38+
file = toString ./a.nix;
39+
imports = [ ];
40+
disabled = false;
41+
}
42+
];
43+
disabled = false;
44+
}
45+
];
46+
disabled = false;
47+
}
48+
{
49+
key = toString ./b.nix;
50+
file = toString ./b.nix;
51+
imports = [
52+
{
53+
key = "explicit-key";
54+
file = toString ./b.nix;
55+
imports = [ ];
56+
disabled = false;
57+
}
58+
];
59+
disabled = true;
60+
}
61+
];
62+
in
63+
assert actual == expected;
64+
null

0 commit comments

Comments
 (0)