diff --git a/docs/src/content/docs/guides/hooks.mdx b/docs/src/content/docs/guides/hooks.mdx index 4a8159a..17ab8a3 100644 --- a/docs/src/content/docs/guides/hooks.mdx +++ b/docs/src/content/docs/guides/hooks.mdx @@ -10,18 +10,26 @@ flake-file provides two hook lists: - **`flake-file.write-hooks`** — commands executed after `write-flake` writes `flake.nix`. - **`flake-file.check-hooks`** — commands executed during `nix flake check`. -Each hook is an attrset with an `index` (for ordering) and a command. +Each hook is an attrset with an `index` (for ordering) and a `program`: a function from `pkgs` to a derivation with `meta.mainProgram`, such as one built by `pkgs.writeShellApplication`. ## write-hooks Use write-hooks to trigger follow-up actions whenever `flake.nix` is regenerated — for example, updating a lock file or reformatting. +Write hooks run from the flake root with no arguments. + ```nix -{ pkgs, ... }: { +{ flake-file.write-hooks = [ { index = 10; - exec = "${pkgs.nix}/bin/nix flake update"; + program = + pkgs: + pkgs.writeShellApplication { + name = "update-lock"; + runtimeInputs = [ pkgs.nix ]; + text = "nix flake update"; + }; } ]; } @@ -33,12 +41,26 @@ Hooks run in ascending `index` order, so you can coordinate between modules with Use check-hooks to add extra validation during `nix flake check` — for example, checking that a derived file is up to date. +Check hooks receive a copy of the flake root containing only the files listed in `flake-file.check-files` (default: `flake.nix` and `flake.lock`), so that commits touching other files keep the check cacheable. Add any file your hook reads: + +```nix +{ flake-file.check-files = [ "flake.nix" "flake.lock" "generated/inputs.nix" ]; } +``` + +Check hooks run inside the check derivation and receive the path of the flake root as their first argument. + ```nix -{ pkgs, ... }: { +{ flake-file.check-hooks = [ { index = 10; - exec = "${pkgs.my-linter}/bin/my-linter --check ."; + program = + pkgs: + pkgs.writeShellApplication { + name = "lint"; + runtimeInputs = [ pkgs.my-linter ]; + text = ''my-linter --check "$1"''; + }; } ]; } diff --git a/docs/src/content/docs/reference/options.mdx b/docs/src/content/docs/reference/options.mdx index 1ecb483..583153b 100644 --- a/docs/src/content/docs/reference/options.mdx +++ b/docs/src/content/docs/reference/options.mdx @@ -77,13 +77,14 @@ flake-file = { |---|---|---| | `flake-file.write-hooks` | `listOf hookType` | Ordered hooks run after `write-flake` writes the file | | `flake-file.check-hooks` | `listOf hookType` | Ordered hooks run during flake check | +| `flake-file.check-files` | `listOf str` | Files (relative to the flake root) visible to the check and its hooks. Default: `flake.nix`, `flake.lock` | Each hook has: | Field | Description | |---|---| | `index` | Integer — lower index runs first | -| `exec` | Shell command string to execute | +| `program` | Function from `pkgs` to a derivation with `meta.mainProgram`. Write hooks run it from the flake root; check hooks pass the flake root path as the first argument | ## Lock Flattening Options diff --git a/modules/options/check-hooks.nix b/modules/options/check-hooks.nix index d3e9ee1..7a5e317 100644 --- a/modules/options/check-hooks.nix +++ b/modules/options/check-hooks.nix @@ -37,4 +37,19 @@ in type = lib.types.listOf hook; }; + options.flake-file.check-files = lib.mkOption { + description = '' + Files, relative to the flake root, copied into the root seen by + check-flake-file and its check-hooks. + + Only these files affect the check's derivation hash, so commits + touching anything else reuse a cached check result. + ''; + type = lib.types.listOf lib.types.str; + default = [ + "flake.nix" + "flake.lock" + ]; + }; + } diff --git a/modules/write-flake.nix b/modules/write-flake.nix index c3e0e31..f58dec8 100644 --- a/modules/write-flake.nix +++ b/modules/write-flake.nix @@ -169,26 +169,40 @@ let check-flake-file = pkgs: let + # Copying all of self would change this check's hash on every commit. + prefix = "${toString top.inputs.self}/"; + files = config.flake-file.check-files; + root = builtins.path { + name = "flake-root"; + path = top.inputs.self; + filter = + path: type: + let + rel = lib.removePrefix prefix path; + in + lib.elem rel files || (type == "directory" && lib.any (lib.hasPrefix "${rel}/") files); + }; hooks = lib.pipe config.flake-file.check-hooks [ (lib.sortOn (i: i.index)) (map (i: pkgs.lib.getExe (i.program pkgs))) - (map (p: "${p} ${top.inputs.self}")) + (map (p: "${p} ${root}")) (lib.concatStringsSep "\n") ]; in - pkgs.runCommandLocal "check-flake-file" + pkgs.runCommand "check-flake-file" { nativeBuildInputs = [ pkgs.diffutils ]; + preferLocalBuild = true; } '' set -e - diff -u ${top.inputs.self}/flake.nix ${formatted pkgs} + diff -u ${root}/flake.nix ${formatted pkgs} ${lib.optionalString auto-follow.enable '' - cp ${top.inputs.self}/flake.nix flake.nix - cp ${top.inputs.self}/flake.lock flake.lock + cp ${root}/flake.nix flake.nix + cp ${root}/flake.lock flake.lock chmod u+w flake.nix flake.lock ${autoFollowCommand pkgs} - diff -u ${top.inputs.self}/flake.nix flake.nix + diff -u ${root}/flake.nix flake.nix ''} ${hooks} touch $out