From e026bdfcf66a20599c492838c9f35d31fae17dc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=A9clairevoyant?= <848000+eclairevoyant@users.noreply.github.com> Date: Sun, 24 Aug 2025 08:27:25 -0400 Subject: [PATCH] lib: liberate from the module system (fixes #52) --- flake.nix | 20 +++- lib.nix | 246 +++++++++++++++++++------------------- modules/common/user.nix | 23 ++-- modules/nixos/default.nix | 5 +- 4 files changed, 159 insertions(+), 135 deletions(-) diff --git a/flake.nix b/flake.nix index 06354df..d738b1f 100644 --- a/flake.nix +++ b/flake.nix @@ -21,7 +21,19 @@ forAllSystems = nixpkgs.lib.genAttrs ["x86_64-linux" "aarch64-linux"]; in { nixosModules = { - hjem = ./modules/nixos; + hjem = { + imports = [ + self.nixosModules.hjem-lib + ./modules/nixos + ]; + }; + hjem-lib = { + lib, + pkgs, + ... + }: { + _module.args.hjem-lib = import ./lib.nix {inherit lib pkgs;}; + }; default = self.nixosModules.hjem; }; @@ -85,5 +97,11 @@ ''; } ); + + hjem-lib = forAllSystems (system: + import ./lib.nix { + inherit (nixpkgs) lib; + pkgs = nixpkgs.legacyPackages.${system}; + }); }; } diff --git a/lib.nix b/lib.nix index 5af7c3e..c4dbfd9 100644 --- a/lib.nix +++ b/lib.nix @@ -1,147 +1,147 @@ { - config, lib, - osOptions, pkgs, - ... }: let inherit (builtins) isList; inherit (lib.modules) mkDefault mkDerivedConfig mkIf mkMerge; inherit (lib.options) literalExpression mkEnableOption mkOption; inherit (lib.strings) concatMapStringsSep hasPrefix; - inherit (lib.types) addCheck anything attrsOf bool either functionTo lines nullOr path str submodule; - cfg = config; + inherit (lib.types) addCheck anything attrsOf bool either functionTo int lines listOf nullOr oneOf path str submodule; in { - _module.args.hjem = { - envVarType = osOptions.environment.variables.type; + # inlined from https://github.com/NixOS/nixpkgs/tree/master/nixos/modules/config/shells-environment.nix + # using osOptions precludes using hjem (or this type) standalone + envVarType = attrsOf (nullOr (oneOf [(listOf (oneOf [int str path])) int str path])); - fileTypeRelativeTo = rootDir: - submodule ({ - name, - target, - config, - options, - ... - }: { - options = { - enable = - mkEnableOption "creation of this file" - // { - default = true; - example = false; - }; - - target = mkOption { - type = str; - apply = p: - if hasPrefix "/" p - then throw "This option cannot handle absolute paths yet!" - else "${config.relativeTo}/${p}"; - defaultText = "name"; - description = '' - Path to target file relative to {option}`hjem.users..files..relativeTo`. - ''; + fileTypeRelativeTo = { + rootDir, + clobberDefault, + clobberDefaultText, + }: + submodule ({ + name, + target, + config, + options, + ... + }: { + options = { + enable = + mkEnableOption "creation of this file" + // { + default = true; + example = false; }; - text = mkOption { - default = null; - type = nullOr lines; - description = "Text of the file"; - }; + target = mkOption { + type = str; + apply = p: + if hasPrefix "/" p + then throw "This option cannot handle absolute paths yet!" + else "${config.relativeTo}/${p}"; + defaultText = "name"; + description = '' + Path to target file relative to {option}`hjem.users..files..relativeTo`. + ''; + }; - source = mkOption { - type = nullOr path; - default = null; - description = "Path of the source file or directory"; - }; + text = mkOption { + default = null; + type = nullOr lines; + description = "Text of the file"; + }; - generator = lib.mkOption { - # functionTo doesn't actually check the return type, so do that ourselves - type = addCheck (nullOr (functionTo (either options.source.type options.text.type))) (x: let - generatedValue = x config.value; - generatesDrv = options.source.type.check generatedValue; - generatesStr = options.text.type.check generatedValue; - in - x != null -> (generatesDrv || generatesStr)); - default = null; - description = '' - Function that when applied to `value` will create the `source` or `text` of the file. + source = mkOption { + type = nullOr path; + default = null; + description = "Path of the source file or directory"; + }; - Detection is automatic, as we check if the `generator` generates a derivation or a string after applying to `value`. - ''; - example = literalExpression "lib.generators.toGitINI"; - }; + generator = lib.mkOption { + # functionTo doesn't actually check the return type, so do that ourselves + type = addCheck (nullOr (functionTo (either options.source.type options.text.type))) (x: let + generatedValue = x config.value; + generatesDrv = options.source.type.check generatedValue; + generatesStr = options.text.type.check generatedValue; + in + x != null -> (generatesDrv || generatesStr)); + default = null; + description = '' + Function that when applied to `value` will create the `source` or `text` of the file. - value = lib.mkOption { - type = nullOr (attrsOf anything); - default = null; - description = "Value passed to the `generator`."; - example = { - user.email = "me@example.com"; - }; - }; + Detection is automatic, as we check if the `generator` generates a derivation or a string after applying to `value`. + ''; + example = literalExpression "lib.generators.toGitINI"; + }; - executable = mkOption { - type = bool; - default = false; - example = true; - description = '' - Whether to set the execute bit on the target file. - ''; - }; - - clobber = mkOption { - type = bool; - default = cfg.clobberFiles; - defaultText = literalExpression "config.hjem.clobberByDefault"; - description = '' - Whether to "clobber" existing target paths. - - - If using the **systemd-tmpfiles** hook (Linux only), tmpfile rules - will be constructed with `L+` (*re*create) instead of `L` - (create) type while this is set to `true`. - ''; - }; - - relativeTo = mkOption { - internal = true; - type = path; - default = rootDir; - description = "Path to which symlinks will be relative to"; - apply = x: - assert (hasPrefix "/" x || abort "Relative path ${x} cannot be used for files..relativeTo"); x; + value = lib.mkOption { + type = nullOr (attrsOf anything); + default = null; + description = "Value passed to the `generator`."; + example = { + user.email = "me@example.com"; }; }; - config = let - generatedValue = config.generator config.value; - hasGenerator = config.generator != null; - generatesDrv = options.source.type.check generatedValue; - generatesStr = options.text.type.check generatedValue; - in - mkMerge [ - { - target = mkDefault name; - source = mkIf (config.text != null) (mkDerivedConfig options.text (text: - pkgs.writeTextFile { - inherit name text; - inherit (config) executable; - })); - } + executable = mkOption { + type = bool; + default = false; + example = true; + description = '' + Whether to set the execute bit on the target file. + ''; + }; - (lib.mkIf (hasGenerator && generatesDrv) { - source = mkDefault generatedValue; - }) + clobber = mkOption { + type = bool; + default = clobberDefault; + defaultText = clobberDefaultText; + description = '' + Whether to "clobber" existing target paths. - (lib.mkIf (hasGenerator && generatesStr) { - text = mkDefault generatedValue; - }) - ]; - }); + - If using the **systemd-tmpfiles** hook (Linux only), tmpfile rules + will be constructed with `L+` (*re*create) instead of `L` + (create) type while this is set to `true`. + ''; + }; - toEnv = env: - if isList env - then concatMapStringsSep ":" toString env - else toString env; - }; + relativeTo = mkOption { + internal = true; + type = path; + default = rootDir; + description = "Path to which symlinks will be relative to"; + apply = x: + assert (hasPrefix "/" x || abort "Relative path ${x} cannot be used for files..relativeTo"); x; + }; + }; + + config = let + generatedValue = config.generator config.value; + hasGenerator = config.generator != null; + generatesDrv = options.source.type.check generatedValue; + generatesStr = options.text.type.check generatedValue; + in + mkMerge [ + { + target = mkDefault name; + source = mkIf (config.text != null) (mkDerivedConfig options.text (text: + pkgs.writeTextFile { + inherit name text; + inherit (config) executable; + })); + } + + (lib.mkIf (hasGenerator && generatesDrv) { + source = mkDefault generatedValue; + }) + + (lib.mkIf (hasGenerator && generatesStr) { + text = mkDefault generatedValue; + }) + ]; + }); + + toEnv = env: + if isList env + then concatMapStringsSep ":" toString env + else toString env; } diff --git a/modules/common/user.nix b/modules/common/user.nix index b206493..2028815 100644 --- a/modules/common/user.nix +++ b/modules/common/user.nix @@ -4,13 +4,14 @@ # be avoided here. { config, - hjem, + hjem-lib, lib, + name, options, pkgs, ... }: let - inherit (hjem) envVarType fileTypeRelativeTo toEnv; + inherit (hjem-lib) envVarType toEnv; inherit (lib.attrsets) mapAttrsToList; inherit (lib.strings) concatLines; inherit (lib.modules) mkIf; @@ -18,13 +19,17 @@ inherit (lib.types) attrsOf bool listOf package path str; cfg = config; + fileTypeRelativeTo' = rootDir: + hjem-lib.fileTypeRelativeTo { + inherit rootDir; + clobberDefault = cfg.clobberFiles; + clobberDefaultText = literalExpression "config.hjem.users.${name}.clobberFiles"; + }; in { imports = [ # Makes "assertions" option available without having to duplicate the work # already done in the Nixpkgs module. (pkgs.path + "/nixos/modules/misc/assertions.nix") - - ../../lib.nix ]; options = { @@ -63,7 +68,7 @@ in { files = mkOption { default = {}; - type = attrsOf (fileTypeRelativeTo cfg.directory); + type = attrsOf (fileTypeRelativeTo' cfg.directory); example = {".config/foo.txt".source = "Hello World";}; description = "Files to be managed by Hjem"; }; @@ -84,7 +89,7 @@ in { }; files = mkOption { default = {}; - type = attrsOf (fileTypeRelativeTo cfg.xdg.cache.directory); + type = attrsOf (fileTypeRelativeTo' cfg.xdg.cache.directory); example = {"foo.txt".source = "Hello World";}; description = "Cache files to be managed by Hjem"; }; @@ -105,7 +110,7 @@ in { }; files = mkOption { default = {}; - type = attrsOf (fileTypeRelativeTo cfg.xdg.config.directory); + type = attrsOf (fileTypeRelativeTo' cfg.xdg.config.directory); example = {"foo.txt".source = "Hello World";}; description = "Config files to be managed by Hjem"; }; @@ -126,7 +131,7 @@ in { }; files = mkOption { default = {}; - type = attrsOf (fileTypeRelativeTo cfg.xdg.data.directory); + type = attrsOf (fileTypeRelativeTo' cfg.xdg.data.directory); example = {"foo.txt".source = "Hello World";}; description = "data files to be managed by Hjem"; }; @@ -147,7 +152,7 @@ in { }; files = mkOption { default = {}; - type = attrsOf (fileTypeRelativeTo cfg.xdg.state.directory); + type = attrsOf (fileTypeRelativeTo' cfg.xdg.state.directory); example = {"foo.txt".source = "Hello World";}; description = "state files to be managed by Hjem"; }; diff --git a/modules/nixos/default.nix b/modules/nixos/default.nix index 7d01ccf..ec169f8 100644 --- a/modules/nixos/default.nix +++ b/modules/nixos/default.nix @@ -1,5 +1,6 @@ { config, + hjem-lib, lib, options, pkgs, @@ -10,7 +11,7 @@ inherit (lib.options) literalExpression mkOption; inherit (lib.strings) optionalString; inherit (lib.trivial) pipe; - inherit (lib.types) attrs attrsOf bool listOf nullOr package raw submoduleWith either singleLineStr; + inherit (lib.types) attrs attrsOf bool either listOf nullOr package raw singleLineStr submoduleWith; inherit (lib.meta) getExe; inherit (builtins) filter attrNames attrValues mapAttrs getAttr concatLists concatStringsSep typeOf toJSON concatMap; @@ -78,7 +79,7 @@ specialArgs = cfg.specialArgs // { - inherit pkgs; + inherit hjem-lib pkgs; osConfig = config; osOptions = options; };