From d88721e4408988202a0ae3cea3f5551ebfa13168 Mon Sep 17 00:00:00 2001 From: Daiderd Jordan Date: Tue, 14 Feb 2017 23:18:44 +0100 Subject: [PATCH] modules: add support for module replacement with disabledModules This is based on a prototype Nicolas B. Pierron worked on during a discussion we had at FOSDEM. A new version with a workaround for problems of the reverted original. Discussion: https://github.com/NixOS/nixpkgs/commit/3f2566689 --- lib/modules.nix | 24 ++++-- lib/tests/modules.sh | 8 ++ lib/tests/modules/default.nix | 1 + lib/tests/modules/disable-declare-enable.nix | 5 ++ lib/tests/modules/disable-define-enable.nix | 5 ++ lib/tests/modules/disable-enable-modules.nix | 5 ++ .../manual/development/replace-modules.xml | 75 +++++++++++++++++++ .../manual/development/writing-modules.xml | 1 + nixos/doc/manual/release-notes/rl-1703.xml | 10 +++ 9 files changed, 127 insertions(+), 7 deletions(-) create mode 100644 lib/tests/modules/disable-declare-enable.nix create mode 100644 lib/tests/modules/disable-define-enable.nix create mode 100644 lib/tests/modules/disable-enable-modules.nix create mode 100644 nixos/doc/manual/development/replace-modules.xml diff --git a/lib/modules.nix b/lib/modules.nix index 4eee41306cd7..a7c397d2cf43 100644 --- a/lib/modules.nix +++ b/lib/modules.nix @@ -20,7 +20,8 @@ rec { , prefix ? [] , # This should only be used for special arguments that need to be evaluated # when resolving module structure (like in imports). For everything else, - # there's _module.args. + # there's _module.args. If specialArgs.modulesPath is defined it will be + # used as the base path for disabledModules. specialArgs ? {} , # This would be remove in the future, Prefer _module.args option instead. args ? {} @@ -58,10 +59,7 @@ rec { closed = closeModules (modules ++ [ internalModule ]) ({ inherit config options; lib = import ./.; } // specialArgs); - # Note: the list of modules is reversed to maintain backward - # compatibility with the old module system. Not sure if this is - # the most sensible policy. - options = mergeModules prefix (reverseList closed); + options = mergeModules prefix (reverseList (filterModules (specialArgs.modulesPath or "") closed)); # Traverse options and extract the option values into the final # config set. At the same time, check whether all option @@ -87,6 +85,16 @@ rec { result = { inherit options config; }; in result; + + # Filter disabled modules. Modules can be disabled allowing + # their implementation to be replaced. + filterModules = modulesPath: modules: + let + moduleKey = m: if isString m then toString modulesPath + "/" + m else toString m; + disabledKeys = map moduleKey (concatMap (m: m.disabledModules) modules); + in + filter (m: !(elem m.key disabledKeys)) modules; + /* Close a set of modules under the ‘imports’ relation. */ closeModules = modules: args: let @@ -111,12 +119,13 @@ rec { else {}; in if m ? config || m ? options then - let badAttrs = removeAttrs m ["imports" "options" "config" "key" "_file" "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; key = toString m.key or key; + disabledModules = m.disabledModules or []; imports = m.imports or []; options = m.options or {}; config = mkMerge [ (m.config or {}) metaSet ]; @@ -124,9 +133,10 @@ rec { else { 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 ["key" "_file" "require" "imports"]) metaSet ]; + config = mkMerge [ (removeAttrs m ["_file" "key" "disabledModules" "require" "imports"]) metaSet ]; }; applyIfFunction = key: f: args@{ config, options, lib, ... }: if isFunction f then diff --git a/lib/tests/modules.sh b/lib/tests/modules.sh index 8b476a5d3dcc..ba0c67fb7d42 100755 --- a/lib/tests/modules.sh +++ b/lib/tests/modules.sh @@ -99,6 +99,14 @@ checkConfigOutput 'true' "$@" ./define-enable.nix ./define-loaOfSub-if-foo-enabl checkConfigOutput 'true' "$@" ./define-enable.nix ./define-loaOfSub-foo-if-enable.nix checkConfigOutput 'true' "$@" ./define-enable.nix ./define-loaOfSub-foo-enable-if.nix +# Check disabledModules with config definitions and option declarations. +set -- config.enable ./define-enable.nix ./declare-enable.nix +checkConfigOutput "true" "$@" +checkConfigOutput "false" "$@" ./disable-define-enable.nix +checkConfigError "The option .*enable.* defined in .* does not exist" "$@" ./disable-declare-enable.nix +checkConfigError "attribute .*enable.* in selection path .*config.enable.* not found" "$@" ./disable-define-enable.nix ./disable-declare-enable.nix +checkConfigError "attribute .*enable.* in selection path .*config.enable.* not found" "$@" ./disable-enable-modules.nix + # Check _module.args. set -- config.enable ./declare-enable.nix ./define-enable-with-custom-arg.nix checkConfigError 'while evaluating the module argument .*custom.* in .*define-enable-with-custom-arg.nix.*:' "$@" diff --git a/lib/tests/modules/default.nix b/lib/tests/modules/default.nix index c5ce9cb3e3b8..5b0947104198 100644 --- a/lib/tests/modules/default.nix +++ b/lib/tests/modules/default.nix @@ -3,5 +3,6 @@ { inherit (lib.evalModules { inherit modules; + specialArgs.modulesPath = ./.; }) config options; } diff --git a/lib/tests/modules/disable-declare-enable.nix b/lib/tests/modules/disable-declare-enable.nix new file mode 100644 index 000000000000..a373ee7e550e --- /dev/null +++ b/lib/tests/modules/disable-declare-enable.nix @@ -0,0 +1,5 @@ +{ lib, ... }: + +{ + disabledModules = [ ./declare-enable.nix ]; +} diff --git a/lib/tests/modules/disable-define-enable.nix b/lib/tests/modules/disable-define-enable.nix new file mode 100644 index 000000000000..0d84a7c3cb6c --- /dev/null +++ b/lib/tests/modules/disable-define-enable.nix @@ -0,0 +1,5 @@ +{ lib, ... }: + +{ + disabledModules = [ ./define-enable.nix ]; +} diff --git a/lib/tests/modules/disable-enable-modules.nix b/lib/tests/modules/disable-enable-modules.nix new file mode 100644 index 000000000000..c325f4e07431 --- /dev/null +++ b/lib/tests/modules/disable-enable-modules.nix @@ -0,0 +1,5 @@ +{ lib, ... }: + +{ + disabledModules = [ "define-enable.nix" "declare-enable.nix" ]; +} diff --git a/nixos/doc/manual/development/replace-modules.xml b/nixos/doc/manual/development/replace-modules.xml new file mode 100644 index 000000000000..cc0539ec5109 --- /dev/null +++ b/nixos/doc/manual/development/replace-modules.xml @@ -0,0 +1,75 @@ +
+ +Replace Modules + +Modules that are imported can also be disabled. The option + declarations and config implementation of a disabled module will be + ignored, allowing another to take it's place. This can be used to + import a set of modules from another channel while keeping the rest + of the system on a stable release. +disabledModules is a top level attribute like + imports, options and + config. It contains a list of modules that will + be disabled. This can either be the full path to the module or a + string with the filename relative to the modules path + (eg. <nixpkgs/nixos/modules> for nixos). + + +This example will replace the existing postgresql module with + the version defined in the nixos-unstable channel while keeping the + rest of the modules and packages from the original nixos channel. + This only overrides the module definition, this won't use postgresql + from nixos-unstable unless explicitly configured to do so. + + +{ config, lib, pkgs, ... }: + +{ + disabledModules = [ "services/databases/postgresql.nix" ]; + + imports = + [ # Use postgresql service from nixos-unstable channel. + # sudo nix-channel --add http://nixos.org/channels/nixos-unstable nixos-unstable + <nixos-unstable/nixos/modules/services/databases/postgresql.nix> + ]; + + services.postgresql.enable = true; +} + + +This example shows how to define a custom module as a + replacement for an existing module. Importing this module will + disable the original module without having to know it's + implementation details. + + +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.programs.man; +in + +{ + disabledModules = [ "services/programs/man.nix" ]; + + options = { + programs.man.enable = mkOption { + type = types.bool; + default = true; + description = "Whether to enable manual pages."; + }; + }; + + config = mkIf cfg.enabled { + warnings = [ "disabled manpages for production deployments." ]; + }; +} + + +
diff --git a/nixos/doc/manual/development/writing-modules.xml b/nixos/doc/manual/development/writing-modules.xml index ef6920160e6d..5bdcad5ceb57 100644 --- a/nixos/doc/manual/development/writing-modules.xml +++ b/nixos/doc/manual/development/writing-modules.xml @@ -179,5 +179,6 @@ in { + diff --git a/nixos/doc/manual/release-notes/rl-1703.xml b/nixos/doc/manual/release-notes/rl-1703.xml index fda46217144c..f40b03ec9b99 100644 --- a/nixos/doc/manual/release-notes/rl-1703.xml +++ b/nixos/doc/manual/release-notes/rl-1703.xml @@ -271,6 +271,16 @@ following incompatible changes: + + + Modules can now be disabled by using + disabledModules, allowing another to take it's place. This can be + used to import a set of modules from another channel while keeping the + rest of the system on a stable release. + + +