├── .vscode
├── settings.json
└── extensions.json
├── test
├── simple
│ ├── README.md
│ ├── src
│ │ └── Main.hs
│ ├── script
│ ├── haskell-flake-test.cabal
│ └── flake.nix
├── with-subdir
│ ├── cabal.project
│ ├── haskell-flake-test
│ │ ├── README.md
│ │ ├── src
│ │ │ └── Main.hs
│ │ └── haskell-flake-test.cabal
│ └── flake.nix
├── otherOverlays
│ ├── src
│ │ └── Main.hs
│ ├── haskell-flake-test.cabal
│ └── flake.nix
├── project-module
│ ├── src
│ │ └── Main.hs
│ ├── haskell-flake-test.cabal
│ └── flake.nix
├── cabal2nix
│ ├── src
│ │ └── Main.hs
│ ├── default.nix
│ ├── haskell-flake-test.cabal
│ └── flake.nix
├── settings-defaults
│ ├── src
│ │ └── Main.hs
│ ├── haskell-flake-test.cabal
│ └── flake.nix
└── README.md
├── .gitignore
├── .envrc
├── doc
├── haskell-flake.webp
├── README
├── ref.md
├── guide.md
├── index.yaml
├── index.md
├── defaults.md
├── examples.md
├── gotchas.md
├── start.md
├── under-the-hood.md
├── hls.md
├── devshell.md
├── flake.nix
├── local.md
├── package-set.md
├── size.md
├── modules.md
├── docker.md
├── debugging.md
├── dependency.md
├── settings.md
└── flake.lock
├── example
├── src
│ └── Main.hs
├── example.cabal
├── flake.lock
└── flake.nix
├── nix
├── modules
│ ├── default.nix
│ ├── project-modules.nix
│ ├── project
│ │ ├── settings
│ │ │ ├── lib.nix
│ │ │ ├── default.nix
│ │ │ └── all.nix
│ │ ├── packages
│ │ │ ├── default.nix
│ │ │ └── package.nix
│ │ ├── hls-check.nix
│ │ ├── default.nix
│ │ ├── outputs.nix
│ │ ├── devshell.nix
│ │ └── defaults.nix
│ ├── projects.nix
│ └── project-tests.nix
├── types
│ ├── haskell-overlay-type.nix
│ └── app-type.nix
├── logging.nix
├── haskell-parsers
│ ├── README.md
│ ├── test
│ │ ├── flake.nix
│ │ └── flake.lock
│ ├── parser.nix
│ ├── parser_tests.nix
│ └── default.nix
└── build-haskell-package.nix
├── justfile
├── .github
└── workflows
│ ├── update-flake-lock-doc.yaml
│ └── update-flake-lock-example.yaml
├── flake.nix
├── vira.hs
├── LICENSE
├── dev
├── flake.nix
└── flake.lock
├── README.md
└── CHANGELOG.md
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | }
--------------------------------------------------------------------------------
/test/simple/README.md:
--------------------------------------------------------------------------------
1 | # simple
2 |
3 | Just a README file.
4 |
5 |
--------------------------------------------------------------------------------
/test/with-subdir/cabal.project:
--------------------------------------------------------------------------------
1 | packages:
2 | ./haskell-flake-test
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .direnv
2 | result
3 | result-*
4 | dist-newstyle
5 | .direnv
6 |
--------------------------------------------------------------------------------
/test/with-subdir/haskell-flake-test/README.md:
--------------------------------------------------------------------------------
1 | A cabal package in a sub-dir.
2 |
--------------------------------------------------------------------------------
/.envrc:
--------------------------------------------------------------------------------
1 | watch_file dev/flake.nix
2 | use flake ./dev --override-input haskell-flake .
3 |
--------------------------------------------------------------------------------
/doc/haskell-flake.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/srid/haskell-flake/HEAD/doc/haskell-flake.webp
--------------------------------------------------------------------------------
/example/src/Main.hs:
--------------------------------------------------------------------------------
1 | module Main where
2 |
3 | main :: IO ()
4 | main = putStrLn "Hello, Haskell!"
5 |
--------------------------------------------------------------------------------
/doc/README:
--------------------------------------------------------------------------------
1 | For contributing to the docs, please see
2 |
3 | https://community.flake.parts/about#contributing
4 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "mkhl.direnv",
4 | "bbenoist.nix"
5 | ]
6 | }
--------------------------------------------------------------------------------
/test/simple/src/Main.hs:
--------------------------------------------------------------------------------
1 | module Main where
2 |
3 | import Foo
4 |
5 | main :: IO ()
6 | main = do
7 | fooFunc
8 |
--------------------------------------------------------------------------------
/test/otherOverlays/src/Main.hs:
--------------------------------------------------------------------------------
1 | module Main where
2 |
3 | import Foo
4 |
5 | main :: IO ()
6 | main = do
7 | fooFunc
8 |
--------------------------------------------------------------------------------
/test/project-module/src/Main.hs:
--------------------------------------------------------------------------------
1 | module Main where
2 |
3 | import Foo
4 |
5 | main :: IO ()
6 | main = do
7 | fooFunc
8 |
--------------------------------------------------------------------------------
/test/with-subdir/haskell-flake-test/src/Main.hs:
--------------------------------------------------------------------------------
1 | module Main where
2 |
3 | main :: IO ()
4 | main = do
5 | putStrLn "Hello"
6 |
--------------------------------------------------------------------------------
/nix/modules/default.nix:
--------------------------------------------------------------------------------
1 | {
2 | imports = [
3 | ./projects.nix
4 | ./project-modules.nix
5 | ./project-tests.nix
6 | ];
7 | }
8 |
--------------------------------------------------------------------------------
/test/cabal2nix/src/Main.hs:
--------------------------------------------------------------------------------
1 | module Main where
2 |
3 | import System.Random
4 |
5 | main :: IO ()
6 | main = putStrLn "Hello, Haskell!"
7 |
--------------------------------------------------------------------------------
/doc/ref.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: -8
3 | ---
4 |
5 | # Reference
6 |
7 | - [Module options](https://flake.parts/options/haskell-flake)
8 | - [[docker]]#
--------------------------------------------------------------------------------
/test/simple/script:
--------------------------------------------------------------------------------
1 | import Toml.Type.Value (Value(Bool))
2 |
3 | main :: IO ()
4 | main = do
5 | putStrLn ("I depend on the `tomland` package. Here's a TOML-flavored boolean: " <> show (Bool True))
6 |
--------------------------------------------------------------------------------
/doc/guide.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: -9
3 | ---
4 |
5 | # Guide
6 |
7 | - [[local]]#
8 | - [[dependency]]#
9 | - [[settings]]#
10 | - [[defaults]]#
11 | - [[devshell]]#
12 | - [[package-set]]#
13 | - [[modules]]#
14 | - [[debugging]]#
15 | - [[size]]#
--------------------------------------------------------------------------------
/test/settings-defaults/src/Main.hs:
--------------------------------------------------------------------------------
1 | module Main where
2 |
3 | import System.Random
4 |
5 | main :: IO ()
6 | main = do
7 | -- Generate a random number between 1 and 100 (inclusive)
8 | randomNumber <- randomRIO (1, 100) :: IO Int
9 | putStrLn $ "Hello " ++ show randomNumber
10 |
--------------------------------------------------------------------------------
/test/README.md:
--------------------------------------------------------------------------------
1 | # haskell-flake tests
2 |
3 | The following tests are available:
4 |
5 | | Directory | Description |
6 | | --- | --- |
7 | | `simple/` | Basic functionalities |
8 | | `with-subdir` | Prevent redundant rebuilds when parent contents change |
9 | | `project-module`| Default project modules are generated correctly |
10 |
11 |
--------------------------------------------------------------------------------
/test/cabal2nix/default.nix:
--------------------------------------------------------------------------------
1 | { mkDerivation, base, lib, random }:
2 | mkDerivation {
3 | pname = "haskell-flake-test";
4 | version = "0.1.0.0";
5 | src = ./.;
6 | isLibrary = false;
7 | isExecutable = true;
8 | executableHaskellDepends = [ base random ];
9 | license = "unknown";
10 | mainProgram = "haskell-flake-test";
11 | }
12 |
--------------------------------------------------------------------------------
/nix/types/haskell-overlay-type.nix:
--------------------------------------------------------------------------------
1 | { lib, ... }:
2 |
3 | # Use this instead of types.functionTo, because Haskell overlay functions cannot
4 | # be merged (whereas functionTo's can).
5 | lib.mkOptionType {
6 | name = "haskell-overlay";
7 | description = ''
8 | A Haskell overlay function taking 'self' and 'super' args.
9 | '';
10 | descriptionClass = "noun";
11 | check = lib.isFunction;
12 | merge = lib.mergeOneOption;
13 | }
14 |
--------------------------------------------------------------------------------
/example/example.cabal:
--------------------------------------------------------------------------------
1 | cabal-version: 3.0
2 | name: example
3 | version: 0.1.0.0
4 | license: NONE
5 | author: Joe
6 | maintainer: joe@example.com
7 | build-type: Simple
8 |
9 | common warnings
10 | ghc-options: -Wall
11 |
12 | executable example
13 | import: warnings
14 | main-is: Main.hs
15 | build-depends: base
16 | hs-source-dirs: src
17 | default-language: Haskell2010
18 |
--------------------------------------------------------------------------------
/justfile:
--------------------------------------------------------------------------------
1 | default:
2 | @just --list
3 |
4 | # Run example
5 | ex:
6 | cd ./example && nix run . --override-input haskell-flake ..
7 |
8 | # Run the checks locally using nixci
9 | check:
10 | nixci
11 |
12 | # Auto-format the Nix files in project tree
13 | fmt:
14 | treefmt
15 |
16 | # Open haskell-flake docs live preview
17 | docs:
18 | cd ./doc && nix run
19 |
20 | # Open flake.parts docs, previewing local haskell-flake version
21 | docs-flake-parts:
22 | cd ./doc && nix run .#flake-parts
23 |
--------------------------------------------------------------------------------
/nix/logging.nix:
--------------------------------------------------------------------------------
1 | { name, ... }:
2 |
3 | {
4 | # traceDebug uses traceVerbose; and is no-op on Nix versions below 2.10
5 | traceDebug =
6 | if builtins.compareVersions "2.10" builtins.nixVersion < 0
7 | then msg: builtins.traceVerbose ("DEBUG[haskell-flake] [${name}]: " + msg)
8 | else x: x;
9 |
10 | traceWarning = msg:
11 | builtins.trace ("WARNING[haskell-flake] [${name}]: " + msg);
12 |
13 | throwError = msg: builtins.throw ''
14 | ERROR[haskell-flake] [${name}]: ${msg}
15 | '';
16 | }
17 |
--------------------------------------------------------------------------------
/doc/index.yaml:
--------------------------------------------------------------------------------
1 | # For documentation and available settings, see
2 | # https://github.com/srid/emanote/blob/master/emanote/default/index.yaml
3 |
4 | page:
5 | siteTitle: haskell-flake
6 | headHtml: |
7 |
8 |
9 | template:
10 | # You can add your own variables here, like editBaseUrl.
11 | # See after-note.tpl to see where editBaseUrl gets used.
12 | editBaseUrl: https://github.com/srid/haskell-flake/edit/master/doc
13 | sidebar:
14 | collapsed: false
15 | urlStrategy: pretty
16 |
--------------------------------------------------------------------------------
/test/cabal2nix/haskell-flake-test.cabal:
--------------------------------------------------------------------------------
1 | cabal-version: 3.0
2 | name: haskell-flake-test
3 | version: 0.1.0.0
4 | license: NONE
5 | author: Joe
6 | maintainer: joe@example.com
7 | build-type: Simple
8 |
9 | common warnings
10 | ghc-options: -Wall
11 |
12 | executable haskell-flake-test
13 | import: warnings
14 | main-is: Main.hs
15 | build-depends:
16 | base,
17 | random
18 | hs-source-dirs: src
19 | default-language: Haskell2010
20 |
21 |
--------------------------------------------------------------------------------
/test/otherOverlays/haskell-flake-test.cabal:
--------------------------------------------------------------------------------
1 | cabal-version: 3.0
2 | name: haskell-flake-test
3 | version: 0.1.0.0
4 | license: NONE
5 | author: Joe
6 | maintainer: joe@example.com
7 | build-type: Simple
8 |
9 | common warnings
10 | ghc-options: -Wall
11 |
12 | executable haskell-flake-test
13 | import: warnings
14 | main-is: Main.hs
15 | build-depends:
16 | base,
17 | foo
18 | hs-source-dirs: src
19 | default-language: Haskell2010
20 |
--------------------------------------------------------------------------------
/test/project-module/haskell-flake-test.cabal:
--------------------------------------------------------------------------------
1 | cabal-version: 3.0
2 | name: haskell-flake-test
3 | version: 0.1.0.0
4 | license: NONE
5 | author: Joe
6 | maintainer: joe@example.com
7 | build-type: Simple
8 |
9 | common warnings
10 | ghc-options: -Wall
11 |
12 | executable haskell-flake-test
13 | import: warnings
14 | main-is: Main.hs
15 | build-depends:
16 | base,
17 | foo
18 | hs-source-dirs: src
19 | default-language: Haskell2010
20 |
--------------------------------------------------------------------------------
/test/with-subdir/haskell-flake-test/haskell-flake-test.cabal:
--------------------------------------------------------------------------------
1 | cabal-version: 3.0
2 | name: haskell-flake-test
3 | version: 0.1.0.0
4 | license: NONE
5 | author: Joe
6 | maintainer: joe@example.com
7 | build-type: Simple
8 |
9 | common warnings
10 | ghc-options: -Wall
11 |
12 | executable haskell-flake-test
13 | import: warnings
14 | main-is: Main.hs
15 | build-depends:
16 | base
17 | hs-source-dirs: src
18 | default-language: Haskell2010
19 |
--------------------------------------------------------------------------------
/test/settings-defaults/haskell-flake-test.cabal:
--------------------------------------------------------------------------------
1 | cabal-version: 3.0
2 | name: haskell-flake-test
3 | version: 0.1.0.0
4 | license: NONE
5 | author: Joe
6 | maintainer: joe@example.com
7 | build-type: Simple
8 |
9 | common warnings
10 | ghc-options: -Wall
11 |
12 | executable haskell-flake-test
13 | import: warnings
14 | main-is: Main.hs
15 | build-depends:
16 | base,
17 | random
18 | hs-source-dirs: src
19 | default-language: Haskell2010
20 |
--------------------------------------------------------------------------------
/doc/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | short-title: haskell-flake
3 | template:
4 | sidebar:
5 | collapsed: true
6 | emanote:
7 | folder-folgezettel: false
8 | ---
9 |
10 | # Haskell development using `haskell-flake`
11 |
12 | [haskell-flake](https://github.com/srid/haskell-flake) is a [flake-parts](https://flake.parts/) module to make Haskell development [[under-the-hood|simpler]] with [Nix](https://nixos.asia/en/nix).
13 |
14 | To get started, see [[start]]# and thereon see [[guide]]#. To get inspired, see [[examples]]#. For reference, see [[ref]]#.
15 |
16 | [![[haskell-flake.webp]]]{.w-32}
--------------------------------------------------------------------------------
/doc/defaults.md:
--------------------------------------------------------------------------------
1 |
2 | # Default options
3 |
4 | haskell-flake provides sensible defaults for various options. See [defaults.nix].
5 |
6 | [defaults.nix]: https://github.com/srid/haskell-flake/blob/master/nix/modules/project/defaults.nix
7 |
8 | {#override}
9 | ## Overriding defaults
10 |
11 | {#packages}
12 | ### Overriding default local packages
13 |
14 | This example shows how to specify [[local]] manually.
15 |
16 | ```nix
17 | {
18 | haskellProjects.default = {
19 | # Specify local packages manually
20 | defaults.packages = {
21 | foo.source = ./foo;
22 | };
23 | };
24 | }
25 | ```
26 |
--------------------------------------------------------------------------------
/test/simple/haskell-flake-test.cabal:
--------------------------------------------------------------------------------
1 | cabal-version: 3.0
2 | name: haskell-flake-test
3 | version: 0.1.0.0
4 | license: NONE
5 | author: Joe
6 | maintainer: joe@example.com
7 | build-type: Simple
8 |
9 | common warnings
10 | ghc-options: -Wall
11 |
12 | executable haskell-flake-test
13 | import: warnings
14 | main-is: Main.hs
15 | build-depends:
16 | base,
17 | -- Add a version constraint that fails unless jailbroken.
18 | --
19 | -- This exists to test if the user's setting to enable `jailbreak` paramounts
20 | -- `buildFromSdist`'s setting to disable it
21 | foo >= 0.2
22 | hs-source-dirs: src
23 | default-language: Haskell2010
24 |
--------------------------------------------------------------------------------
/nix/haskell-parsers/README.md:
--------------------------------------------------------------------------------
1 | # `haskell-parsers`
2 |
3 | `haskell-parsers` provides parsers for Haskell associated files: cabal and cabal.project. It provides:
4 |
5 | - **`findPackagesInCabalProject`**: a superior alternative to nixpkgs' [`haskellPathsInDir`](https://github.com/NixOS/nixpkgs/blob/f991762ea1345d850c06cd9947700f3b08a12616/lib/filesystem.nix#L18).
6 | - It locates packages based on the "packages" field of `cabal.project` file if it exists (otherwise it returns the top-level package).
7 | - **`getCabalExecutables`**: a function to extract executables from a `.cabal` file.
8 |
9 | ## Limitations
10 |
11 | - Glob patterns in `cabal.project` are not supported yet. Feel free to open a PR improving the parser to support them.
12 | - https://github.com/srid/haskell-flake/issues/113
13 |
--------------------------------------------------------------------------------
/nix/haskell-parsers/test/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | inputs = {
3 | nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
4 | flake-parts.url = "github:hercules-ci/flake-parts";
5 | haskell-parsers = { flake = false; }; # Overriden by nixci (see top-level flake.nix)
6 | };
7 | outputs = inputs:
8 | inputs.flake-parts.lib.mkFlake { inherit inputs; } {
9 | systems = inputs.nixpkgs.lib.systems.flakeExposed;
10 | perSystem = { pkgs, lib, ... }: {
11 | checks.tests =
12 | let
13 | result = import (inputs.haskell-parsers + /parser_tests.nix) { inherit pkgs lib; };
14 | in
15 | pkgs.writeTextFile {
16 | name = "haskell-parsers-test-results";
17 | text = builtins.toJSON result;
18 | };
19 | };
20 | };
21 | }
22 |
--------------------------------------------------------------------------------
/.github/workflows/update-flake-lock-doc.yaml:
--------------------------------------------------------------------------------
1 | name: update-flake-lock-doc
2 | on:
3 | workflow_dispatch: # allows manual triggering
4 | # schedule:
5 | # - cron: '0 0 * * 0' # runs weekly on Sunday at 00:00
6 |
7 | jobs:
8 | doc-lock:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Checkout repository
12 | uses: actions/checkout@v4
13 | - name: Install Nix
14 | uses: DeterminateSystems/nix-installer-action@main
15 | - name: Update flake.lock
16 | uses: DeterminateSystems/update-flake-lock@main
17 | with:
18 | commit-msg: "chore(doc): Update flake.lock"
19 | pr-title: "Update doc/flake.lock"
20 | pr-labels: |
21 | automated
22 | path-to-flake-dir: 'doc/'
23 | branch: "update_flake_lock_doc"
24 |
--------------------------------------------------------------------------------
/.github/workflows/update-flake-lock-example.yaml:
--------------------------------------------------------------------------------
1 | name: update-flake-lock-example
2 | on:
3 | workflow_dispatch: # allows manual triggering
4 | schedule:
5 | - cron: '0 0 * * 0' # runs weekly on Sunday at 00:00
6 |
7 | jobs:
8 | example-lock:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Checkout repository
12 | uses: actions/checkout@v4
13 | - name: Install Nix
14 | uses: DeterminateSystems/nix-installer-action@main
15 | - name: Update flake.lock
16 | uses: DeterminateSystems/update-flake-lock@main
17 | with:
18 | commit-msg: "chore(example): Update flake.lock"
19 | pr-title: "Update example/flake.lock"
20 | pr-labels: |
21 | automated
22 | path-to-flake-dir: 'example/'
23 | branch: "update_flake_lock_example"
24 |
--------------------------------------------------------------------------------
/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | description = "A `flake-parts` module for Haskell development";
3 | outputs = inputs: {
4 | flakeModule = ./nix/modules;
5 |
6 | templates.default = {
7 | description = "A simple flake.nix using haskell-flake";
8 | path = builtins.path { path = ./example; filter = path: _: baseNameOf path == "flake.nix"; };
9 | };
10 | templates.example = {
11 | description = "Example Haskell project using haskell-flake";
12 | path = builtins.path { path = ./example; };
13 | };
14 |
15 | om = {
16 | # https://omnix.page/om/init.html#spec
17 | templates.haskell-flake = {
18 | template = inputs.self.templates.example;
19 | params = [
20 | {
21 | name = "package-name";
22 | description = "Name of the Haskell package";
23 | placeholder = "example";
24 | }
25 | ];
26 | };
27 | };
28 | };
29 | }
30 |
--------------------------------------------------------------------------------
/doc/examples.md:
--------------------------------------------------------------------------------
1 | # Examples
2 |
3 | - [srid/haskell-template](https://github.com/srid/haskell-template/blob/master/flake.nix)
4 | - [fpindia/fpindia-site](https://github.com/fpindia/fpindia-site/blob/master/flake.nix)
5 | - [srid/haskell-multi-nix](https://github.com/srid/haskell-multi-nix/blob/master/flake.nix) - multiple [[local|local packages]]
6 | - [srid/emanote](https://github.com/srid/emanote/blob/master/flake.nix)
7 | - [srid/ema](https://github.com/srid/ema/blob/master/flake.nix)
8 | - [nammayatri/nammayatri backend](https://github.com/nammayatri/nammayatri/blob/main/Backend/default.nix) - a polyglot flake
9 | - [haskell/hackage-server](https://github.com/haskell/hackage-server/blob/master/flake.nix)
10 | - [hercules-ci/warp-systemd](https://github.com/hercules-ci/warp-systemd/blob/master/flake.nix) - a library with a NixOS test
11 | - [KovalevDima/ClickHaskell](https://github.com/KovalevDima/ClickHaskell) - ClickHouse driver for Haskell
12 |
--------------------------------------------------------------------------------
/doc/gotchas.md:
--------------------------------------------------------------------------------
1 | # Gotchas
2 |
3 | {#libssh2}
4 | ## Overriding `libssh2` Haskell library
5 |
6 | Overriding the package with `packages.libssh2.source = "0.2.0.9"` results in infinite recursion.
7 |
8 | Possibly having to do with `cabal2nix` not understanding that [`libssh2` in `pkgconfig-depends` of `libssh2.cabal`](https://github.com/portnov/libssh2-hs/blob/bf7cbe643c7f4fb4fad3963705feb8351471eb01/libssh2/libssh2.cabal#L70)
9 | is not self-referential.
10 |
11 | Use the following [[settings]] configuration to override `libssh2`:
12 |
13 | ```nix
14 | # In `haskellProjects.default`
15 | {
16 | settings = {
17 | libssh2 = {
18 | broken = false;
19 | custom = (p: p.overrideAttrs (oa: rec {
20 | version = "0.2.0.9";
21 | src = pkgs.fetchzip {
22 | url = "mirror://hackage/${oa.pname}-${version}/${oa.pname}-${version}.tar.gz";
23 | sha256 = "sha256-/zzj11iOxkpEsKVwB4+IF8dNZwEuwUlgw+cZYguN8QI=";
24 | };
25 | }));
26 | };
27 | };
28 | }
29 | ```
30 |
31 |
--------------------------------------------------------------------------------
/vira.hs:
--------------------------------------------------------------------------------
1 | -- CI configuration
2 | \ctx pipeline ->
3 | let
4 | isMaster = ctx.branch == "master"
5 | hf = [("haskell-flake", ".")]
6 | in pipeline
7 | { build.systems =
8 | [ "x86_64-linux"
9 | , "aarch64-darwin"
10 | ]
11 | , build.flakes =
12 | [ "./dev" { overrideInputs = hf }
13 | , "./doc" { overrideInputs = hf }
14 | , "./example" { overrideInputs = hf }
15 | , "./nix/haskell-parsers/test" { overrideInputs = [("haskell-parsers", "path:./nix/haskell-parsers")] }
16 | , "./test/simple" { overrideInputs = hf }
17 | , "./test/cabal2nix" { overrideInputs = hf }
18 | , "./test/with-subdir" { overrideInputs = hf }
19 | , "./test/project-module" { overrideInputs = hf }
20 | , "./test/settings-defaults" { overrideInputs = hf }
21 | , "./test/otherOverlays" { overrideInputs = hf }
22 | ]
23 | , signoff.enable = True
24 | , cache.url = if isMaster then Just "https://cache.nixos.asia/oss" else Nothing
25 | }
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Sridhar Ratnakumar
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/doc/start.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: -10
3 | ---
4 |
5 | # Getting Started
6 |
7 | Before using `haskell-flake` you must first [install Nix](https://flakular.in/install).
8 |
9 | ## Existing projects
10 |
11 | To use `haskell-flake` in an *existing* Haskell project, run:
12 |
13 | ```bash
14 | nix flake init -t github:srid/haskell-flake
15 | ```
16 |
17 | Open the generated `flake.nix` and change `self'.packages.example` to use your package name. For example, if your package is named `my-package` (with a `my-package.cabal` file), change `example` to `my-package`. Follow the comments along the `flake.nix` to make any necessary changes to the project configuration.
18 |
19 | ## New projects
20 |
21 | To create a *new* Haskell project, instead, run:
22 |
23 | ```bash
24 | mkdir example && cd ./example
25 | nix flake init -t github:srid/haskell-flake#example
26 | ```
27 |
28 | ### Template
29 |
30 | You may also use https://github.com/srid/haskell-template which already uses `haskell-flake` along with other opinionated defaults.
31 |
32 | ## Under the hood
33 |
34 | ![[under-the-hood]]
35 |
36 | ## Next steps
37 |
38 | Visit [[guide]] for more details, and [[ref]] for module options.
39 |
--------------------------------------------------------------------------------
/nix/modules/project-modules.nix:
--------------------------------------------------------------------------------
1 | { lib, withSystem, ... }:
2 |
3 | let
4 | inherit (lib)
5 | mkOption
6 | types;
7 | in
8 | {
9 | options.flake = mkOption {
10 | type = types.submoduleWith {
11 | modules = [{
12 | options.haskellFlakeProjectModules = mkOption
13 | {
14 | type = types.lazyAttrsOf types.deferredModule;
15 | description = ''
16 | A lazy attrset of `haskellProjects.` modules that can be
17 | imported in other flakes.
18 | '';
19 | defaultText = lib.literalMD ''
20 | Package and dependency information for this project exposed for reuse
21 | in another flake, when using this project as a Haskell dependency.
22 |
23 | The 'output' module of the default project is included by default,
24 | returning `defaults.projectModules.output`.
25 | '';
26 | default = { };
27 | };
28 |
29 | config.haskellFlakeProjectModules = {
30 | output = { pkgs, lib, ... }: withSystem pkgs.system ({ config, ... }:
31 | config.haskellProjects."default".defaults.projectModules.output
32 | );
33 | };
34 | }];
35 | };
36 | };
37 | }
38 |
--------------------------------------------------------------------------------
/test/project-module/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | # Since there is no flake.lock file (to avoid incongruent haskell-flake
3 | # pinning), we must specify revisions for *all* inputs to ensure
4 | # reproducibility.
5 | inputs = {
6 | nixpkgs.url = "github:nixos/nixpkgs/870493f9a8cb0b074ae5b411b2f232015db19a65";
7 | flake-parts.url = "github:hercules-ci/flake-parts/758cf7296bee11f1706a574c77d072b8a7baa881";
8 | haskell-flake = { };
9 |
10 | haskell-multi-nix.url = "github:srid/haskell-multi-nix/d6ac6ccab559f886d1fc7da8cab44b99cb0c2c3d";
11 | haskell-multi-nix.inputs.haskell-flake.follows = "haskell-flake";
12 | haskell-multi-nix.inputs.nixpkgs.follows = "nixpkgs";
13 | haskell-multi-nix.inputs.flake-parts.follows = "flake-parts";
14 | };
15 | outputs = inputs@{ self, nixpkgs, flake-parts, ... }:
16 | flake-parts.lib.mkFlake { inherit inputs; } {
17 | systems = nixpkgs.lib.systems.flakeExposed;
18 | imports = [
19 | inputs.haskell-flake.flakeModule
20 | ];
21 | perSystem = { self', pkgs, ... }: {
22 | haskellProjects.default = {
23 | imports = [ inputs.haskell-multi-nix.haskellFlakeProjectModules.output ];
24 | };
25 | packages.default = self'.packages.haskell-flake-test;
26 | };
27 | };
28 | }
29 |
--------------------------------------------------------------------------------
/dev/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | inputs = {
3 | nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
4 | flake-parts.url = "github:hercules-ci/flake-parts";
5 | flake-root.url = "github:srid/flake-root";
6 | treefmt-nix.url = "github:numtide/treefmt-nix";
7 | haskell-flake.url = "github:srid/haskell-flake";
8 | };
9 | outputs = inputs@{ self, nixpkgs, flake-parts, ... }:
10 | flake-parts.lib.mkFlake { inherit inputs; } {
11 | systems = nixpkgs.lib.systems.flakeExposed;
12 | imports = [
13 | inputs.flake-root.flakeModule
14 | inputs.treefmt-nix.flakeModule
15 | ];
16 | perSystem = { pkgs, lib, config, ... }: {
17 | treefmt.config = {
18 | projectRoot = inputs.haskell-flake;
19 | projectRootFile = "README.md";
20 | programs.nixpkgs-fmt.enable = true;
21 | };
22 | devShells.default = pkgs.mkShell {
23 | # cf. https://community.flake.parts/haskell-flake/devshell#composing-devshells
24 | inputsFrom = [
25 | config.treefmt.build.devShell
26 | ];
27 | packages = with pkgs; [
28 | just
29 | ];
30 | shellHook = ''
31 | echo
32 | echo "🍎🍎 Run 'just ' to get started"
33 | just
34 | '';
35 | };
36 | };
37 | };
38 | }
39 |
--------------------------------------------------------------------------------
/test/cabal2nix/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | # Disable IFD for this test.
3 | nixConfig = {
4 | allow-import-from-derivation = false;
5 | };
6 |
7 | # Since there is no flake.lock file (to avoid incongruent haskell-flake
8 | # pinning), we must specify revisions for *all* inputs to ensure
9 | # reproducibility.
10 | inputs = {
11 | nixpkgs.url = "github:nixos/nixpkgs/870493f9a8cb0b074ae5b411b2f232015db19a65";
12 | flake-parts.url = "github:hercules-ci/flake-parts/758cf7296bee11f1706a574c77d072b8a7baa881";
13 | haskell-flake = { };
14 | };
15 |
16 | outputs = inputs@{ self, nixpkgs, flake-parts, ... }:
17 | flake-parts.lib.mkFlake { inherit inputs; } {
18 | systems = nixpkgs.lib.systems.flakeExposed;
19 | imports = [
20 | inputs.haskell-flake.flakeModule
21 | ];
22 | debug = true;
23 | perSystem = { config, self', pkgs, lib, ... }: {
24 | haskellProjects.default = {
25 | # If IFD is disabled,
26 | # we need to specify the pre-generated `cabal2nix` expressions
27 | # file to haskell-flake for the package,
28 | # otherwise build would fail as it would use `callCabal2nix` function
29 | # which uses IFD.
30 | packages.haskell-flake-test.cabal2NixFile = "default.nix";
31 | settings = { };
32 | };
33 | };
34 | };
35 | }
36 |
--------------------------------------------------------------------------------
/nix/modules/project/settings/lib.nix:
--------------------------------------------------------------------------------
1 | # Provides the `mkCabalSettingOptions` helper for defining settings..???.
2 | { lib, config, ... }:
3 |
4 | let
5 | inherit (lib)
6 | mkOption
7 | types;
8 | inherit (types)
9 | functionTo listOf nullOr;
10 |
11 | mkImplOption = name: f: mkOption {
12 | # [ pkg -> pkg ]
13 | type = listOf (nullOr (functionTo types.package));
14 | description = ''
15 | Implementation for settings.${name}
16 | '';
17 | default =
18 | let
19 | cfg = config.${name};
20 | in
21 | if cfg != null then
22 | (
23 | let g = f cfg;
24 | in lib.optional (g != null) g
25 | ) else [ ];
26 | };
27 |
28 |
29 | mkNullableOption = attrs:
30 | mkOption (attrs // {
31 | type = types.nullOr attrs.type;
32 | default = null;
33 | });
34 |
35 | # This creates `options.${name}` and `options.impl.${name}`.
36 | #
37 | # The user sets the former, whereas the latter provides the list of functions
38 | # to apply on the package (as implementation for this setting).
39 | mkCabalSettingOptions = { name, type, description, impl }: {
40 | "${name}" = mkNullableOption {
41 | inherit type description;
42 | };
43 | impl."${name}" = mkImplOption name impl;
44 | };
45 | in
46 | {
47 | inherit mkCabalSettingOptions;
48 | }
49 |
--------------------------------------------------------------------------------
/test/otherOverlays/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | # Since there is no flake.lock file (to avoid incongruent haskell-flake
3 | # pinning), we must specify revisions for *all* inputs to ensure
4 | # reproducibility.
5 | inputs = {
6 | nixpkgs.url = "github:nixos/nixpkgs/870493f9a8cb0b074ae5b411b2f232015db19a65";
7 | flake-parts.url = "github:hercules-ci/flake-parts/758cf7296bee11f1706a574c77d072b8a7baa881";
8 | haskell-flake = { };
9 |
10 | haskell-multi-nix.url = "github:srid/haskell-multi-nix/d6ac6ccab559f886d1fc7da8cab44b99cb0c2c3d";
11 | haskell-multi-nix.inputs.haskell-flake.follows = "haskell-flake";
12 | haskell-multi-nix.inputs.nixpkgs.follows = "nixpkgs";
13 | haskell-multi-nix.inputs.flake-parts.follows = "flake-parts";
14 | };
15 | outputs = inputs@{ self, nixpkgs, flake-parts, ... }:
16 | flake-parts.lib.mkFlake { inherit inputs; } {
17 | systems = nixpkgs.lib.systems.flakeExposed;
18 | imports = [
19 | inputs.haskell-flake.flakeModule
20 | ];
21 | perSystem = { self', pkgs, ... }: {
22 | haskellProjects.default = {
23 | otherOverlays = [
24 | (self: super: {
25 | foo = super.callCabal2nix "foo" "${inputs.haskell-multi-nix}/foo" { };
26 | })
27 | ];
28 | };
29 | packages.default = self'.packages.haskell-flake-test;
30 | };
31 | };
32 | }
33 |
--------------------------------------------------------------------------------
/doc/under-the-hood.md:
--------------------------------------------------------------------------------
1 |
2 | # Under the hood
3 |
4 | >[!tip] Under the hood
5 | > See [this tutorial](https://nixos.asia/en/nixify-haskell-nixpkgs) to understand what it takes to package a Haskell application without haskell-flake.
6 |
7 | When nixifying a Haskell project without flake-parts (thus without haskell-flake) you would generally use the [raw Haskell infrastructure from nixpkgs](https://nixos.asia/en/nixify-haskell-nixpkgs). haskell-flake uses these functions, while exposing a simpler [modular](https://flake.parts) API on top: your `flake.nix` becomes more [declarative] and less [imperative].
8 |
9 | [declarative]: https://github.com/srid/haskell-template/blob/033913a6fe418ea0c25ec2c2604ab4030563ba2e/flake.nix#L22-L59
10 | [imperative]: https://github.com/srid/haskell-template/blob/3fc6858830ecee3d2fe1dfe9a8bfa2047cf561ac/flake.nix#L20-L79
11 |
12 | In addition, compared to using plain nixpkgs, haskell-flake supports:
13 |
14 | - Auto-detection of [[local|local packages]] based on `cabal.project` file (via [haskell-parsers](https://github.com/srid/haskell-flake/tree/master/nix/haskell-parsers))
15 | - Parse executables from `.cabal` file, to create Flake apps.
16 | - Modular interface to `pkgs.haskell.lib.compose.*` (via `packages` and `settings` submodules)
17 | - Composition of dependency overrides, and other project settings, via [[modules]]
18 |
19 | See #[[start]] for getting started with haskell-flake.
20 |
--------------------------------------------------------------------------------
/doc/hls.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: -8
3 | ---
4 |
5 | # IDE configuration (HLS)
6 |
7 | By default, #[[devshell]] of haskell-flake projects includes [haskell-language-server](https://github.com/haskell/haskell-language-server) and [a few other tools by default](https://github.com/srid/haskell-flake/blob/988a78590c158c5fa0b4893de793c9c783b9d7e9/nix/modules/project/defaults.nix#L23-L29).
8 | {#disable}
9 | ## Disabling `haskell-language-server`
10 |
11 | > [!tip] Default options
12 | > Alternatively, disabling the [[defaults|default options]] (i.e., `haskellProjects..defaults.enable = false;`) automatically removes HLS.
13 |
14 | HLS is included as part of the default value of `devShell.tools` options. You can override this default by overriding it, for e.g.:
15 |
16 | ```nix
17 | {
18 | haskellProjects. = {
19 | # NOTE: This is 'defaults.devShell.tools', not 'devShell.tools'
20 | defaults.devShell.tools = hp: with hp; {
21 | inherit
22 | cabal-install
23 | ghcid;
24 | };
25 | };
26 | }
27 | ```
28 |
29 | Alternatively, you can set it to `null` at a project-level:
30 |
31 | ```nix
32 | {
33 | haskellProjects. = {
34 | # NOTE: This is 'devShell.tools', not 'defaults.devShell.tools'
35 | devShell.tools = {
36 | haskell-language-server = null;
37 | };
38 | };
39 | }
40 | ```
41 |
42 | {#disable-plugins}
43 | ## Disabling HLS plugins
44 |
45 | >[!warning] TODO
46 | > See here for current status:
47 |
--------------------------------------------------------------------------------
/nix/types/app-type.nix:
--------------------------------------------------------------------------------
1 | # Taken from https://github.com/hercules-ci/flake-parts/blob/dcc36e45d054d7bb554c9cdab69093debd91a0b5/modules/apps.nix#L11-L41
2 |
3 | { pkgs, lib, ... }:
4 | let
5 | derivationType = lib.types.package // {
6 | check = lib.isDerivation;
7 | };
8 |
9 | getExe = x:
10 | "${lib.getBin x}/bin/${x.meta.mainProgram or (throw ''Package ${x.name or ""} does not have meta.mainProgram set, so I don't know how to find the main executable. You can set meta.mainProgram, or pass the full path to executable, e.g. program = "''${pkg}/bin/foo"'')}";
11 | programType = lib.types.coercedTo derivationType getExe lib.types.str;
12 | appType = lib.types.submodule {
13 | options = {
14 | type = lib.mkOption {
15 | type = lib.types.enum [ "app" ];
16 | default = "app";
17 | description = ''
18 | A type tag for `apps` consumers.
19 | '';
20 | };
21 | program = lib.mkOption {
22 | type = programType;
23 | description = ''
24 | A path to an executable or a derivation with `meta.mainProgram`.
25 | '';
26 | };
27 | meta = lib.mkOption {
28 | type = lib.types.lazyAttrsOf lib.types.raw;
29 | default = { };
30 | # TODO refer to Nix manual 2.25
31 | description = ''
32 | Metadata information about the app.
33 | Standardized in Nix at .
34 |
35 | Note: `nix flake check` is only aware of the `description` attribute in `meta`.
36 | '';
37 | };
38 | };
39 | };
40 | in
41 | appType
42 |
--------------------------------------------------------------------------------
/nix/haskell-parsers/test/flake.lock:
--------------------------------------------------------------------------------
1 | {
2 | "nodes": {
3 | "flake-parts": {
4 | "inputs": {
5 | "nixpkgs-lib": "nixpkgs-lib"
6 | },
7 | "locked": {
8 | "lastModified": 1706830856,
9 | "narHash": "sha256-a0NYyp+h9hlb7ddVz4LUn1vT/PLwqfrWYcHMvFB1xYg=",
10 | "owner": "hercules-ci",
11 | "repo": "flake-parts",
12 | "rev": "b253292d9c0a5ead9bc98c4e9a26c6312e27d69f",
13 | "type": "github"
14 | },
15 | "original": {
16 | "owner": "hercules-ci",
17 | "repo": "flake-parts",
18 | "type": "github"
19 | }
20 | },
21 | "nixpkgs": {
22 | "locked": {
23 | "lastModified": 1708564076,
24 | "narHash": "sha256-KKkqoxlgx9n3nwST7O2kM8tliDOijiSSNaWuSkiozdQ=",
25 | "owner": "nixos",
26 | "repo": "nixpkgs",
27 | "rev": "98b00b6947a9214381112bdb6f89c25498db4959",
28 | "type": "github"
29 | },
30 | "original": {
31 | "owner": "nixos",
32 | "ref": "nixpkgs-unstable",
33 | "repo": "nixpkgs",
34 | "type": "github"
35 | }
36 | },
37 | "nixpkgs-lib": {
38 | "locked": {
39 | "dir": "lib",
40 | "lastModified": 1706550542,
41 | "narHash": "sha256-UcsnCG6wx++23yeER4Hg18CXWbgNpqNXcHIo5/1Y+hc=",
42 | "owner": "NixOS",
43 | "repo": "nixpkgs",
44 | "rev": "97b17f32362e475016f942bbdfda4a4a72a8a652",
45 | "type": "github"
46 | },
47 | "original": {
48 | "dir": "lib",
49 | "owner": "NixOS",
50 | "ref": "nixos-unstable",
51 | "repo": "nixpkgs",
52 | "type": "github"
53 | }
54 | },
55 | "root": {
56 | "inputs": {
57 | "flake-parts": "flake-parts",
58 | "nixpkgs": "nixpkgs"
59 | }
60 | }
61 | },
62 | "root": "root",
63 | "version": 7
64 | }
65 |
--------------------------------------------------------------------------------
/nix/haskell-parsers/parser.nix:
--------------------------------------------------------------------------------
1 | # Sufficiently basic parsers for `cabal.project` and `package.yaml` formats
2 | #
3 | # "sufficiently" because we care only about 'packages' from `cabal.project` and
4 | # 'name' from `package.yaml`.
5 | { lib, ... }:
6 |
7 | let
8 | nix-parsec = builtins.fetchGit {
9 | url = "https://github.com/kanwren/nix-parsec.git";
10 | ref = "master";
11 | rev = "1bf25dd9c5de1257a1c67de3c81c96d05e8beb5e";
12 | shallow = true;
13 | };
14 | inherit (import nix-parsec) parsec;
15 | in
16 | {
17 | # Extract the "packages" list from a cabal.project file.
18 | #
19 | # Globs are not supported yet. Values must be refer to a directory, not file.
20 | parseCabalProjectPackages = cabalProjectFile:
21 | let
22 | spaces1 = parsec.skipWhile1 (c: c == " " || c == "\t");
23 | newline = parsec.string "\n";
24 | path = parsec.fmap lib.concatStrings (parsec.many1 (parsec.anyCharBut "\n"));
25 | key = parsec.string "packages:\n";
26 | val =
27 | parsec.many1
28 | (parsec.choice
29 | [
30 | (parsec.between spaces1 newline path)
31 | (parsec.between spaces1 parsec.eof path)
32 | ]);
33 | parser = parsec.skipThen
34 | key
35 | val;
36 | in
37 | parsec.runParser parser cabalProjectFile;
38 |
39 | # Extract all the executables from a .cabal file
40 | parseCabalExecutableNames = cabalFile:
41 | with parsec;
42 | let
43 | # Skip empty lines and lines that don't start with 'executable'
44 | skipLines =
45 | skipTill
46 | (sequence [ (skipWhile (x: x != "\n")) anyChar ])
47 | (parsec.string "executable ");
48 | val = parsec.fmap lib.concatStrings (parsec.many1 (parsec.anyCharBut "\n"));
49 | parser = parsec.many (parsec.skipThen
50 | skipLines
51 | val);
52 | in
53 | parsec.runParser parser cabalFile;
54 | }
55 |
--------------------------------------------------------------------------------
/nix/haskell-parsers/parser_tests.nix:
--------------------------------------------------------------------------------
1 | # Run this using 'nixci' at top-level
2 | { pkgs ? import { }, lib ? pkgs.lib, ... }:
3 |
4 | let
5 | parser = pkgs.callPackage ./parser.nix { };
6 | cabalProjectTests =
7 | let
8 | eval = s:
9 | let res = parser.parseCabalProjectPackages s; in
10 | if res.type == "success" then res.value else res;
11 | in
12 | {
13 | testSimple = {
14 | expr = eval ''
15 | packages:
16 | foo
17 | bar
18 | '';
19 | expected = [ "foo" "bar" ];
20 | };
21 |
22 | # Handles cases where cabal.project does not end with newline
23 | testEOF = {
24 | expr = eval ''
25 | packages:
26 | foo
27 | bar'';
28 | expected = [ "foo" "bar" ];
29 | };
30 | };
31 | cabalExecutableTests =
32 | let
33 | eval = s:
34 | let res = parser.parseCabalExecutableNames s; in
35 | if res.type == "success" then res.value else res;
36 | in
37 | {
38 | testSimple = {
39 | expr = eval ''
40 | cabal-version: 3.0
41 | name: test-package
42 | version: 0.1
43 |
44 | executable foo-exec
45 | main-is: foo.hs
46 |
47 | library test
48 | exposed-modules: Test.Types
49 |
50 | executable bar-exec
51 | main-is: bar.hs
52 | '';
53 | expected = [ "foo-exec" "bar-exec" ];
54 | };
55 | };
56 | # Like lib.runTests, but actually fails if any test fails.
57 | runTestsFailing = tests:
58 | let
59 | res = lib.runTests tests;
60 | in
61 | if res == builtins.trace "All tests passed" [ ]
62 | then res
63 | else builtins.throw "Some tests failed: ${builtins.toJSON res}" res;
64 | in
65 | {
66 | "cabal.project" = runTestsFailing cabalProjectTests;
67 | "foo-bar.cabal" = runTestsFailing cabalExecutableTests;
68 | }
69 |
--------------------------------------------------------------------------------
/doc/devshell.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: -8
3 | ---
4 |
5 | # DevShell
6 |
7 | haskell-flake uses the [`shellFor`][shellFor] function to provide a Haskell development shell. `shellFor` in turn uses the standard [`mkShell`][mkShell] function to create a Nix shell environment. The `mkShellArgs` option can be used to pass custom arguments to `mkShell`.
8 |
9 | ```nix
10 | {
11 | haskellProjects.default = {
12 | devShell = {
13 | mkShellArgs = {
14 | shellHook = ''
15 | export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${pkgs.flint}/lib
16 | ''
17 | };
18 | }
19 | }
20 | }
21 | ```
22 |
23 | ## Composing devShells
24 |
25 | While `mkShellArgs` is a convenient way to extend the Haskell devShell, sometimes you want to compose multiple devShell environments in a way you want.
26 |
27 | The devShell of a haskell-flake project is exposed in the `config.haskellProjects..outputs.devShell` attribute. You can pass this devShell to the `inputsFrom` argument of a [`mkShell`][mkShell] function in order to include the Haskell devShell in another devShell. The same technique can be used to compose devShells created by other flake-parts modules.
28 |
29 | For example, [in haskell-template](https://github.com/srid/haskell-template/blob/fc263b19e4ef02710ffc61fc656aec6c1a873974/flake.nix#L96-L102), we create a top-level devShell that merges the devShell of the haskell-flake project, the devShell of [mission-control](https://community.flake.parts/mission-control) and the devShell of [flake-root](https://github.com/srid/flake-root) as follows::
30 |
31 | ```nix
32 | {
33 | devShell = pkgs.mkShell {
34 | inputsFrom = [
35 | config.haskellProjects.default.outputs.devShell
36 | config.flake-root.devShell
37 | config.mission-control.devShell
38 | ];
39 | };
40 | }
41 | ```
42 |
43 | This sort of composition is either impossible or very complex to do with the `mkShellArgs` approach.
44 |
45 |
46 | [shellFor]: https://nixos.org/manual/nixpkgs/unstable/#haskell-shellFor
47 | [mkShell]: https://nixos.org/manual/nixpkgs/stable/#sec-pkgs-mkShell
48 |
49 |
--------------------------------------------------------------------------------
/doc/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | nixConfig = {
3 | extra-substituters = "https://srid.cachix.org";
4 | extra-trusted-public-keys = "srid.cachix.org-1:3clnql5gjbJNEvhA/WQp7nrZlBptwpXnUk6JAv8aB2M=";
5 | };
6 |
7 | inputs = {
8 | cfp.url = "github:flake-parts/community.flake.parts";
9 | nixpkgs.follows = "cfp/nixpkgs";
10 | flake-parts.follows = "cfp/flake-parts";
11 |
12 | # flake-parts-website.url = "github:hercules-ci/flake.parts-website";
13 | # flake-parts-website.inputs.haskell-flake.follows = "haskell-flake";
14 | # flake-parts-website.inputs.flake-parts.follows = "flake-parts";
15 |
16 | haskell-flake.url = "github:srid/haskell-flake";
17 | };
18 |
19 | outputs = inputs@{ self, flake-parts, nixpkgs, ... }:
20 | flake-parts.lib.mkFlake { inherit inputs; } {
21 | systems = nixpkgs.lib.systems.flakeExposed;
22 | imports = [
23 | inputs.cfp.flakeModules.default
24 | ];
25 | perSystem = { self', inputs', pkgs, system, ... }: {
26 | flake-parts-docs = {
27 | enable = true;
28 | modules."haskell-flake" = {
29 | path = self;
30 | pathString = ".";
31 | };
32 | };
33 | formatter = pkgs.nixpkgs-fmt;
34 |
35 | # Disable linkcheck due to upstream issue
36 | # https://github.com/hercules-ci/flake.parts-website/issues/1367
37 | /*
38 | checks.linkcheck = inputs'.flake-parts-website.checks.linkcheck;
39 | packages.flake-parts = inputs'.flake-parts-website.packages.default;
40 | apps.flake-parts.program = pkgs.writeShellApplication {
41 | name = "docs-flake-parts";
42 | meta.description = "Open flake.parts docs, previewing local haskell-flake version";
43 | text = ''
44 | DOCSHTML="${self'.packages.flake-parts}/options/haskell-flake.html"
45 |
46 | if [ "$(uname)" == "Darwin" ]; then
47 | open $DOCSHTML
48 | else
49 | if type xdg-open &>/dev/null; then
50 | xdg-open $DOCSHTML
51 | fi
52 | fi
53 | '';
54 | };
55 | */
56 | };
57 | };
58 | }
59 |
--------------------------------------------------------------------------------
/nix/modules/project/packages/default.nix:
--------------------------------------------------------------------------------
1 | # Definition of the `haskellProjects.${name}` submodule's `config`
2 | project@{ name, lib, pkgs, ... }:
3 | let
4 | inherit (lib)
5 | types;
6 |
7 | packageSubmodule = import ./package.nix { inherit project lib pkgs; };
8 |
9 | # Merge the list of attrset of modules.
10 | mergeModuleAttrs =
11 | lib.zipAttrsWith (k: vs: { imports = vs; });
12 |
13 | tracePackages = k: x:
14 | project.config.log.traceDebug "${k} ${builtins.toJSON x}" x;
15 | in
16 | {
17 | options = {
18 | packages = lib.mkOption {
19 | type = types.lazyAttrsOf types.deferredModule;
20 | default = { };
21 | apply = packages:
22 | let
23 | packages' =
24 | # Merge user-provided 'packages' with 'defaults.packages'.
25 | #
26 | # Note that the user can override the latter too if they wish.
27 | mergeModuleAttrs
28 | [ project.config.defaults.packages packages ];
29 | in
30 | tracePackages "${name}.packages:apply" (
31 | lib.mapAttrs
32 | (name: v:
33 | (lib.evalModules {
34 | modules = [ packageSubmodule v ];
35 | specialArgs = { inherit name pkgs; };
36 | }).config
37 | )
38 | packages');
39 |
40 | description = ''
41 | Additional packages to add to `basePackages`.
42 |
43 | Local packages are added automatically (see `config.defaults.packages`):
44 |
45 | You can also override the source for existing packages here.
46 | '';
47 | };
48 |
49 | packagesOverlay = lib.mkOption {
50 | type = import ../../../types/haskell-overlay-type.nix { inherit lib; };
51 | description = ''
52 | The Haskell overlay computed from `packages` modules.
53 | '';
54 | internal = true;
55 | default = self: _super:
56 | let
57 | inherit (project.config) log;
58 | build-haskell-package = import ../../../build-haskell-package.nix {
59 | inherit pkgs lib self log;
60 | };
61 | in
62 | lib.mapAttrs build-haskell-package project.config.packages;
63 | };
64 | };
65 | }
66 |
--------------------------------------------------------------------------------
/example/flake.lock:
--------------------------------------------------------------------------------
1 | {
2 | "nodes": {
3 | "flake-parts": {
4 | "inputs": {
5 | "nixpkgs-lib": "nixpkgs-lib"
6 | },
7 | "locked": {
8 | "lastModified": 1765835352,
9 | "narHash": "sha256-XswHlK/Qtjasvhd1nOa1e8MgZ8GS//jBoTqWtrS1Giw=",
10 | "owner": "hercules-ci",
11 | "repo": "flake-parts",
12 | "rev": "a34fae9c08a15ad73f295041fec82323541400a9",
13 | "type": "github"
14 | },
15 | "original": {
16 | "owner": "hercules-ci",
17 | "repo": "flake-parts",
18 | "type": "github"
19 | }
20 | },
21 | "haskell-flake": {
22 | "locked": {
23 | "lastModified": 1765673601,
24 | "narHash": "sha256-dzkd+XBE34fYGh17OuJWJ8+tjNV8t2tTsq8zRM05OtQ=",
25 | "owner": "srid",
26 | "repo": "haskell-flake",
27 | "rev": "a4782ecf6aa442c5480ae925c4e5a48f5ea8fed7",
28 | "type": "github"
29 | },
30 | "original": {
31 | "owner": "srid",
32 | "repo": "haskell-flake",
33 | "type": "github"
34 | }
35 | },
36 | "nixpkgs": {
37 | "locked": {
38 | "lastModified": 1766125104,
39 | "narHash": "sha256-l/YGrEpLromL4viUo5GmFH3K5M1j0Mb9O+LiaeCPWEM=",
40 | "owner": "nixos",
41 | "repo": "nixpkgs",
42 | "rev": "7d853e518814cca2a657b72eeba67ae20ebf7059",
43 | "type": "github"
44 | },
45 | "original": {
46 | "owner": "nixos",
47 | "ref": "nixpkgs-unstable",
48 | "repo": "nixpkgs",
49 | "type": "github"
50 | }
51 | },
52 | "nixpkgs-lib": {
53 | "locked": {
54 | "lastModified": 1765674936,
55 | "narHash": "sha256-k00uTP4JNfmejrCLJOwdObYC9jHRrr/5M/a/8L2EIdo=",
56 | "owner": "nix-community",
57 | "repo": "nixpkgs.lib",
58 | "rev": "2075416fcb47225d9b68ac469a5c4801a9c4dd85",
59 | "type": "github"
60 | },
61 | "original": {
62 | "owner": "nix-community",
63 | "repo": "nixpkgs.lib",
64 | "type": "github"
65 | }
66 | },
67 | "root": {
68 | "inputs": {
69 | "flake-parts": "flake-parts",
70 | "haskell-flake": "haskell-flake",
71 | "nixpkgs": "nixpkgs"
72 | }
73 | }
74 | },
75 | "root": "root",
76 | "version": 7
77 | }
78 |
--------------------------------------------------------------------------------
/example/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | inputs = {
3 | nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
4 | flake-parts.url = "github:hercules-ci/flake-parts";
5 | haskell-flake.url = "github:srid/haskell-flake";
6 | };
7 | outputs = inputs@{ self, nixpkgs, flake-parts, ... }:
8 | flake-parts.lib.mkFlake { inherit inputs; } {
9 | systems = nixpkgs.lib.systems.flakeExposed;
10 | imports = [ inputs.haskell-flake.flakeModule ];
11 |
12 | perSystem = { self', pkgs, ... }: {
13 |
14 | # Typically, you just want a single project named "default". But
15 | # multiple projects are also possible, each using different GHC version.
16 | haskellProjects.default = {
17 | # The base package set representing a specific GHC version.
18 | # By default, this is pkgs.haskellPackages.
19 | # You may also create your own. See https://community.flake.parts/haskell-flake/package-set
20 | # basePackages = pkgs.haskellPackages;
21 |
22 | # Extra package information. See https://community.flake.parts/haskell-flake/dependency
23 | #
24 | # Note that local packages are automatically included in `packages`
25 | # (defined by `defaults.packages` option).
26 | #
27 | packages = {
28 | # aeson.source = "1.5.0.0"; # Override aeson to a custom version from Hackage
29 | # shower.source = inputs.shower; # Override shower to a custom source path
30 | };
31 | settings = {
32 | # aeson = {
33 | # check = false;
34 | # };
35 | # relude = {
36 | # haddock = false;
37 | # broken = false;
38 | # };
39 | };
40 |
41 | devShell = {
42 | # Enabled by default
43 | # enable = true;
44 |
45 | # Programs you want to make available in the shell.
46 | # Default programs can be disabled by setting to 'null'
47 | # tools = hp: { fourmolu = hp.fourmolu; ghcid = null; };
48 |
49 | # Check that haskell-language-server works
50 | # hlsCheck.enable = true; # Requires sandbox to be disabled
51 | };
52 | };
53 |
54 | # haskell-flake doesn't set the default package, but you can do it here.
55 | packages.default = self'.packages.example;
56 | };
57 | };
58 | }
59 |
--------------------------------------------------------------------------------
/nix/modules/projects.nix:
--------------------------------------------------------------------------------
1 | { self, lib, flake-parts-lib, ... }:
2 |
3 | let
4 | inherit (flake-parts-lib)
5 | mkPerSystemOption;
6 | inherit (lib)
7 | mkOption
8 | types;
9 | in
10 | {
11 | options.perSystem = mkPerSystemOption ({ config, self', pkgs, ... }: {
12 | options = {
13 | haskellProjects = mkOption {
14 | description = "Haskell projects";
15 | type = types.lazyAttrsOf (types.submoduleWith {
16 | specialArgs = { inherit pkgs self; };
17 | modules = [
18 | ./project
19 | ];
20 | });
21 | };
22 | };
23 |
24 | config =
25 | let
26 | # Like mapAttrs, but merges the values (also attrsets) of the resulting attrset.
27 | mergeMapAttrs = f: attrs: lib.mkMerge (lib.mapAttrsToList f attrs);
28 | mapKeys = f: lib.mapAttrs' (n: v: { name = f n; value = v; });
29 |
30 | contains = k: lib.any (x: x == k);
31 |
32 | # Prefix value with the project name (unless
33 | # project is named `default`)
34 | prefixUnlessDefault = projectName: value:
35 | if projectName == "default"
36 | then value
37 | else "${projectName}-${value}";
38 | in
39 | {
40 | packages =
41 | mergeMapAttrs
42 | (name: project:
43 | let
44 | packages = lib.mapAttrs (_: info: info.package) project.outputs.packages;
45 | in
46 | lib.optionalAttrs (contains "packages" project.autoWire)
47 | (mapKeys (prefixUnlessDefault name) packages))
48 | config.haskellProjects;
49 | devShells =
50 | mergeMapAttrs
51 | (name: project:
52 | lib.optionalAttrs (contains "devShells" project.autoWire && project.devShell.enable) {
53 | "${name}" = project.outputs.devShell;
54 | })
55 | config.haskellProjects;
56 | checks =
57 | mergeMapAttrs
58 | (name: project:
59 | lib.optionalAttrs (contains "checks" project.autoWire)
60 | project.outputs.checks
61 | )
62 | config.haskellProjects;
63 | apps =
64 | mergeMapAttrs
65 | (name: project:
66 | lib.optionalAttrs (contains "apps" project.autoWire)
67 | (mapKeys (prefixUnlessDefault name) project.outputs.apps)
68 | )
69 | config.haskellProjects;
70 | };
71 | });
72 | }
73 |
74 |
75 |
--------------------------------------------------------------------------------
/doc/local.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: -11
3 | ---
4 |
5 | # Local packages
6 |
7 | Local Haskell packages are defined in the `packages.*.source` option under `haskellProjects.` module. They are automatically detected if you have a single top-level Cabal package, or multiple Cabal packages defined in a `cabal.project` file, via [[defaults|default settings]].
8 |
9 | {#single}
10 | ## Single package
11 |
12 | If your repository has a single top-level `.cabal` file, haskell-flake will use it by [[defaults|default]]. There is no need to specify `packages.*.source` yourself.
13 |
14 | {#multi}
15 | ## Multiple packages
16 |
17 | If you have multiple Haskell packages in sub-directories, you can refer to them in a `cabal.project` file to have haskell-flake automatically use them as local packages by [[defaults|default]]:
18 |
19 | See https://github.com/srid/haskell-multi-nix for example:
20 |
21 | ```sh
22 | $ cat cabal.project
23 | packages:
24 | ./foo
25 | ./bar
26 | ```
27 |
28 | The `cabal.project` file must begin with a `packages:` key, followed by a list of directories containing the cabal files.
29 |
30 | ```sh
31 | $ ls */*.cabal
32 | bar/bar.cabal foo/foo.cabal
33 | ```
34 |
35 | {#source-filtering}
36 | ## Source filtering
37 |
38 | When a local package is in a sub-directory, haskell-flake will create a new store path to avoid changes to parent files (using [`cleanSourceWith`]) triggering a rebuild.
39 |
40 | When a local package is the only top-level one, however, any file in the repository will by default trigger a rebuild. This is because `haskellProjects..projectRoot` is set to `self` by default.
41 |
42 | {#rebuild}
43 | ### Avoiding rebuild of top-level package
44 |
45 | To avoid rebuilding the top-level package whenever irrelevant files change, you can do one of the following:
46 |
47 | - Put the top-level package in a sub-directory.
48 | - Or, set `projectRoot` to a subset of your flake root using [`fileset.toSource`](https://nixos.org/manual/nixpkgs/stable/#function-library-example-lib.fileset.toSource). For [example](https://github.com/srid/haskell-template/blob/033913a6fe418ea0c25ec2c2604ab4030563ba2e/flake.nix#L28-L34):
49 | ```nix
50 | {
51 | haskellProjects.default = {
52 | projectRoot = builtins.toString (lib.fileset.toSource {
53 | root = ./.;
54 | fileset = lib.fileset.unions [
55 | ./src
56 | ./haskell-template.cabal
57 | ];
58 | });
59 | }
60 | }
61 | ```
62 |
63 | [`cleanSourceWith`]: https://github.com/srid/haskell-flake/blob/67db46409b4c2e92abf27ddde7c75ae310d4068c/nix/build-haskell-package.nix#L15-L24
64 |
--------------------------------------------------------------------------------
/nix/build-haskell-package.nix:
--------------------------------------------------------------------------------
1 | # Like callCabal2nix, but does more:
2 | # - Source filtering (to prevent parent content changes causing rebuilds)
3 | # - Always build from cabal's sdist for release-worthiness
4 | # - Logs what it's doing (based on 'log' option)
5 | #
6 | { pkgs
7 | , lib
8 | # 'self' refers to the Haskell package set context.
9 | , self
10 | , log
11 | , ...
12 | }:
13 |
14 | let
15 | mkNewStorePath' = name: src:
16 | # Since 'src' may be a subdirectory of a store path
17 | # (in string form, which means that it isn't automatically
18 | # copied), the purpose of cleanSourceWith here is to create a
19 | # new (smaller) store path that is a copy of 'src' but
20 | # does not contain the unrelated parent source contents.
21 | lib.cleanSourceWith {
22 | name = "${name}";
23 | inherit src;
24 | };
25 |
26 | # Avoid rebuilding because of changes in parent directories
27 | mkNewStorePath = name: src:
28 | let newSrc = mkNewStorePath' name src;
29 | in log.traceDebug "${name}.mkNewStorePath ${newSrc}" newSrc;
30 |
31 | callCabal2nix = name: src: opts:
32 | let pkg = self.callCabal2nixWithOptions name src { extraCabal2nixOptions = opts; } { };
33 | in log.traceDebug "${name}.callCabal2nixWithOptions src=${src} deriver=${pkg.cabal2nixDeriver.outPath} opts=${opts}" pkg;
34 |
35 | # Use cached cabal2nix generated nix expression if present, otherwise use IFD (callCabal2nix)
36 | callCabal2NixUnlessCached = name: src: cabal2nixFile: opts:
37 | let path = "${src}/${cabal2nixFile}";
38 | in
39 | if builtins.pathExists path
40 | then
41 | callPackage name path
42 | else
43 | callCabal2nix name src opts;
44 |
45 | callPackage = name: nixFilePath:
46 | let pkg = self.callPackage nixFilePath { };
47 | in log.traceDebug "${name}.callPackage[cabal2nix] ${nixFilePath}" pkg;
48 |
49 | callHackage = name: version:
50 | let pkg = self.callHackage name version { };
51 | in log.traceDebug "${name}.callHackage ver=${version}" pkg;
52 | in
53 |
54 | name: cfg:
55 | # If 'source' is a path, we treat it as such. Otherwise, we assume it's a version (from hackage).
56 | if lib.types.path.check cfg.source
57 | then
58 | let
59 | cfgCabalFlags =
60 | lib.mapAttrsToList
61 | (flag: enabled: "-f${if enabled then "" else "-"}${flag}")
62 | cfg.cabalFlags;
63 | extraCabal2nixOptions =
64 | lib.strings.concatStringsSep " " (cfgCabalFlags ++ cfg.extraCabal2nixOptions);
65 | in
66 | callCabal2NixUnlessCached name (mkNewStorePath name cfg.source) cfg.cabal2NixFile extraCabal2nixOptions
67 | else
68 | callHackage name cfg.source
69 |
--------------------------------------------------------------------------------
/doc/package-set.md:
--------------------------------------------------------------------------------
1 | # Creating package sets
2 |
3 | While haskell-flake is generally used to develop and build individual Haskell projects, you can also use it to create a custom Haskell package set that you can use in other projects. This is useful if you want to create a common package set to be shared across multiple projects.
4 |
5 | A "project" in haskell-flake primarily serves the purpose of developing Haskell projects. Additionally, a project also exposes the final *package set* via the readonly option `outputs.finalPackages`. This package set includes the base packages (`basePackages`), the [[local|local packages]] as well as any [[dependency|dependency overrides]] you set. Since we are are only interested in creating a new package set, we can use empty local packages and disable the dev shell:
6 |
7 | ```nix
8 | {
9 | haskellProjects.ghc810 = {
10 | defaults.packages = {}; # Disable scanning for local package
11 | devShell.enable = false; # Disable devShells
12 | autoWire = [ ]; # Don't wire any flake outputs
13 |
14 | # Start from nixpkgs's ghc8107 package set
15 | basePackages = pkgs.haskell.packages.ghc8107;
16 | };
17 | }
18 | ```
19 |
20 | You can access this package set as `config.haskellProjects.ghc810.outputs.finalPackages`. But this is not terribly interesting, because it is the exact same as the package set `pkgs.haskell.packages.ghc8107` from nixpkgs. So let's add and override some packages in this set:
21 |
22 | ```nix
23 | {
24 | haskellProjects.ghc810 = {
25 | defaults.packages = {}; # No local packages
26 | devShell.enable = false;
27 |
28 | basePackages = pkgs.haskell.packages.ghc8107;
29 |
30 | packages = {
31 | # New packages from flake inputs
32 | mylib.source = inputs.mylib;
33 | # Dependencies from Hackage
34 | aeson.source = "1.5.6.0";
35 | dhall.source = "1.35.0";
36 | };
37 | settings = {
38 | aeson.jailbreak = true;
39 | };
40 | };
41 | }
42 | ```
43 |
44 | This will create a package set that overrides the `aeson` and `dhall` packages using the specified versions from Hackage, but with the `aeson` package having the `jailbreak` flag set (which relaxes its Cabal constraints). It also adds the `mylib` package which exists neither in nixpkgs nor in Hackage, but comes from somewhere arbitrary and specified as flake input.
45 |
46 | In your *actual* haskell project, you can use this package set (`config.haskellProjects.ghc810.outputs.finalPackages`) as its base package set:
47 |
48 | ```nix
49 | {
50 | haskellProjects.myproject = {
51 | packages.mypackage.source = ./.;
52 |
53 | basePackages = config.haskellProjects.ghc810.outputs.finalPackages;
54 | };
55 | }
56 | ```
57 |
58 | Finally, you can externalize this `ghc810` package set as either a flake-parts module or as a [[modules|haskell-flake module]], and thereon import it from multiple repositories.
59 |
60 | ## Examples
61 |
62 | - https://github.com/nammayatri/common/pull/11/files
63 |
64 | ## See also
65 |
66 | - [[gotchas]]
67 |
68 |
--------------------------------------------------------------------------------
/nix/haskell-parsers/default.nix:
--------------------------------------------------------------------------------
1 | { pkgs
2 | , lib
3 | , throwError ? builtins.throw
4 | , throwErrorOnCabalProjectParseError ? builtins.throw
5 | , ...
6 | }:
7 |
8 | let
9 | parser = import ./parser.nix { inherit pkgs lib; };
10 | traversal = rec {
11 | findSingleCabalFile = path:
12 | let
13 | cabalFiles = lib.filter (lib.strings.hasSuffix ".cabal") (builtins.attrNames (builtins.readDir path));
14 | num = builtins.length cabalFiles;
15 | in
16 | if num == 0
17 | then null
18 | else if num == 1
19 | then builtins.head cabalFiles
20 | else throwError "Expected a single .cabal file, but found multiple: ${builtins.toJSON cabalFiles}";
21 | getCabalName =
22 | lib.strings.removeSuffix ".cabal";
23 | findHaskellPackageNameOfDirectory = path:
24 | let
25 | cabalFile = findSingleCabalFile path;
26 | in
27 | if cabalFile != null
28 | then
29 | getCabalName cabalFile
30 | else
31 | throwError "No .cabal file found under ${path}";
32 | };
33 | in
34 | {
35 | findPackagesInCabalProject = projectRoot:
36 | let
37 | cabalProjectFile = projectRoot + "/cabal.project";
38 | packageDirs =
39 | if builtins.pathExists cabalProjectFile
40 | then
41 | let
42 | res = parser.parseCabalProjectPackages (builtins.readFile cabalProjectFile);
43 | isSelfPath = path:
44 | path == "." || path == "./" || path == "./.";
45 | in
46 | if res.type == "success"
47 | then
48 | map
49 | (path:
50 | if isSelfPath path
51 | then projectRoot
52 | else if lib.strings.hasInfix "*" path
53 | then throwErrorOnCabalProjectParseError "Found a path with glob (${path}) in ${cabalProjectFile}, which is not supported"
54 | else if lib.strings.hasSuffix ".cabal" path
55 | then throwErrorOnCabalProjectParseError "Expected a directory but ${path} (in ${cabalProjectFile}) is a .cabal filepath"
56 | else "${projectRoot}/${path}"
57 | )
58 | res.value
59 | else throwErrorOnCabalProjectParseError "Failed to parse ${cabalProjectFile}: ${builtins.toJSON res}"
60 | else
61 | lib.optional (traversal.findSingleCabalFile projectRoot != null)
62 | projectRoot;
63 | in
64 | lib.listToAttrs
65 | (map
66 | (path:
67 | lib.nameValuePair (traversal.findHaskellPackageNameOfDirectory path) path)
68 | packageDirs);
69 |
70 | getCabalExecutables = path:
71 | let
72 | cabalFile = traversal.findSingleCabalFile path;
73 | in
74 | if cabalFile != null then
75 | let res = parser.parseCabalExecutableNames (builtins.readFile (lib.concatStrings [ path "/" cabalFile ]));
76 | in
77 | if res.type == "success"
78 | then res.value
79 | else throwError "Failed to parse ${cabalFile}: ${builtins.toJSON res}"
80 | else
81 | throwError "No .cabal file found under ${path}";
82 | }
83 |
--------------------------------------------------------------------------------
/nix/modules/project-tests.nix:
--------------------------------------------------------------------------------
1 | # A convenient module for testing haskell-flake behaviour.
2 | { self, lib, flake-parts-lib, ... }:
3 |
4 | let
5 | inherit (flake-parts-lib)
6 | mkPerSystemOption;
7 | inherit (lib)
8 | mkOption
9 | types;
10 | in
11 | {
12 | options.perSystem = mkPerSystemOption ({ config, self', pkgs, ... }: {
13 | options = {
14 | haskellProjectTests = mkOption {
15 | description = ''
16 | Patch an existing `haskellProject` to run some checks. This module
17 | will create a flake check automatically.
18 | '';
19 | default = { };
20 | type = types.lazyAttrsOf (types.submoduleWith {
21 | specialArgs = { inherit pkgs self; };
22 | modules = [
23 | {
24 | options = {
25 | from = lib.mkOption {
26 | type = types.str;
27 | default = "default";
28 | description = ''
29 | Which `haskellProjects.??` to patch.
30 | '';
31 | };
32 | patches = lib.mkOption {
33 | type = types.listOf (types.either types.path types.str);
34 | description = ''
35 | List of patches to apply to the project root.
36 |
37 | Each patch can be a path to the diff file, or inline patch string.
38 | '';
39 | };
40 | extraHaskellProjectConfig = lib.mkOption {
41 | type = types.deferredModule;
42 | description = ''
43 | Extra configuration to apply to the patched haskell-flake project.
44 | '';
45 | default = { };
46 | };
47 | expect = lib.mkOption {
48 | type = types.raw;
49 | description = ''
50 | Arbitrary expression to evaluate as part of the generated
51 | flake check
52 | '';
53 | };
54 | };
55 | }
56 | ];
57 | });
58 | };
59 | };
60 |
61 | config = {
62 | haskellProjects = lib.flip lib.mapAttrs config.haskellProjectTests (name: cfg: {
63 | imports = [
64 | cfg.extraHaskellProjectConfig
65 | ];
66 | projectRoot = pkgs.applyPatches {
67 | name = "haskellProject-patched-${name}";
68 | src = config.haskellProjects.${cfg.from}.projectRoot;
69 | patches = lib.flip builtins.map cfg.patches (patch:
70 | if types.path.check patch then patch else
71 | pkgs.writeTextFile {
72 | name = "${name}.diff";
73 | text = patch;
74 | }
75 | );
76 | };
77 | });
78 |
79 | checks = lib.flip lib.mapAttrs config.haskellProjectTests (name: cfg:
80 | pkgs.runCommandNoCC "haskell-flake-patch-test-${name}"
81 | {
82 | EXPECT = builtins.toJSON cfg.expect;
83 | }
84 | ''touch $out''
85 | );
86 | };
87 | });
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/nix/modules/project/hls-check.nix:
--------------------------------------------------------------------------------
1 | # Definition of the `haskellProjects.${name}` submodule's `config`
2 | { name, self, config, lib, pkgs, ... }:
3 | let
4 | inherit (lib)
5 | mkOption
6 | types;
7 |
8 | # Like pkgs.runCommand but runs inside nix-shell with a mutable project directory.
9 | #
10 | # It currenty respects only the nativeBuildInputs (and no shellHook for
11 | # instance), which seems sufficient for our purposes. We also set $HOME and
12 | # make the project root mutable, because those are expected when running
13 | # something in a project shell (it is indeed the case with HLS).
14 | runCommandInSimulatedShell = devShell: projectRoot: name: attrs: command:
15 | pkgs.runCommand name (attrs // { inherit (devShell) nativeBuildInputs; })
16 | ''
17 | # Set pipefail option for safer bash
18 | set -euo pipefail
19 |
20 | # Copy project root to a mutable area
21 | # We expect "command" to mutate it.
22 | export HOME=$TMP
23 | cp -R ${projectRoot} $HOME/project
24 | chmod -R a+w $HOME/project
25 | pushd $HOME/project
26 |
27 | ${command}
28 | touch $out
29 | '';
30 |
31 | hlsCheckSubmodule = types.submodule {
32 | options = {
33 | enable = mkOption {
34 | type = types.bool;
35 | description = ''
36 | Whether to enable a flake check to verify that HLS works.
37 |
38 | This is equivalent to `nix develop -i -c haskell-language-server`.
39 |
40 | Note that, HLS will try to access the network through Cabal (see
41 | ),
42 | therefore sandboxing must be disabled when evaluating this
43 | check.
44 | '';
45 | default = false;
46 | };
47 | drv = mkOption {
48 | type = types.package;
49 | readOnly = true;
50 | description = ''
51 | The `hlsCheck` derivation generated for this project.
52 | '';
53 | };
54 | };
55 | };
56 |
57 |
58 | in
59 | {
60 | options = {
61 |
62 | devShell.hlsCheck = mkOption {
63 | default = { };
64 | type = hlsCheckSubmodule;
65 | description = ''
66 | A [check](flake-parts.html#opt-perSystem.checks) to make sure that your IDE will work.
67 | '';
68 | };
69 | outputs.checks = mkOption {
70 | type = types.lazyAttrsOf types.package;
71 | readOnly = true;
72 | description = ''
73 | The flake checks generated for this project.
74 | '';
75 | };
76 |
77 | };
78 | config =
79 | let
80 | projectKey = name;
81 |
82 | hlsCheck =
83 | runCommandInSimulatedShell
84 | config.outputs.devShell
85 | self "${projectKey}-hls-check"
86 | { } "haskell-language-server";
87 | in
88 | {
89 | outputs = {
90 | checks = lib.filterAttrs (_: v: v != null) {
91 | "${name}-hls" = if (config.devShell.enable && config.devShell.hlsCheck.enable) then hlsCheck else null;
92 | };
93 | };
94 |
95 | devShell.hlsCheck.drv = hlsCheck;
96 | };
97 | }
98 |
--------------------------------------------------------------------------------
/nix/modules/project/default.nix:
--------------------------------------------------------------------------------
1 | # Definition of the `haskellProjects.${name}` submodule's `config`
2 | { self, name, config, lib, pkgs, ... }:
3 | let
4 | inherit (lib)
5 | mkOption
6 | types;
7 | inherit (types)
8 | raw;
9 | in
10 | {
11 | imports = [
12 | ./defaults.nix
13 | ./packages
14 | ./settings
15 | ./devshell.nix
16 | ./outputs.nix
17 | ];
18 | options = {
19 | projectRoot = mkOption {
20 | type = types.path;
21 | description = ''
22 | Path to the root of the project directory.
23 |
24 | Chaning this affects certain functionality, like where to
25 | look for the 'cabal.project' file.
26 | '';
27 | default = self;
28 | defaultText = "Top-level directory of the flake";
29 | };
30 | projectFlakeName = mkOption {
31 | type = types.nullOr types.str;
32 | description = ''
33 | A descriptive name for the flake in which this project resides.
34 |
35 | If unspecified, the Nix store path's basename will be used.
36 | '';
37 | default = null;
38 | apply = cls:
39 | if cls == null
40 | then builtins.baseNameOf config.projectRoot
41 | else cls;
42 | };
43 | log = mkOption {
44 | type = types.lazyAttrsOf (types.functionTo types.raw);
45 | default = import ../../logging.nix {
46 | name = config.projectFlakeName + "#haskellProjects." + name;
47 | };
48 | internal = true;
49 | readOnly = true;
50 | description = ''
51 | Internal logging module
52 | '';
53 | };
54 | basePackages = mkOption {
55 | type = types.lazyAttrsOf raw;
56 | description = ''
57 | Which Haskell package set / compiler to use.
58 |
59 | You can effectively select the GHC version here.
60 |
61 | To get the appropriate value, run:
62 |
63 | nix-env -f "" -qaP -A haskell.compiler
64 |
65 | And then, use that in `pkgs.haskell.packages.ghc`
66 | '';
67 | example = "pkgs.haskell.packages.ghc924";
68 | default = pkgs.haskellPackages;
69 | defaultText = lib.literalExpression "pkgs.haskellPackages";
70 | };
71 | otherOverlays = lib.mkOption {
72 | type = types.listOf (import ../../types/haskell-overlay-type.nix { inherit lib; });
73 | description = ''
74 | Extra overlays to apply.
75 |
76 | Normally, you would only use `packages.*` and `settings.*` (which
77 | translate to overlays), but you can use this option if you want control
78 | over the final overlay.
79 | '';
80 | default = [ ];
81 | };
82 | autoWire =
83 | let
84 | outputTypes = [ "packages" "checks" "apps" "devShells" ];
85 | in
86 | mkOption {
87 | type = types.listOf (types.enum outputTypes);
88 | description = ''
89 | List of flake output types to autowire.
90 |
91 | Using an empty list will disable autowiring entirely,
92 | enabling you to manually wire them using
93 | `config.haskellProjects..outputs`.
94 | '';
95 | default = outputTypes;
96 | };
97 | };
98 | }
99 |
100 |
--------------------------------------------------------------------------------
/doc/size.md:
--------------------------------------------------------------------------------
1 | # Optimize package size
2 |
3 | Haskell package derivations created by `haskell-flake` are shipped with symlinks to other store paths, like `$out/lib`, `$out/nix-support` and `$out/share/doc`. In addition, enabling profiling or haddock can increase the size of these packages. If your Haskell application is end-user software, you will want to strip all but the executables. This can be achieved using `justStaticExecutables`:
4 |
5 | ```nix title="flake.nix"
6 | # Inside perSystem
7 | packages.default = pkgs.haskell.lib.justStaticExecutables self'.packages.foo;
8 | ```
9 |
10 | ## Removing unnecessary Nix dependencies
11 |
12 |
13 | There can be cases where `justStaticExecutables` doesn't work. In such cases, you can manually remove references to the store paths that you don't want to ship. Let's say you have a haskell project `foo` that is dependendent on `bar` and `bar`
14 | relies on `data-files` in its cabal (which data-files can be, for instance, `js` or `html` files). Considering you are using `cabal-install < 3.10.1.0` the final executable of `foo` will have a reference to `bar` and `bar` will depend on `ghc`, thus increasing the overal size of the docker image.
15 |
16 | But how do you arrive at this point in the first place? i.e how do you pin point the exact store paths that are causing the increase in size? These are the rough steps that you can follow, if you are packaging it as part of a docker image:
17 |
18 | - Build and scan [[docker|the docker image]] for store paths that are taking up the most space:
19 | ```bash
20 | nix build .#dockerImage
21 | docker load -i < result
22 | docker run --rm -it sh -c 'du -sh /nix/store/*' | sort -h | tail
23 | ```
24 | - After the scan you will notice that `bar` will be present and its quite obvious it shouldn't be present because all of that will be packaged in the executable of `foo`.
25 |
26 | - It might not be obvious to you that `bar` is causing the increase in size. In such cases you can use `nix why-depends` to find out why `ghc` is present in the docker image:
27 | ```bash
28 | nix why-depends /nix/store/...-foo /nix/store/...-ghc-x.x.x
29 | ```
30 |
31 | - Now that you know that `bar` is causing the increase in size, let's wrap the executable of `foo` [removing references to](https://srid.ca/remove-references-to) `bar`:
32 | ```nix title="flake.nix"
33 | {
34 | # Inside `haskellProjects`
35 | haskellProjects.default =
36 | let
37 | # Forked from: https://github.com/srid/emanote/blob/24c7e5e29a91ec201a48fad1ac028a123b82a402/flake.nix#L52-L62
38 | # We shouldn't need this after https://github.com/haskell/cabal/pull/8534
39 | removeReferencesTo = disallowedReferences: drv:
40 | drv.overrideAttrs (old: rec {
41 | inherit disallowedReferences;
42 | # Ditch data dependencies that are not needed at runtime.
43 | # cf. https://github.com/NixOS/nixpkgs/pull/204675
44 | # cf. https://srid.ca/remove-references-to
45 | postInstall = (old.postInstall or "") + ''
46 | ${lib.concatStrings (map (e: "echo Removing reference to: ${e}\n") disallowedReferences)}
47 | ${lib.concatStrings (map (e: "remove-references-to -t ${e} $out/bin/*\n") disallowedReferences)}
48 | '';
49 | });
50 | in
51 | {
52 | # ...
53 | settings = {
54 | foo = {self, super, ... }: {
55 | justStaticExecutables = true;
56 | removeReferencesTo = [
57 | self.bar
58 | ];
59 | };
60 | };
61 | # ...
62 | };
63 | }
64 | ```
65 | - Voila! Now you have a docker image that is much smaller than before.
66 |
--------------------------------------------------------------------------------
/nix/modules/project/packages/package.nix:
--------------------------------------------------------------------------------
1 | # haskellProjects..packages. module.
2 | { project, lib, pkgs, ... }:
3 | let
4 | inherit (lib)
5 | mkOption
6 | types;
7 |
8 | # Whether the 'path' is local to `project.config.projectRoot`
9 | localToProject = path:
10 | path != null &&
11 | lib.types.path.check path &&
12 | lib.strings.hasPrefix "${project.config.projectRoot}" "${path}";
13 | in
14 | { name, config, ... }: {
15 | options = {
16 | source = mkOption {
17 | type = with types; either path str;
18 | description = ''
19 | Source refers to a Haskell package defined by one of the following:
20 |
21 | - Path containing the Haskell package's `.cabal` file.
22 | - Hackage version string
23 | '';
24 | };
25 |
26 | cabal2NixFile = lib.mkOption {
27 | type = lib.types.str;
28 | description = ''
29 | Filename of the cabal2nix generated nix expression.
30 |
31 | This gets used if it exists instead of using IFD (callCabal2nix).
32 | '';
33 | default = "cabal.nix";
34 | };
35 |
36 | cabal.executables = mkOption {
37 | type = types.nullOr (types.listOf types.str);
38 | description = ''
39 | List of executable names found in the cabal file of the package.
40 |
41 | The value is null if 'source' option is Hackage version.
42 | '';
43 | default =
44 | let
45 | haskell-parsers = import ../../../haskell-parsers {
46 | inherit pkgs lib;
47 | throwError = msg: project.config.log.throwError ''
48 | Unable to determine executable names for package ${name}:
49 |
50 | ${msg}
51 | '';
52 | };
53 | in
54 | if lib.types.path.check config.source
55 | then
56 | lib.pipe config.source [
57 | haskell-parsers.getCabalExecutables
58 | (x: project.config.log.traceDebug "${name}.getCabalExecutables = ${builtins.toString x}" x)
59 | ]
60 | else null; # cfg.source is Hackage version; nothing to do.
61 | };
62 |
63 | local.toCurrentProject = mkOption {
64 | type = types.bool;
65 | description = ''
66 | Whether this package is local to the project that is importing it.
67 | '';
68 | internal = true;
69 | readOnly = true;
70 | # We use 'apply' rather than 'default' to make this evaluation lazy at
71 | # call site (which could be different projectRoot)
72 | apply = _:
73 | localToProject config.source;
74 | defaultText = ''
75 | Computed automatically if package 'source' is under 'projectRoot' of the
76 | importing project.
77 | '';
78 | };
79 |
80 | local.toDefinedProject = mkOption {
81 | type = types.bool;
82 | description = ''
83 | Whether this package is local to the project it is defined in.
84 | '';
85 | internal = true;
86 | default =
87 | localToProject config.source;
88 | defaultText = ''
89 | Computed automatically if package 'source' is under 'projectRoot' of the
90 | defining project.
91 | '';
92 | };
93 |
94 | extraCabal2nixOptions = mkOption {
95 | type = types.listOf types.str;
96 | description = ''
97 | List of extra options given to cabal2nix.
98 | '';
99 | default = [ ];
100 | };
101 |
102 | cabalFlags = mkOption {
103 | type = types.lazyAttrsOf types.bool;
104 | description = ''
105 | Cabal flags to enable or disable explicitly when calling cabal2nix.
106 | '';
107 | default = { };
108 | };
109 | };
110 | }
111 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://nixos.zulipchat.com/#narrow/stream/413949-haskell-flake)
2 | [](https://srid.ca/coc "This project follows the 'Naiveté Compass of Mood'")
3 |
4 | # haskell-flake - Manage Haskell projects conveniently with Nix
5 |
6 |
7 |
8 | There are [several ways](https://nixos.asia/en/haskell) to manage Haskell packages using [Nix](https://nixos.asia/en/nix) with varying degrees of integration. `haskell-flake` makes Haskell development, packaging and deployment with Nix flakes a lot [simpler](https://community.flake.parts/haskell-flake/start#under-the-hood) than other existing approaches. This project is set up as a modern [`flake-parts`](https://flake.parts/) module to integrate easily into other Nix projects and shell development environments in a lightweight and modular way.
9 |
10 | To see more background information, guides and best practices, visit https://community.flake.parts/haskell-flake
11 |
12 | Caveat: `haskell-flake` only supports the Haskell package manager [Cabal](https://www.haskell.org/cabal/),
13 | so your project must have a top-level `.cabal` file (single package project) or a `cabal.project` file
14 | (multi-package project).
15 |
16 | ## Getting started
17 |
18 | The minimal changes to your `flake.nix` to introduce the `haskell-flake` and [`flake-parts`](https://flake.parts/) modules will look similar to:
19 |
20 | ```nix
21 | # file: flake.nix
22 | {
23 | inputs = {
24 | ...
25 | flake-parts.url = "github:hercules-ci/flake-parts";
26 | haskell-flake.url = "github:srid/haskell-flake";
27 | };
28 |
29 | outputs = inputs:
30 | inputs.flake-parts.lib.mkFlake { inherit inputs; } {
31 | systems = [ "x86_64-linux", ... ];
32 | imports = [
33 | ...
34 | inputs.haskell-flake.flakeModule
35 | ];
36 | perSystem = { self', system, lib, config, pkgs, ... }: {
37 | haskellProjects.default = {
38 | # basePackages = pkgs.haskellPackages;
39 |
40 | # Packages to add on top of `basePackages`, e.g. from Hackage
41 | packages = {
42 | aeson.source = "1.5.0.0"; # Hackage version
43 | };
44 |
45 | # my-haskell-package development shell configuration
46 | devShell = {
47 | hlsCheck.enable = false;
48 | };
49 |
50 | # What should haskell-flake add to flake outputs?
51 | autoWire = [ "packages" "apps" "checks" ]; # Wire all but the devShell
52 | };
53 |
54 | devShells.default = pkgs.mkShell {
55 | name = "my-haskell-package custom development shell";
56 | inputsFrom = [
57 | ...
58 | config.haskellProjects.default.outputs.devShell
59 | ];
60 | nativeBuildInputs = with pkgs; [
61 | # other development tools.
62 | ];
63 | };
64 | };
65 | };
66 | }
67 | ```
68 |
69 | `haskell-flake` scans your folder automatically for a `.cabal` or `cabal.project` file.
70 | In this example an imaginary `my-haskell-package.cabal` project is used.
71 |
72 | To see in more detail how to use `haskell-flake` in a realistic Haskell project
73 | with several other development tools, take a look at
74 | the corresponding [Haskell single-package project Nix template](https://github.com/srid/haskell-template) and
75 | this [Haskell multi-package project Nix example](https://github.com/srid/haskell-multi-nix).
76 |
77 | ## Documentation
78 |
79 | https://community.flake.parts/haskell-flake
80 |
81 | ## Discussion
82 |
83 | [Zulip](https://nixos.zulipchat.com/#narrow/stream/413949-haskell-flake) is the primary venue for discussion; we also have [Github Discussions](https://github.com/srid/haskell-flake/discussions) enabled.
84 |
--------------------------------------------------------------------------------
/doc/modules.md:
--------------------------------------------------------------------------------
1 | # Project modules
2 |
3 | haskell-flake's per-project configuration can be modularized and shared among multiple repos. This is done using the `flake.haskellFlakeProjectModules` flake output.
4 |
5 | Let's say you have two repositories -- `common` and `myapp`. The `common` repository may expose some shared haskell-flake settings as follows:
6 |
7 | ```nix
8 | {
9 | # Inside flake-parts' `mkFlake`:
10 | flake.haskellFlakeProjectModules.default = { pkgs, ... }: {
11 | devShell.tools = hp: {
12 | inherit (hp)
13 | hlint
14 | cabal-fmt
15 | ormolu;
16 | };
17 | packages = {
18 | mylib.source = inputs.mylib;
19 | };
20 | };
21 | }
22 | ```
23 |
24 | This module can then be imported in multiple projects, such as the `myapp` project:
25 |
26 | ```nix
27 | {
28 | # Inside flake-parts' `perSystem`:
29 | haskellProjects.default = {
30 | imports = [
31 | inputs.common.haskellFlakeProjectModules.default
32 | ];
33 | packages = {
34 | myapp.root = ./.;
35 | };
36 | };
37 | }
38 | ```
39 |
40 | This way your `app` project knows how to find "mylib" library as well as includes the default tools you want to use in the dev shell.
41 |
42 | ## Module arguments {#args}
43 |
44 | A haskell-flake project module takes the following arguments:
45 |
46 | | Argument | Description |
47 | | --- | --- |
48 | | `pkgs` | The perSystem's `pkgs` argument |
49 | | `self` | The flake's `self` |
50 |
51 | ## Default modules {#default}
52 |
53 | By default, haskell-flake will generate the following modules for the "default" `haskellProject`:
54 |
55 | | Module | Contents |
56 | | -- | -- |
57 | | `haskellFlakeProjectModules.output` | [[local\|Local packages]] & dependency overrides |
58 |
59 | The idea here being that you can "connect" two Haskell projects such that they depend on one another while reusing the overrides -- `packages` (see [[dependency]]) and `settings` (see [[settings]]) -- from one place. For example, if you have a project "foo" that depends on "bar" and if "foo"'s flake.nix has "bar" as its input, then in "foo"'s `haskellProject.default` entry you can import "bar" as follows:
60 |
61 | ```nix
62 | # foo's flake.nix's perSystem
63 | {
64 | haskellProjects.default = {
65 | imports = [
66 | inputs.bar.haskellFlakeProjectModules.output
67 | ];
68 | packages = {
69 | foo.root = ./.;
70 | };
71 | };
72 | }
73 | ```
74 |
75 | By importing "bar"'s `output` project module, you automatically get the overrides from "bar" as well as the [[local|local packages]]. This way you don't have to duplicate the `settings` and manually specify the `packages..source` in "foo"'s flake.nix.
76 |
77 | ## Export non-default project modules {#non-default}
78 |
79 | The flake output `haskellFlakeProjectModules.output` exports `packages` and [[settings]] options of `haskellProject.default`, but you could create custom flake output that does the same for an arbitrary project, let's say `bar`, as follows:
80 |
81 | ```nix
82 | # Inside foo/flake.nix's outputs
83 | {
84 | flake-parts.lib.mkFlake { inherit inputs; } ({ withSystem, ... }: {
85 | flake.haskellFlakeProjectModules = {
86 | bar = { pkgs, lib, ... }: withSystem pkgs.system ({ config, ... }:
87 | config.haskellProjects.bar.defaults.projectModules.output
88 | );
89 | };
90 | });
91 | }
92 | ```
93 |
94 | The flake output `haskellFlakeProjectModules.bar` of `foo` can be imported in another project, let's say `baz`, as:
95 |
96 | ```nix
97 | # baz/flake.nix's perSystem
98 | {
99 | haskellProjects.default = {
100 | imports = [
101 | inputs.foo.haskellFlakeProjectModules.bar
102 | ];
103 | };
104 | }
105 | ```
106 |
107 | ## Examples
108 |
109 | - https://github.com/nammayatri/nammayatri (imports `shared-kernel` which in turn imports `euler-hs`)
110 |
--------------------------------------------------------------------------------
/doc/docker.md:
--------------------------------------------------------------------------------
1 |
2 | # Building a docker image
3 |
4 | Building a docker image is much simpler with Nix compared to writing `Dockerfile`. Since the entire build process is handled by Nix flakes, most of what's left to do for docker image creation is copying of the derivations and configuration.
5 |
6 | ## Writing the Nix to build the docker image
7 |
8 | Consider a haskell-flake project "foo". To copy the binaries generated by the `default` package to `/bin` on the image, one can use `copyToRoot` attribute offered by [`dockerTools.buildImage`](https://nixos.org/manual/nixpkgs/stable/#sec-pkgs-dockerTools). For example:
9 |
10 | ```nix
11 | {
12 | # Inside perSystem
13 | packages.dockerImage = pkgs.dockerTools.buildImage {
14 | name = "foo";
15 | copyToRoot = pkgs.buildEnv {
16 | paths = with pkgs; [
17 | self'.packages.default
18 | ];
19 | name = "foo-root";
20 | pathsToLink = [ "/bin" ];
21 | };
22 | };
23 | }
24 | ```
25 |
26 | In addition to copying over the flake `packages`, we may also copy *paths* in the project. `self` can be added to `paths` to expose the project directory.
27 | ```nix
28 | {
29 | copyToRoot = pkgs.buildEnv {
30 | paths = with pkgs; [
31 | coreutils
32 | bash
33 | self
34 | ];
35 | name = "foo-root";
36 | pathsToLink = [ "/foo_sub" "/bin" ];
37 | };
38 | }
39 | ```
40 | If you'd like your docker image to run your haskell project's default package when the container starts, use the following config:
41 | ```nix
42 | {
43 | # Inside dockerImage's `buildImage`
44 | config = {
45 | Cmd = [ "${pkgs.lib.getExe self'.packages.default}" ];
46 | };
47 | }
48 | ```
49 |
50 | ## Build the docker image
51 |
52 | To build the docker image *as a Nix derivation*, run:
53 |
54 | ```bash
55 | nix build .#dockerImage
56 | ```
57 |
58 | To load this image into your local docker image registry, run:
59 |
60 | ```bash
61 | docker load -i $(nix build .#dockerImage --print-out-paths)
62 | ```
63 |
64 | ## Tips
65 |
66 | ### Size
67 |
68 | Docker images including Haskell packages can be optimized using the methods described [[size|here]].
69 |
70 | ### Time
71 |
72 | If you don't want `docker images` showing that the image was created several decades ago, use the following:
73 | ```nix
74 | {
75 | # Inside perSystem.packages' `dockerImage`:
76 | pkgs.dockerTools.buildImage {
77 | name = "foo";
78 | created = "now";
79 | };
80 | }
81 | ```
82 |
83 | ### Tag
84 |
85 | If you want to tag the images with the commit id of the working copy:
86 |
87 | ```nix
88 | {
89 | # Inside perSystem.packages' `dockerImage`:
90 | pkgs.dockerTools.buildImage {
91 | name = "foo";
92 | tag = builtins.substring 0 9 (self.rev or "dev");
93 | };
94 | }
95 | ```
96 | [`builtins.substring 0 9 self.rev`](https://nixos.org/manual/nix/stable/language/builtins.html#builtins-substring) is the same as `git rev-parse --short HEAD`. `self.rev` is non-null only on a clean working copy and hence the tag is set to `dev` when the working copy is dirty.
97 |
98 | ### SSL certs
99 |
100 | In order to be able to make https connections from inside of the docker image, you must expose the cacert Nix package via the relevant environment variable:
101 |
102 | ```nix
103 | {
104 | # Inside dockerTools.buildImage
105 | config = {
106 | Env = [
107 | "SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"
108 | # Ref: https://hackage.haskell.org/package/x509-system-1.6.7/docs/src/System.X509.Unix.html#getSystemCertificateStore
109 | "SYSTEM_CERTIFICATE_PATH=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"
110 | ];
111 | };
112 | }
113 | ```
114 |
115 |
116 | ## Example
117 |
118 | - [Sample flake-parts module for docker](https://github.com/nammayatri/nammayatri/pull/14/files#diff-18ea3dd9a6a84702796b8dac608d0cad8e396a7c2e8c52732fcb7e5f52d1b0b9)
119 |
--------------------------------------------------------------------------------
/doc/debugging.md:
--------------------------------------------------------------------------------
1 | # Debugging logs
2 |
3 | >[!warning]
4 | > This feature is available only in Nix versions 2.10 or later.
5 |
6 | Passing `--trace-verbose` to Nix commands causes haskell-flake to print verbose logging of its activity. To enable it:
7 |
8 | >[!tip] Timestamps in logs
9 | > `moreutils` provides the `ts` command that you can pipe your nix command output to in order to get timestamps in the logs.
10 |
11 | The below is a sample output when building [haskell-multi-nix](https://github.com/srid/haskell-multi-nix/tree/debug) with `--trace-verbose`:
12 |
13 | ```text
14 | $ nix --no-eval-cache build -L --trace-verbose github:srid/haskell-multi-nix 2>&1 | ts '[%H:%M:%S]'
15 | [22:45:38] trace: DEBUG[haskell-flake] [haskell-multi-nix#haskellProjects.default]: default.findPackagesInCabalProject = {"bar":"/nix/store/5zvwxw2n801bbjcz3685dp20y8afjmld-source/./bar","foo":"/nix/store/5zvwxw2n801bbjcz3685dp20y8afjmld-source/./foo"}
16 | [22:45:38] trace: DEBUG[haskell-flake] [haskell-multi-nix#haskellProjects.default]: defaults.packages = {"bar":{"imports":[{"_file":"/nix/store/sv90dpz2fgn93kvzc14szqn77wvjssv0-source/nix/modules/project/defaults.nix, via option perSystem.aarch64-darwin.haskellProjects.default.defaults.packages.bar","imports":[{"source":"/nix/store/5zvwxw2n801bbjcz3685dp20y8afjmld-source/./bar"}]}]},"foo":{"imports":[{"_file":"/nix/store/sv90dpz2fgn93kvzc14szqn77wvjssv0-source/nix/modules/project/defaults.nix, via option perSystem.aarch64-darwin.haskellProjects.default.defaults.packages.foo","imports":[{"source":"/nix/store/5zvwxw2n801bbjcz3685dp20y8afjmld-source/./foo"}]}]}}
17 | [22:45:38] trace: DEBUG[haskell-flake] [haskell-multi-nix#haskellProjects.default]: bar.getCabalExecutables = bar
18 | [22:45:38] trace: DEBUG[haskell-flake] [haskell-multi-nix#haskellProjects.default]: foo.getCabalExecutables =
19 | [22:45:38] trace: DEBUG[haskell-flake] [haskell-multi-nix#haskellProjects.default]: default.packages:apply {"bar":{"cabal":{"executables":["bar"]},"local":{"toCurrentProject":true,"toDefinedProject":true},"source":"/nix/store/5zvwxw2n801bbjcz3685dp20y8afjmld-source/./bar"},"foo":{"cabal":{"executables":[]},"local":{"toCurrentProject":true,"toDefinedProject":true},"source":"/nix/store/5zvwxw2n801bbjcz3685dp20y8afjmld-source/./foo"}}
20 | [22:45:40] trace: DEBUG[haskell-flake] [haskell-multi-nix#haskellProjects.default]: settings.bar {"haddock":false,"libraryProfiling":false}
21 | [22:45:40] trace: DEBUG[haskell-flake] [haskell-multi-nix#haskellProjects.default]: bar.callCabal2nix /nix/store/5zvwxw2n801bbjcz3685dp20y8afjmld-source/./bar
22 | [22:45:40] trace: DEBUG[haskell-flake] [haskell-multi-nix#haskellProjects.default]: bar.mkNewStorePath /nix/store/hr0a6v8wwwvw323clv9x28zknd5fqz84-source-bar
23 | [22:45:41] trace: DEBUG[haskell-flake] [haskell-multi-nix#haskellProjects.default]: bar.cabal2nixDeriver /nix/store/pxcqizj7mvmwflx7hxlq7ll5bdmcis2a-cabal2nix-bar
24 | [22:45:41] trace: DEBUG[haskell-flake] [haskell-multi-nix#haskellProjects.default]: settings.foo {"haddock":false,"libraryProfiling":false}
25 | [22:45:41] trace: DEBUG[haskell-flake] [haskell-multi-nix#haskellProjects.default]: foo.callCabal2nix /nix/store/5zvwxw2n801bbjcz3685dp20y8afjmld-source/./foo
26 | [22:45:41] trace: DEBUG[haskell-flake] [haskell-multi-nix#haskellProjects.default]: foo.mkNewStorePath /nix/store/bpybsny4gd5jnw0lvk5khpq7md6nwg5f-source-foo
27 | [22:45:41] trace: DEBUG[haskell-flake] [haskell-multi-nix#haskellProjects.default]: foo.cabal2nixDeriver /nix/store/i36x01zcdpm7c3m3fjjq1qa4slv61jpw-cabal2nix-foo
28 | [22:45:41] trace: DEBUG[haskell-flake] [haskell-multi-nix#haskellProjects.default]: foo.fromSdist /nix/store/qrsy0bm4khcs1hxy0rhb6m3g2bvi15sm-foo-0.1.0.0
29 | [22:45:41] trace: DEBUG[haskell-flake] [haskell-multi-nix#haskellProjects.default]: bar.fromSdist /nix/store/anyx51rm5gjdclafcz5is7jpqgfq2i4w-bar-0.1.0.0
30 | ```
31 |
32 | ## See also
33 |
34 | - Read more about [the `traceVerbose` function](https://nixos.asia/en/traceVerbose) which haskell-flake uses to produce the above logs.
--------------------------------------------------------------------------------
/dev/flake.lock:
--------------------------------------------------------------------------------
1 | {
2 | "nodes": {
3 | "flake-parts": {
4 | "inputs": {
5 | "nixpkgs-lib": "nixpkgs-lib"
6 | },
7 | "locked": {
8 | "lastModified": 1706830856,
9 | "narHash": "sha256-a0NYyp+h9hlb7ddVz4LUn1vT/PLwqfrWYcHMvFB1xYg=",
10 | "owner": "hercules-ci",
11 | "repo": "flake-parts",
12 | "rev": "b253292d9c0a5ead9bc98c4e9a26c6312e27d69f",
13 | "type": "github"
14 | },
15 | "original": {
16 | "owner": "hercules-ci",
17 | "repo": "flake-parts",
18 | "type": "github"
19 | }
20 | },
21 | "flake-root": {
22 | "locked": {
23 | "lastModified": 1692742795,
24 | "narHash": "sha256-f+Y0YhVCIJ06LemO+3Xx00lIcqQxSKJHXT/yk1RTKxw=",
25 | "owner": "srid",
26 | "repo": "flake-root",
27 | "rev": "d9a70d9c7a5fd7f3258ccf48da9335e9b47c3937",
28 | "type": "github"
29 | },
30 | "original": {
31 | "owner": "srid",
32 | "repo": "flake-root",
33 | "type": "github"
34 | }
35 | },
36 | "haskell-flake": {
37 | "locked": {
38 | "lastModified": 1711376786,
39 | "narHash": "sha256-AgTchS11b8y7RGLKv6tsHPuzthcgDok4VIpqq+BG5O4=",
40 | "owner": "srid",
41 | "repo": "haskell-flake",
42 | "rev": "b5bfa21723e55590e92f4896d7bc3ad212cf425a",
43 | "type": "github"
44 | },
45 | "original": {
46 | "owner": "srid",
47 | "repo": "haskell-flake",
48 | "type": "github"
49 | }
50 | },
51 | "nixpkgs": {
52 | "locked": {
53 | "lastModified": 1708751719,
54 | "narHash": "sha256-0uWOKSpXJXmXswOvDM5Vk3blB74apFB6rNGWV5IjoN0=",
55 | "owner": "nixos",
56 | "repo": "nixpkgs",
57 | "rev": "f63ce824cd2f036216eb5f637dfef31e1a03ee89",
58 | "type": "github"
59 | },
60 | "original": {
61 | "owner": "nixos",
62 | "ref": "nixpkgs-unstable",
63 | "repo": "nixpkgs",
64 | "type": "github"
65 | }
66 | },
67 | "nixpkgs-lib": {
68 | "locked": {
69 | "dir": "lib",
70 | "lastModified": 1706550542,
71 | "narHash": "sha256-UcsnCG6wx++23yeER4Hg18CXWbgNpqNXcHIo5/1Y+hc=",
72 | "owner": "NixOS",
73 | "repo": "nixpkgs",
74 | "rev": "97b17f32362e475016f942bbdfda4a4a72a8a652",
75 | "type": "github"
76 | },
77 | "original": {
78 | "dir": "lib",
79 | "owner": "NixOS",
80 | "ref": "nixos-unstable",
81 | "repo": "nixpkgs",
82 | "type": "github"
83 | }
84 | },
85 | "nixpkgs_2": {
86 | "locked": {
87 | "lastModified": 1705856552,
88 | "narHash": "sha256-JXfnuEf5Yd6bhMs/uvM67/joxYKoysyE3M2k6T3eWbg=",
89 | "owner": "nixos",
90 | "repo": "nixpkgs",
91 | "rev": "612f97239e2cc474c13c9dafa0df378058c5ad8d",
92 | "type": "github"
93 | },
94 | "original": {
95 | "owner": "nixos",
96 | "ref": "nixos-unstable",
97 | "repo": "nixpkgs",
98 | "type": "github"
99 | }
100 | },
101 | "root": {
102 | "inputs": {
103 | "flake-parts": "flake-parts",
104 | "flake-root": "flake-root",
105 | "haskell-flake": "haskell-flake",
106 | "nixpkgs": "nixpkgs",
107 | "treefmt-nix": "treefmt-nix"
108 | }
109 | },
110 | "treefmt-nix": {
111 | "inputs": {
112 | "nixpkgs": "nixpkgs_2"
113 | },
114 | "locked": {
115 | "lastModified": 1707300477,
116 | "narHash": "sha256-qQF0fEkHlnxHcrKIMRzOETnRBksUK048MXkX0SOmxvA=",
117 | "owner": "numtide",
118 | "repo": "treefmt-nix",
119 | "rev": "ac599dab59a66304eb511af07b3883114f061b9d",
120 | "type": "github"
121 | },
122 | "original": {
123 | "owner": "numtide",
124 | "repo": "treefmt-nix",
125 | "type": "github"
126 | }
127 | }
128 | },
129 | "root": "root",
130 | "version": 7
131 | }
132 |
--------------------------------------------------------------------------------
/doc/dependency.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: -10
3 | ---
4 |
5 | # Overriding dependencies
6 |
7 | Haskell libraries ultimately come from [Hackage](https://hackage.haskell.org/), and [nixpkgs] contains [most of these](https://nixpkgs.haskell.page/). Adding a library to your project involves modifying the `.cabal` file and restarting the nix shell. The process is typically as follows:
8 |
9 | 1. Identify the package name from Hackage. Let's say you want to use [`ema`](https://hackage.haskell.org/package/ema)
10 | 2. Add the package, `ema`, to the `.cabal` file under [the `build-depends` section](https://cabal.readthedocs.io/en/3.4/cabal-package.html#pkg-field-build-depends).
11 | 3. Exit and restart the nix shell (`nix develop`).
12 |
13 | Step (3) above will try to fetch the package from the Haskell package set in [nixpkgs] (`pkgs.haskellPackages` by default). For various reasons, this package may be either missing or marked as "broken". In such cases, you will have to override the package locally in the project (see the next section).
14 |
15 | ## Overriding a Haskell package source {#source}
16 |
17 | In Nix, it is possible to use an exact package built from an arbitrary source - which can be a Git repo, local directory or a Hackage version.
18 |
19 | ### Using a Git repo {#path}
20 |
21 | If you want to use the `master` branch of the [ema](https://hackage.haskell.org/package/ema) library, for instance, you can do it as follows:
22 |
23 | 1. Add a flake input pointing to the ema Git repo in `flake.nix`:
24 | ```nix
25 | {
26 | inputs = {
27 | ema.url = "github:srid/ema";
28 | ema.flake = false;
29 | };
30 | }
31 | ```
32 | 1. Build it using `callCabal2nix` and assign it to the `ema` name in the Haskell package set by adding it to the `packages` argument of your `flake.nix` that is using haskell-flake:
33 | ```nix
34 | {
35 | perSystem = { self', config, pkgs, ... }: {
36 | haskellProjects.default = {
37 | packages = {
38 | ema.source = inputs.ema;
39 | };
40 | };
41 | };
42 | }
43 | ```
44 | 1. Re-run the nix shell (`nix develop`).
45 |
46 | ### Using a multi-package Haskell Git repo {#multi-path}
47 |
48 | If you want to override multiple dependencies whose source exist in the same mono repo (for e.g., `foo` and `bar` in [haskell-multi-nix](https://github.com/srid/haskell-multi-nix), you can do so as follows:
49 |
50 | 1. Add a flake input pointing to the monorepo (eg., `haskell-multi-nix` Git repo) in `flake.nix`:
51 | ```nix
52 | {
53 | inputs = {
54 | haskell-multi-nix.url = "github:srid/haskell-multi-nix";
55 | haskell-multi-nix.flake = false;
56 | };
57 | }
58 | ```
59 | 1. Add a separate entry in `haskellProjects..packages` for each of the package in the subdirectories:
60 | ```nix
61 | {
62 | perSystem = { self', config, pkgs, ... }: {
63 | haskellProjects.default = {
64 | packages = {
65 | foo.source = inputs.haskell-multi-nix + /foo;
66 | bar.source = inputs.haskell-multi-nix + /bar;
67 | };
68 | };
69 | };
70 | }
71 | ```
72 |
73 | ### Using a Hackage version {#hackage}
74 |
75 | `packages..source` also supports Hackage versions. So the following works to pull [ema 0.8.2.0](https://hackage.haskell.org/package/ema-0.8.2.0):
76 |
77 | ```nix
78 | {
79 | perSystem = { self', config, pkgs, ... }: {
80 | haskellProjects.default = {
81 | packages = {
82 | ema.source = "0.8.2.0";
83 | };
84 | };
85 | };
86 | }
87 | ```
88 |
89 | ### Using a nixpkgs version {#nixpkgs}
90 |
91 | ```nix
92 | haskellProjects.default = {
93 | settings = {
94 | fourmolu = { super, ...}: { custom = _: super.fourmolu_0_13_1_0; };
95 | };
96 | };
97 | ```
98 |
99 | ## Overriding a Haskell package settings {#settings}
100 |
101 | See [[settings]]
102 |
103 | ## Sharing dependency overrides {#share}
104 |
105 | [[modules]] export both `packages` and `settings` options for reuse in downstream Haskell projects.
106 |
107 |
108 | [nixpkgs]: https://nixos.asia/en/nixpkgs
109 |
--------------------------------------------------------------------------------
/nix/modules/project/settings/default.nix:
--------------------------------------------------------------------------------
1 | project@{ name, pkgs, lib, ... }:
2 |
3 | let
4 | inherit (lib)
5 | types;
6 | traceSettings = hpkg: x:
7 | let
8 | # Convert the settings config (x) to be stripped of functions, so we can
9 | # convert it to JSON for logging.
10 | xSanitized = lib.filterAttrs
11 | (s: v:
12 | !(builtins.isFunction v) && !(s == "impl") && v != null)
13 | x;
14 | in
15 | project.config.log.traceDebug "settings.${hpkg} ${builtins.toJSON xSanitized}" x;
16 | in
17 | {
18 | options.settings = lib.mkOption {
19 | type = types.lazyAttrsOf types.deferredModule;
20 | default = { };
21 | apply = settings:
22 | # Polyfill 'packages'; because overlay's defaults setting merge requires it.
23 | let
24 | packagesEmptySettings =
25 | lib.mapAttrs (_: _: { }) project.config.packages;
26 | in
27 | packagesEmptySettings // settings;
28 | description = ''
29 | Overrides for packages in `basePackages` and `packages`.
30 |
31 | Attr values are submodules that take the following arguments:
32 |
33 | - `name`: Package name
34 | - `package`: The reference to the package in `packages` option if it exists, null otherwise.
35 | - `self`/`super`: The 'self' and 'super' (aka. 'final' and 'prev') used in the Haskell overlay.
36 | - `pkgs`: Nixpkgs instance of the module user (import'er).
37 |
38 | Default settings are defined in `project.config.defaults.settings` which can be overriden.
39 | '';
40 | };
41 |
42 | options.settingsOverlay = lib.mkOption {
43 | type = import ../../../types/haskell-overlay-type.nix { inherit lib; };
44 | description = ''
45 | The Haskell overlay computed from `settings` modules, as well as
46 | `defaults.settings.default` module.
47 | '';
48 | internal = true;
49 | default = self: super:
50 | let
51 | applySettingsFor = name: mod:
52 | let
53 | cfg' = (lib.evalModules {
54 | modules = [
55 | # Settings spec
56 | ./all.nix
57 |
58 | # Default settings
59 | project.config.defaults.settings.local
60 | project.config.defaults.settings.defined
61 | project.config.defaults.settings.all
62 |
63 |
64 | # User module
65 | mod
66 | ];
67 | specialArgs = {
68 | inherit name pkgs self super;
69 | inherit (project.config) log;
70 | package = project.config.packages.${name} or null;
71 | } // (import ./lib.nix {
72 | inherit lib;
73 | # NOTE: Recursively referring generated config in lib.nix.
74 | config = cfg;
75 | });
76 | }).config;
77 | cfg = traceSettings name cfg';
78 |
79 | # Some settings must be applied in deterministic order
80 | #
81 | # NOTE: The `custom` setting must apply first, since it can discard its argument.
82 | # HACK: buildFromSdist must apply *last*
83 | # cf. https://github.com/srid/haskell-flake/pull/252
84 | # In future, we can refactor this as part of https://github.com/srid/haskell-flake/issues/285
85 | # NOTE: removeReferencesTo must apply *before* buildFromSdist, because the
86 | # later appears it fuck up the former otherwise.
87 | # NOTE: separateIntermediatesOutput must apply after buildFromSdist, otherwise
88 | # the `installIntermediatesPhase` is set on the `sdist`'s drv instead of the package's.
89 | impl = builtins.removeAttrs cfg.impl [ "custom" "buildFromSdist" "removeReferencesTo" "separateIntermediatesOutput" ];
90 | fns = [ cfg.impl.custom ] ++ lib.attrValues impl ++ [ cfg.impl.buildFromSdist cfg.impl.removeReferencesTo cfg.impl.separateIntermediatesOutput ];
91 | in
92 | lib.pipe super.${name} (
93 | # TODO: Do we care about the *order* of overrides?
94 | lib.concatMap
95 | (impl: impl)
96 | fns
97 | );
98 | in
99 | lib.mapAttrs applySettingsFor project.config.settings;
100 | };
101 | }
102 |
--------------------------------------------------------------------------------
/nix/modules/project/outputs.nix:
--------------------------------------------------------------------------------
1 | # haskellProjects..outputs module.
2 | { config, lib, pkgs, ... }:
3 | let
4 | inherit (lib)
5 | mkOption
6 | types;
7 |
8 | appType = import ../../types/app-type.nix { inherit pkgs lib; };
9 |
10 | outputsSubmodule = types.submodule {
11 | options = {
12 | finalOverlay = mkOption {
13 | type = types.raw;
14 | readOnly = true;
15 | internal = true;
16 | };
17 | finalPackages = mkOption {
18 | # This must be raw because the Haskell package set also contains functions.
19 | type = types.lazyAttrsOf types.raw;
20 | readOnly = true;
21 | description = ''
22 | The final Haskell package set including local packages and any
23 | overrides, on top of `basePackages`.
24 | '';
25 | };
26 | packages = mkOption {
27 | type = types.lazyAttrsOf packageInfoSubmodule;
28 | readOnly = true;
29 | description = ''
30 | Package information for all local packages. Contains the following keys:
31 |
32 | - `package`: The Haskell package derivation
33 | - `exes`: Attrset of executables found in the .cabal file
34 | '';
35 | };
36 | apps = mkOption {
37 | type = types.lazyAttrsOf appType;
38 | readOnly = true;
39 | description = ''
40 | Flake apps for each Cabal executable in the project.
41 | '';
42 | };
43 | };
44 | };
45 |
46 | packageInfoSubmodule = types.submodule {
47 | options = {
48 | package = mkOption {
49 | type = types.package;
50 | description = ''
51 | The local package derivation.
52 | '';
53 | };
54 | exes = mkOption {
55 | type = types.lazyAttrsOf appType;
56 | description = ''
57 | Attrset of executables from `.cabal` file.
58 |
59 | If the associated Haskell project has a separate bin output
60 | (cf. `enableSeparateBinOutput`), then this exe will refer
61 | only to the bin output.
62 |
63 | NOTE: Evaluating up to this option will involve IFD.
64 | '';
65 | };
66 | };
67 | };
68 | in
69 | {
70 | options = {
71 | outputs = mkOption {
72 | type = outputsSubmodule;
73 | description = ''
74 | The flake outputs generated for this project.
75 |
76 | This is an internal option, not meant to be set by the user.
77 | '';
78 | };
79 | };
80 | config =
81 | let
82 | # Subet of config.packages that are local to the project.
83 | localPackages =
84 | lib.filterAttrs (_: cfg: cfg.local.toCurrentProject) config.packages;
85 |
86 | finalOverlay = lib.composeManyExtensions ([
87 | config.packagesOverlay
88 | config.settingsOverlay
89 | ] ++ config.otherOverlays);
90 |
91 | finalPackages = config.basePackages.extend finalOverlay;
92 |
93 | buildPackageInfo = name: value: rec {
94 | package = finalPackages.${name};
95 | exes =
96 | lib.listToAttrs
97 | (map
98 | (exe:
99 | lib.nameValuePair exe ({
100 | program = "${lib.getBin package}/bin/${exe}";
101 | meta.description =
102 | if lib.hasAttrByPath [ "meta" "description" ] package
103 | then "${if exe != name then "[${exe}] " else ""}${package.meta.description}"
104 | else "Executable ${if exe != name then "${exe} from " else "for "}package ${name}";
105 | })
106 | )
107 | value.cabal.executables
108 | );
109 | };
110 |
111 | # To fix the following error with lib.mkMerge
112 | # error: The option `perSystem.aarch64-darwin.haskellProjects.default.outputs.apps' is used but not defined.
113 | mkMergeHandlingEmpty = xs:
114 | if xs == [ ] then { } else lib.mkMerge xs;
115 |
116 | in
117 | {
118 | outputs = {
119 | inherit finalOverlay finalPackages;
120 |
121 | packages = lib.mapAttrs buildPackageInfo localPackages;
122 |
123 | apps =
124 | mkMergeHandlingEmpty
125 | (lib.mapAttrsToList (_: packageInfo: packageInfo.exes) config.outputs.packages);
126 | };
127 | };
128 | }
129 |
--------------------------------------------------------------------------------
/test/settings-defaults/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | # Since there is no flake.lock file (to avoid incongruent haskell-flake
3 | # pinning), we must specify revisions for *all* inputs to ensure
4 | # reproducibility.
5 | inputs = {
6 | nixpkgs.url = "github:nixos/nixpkgs/870493f9a8cb0b074ae5b411b2f232015db19a65";
7 | flake-parts.url = "github:hercules-ci/flake-parts/758cf7296bee11f1706a574c77d072b8a7baa881";
8 | haskell-flake = { };
9 | haskell-template.url = "github:srid/haskell-template/554b7c565396cf2d49a248e7e1dc0e0b46883b10";
10 | };
11 | outputs = inputs@{ self, nixpkgs, flake-parts, ... }:
12 | flake-parts.lib.mkFlake { inherit inputs; } {
13 | systems = nixpkgs.lib.systems.flakeExposed;
14 | imports = [
15 | inputs.haskell-flake.flakeModule
16 | ];
17 | debug = true;
18 | perSystem = { config, self', pkgs, lib, ... }: {
19 | haskellProjects.default = { };
20 | haskellProjectTests =
21 | let
22 | finalPackagesOf = projectName: config.haskellProjects.${projectName}.outputs.finalPackages;
23 | isSettingApplied = pkg: lib.hasAttr "setting-applied" pkg.meta;
24 | in
25 | {
26 | test-default-current = { name, ... }: {
27 | patches = [ ];
28 | extraHaskellProjectConfig = {
29 | imports = [
30 | inputs.haskell-template.haskellFlakeProjectModules.output
31 | ];
32 | defaults.settings.local = {
33 | custom = pkg: pkg.overrideAttrs (oldAttrs: {
34 | meta = oldAttrs.meta // {
35 | setting-applied = true;
36 | };
37 | });
38 | };
39 | };
40 | expect =
41 | lib.assertMsg
42 | (lib.all (x: x) [
43 | (! isSettingApplied (finalPackagesOf name).random)
44 | (! isSettingApplied (finalPackagesOf name).haskell-template)
45 | (isSettingApplied (finalPackagesOf name).haskell-flake-test)
46 | ])
47 | "defaults.settings: ${name} failed";
48 | };
49 | test-default-defined = { name, ... }: {
50 | patches = [ ];
51 | extraHaskellProjectConfig = {
52 | imports = [
53 | inputs.haskell-template.haskellFlakeProjectModules.output
54 | ];
55 | defaults.settings.defined = {
56 | custom = pkg: pkg.overrideAttrs (oldAttrs: {
57 | meta = oldAttrs.meta // {
58 | setting-applied = true;
59 | };
60 | });
61 | };
62 | };
63 | expect =
64 | lib.assertMsg
65 | (lib.all (x: x) [
66 | (! isSettingApplied (finalPackagesOf name).random)
67 | (isSettingApplied (finalPackagesOf name).haskell-template)
68 | (isSettingApplied (finalPackagesOf name).haskell-flake-test)
69 | ])
70 | "defaults.settings: ${name} failed";
71 | };
72 | test-default-all = { name, ... }: {
73 | patches = [ ];
74 | extraHaskellProjectConfig = {
75 | imports = [
76 | inputs.haskell-template.haskellFlakeProjectModules.output
77 | ];
78 | settings.random = { };
79 | defaults.settings.all = {
80 | custom = pkg: pkg.overrideAttrs (oldAttrs: {
81 | meta = oldAttrs.meta // {
82 | setting-applied = true;
83 | };
84 | });
85 | };
86 | };
87 | expect =
88 | lib.assertMsg
89 | (lib.all (x: x) [
90 | (isSettingApplied (finalPackagesOf name).random)
91 | (isSettingApplied (finalPackagesOf name).haskell-template)
92 | (isSettingApplied (finalPackagesOf name).haskell-flake-test)
93 | ])
94 | "defaults.settings: ${name} failed";
95 | };
96 | };
97 | };
98 | };
99 | }
100 |
--------------------------------------------------------------------------------
/doc/settings.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: -9
3 | ---
4 |
5 | # Package Settings
6 |
7 | Settings for individual Haskell packages can be specified in the `settings` attribute of a `haskellProjects` module.
8 |
9 | ```nix
10 | haskellProjects.default = {
11 | settings = {
12 | ema = { # This module can take `{self, super, ...}` args, optionally.
13 | # Disable running tests
14 | check = false;
15 |
16 | # Disable building haddock (documentation)
17 | haddock = false;
18 |
19 | # Ignore Cabal version constraints
20 | jailbreak = true;
21 |
22 | # Extra non-Haskell dependencies
23 | extraBuildDepends = [ pkgs.stork ];
24 |
25 | # Library-specific dependencies
26 | librarySystemDepends = [ pkgs.zlib ];
27 | libraryPkgconfigDepends = [ pkgs.openssl ];
28 |
29 | # Source patches
30 | patches = [ ./patches/ema-bug-fix.patch ];
31 |
32 | # Enable/disable Cabal flags
33 | cabalFlags.with-generics = true;
34 |
35 | # Allow building a package marked as "broken"
36 | broken = false;
37 | };
38 | };
39 | };
40 | ```
41 |
42 | > [!info] Note
43 | >
44 | > ### [nixpkgs] functions
45 | >
46 | > - The `pkgs.haskell.lib` module provides various utility functions that you can use to override Haskell packages. The canonical place to find documentation on these is [the source](https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/haskell-modules/lib/compose.nix). haskell-flake provides a `settings` submodule for convenience. For eg., the `dontCheck` function translates to `settings..check`; the full list of options can be seen [here](https://github.com/srid/haskell-flake/blob/master/nix/modules/project/settings/all.nix).
47 |
48 | ## Sharing package settings {#share}
49 |
50 | [[modules]] export both `packages` and `settings` options for reuse in downstream Haskell projects.
51 |
52 | ## Custom settings {#custom}
53 |
54 | You can provide custom settings for use in multiple packages (even across multiple repos). For example, see [this Emanote change](https://github.com/srid/emanote/commit/5b24bd04f94e03afe66ee01da723e4a05d854953) which demonstrates how to add a _new_ setting option (`removeReferencesTo`).
55 |
56 | ## Extra settings {#extra}
57 |
58 | haskell-flake provides the following settings on top of those provided by [nixpkgs].
59 |
60 | ### `drvAttrs`
61 |
62 | Set arbitrary attributes on the Haskell package derivation. This is a simpler alternative to the `custom` option for setting [derivation](https://nixos.asia/en/drv) attributes like environment variables.
63 |
64 | ```nix
65 | settings = {
66 | foo = {
67 | drvAttrs = {
68 | GIT_BIN = lib.getExe' pkgs.git "git";
69 | SOME_ENV_VAR = "value";
70 | };
71 | };
72 | };
73 | ```
74 |
75 | [Unlike](https://github.com/srid/haskell-flake/discussions/430) `custom`, the `drvAttrs` option composes well with other settings and doesn't require writing a function.
76 |
77 | ### `generateOptparseApplicativeCompletions`
78 |
79 | Generate and install shell completion files for executables.
80 | The executables need to be using `optparse-applicative` for this to work.
81 | Note that this feature is automatically disabled when cross-compiling, since it requires executing the binaries in question.
82 |
83 | ### `removeReferencesTo`
84 |
85 | Remove references to other packages from this Haskell package. This is useful to eliminate unnecessary data dependencies from your Haskell executable so as to reduce its closure size.
86 |
87 | > [!info] For more, see
88 | >
89 | > - https://github.com/NixOS/nixpkgs/pull/204675
90 | > - https://srid.ca/remove-references-to
91 |
92 | ### `buildFromSdist`
93 |
94 | Newer versions of [nixpkgs] provide `buildFromSdist` to build your package from the `cabal sdist` tarball. This is enabled by default, to help with checking release-worthiness of your packages.
95 |
96 | > [!warning] Issues with `buildFromSdist`
97 | > If you encounter issues with `buildFromSdist` you can disable it by setting `settings..buildFromSdist` to `true`.
98 |
99 | [nixpkgs]: https://nixos.asia/en/nixpkgs
100 |
101 | ### `stan`
102 |
103 | Run **ST**atic **AN**alysis on the package using [stan] and generate an HTML report. The report is created in the `/nix/store` path alongside your package outputs.
104 |
105 | > [!note] stan configuration
106 | > This setting looks for a `.stan.toml` file in the root of the package source. See a sample [.stan.toml] configuration for reference.
107 |
108 | [stan]: https://github.com/kowainik/stan
109 | [.stan.toml]: https://github.com/kowainik/stan/blob/main/.stan.toml
110 |
--------------------------------------------------------------------------------
/test/with-subdir/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | # Since there is no flake.lock file (to avoid incongruent haskell-flake
3 | # pinning), we must specify revisions for *all* inputs to ensure
4 | # reproducibility.
5 | inputs = {
6 | nixpkgs.url = "github:nixos/nixpkgs/870493f9a8cb0b074ae5b411b2f232015db19a65";
7 | flake-parts.url = "github:hercules-ci/flake-parts/758cf7296bee11f1706a574c77d072b8a7baa881";
8 | haskell-flake = { };
9 | };
10 | outputs = inputs@{ self, nixpkgs, flake-parts, ... }:
11 | flake-parts.lib.mkFlake { inherit inputs; } {
12 | systems = nixpkgs.lib.systems.flakeExposed;
13 | imports = [
14 | inputs.haskell-flake.flakeModule
15 | ];
16 | debug = true;
17 | perSystem = { config, self', pkgs, lib, ... }: {
18 | haskellProjects.default = { };
19 |
20 | # Test the default project by patching and evaluating the result.
21 | haskellProjectTests =
22 | let
23 | pkgOf = projectName: config.haskellProjects.${projectName}.outputs.finalPackages.haskell-flake-test;
24 | drvHash = drv: drv.drvPath;
25 | deriverHash = drv: drv.cabal2nixDeriver.drvPath;
26 | in
27 | {
28 | # Eval is constant under changes to irrelevant files
29 | touch-cabal-project = { name, ... }: {
30 | patches = [
31 | ''
32 | diff --git a/cabal.project b/cabal.project
33 | index 1a862c3..92dd52b 100644
34 | --- a/cabal.project
35 | +++ b/cabal.project
36 | @@ -1,2 +1,4 @@
37 | packages:
38 | - ./haskell-flake-test
39 | \ No newline at end of file
40 | + ./haskell-flake-test
41 | +-- irrelevant comment
42 | +
43 | ''
44 | ];
45 | expect =
46 | lib.assertMsg
47 | (lib.all (x: x) [
48 | (drvHash (pkgOf "default") == drvHash (pkgOf name))
49 | (deriverHash (pkgOf "default") == deriverHash (pkgOf name))
50 | ])
51 | "touch-cabal-project failed";
52 | };
53 |
54 | # A relevant change to Haskell source causes a .drv change (control check)
55 | # But no cabal2nix re-eval
56 | touch-src = { name, ... }: {
57 | patches = [
58 | ''
59 | diff --git a/haskell-flake-test/src/Main.hs b/haskell-flake-test/src/Main.hs
60 | index fa10095..293744c 100644
61 | --- a/haskell-flake-test/src/Main.hs
62 | +++ b/haskell-flake-test/src/Main.hs
63 | @@ -3,3 +3,5 @@ module Main where
64 | main :: IO ()
65 | main = do
66 | putStrLn "Hello"
67 | +-- irrelevant comment
68 | +
69 |
70 | ''
71 | ];
72 | expect =
73 | lib.assertMsg
74 | (lib.all (x: x) [
75 | (drvHash (pkgOf "default") != drvHash (pkgOf name))
76 | (deriverHash (pkgOf "default") == deriverHash (pkgOf name))
77 | ])
78 | "touch-src failed";
79 | };
80 |
81 | # A relevant change to .cabal file causes cabal2nix re-eval
82 | touch-cabal = { name, ... }: {
83 | patches = [
84 | ''
85 | diff --git a/haskell-flake-test/haskell-flake-test.cabal b/haskell-flake-test/haskell-flake-test.cabal
86 | index 950a0ff..ef3131b 100644
87 | --- a/haskell-flake-test/haskell-flake-test.cabal
88 | +++ b/haskell-flake-test/haskell-flake-test.cabal
89 | @@ -16,3 +16,5 @@ executable haskell-flake-test
90 | base
91 | hs-source-dirs: src
92 | default-language: Haskell2010
93 | +-- irrelevant comment
94 | +
95 | ''
96 | ];
97 | expect =
98 | lib.assertMsg
99 | (lib.all (x: x) [
100 | (drvHash (pkgOf "default") != drvHash (pkgOf name))
101 | (deriverHash (pkgOf "default") != deriverHash (pkgOf name))
102 | ])
103 | "touch-src failed";
104 | };
105 | };
106 | };
107 | };
108 | }
109 |
--------------------------------------------------------------------------------
/nix/modules/project/devshell.nix:
--------------------------------------------------------------------------------
1 | # Definition of the `haskellProjects.${name}` submodule's `config`
2 | { config, lib, ... }:
3 | let
4 | inherit (lib)
5 | mkOption
6 | types;
7 | inherit (types)
8 | functionTo;
9 |
10 | devShellSubmodule = types.submodule {
11 | options = {
12 | enable = mkOption {
13 | type = types.bool;
14 | description = ''
15 | Whether to enable a development shell for the project.
16 | '';
17 | default = true;
18 | };
19 | tools = mkOption {
20 | type = functionTo (types.lazyAttrsOf (types.nullOr types.package));
21 | description = ''
22 | Build tools for developing the Haskell project.
23 |
24 | These tools are merged with the haskell-flake defaults defined in the
25 | `defaults.devShell.tools` option. Set the value to `null` to remove
26 | that default tool.
27 | '';
28 | default = hp: { };
29 | };
30 | extraLibraries = mkOption {
31 | type = types.nullOr (functionTo (types.lazyAttrsOf (types.nullOr types.package)));
32 | description = ''
33 | Extra Haskell libraries available in the shell's environment.
34 | These can be used in the shell's `runghc` and `ghci` for instance.
35 |
36 | The argument is the Haskell package set.
37 |
38 | The return type is an attribute set for overridability and syntax, as only the values are used.
39 | '';
40 | default = null;
41 | defaultText = lib.literalExpression "null";
42 | example = lib.literalExpression "hp: { inherit (hp) releaser; }";
43 | };
44 |
45 | mkShellArgs = mkOption {
46 | type = types.lazyAttrsOf types.raw;
47 | description = ''
48 | Extra arguments to pass to `pkgs.mkShell`.
49 | '';
50 | default = { };
51 | example = ''
52 | {
53 | shellHook = \'\'
54 | # Re-generate .cabal files so HLS will work (per hie.yaml)
55 | ''${pkgs.findutils}/bin/find -name package.yaml -exec hpack {} \;
56 | \'\';
57 | };
58 | '';
59 | };
60 |
61 | benchmark = mkOption {
62 | type = types.bool;
63 | description = ''
64 | Whether to include benchmark dependencies in the development shell.
65 | The value of this option will set the corresponding `doBenchmark` flag in the
66 | `shellFor` derivation.
67 | '';
68 | default = false;
69 | };
70 |
71 | hoogle = mkOption {
72 | type = types.bool;
73 | default = true;
74 | description = ''
75 | Whether to include Hoogle in the development shell.
76 | The value of this option will set the corresponding `withHoogle` flag in the
77 | `shellFor` derivation.
78 | '';
79 | };
80 | };
81 | };
82 | in
83 | {
84 | imports = [
85 | ./hls-check.nix
86 | ];
87 | options = {
88 | devShell = mkOption {
89 | type = devShellSubmodule;
90 | description = ''
91 | Development shell configuration
92 | '';
93 | default = { };
94 | };
95 | outputs.devShell = mkOption {
96 | type = types.package;
97 | readOnly = true;
98 | description = ''
99 | The development shell derivation generated for this project.
100 | '';
101 | };
102 | };
103 | config =
104 | let
105 | inherit (config.outputs) finalPackages;
106 |
107 | nativeBuildInputs = lib.attrValues (
108 | config.defaults.devShell.tools finalPackages //
109 | config.devShell.tools finalPackages
110 | );
111 | mkShellArgs = config.devShell.mkShellArgs // {
112 | nativeBuildInputs = (config.devShell.mkShellArgs.nativeBuildInputs or [ ]) ++ nativeBuildInputs;
113 | };
114 | devShell = finalPackages.shellFor (mkShellArgs // {
115 | packages = p:
116 | let
117 | localPackages = (lib.filterAttrs (k: v: v.local.toCurrentProject) config.packages);
118 | in
119 | map
120 | (name: p."${name}")
121 | (lib.attrNames localPackages);
122 | withHoogle = config.devShell.hoogle;
123 | doBenchmark = config.devShell.benchmark;
124 | extraDependencies = let hasExtraLibraries = config.devShell.extraLibraries != null; in
125 | if lib.hasAttr "extraDependencies" (lib.functionArgs finalPackages.shellFor) then
126 | p:
127 | let o = mkShellArgs.extraDependencies or (_: { }) p;
128 | in o // {
129 | libraryHaskellDepends = o.libraryHaskellDepends or [ ]
130 | ++ builtins.attrValues (if hasExtraLibraries then config.devShell.extraLibraries p else { });
131 | }
132 | else
133 | if hasExtraLibraries then
134 | builtins.throw "The 'extraLibraries' option is only available when using a version of nixpkgs that supports extraDependencies in shellFor."
135 | else
136 | null;
137 | });
138 |
139 | in
140 | {
141 | outputs = {
142 | inherit devShell;
143 | };
144 | };
145 | }
146 |
--------------------------------------------------------------------------------
/test/simple/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | # Since there is no flake.lock file (to avoid incongruent haskell-flake
3 | # pinning), we must specify revisions for *all* inputs to ensure
4 | # reproducibility.
5 | inputs = {
6 | nixpkgs.url = "github:nixos/nixpkgs/870493f9a8cb0b074ae5b411b2f232015db19a65";
7 | flake-parts.url = "github:hercules-ci/flake-parts/758cf7296bee11f1706a574c77d072b8a7baa881";
8 | haskell-flake = { };
9 |
10 | haskell-multi-nix.url = "github:srid/haskell-multi-nix/7aed736571714ec12105ec110358998d70d59e34";
11 | haskell-multi-nix.flake = false;
12 | };
13 | outputs = inputs@{ self, nixpkgs, flake-parts, ... }:
14 | flake-parts.lib.mkFlake { inherit inputs; } {
15 | systems = nixpkgs.lib.systems.flakeExposed;
16 | imports = [
17 | inputs.haskell-flake.flakeModule
18 | ];
19 | flake.haskellFlakeProjectModules.default = { pkgs, lib, ... }: {
20 | packages = {
21 | # This is purposefully incorrect (pointing to ./.) because we
22 | # expect it to be overriden in perSystem below.
23 | foo.source = ./.;
24 | };
25 | settings = {
26 | # Test that self and super are passed
27 | foo = { self, super, ... }: {
28 | custom = _: builtins.seq
29 | (lib.assertMsg (lib.hasAttr "ghc" self) "self is bad")
30 | super.foo;
31 | };
32 | };
33 | devShell = {
34 | tools = hp: {
35 | # Setting to null should remove this tool from defaults.
36 | ghcid = null;
37 | };
38 | };
39 | };
40 | perSystem = { config, self', pkgs, lib, ... }: {
41 | haskellProjects.default = {
42 | # Multiple modules should be merged correctly.
43 | imports = [ self.haskellFlakeProjectModules.default ];
44 | packages = {
45 | # Because the module being imported above also defines a root for
46 | # the 'foo' package, we must override it here using `lib.mkForce`.
47 | foo.source = lib.mkForce (inputs.haskell-multi-nix + /foo);
48 | };
49 | settings = {
50 | foo = {
51 | jailbreak = true;
52 | cabalFlags.blah = true;
53 | # Test drvAttrs option
54 | drvAttrs = {
55 | TEST_RAW_ATTR = "test-value";
56 | };
57 | };
58 | haskell-flake-test = {
59 | # Test STatic ANalysis report generation
60 | stan = true;
61 | # Test if user's setting overrides the `jailbreak = false;` override by `buildFromSdist`.
62 | #
63 | # This jailbreak ignores the unsatisfiable version constraints on the library `foo`.
64 | jailbreak = true;
65 | };
66 | };
67 | devShell = {
68 | tools = hp: {
69 | # Adding a tool should make it available in devshell.
70 | inherit (pkgs) fzf;
71 | };
72 | extraLibraries = hp: {
73 | inherit (hp) tomland;
74 | };
75 | mkShellArgs.shellHook = ''
76 | echo "Hello from devshell!"
77 | export FOO=bar
78 | '';
79 | };
80 | };
81 | packages.default = self'.packages.haskell-flake-test;
82 |
83 | # An explicit app to test `nix run .#test` (*without* falling back to
84 | # using self.packages.test)
85 | apps.app1 = self'.apps.haskell-flake-test;
86 |
87 | # Our test
88 | checks.test =
89 | pkgs.runCommandNoCC "simple-test"
90 | {
91 | nativeBuildInputs = with pkgs; [
92 | which
93 | ] ++ self'.devShells.default.nativeBuildInputs;
94 |
95 | # Test defaults.settings module behaviour, viz: haddock
96 | NO_HADDOCK =
97 | lib.assertMsg (!lib.hasAttr "doc" self'.packages.default)
98 | "doc output should not be present";
99 |
100 | # Test drvAttrs option - verify that the TEST_RAW_ATTR is applied
101 | TEST_RAW_ATTR =
102 | lib.assertMsg (config.haskellProjects.default.outputs.finalPackages.foo.TEST_RAW_ATTR == "test-value")
103 | "drvAttrs option should apply TEST_RAW_ATTR attribute";
104 | }
105 | ''
106 | (
107 | set -x
108 | echo "Testing test/simple ..."
109 |
110 | # Run the cabal executable as flake app
111 | ${self'.apps.app1.program} | grep fooFunc
112 |
113 | # Setting buildTools.ghcid to null should disable that default
114 | # buildTool (ghcid)
115 | which ghcid && \
116 | (echo "ghcid should not be in devshell"; exit 2)
117 |
118 | # Adding a buildTool (fzf, here) should put it in devshell.
119 | which fzf || \
120 | (echo "fzf should be in devshell"; exit 2)
121 |
122 | # mkShellArgs works
123 | ${self'.devShells.default.shellHook}
124 | if [[ "$FOO" == "bar" ]]; then
125 | echo "$FOO"
126 | else
127 | echo "FOO is not bar"
128 | exit 2
129 | fi
130 |
131 | # extraLibraries works
132 | runghc ${./script} | grep -F 'TOML-flavored boolean: Bool True'
133 |
134 | touch $out
135 | )
136 | '';
137 | };
138 | };
139 | }
140 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Revision history for haskell-flake
2 |
3 | ## Unreleased
4 |
5 | - Enhancements
6 | - #382: Support for cabal2nix generated expressions (avoiding IFD for local packages)
7 | - #418: Add `extraCabal2nixOptions` and `cabalFlags` to `packages.*`. The former passes custom options to `cabal2nix`. The later does the same, but formatting the flag options appropriately.
8 | - #356: Support `meta.description` in flake apps. Requires newer version of flake-parts.
9 | - `settings` module:
10 | - #384: Add `stan`
11 | - #419, #421: Add `installIntermediates`, `previousIntermediates` and `separateIntermediatesOutput`
12 | - #427: Add `generateOptparseApplicativeCompletions`
13 | - #455: Add `drvAttrs` option for setting arbitrary derivation attributes
14 | - #459: Apply `custom` ahead of other settings.
15 | - #469: Add `libraryHaskellDepends`, `librarySystemDepends`, `libraryToolDepends`, and `libraryPkgconfigDepends`
16 |
17 | ## 0.5.0 (Jun 24, 2024)
18 |
19 | - Breaking changes
20 | - #221: Switch from `buildFromCabalSdist` to `buildFromSdist`, to allow using non-standard package sets (wherein `cabal-install` is otherwise built without using user's overrides)
21 | - #253: Enable controlling `buildFromSdist` through `settings..buildFromSdist`. (This was turned off by default originally, but was turned on by default in #286, limited to packages defined by haskell-flake in #298 & #306)
22 | - Enhancements
23 | - `settings` module:
24 | - #210: Add `extraLibraries` to `settings` module.
25 | - #225: Add `removeReferencesTo` to `settings` module.
26 | - #275: Fine grained settings defaults via `defaults.settings.{local, defined, all}`
27 | - #277: Add `otherOverlays` option to add custom Haskell package overlays.
28 | - #215: Improved debug logging.
29 | - #216: Remove `debug` option (pass `--trace-verbose` to nix instead)
30 | - Fixes
31 | - #222: Improve `cabal.project` parser by handling files not ending with newline
32 | - #271, #223: Change all `types.attrsOf` to `types.lazyAttrsOf`. If you use `lib.mkIf` for `attrsOf` values (not `submodule` options), use `lib.optionalAttrs` instead. This fixes #270 (`basePackages`) and improves performance.
33 | - #296: Fix `removeConfigureFlags` to actually work.
34 | - #297: Fix `brokenVersions` to actually work.
35 |
36 | ## 0.4.0 (Aug 22, 2023)
37 |
38 | - #162: **Completely new way to override Haskell packages**: removed `overrides` and `source-overrides`. Use `packages` to specify your source overrides; use `settings` to override individual packages in modular fashion (like NixOS modules). Additional changes include:
39 | - Add `package..cabal.executables` referring to the executables in a package. This is auto-detected by parsing the Cabal file.
40 | - Add `packages..local.*` to determine of a package is a local package or not.
41 | - Add `projectFlakeName` option (useful in debug logging prefix)
42 | - `flake.haskellFlakeProjectModules`: Dropped all defaults, except the `output` module, which now exports `packages` and `settings`. Added a `defaults.projectModules.output` option that allows the user to override this module, or directly access the generated module.
43 | - Add `project.config.defaults.settings.default` defining sensible defaults for local packages.
44 | - Add `project.config.defaults.enable` to turn off all default settings en masse.
45 | - Regressions in this PR: #169, #178
46 | - #175: `devShell`: Add options `benchmark` (to enable benchmark dependencies) and `hoogle` (whether to include Hoogle in development shell)
47 | - #189: Use `types.str` instead of `types.string` in option types
48 |
49 | ## 0.3.0 (May 22, 2023)
50 |
51 | - #134: Add `autoWire` option to control generation of flake outputs
52 | - #138: Add `checks` to `outputs` submodule
53 | - #143: Changed `autoWire` to be an enum type, for granular controlling of which outputs to autowire.
54 | - #137: Expose cabal executables as flake apps. Add a corresponding `outputs.apps` option, while the `outputs.localPackages` option is renamed to `outputs.packages` (it now contains package metadata, including packages and its executables).
55 | - #151: Use `lib.getBin` to get the bin output
56 | - #148: Remove automatic hpack->cabal generation. Use `pre-commit-hooks.nix` instead.
57 | - #149: Fix unnecessary re-runs of cabal2nix evaluation. Add a `debug` option to have haskell-flake produce diagnostic messages.
58 | - #153: Add `config.defaults` submodule to allow overriding the default devShell tools added by haskell-flake
59 |
60 | ## 0.2.0 (Mar 13, 2023)
61 |
62 | - New features
63 | - #68, #79, #106: Add support for project modules that can be imported in `imports`. Export them in `flake.haskellFlakeProjectModules`. Default modules are exported by default, to reuse overrides and local packages from external flakes. For details, see https://haskell.flake.page/modules
64 | - #67: `overrides` will be combined using `composeManyExtensions`, however their order is arbitrary. This is an experimental feature, and a warning will be logged.
65 | - Dev shell
66 | - #37: Devshell can now be disabled using `devShell.enable = false;` (useful if you want haskell-flake to produce just the package outputs)
67 | - #92: Add `devShell.mkShellArgs` to pass custom arguments to `mkShell`
68 | - #111: Add `devShell.extraLibraries` to add custom Haskell libraries to the devshell.
69 | - #63, #52: Add `config.haskellProjects.${name}.outputs` containing all flake outputs for that project; as well as (#102) `finalPackages` and `localPackages`.
70 | - #49 & #91 & #110: The default value for the `packages` option is now determined from the `cabal.project` file. If it doesn't exist, it looks for top-level `.cabal` file or `package.yaml`. Better hpack support throughout.
71 | - #100: `source-overrides` option now supports specifying Hackage versions as string.
72 | - #114: Prevent unnecessary Nix rebuilds of packages in sub-directories when parent contents change.
73 | - API changes
74 | - #37: Group `buildTools` (renamed to `tools`), `hlsCheck` and `hlintCheck` under the new `devShell` submodule option
75 | - #64: Remove hlintCheck (use [treefmt-nix](https://github.com/numtide/treefmt-nix#flake-parts) instead)
76 | - #52: Rename `haskellPackages` to `basePackages`. Overlays are applied on top of `basePackage` -- using `source-overrides`, `overrides`, `packages` in that order -- to produce `finalPackages`.
77 | - #69: The default flake template creates `flake.nix` only, while the `#example` one creates the full Haskell project template.
78 |
79 | ## 0.1.0 (Feb 1, 2023)
80 |
81 | - Initial release
82 |
--------------------------------------------------------------------------------
/nix/modules/project/defaults.nix:
--------------------------------------------------------------------------------
1 | # A module representing the default values used internally by haskell-flake.
2 | { name, lib, pkgs, config, ... }:
3 | let
4 | inherit (lib)
5 | mkOption
6 | types;
7 | inherit (types)
8 | functionTo;
9 | in
10 | {
11 | options.defaults = {
12 | enable = mkOption {
13 | type = types.bool;
14 | description = ''
15 | Whether to enable haskell-flake's default settings for this project.
16 | '';
17 | default = true;
18 | };
19 |
20 | devShell.tools = mkOption {
21 | type = functionTo (types.lazyAttrsOf (types.nullOr types.package));
22 | description = ''Build tools always included in devShell'';
23 | default = hp: with hp; lib.optionalAttrs config.defaults.enable {
24 | inherit
25 | cabal-install
26 | haskell-language-server
27 | ghcid
28 | hlint;
29 | };
30 | };
31 |
32 | packages = mkOption {
33 | type = types.lazyAttrsOf types.deferredModule;
34 | description = ''Local packages scanned from projectRoot'';
35 | default =
36 | let
37 | haskell-parsers = import ../../haskell-parsers {
38 | inherit pkgs lib;
39 | throwError = msg: config.log.throwError ''
40 |
41 | A default value for `packages` cannot be auto-determined:
42 |
43 | ${msg}
44 | '';
45 | throwErrorOnCabalProjectParseError = msg: config.log.throwError ''
46 |
47 | A default value for `packages` cannot be auto-determined:
48 |
49 | ${msg}
50 |
51 | haskell-flake's `cabal.project` parser is limited; see #307.
52 | Please specify the `packages` option manually or modify your
53 | cabal.project file for acceptance by haskell-flake.
54 | '';
55 | };
56 | localPackages = lib.pipe config.projectRoot [
57 | haskell-parsers.findPackagesInCabalProject
58 | (x: config.log.traceDebug "${name}.findPackagesInCabalProject = ${builtins.toJSON x}" x)
59 | (lib.mapAttrs (_: path: {
60 | # The rest of the module options are not defined, because we'll use
61 | # the submodule defaults.
62 | source = path;
63 | }))
64 | ];
65 | in
66 | lib.optionalAttrs config.defaults.enable localPackages;
67 | apply = x:
68 | config.log.traceDebug "defaults.packages = ${builtins.toJSON x}" x;
69 | defaultText = lib.literalMD ''
70 | If you have a `cabal.project` file (under `projectRoot`), those packages
71 | are automatically discovered. Otherwise, the top-level .cabal file is
72 | used to discover the only local package.
73 |
74 | haskell-flake currently supports a limited range of syntax for
75 | `cabal.project`. Specifically it requires an explicit list of package
76 | directories under the "packages" option.
77 | '';
78 | };
79 |
80 | settings.local = mkOption {
81 | type = types.deferredModule;
82 | description = ''
83 | Default settings for packages local to the current project.
84 | '';
85 | apply = settings:
86 | if config.defaults.enable then
87 | { package, ... }:
88 | lib.optionalAttrs (package.local.toCurrentProject or false) {
89 | imports = [
90 | settings
91 | ];
92 | }
93 | else { };
94 | default = { };
95 | };
96 |
97 | settings.defined = mkOption {
98 | type = types.deferredModule;
99 | description = ''
100 | Default settings for all the packages defined using haskell-flake.
101 |
102 | For example,
103 | ```nix
104 | {
105 | # Inside haskellProjects.
106 | imports = [
107 | inputs.moo.haskellFlakeProjectModules.output
108 | ];
109 | packages = {
110 | foo.source = "0.1";
111 | bar.source = inputs.bar;
112 | };
113 | settings = {
114 | baz.check = false;
115 | };
116 | }
117 | ```
118 | and
119 | ```cabal
120 | ...
121 | build-depends:
122 | moo
123 | , foo
124 | , bar
125 | , baz
126 | , qux
127 | ...
128 | ```
129 | This will apply the settings to `moo` and packages in current project. But not to `foo`, `bar`, `baz` and `qux`.
130 | '';
131 |
132 | apply = settings:
133 | if config.defaults.enable then
134 | { package, ... }:
135 | lib.optionalAttrs (package.local.toDefinedProject or false) {
136 | imports = [
137 | settings
138 | ];
139 | }
140 | else { };
141 |
142 | defaultText = ''
143 | Speed up builds by disabling haddock and library profiling. Also ensures
144 | release-worthiness.
145 |
146 | This uses `local.toDefinedProject` option to determine which packages to
147 | override. Thus, it applies to both local packages as well as
148 | transitively imported packags that are local to that flake (managed by
149 | haskell-flake). The goal being to use the same configuration
150 | consistently for all packages using haskell-flake.
151 | '';
152 |
153 | default = {
154 | # Disabling haddock and profiling is mainly to speed up Nix builds.
155 | haddock = lib.mkDefault false; # Because, this is end-user software. No need for library docs.
156 | libraryProfiling = lib.mkDefault false; # Avoid double-compilation.
157 | buildFromSdist = lib.mkDefault true; # Ensure release-worthiness
158 | };
159 | };
160 |
161 | settings.all = mkOption {
162 | type = types.deferredModule;
163 | description = ''
164 | Default settings for all packages whose derivations are produced by haskell-flake.
165 |
166 | For example,
167 | ```nix
168 | {
169 | # Inside haskellProjects.
170 | imports = [
171 | inputs.moo.haskellFlakeProjectModules.output
172 | ];
173 | packages = {
174 | foo.source = "0.1";
175 | bar.source = inputs.bar;
176 | };
177 | settings = {
178 | baz.check = false;
179 | };
180 | }
181 | ```
182 | and
183 | ```cabal
184 | ...
185 | build-depends:
186 | moo
187 | , foo
188 | , bar
189 | , baz
190 | , qux
191 | ...
192 | ```
193 | This will apply the settings to `moo`, `foo`, `bar`, `baz`. But not to `qux`.
194 | '';
195 |
196 | apply = settings:
197 | if config.defaults.enable then
198 | {
199 | imports = [
200 | settings
201 | ];
202 | }
203 | else { };
204 | defaultText = ''
205 | Nothing is changed by default.
206 | '';
207 | default = { };
208 | };
209 |
210 | projectModules.output = mkOption {
211 | type = types.deferredModule;
212 | description = ''
213 | A haskell-flake project module that exports the `packages` and
214 | `settings` options to the consuming flake. This enables the use of this
215 | flake's Haskell package as a dependency, re-using its overrides.
216 | '';
217 | default = lib.optionalAttrs config.defaults.enable {
218 | inherit (config)
219 | packages settings;
220 | };
221 | defaultText = lib.literalMD ''a generated module'';
222 | };
223 | };
224 | }
225 |
--------------------------------------------------------------------------------
/doc/flake.lock:
--------------------------------------------------------------------------------
1 | {
2 | "nodes": {
3 | "cfp": {
4 | "inputs": {
5 | "emanote": "emanote",
6 | "flake-parts": [
7 | "cfp",
8 | "emanote",
9 | "flake-parts"
10 | ],
11 | "haskell-flake": "haskell-flake_2",
12 | "hercules-ci-effects": "hercules-ci-effects",
13 | "mission-control": "mission-control",
14 | "nixpkgs": [
15 | "cfp",
16 | "emanote",
17 | "nixpkgs"
18 | ],
19 | "process-compose-flake": "process-compose-flake",
20 | "services-flake": "services-flake"
21 | },
22 | "locked": {
23 | "lastModified": 1732824556,
24 | "narHash": "sha256-H6+TAGdrE0rtpQ/rM/e2d26XA1QFJzT40aijYsbznmE=",
25 | "owner": "flake-parts",
26 | "repo": "community.flake.parts",
27 | "rev": "2ec00db8bbc45193ba4a49c36878cdb28dd97ce3",
28 | "type": "github"
29 | },
30 | "original": {
31 | "owner": "flake-parts",
32 | "repo": "community.flake.parts",
33 | "type": "github"
34 | }
35 | },
36 | "commonmark-simple": {
37 | "flake": false,
38 | "locked": {
39 | "lastModified": 1707333942,
40 | "narHash": "sha256-o1am93UXviPVdwwPPL5DcD8M4gwFple8SKw/lVmixa8=",
41 | "owner": "srid",
42 | "repo": "commonmark-simple",
43 | "rev": "0308362957d77eea462c2c99d110820fbf30b4b8",
44 | "type": "github"
45 | },
46 | "original": {
47 | "owner": "srid",
48 | "repo": "commonmark-simple",
49 | "type": "github"
50 | }
51 | },
52 | "commonmark-wikilink": {
53 | "flake": false,
54 | "locked": {
55 | "lastModified": 1711394028,
56 | "narHash": "sha256-eu5gMmgRz6Y51TBCaB26uJKNN3z1LRfUcTV4+PMy5Gw=",
57 | "owner": "srid",
58 | "repo": "commonmark-wikilink",
59 | "rev": "57dcf665082ffc1b6f35a427e203ed115821b15c",
60 | "type": "github"
61 | },
62 | "original": {
63 | "owner": "srid",
64 | "repo": "commonmark-wikilink",
65 | "type": "github"
66 | }
67 | },
68 | "ema": {
69 | "flake": false,
70 | "locked": {
71 | "lastModified": 1724260267,
72 | "narHash": "sha256-pxTlvpK0l7pek43FIz6KYAazK0BWbnuBJSFrcShVoWE=",
73 | "owner": "srid",
74 | "repo": "ema",
75 | "rev": "16e2752267cd49027e281409daa25b6ecba68fd3",
76 | "type": "github"
77 | },
78 | "original": {
79 | "owner": "srid",
80 | "repo": "ema",
81 | "type": "github"
82 | }
83 | },
84 | "emanote": {
85 | "inputs": {
86 | "commonmark-simple": "commonmark-simple",
87 | "commonmark-wikilink": "commonmark-wikilink",
88 | "ema": "ema",
89 | "emanote-template": "emanote-template",
90 | "flake-parts": "flake-parts",
91 | "flake-root": "flake-root",
92 | "haskell-flake": "haskell-flake",
93 | "heist-extra": "heist-extra",
94 | "nixos-unified": "nixos-unified",
95 | "nixpkgs": "nixpkgs",
96 | "treefmt-nix": "treefmt-nix",
97 | "unionmount": "unionmount"
98 | },
99 | "locked": {
100 | "lastModified": 1731365623,
101 | "narHash": "sha256-MdS/72MESb1Bp1i7vcGQ/WejBdvg1QL8F4j5fjMUHbE=",
102 | "owner": "srid",
103 | "repo": "emanote",
104 | "rev": "d165ad189dbf71bd66b0c7f211c45c5431499540",
105 | "type": "github"
106 | },
107 | "original": {
108 | "owner": "srid",
109 | "repo": "emanote",
110 | "type": "github"
111 | }
112 | },
113 | "emanote-template": {
114 | "flake": false,
115 | "locked": {
116 | "lastModified": 1711847690,
117 | "narHash": "sha256-A/5b7vB1+FI2qsuPJL/pZ9CkWozSCbOoaqqN4y+Pmxc=",
118 | "owner": "srid",
119 | "repo": "emanote-template",
120 | "rev": "32330b5e3bdca89ca67f5c212be6db43dbb13cd8",
121 | "type": "github"
122 | },
123 | "original": {
124 | "owner": "srid",
125 | "repo": "emanote-template",
126 | "type": "github"
127 | }
128 | },
129 | "flake-parts": {
130 | "inputs": {
131 | "nixpkgs-lib": [
132 | "cfp",
133 | "emanote",
134 | "nixpkgs"
135 | ]
136 | },
137 | "locked": {
138 | "lastModified": 1727826117,
139 | "narHash": "sha256-K5ZLCyfO/Zj9mPFldf3iwS6oZStJcU4tSpiXTMYaaL0=",
140 | "owner": "hercules-ci",
141 | "repo": "flake-parts",
142 | "rev": "3d04084d54bedc3d6b8b736c70ef449225c361b1",
143 | "type": "github"
144 | },
145 | "original": {
146 | "owner": "hercules-ci",
147 | "repo": "flake-parts",
148 | "type": "github"
149 | }
150 | },
151 | "flake-parts_2": {
152 | "inputs": {
153 | "nixpkgs-lib": [
154 | "cfp",
155 | "hercules-ci-effects",
156 | "nixpkgs"
157 | ]
158 | },
159 | "locked": {
160 | "lastModified": 1712014858,
161 | "narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=",
162 | "owner": "hercules-ci",
163 | "repo": "flake-parts",
164 | "rev": "9126214d0a59633752a136528f5f3b9aa8565b7d",
165 | "type": "github"
166 | },
167 | "original": {
168 | "id": "flake-parts",
169 | "type": "indirect"
170 | }
171 | },
172 | "flake-root": {
173 | "locked": {
174 | "lastModified": 1692742795,
175 | "narHash": "sha256-f+Y0YhVCIJ06LemO+3Xx00lIcqQxSKJHXT/yk1RTKxw=",
176 | "owner": "srid",
177 | "repo": "flake-root",
178 | "rev": "d9a70d9c7a5fd7f3258ccf48da9335e9b47c3937",
179 | "type": "github"
180 | },
181 | "original": {
182 | "owner": "srid",
183 | "repo": "flake-root",
184 | "type": "github"
185 | }
186 | },
187 | "haskell-flake": {
188 | "locked": {
189 | "lastModified": 1725314890,
190 | "narHash": "sha256-jjVrhLOlPjQiZ/8pe+g6Xc5sa563WXnNZDwGXVl4PXQ=",
191 | "owner": "srid",
192 | "repo": "haskell-flake",
193 | "rev": "ed94388c2e622f28cb45108a4e73c9d2b2a796da",
194 | "type": "github"
195 | },
196 | "original": {
197 | "owner": "srid",
198 | "repo": "haskell-flake",
199 | "type": "github"
200 | }
201 | },
202 | "haskell-flake_2": {
203 | "flake": false,
204 | "locked": {
205 | "lastModified": 1732461017,
206 | "narHash": "sha256-FS4ZavcceO2GJOKGDejkJsuEngHb2Ioq+jnORGZzaKE=",
207 | "owner": "srid",
208 | "repo": "haskell-flake",
209 | "rev": "725292998cdb695dc312325603a662afa97b9e86",
210 | "type": "github"
211 | },
212 | "original": {
213 | "owner": "srid",
214 | "repo": "haskell-flake",
215 | "type": "github"
216 | }
217 | },
218 | "haskell-flake_3": {
219 | "locked": {
220 | "lastModified": 1732461017,
221 | "narHash": "sha256-FS4ZavcceO2GJOKGDejkJsuEngHb2Ioq+jnORGZzaKE=",
222 | "owner": "srid",
223 | "repo": "haskell-flake",
224 | "rev": "725292998cdb695dc312325603a662afa97b9e86",
225 | "type": "github"
226 | },
227 | "original": {
228 | "owner": "srid",
229 | "repo": "haskell-flake",
230 | "type": "github"
231 | }
232 | },
233 | "heist-extra": {
234 | "flake": false,
235 | "locked": {
236 | "lastModified": 1710541479,
237 | "narHash": "sha256-9e4U78eutom6D3EJqsCdV8iQxNgYA/pi001r5CZdm0A=",
238 | "owner": "srid",
239 | "repo": "heist-extra",
240 | "rev": "589b7636f620dcdfc0dc07dea720feed1ab3e0fa",
241 | "type": "github"
242 | },
243 | "original": {
244 | "owner": "srid",
245 | "repo": "heist-extra",
246 | "type": "github"
247 | }
248 | },
249 | "hercules-ci-effects": {
250 | "inputs": {
251 | "flake-parts": "flake-parts_2",
252 | "nixpkgs": "nixpkgs_2"
253 | },
254 | "locked": {
255 | "lastModified": 1730903510,
256 | "narHash": "sha256-mnynlrPeiW0nUQ8KGZHb3WyxAxA3Ye/BH8gMjdoKP6E=",
257 | "owner": "hercules-ci",
258 | "repo": "hercules-ci-effects",
259 | "rev": "b89ac4d66d618b915b1f0a408e2775fe3821d141",
260 | "type": "github"
261 | },
262 | "original": {
263 | "owner": "hercules-ci",
264 | "repo": "hercules-ci-effects",
265 | "type": "github"
266 | }
267 | },
268 | "mission-control": {
269 | "flake": false,
270 | "locked": {
271 | "lastModified": 1727581548,
272 | "narHash": "sha256-LDAHv2KECDaf9hf6oFV2dMCqPzEaYFcmeQCOnA+/eh8=",
273 | "owner": "Platonic-Systems",
274 | "repo": "mission-control",
275 | "rev": "781a209a8cdeb9e63a26d19b2e3b75565266db97",
276 | "type": "github"
277 | },
278 | "original": {
279 | "owner": "Platonic-Systems",
280 | "repo": "mission-control",
281 | "type": "github"
282 | }
283 | },
284 | "nixos-unified": {
285 | "locked": {
286 | "lastModified": 1729549045,
287 | "narHash": "sha256-W0VyC1MueUy6zMzcKZ9Ofnz/03da+SPFCYdbQ3MugfM=",
288 | "owner": "srid",
289 | "repo": "nixos-unified",
290 | "rev": "09752a3c33541b7342416fb968c299c03c3e7e39",
291 | "type": "github"
292 | },
293 | "original": {
294 | "owner": "srid",
295 | "repo": "nixos-unified",
296 | "type": "github"
297 | }
298 | },
299 | "nixpkgs": {
300 | "locked": {
301 | "lastModified": 1712883908,
302 | "narHash": "sha256-icE1IJE9fHcbDfJ0+qWoDdcBXUoZCcIJxME4lMHwvSM=",
303 | "owner": "nixos",
304 | "repo": "nixpkgs",
305 | "rev": "a0c9e3aee1000ac2bfb0e5b98c94c946a5d180a9",
306 | "type": "github"
307 | },
308 | "original": {
309 | "owner": "nixos",
310 | "ref": "nixpkgs-unstable",
311 | "repo": "nixpkgs",
312 | "type": "github"
313 | }
314 | },
315 | "nixpkgs_2": {
316 | "locked": {
317 | "lastModified": 1713714899,
318 | "narHash": "sha256-+z/XjO3QJs5rLE5UOf015gdVauVRQd2vZtsFkaXBq2Y=",
319 | "owner": "NixOS",
320 | "repo": "nixpkgs",
321 | "rev": "6143fc5eeb9c4f00163267708e26191d1e918932",
322 | "type": "github"
323 | },
324 | "original": {
325 | "owner": "NixOS",
326 | "ref": "nixos-unstable",
327 | "repo": "nixpkgs",
328 | "type": "github"
329 | }
330 | },
331 | "process-compose-flake": {
332 | "flake": false,
333 | "locked": {
334 | "lastModified": 1731079499,
335 | "narHash": "sha256-ir7WTVpG999N07wkOCs1kwZsQKitOv3CNDqNalCMK3c=",
336 | "owner": "Platonic-Systems",
337 | "repo": "process-compose-flake",
338 | "rev": "018783e68c89f94dbe11ce918d6206e9e4138787",
339 | "type": "github"
340 | },
341 | "original": {
342 | "owner": "Platonic-Systems",
343 | "repo": "process-compose-flake",
344 | "type": "github"
345 | }
346 | },
347 | "root": {
348 | "inputs": {
349 | "cfp": "cfp",
350 | "flake-parts": [
351 | "cfp",
352 | "flake-parts"
353 | ],
354 | "haskell-flake": "haskell-flake_3",
355 | "nixpkgs": [
356 | "cfp",
357 | "nixpkgs"
358 | ]
359 | }
360 | },
361 | "services-flake": {
362 | "flake": false,
363 | "locked": {
364 | "lastModified": 1732821952,
365 | "narHash": "sha256-kAhsXcCwjGTNVnACXPENGNLIHva6Hqg86IHFWd34DME=",
366 | "owner": "juspay",
367 | "repo": "services-flake",
368 | "rev": "394b03d95afb3d7cb9c35c6a6d5e65027a8820a6",
369 | "type": "github"
370 | },
371 | "original": {
372 | "owner": "juspay",
373 | "repo": "services-flake",
374 | "type": "github"
375 | }
376 | },
377 | "treefmt-nix": {
378 | "inputs": {
379 | "nixpkgs": [
380 | "cfp",
381 | "emanote",
382 | "nixpkgs"
383 | ]
384 | },
385 | "locked": {
386 | "lastModified": 1711963903,
387 | "narHash": "sha256-N3QDhoaX+paWXHbEXZapqd1r95mdshxToGowtjtYkGI=",
388 | "owner": "numtide",
389 | "repo": "treefmt-nix",
390 | "rev": "49dc4a92b02b8e68798abd99184f228243b6e3ac",
391 | "type": "github"
392 | },
393 | "original": {
394 | "owner": "numtide",
395 | "repo": "treefmt-nix",
396 | "type": "github"
397 | }
398 | },
399 | "unionmount": {
400 | "flake": false,
401 | "locked": {
402 | "lastModified": 1710078535,
403 | "narHash": "sha256-gKBgBtuiRTD3/3EeY8aMgFzuaSEffJacBxsCB3ct1eg=",
404 | "owner": "srid",
405 | "repo": "unionmount",
406 | "rev": "41ae982fa118770bf4d3a3f2d48ac1ffb61c9f09",
407 | "type": "github"
408 | },
409 | "original": {
410 | "owner": "srid",
411 | "repo": "unionmount",
412 | "type": "github"
413 | }
414 | }
415 | },
416 | "root": "root",
417 | "version": 7
418 | }
419 |
--------------------------------------------------------------------------------
/nix/modules/project/settings/all.nix:
--------------------------------------------------------------------------------
1 | { name, pkgs, self, lib, config, log, ... }:
2 | let
3 | inherit (lib) types;
4 | inherit (import ./lib.nix {
5 | inherit lib config;
6 | }) mkCabalSettingOptions;
7 |
8 | # Convenient way to create multiple settings using `mkCabalSettingOptions`
9 | cabalSettingsFrom =
10 | lib.mapAttrsToList (name: args: {
11 | options = mkCabalSettingOptions (args // {
12 | inherit name;
13 | });
14 | });
15 | in
16 | {
17 | # NOTE: These settings are based on the functions in:
18 | # https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/haskell-modules/lib/compose.nix
19 | #
20 | # Some functions (like checkUnusedPackages) are not included here, but we
21 | # probably should, especially if there is demand.
22 | imports = with pkgs.haskell.lib.compose; cabalSettingsFrom {
23 | check = {
24 | type = types.bool;
25 | description = ''
26 | Whether to run cabal tests as part of the nix build
27 | '';
28 | impl = enable:
29 | if enable then doCheck else dontCheck;
30 | };
31 | jailbreak = {
32 | type = types.bool;
33 | description = ''
34 | Remove version bounds from this package's cabal file.
35 | '';
36 | impl = enable:
37 | if enable then doJailbreak else dontJailbreak;
38 | };
39 | broken = {
40 | type = types.bool;
41 | description = ''
42 | Whether to mark the package as broken
43 | '';
44 | impl = enable:
45 | if enable then markBroken else unmarkBroken;
46 | };
47 | brokenVersions = {
48 | type = types.listOf types.str;
49 | description = ''
50 | List of versions that are known to be broken.
51 | '';
52 | impl = versions:
53 | let
54 | markBrokenVersions = vs: drv:
55 | builtins.foldl' (lib.flip markBrokenVersion) drv vs;
56 | in
57 | markBrokenVersions versions;
58 | };
59 | haddock = {
60 | type = types.bool;
61 | description = ''
62 | Whether to build the haddock documentation.
63 | '';
64 | impl = enable:
65 | if enable then doHaddock else dontHaddock;
66 | };
67 | coverage = {
68 | type = types.bool;
69 | description = ''
70 | Modifies thae haskell package to disable the generation
71 | and installation of a coverage report.
72 | '';
73 | impl = enable:
74 | if enable then doCoverage else dontCoverage;
75 | };
76 | benchmark = {
77 | type = types.bool;
78 | description = ''
79 | Enables dependency checking and compilation
80 | for benchmarks listed in the package description file.
81 | Benchmarks are, however, not executed at the moment.
82 | '';
83 | impl = enable:
84 | if enable then doBenchmark else dontBenchmark;
85 | };
86 | libraryProfiling = {
87 | type = types.bool;
88 | description = ''
89 | Build the library for profiling by default.
90 | '';
91 | impl = enable:
92 | if enable then enableLibraryProfiling else disableLibraryProfiling;
93 | };
94 | executableProfiling = {
95 | type = types.bool;
96 | description = ''
97 | Build the executable with profiling enabled.
98 | '';
99 | impl = enable:
100 | if enable then enableExecutableProfiling else disableExecutableProfiling;
101 | };
102 | sharedExecutables = {
103 | type = types.bool;
104 | description = ''
105 | Build the executables as shared libraries.
106 | '';
107 | impl = enable:
108 | if enable then enableSharedExecutables else disableSharedExecutables;
109 | };
110 | sharedLibraries = {
111 | type = types.bool;
112 | description = ''
113 | Build the libraries as shared libraries.
114 | '';
115 | impl = enable:
116 | if enable then enableSharedLibraries else disableSharedLibraries;
117 | };
118 | deadCodeElimination = {
119 | type = types.bool;
120 | description = ''
121 | Enable dead code elimination.
122 | '';
123 | impl = enable:
124 | if enable then enableDeadCodeElimination else disableDeadCodeElimination;
125 | };
126 | staticLibraries = {
127 | type = types.bool;
128 | description = ''
129 | Build the libraries as static libraries.
130 | '';
131 | impl = enable:
132 | if enable then enableStaticLibraries else disableStaticLibraries;
133 | };
134 | extraBuildDepends = {
135 | type = types.listOf types.package;
136 | description = ''
137 | Extra build dependencies for the package.
138 | '';
139 | impl = addBuildDepends;
140 | };
141 | extraBuildTools = {
142 | type = types.listOf types.package;
143 | description = ''
144 | Extra build tools for the package.
145 | '';
146 | impl = addBuildTools;
147 | };
148 | extraLibraries = {
149 | type = types.listOf types.package;
150 | description = ''
151 | Extra library dependencies for the package.
152 | '';
153 | impl = addExtraLibraries;
154 | };
155 | extraTestToolDepends = {
156 | type = types.listOf types.package;
157 | description = ''
158 | Extra test tool dependencies for the package.
159 | '';
160 | impl = addTestToolDepends;
161 | };
162 | extraPkgconfigDepends = {
163 | type = types.listOf types.package;
164 | description = ''
165 | Extra pkgconfig dependencies for the package.
166 | '';
167 | impl = addPkgconfigDepends;
168 | };
169 | extraSetupDepends = {
170 | type = types.listOf types.package;
171 | description = ''
172 | Extra setup dependencies for the package.
173 | '';
174 | impl = addSetupDepends;
175 | };
176 | libraryHaskellDepends = {
177 | type = types.listOf types.package;
178 | description = ''
179 | Haskell library dependencies for the package.
180 | '';
181 | impl = deps: overrideCabal (drv: {
182 | libraryHaskellDepends = (drv.libraryHaskellDepends or [ ]) ++ deps;
183 | });
184 | };
185 | librarySystemDepends = {
186 | type = types.listOf types.package;
187 | description = ''
188 | System library dependencies for the package.
189 | '';
190 | impl = deps: overrideCabal (drv: {
191 | librarySystemDepends = (drv.librarySystemDepends or [ ]) ++ deps;
192 | });
193 | };
194 | libraryToolDepends = {
195 | type = types.listOf types.package;
196 | description = ''
197 | Tool dependencies for the library.
198 | '';
199 | impl = deps: overrideCabal (drv: {
200 | libraryToolDepends = (drv.libraryToolDepends or [ ]) ++ deps;
201 | });
202 | };
203 | libraryPkgconfigDepends = {
204 | type = types.listOf types.package;
205 | description = ''
206 | Pkgconfig dependencies for the library.
207 | '';
208 | impl = deps: overrideCabal (drv: {
209 | libraryPkgconfigDepends = (drv.libraryPkgconfigDepends or [ ]) ++ deps;
210 | });
211 | };
212 | extraConfigureFlags = {
213 | type = types.listOf types.str;
214 | description = ''
215 | Extra flags to pass to 'cabal configure'
216 | '';
217 | impl = appendConfigureFlags;
218 | };
219 | extraBuildFlags = {
220 | type = types.listOf types.str;
221 | description = ''
222 | Extra flags to pass to 'cabal build'
223 | '';
224 | impl = appendBuildFlags;
225 | };
226 | removeConfigureFlags = {
227 | type = types.listOf types.str;
228 | description = ''
229 | Flags to remove from the default flags passed to 'cabal configure'
230 | '';
231 | impl =
232 | let
233 | removeConfigureFlags = flags: drv:
234 | builtins.foldl' (lib.flip removeConfigureFlag) drv flags;
235 | in
236 | removeConfigureFlags;
237 | };
238 | cabalFlags = {
239 | type = types.lazyAttrsOf types.bool;
240 | description = ''
241 | Cabal flags to enable or disable explicitly.
242 |
243 | NOTE: You may wish to use `packages.*.cabalFlags` instead, as those are passed directly to `cabal2nix` (see #418).
244 | '';
245 | impl = flags: drv:
246 | let
247 | fns = lib.flip lib.mapAttrsToList flags (flag: enabled:
248 | (if enabled then enableCabalFlag else disableCabalFlag) flag
249 | );
250 | in
251 | lib.pipe drv fns;
252 | };
253 | patches = {
254 | type = types.listOf types.path;
255 | description = ''
256 | A list of patches to apply to the package.
257 | '';
258 | impl = appendPatches;
259 | };
260 | justStaticExecutables = {
261 | type = types.bool;
262 | description = ''
263 | Link executables statically against haskell libs to reduce closure size
264 | '';
265 | impl = enable:
266 | if enable then justStaticExecutables else null;
267 | };
268 | separateBinOutput = {
269 | type = types.bool;
270 | description = ''
271 | Create two outputs for this Haskell package -- 'out' and 'bin'. This is
272 | useful to separate out the binary with a reduced closure size.
273 |
274 | WARNING: This can lead to cyclic references; see
275 | https://github.com/srid/haskell-flake/issues/167
276 | '';
277 | impl = enable:
278 | let
279 | disableSeparateBinOutput =
280 | overrideCabal (drv: { enableSeparateBinOutput = false; });
281 | in
282 | if enable then enableSeparateBinOutput else disableSeparateBinOutput;
283 | };
284 | buildTargets = {
285 | type = types.listOf types.str;
286 | description = ''
287 | A list of targets to build.
288 |
289 | By default all cabal executable targets are built.
290 | '';
291 | impl = setBuildTargets;
292 | };
293 | hyperlinkSource = {
294 | type = types.bool;
295 | description = ''
296 | Whether to hyperlink the source code in the generated documentation.
297 | '';
298 | impl = enable:
299 | if enable then doHyperlinkSource else dontHyperlinkSource;
300 | };
301 | disableHardening = {
302 | type = types.bool;
303 | description = ''
304 | Disable hardening flags for the package.
305 | '';
306 | impl = enable:
307 | if enable then disableHardening else null;
308 | };
309 | strip = {
310 | type = types.bool;
311 | description = ''
312 | Let Nix strip the binary files.
313 |
314 | This removes debugging symbols.
315 | '';
316 | impl = enable:
317 | if enable then doStrip else dontStrip;
318 | };
319 | enableDWARFDebugging = {
320 | type = types.bool;
321 | description = ''
322 | Enable DWARF debugging.
323 | '';
324 | impl = enable:
325 | if enable then enableDWARFDebugging else null;
326 | };
327 | disableOptimization = {
328 | type = types.bool;
329 | description = ''
330 | Disable core optimizations, significantly speeds up build time
331 | '';
332 | impl = enable:
333 | if enable then disableOptimization else null;
334 | };
335 | failOnAllWarnings = {
336 | type = types.bool;
337 | description = ''
338 | Turn on most of the compiler warnings and fail the build if any of them occur
339 | '';
340 | impl = enable:
341 | if enable then failOnAllWarnings else null;
342 | };
343 | triggerRebuild = {
344 | type = types.raw;
345 | description = ''
346 | Add a dummy command to trigger a build despite an equivalent earlier
347 | build that is present in the store or cache.
348 | '';
349 | impl = triggerRebuild;
350 | };
351 | buildFromSdist = {
352 | type = types.bool;
353 | description = ''
354 | Whether to use `buildFromSdist` to build the package.
355 | Make sure all files we use are included in the sdist, as a check
356 | for release-worthiness.
357 | '';
358 | impl = enable:
359 | if enable then
360 | (pkg: lib.pipe pkg [
361 | buildFromSdist
362 | (x: log.traceDebug "${name}.buildFromSdist ${x.outPath}" x)
363 | ]) else null;
364 | };
365 |
366 | generateOptparseApplicativeCompletions = {
367 | type = types.listOf types.str;
368 | description = ''
369 | Generate and install shell completion files for executables.
370 | The executables need to be using `optparse-applicative` for this to work.
371 | Note that this feature is automatically disabled when cross-compiling, since it requires executing the binaries in question.
372 | '';
373 | impl = self.generateOptparseApplicativeCompletions;
374 | };
375 |
376 | removeReferencesTo = {
377 | type = types.listOf types.package;
378 | description = ''
379 | Packages to remove references to.
380 |
381 | This is useful to ditch unnecessary data dependencies from your Haskell
382 | executable so as to reduce its closure size.
383 |
384 | cf.
385 | - https://github.com/NixOS/nixpkgs/pull/204675
386 | - https://srid.ca/remove-references-to
387 | '';
388 | impl = disallowedReferences: drv:
389 | drv.overrideAttrs (old: rec {
390 | inherit disallowedReferences;
391 | postInstall = (old.postInstall or "") + ''
392 | ${lib.concatStrings (map (e: "echo Removing reference to: ${e}\n") disallowedReferences)}
393 | ${lib.concatStrings (map (e: "remove-references-to -t ${e} $out/bin/*\n") disallowedReferences)}
394 | '';
395 | });
396 | };
397 |
398 | stan = {
399 | type = types.bool;
400 | description = ''
401 | Modifies the Haskell package to generate a static analysis report using .
402 | '';
403 | impl = enable: drv:
404 | let
405 | inherit (pkgs.haskell.lib.compose) appendConfigureFlags addBuildTool;
406 | in
407 | if enable then
408 | lib.pipe drv [
409 | (appendConfigureFlags [ "--ghc-options=-fwrite-ide-info" "--ghc-options=-hiedir=.hie" ])
410 | (addBuildTool self.stan)
411 | (drv:
412 | drv.overrideAttrs (old: {
413 | postInstall = (old.postInstall or "") + ''
414 | echo "Generating stan.html"
415 | stan report
416 | mv stan.html $out
417 | echo "Finished generating stan.html"
418 | '';
419 | }))
420 | ]
421 | else drv;
422 | };
423 |
424 | installIntermediates = {
425 | type = types.bool;
426 | description = ''
427 | Whether to install intermediate build artifacts (for incremental builds).
428 | '';
429 | impl = enable: overrideCabal (drv: {
430 | doInstallIntermediates = enable;
431 | });
432 | };
433 |
434 | separateIntermediatesOutput = {
435 | type = types.bool;
436 | description = ''
437 | Whether to separate intermediate build artifacts into a distinct output.
438 | Only applies if `installIntermediates` is enabled.
439 | '';
440 | impl = enable: overrideCabal (drv: {
441 | enableSeparateIntermediatesOutput = enable;
442 | });
443 | };
444 |
445 | previousIntermediates = {
446 | type = types.path;
447 | description = ''
448 | Previous build intermediates of the same package.
449 | '';
450 | impl = intermediates: overrideCabal (drv: {
451 | previousIntermediates = intermediates;
452 | });
453 | };
454 |
455 | # Setting raw attributes on the derivation
456 | drvAttrs = {
457 | type = types.lazyAttrsOf types.raw;
458 | description = ''
459 | Raw attributes to set on the Haskell package derivation.
460 |
461 | This allows setting arbitrary attributes like environment variables
462 | or other derivation attributes without the complexity of using custom.
463 |
464 | Example:
465 |
466 | drvAttrs = {
467 | GIT_BIN = lib.getExe' pkgs.git "git";
468 | };
469 | '';
470 | impl = attrs: drv: drv.overrideAttrs (oldAttrs: oldAttrs // attrs);
471 | };
472 |
473 | # When none of the above settings is suitable:
474 | custom = {
475 | type = types.functionTo types.package;
476 | description = ''
477 | A custom function to apply on the Haskell package.
478 |
479 | Use this only if none of the existing settings are suitable.
480 |
481 | The function must take three arguments: self, super and the package being
482 | applied to.
483 |
484 | Example:
485 |
486 | custom = pkg: builtins.trace pkg.version pkg;
487 | '';
488 | impl = f: f;
489 | };
490 | };
491 | }
492 |
--------------------------------------------------------------------------------