Skip to main content
mkChecks converts declarative check configurations into proper Nix derivations that can be used in your flake’s checks output.

Overview

Define checks with simple script strings instead of writing full derivations:
# Before: Manual derivation
checks.format = pkgs.stdenv.mkDerivation {
  name = "format-check";
  src = ./.;
  nativeBuildInputs = [ pkgs.nixfmt ];
  doCheck = true;
  checkPhase = ''nixfmt --check .'';
  # ... more boilerplate
};

# After: Using mkChecks
checks = lib.mkChecks {
  format = {
    script = ''nixfmt --check .'';
    deps = [ pkgs.nixfmt ];
    root = ./.;
  };
};

Function Signature

mkChecks :: AttrSet -> AttrSet
Takes an attribute set where each value is a check configuration, returns an attribute set of derivations.

Check Configuration

Each check can have the following fields:
script
string
Shell script to run for the check. Cannot be combined with checkPhase or forEach.
checkPhase
string
Custom check phase script. Cannot be combined with script or forEach.
forEach
string
Script to run for each file. The file path is available as $file. Cannot be combined with script or checkPhase.
deps
list
List of packages to add to nativeBuildInputs. Alias: nativeBuildInputs.
src
path or derivation
Source to check. Can be a derivation or a path.
root
path
Root directory for file selection. Used with fileset or filter.
fileset
fileset
Nix fileset to check. Requires root to be set.
filter
function
File filter function. Requires root to be set.
ignore
fileset or list of filesets
Files or directories to exclude from checking.

Environment Variables

All checks run with these environment variables set:
  • HOME: Temporary directory (for tools that need a home)
  • TREEFMT_TREE_ROOT: Current working directory

Examples

Basic Format Check

checks = lib.mkChecks {
  format = {
    script = ''treefmt --check .'';
    deps = [ pkgs.treefmt ];
    root = ./.;
  };
};

Multiple Checks

checks = lib.mkChecks {
  format = {
    script = ''nixfmt --check .'';
    deps = [ pkgs.nixfmt ];
    root = ./.;
  };

  lint = {
    script = ''statix check .'';
    deps = [ pkgs.statix ];
    root = ./.;
  };

  test = {
    script = ''pytest tests/'';
    deps = [ pkgs.python3Packages.pytest ];
    root = ./.;
  };
};

Per-File Checks

Run a check on each file individually:
checks = lib.mkChecks {
  shellcheck = {
    forEach = ''shellcheck "$file"'';
    deps = [ pkgs.shellcheck ];
    root = ./.;
    filter = file: file.hasExt "sh";
  };
};
The forEach script runs with:
  • $file set to the absolute path of each file
  • Bash globstar enabled (shopt -s globstar)
  • Automatic file type filtering

Using Fileset Filtering

checks = lib.mkChecks {
  check-nix = {
    script = ''statix check .'';
    deps = [ pkgs.statix ];
    root = ./.;
    fileset = pkgs.lib.fileset.fileFilter
      (file: file.hasExt "nix")
      ./.;
  };
};

Ignoring Files

checks = lib.mkChecks {
  format = {
    script = ''treefmt --check .'';
    deps = [ pkgs.treefmt ];
    root = ./.;
    ignore = [
      ./vendor
      ./node_modules
    ];
  };
};

Check with Custom Phase

checks = lib.mkChecks {
  custom = {
    checkPhase = ''
      echo "Running custom checks"
      make test
      make lint
    '';
    deps = [ pkgs.gnumake ];
    src = ./.;
  };
};

Checking a Derivation

You can also add checks to an existing derivation:
let
  myPackage = pkgs.buildGoModule {
    pname = "my-app";
    version = "1.0.0";
    src = ./.;
    # ...
  };
in
{
  checks = lib.mkChecks {
    # Run tests on the built package
    test-package = {
      src = myPackage;
      checkPhase = ''go test ./...'';
      deps = [ pkgs.go ];
    };
  };
}
When src is a derivation, mkChecks uses .overrideAttrs to add the check phase.

Usage in Flakes

Combine with mkFlake for multi-system checks:
flake.nix
{
  outputs = { self, nixpkgs, nur }:
    nur.lib.mkFlake { } (system:
      let
        pkgs = nixpkgs.legacyPackages.${system};
        lib = nur.lib.${system};
      in
      {
        checks = lib.mkChecks {
          format = {
            script = ''treefmt --check .'';
            deps = [ pkgs.treefmt ];
            root = ./.;
          };

          build = {
            src = self.packages.${system}.default;
            checkPhase = ''echo "Build successful"'';
          };
        };
      }
    );
}
Run checks with:
nix flake check

Implementation Details

Check Phase Generation

The check phase is constructed from:
  1. Environment variable exports (HOME, TREEFMT_TREE_ROOT)
  2. User’s checkPhase, script, or forEach script
  3. For forEach: Bash loop over all files with globstar
Example generated check phase:
export HOME=$(mktemp -d)
export TREEFMT_TREE_ROOT=$(pwd)

shopt -s globstar

for_each() {
  local file="$1"
  shellcheck "$file"
}

for file in ./**; do
  if [[ -f "$file" ]]; then
    echo "Checking $(basename "$file")"
    for_each "$(realpath "$file")"
  fi
done

shopt -u globstar

Source Location

Implemented in: libs/mkChecks/default.nix:1

Best Practices

  1. Use root with filesets: More efficient than copying entire directories
  2. Leverage forEach: Easier to debug when checks fail on specific files
  3. Keep checks fast: CI runs all checks, so optimize for speed
  4. Use deps not nativeBuildInputs: More concise and clear intent

mkApps

Create flake apps with similar simplicity

mkFlake

Combine with mkFlake for multi-system checks

Build docs developers (and LLMs) love