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

Add Nixpkgs invocation helper modules #231940

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
7 changes: 7 additions & 0 deletions doc/using/configuration.chapter.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,13 @@ list-id: configuration-variable-list
source: ../config-options.json
```

## Calling Nixpkgs from the Module System {#sec-invoke-nixpkgs-by-module}

For [module system](#module-system) application authors, Nixpkgs provides a reusable module for the purpose of invoking Nixpkgs. Most users should not have to be aware of this. The documentation for its options is meant to be re-exposed in the application's documentation and is therefore not repeated here.
Copy link
Contributor

Choose a reason for hiding this comment

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

By "module system application" you mean things like Home Manager? I'm not sure what you mean here.

Copy link
Member Author

Choose a reason for hiding this comment

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

That's what I meant.
I don't know how to say this without going on a tangent. I think going on a tangent to explain this would distract from the content. Most users will know to skip the clause or skip the paragraph.

Copy link
Contributor

@fricklerhandwerk fricklerhandwerk May 15, 2023

Choose a reason for hiding this comment

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

Suggested change
For [module system](#module-system) application authors, Nixpkgs provides a reusable module for the purpose of invoking Nixpkgs. Most users should not have to be aware of this. The documentation for its options is meant to be re-exposed in the application's documentation and is therefore not repeated here.
For authors of applications based on the [module system](#module-system), Nixpkgs provides a reusable module for the purpose of invoking Nixpkgs. Most users should not have to be aware of this. The documentation for its options is meant to be re-exposed in the application's documentation and is therefore not repeated here.

I can do a documentation review pass when the implementation is done. Unsubscribing for now, ping me if needed.


Applications can import the implementation file [`"${nixpkgs}/pkgs/top-level/module/module.nix"`](https://github.com/NixOS/nixpkgs/blob/master/pkgs/top-level/module/module.nix).

The flake attribute (experimental) for this module is `.modules.generic.nixpkgs`.

## Declarative Package Management {#sec-declarative-package-management}

Expand Down
44 changes: 34 additions & 10 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,20 @@
lib = import ./lib;

forAllSystems = lib.genAttrs lib.systems.flakeExposed;

pkgsMemoizer = { pkgs, inputsSet, ... }:
let system = lib.systems.toLosslessStringMaybe inputsSet.hostPlatform.value.system;
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
let system = lib.systems.toLosslessStringMaybe inputsSet.hostPlatform.value.system;
let system = lib.systems.toLosslessStringMaybe inputsSet.hostPlatform.value.system;

Calling this on system sounds wrong 😆

in
if lib.attrNames inputsSet == [ "hostPlatform" ] && system != null
Copy link
Member

Choose a reason for hiding this comment

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

See also the other comment

then
# As only hostPlatform matters in this invocation, we can save
# memory by sharing the Nixpkgs invocation.
# `pkgs` is never evaluated.
self.legacyPackages.${system}
else
# We let the module invoke Nixpkgs as usual.
pkgs;

in
{
lib = lib.extend (final: prev: {
Expand Down Expand Up @@ -58,18 +72,28 @@
nixosModules = {
notDetected = ./nixos/modules/installer/scan/not-detected.nix;

/*
Make the `nixpkgs.*` configuration read-only. Guarantees that `pkgs`
is the way you initialize it.
# See nixos/doc/manual/nixos-optional-modules.md
# TODO: online manual link
readOnlyPkgs = ./nixos/modules/misc/nixpkgs/read-only.nix;

Example:
# See nixos/doc/manual/nixos-optional-modules.md
# TODO: online manual link
noLegacyPkgs = {
imports = [ ./nixos/modules/misc/nixpkgs/no-legacy.nix ];
nixpkgs._memoize = pkgsMemoizer;
};
};

{
imports = [ nixpkgs.nixosModules.readOnlyPkgs ];
nixpkgs.pkgs = nixpkgs.legacyPackages.x86_64-linux;
}
*/
readOnlyPkgs = ./nixos/modules/misc/nixpkgs/read-only.nix;
modules = {
/* Modules that are not specific to any application of the module system. */
generic = {
nixpkgs = {
imports = [
./pkgs/top-level/module/module.nix
];
config._memoize = pkgsMemoizer;
};
};
};
};
}
65 changes: 54 additions & 11 deletions nixos/doc/manual/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,14 @@ let
};
};

nixos-lib = import ../../lib { };
nixos-lib = import ../../lib { featureFlags = {
# We use a minimal module list to evaluate the docs of the extra modules.
# This is significantly faster than loading all the modules, and we can pull
# this off because we don't require any dependencies to be loaded.
minimalModules = { }; };
};

testOptionsDoc = let
eval = nixos-lib.evalTest {
# Avoid evaluating a NixOS config prototype.
config.node.type = lib.types.deferredModule;
options._module.args = lib.mkOption { internal = true; };
};
in buildPackages.nixosOptionsDoc {
inherit (eval) options;
inherit revision;
transformOptions = opt: opt // {
cleanupLocations = opt: opt // {
# Clean up declaration sites to not refer to the NixOS source tree.
declarations =
map
Expand All @@ -58,11 +54,50 @@ let
else decl)
opt.declarations;
};

testOptionsDoc = let
eval = nixos-lib.evalTest {
# Avoid evaluating a NixOS config prototype.
config.node.type = lib.types.deferredModule;
options._module.args = lib.mkOption { internal = true; };
};
in buildPackages.nixosOptionsDoc {
inherit (eval) options;
inherit revision;
transformOptions = cleanupLocations;
documentType = "none";
variablelistId = "test-options-list";
optionIdPrefix = "test-opt-";
};

optionalDocs = lib.mapAttrs (name: module:
let
# This is quite simple for now, but may need stubs for more complex modules.
eval = nixos-lib.evalModules {
modules = [
module
{ options._module.args = lib.mkOption { internal = true; }; }
];
};
in
buildPackages.nixosOptionsDoc {
inherit (eval) options;
inherit revision;
transformOptions = cleanupLocations;
# These are for direct to docbook generation, which we don't use here.
documentType = throw "documentType not set";
variablelistId = throw "variablelistId not set";
optionIdPrefix = throw "optionIdPrefix not set";
}
) {
# NOTE: These don't have to be paths. If a module needs dependencies to be loaded
# for doc rendering, do something like
# newModule = { imports = [ ../../modules/new.nix ../../modules/dep.nix ]; }
# or import it transitively.
readOnlyPkgs = ../../modules/misc/nixpkgs/read-only.nix;
noLegacyPkgs = ../../modules/misc/nixpkgs/no-legacy.nix;
};

prepareManualFromMD = ''
cp -r --no-preserve=all $inputs/* .

Expand All @@ -76,6 +111,14 @@ let
--replace \
'@NIXOS_OPTIONS_JSON@' \
${optionsDoc.optionsJSON}/share/doc/nixos/options.json
substituteInPlace ./nixos-optional-modules.md \
--replace \
'@OPTIONS_JSON_noLegacyPkgs@' \
${optionalDocs.noLegacyPkgs.optionsJSON}/share/doc/nixos/options.json \
--replace \
'@OPTIONS_JSON_readOnlyPkgs@' \
${optionalDocs.readOnlyPkgs.optionsJSON}/share/doc/nixos/options.json \
;
substituteInPlace ./development/writing-nixos-tests.section.md \
--replace \
'@NIXOS_TEST_OPTIONS_JSON@' \
Expand Down
4 changes: 4 additions & 0 deletions nixos/doc/manual/manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ contributing-to-this-manual.chapter.md
nixos-options.md
```

```{=include=} appendix html:into-file=//optional-modules.html
nixos-optional-modules.md
```

```{=include=} appendix html:into-file=//release-notes.html
release-notes/release-notes.md
```
53 changes: 53 additions & 0 deletions nixos/doc/manual/nixos-optional-modules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Optional Modules {#ch-optional-modules}

## `noLegacyPkgs` {#sec-noLegacyPkgs}

This module reimplements `nixpkgs.*` without legacy options such as `nixpkgs.localSystem`.

When imported from the flake (experimental), this module will reuse `legacyPackages` when only `hostPlatform` is specified.

This module is ignored when [`readOnlyPkgs`](#sec-readOnlyPkgs) is imported.

Example use with a flake (experimental):

```nix
inputs.nixpkgs.lib.nixosSystem {
modules = [
inputs.nixpkgs.nixosModules.noLegacyPkgs
./configuration.nix
];
}
```

```{=include=} options
id-prefix: module-noLegacyPkgs-opt-
list-id: module-noLegacyPkgs-options
source: @OPTIONS_JSON_noLegacyPkgs@
```

## `readOnlyPkgs` {#sec-readOnlyPkgs}

This module ensures that the `pkgs` module argument is as specified, manually.

The usual `nixpkgs.*` options are replaced by read-only options for compatibility. These are not listed here. NixOS modules should
get their information from the `pkgs` module argument and ignore `nixpkgs.*`.

Example use with a flake (experimental):

```nix
inputs.nixpkgs.lib.nixosSystem {
modules = [
inputs.nixpkgs.nixosModules.readOnlyPkgs
./configuration.nix
{
nixpkgs.pkgs = inputs.nixpkgs.legacyPackages.x86_64-linux;
}
];
}
```

```{=include=} options
id-prefix: module-readOnlyPkgs-opt-
list-id: module-readOnlyPkgs-options
source: @OPTIONS_JSON_readOnlyPkgs@
```
17 changes: 17 additions & 0 deletions nixos/doc/manual/release-notes/rl-2311.section.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,23 @@

- A new option was added to the virtualisation module that enables specifying explicitly named network interfaces in QEMU VMs. The existing `virtualisation.vlans` is still supported for cases where the name of the network interface is irrelevant.

- The flake now exposes a module [`modules.generic.nixpkgs`](https://nixos.org/manual/nixpkgs/unstable/#sec-invoke-nixpkgs-by-module) for configuring and invoking Nixpkgs, to be used in new or existing module system applications.
It is more efficient than a simple reimplementation, and it carries none of the NixOS legacy options.

- Importable modules have been added for flakes users to optimize `pkgs` in NixOS.

- To reuse the `legacyPackages.*` instantiation of Nixpkgs *if applicable*, use.
```nix
imports = [ nixpkgs.nixosModules.noLegacyPkgs ];
```
Here, legacy refers to old `nixpkgs.*` options.

- If you want to be sure that a shared `pkgs` instance is reused, you may invoke Nixpkgs yourself and use `readOnlyPkgs`, which makes sure the other `nixpkgs.*` options are unset.
```nix
imports = [ nixpkgs.nixosModules.readOnlyPkgs ];
nixpkgs.pkgs = ...;
```

- DocBook option documentation is no longer supported, all module documentation now uses markdown.

- `services.fail2ban.jails` can now be configured with attribute sets defining settings and filters instead of lines. The stringed options `daemonConfig` and `extraSettings` have respectively been replaced by `daemonSettings` and `jails.DEFAULT.settings` which use attribute sets.
Expand Down
39 changes: 39 additions & 0 deletions nixos/modules/misc/nixpkgs/no-legacy.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
A clean, [RFC 0078] style implementation of the NixOS `nixpkgs.*` module, which
is free of legacy options (as of 23.05).

[RFC 0078]: https://github.com/NixOS/rfcs/pull/78
*/

{ config, lib, ... }:

{
disabledModules = [
# This replaces the traditional nixpkgs module
../nixpkgs.nix
Copy link
Member

Choose a reason for hiding this comment

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

That module has other imports that would also get removed:

imports = [
./assertions.nix
./meta.nix
(mkRemovedOptionModule [ "nixpkgs" "initialSystem" ] "The NixOS options `nesting.clone` and `nesting.children` have been deleted, and replaced with named specialisation. Therefore `nixpgks.initialSystem` has no effect anymore.")
];

Copy link
Member

Choose a reason for hiding this comment

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

Why does this have to be a separate module? How about updating the ../nixpkgs.nix one, and soft deprecating the localSystem and crossSystem options.

];
options = {
nixpkgs = lib.mkOption {
description = lib.mdDoc ''
Options that configure the `pkgs` module argument.
'';
example = lib.literalExpression ''
nixpkgs = {
config.allowUnfree = true;
overlays = [
(final: prev: {
# Patch nixpkgs here
})
];
};
'';
type = lib.types.submoduleWith {
shorthandOnlyDefinesConfig = true;
modules = [ ../../../../pkgs/top-level/module/module.nix ];
};
};
};
config = {
_module.args.pkgs = config.nixpkgs.pkgs;
};
}
1 change: 1 addition & 0 deletions nixos/modules/misc/nixpkgs/read-only.nix
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ in
{
disabledModules = [
../nixpkgs.nix
./no-legacy.nix
];
options = {
nixpkgs = {
Expand Down
11 changes: 11 additions & 0 deletions nixos/modules/misc/nixpkgs/test.nix
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ let
nixpkgs.buildPlatform = "foo-linux"; # do in pkgs instead!
};

noLegacy = evalMinimalConfig {
imports = [ ./no-legacy.nix ];
nixpkgs.hostPlatform = "aarch64-linux";
};

throws = x: ! (builtins.tryEval x).success;

in
Expand Down Expand Up @@ -124,5 +129,11 @@ lib.recurseIntoAttrs {
assert !readOnly.options.nixpkgs?localSystem;
assert !readOnly.options.nixpkgs?crossSystem;

assert noLegacy._module.args.pkgs.stdenv.hostPlatform.system == "aarch64-linux";
assert noLegacy._module.args.pkgs.stdenv.buildPlatform.system == "aarch64-linux";
assert lib.systems.equals noLegacy._module.args.pkgs.stdenv.hostPlatform withHost._module.args.pkgs.stdenv.hostPlatform;
assert lib.systems.equals noLegacy._module.args.pkgs.stdenv.buildPlatform withHost._module.args.pkgs.stdenv.buildPlatform;
assert noLegacy._module.args.pkgs.hello == withHost._module.args.pkgs.hello;

pkgs.emptyFile;
}
2 changes: 1 addition & 1 deletion nixos/modules/virtualisation/qemu-vm.nix
Original file line number Diff line number Diff line change
Expand Up @@ -630,7 +630,7 @@ in
};

virtualisation.host.pkgs = mkOption {
type = options.nixpkgs.pkgs.type;
type = types.pkgs;
default = pkgs;
defaultText = literalExpression "pkgs";
example = literalExpression ''
Expand Down
6 changes: 6 additions & 0 deletions pkgs/test/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,10 @@ with pkgs;
};

pkgs-lib = recurseIntoAttrs (import ../pkgs-lib/tests { inherit pkgs; });

top-level = recurseIntoAttrs {
module = recurseIntoAttrs (
(callPackage ../top-level/module/tests.nix { }).tests
);
};
}
Loading