Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,7 @@ let
fixupOptionType
mkIf
mkAssert
mkDefinition
mkMerge
mkOverride
mkOptionDefault
Expand Down
20 changes: 16 additions & 4 deletions lib/modules.nix
Original file line number Diff line number Diff line change
Expand Up @@ -1097,10 +1097,16 @@ let
# Process mkMerge and mkIf properties.
defs' = concatMap (
m:
map (value: {
inherit (m) file;
inherit value;
}) (addErrorContext "while evaluating definitions from `${m.file}':" (dischargeProperties m.value))
map (
value:
if value._type or null == "definition" then
value
else
{
inherit (m) file;
inherit value;
}
) (addErrorContext "while evaluating definitions from `${m.file}':" (dischargeProperties m.value))
) defs;

# Process mkOverride properties.
Expand Down Expand Up @@ -1365,6 +1371,11 @@ let
inherit contents;
};

/**
Return a definition with file location information.
*/
mkDefinition = args@{ file, value, ... }: args // { _type = "definition"; };

mkOverride = priority: content: {
_type = "override";
inherit priority content;
Expand Down Expand Up @@ -2095,6 +2106,7 @@ private
mkBefore
mkChangedOptionModule
mkDefault
mkDefinition
mkDerivedConfig
mkFixStrictness
mkForce
Expand Down
8 changes: 8 additions & 0 deletions lib/tests/modules.sh
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,14 @@ checkConfigError 'The option .conflictingPathOptionType. in .*/pathWith.nix. is
# types.pathWith { inStore = true; absolute = false; }
checkConfigError 'In pathWith, inStore means the path must be absolute' config.impossiblePathOptionType ./pathWith.nix

# mkDefinition
# check that mkDefinition 'file' is printed in the error message
checkConfigError 'Cannot merge definitions.*\n\s*- In .file.*\n\s*- In .other.*' config.conflict ./mkDefinition.nix
checkConfigError 'A definition for option .viaOptionDefault. is not of type .boolean.*' config.viaOptionDefault ./mkDefinition.nix
checkConfigOutput '^true$' config.viaConfig ./mkDefinition.nix
checkConfigOutput '^true$' config.mkMerge ./mkDefinition.nix
checkConfigOutput '^true$' config.mkForce ./mkDefinition.nix

cat <<EOF
====== module tests ======
$pass Pass
Expand Down
71 changes: 71 additions & 0 deletions lib/tests/modules/mkDefinition.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
{ lib, ... }:
let
inherit (lib)
mkOption
mkDefinition
mkOptionDefault
;
in
{
imports = [
{
_file = "file";
options.conflict = mkOption {
default = 1;
};
config.conflict = mkDefinition {
file = "other";
value = mkOptionDefault 42;
};
}
{
# Check that mkDefinition works within 'config'
options.viaConfig = mkOption { };
config.viaConfig = mkDefinition {
file = "other";
value = true;
};
}
{
# Check mkMerge can wrap mkDefinitions
# Not the other way around
options.mkMerge = mkOption {
type = lib.types.bool;
};
config.mkMerge = lib.mkMerge [
(mkDefinition {
file = "a.nix";
value = true;
})
(mkDefinition {
file = "b.nix";
value = true;
})
];
}
{
# Check mkDefinition can use mkForce on the value
# Not the other way around
options.mkForce = mkOption {
type = lib.types.bool;
default = false;
};
config.mkForce = mkDefinition {
file = "other";
value = lib.mkForce true;
};
}
{
# Currently expects an error
# mkDefinition doesn't work on option default
# This is a limitation and might be resolved in the future
options.viaOptionDefault = mkOption {
type = lib.types.bool;
default = mkDefinition {
file = "other";
value = true;
};
};
}
];
}
62 changes: 62 additions & 0 deletions nixos/doc/manual/development/option-def.section.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,65 @@ they were declared in separate modules. This can be done using
];
}
```

## Free-floating definitions {#sec-option-definitions-definitions}

:::{.note}
The module system internally transforms module syntax into definitions. This always happens internally.
:::

It is possible to create first class definitions which are not transformed _again_ into definitions by the module system.

Usually the file location of a definition is implicit and equal to the file it came from.
However, when manipulating definitions, it may be useful for them to be completely self-contained (or "free-floating").

A free-floating definition is created with `mkDefinition { file = ...; value = ...; }`.

Preserving the file location creates better error messages, for example when copying definitions from one option to another.

Other properties like `mkOverride` `mkMerge` `mkAfter` can be used in the `value` attribute but not on the entire definition.

This is what would work

```nix
mkDefinition {
value = mkForce 42;
file = "somefile.nix";
}
```

While this would NOT work.

```nix
mkForce (mkDefinition {
value = 42;
file = "somefile.nix";
})
```

The following shows an example configuration that yields an error with the custom position information:

```nix
{
_file = "file.nix";
options.foo = mkOption {
default = 13;
};
config.foo = lib.mkDefinition {
file = "custom place";
# mkOptionDefault creates a conflict with the option foo's `default = 1` on purpose
# So we see the error message below contains the conflicting values and different positions
value = lib.mkOptionDefault 42;
};
}
```

evaluating the module yields the following error:

```
error: Cannot merge definitions of `foo'. Definition values:
- In `file.nix': 13
- In `custom place': 42
```

To set the file location for all definitions in a module, you may add the `_file` module syntax attribute, which has a similar effect to using `mkDefinition` on all definitions in the module, without the hassle.
3 changes: 3 additions & 0 deletions nixos/doc/manual/redirects.json
Original file line number Diff line number Diff line change
Expand Up @@ -1664,6 +1664,9 @@
"sec-option-definitions-merging": [
"index.html#sec-option-definitions-merging"
],
"sec-option-definitions-definitions": [
"index.html#sec-option-definitions-definitions"
],
"sec-assertions": [
"index.html#sec-assertions"
],
Expand Down