Skip to content

Commit

Permalink
Merge pull request #397 from cachix/new-modules
Browse files Browse the repository at this point in the history
Move to new module structure
  • Loading branch information
domenkozar authored Mar 19, 2024
2 parents 5df5a70 + fc79a53 commit 844ae66
Show file tree
Hide file tree
Showing 7 changed files with 2,084 additions and 1,560 deletions.
105 changes: 58 additions & 47 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,68 @@

![pre-commit.png](pre-commit.png)

The goal is to **manage commit hooks with Nix** and solve the following:
## Features

- **Trivial integration for Nix projects** (wires up a few things behind the scenes)

- Provide a low-overhead build of all the tooling available for the hooks to use
(naive implementation of calling nix-shell does bring some latency when committing)

- **Common hooks for languages** like Python, Haskell, Elm, etc.
- **Common hooks for languages** like Python, Haskell, Elm, etc. [see all hook options](https://devenv.sh/?q=pre-commit.hooks)

- Run hooks **as part of development** and **on during CI**

- Run hooks **as part of development** and **on your CI**

# Getting started

## devenv.sh

https://devenv.sh/pre-commit-hooks/

## Flakes support

Given the following `flake.nix` example:

```nix
{
description = "An example project.";
inputs.pre-commit-hooks.url = "github:cachix/pre-commit-hooks.nix";
inputs.flake-utils.url = "github:numtide/flake-utils";
outputs = { self, nixpkgs, pre-commit-hooks, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
{
checks = {
pre-commit-check = pre-commit-hooks.lib.${system}.run {
src = ./.;
hooks = {
nixpkgs-fmt.enable = true;
};
};
};
devShell = nixpkgs.legacyPackages.${system}.mkShell {
inherit (self.checks.${system}.pre-commit-check) shellHook;
};
}
);
}
```

Add `/.pre-commit-config.yaml` to the `.gitignore`.

To run the all the hooks on CI:

```bash
nix flake check
```

To install pre-commit hooks developers would run:

```bash
nix develop
```

## Nix

1. (optional) Use binary caches to avoid compilation:
Expand All @@ -41,8 +86,17 @@ https://devenv.sh/pre-commit-hooks/
# default_stages = ["manual" "push"];
hooks = {
elm-format.enable = true;
# override a package with a different version
ormolu.enable = true;
shellcheck.enable = true;
ormolu.package = pkgs.haskellPackages.ormolu;
# some hooks have more than one package, like clippy:
clippy.enable = true;
clippy.packageOverrides.cargo = pkgs.cargo;
clippy.packageOverrides.clippy = tools.clippy;
# some hooks provide settings
clippy.settings.allFeatures = true;
};
# Set the pkgs to get the tools for the hooks from.
Expand Down Expand Up @@ -331,49 +385,6 @@ Example configuration:
Custom hooks are defined with the same schema as [pre-defined
hooks](modules/pre-commit.nix).

# Nix Flakes support

Given the following `flake.nix` example:

```nix
{
description = "An example project.";
inputs.pre-commit-hooks.url = "github:cachix/pre-commit-hooks.nix";
inputs.flake-utils.url = "github:numtide/flake-utils";
outputs = { self, nixpkgs, pre-commit-hooks, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
{
checks = {
pre-commit-check = pre-commit-hooks.lib.${system}.run {
src = ./.;
hooks = {
nixpkgs-fmt.enable = true;
};
};
};
devShell = nixpkgs.legacyPackages.${system}.mkShell {
inherit (self.checks.${system}.pre-commit-check) shellHook;
};
}
);
}
```

Add `/.pre-commit-config.yaml` to the `.gitignore`.

To run the all the hooks on CI:

```bash
nix flake check
```

To install pre-commit hooks developers would run:

```bash
nix develop
```

# Contributing hooks

Expand Down
191 changes: 191 additions & 0 deletions modules/hook.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
{ config, name, lib, default_stages, ... }:

let
inherit (lib) concatStringsSep mkOption types;
mergeExcludes =
excludes:
if excludes == [ ] then "^$" else "(${concatStringsSep "|" excludes})";
in
{
options = {
enable = mkOption {
type = types.bool;
description = lib.mdDoc "Whether to enable this pre-commit hook.";
default = false;
};

raw = mkOption {
type = types.attrsOf types.unspecified;
description = lib.mdDoc
''
Raw fields of a pre-commit hook. This is mostly for internal use but
exposed in case you need to work around something.
Default: taken from the other hook options.
'';
};

name = mkOption {
type = types.str;
defaultText = lib.literalMD "internal name, same as `id`";
default = name;
description = lib.mdDoc
''
The name of the hook. Shown during hook execution.
'';
};

description = mkOption {
type = types.str;
description = lib.mdDoc
''
Description of the hook. Used for metadata purposes only.
'';
default = "";
};

package = mkOption {
type = types.nullOr types.package;
description = lib.mdDoc
''
An optional package that provides the hook.
For most hooks, the package name matches the name of the hook and can be overriden directly.
```
hooks.nixfmt.package = pkgs.nixfmt;
```
Some hooks may require multiple packages or a wrapper script to function correctly.
Such hooks can expose additional named packages as `packageOverrides`.
```
hooks.rustfmt.packageOverrides.cargo = pkgs.cargo;
hooks.rustfmt.packageOverrides.rustfmt = pkgs.rustfmt;
```
'';
};

packageOverrides = mkOption {
type = types.submodule {
freeformType = types.attrsOf types.package;
};
default = { };
description = lib.mdDoc
''
Additional packages required to construct the hook package.
'';
};

entry = mkOption {
type = types.str;
description = lib.mdDoc
''
The entry point - the executable to run. {option}`entry` can also contain arguments that will not be overridden, such as `entry = "autopep8 -i";`.
'';
};

language = mkOption {
type = types.str;
description = lib.mdDoc
''
The language of the hook - tells pre-commit how to install the hook.
'';
default = "system";
};

files = mkOption {
type = types.str;
description = lib.mdDoc
''
The pattern of files to run on.
'';
default = "";
};

types = mkOption {
type = types.listOf types.str;
description = lib.mdDoc
''
List of file types to run on. See [Filtering files with types](https://pre-commit.com/#plugins).
'';
default = [ "file" ];
};

types_or = mkOption {
type = types.listOf types.str;
description = lib.mdDoc
''
List of file types to run on, where only a single type needs to match.
'';
default = [ ];
};

excludes = mkOption {
type = types.listOf types.str;
description = lib.mdDoc
''
Exclude files that were matched by these patterns.
'';
default = [ ];
};

pass_filenames = mkOption {
type = types.bool;
description = lib.mdDoc ''
Whether to pass filenames as arguments to the entry point.
'';
default = true;
};

fail_fast = mkOption {
type = types.bool;
description = lib.mdDoc ''
if true pre-commit will stop running hooks if this hook fails.
'';
default = false;
};

require_serial = mkOption {
type = types.bool;
description = lib.mdDoc ''
if true this hook will execute using a single process instead of in parallel.
'';
default = false;
};

stages = mkOption {
type = types.listOf types.str;
description = lib.mdDoc ''
Confines the hook to run at a particular stage.
'';
default = default_stages;
defaultText = (lib.literalExpression or lib.literalExample) "default_stages";
};

verbose = mkOption {
type = types.bool;
default = false;
description = lib.mdDoc ''
forces the output of the hook to be printed even when the hook passes.
'';
};

always_run = mkOption {
type = types.bool;
default = false;
description = lib.mdDoc ''
if true this hook will run even if there are no matching files.
'';
};
};

config = {
raw =
{
inherit (config) name entry language files types types_or pass_filenames fail_fast require_serial stages verbose always_run;
id = config.name;
exclude = mergeExcludes config.excludes;
};
};
}
Loading

0 comments on commit 844ae66

Please sign in to comment.