nixos/nixpkgs.config: use deferredModule to avoid double evaluation

Switch the option type to `deferredModuleWith`, which collects definitions without evaluating them.
The raw definitions are forwarded to nixpkgs function via a new internal `_configDefinitions` parameter.

`apply` on the option returns `finalPkgs.config`, preserving
backwards compatibility for external readers of `config.nixpkgs.config`
This commit is contained in:
Johannes Kirschbauer
2026-04-23 11:51:43 +02:00
committed by jopejoe1
parent 169e69597b
commit 1d1e96211e
5 changed files with 173 additions and 21 deletions
+13 -8
View File
@@ -34,6 +34,8 @@ let
++ lib.optional (opt.localSystem.highestPrio < (lib.mkOptionDefault { }).priority) opt.localSystem
++ lib.optional (opt.crossSystem.highestPrio < (lib.mkOptionDefault { }).priority) opt.crossSystem;
_configDefinitions = opt.config.definitionsWithLocations;
defaultPkgs =
if opt.hostPlatform.isDefined then
let
@@ -51,14 +53,15 @@ let
in
import ../../.. (
{
inherit (cfg) config overlays;
inherit _configDefinitions;
inherit (cfg) overlays;
}
// systemArgs
)
else
import ../../.. {
inherit _configDefinitions;
inherit (cfg)
config
overlays
localSystem
crossSystem
@@ -126,13 +129,15 @@ in
example = lib.literalExpression ''
{ allowBroken = true; allowUnfree = true; }
'';
type = lib.types.submoduleWith {
modules = [ ../../../pkgs/top-level/config.nix ];
specialArgs = {
docPrefix = "https://nixos.org/manual/nixpkgs/unstable/";
};
shorthandOnlyDefinesConfig = true;
type = lib.types.deferredModuleWith {
staticModules = [
{ _module.args.docPrefix = "https://nixos.org/manual/nixpkgs/unstable/"; }
../../../pkgs/top-level/config.nix
];
};
# Returns pkgs.config instead of nixpkgs.config
# This shadows the deferredModule to make it look like a submodule
apply = _: finalPkgs.config;
description = ''
Global configuration for Nixpkgs.
The complete list of [Nixpkgs configuration options](https://nixos.org/manual/nixpkgs/unstable/#sec-config-options-reference) is in the [Nixpkgs manual section on global configuration](https://nixos.org/manual/nixpkgs/unstable/#chap-packageconfig).
+117
View File
@@ -0,0 +1,117 @@
# Tests for nixpkgs config forwarding from NixOS modules.
#
# Run with:
# nix-unit pkgs/test/config-nix-unit.nix
# or
# nix-build -A tests.config-nix-unit
#
{
nixpkgsPath ? ../..,
pkgs ? import nixpkgsPath { },
}:
let
lib = pkgs.lib;
# Test helper
evalNixos =
modules:
import (nixpkgsPath + "/nixos/lib/eval-config.nix") {
modules = [ { nixpkgs.hostPlatform = "x86_64-linux"; } ] ++ modules;
};
in
{
# Basic: a single config option is forwarded correctly.
testSingleConfigOption = {
expr = (evalNixos [ { nixpkgs.config.allowUnfree = true; } ]).config.nixpkgs.config.allowUnfree;
expected = true;
};
# Multiple config definitions from separate modules are merged.
testMultipleModulesMerge = {
expr =
let
eval = evalNixos [
{ nixpkgs.config.allowUnfree = true; }
{ nixpkgs.config.allowBroken = true; }
];
in
{
inherit (eval.config.nixpkgs.config) allowUnfree allowBroken;
};
expected = {
allowUnfree = true;
allowBroken = true;
};
};
# mkForce works. Also covers other properties
testMkForce = {
expr =
(evalNixos [
{ nixpkgs.config.allowUnfree = true; }
{ nixpkgs.config.allowUnfree = lib.mkForce false; }
]).config.nixpkgs.config.allowUnfree;
expected = false;
};
testDefaults = {
expr = (evalNixos [ ]).config.nixpkgs.config.allowUnfree;
expected = false;
};
# Standalone nixpkgs (i.e. import <nixpkgs> { ... })
testStandaloneConfig = {
expr = (import nixpkgsPath { config.allowUnfree = true; }).config.allowUnfree;
expected = true;
};
# Standalone nixpkgs with a function (i.e. import <nixpkgs> ({pkgs, lib, ...}: { ... })
testStandaloneConfigFunctionPkgs = {
expr =
(import nixpkgsPath {
config =
{ pkgs, lib, ... }:
{
allowUnfree = lib.isAttrs pkgs;
};
}).config.allowUnfree;
expected = true;
};
# NixOS module sets nixpkgs.config as a function
testNixosConfigFunction = {
expr =
(evalNixos [
{
nixpkgs.config =
{ lib, ... }:
{
allowUnfree = lib.isFunction lib.id;
};
}
]).config.nixpkgs.config.allowUnfree;
expected = true;
};
# Passing both config and _configDefinitions is not allowed
testConfigAndDefinitionsMutuallyExclusive = {
expr =
(import nixpkgsPath {
config = {
allowUnfree = true;
};
_configDefinitions = [
{
file = "test";
value = {
allowBroken = true;
};
}
];
}).config.allowUnfree;
expectedError = {
type = "ThrownError";
msg = ".*_configDefinitions.*internal.*must not be combined.*";
};
};
}
+15
View File
@@ -128,6 +128,21 @@ in
config = callPackage ./config.nix { };
# Technically nix-unit binds to a fixed nix version
# We have tests in lib to test the module system itself against different nix-versions
# Based on this assumption (transitivity of correctness) this test should therefore also cover all tested nix-versions
config-nix-unit =
pkgs.runCommand "config-nix-unit"
{
nativeBuildInputs = [ pkgs.nix-unit ];
}
''
export HOME=$TMPDIR
nix-unit --eval-store "$HOME" ${./config-nix-unit.nix} \
--arg nixpkgsPath "${../..}"
mkdir $out
'';
top-level = callPackage ./top-level { };
haskell = callPackage ./haskell { };
+3 -3
View File
@@ -9,11 +9,11 @@
{
config,
lib,
docPrefix ? "",
...
}@args:
}:
let
docPrefix = args.docPrefix or "";
inherit (lib)
literalExpression
mapAttrsToList
@@ -471,7 +471,7 @@ let
Silence the warning for the upcoming deprecation of the
`x86_64-darwin` platform in Nixpkgs 26.11.
See the [release notes](#x86_64-darwin-26.05) for more
See the [release notes](${docPrefix}#x86_64-darwin-26.05) for more
information.
'';
};
+25 -10
View File
@@ -64,6 +64,11 @@ in
# list it returns.
stdenvStages ? import ../stdenv,
# Temporary parameter to unify nixpkgs/pkgs evaluation
# Internal, do not use this manually!
# Will be removed again within the next releases
_configDefinitions ? null,
# Ignore unexpected args.
...
}@args:
@@ -109,7 +114,13 @@ let
then
x86_64DarwinDeprecationWarning
else
x: x
x:
x throwIfNot (lib.all lib.isFunction crossOverlays)
"All crossOverlays passed to nixpkgs must be functions."
)
(
throwIfNot (_configDefinitions == null || config0 == { })
"The `_configDefinitions` argument is an internal interface and must not be combined with `config`."
);
localSystem = lib.systems.elaborate args.localSystem;
@@ -134,20 +145,24 @@ let
# Allow both:
# { /* the config */ } and
# { pkgs, ... } : { /* the config */ }
# { lib, pkgs, ... } : { /* the config */ }
config1 = if lib.isFunction config0 then config0 { inherit lib pkgs; } else config0;
configEval = lib.evalModules {
modules = [
./config.nix
(
{ options, ... }:
{
_file = "nixpkgs.config";
config = config1;
}
)
];
]
++ (
if _configDefinitions != null then
map (def: lib.modules.setDefaultModuleLocation def.file def.value) _configDefinitions
else
[
{
_file = "nixpkgs.config";
config = config1;
}
]
);
class = "nixpkgsConfig";
};