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