diff --git a/nixos/modules/misc/nixpkgs.nix b/nixos/modules/misc/nixpkgs.nix index dcb05e349197..a9e49a2a26cd 100644 --- a/nixos/modules/misc/nixpkgs.nix +++ b/nixos/modules/misc/nixpkgs.nix @@ -2,51 +2,12 @@ config, options, lib, - pkgs, ... }: let cfg = config.nixpkgs; opt = options.nixpkgs; - isConfig = x: builtins.isAttrs x || lib.isFunction x; - - optCall = f: x: if lib.isFunction f then f x else f; - - mergeConfig = - lhs_: rhs_: - let - lhs = optCall lhs_ { inherit lib pkgs; }; - rhs = optCall rhs_ { inherit lib pkgs; }; - in - lib.recursiveUpdate lhs rhs - // lib.optionalAttrs (lhs ? allowUnfreePackages) { - allowUnfreePackages = lhs.allowUnfreePackages ++ (lib.attrByPath [ "allowUnfreePackages" ] [ ] rhs); - } - // lib.optionalAttrs (lhs ? packageOverrides) { - packageOverrides = - pkgs: - optCall lhs.packageOverrides pkgs // optCall (lib.attrByPath [ "packageOverrides" ] { } rhs) pkgs; - } - // lib.optionalAttrs (lhs ? perlPackageOverrides) { - perlPackageOverrides = - pkgs: - optCall lhs.perlPackageOverrides pkgs - // optCall (lib.attrByPath [ "perlPackageOverrides" ] { } rhs) pkgs; - }; - - configType = lib.mkOptionType { - name = "nixpkgs-config"; - description = "nixpkgs config"; - check = - x: - let - traceXIfNot = c: if c x then true else lib.traceSeqN 1 x false; - in - traceXIfNot isConfig; - merge = args: lib.foldr (def: mergeConfig def.value) { }; - }; - overlayType = lib.mkOptionType { name = "nixpkgs-overlay"; description = "nixpkgs overlay"; @@ -73,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 @@ -90,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 @@ -165,7 +129,15 @@ in example = lib.literalExpression '' { allowBroken = true; allowUnfree = true; } ''; - type = configType; + 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). @@ -406,7 +378,7 @@ in ''; } { - assertion = opt.pkgs.isDefined -> cfg.config == { }; + assertion = opt.pkgs.isDefined -> opt.config.highestPrio == (lib.mkOptionDefault null).priority; message = '' Your system configures nixpkgs with an externally created instance. `nixpkgs.config` options should be passed when creating the instance instead. diff --git a/pkgs/test/config-nix-unit.nix b/pkgs/test/config-nix-unit.nix new file mode 100644 index 000000000000..cd16d5ca1c43 --- /dev/null +++ b/pkgs/test/config-nix-unit.nix @@ -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 { ... }) + testStandaloneConfig = { + expr = (import nixpkgsPath { config.allowUnfree = true; }).config.allowUnfree; + expected = true; + }; + + # Standalone nixpkgs with a function (i.e. import ({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.*"; + }; + }; +} diff --git a/pkgs/test/default.nix b/pkgs/test/default.nix index 63e8e2b32aae..fe62c0d45612 100644 --- a/pkgs/test/default.nix +++ b/pkgs/test/default.nix @@ -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 { }; diff --git a/pkgs/top-level/config.nix b/pkgs/top-level/config.nix index 526dd4182702..a92dd200ccf6 100644 --- a/pkgs/top-level/config.nix +++ b/pkgs/top-level/config.nix @@ -6,7 +6,12 @@ # nix-build -A tests.config # -{ config, lib, ... }: +{ + config, + lib, + docPrefix, + ... +}: let inherit (lib) @@ -115,13 +120,13 @@ let gitConfig = mkOption { type = types.attrsOf (types.attrsOf types.anything); description = '' - The default [git configuration](https://git-scm.com/docs/git-config#_variables) for all [`pkgs.fetchgit`](#fetchgit) calls. + The default [git configuration](https://git-scm.com/docs/git-config#_variables) for all [`pkgs.fetchgit`](${docPrefix}#fetchgit) calls. Among many other potential uses, this can be used to override URLs to point to local mirrors. Changing this will not cause any rebuilds because `pkgs.fetchgit` produces a [fixed-output derivation](https://nix.dev/manual/nix/stable/glossary.html?highlight=fixed-output%20derivation#gloss-fixed-output-derivation). - To set the configuration file directly, use the [`gitConfigFile`](#opt-gitConfigFile) option instead. + To set the configuration file directly, use the [`gitConfigFile`](${docPrefix}#opt-gitConfigFile) option instead. To set the configuration file for individual calls, use `fetchgit { gitConfigFile = "..."; }`. ''; @@ -135,9 +140,9 @@ let gitConfigFile = mkOption { type = types.nullOr types.path; description = '' - A path to a [git configuration](https://git-scm.com/docs/git-config#_variables) file, to be used for all [`pkgs.fetchgit`](#fetchgit) calls. + A path to a [git configuration](https://git-scm.com/docs/git-config#_variables) file, to be used for all [`pkgs.fetchgit`](${docPrefix}#fetchgit) calls. - This overrides the [`gitConfig`](#opt-gitConfig) option, see its documentation for more details. + This overrides the [`gitConfig`](${docPrefix}#opt-gitConfig) option, see its documentation for more details. ''; default = if config.gitConfig != { } then @@ -155,7 +160,7 @@ let For example, an override like `"registry.npmjs.org" = "my-mirror.local/registry.npmjs.org"` will replace a URL like `https://registry.npmjs.org/foo.tar.gz` with `https://my-mirror.local/registry.npmjs.org/foo.tar.gz`. - To set the string directly, see [`npmRegistryOverridesString`](#opt-npmRegistryOverridesString). + To set the string directly, see [`npmRegistryOverridesString`](${docPrefix}#opt-npmRegistryOverridesString). ''; default = { }; example = { @@ -174,7 +179,7 @@ let description = '' A string containing a string with a JSON representation of npm registry overrides for `fetchNpmDeps`. - This overrides the [`npmRegistryOverrides`](#opt-npmRegistryOverrides) option, see its documentation for more details. + This overrides the [`npmRegistryOverrides`](${docPrefix}#opt-npmRegistryOverrides) option, see its documentation for more details. ''; default = builtins.toJSON config.npmRegistryOverrides; }; @@ -412,7 +417,7 @@ let type = types.listOf types.str; default = [ "https://tarballs.nixos.org" ]; description = '' - The set of content-addressed/hashed mirror URLs used by [`pkgs.fetchurl`](#sec-pkgs-fetchers-fetchurl). + The set of content-addressed/hashed mirror URLs used by [`pkgs.fetchurl`](${docPrefix}#sec-pkgs-fetchers-fetchurl). In case `pkgs.fetchurl` can't download from the given URLs, it will try the hashed mirrors based on the expected output hash. @@ -466,11 +471,27 @@ 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. ''; }; + packageOverrides = mkOption { + type = types.functionTo types.attrs; + default = pkgs: { }; + description = '' + A function to replace or add packages in `pkgs` expects an attrset to be returned when called. + ''; + }; + + perlPackageOverrides = mkOption { + type = types.functionTo types.attrs; + default = pkgs: { }; + description = '' + The same as `packageOverrides` but for packages in the perl package set. + ''; + }; + problems = (import ../stdenv/generic/problems.nix { inherit lib; }).configOptions; }; @@ -494,6 +515,7 @@ in inherit options; config = { + _module.args.docPrefix = lib.mkDefault ""; warnings = optionals config.warnUndeclaredOptions ( mapAttrsToList (k: v: "undeclared Nixpkgs option set: config.${k}") config._undeclared or { } diff --git a/pkgs/top-level/default.nix b/pkgs/top-level/default.nix index 521745d67ec9..12fdbed08197 100644 --- a/pkgs/top-level/default.nix +++ b/pkgs/top-level/default.nix @@ -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"; };