From ddf4abd2a41d71525e4c5356ec5b1222c9951914 Mon Sep 17 00:00:00 2001 From: Olivier Bitter Date: Tue, 24 Mar 2026 19:13:58 +0100 Subject: [PATCH] feat(wrapperModules.direnv): init style(wrapperModues.direnv): apply nix fmt fix(wrapperModules.direnv): add maintainers feat: use key direnv: add checks refactor tests rework tests use a single drv for tests rework: use nix functions as assertions This way we have a way to join assertions using '&&' and can group assertions. If any assertion fails it will print the error message of the given assertin as well as the name of the test make all tests assertion-based rename functions add documentation add mise integration fix maintainers add env.CONFIG_DIRENV remove "key"-transform hack construcFiles sanitizes the key by default now integrate tests with tlib remove duplicate maintainer entry fix(direnv): avoid IFD when generating TOML --- wrapperModules/d/direnv/check.nix | 150 +++++++++++++++++++++++++++++ wrapperModules/d/direnv/module.nix | 119 +++++++++++++++++++++++ 2 files changed, 269 insertions(+) create mode 100644 wrapperModules/d/direnv/check.nix create mode 100644 wrapperModules/d/direnv/module.nix diff --git a/wrapperModules/d/direnv/check.nix b/wrapperModules/d/direnv/check.nix new file mode 100644 index 00000000..b7e22f44 --- /dev/null +++ b/wrapperModules/d/direnv/check.nix @@ -0,0 +1,150 @@ +{ + pkgs, + self, + tlib, +}: + +let + inherit (tlib) + fileContains + isDirectory + isFile + notIsFile + test + ; + + lib = pkgs.lib; + + getDotdir = + wrapper: + let + cfg = (wrapper.eval { }).config; + dotdir = "${wrapper}/${cfg.configDirname}"; + in + dotdir; +in +test { wrapper = "direnv"; } { + + "direnv wrapper should be created" = + let + wrapper = self.wrappers.direnv.wrap { + inherit pkgs; + nix-direnv.enable = true; + }; + in + '' + "${wrapper}/bin/direnv" --version | grep -q "${wrapper.version}" + ''; + + "if nix-direnv is enabled then lib/nix-direnv.sh should exists" = + let + wrapper = self.wrappers.direnv.wrap { + inherit pkgs; + nix-direnv.enable = true; + }; + in + [ + (isDirectory (getDotdir wrapper)) + (isFile "${getDotdir wrapper}/lib/nix-direnv.sh") + ]; + + "if nix-direnv is disabled then lib/nix-direnv.sh should not exist" = + let + wrapper = self.wrappers.direnv.wrap { + inherit pkgs; + nix-direnv.enable = false; + }; + in + [ + (isDirectory (getDotdir wrapper)) + (notIsFile "${getDotdir wrapper}/lib/nix-direnv.sh") + ]; + + "if mise is enabled then lib/mise.sh should exists" = + let + wrapper = self.wrappers.direnv.wrap { + inherit pkgs; + mise.enable = true; + }; + in + [ + (isDirectory (getDotdir wrapper)) + (isFile "${getDotdir wrapper}/lib/mise.sh") + ]; + + "if mise is disabled then lib/mise.sh should not exist" = + let + wrapper = self.wrappers.direnv.wrap { + inherit pkgs; + mise.enable = false; + }; + in + [ + (isDirectory (getDotdir wrapper)) + (notIsFile "${getDotdir wrapper}/lib/mise.sh") + ]; + + "if a lib-script is set then it should be generated" = + let + libScriptFile = "${getDotdir wrapper}/lib/foo.sh"; + libScriptContent = "echo foo"; + wrapper = self.wrappers.direnv.wrap { + inherit pkgs; + lib."foo.sh" = libScriptContent; + }; + in + [ + (isDirectory (getDotdir wrapper)) + (isFile libScriptFile) + (fileContains libScriptFile libScriptContent) + ]; + + "if silent mode is enabled then log settings should be set" = + let + direnvTomlFile = "${getDotdir wrapper}/direnv.toml"; + wrapper = self.wrappers.direnv.wrap { + inherit pkgs; + silent = true; + }; + in + [ + (isDirectory (getDotdir wrapper)) + (isFile direnvTomlFile) + (fileContains direnvTomlFile "log_format") + (fileContains direnvTomlFile "log_filter") + + ]; + + "if extraConfig is working" = + let + direnvTomlFile = "${getDotdir wrapper}/direnv.toml"; + wrapper = self.wrappers.direnv.wrap { + inherit pkgs; + extraConfig = { + fooSection.fooKey = "fooValue"; + }; + }; + in + [ + (isDirectory (getDotdir wrapper)) + (isFile direnvTomlFile) + (fileContains direnvTomlFile "\\[fooSection\\]") + (fileContains direnvTomlFile "fooKey.*fooValue") + ]; + + "if direnvrc is working" = + let + direnvrcFile = "${getDotdir wrapper}/direnvrc"; + direnvrcContent = "echo foo"; + + wrapper = self.wrappers.direnv.wrap { + inherit pkgs; + direnvrc = direnvrcContent; + }; + in + [ + (isDirectory (getDotdir wrapper)) + (isFile direnvrcFile) + (fileContains direnvrcFile direnvrcContent) + ]; +} diff --git a/wrapperModules/d/direnv/module.nix b/wrapperModules/d/direnv/module.nix new file mode 100644 index 00000000..762128d1 --- /dev/null +++ b/wrapperModules/d/direnv/module.nix @@ -0,0 +1,119 @@ +{ + config, + lib, + wlib, + pkgs, + ... +}: +let + tomlFmt = pkgs.formats.toml { }; + direnvDotdir = "${config.wrapper.${config.outputName}}/${config.configDirname}"; +in +{ + imports = [ wlib.modules.default ]; + options = { + configDirname = lib.mkOption { + type = lib.types.str; + default = "${config.binName}-dot-dir"; + description = "Name of the directory which is created as the dotdir in the wrapper output"; + }; + silent = lib.mkEnableOption "silent mode, that is, disabling direnv logging"; + direnvrc = lib.mkOption { + type = lib.types.lines; + description = "Content of `$DIRENV_CONFIG/direnv`"; + default = ""; + }; + nix-direnv = { + enable = lib.mkEnableOption "nix-direnv integration"; + package = lib.mkPackageOption pkgs "nix-direnv" { }; + }; + mise = { + enable = lib.mkEnableOption "mise integration"; + package = lib.mkPackageOption pkgs "mise" { }; + }; + lib = lib.mkOption { + type = with lib.types; attrsOf lines; + description = '' + Configuration of [extension files](https://direnv.net/#the-stdlib) + that will be created at `$DIRENV_CONFIG/lib/*.sh` + ''; + example = { + "my-lib-script.sh" = "echo 'content of my-lib-script.sh'"; + }; + default = { }; + }; + extraConfig = lib.mkOption { + inherit (tomlFmt) type; + default = { }; + description = '' + Configuration of direnv.toml. + See + ''; + }; + }; + config = { + package = lib.mkDefault pkgs.direnv; + env = { + # We currently do not inject `DIRENV_CONFIG` for the reasons outlined in + # meta.description.pre. + + # **IMPORTANT** Using `placeholder "out"` here seems to cause issues if this wrapper is + # built inside a subWrapperModule (for example within the zshWrapper) as it refers + # to the build zsh output in that context. The passthru variants seems to solve this issue. + DIRENV_CONFIG = "${placeholder "out"}/${config.configDirname}"; + }; + passthru.DIRENV_CONFIG = direnvDotdir; + lib = { + "nix-direnv.sh" = lib.mkIf config.nix-direnv.enable '' + source ${config.nix-direnv.package}/share/nix-direnv/direnvrc + ''; + "mise.sh" = lib.mkIf config.mise.enable '' + eval "$(${lib.getExe config.mise.package} direnv activate)" + ''; + }; + extraConfig = { + global = lib.mkIf (config.silent) { + log_format = "-"; + log_filter = "^$"; + }; + }; + constructFiles = { + direnvToml = { + content = builtins.toJSON config.extraConfig; + relPath = "${config.configDirname}/direnv.toml"; + builder = ''mkdir -p "$(dirname "$2")" && ${pkgs.remarshal}/bin/json2toml "$1" "$2"''; + }; + direnvRc = { + content = config.direnvrc; + relPath = "${config.configDirname}/direnvrc"; + }; + } + // lib.mapAttrs (name: value: { + content = value; + relPath = "${config.configDirname}/lib/${name}"; + }) config.lib; + meta.maintainers = [ wlib.maintainers.zenoli ]; + meta.description.pre = '' + **IMPORTANT** In order to use this wrapper, `DIRENV_CONFIG` needs to be explicitly + set in your shells environment: + + ```shell + DIRENV_CONFIG="''${direnvWrapper.passthru.DIRENV_CONFIG}" + ``` + + This is because right now, direnv will use the original `direnv` binary in its shell hook + and not the wrapper script. So injecting `DIRENV_CONFIG` currently has no effect. + + If the PR below will ever be merged, this issue can be fixed by setting: + + ```nix + env.DIRENV_EXE_PATH = "''${placeholder "out"}/bin/direnv"; + ``` + + This would make the direnv hook use the wrapper instead of the original binary and + injecting `DIRENV_CONFIG` into the wrapper would start to take effect. + + https://github.com/direnv/direnv/pull/1564 + ''; + }; +}