├── .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 | [![project chat](https://img.shields.io/badge/zulip-join_chat-brightgreen.svg)](https://nixos.zulipchat.com/#narrow/stream/413949-haskell-flake) 2 | [![Naiveté Compass of Mood](https://img.shields.io/badge/naïve-FF10F0)](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 | --------------------------------------------------------------------------------