diff --git a/lib/modules.nix b/lib/modules.nix index 0869eae1982b6..099539f781d78 100644 --- a/lib/modules.nix +++ b/lib/modules.nix @@ -35,9 +35,9 @@ rec { # attribute. These options are fragile, as they are used by the # module system to change the interpretation of modules. internalModule = rec { - _file = ./modules.nix; + file = ./modules.nix; - key = _file; + key = file; options = { _module.args = mkOption { @@ -116,29 +116,29 @@ rec { /* Massage a module into canonical form, that is, a set consisting of ‘options’, ‘config’ and ‘imports’ attributes. */ unifyModuleSyntax = file: key: m: - let metaSet = if m ? meta - then { meta = m.meta; } - else {}; + let addMetaSet = arg: if m ? meta + then mkMerge [ arg { meta = m.meta; } ] + else arg; in if m ? config || m ? options then - let badAttrs = removeAttrs m ["_file" "key" "disabledModules" "imports" "options" "config" "meta"]; in + let badAttrs = removeAttrs m ["file" "key" "disabledModules" "imports" "options" "config" "meta"]; in if badAttrs != {} then throw "Module `${key}' has an unsupported attribute `${head (attrNames badAttrs)}'. This is caused by assignments to the top-level attributes `config' or `options'." else - { file = m._file or file; + { file = m.file or file; key = toString m.key or key; disabledModules = m.disabledModules or []; imports = m.imports or []; options = m.options or {}; - config = mkMerge [ (m.config or {}) metaSet ]; + config = addMetaSet (m.config or {}); } else - { file = m._file or file; + { file = m.file or file; key = toString m.key or key; disabledModules = m.disabledModules or []; imports = m.require or [] ++ m.imports or []; options = {}; - config = mkMerge [ (removeAttrs m ["_file" "key" "disabledModules" "require" "imports"]) metaSet ]; + config = addMetaSet (removeAttrs m ["file" "key" "disabledModules" "require" "imports"]); }; applyIfFunction = key: f: args@{ config, options, lib, ... }: if isFunction f then @@ -176,7 +176,7 @@ rec { of the submodule. (see applyIfFunction) */ unpackSubmodule = unpack: m: args: if isType "submodule" m then - { _file = m.file; } // (unpack m.submodule args) + { inherit (m) file; } // (unpack m.submodule args) else unpack m args; packSubmodule = file: m: diff --git a/lib/types.nix b/lib/types.nix index b225119299da1..b11bcb436f524 100644 --- a/lib/types.nix +++ b/lib/types.nix @@ -94,7 +94,7 @@ rec { # When adding new types don't forget to document them in - # nixos/doc/manual/development/option-types.xml! + # ../nixos/doc/manual/development/option-types.xml! types = rec { unspecified = mkOptionType { name = "unspecified"; @@ -291,6 +291,15 @@ rec { functor = (defaultFunctor name) // { wrapped = elemType; }; }; + # Same as attrsOf, but lazy towards attribute values (at the cost of not supporting mkIf and etc) + lazyAttrsOf = elemType: attrsOf elemType // { + merge = loc: defs: zipAttrsWith (name: defs: + (mergeDefinitions (loc ++ [name]) elemType defs).mergedValue + ) + # Push down position info. + (map (def: mapAttrs (n: v: { inherit (def) file; value = v; }) def.value) defs); + }; + # List or attribute set of ... loaOf = elemType: let @@ -356,6 +365,30 @@ rec { functor = (defaultFunctor name) // { wrapped = elemType; }; }; + # A function to something mergeable (i.e., to a monoid). You + # should use something else. This type is a last resort and should + # only be used when there is absolutely nothing else you can do. + # Most uses of this type imply bad design. + functionTo = elemType: mkOptionType { + name = "function that evaluates to a(n) ${elemType.name}"; + check = isFunction; + merge = loc: defs: + fnArgs: elemType.merge loc (map (fn: { inherit (fn) file; value = fn.value fnArgs; }) defs); + getSubOptions = elemType.getSubOptions; + getSubModules = elemType.getSubModules; + substSubModules = m: functionTo (elemType.substSubModules m); + }; + + # An opaque value. The result of evaluation of an option of this + # type is a list of { file, value } pairs describing all the + # values assigned to this option (with corresponding files where + # these values were applied). + opaque = mkOptionType { + name = "opaque"; + description = "opaque value"; + merge = loc: args: args; + }; + # A submodule (like typed attribute set). See NixOS manual. submodule = opts: let @@ -368,7 +401,7 @@ rec { merge = loc: defs: let coerce = def: if isFunction def then def else { config = def; }; - modules = opts' ++ map (def: { _file = def.file; imports = [(coerce def.value)]; }) defs; + modules = opts' ++ map (def: { inherit (def) file; imports = [(coerce def.value)]; }) defs; in (evalModules { inherit modules; args.name = last loc; diff --git a/nixos/doc/manual/development/option-types.xml b/nixos/doc/manual/development/option-types.xml index d993e47bc914b..5c3b459455328 100644 --- a/nixos/doc/manual/development/option-types.xml +++ b/nixos/doc/manual/development/option-types.xml @@ -364,6 +364,20 @@ +
Submodule diff --git a/nixos/doc/manual/release-notes/rl-1909.xml b/nixos/doc/manual/release-notes/rl-1909.xml index f54592b6bf6c1..6f913acd18f8c 100644 --- a/nixos/doc/manual/release-notes/rl-1909.xml +++ b/nixos/doc/manual/release-notes/rl-1909.xml @@ -42,6 +42,30 @@
+
+ Backward Incompatibilities + + + When upgrading from a previous release, please be aware of the following + incompatible changes: + + + + + + The internal top-level NixOS module option was + renamed to . Using the wrong name will cause a + evaluation error, but the indicated location of the error might be + incorrect since that option is one of the sources for those locations. + + + +
+
0 else false; @@ -233,9 +210,9 @@ let { valid = false; reason = "unfree"; errormsg = "has an unfree license (‘${showLicense attrs.meta.license}’)"; } else if hasBlacklistedLicense attrs then { valid = false; reason = "blacklisted"; errormsg = "has a blacklisted license (‘${showLicense attrs.meta.license}’)"; } - else if !allowBroken && attrs.meta.broken or false then + else if !config.allowBroken && attrs.meta.broken or false then { valid = false; reason = "broken"; errormsg = "is marked as broken"; } - else if !allowUnsupportedSystem && !(checkPlatform attrs) then + else if !config.allowUnsupportedSystem && !(checkPlatform attrs) then { valid = false; reason = "unsupported"; errormsg = "is not supported on ‘${hostPlatform.config}’"; } else if !(hasAllowedInsecure attrs) then { valid = false; reason = "insecure"; errormsg = "is marked as insecure"; } diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index 6f25783a71d76..3b7406b60015a 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -13384,12 +13384,12 @@ in # the latest Maint version perl528Packages = recurseIntoAttrs (callPackage ./perl-packages.nix { perl = perl528; - overrides = (config.perlPackageOverrides or (p: {})) pkgs; + overrides = config.perlPackageOverrides pkgs; }); # the latest Devel version perldevelPackages = recurseIntoAttrs (callPackage ./perl-packages.nix { perl = perldevel; - overrides = (config.perlPackageOverrides or (p: {})) pkgs; + overrides = config.perlPackageOverrides pkgs; }); perlPackages = perl528Packages; @@ -13437,7 +13437,7 @@ in }; rPackages = dontRecurseIntoAttrs (callPackage ../development/r-modules { - overrides = (config.rPackageOverrides or (p: {})) pkgs; + overrides = config.rPackageOverrides pkgs; }); ### SERVERS diff --git a/pkgs/top-level/config.nix b/pkgs/top-level/config.nix index 17ded76a0649b..d8b39791a7586 100644 --- a/pkgs/top-level/config.nix +++ b/pkgs/top-level/config.nix @@ -1,6 +1,6 @@ # This file defines the structure of the `config` nixpkgs option. -{ lib, config, ... }: +{ lib, options, config, ... }: with lib; @@ -26,7 +26,12 @@ let ''; }); - options = { + mkOverrides = args: mkMassRebuild ({ + type = types.functionTo (types.lazyAttrsOf (types.uniq types.unspecified)); + default = super: {}; + } // args); + + optionsDef = { /* Internal stuff */ @@ -36,8 +41,110 @@ let internal = true; }; + unknowns = mkOption { + type = types.attrsOf (types.uniq types.unspecified); + default = {}; + internal = true; + }; + /* Config options */ + inHydra = mkMeta { + internal = true; + description = '' + If we're in hydra, we can dispense with the more verbose error + messages and make problems easier to spot. + ''; + }; + + allowBroken = mkMeta { + default = builtins.getEnv "NIXPKGS_ALLOW_BROKEN" == "1"; + defaultText = ''builtins.getEnv "NIXPKGS_ALLOW_BROKEN" == "1"''; + feature = "permit evaluation of broken packages"; + }; + + allowUnsupportedSystem = mkMeta { + default = builtins.getEnv "NIXPKGS_ALLOW_UNSUPPORTED_SYSTEM" == "1"; + defaultText = ''builtins.getEnv "NIXPKGS_ALLOW_UNSUPPORTED_SYSTEM" == "1"''; + feature = "permit evaluation of packages not available for the current system"; + }; + + allowUnfree = mkMeta { + default = builtins.getEnv "NIXPKGS_ALLOW_UNFREE" == "1"; + defaultText = ''builtins.getEnv "NIXPKGS_ALLOW_UNFREE" == "1"''; + feature = "permit evaluation of unfree packages"; + }; + + permittedUnfreePackages = mkMeta { + type = types.listOf types.str; + default = []; + description = "A list of permitted unfree packages."; + }; + + allowUnfreePredicate = mkMeta { + type = types.unspecified; + default = x: builtins.elem x.name config.permittedUnfreePackages; + description = "A predicate permitting evaluation for some unfree packages."; + }; + + allowInsecure = mkMeta { + default = builtins.getEnv "NIXPKGS_ALLOW_INSECURE" == "1"; + defaultText = ''builtins.getEnv "NIXPKGS_ALLOW_INSECURE" == "1"''; + feature = "permit evaluation of packages marked as insecure"; + }; + + permittedInsecurePackages = mkMeta { + type = types.listOf types.str; + default = []; + description = "A list of permitted insecure packages."; + }; + + allowInsecurePredicate = mkMeta { + type = types.unspecified; + default = x: builtins.elem x.name config.permittedInsecurePackages; + description = "A predicate for permitting evaluation for some insecure packages."; + }; + + whitelistedLicenses = mkMeta { + type = types.listOf types.unspecified; + default = []; + description = "A list of whitelisted licenses."; + }; + + blacklistedLicenses = mkMeta { + type = types.listOf types.unspecified; + default = []; + description = "A list of blacklisted licenses."; + }; + + /* Overlays */ + + # It feels to me like if overlays really belong here. + + packageOverrides = mkOverrides { + description = "Poor man's global overlay."; + }; + + haskellPackageOverrides = mkMassRebuild { + type = types.uniq types.unspecified; + default = self: super: {}; + description = "Haskell's overlay."; + }; + + perlPackageOverrides = mkOverrides { + description = "Poor man's perl overlay."; + }; + + rPackageOverrides = mkOverrides { + description = "Poor man's R overlay."; + }; + + # See discussion at https://github.com/NixOS/nixpkgs/pull/25304#issuecomment-298385426 + # for why this defaults to false, but I (@copumpkin) want to default it to true soon. + checkMeta = mkMeta { + feature = "check meta attributes of all the packages"; + }; + doCheckByDefault = mkMassRebuild { feature = "run checkPhase by default"; }; @@ -46,6 +153,11 @@ let in { - inherit options; + options = optionsDef; + + config = { + warnings = optional (options.haskellPackageOverrides.highestPrio != 1500) + "`config.haskellPackageOverrides` is deprecated, override `haskell.packageOverrides` using overlays instead."; + }; } diff --git a/pkgs/top-level/default.nix b/pkgs/top-level/default.nix index b6de076a570c6..2fdba23d1c972 100644 --- a/pkgs/top-level/default.nix +++ b/pkgs/top-level/default.nix @@ -25,8 +25,8 @@ , # The system packages will ultimately be run on. crossSystem ? localSystem -, # Allow a configuration attribute set to be passed in as an argument. - config ? {} +, # List of configuration modules to apply. + configs ? [] , # List of overlays layers used to extend Nixpkgs. overlays ? [] @@ -41,45 +41,59 @@ } @ args: let # Rename the function arguments - config0 = config; crossSystem0 = crossSystem; in let lib = import ../../lib; - # Allow both: - # { /* the config */ } and - # { pkgs, ... } : { /* the config */ } - config1 = - if lib.isFunction config0 - then config0 { inherit pkgs; } - else config0; - # From a minimum of `system` or `config` (actually a target triple, *not* # nixpkgs configuration), infer the other one and platform as needed. localSystem = lib.systems.elaborate ( # Allow setting the platform in the config file. This take precedence over # the inferred platform, but not over an explicitly passed-in one. - builtins.intersectAttrs { platform = null; } config1 + builtins.intersectAttrs { platform = null; } config // args.localSystem); crossSystem = if crossSystem0 == null then localSystem else lib.systems.elaborate crossSystem0; + # Massage e into a NixOS module. + # We need all this stuff only because we want to support unknown options, + # without them this can be simplified a lot. + mkModule = e: { options, ... }@args: + let + unify = file: value: lib.unifyModuleSyntax file file (lib.applyIfFunction file value args); + + fake = "nixpkgs.configs element"; + module = + if lib.isFunction e then unify fake e # { ... }: config + else if lib.isAttrs e then + (if e ? file && e ? value then unify e.file e.value # types.opaque + else unify fake e) # plain config + else unify (toString e) (import e); # path + + # a bit of magic to move all options unknown to the ./config.nix + # under "unknowns" option so that the module checker won't complain + # FIXME: remove this eventually + configWithoutUnknowns = builtins.intersectAttrs options module.config // { + unknowns = lib.filterAttrs (n: v: !(options ? ${n})) module.config; + }; + in module // { + config = configWithoutUnknowns; + }; + + # Eval configs. configEval = lib.evalModules { modules = [ + { _module.args = { inherit pkgs; }; } ./config.nix - ({ options, ... }: { - _file = "nixpkgs.config"; - # filter-out known options, FIXME: remove this eventually - config = builtins.intersectAttrs options config1; - }) - ]; + ] ++ map mkModule configs; }; - # take all the rest as-is - config = lib.showWarnings configEval.config.warnings - (config1 // builtins.removeAttrs configEval.config [ "_module" ]); + # Do the reverse of configWithoutUnknowns. + configWithUnknowns = configEval.config // configEval.config.unknowns; + + config = lib.showWarnings configEval.config.warnings configWithUnknowns; # A few packages make a new package set to draw their dependencies from. # (Currently to get a cross tool chain, or forced-i686 package.) Rather than @@ -98,14 +112,6 @@ in let # compiling toolchains and 32-bit packages on x86_64). In both those cases we # want the provided non-native `localSystem` argument to affect the stdenv # chosen. - # - # NB!!! This thing gets its `config` argument from `args`, i.e. it's actually - # `config0`. It is important to keep it to `config0` format (as opposed to the - # result of `evalModules`, i.e. the `config` variable above) throughout all - # nixpkgs evaluations since the above function `config0 -> config` implemented - # via `evalModules` is not idempotent. In other words, if you add `config` to - # `newArgs`, expect strange very hard to debug errors! (Yes, I'm speaking from - # experience here.) nixpkgsFun = newArgs: import ./. (args // newArgs); # Partially apply some arguments for building bootstraping stage pkgs diff --git a/pkgs/top-level/haskell-packages.nix b/pkgs/top-level/haskell-packages.nix index aeb3b471a149f..399d42d5ff18e 100644 --- a/pkgs/top-level/haskell-packages.nix +++ b/pkgs/top-level/haskell-packages.nix @@ -1,4 +1,4 @@ -{ buildPackages, pkgs +{ config, buildPackages, pkgs , newScope }: @@ -103,7 +103,7 @@ in { }; # Default overrides that are applied to all package sets. - packageOverrides = self : super : {}; + packageOverrides = config.haskellPackageOverrides; # Always get compilers from `buildPackages` packages = let bh = buildPackages.haskell; in { diff --git a/pkgs/top-level/impure.nix b/pkgs/top-level/impure.nix index b0532ceb5db47..087bbcb7d2344 100644 --- a/pkgs/top-level/impure.nix +++ b/pkgs/top-level/impure.nix @@ -5,18 +5,62 @@ with builtins; let - homeDir = builtins.getEnv "HOME"; + homeDir = getEnv "HOME"; # Return ‘x’ if it evaluates, or ‘def’ if it throws an exception. try = x: def: let res = tryEval x; in if res.success then res.value else def; + configFile = getEnv "NIXPKGS_CONFIG"; + configFile2 = homeDir + "/.config/nixpkgs/config.nix"; + configFile3 = homeDir + "/.nixpkgs/config.nix"; # obsolete + + mkConfigs = config: + if config != null then [ { file = "nixpkgs.config-argument"; value = config; } ] + else if configFile != "" && pathExists configFile then [ configFile ] + else if homeDir != "" && pathExists configFile2 then [ configFile2 ] + else if homeDir != "" && pathExists configFile3 then [ configFile3 ] + else []; + + isDir = path: pathExists (path + "/."); + pathOverlays = try (toString ) ""; + homeOverlaysFile = homeDir + "/.config/nixpkgs/overlays.nix"; + homeOverlaysDir = homeDir + "/.config/nixpkgs/overlays"; + importOverlays = path: + # check if the path is a directory or a file + if isDir path then + # it's a directory, so the set of overlays from the directory, ordered lexicographically + let content = readDir path; in + map (n: import (path + ("/" + n))) + (filter (n: match ".*\\.nix" n != null || pathExists (path + ("/" + n + "/default.nix"))) + (attrNames content)) + else + # it's a file, so the result is the contents of the file itself + import path; + + overlays_ = + if pathOverlays != "" && pathExists pathOverlays then importOverlays pathOverlays + else if pathExists homeOverlaysFile && pathExists homeOverlaysDir then + throw '' + Nixpkgs overlays can be specified with ${homeOverlaysFile} or ${homeOverlaysDir}, but not both. + Please remove one of them and try again. + '' + else if pathExists homeOverlaysFile then + if isDir homeOverlaysFile then + throw (homeOverlaysFile + " should be a file") + else importOverlays homeOverlaysFile + else if pathExists homeOverlaysDir then + if !(isDir homeOverlaysDir) then + throw (homeOverlaysDir + " should be a directory") + else importOverlays homeOverlaysDir + else []; + in { # We combine legacy `system` and `platform` into `localSystem`, if # `localSystem` was not passed. Strictly speaking, this is pure desugar, but # it is most convient to do so before the impure `localSystem.system` default, # so we do it now. - localSystem ? builtins.intersectAttrs { system = null; platform = null; } args + localSystem ? intersectAttrs { system = null; platform = null; } args , # These are needed only because nix's `--arg` command-line logic doesn't work # with unnamed parameters allowed by ... @@ -26,51 +70,15 @@ in , # Fallback: The contents of the configuration file found at $NIXPKGS_CONFIG or # $HOME/.config/nixpkgs/config.nix. - config ? let - configFile = getEnv "NIXPKGS_CONFIG"; - configFile2 = homeDir + "/.config/nixpkgs/config.nix"; - configFile3 = homeDir + "/.nixpkgs/config.nix"; # obsolete - in - if configFile != "" && pathExists configFile then import configFile - else if homeDir != "" && pathExists configFile2 then import configFile2 - else if homeDir != "" && pathExists configFile3 then import configFile3 - else {} + config ? null + +, # This is what actually gets evaluated for config. + configs ? mkConfigs config , # Overlays are used to extend Nixpkgs collection with additional # collections of packages. These collection of packages are part of the # fix-point made by Nixpkgs. - overlays ? let - isDir = path: pathExists (path + "/."); - pathOverlays = try (toString ) ""; - homeOverlaysFile = homeDir + "/.config/nixpkgs/overlays.nix"; - homeOverlaysDir = homeDir + "/.config/nixpkgs/overlays"; - overlays = path: - # check if the path is a directory or a file - if isDir path then - # it's a directory, so the set of overlays from the directory, ordered lexicographically - let content = readDir path; in - map (n: import (path + ("/" + n))) - (builtins.filter (n: builtins.match ".*\\.nix" n != null || pathExists (path + ("/" + n + "/default.nix"))) - (attrNames content)) - else - # it's a file, so the result is the contents of the file itself - import path; - in - if pathOverlays != "" && pathExists pathOverlays then overlays pathOverlays - else if pathExists homeOverlaysFile && pathExists homeOverlaysDir then - throw '' - Nixpkgs overlays can be specified with ${homeOverlaysFile} or ${homeOverlaysDir}, but not both. - Please remove one of them and try again. - '' - else if pathExists homeOverlaysFile then - if isDir homeOverlaysFile then - throw (homeOverlaysFile + " should be a file") - else overlays homeOverlaysFile - else if pathExists homeOverlaysDir then - if !(isDir homeOverlaysDir) then - throw (homeOverlaysDir + " should be a directory") - else overlays homeOverlaysDir - else [] + overlays ? overlays_ , ... } @ args: @@ -79,8 +87,8 @@ in # not be passed. assert args ? localSystem -> !(args ? system || args ? platform); -import ./. (builtins.removeAttrs args [ "system" "platform" ] // { - inherit config overlays crossSystem; +import ./. (removeAttrs args [ "system" "platform" "config" ] // { + inherit configs overlays crossSystem; # Fallback: Assume we are building packages on the current (build, in GNU # Autotools parlance) system. localSystem = (if args ? localSystem then {} diff --git a/pkgs/top-level/stage.nix b/pkgs/top-level/stage.nix index 0ee5c25b0101b..629a135be407d 100644 --- a/pkgs/top-level/stage.nix +++ b/pkgs/top-level/stage.nix @@ -111,8 +111,7 @@ let # attributes to refer to the original attributes (e.g. "foo = # ... pkgs.foo ..."). configOverrides = self: super: - lib.optionalAttrs allowCustomOverrides - ((config.packageOverrides or (super: {})) super); + lib.optionalAttrs allowCustomOverrides (config.packageOverrides super); # Convenience attributes for instantitating package sets. Each of # these will instantiate a new version of allPackages. Currently the