diff --git a/README.md b/README.md index 4b4c8738..7dbffed2 100644 --- a/README.md +++ b/README.md @@ -470,6 +470,7 @@ hooks](modules/pre-commit.nix). - [alejandra](https://github.com/kamadorueda/alejandra) - [deadnix](https://github.com/astro/deadnix) - [flake-checker](https://github.com/DeterminateSystems/flake-checker) +- [flake-edit](https://github.com/a-kenji/flake-edit) (for `flake-follows`) - [nil](https://github.com/oxalica/nil) - [nixf-diagnose](https://github.com/inclyc/nixf-diagnose) - [nixfmt](https://github.com/NixOS/nixfmt/) (supports `nixfmt` >=v1.0) diff --git a/modules/hooks.nix b/modules/hooks.nix index 13074d33..988f5f7c 100644 --- a/modules/hooks.nix +++ b/modules/hooks.nix @@ -534,6 +534,31 @@ in }; }; }; + flake-follows = mkOption { + description = "flake-follows hook"; + type = types.submodule { + imports = [ hookModule ]; + options.settings = { + flake = mkOption { + type = types.oneOf [ types.str types.path ]; + description = "Path to the flake file to update."; + default = "flake.nix"; + }; + + lockFile = mkOption { + type = types.oneOf [ types.str types.path ]; + description = "Path to the flake lock file used as input by `flake-edit`."; + default = "flake.lock"; + }; + + noLock = mkOption { + type = types.bool; + description = "Whether to pass `--no-lock` to avoid updating the lock file."; + default = true; + }; + }; + }; + }; flake8 = mkOption { description = "flake8 hook"; type = types.submodule { @@ -3280,6 +3305,38 @@ in entry = "${hooks.fix-encoding-pragma.package}/bin/fix-encoding-pragma"; types = [ "python" ]; }; + flake-follows = + let + flake = lib.escapeShellArg (toString hooks.flake-follows.settings.flake); + lockFile = lib.escapeShellArg (toString hooks.flake-follows.settings.lockFile); + noLock = lib.optionalString hooks.flake-follows.settings.noLock "--no-lock"; + flakeEdit = lib.getExe hooks.flake-follows.package; + script = pkgs.writeShellScript "precommit-flake-follows" '' + set -euo pipefail + + flake=${flake} + lock_file=${lockFile} + + if [ ! -f "$flake" ] || [ ! -f "$lock_file" ]; then + exit 0 + fi + + ${flakeEdit} \ + --flake "$flake" \ + --lock-file "$lock_file" \ + ${noLock} \ + --non-interactive \ + follow >/dev/null + ''; + in + { + name = "flake-follows"; + description = "Add missing follows declarations to Nix flakes."; + package = tools.flake-edit; + entry = builtins.toString script; + files = "^(flake\\.nix|flake\\.lock)$"; + pass_filenames = false; + }; flake8 = let extendIgnoreStr = diff --git a/nix/tests/default.nix b/nix/tests/default.nix index 6f0df090..de4f1539 100644 --- a/nix/tests/default.nix +++ b/nix/tests/default.nix @@ -2,4 +2,5 @@ { installation-test = pkgs.callPackage ./installation.nix { inherit run; }; hook-config-test = pkgs.callPackage ./hook-config.nix { inherit run; }; + flake-follows-test = pkgs.callPackage ./flake-follows.nix { inherit run; }; } diff --git a/nix/tests/flake-follows.nix b/nix/tests/flake-follows.nix new file mode 100644 index 00000000..23b20700 --- /dev/null +++ b/nix/tests/flake-follows.nix @@ -0,0 +1,114 @@ +{ pkgs, run }: +let + fakeFlakeEdit = pkgs.writeShellScriptBin "flake-edit" '' + set -eu + + flake= + : > flake-edit-args + + while [ "$#" -gt 0 ]; do + case "$1" in + --flake) + flake=$2 + printf '%s\n' "$1" "$2" >> flake-edit-args + shift 2 + ;; + --lock-file) + printf '%s\n' "$1" "$2" >> flake-edit-args + shift 2 + ;; + --no-lock|--non-interactive|follow) + printf '%s\n' "$1" >> flake-edit-args + shift + ;; + *) + echo "unexpected argument: $1" >&2 + exit 2 + ;; + esac + done + + test -n "$flake" + echo '# flake-edit follow ran' >> "$flake" + ''; + + hook = (run { + src = null; + addGcRoot = false; + tools.flake-edit = fakeFlakeEdit; + hooks.flake-follows.enable = true; + }).config.hooks.flake-follows; + + hookWithoutNoLock = (run { + src = null; + addGcRoot = false; + tools.flake-edit = fakeFlakeEdit; + hooks.flake-follows = { + enable = true; + settings.noLock = false; + }; + }).config.hooks.flake-follows; + + customPathHook = (run { + src = null; + addGcRoot = false; + tools.flake-edit = fakeFlakeEdit; + hooks.flake-follows = { + enable = true; + settings.flake = "nix/flake.nix"; + settings.lockFile = "nix/flake.lock"; + }; + }).config.hooks.flake-follows; +in +pkgs.runCommand "flake-follows-test" { } '' + set -euo pipefail + + mkdir enabled + cd enabled + touch flake.nix flake.lock + ${hook.entry} + grep -q '# flake-edit follow ran' flake.nix + grep -q -- '--flake' flake-edit-args + grep -q -- 'flake.nix' flake-edit-args + grep -q -- '--lock-file' flake-edit-args + grep -q -- 'flake.lock' flake-edit-args + grep -q -- '--no-lock' flake-edit-args + grep -q -- '--non-interactive' flake-edit-args + grep -q -- 'follow' flake-edit-args + cd .. + + mkdir no-lock-disabled + cd no-lock-disabled + touch flake.nix flake.lock + ${hookWithoutNoLock.entry} + grep -q '# flake-edit follow ran' flake.nix + if grep -q -- '--no-lock' flake-edit-args; then + echo 'unexpected --no-lock argument' >&2 + exit 1 + fi + cd .. + + mkdir missing-lock + cd missing-lock + touch flake.nix + ${hook.entry} + test ! -e flake-edit-args + cd .. + + mkdir missing-flake + cd missing-flake + touch flake.lock + ${hook.entry} + test ! -e flake-edit-args + cd .. + + mkdir -p custom-path/nix + cd custom-path + touch nix/flake.nix nix/flake.lock + ${customPathHook.entry} + grep -q '# flake-edit follow ran' nix/flake.nix + grep -q -- 'nix/flake.nix' flake-edit-args + grep -q -- 'nix/flake.lock' flake-edit-args + + echo success > $out +'' diff --git a/nix/tests/hook-config.nix b/nix/tests/hook-config.nix index 6c19bf4d..02076bab 100644 --- a/nix/tests/hook-config.nix +++ b/nix/tests/hook-config.nix @@ -7,6 +7,13 @@ let fakeNewPreCommit = pkgs.writeShellScriptBin "pre-commit" "" // { pname = "pre-commit"; version = "4.4.0"; }; hookTests = { + "flake-follows: runs once for flake inputs" = { + conf.hooks.flake-follows.enable = true; + hook = "flake-follows"; + check = raw: raw.files == "^(flake\\.nix|flake\\.lock)$" && raw.pass_filenames == false; + expected = "files limited to flake.nix/flake.lock and pass_filenames = false"; + }; + "pre-commit: no priority field when unset" = { conf.hooks.shellcheck.enable = true; hook = "shellcheck"; diff --git a/nix/tools.nix b/nix/tools.nix index 050db48a..390b32e3 100644 --- a/nix/tools.nix +++ b/nix/tools.nix @@ -36,6 +36,7 @@ , elmPackages , eslint , flake-checker ? placeholder "flake-checker" +, flake-edit ? placeholder "flake-edit" , fprettify , git-annex , gitlint @@ -159,6 +160,7 @@ in elixir eslint flake-checker + flake-edit fprettify git-annex gitlint