Skip to content

Commit e5db80a

Browse files
committed
nixosModules.pkgsReadOnly: init
1 parent 693e2c3 commit e5db80a

File tree

3 files changed

+146
-0
lines changed

3 files changed

+146
-0
lines changed

flake.nix

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,19 @@
5757

5858
nixosModules = {
5959
notDetected = ./nixos/modules/installer/scan/not-detected.nix;
60+
61+
/*
62+
Make the `nixpkgs.*` configuration read-only. Guarantees that `pkgs`
63+
is the way you initialize it.
64+
65+
Example:
66+
67+
{
68+
imports = [ nixpkgs.nixosModules.readOnlyPkgs ];
69+
nixpkgs.pkgs = nixpkgs.legacyPackages.x86_64-linux;
70+
}
71+
*/
72+
readOnlyPkgs = ./nixos/modules/misc/nixpkgs/read-only.nix;
6073
};
6174
};
6275
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# A replacement for the traditional nixpkgs module, such that none of the modules
2+
# can add their own configuration. This ensures that the Nixpkgs configuration is
3+
# exactly as the user intends.
4+
# This may also be used as a performance optimization when evaluating multiple
5+
# configurations at once, with a shared `pkgs`.
6+
7+
# This is a separate module, because merging this logic into the nixpkgs module
8+
# is too burdensome, considering that it is already burdened with legacy.
9+
# Moving this logic into a module does not lose any composition benefits, because
10+
# its purpose is not something that composes anyway.
11+
12+
{ lib, config, ... }:
13+
14+
let
15+
cfg = config.nixpkgs;
16+
inherit (lib) mkOption types;
17+
18+
in
19+
{
20+
disabledModules = [
21+
../nixpkgs.nix
22+
];
23+
options = {
24+
nixpkgs = {
25+
pkgs = mkOption {
26+
type = lib.types.pkgs;
27+
description = lib.mdDoc ''The pkgs module argument.'';
28+
};
29+
config = mkOption {
30+
internal = true;
31+
type = types.unique { message = "nixpkgs.config is set to read-only"; } types.anything;
32+
description = lib.mdDoc ''
33+
The Nixpkgs `config` that `pkgs` was initialized with.
34+
'';
35+
};
36+
overlays = mkOption {
37+
internal = true;
38+
type = types.unique { message = "nixpkgs.overlays is set to read-only"; } types.anything;
39+
description = lib.mdDoc ''
40+
The Nixpkgs overlays that `pkgs` was initialized with.
41+
'';
42+
};
43+
hostPlatform = mkOption {
44+
internal = true;
45+
readOnly = true;
46+
description = lib.mdDoc ''
47+
The platform of the machine that is running the NixOS configuration.
48+
'';
49+
};
50+
buildPlatform = mkOption {
51+
internal = true;
52+
readOnly = true;
53+
description = lib.mdDoc ''
54+
The platform of the machine that built the NixOS configuration.
55+
'';
56+
};
57+
# NOTE: do not add the legacy options such as localSystem here. Let's keep
58+
# this module simple and let module authors upgrade their code instead.
59+
};
60+
};
61+
config = {
62+
_module.args.pkgs =
63+
# find mistaken definitions
64+
builtins.seq cfg.config
65+
builtins.seq cfg.overlays
66+
builtins.seq cfg.hostPlatform
67+
builtins.seq cfg.buildPlatform
68+
cfg.pkgs;
69+
nixpkgs.config = cfg.pkgs.config;
70+
nixpkgs.overlays = cfg.pkgs.overlays;
71+
nixpkgs.hostPlatform = cfg.pkgs.stdenv.hostPlatform;
72+
nixpkgs.buildPlatform = cfg.pkgs.stdenv.buildPlatform;
73+
};
74+
}

nixos/modules/misc/nixpkgs/test.nix

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# [nixpkgs]$ nix-build -A nixosTests.nixpkgs --show-trace
2+
13
{ evalMinimalConfig, pkgs, lib, stdenv }:
24
let
35
eval = mod: evalMinimalConfig {
@@ -27,6 +29,47 @@ let
2729
let
2830
uncheckedEval = lib.evalModules { modules = [ ../nixpkgs.nix module ]; };
2931
in map (ass: ass.message) (lib.filter (ass: !ass.assertion) uncheckedEval.config.assertions);
32+
33+
readOnlyUndefined = evalMinimalConfig {
34+
imports = [ ./read-only.nix ];
35+
};
36+
37+
readOnlyBad = evalMinimalConfig {
38+
imports = [ ./read-only.nix ];
39+
nixpkgs.pkgs = { };
40+
};
41+
42+
readOnly = evalMinimalConfig {
43+
imports = [ ./read-only.nix ];
44+
nixpkgs.pkgs = pkgs;
45+
};
46+
47+
readOnlyBadConfig = evalMinimalConfig {
48+
imports = [ ./read-only.nix ];
49+
nixpkgs.pkgs = pkgs;
50+
nixpkgs.config.allowUnfree = true; # do in pkgs instead!
51+
};
52+
53+
readOnlyBadOverlays = evalMinimalConfig {
54+
imports = [ ./read-only.nix ];
55+
nixpkgs.pkgs = pkgs;
56+
nixpkgs.overlays = [ (_: _: {}) ]; # do in pkgs instead!
57+
};
58+
59+
readOnlyBadHostPlatform = evalMinimalConfig {
60+
imports = [ ./read-only.nix ];
61+
nixpkgs.pkgs = pkgs;
62+
nixpkgs.hostPlatform = "foo-linux"; # do in pkgs instead!
63+
};
64+
65+
readOnlyBadBuildPlatform = evalMinimalConfig {
66+
imports = [ ./read-only.nix ];
67+
nixpkgs.pkgs = pkgs;
68+
nixpkgs.buildPlatform = "foo-linux"; # do in pkgs instead!
69+
};
70+
71+
throws = x: ! (builtins.tryEval x).success;
72+
3073
in
3174
lib.recurseIntoAttrs {
3275
invokeNixpkgsSimple =
@@ -65,5 +108,21 @@ lib.recurseIntoAttrs {
65108
nixpkgs.pkgs = pkgs;
66109
} == [];
67110

111+
112+
# Tests for the read-only.nix module
113+
assert readOnly._module.args.pkgs.stdenv.hostPlatform.system == pkgs.stdenv.hostPlatform.system;
114+
assert throws readOnlyBad._module.args.pkgs.stdenv;
115+
assert throws readOnlyUndefined._module.args.pkgs.stdenv;
116+
assert throws readOnlyBadConfig._module.args.pkgs.stdenv;
117+
assert throws readOnlyBadOverlays._module.args.pkgs.stdenv;
118+
assert throws readOnlyBadHostPlatform._module.args.pkgs.stdenv;
119+
assert throws readOnlyBadBuildPlatform._module.args.pkgs.stdenv;
120+
# read-only.nix does not provide legacy options, for the sake of simplicity
121+
# If you're bothered by this, upgrade your configs to use the new *Platform
122+
# options.
123+
assert !readOnly.options.nixpkgs?system;
124+
assert !readOnly.options.nixpkgs?localSystem;
125+
assert !readOnly.options.nixpkgs?crossSystem;
126+
68127
pkgs.emptyFile;
69128
}

0 commit comments

Comments
 (0)