From 3ac955acf44e2155d1fef77c49b5c6f068e6e27b Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Thu, 20 Jan 2022 14:53:35 +0100 Subject: [PATCH 1/8] nixos/system/build: Extract Modules that do not depend on e.g. toplevel should not have to include it just to set things in `system.build`. As a general rule, this keeps tests simple, usage flexible and evaluation fast. While one module is insignificant, consistency and good practices are. --- nixos/modules/system/activation/top-level.nix | 10 +--------- nixos/modules/system/build.nix | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 9 deletions(-) create mode 100644 nixos/modules/system/build.nix diff --git a/nixos/modules/system/activation/top-level.nix b/nixos/modules/system/activation/top-level.nix index 8e53ec1ffab20..e10d668ad2cf6 100644 --- a/nixos/modules/system/activation/top-level.nix +++ b/nixos/modules/system/activation/top-level.nix @@ -139,21 +139,13 @@ in { imports = [ + ../build.nix (mkRemovedOptionModule [ "nesting" "clone" ] "Use `specialisation.«name» = { inheritParentConfig = true; configuration = { ... }; }` instead.") (mkRemovedOptionModule [ "nesting" "children" ] "Use `specialisation.«name».configuration = { ... }` instead.") ]; options = { - system.build = mkOption { - internal = true; - default = {}; - type = with types; lazyAttrsOf (uniq unspecified); - description = '' - Attribute set of derivations used to setup the system. - ''; - }; - specialisation = mkOption { default = {}; example = lib.literalExpression "{ fewJobsManyCores.configuration = { nix.buildCores = 0; nix.maxJobs = 1; }; }"; diff --git a/nixos/modules/system/build.nix b/nixos/modules/system/build.nix new file mode 100644 index 0000000000000..1539e5b53b0ca --- /dev/null +++ b/nixos/modules/system/build.nix @@ -0,0 +1,18 @@ +{ lib, ... }: +let + inherit (lib) mkOption types; +in +{ + options = { + + system.build = mkOption { + internal = true; + default = {}; + type = with types; lazyAttrsOf (uniq unspecified); + description = '' + Attribute set of derivations used to set up the system. + ''; + }; + + }; +} From ccb85a53b6a496984073227fd8c4d4c58889f421 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Thu, 20 Jan 2022 15:04:08 +0100 Subject: [PATCH 2/8] nixos: Make system.build a submodule with freeformType This allows the values below it to be specified as options, while remaining compatible with existing code. --- nixos/modules/system/build.nix | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/nixos/modules/system/build.nix b/nixos/modules/system/build.nix index 1539e5b53b0ca..58dc3f0d41134 100644 --- a/nixos/modules/system/build.nix +++ b/nixos/modules/system/build.nix @@ -6,12 +6,15 @@ in options = { system.build = mkOption { - internal = true; default = {}; - type = with types; lazyAttrsOf (uniq unspecified); description = '' Attribute set of derivations used to set up the system. ''; + type = types.submoduleWith { + modules = [{ + freeformType = with types; lazyAttrsOf (uniq unspecified); + }]; + }; }; }; From ba3e91ed43c05a4a0984a6faa948949612fd113c Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Mon, 24 Jan 2022 01:07:32 +0100 Subject: [PATCH 3/8] lib.types: Add unique like uniq, but custom errors Couldn't extend types.uniq and it had a silly name anyway. Now we can have better error messages. --- lib/default.nix | 5 +++-- lib/options.nix | 6 ++++++ lib/types.nix | 15 ++++++++++++++- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/lib/default.nix b/lib/default.nix index 2dfe62e82a8b1..268422538803d 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -122,8 +122,9 @@ let mkRenamedOptionModule mkMergedOptionModule mkChangedOptionModule mkAliasOptionModule mkDerivedConfig doRename; inherit (self.options) isOption mkEnableOption mkSinkUndeclaredOptions - mergeDefaultOption mergeOneOption mergeEqualOption getValues - getFiles optionAttrSetToDocList optionAttrSetToDocList' + mergeDefaultOption mergeOneOption mergeEqualOption mergeUniqueOption + getValues getFiles + optionAttrSetToDocList optionAttrSetToDocList' scrubOptionValue literalExpression literalExample literalDocBook showOption showFiles unknownModule mkOption; inherit (self.types) isType setType defaultTypeMerge defaultFunctor diff --git a/lib/options.nix b/lib/options.nix index 53001a3113f93..44ec335545ca3 100644 --- a/lib/options.nix +++ b/lib/options.nix @@ -134,6 +134,12 @@ rec { throw "The unique option `${showOption loc}' is defined multiple times. Definition values:${showDefs defs}" else (head defs).value; + mergeUniqueOption = { message }: loc: defs: + if length defs == 1 + then (head defs).value + else assert length defs > 1; + throw "The option `${showOption loc}' is defined multiple times.\n${message}\nDefinition values:${showDefs defs}"; + /* "Merge" option definitions by checking that they all have the same value. */ mergeEqualOption = loc: defs: if defs == [] then abort "This case should never happen." diff --git a/lib/types.nix b/lib/types.nix index cc3ac5fdf6fbe..f2f9b2bca9853 100644 --- a/lib/types.nix +++ b/lib/types.nix @@ -32,7 +32,6 @@ let last length tail - unique ; inherit (lib.attrsets) attrNames @@ -48,6 +47,7 @@ let mergeDefaultOption mergeEqualOption mergeOneOption + mergeUniqueOption showFiles showOption ; @@ -470,6 +470,18 @@ rec { nestedTypes.elemType = elemType; }; + unique = { message }: type: mkOptionType rec { + name = "unique"; + inherit (type) description check; + merge = mergeUniqueOption { inherit message; }; + emptyValue = type.emptyValue; + getSubOptions = type.getSubOptions; + getSubModules = type.getSubModules; + substSubModules = m: uniq (type.substSubModules m); + functor = (defaultFunctor name) // { wrapped = type; }; + nestedTypes.elemType = type; + }; + # Null or value of ... nullOr = elemType: mkOptionType rec { name = "nullOr"; @@ -599,6 +611,7 @@ rec { # A value from a set of allowed ones. enum = values: let + inherit (lib.lists) unique; show = v: if builtins.isString v then ''"${v}"'' else if builtins.isInt v then builtins.toString v From 2aa7c25808847847bc40be93b57a1e6174aeda09 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Thu, 20 Jan 2022 15:05:45 +0100 Subject: [PATCH 4/8] nixos: Document system.build.toplevel --- nixos/modules/system/activation/top-level.nix | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/nixos/modules/system/activation/top-level.nix b/nixos/modules/system/activation/top-level.nix index e10d668ad2cf6..da8ff8ef145b4 100644 --- a/nixos/modules/system/activation/top-level.nix +++ b/nixos/modules/system/activation/top-level.nix @@ -135,6 +135,16 @@ let pkgs.replaceDependency { inherit oldDependency newDependency drv; } ) baseSystemAssertWarn config.system.replaceRuntimeDependencies; + /* Workaround until https://github.com/NixOS/nixpkgs/pull/156533 + Call can be replaced by argument when that's merged. + */ + tmpFixupSubmoduleBoundary = subopts: + lib.mkOption { + type = lib.types.submoduleWith { + modules = [ { options = subopts; } ]; + }; + }; + in { @@ -216,6 +226,19 @@ in ''; }; + system.build = tmpFixupSubmoduleBoundary { + toplevel = mkOption { + type = types.package; + readOnly = true; + description = '' + This option contains the store path that typically represents a NixOS system. + + You can read this path in a custom deployment tool for example. + ''; + }; + }; + + system.copySystemConfiguration = mkOption { type = types.bool; default = false; From 511e89f5a66c77c378d30853e5e0ac6995e0013e Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Mon, 24 Jan 2022 12:49:58 +0100 Subject: [PATCH 5/8] nixos: Make system.build.installBootLoader a proper option This improves the error message when the configuration contains more than one boot loader. --- nixos/modules/system/activation/top-level.nix | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/nixos/modules/system/activation/top-level.nix b/nixos/modules/system/activation/top-level.nix index da8ff8ef145b4..b1e0274cff102 100644 --- a/nixos/modules/system/activation/top-level.nix +++ b/nixos/modules/system/activation/top-level.nix @@ -109,9 +109,7 @@ let utillinux = pkgs.util-linux; kernelParams = config.boot.kernelParams; - installBootLoader = - config.system.build.installBootLoader - or "echo 'Warning: do not know how to make this configuration bootable; please enable a boot loader.' 1>&2; true"; + installBootLoader = config.system.build.installBootLoader; activationScript = config.system.activationScripts.script; dryActivationScript = config.system.dryActivationScript; nixosLabel = config.system.nixos.label; @@ -227,6 +225,23 @@ in }; system.build = tmpFixupSubmoduleBoundary { + installBootLoader = mkOption { + internal = true; + default = "echo 'Warning: do not know how to make this configuration bootable; please enable a boot loader.' 1>&2; true"; + description = '' + A program that writes a bootloader installation script to the path passed in the first command line argument. + + See nixos/modules/system/activation/switch-to-configuration.pl. + ''; + type = types.unique { + message = '' + Only one bootloader can be enabled at a time. This requirement has not + been checked until NixOS 22.05. Earlier versions defaulted to the last + definition. Change your configuration to enable only one bootloader. + ''; + } (types.either types.str types.package); + }; + toplevel = mkOption { type = types.package; readOnly = true; From 4800f308413d03c39be5311ff63a218177af32df Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Mon, 24 Jan 2022 15:09:17 +0100 Subject: [PATCH 6/8] nixos: Explain system.build.installBootLoader's odd default I don't really approve of this solution, but documenting its purpose was the least I could do for now. --- nixos/modules/system/activation/top-level.nix | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nixos/modules/system/activation/top-level.nix b/nixos/modules/system/activation/top-level.nix index b1e0274cff102..c9fef33c94038 100644 --- a/nixos/modules/system/activation/top-level.nix +++ b/nixos/modules/system/activation/top-level.nix @@ -227,6 +227,9 @@ in system.build = tmpFixupSubmoduleBoundary { installBootLoader = mkOption { internal = true; + # "; true" => make the `$out` argument from switch-to-configuration.pl + # go to `true` instead of `echo`, hiding the useless path + # from the log. default = "echo 'Warning: do not know how to make this configuration bootable; please enable a boot loader.' 1>&2; true"; description = '' A program that writes a bootloader installation script to the path passed in the first command line argument. From 8691ab3d47f1f9f94b51357fba7b8133cc8bcd88 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Mon, 24 Jan 2022 16:23:18 +0100 Subject: [PATCH 7/8] lib.modules: Define mergeOneOption in terms of mergeUniqueOption --- lib/options.nix | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/options.nix b/lib/options.nix index 44ec335545ca3..ffe4b26516616 100644 --- a/lib/options.nix +++ b/lib/options.nix @@ -128,11 +128,7 @@ rec { else if all isInt list && all (x: x == head list) list then head list else throw "Cannot merge definitions of `${showOption loc}'. Definition values:${showDefs defs}"; - mergeOneOption = loc: defs: - if defs == [] then abort "This case should never happen." - else if length defs != 1 then - throw "The unique option `${showOption loc}' is defined multiple times. Definition values:${showDefs defs}" - else (head defs).value; + mergeOneOption = mergeUniqueOption { message = ""; }; mergeUniqueOption = { message }: loc: defs: if length defs == 1 From 48dbe26229124114f26cfe0eec32866a47888452 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Mon, 24 Jan 2022 16:27:41 +0100 Subject: [PATCH 8/8] nixos/doc: Document types.unique --- .../manual/development/option-types.section.md | 6 ++++++ .../from_md/development/option-types.section.xml | 16 ++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/nixos/doc/manual/development/option-types.section.md b/nixos/doc/manual/development/option-types.section.md index ed557206659f8..56ffa8e9d79c4 100644 --- a/nixos/doc/manual/development/option-types.section.md +++ b/nixos/doc/manual/development/option-types.section.md @@ -250,6 +250,12 @@ Composed types are types that take a type as parameter. `listOf : Ensures that type *`t`* cannot be merged. It is used to ensure option definitions are declared only once. +`types.unique` `{ message = m }` *`t`* + +: Ensures that type *`t`* cannot be merged. Prints the message *`m`*, after + the line `The option