Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lib/modules: Document _module.args #165540

Merged
merged 1 commit into from
Apr 5, 2022
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
90 changes: 87 additions & 3 deletions lib/modules.nix
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ let
showFiles
showOption
unknownModule
literalExpression
;

showDeclPrefix = loc: decl: prefix:
Expand Down Expand Up @@ -140,7 +141,7 @@ rec {
# this module is used, to avoid conflicts and allow chaining of
# extendModules.
internalModule = rec {
_file = ./modules.nix;
_file = "lib/modules.nix";

key = _file;

Expand All @@ -153,8 +154,91 @@ rec {
# a `_module.args.pkgs = import (fetchTarball { ... }) {}` won't
# start a download when `pkgs` wasn't evaluated.
type = types.lazyAttrsOf types.raw;
internal = true;
description = "Arguments passed to each module.";
# Only render documentation once at the root of the option tree,
# not for all individual submodules.
internal = prefix != [];
# TODO: Change the type of this option to a submodule with a
# freeformType, so that individual arguments can be documented
# separately
description = ''
Additional arguments passed to each module in addition to ones
like <literal>lib</literal>, <literal>config</literal>,
and <literal>pkgs</literal>, <literal>modulesPath</literal>.
</para>
<para>
This option is also available to all submodules. Submodules do not
inherit args from their parent module, nor do they provide args to
their parent module or sibling submodules. The sole exception to
this is the argument <literal>name</literal> which is provided by
parent modules to a submodule and contains the attribute name
the submodule is bound to, or a unique generated name if it is
not bound to an attribute.
</para>
<para>
Some arguments are already passed by default, of which the
following <emphasis>cannot</emphasis> be changed with this option:
<itemizedlist>
<listitem>
<para>
<varname>lib</varname>: The nixpkgs library.
</para>
</listitem>
<listitem>
<para>
<varname>config</varname>: The results of all options after merging the values from all modules together.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Subtle but important.

Suggested change
<varname>config</varname>: The results of all options after merging the values from all modules together.
<varname>config</varname>: The results of all options after merging the values from all modules together. In the context of a submodule, "all modules" does not include the parent module that contains the submodule.
</para>
<para>
The module arguments of the parent can be accessed through the lexical scope instead.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that something is missing from my terminology here. I can't find a word for what's like a level of modules, corresponding to an evalModules invocation, whether through lib or types.submodule. "Modules" is too vague "configuration" is a little more accurate but deemphasizes the options. "Configuration fixpoint" will send our readers studying for a day ;). Not sure what to do.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"modules are merged according to the longest common option path prefix"? that covers both configurations (prefix 𝜀) and submodules (nested or not)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While that is true, it does not indicate where config starts or equivalently: what is the result of "all options" and which options are "all".

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this clarification is needed because it's fairly obvious, submodules have a completely separate option set. This would fit better into the full NixOS documentation, this here should only be a very short explanation, only in relation to _module.args.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fair enough 🚀

</para>
</listitem>
<listitem>
<para>
<varname>options</varname>: The options declared in all modules.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<varname>options</varname>: The options declared in all modules.
<varname>options</varname>: The options declared in all modules, where "all modules" is defined the same as in <varname>config</varname>.

</para>
</listitem>
<listitem>
<para>
<varname>specialArgs</varname>: The <literal>specialArgs</literal> argument passed to <literal>evalModules</literal>.
</para>
</listitem>
<listitem>
<para>
All attributes of <varname>specialArgs</varname>
</para>
<para>
Whereas option values can generally depend on other option values
thanks to laziness, this does not apply to <literal>imports</literal>, which
must be computed statically before anything else.
</para>
<para>
For this reason, callers of the module system can provide <literal>specialArgs</literal>
which are available during import resolution.
</para>
<para>
For NixOS, <literal>specialArgs</literal> includes
<varname>modulesPath</varname>, which allows you to import
extra modules from the nixpkgs package tree without having to
somehow make the module aware of the location of the
<literal>nixpkgs</literal> or NixOS directories.
<programlisting>
{ modulesPath, ... }: {
imports = [
(modulesPath + "/profiles/minimal.nix")
];
}
</programlisting>
</para>
</listitem>
</itemizedlist>
</para>
<para>
For NixOS, the default value for this option includes at least this argument:
<itemizedlist>
<listitem>
<para>
<varname>pkgs</varname>: The nixpkgs package set according to
the <option>nixpkgs.pkgs</option> option.
</para>
</listitem>
</itemizedlist>
'';
};

_module.check = mkOption {
Expand Down
2 changes: 1 addition & 1 deletion lib/tests/misc.nix
Original file line number Diff line number Diff line change
Expand Up @@ -691,7 +691,7 @@ runTests {

locs = filter (o: ! o.internal) (optionAttrSetToDocList options);
in map (o: o.loc) locs;
expected = [ [ "foo" ] [ "foo" "<name>" "bar" ] [ "foo" "bar" ] ];
expected = [ [ "_module" "args" ] [ "foo" ] [ "foo" "<name>" "bar" ] [ "foo" "bar" ] ];
};

testCartesianProductOfEmptySet = {
Expand Down
4 changes: 3 additions & 1 deletion nixos/lib/make-options-doc/mergeJSON.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ def unpivot(options: Dict[Key, Option]) -> Dict[str, JSON]:

# fix up declaration paths in lazy options, since we don't eval them from a full nixpkgs dir
for (k, v) in options.items():
v.value['declarations'] = list(map(lambda s: f'nixos/modules/{s}', v.value['declarations']))
# The _module options are not declared in nixos/modules
if v.value['loc'][0] != "_module":
v.value['declarations'] = list(map(lambda s: f'nixos/modules/{s}', v.value['declarations']))

# merge both descriptions
for (k, v) in overrides.items():
Expand Down