Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 27 additions & 5 deletions docs/src/content/docs/guides/hooks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
};
}
];
}
Expand All @@ -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"'';
};
}
];
}
Expand Down
3 changes: 2 additions & 1 deletion docs/src/content/docs/reference/options.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
15 changes: 15 additions & 0 deletions modules/options/check-hooks.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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"
];
};

}
26 changes: 20 additions & 6 deletions modules/write-flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading