diff --git a/README.md b/README.md index e993c94c..acc47cc6 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,8 @@ The goal is to **manage commit hooks with Nix** and solve the following: 1. (optional) Use binary caches to avoid compilation: ```bash - $ nix-env -iA cachix -f https://cachix.org/api/v1/install - $ cachix use pre-commit-hooks + nix-env -iA cachix -f https://cachix.org/api/v1/install + cachix use pre-commit-hooks ``` 2. Integrate hooks to be built as part of `default.nix`: @@ -40,7 +40,7 @@ The goal is to **manage commit hooks with Nix** and solve the following: } ``` - Run `$ nix-build -A pre-commit-check` to perform the checks as a Nix derivation. + Run `nix-build -A pre-commit-check` to perform the checks as a Nix derivation. 3. Integrate hooks to prepare environment as part of `shell.nix`: ```nix @@ -53,12 +53,76 @@ The goal is to **manage commit hooks with Nix** and solve the following: Add `/.pre-commit-config.yaml` to `.gitignore`. - Run `$ nix-shell` to execute `shellHook` which will: + Run `nix-shell` to execute `shellHook` which will: - build the tools and `.pre-commit-config.yaml` config file symlink which references the binaries, for speed and safe garbage collection - provide the `pre-commit` executable that `git commit` will invoke +## nix flakes + +To initialise a project with a `flake.nix` and pre-commit hooks, run: + +```bash +nix flake init -t github:cachix/pre-commit-hooks.nix +``` + +Alternatively, the following snippet shows how pre-commit hooks may be integrated into an already existing `flake.nix`: + +```nix +{ + inputs.nixpkgs.url = "github:NixOS/nixpkgs-channels/nixos-20.03"; + + inputs.flake-utils.url = "github:numtide/flake-utils"; + + inputs.pre-commit-hooks.url = "github:cachix/pre-commit-hooks.nix/master"; + + outputs = { self, nixpkgs, flake-utils, pre-commit-hooks }: + flake-utils.lib.eachDefaultSystem + ( + system: rec { + pre-commit-check = pre-commit-hooks.packages.${system}.run { + src = ./.; + # If your hooks are intrusive, avoid running on each commit with a default_states like this: + # default_stages = ["manual" "push"]; + hooks = { + elm-format.enable = true; + ormolu.enable = true; + shellcheck.enable = true; + }; + }; + + devShell = + nixpkgs.legacyPackages.${system}.mkShell { + inherit (pre-commit-check) shellHook; + }; + } + ); +} +``` + +Add `/.pre-commit-config.yaml` to the `.gitignore`. + +With this in place you may run + +```bash +nix build '.#pre-commit-check.{system}' --impure +``` + +(in which `{system}` is one of `aarch64-linux`, `i686-linux`, `x86_64-darwin`, and `x86_64-linux`) + +to perform the checks as a Nix derivation or run + +```bash +nix develop +``` + +to execute the `shellHook` which will: + +- build the tools and `.pre-commit-config.yaml` config file symlink which + references the binaries, for speed and safe garbage collection +- provide the `pre-commit` executable that `git commit` will invoke + ## Optional ### Direnv + Lorri diff --git a/default.nix b/default.nix index 9d150316..87697a54 100644 --- a/default.nix +++ b/default.nix @@ -1 +1,13 @@ -let pkgs = import ./nix {}; in pkgs.packages +( + import ( + let + lock = builtins.fromJSON (builtins.readFile ./flake.lock); + in + fetchTarball { + url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; + sha256 = lock.nodes.flake-compat.locked.narHash; + } + ) { + src = ./.; + } +).defaultNix.outputs.packages.${builtins.currentSystem} diff --git a/flake.lock b/flake.lock new file mode 100644 index 00000000..f0ab9ac6 --- /dev/null +++ b/flake.lock @@ -0,0 +1,114 @@ +{ + "nodes": { + "cabal-fmt-src": { + "flake": false, + "locked": { + "lastModified": 1598802028, + "narHash": "sha256-5yUORdBD9mtL7vPnqs+MGnHvQ7y7OGx5VeMHx0pIe2U=", + "owner": "phadej", + "repo": "cabal-fmt", + "rev": "0c716667848e0a6f2b5f999b60b067e36b0495ce", + "type": "github" + }, + "original": { + "owner": "phadej", + "ref": "master", + "repo": "cabal-fmt", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1600853454, + "narHash": "sha256-EgsgbcJNZ9AQLVhjhfiegGjLbO+StBY9hfKsCwc8Hw8=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "94cf59784c73ecec461eaa291918eff0bfb538ac", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-utils": { + "locked": { + "lastModified": 1600209923, + "narHash": "sha256-zoOWauTliFEjI++esk6Jzk7QO5EKpddWXQm9yQK24iM=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "3cd06d3c1df6879c9e41cb2c33113df10566c760", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "gitignore-nix-src": { + "flake": false, + "locked": { + "lastModified": 1594969032, + "narHash": "sha256-nbZfz02QoVe1yYK7EtCV7wMi4VdHzZEoPg20ZSDo9to=", + "owner": "hercules-ci", + "repo": "gitignore", + "rev": "c4662e662462e7bf3c2a968483478a665d00e717", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "ref": "master", + "repo": "gitignore", + "type": "github" + } + }, + "hindent-src": { + "flake": false, + "locked": { + "lastModified": 1599403541, + "narHash": "sha256-Js9uf2JSID3Ny5DVJbJ/cAbOPthcb1fKXCfyk+uoLT0=", + "owner": "chrisdone", + "repo": "hindent", + "rev": "a75d4033969ecf7d4d0a5eb985d3d1abb3b9301f", + "type": "github" + }, + "original": { + "owner": "chrisdone", + "ref": "master", + "repo": "hindent", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1600791342, + "narHash": "sha256-rHcPneff0ktF2Jhmh8kd+NqP1SaHa6uU2YJx1kyG1qQ=", + "owner": "NixOS", + "repo": "nixpkgs-channels", + "rev": "6ec10fc77e56b9f848930f129833cfbbac736e4f", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-20.03", + "repo": "nixpkgs-channels", + "type": "github" + } + }, + "root": { + "inputs": { + "cabal-fmt-src": "cabal-fmt-src", + "flake-compat": "flake-compat", + "flake-utils": "flake-utils", + "gitignore-nix-src": "gitignore-nix-src", + "hindent-src": "hindent-src", + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 00000000..b9408e2a --- /dev/null +++ b/flake.nix @@ -0,0 +1,88 @@ +{ + description = "Seamless integration of pre-commit git hooks with Nix"; + + inputs.nixpkgs.url = "github:NixOS/nixpkgs-channels/nixos-20.03"; + + inputs.flake-utils.url = "github:numtide/flake-utils"; + + inputs.flake-compat = { + url = "github:edolstra/flake-compat"; + flake = false; + }; + + inputs.cabal-fmt-src = { + url = "github:phadej/cabal-fmt/master"; + flake = false; + }; + + inputs.gitignore-nix-src = { + url = "github:hercules-ci/gitignore/master"; + flake = false; + }; + + inputs.hindent-src = { + url = "github:chrisdone/hindent/master"; + flake = false; + }; + + outputs = { self, nixpkgs, flake-utils, flake-compat, cabal-fmt-src, gitignore-nix-src, hindent-src }: + let + module = { config, lib, pkgs, ... }: let + inherit (lib) + mkDefault + ; + inherit (import gitignore-nix-src { inherit lib; }) + gitignoreSource + ; + in + { + imports = [ ./modules/all-modules.nix ]; + + config = { + pre-commit.tools = mkDefault ((pkgs.callPackage ./nix { inherit hindent-src cabal-fmt-src; }).callPackage ./nix/tools.nix {}); + pre-commit.rootSrc = mkDefault (gitignoreSource config.root); + }; + }; + in + flake-utils.lib.eachDefaultSystem + ( + system: + let + pkgs = import ./nix { + inherit nixpkgs hindent-src cabal-fmt-src system; + pre-commit-hooks-module = module; + }; + pre-commit-check = pkgs.packages.run { + src = ./.; + hooks = { + shellcheck.enable = true; + nixpkgs-fmt.enable = true; + }; + excludes = [ + # autogenerated by nix flake update + "flake.lock$" + ]; + }; + in + + { + packages = pkgs.packages; + + projectModules.pre-commit-hooks = module; + + inherit pre-commit-check; + + devShell = + pkgs.mkShell { + inherit (pre-commit-check) shellHook; + }; + } + ) + // rec { + templates.pre-commit-hook = { + path = ./templates/pre-commit-hooks; + description = "Nix flake with pre-commit hooks"; + }; + defaultTemplate = templates.pre-commit-hook; + }; +} diff --git a/modules/pre-commit.nix b/modules/pre-commit.nix index 3d2bbf6f..efa10e44 100644 --- a/modules/pre-commit.nix +++ b/modules/pre-commit.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, ... }: +{ config, lib, pkgs, gitignore-nix, ... }: let @@ -165,10 +165,6 @@ let [ $? -eq 0 ] && exit $exitcode ''; - # TODO: provide a default pin that the user may override - inherit (import (import ../nix/sources.nix)."gitignore.nix" { inherit lib; }) - gitignoreSource - ; in { options.pre-commit = @@ -198,9 +194,6 @@ in nix-pre-commit comes with its own set of packages for this purpose. ''; - # This default is for when the module is the entry point rather than - # /default.nix. /default.nix will override this for efficiency. - default = (import ../nix { inherit (pkgs) system; }).callPackage ../nix/tools.nix {}; defaultText = literalExample ''nix-pre-commit-hooks-pkgs.callPackage tools-dot-nix { inherit (pkgs) system; }''; }; @@ -245,7 +238,6 @@ in The source of the project to be checked. ''; defaultText = literalExample ''gitignoreSource config.root''; - default = gitignoreSource config.root; }; excludes = diff --git a/nix/default.nix b/nix/default.nix index 17ab931f..50fa113a 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -1,4 +1,7 @@ -{ sources ? import ./sources.nix +{ nixpkgs +, hindent-src +, cabal-fmt-src +, pre-commit-hooks-module , system ? builtins.currentSystem }: @@ -7,12 +10,11 @@ let _: pkgs: let cabal-fmt = - pkgs.haskellPackages.callCabal2nix "cabal-fmt" sources.cabal-fmt {}; + pkgs.haskellPackages.callCabal2nix "cabal-fmt" cabal-fmt-src {}; in { - inherit (pkgs) nixfmt niv ormolu nixpkgs-fmt nix-linter; hindent = - pkgs.haskellPackages.callCabal2nix "hindent" sources.hindent {}; + pkgs.haskellPackages.callCabal2nix "hindent" hindent-src {}; cabal-fmt = cabal-fmt.overrideScope ( self: super: @@ -20,11 +22,13 @@ let Cabal = self.Cabal_3_0_0_0; } ); - packages = pkgs.callPackages ./packages.nix {}; + packages = pkgs.callPackages ./packages.nix { inherit pre-commit-hooks-module; }; }; in -import sources.nixpkgs { - overlays = [ overlay ]; +import nixpkgs { + overlays = [ + overlay + ]; config = {}; inherit system; } diff --git a/nix/packages.nix b/nix/packages.nix index 4a7d7dbd..74d97839 100644 --- a/nix/packages.nix +++ b/nix/packages.nix @@ -1,23 +1,9 @@ -{ niv, gitAndTools, callPackage }: +{ gitAndTools, callPackage, pre-commit-hooks-module }: let tools = callPackage ./tools.nix {}; in tools // rec { - inherit niv; inherit (gitAndTools) pre-commit; - run = callPackage ./run.nix { inherit tools; }; - - # A pre-commit-check for nix-pre-commit itself - pre-commit-check = run { - src = ../.; - hooks = { - shellcheck.enable = true; - nixpkgs-fmt.enable = true; - }; - excludes = [ - # autogenerated by niv - "nix/sources.nix$" - ]; - }; + run = callPackage ./run.nix { inherit tools pre-commit-hooks-module; }; } diff --git a/nix/project-module.nix b/nix/project-module.nix deleted file mode 100644 index 1639ef7d..00000000 --- a/nix/project-module.nix +++ /dev/null @@ -1,11 +0,0 @@ -/* - This module is picked up by https://github.com/hercules-ci/project.nix - */ -{ - imports = - [ - ../modules/all-modules.nix - ]; - - # TODO: move project.nix/modules/pre-commit.nix here -} diff --git a/nix/run.nix b/nix/run.nix index 601d402e..44d9da3c 100644 --- a/nix/run.nix +++ b/nix/run.nix @@ -1,4 +1,4 @@ -builtinStuff@{ pkgs, tools, pre-commit, git, runCommand, writeText, writeScript, lib }: +builtinStuff@{ pkgs, tools, pre-commit, git, runCommand, writeText, writeScript, lib, pre-commit-hooks-module }: { src , hooks ? {} @@ -8,13 +8,11 @@ builtinStuff@{ pkgs, tools, pre-commit, git, runCommand, writeText, writeScript, }: let - sources = import ./sources.nix; - project = lib.evalModules { modules = [ - ../modules/all-modules.nix + pre-commit-hooks-module { options = { diff --git a/nix/sources.json b/nix/sources.json deleted file mode 100644 index 52aceb30..00000000 --- a/nix/sources.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "cabal-fmt": { - "branch": "master", - "description": "An experiment of formatting .cabal files", - "homepage": null, - "owner": "phadej", - "repo": "cabal-fmt", - "rev": "177b4ca8a446682ee91c536a5492157c1e232864", - "sha256": "1yir989pl2aizlv9wsf5amrixdayf1rc97n8jwfy0f63szrw1rf9", - "type": "tarball", - "url": "https://github.com/phadej/cabal-fmt/archive/177b4ca8a446682ee91c536a5492157c1e232864.tar.gz", - "url_template": "https://github.com///archive/.tar.gz" - }, - "gitignore.nix": { - "branch": "master", - "description": "Nix function for filtering local git sources", - "homepage": "", - "owner": "hercules-ci", - "repo": "gitignore.nix", - "rev": "2ced4519f865341adcb143c5d668f955a2cb997f", - "sha256": "0fc5bgv9syfcblp23y05kkfnpgh3gssz6vn24frs8dzw39algk2z", - "type": "tarball", - "url": "https://github.com/hercules-ci/gitignore/archive/2ced4519f865341adcb143c5d668f955a2cb997f.tar.gz", - "url_template": "https://github.com///archive/.tar.gz" - }, - "hindent": { - "branch": "master", - "description": "Haskell pretty printer", - "homepage": null, - "owner": "chrisdone", - "repo": "hindent", - "rev": "1583be4a8a01b765841f7306284528ae713abb7b", - "sha256": "sha256:1l8v3vq3yw7zr1yxyscfw8lggcf0klnyszhv18505c6myybp2dkp", - "type": "tarball", - "url": "https://github.com/chrisdone/hindent/archive/1583be4a8a01b765841f7306284528ae713abb7b.tar.gz", - "url_template": "https://github.com///archive/.tar.gz" - }, - "nixpkgs": { - "branch": "nixos-20.03", - "description": "A read-only mirror of NixOS/nixpkgs tracking the released channels. Send issues and PRs to", - "homepage": "https://github.com/NixOS/nixpkgs", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "99a3d7a86fce9e9c9f23b3e304d7d2b1270a12b8", - "sha256": "0i40cl3n6600z2lkwrpiy28dcnv2r63fcgfswj91aaf1xfn2chql", - "type": "tarball", - "url": "https://github.com/NixOS/nixpkgs/archive/99a3d7a86fce9e9c9f23b3e304d7d2b1270a12b8.tar.gz", - "url_template": "https://github.com///archive/.tar.gz" - } -} diff --git a/nix/sources.nix b/nix/sources.nix deleted file mode 100644 index c9c3d525..00000000 --- a/nix/sources.nix +++ /dev/null @@ -1,95 +0,0 @@ -# This file has been generated by Niv. - -# A record, from name to path, of the third-party packages -with rec -{ - pkgs = - if hasNixpkgsPath - then - if hasThisAsNixpkgsPath - then import (builtins_fetchTarball { inherit (sources_nixpkgs) url sha256; }) {} - else import {} - else - import (builtins_fetchTarball { inherit (sources_nixpkgs) url sha256; }) {}; - - sources_nixpkgs = - if builtins.hasAttr "nixpkgs" sources - then sources.nixpkgs - else abort - '' - Please specify either (through -I or NIX_PATH=nixpkgs=...) or - add a package called "nixpkgs" to your sources.json. - ''; - - # fetchTarball version that is compatible between all the versions of Nix - builtins_fetchTarball = - { url, sha256 }@attrs: - let - inherit (builtins) lessThan nixVersion fetchTarball; - in - if lessThan nixVersion "1.12" then - fetchTarball { inherit url; } - else - fetchTarball attrs; - - # fetchurl version that is compatible between all the versions of Nix - builtins_fetchurl = - { url, sha256 }@attrs: - let - inherit (builtins) lessThan nixVersion fetchurl; - in - if lessThan nixVersion "1.12" then - fetchurl { inherit url; } - else - fetchurl attrs; - - # A wrapper around pkgs.fetchzip that has inspectable arguments, - # annoyingly this means we have to specify them - fetchzip = { url, sha256 }@attrs: pkgs.fetchzip attrs; - - hasNixpkgsPath = (builtins.tryEval ).success; - hasThisAsNixpkgsPath = - (builtins.tryEval ).success && == ./.; - - sources = builtins.fromJSON (builtins.readFile ./sources.json); - - mapAttrs = builtins.mapAttrs or - ( - f: set: with builtins; - listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set)) - ); - - # borrowed from nixpkgs - functionArgs = f: f.__functionArgs or (builtins.functionArgs f); - callFunctionWith = autoArgs: f: args: - let - auto = builtins.intersectAttrs (functionArgs f) autoArgs; - in - f (auto // args); - - getFetcher = spec: - let - fetcherName = - if builtins.hasAttr "type" spec - then builtins.getAttr "type" spec - else "builtin-tarball"; - in - builtins.getAttr fetcherName { - "tarball" = fetchzip; - "builtin-tarball" = builtins_fetchTarball; - "file" = pkgs.fetchurl; - "builtin-url" = builtins_fetchurl; - }; -}; -# NOTE: spec must _not_ have an "outPath" attribute -mapAttrs ( - _: spec: - if builtins.hasAttr "outPath" spec - then abort - "The values in sources.json should not have an 'outPath' attribute" - else - if builtins.hasAttr "url" spec && builtins.hasAttr "sha256" spec - then - spec // { outPath = callFunctionWith spec (getFetcher spec) {}; } - else spec -) sources diff --git a/nix/tools.nix b/nix/tools.nix index de115d7c..2ce24d44 100644 --- a/nix/tools.nix +++ b/nix/tools.nix @@ -6,7 +6,6 @@ , hindent , cabal-fmt , elmPackages -, niv , gitAndTools , runCommand , writeText diff --git a/shell.nix b/shell.nix index f38bbdc3..6f196112 100644 --- a/shell.nix +++ b/shell.nix @@ -1,6 +1,13 @@ -with { pkgs = import ./nix {}; }; - -pkgs.mkShell { - buildInputs = [ pkgs.niv ]; - inherit ((import ./.).pre-commit-check) shellHook; -} +( + import ( + let + lock = builtins.fromJSON (builtins.readFile ./flake.lock); + in + fetchTarball { + url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; + sha256 = lock.nodes.flake-compat.locked.narHash; + } + ) { + src = ./.; + } +).shellNix diff --git a/templates/pre-commit-hooks/.gitignore b/templates/pre-commit-hooks/.gitignore new file mode 100644 index 00000000..fcf72467 --- /dev/null +++ b/templates/pre-commit-hooks/.gitignore @@ -0,0 +1 @@ +/.pre-commit-config.yaml diff --git a/templates/pre-commit-hooks/flake.nix b/templates/pre-commit-hooks/flake.nix new file mode 100644 index 00000000..210f9b9e --- /dev/null +++ b/templates/pre-commit-hooks/flake.nix @@ -0,0 +1,31 @@ +{ + description = ""; + + inputs.nixpkgs.url = "github:NixOS/nixpkgs-channels/nixos-20.03"; + + inputs.flake-utils.url = "github:numtide/flake-utils"; + + inputs.pre-commit-hooks.url = "github:cachix/pre-commit-hooks.nix/master"; + + outputs = { self, nixpkgs, flake-utils, pre-commit-hooks }: + flake-utils.lib.eachDefaultSystem + ( + system: rec { + pre-commit-check = pre-commit-hooks.packages.${system}.run { + src = ./.; + # If your hooks are intrusive, avoid running on each commit with a default_states like this: + # default_stages = ["manual" "push"]; + hooks = { + elm-format.enable = true; + ormolu.enable = true; + shellcheck.enable = true; + }; + }; + + devShell = + nixpkgs.legacyPackages.${system}.mkShell { + inherit (pre-commit-check) shellHook; + }; + } + ); +}