├── .envrc ├── .gitattributes ├── .gitignore ├── cabal.project ├── foo ├── src │ └── Foo.hs ├── CHANGELOG.md ├── foo.cabal └── LICENSE ├── bar ├── CHANGELOG.md ├── src │ └── Main.hs ├── bar.cabal └── LICENSE ├── .github └── workflows │ └── nix.yml ├── flake.nix ├── LICENSE ├── flake.lock └── README.md /.envrc: -------------------------------------------------------------------------------- 1 | use flake . --trace-verbose -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | flake.lock linguist-generated=true 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .direnv 2 | result 3 | dist-newstyle 4 | .vscode -------------------------------------------------------------------------------- /cabal.project: -------------------------------------------------------------------------------- 1 | packages: 2 | ./foo 3 | ./bar 4 | -------------------------------------------------------------------------------- /foo/src/Foo.hs: -------------------------------------------------------------------------------- 1 | module Foo (fooFunc) where 2 | 3 | fooFunc :: IO () 4 | fooFunc = putStrLn "fooFunc" 5 | -------------------------------------------------------------------------------- /bar/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Revision history for bar 2 | 3 | ## 0.1.0.0 -- YYYY-mm-dd 4 | 5 | * First version. Released on an unsuspecting world. 6 | -------------------------------------------------------------------------------- /foo/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Revision history for foo 2 | 3 | ## 0.1.0.0 -- YYYY-mm-dd 4 | 5 | * First version. Released on an unsuspecting world. 6 | -------------------------------------------------------------------------------- /bar/src/Main.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Foo (fooFunc) 4 | 5 | main :: IO () 6 | main = do 7 | putStrLn "Hello, Haskell!" 8 | fooFunc 9 | -------------------------------------------------------------------------------- /foo/foo.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 3.0 2 | name: foo 3 | version: 0.1.0.0 4 | license: MIT 5 | license-file: LICENSE 6 | author: Sridhar Ratnakumar 7 | maintainer: srid@srid.ca 8 | category: Other 9 | build-type: Simple 10 | extra-doc-files: CHANGELOG.md 11 | 12 | common warnings 13 | ghc-options: -Wall 14 | 15 | library 16 | import: warnings 17 | exposed-modules: Foo 18 | build-depends: base 19 | hs-source-dirs: src 20 | default-language: Haskell2010 21 | -------------------------------------------------------------------------------- /bar/bar.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 3.0 2 | name: bar 3 | version: 0.1.0.0 4 | license: MIT 5 | license-file: LICENSE 6 | author: Sridhar Ratnakumar 7 | maintainer: srid@srid.ca 8 | category: Other 9 | build-type: Simple 10 | extra-doc-files: CHANGELOG.md 11 | 12 | common warnings 13 | ghc-options: -Wall 14 | 15 | executable bar 16 | import: warnings 17 | main-is: Main.hs 18 | build-depends: 19 | base 20 | , foo 21 | hs-source-dirs: src 22 | default-language: Haskell2010 23 | -------------------------------------------------------------------------------- /.github/workflows/nix.yml: -------------------------------------------------------------------------------- 1 | name: "CI" 2 | on: 3 | # Run only when pushing to master branch, and making PRs 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | jobs: 9 | build: 10 | runs-on: ${{ matrix.os }} 11 | strategy: 12 | matrix: 13 | os: [ubuntu-latest, macos-latest] 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: DeterminateSystems/nix-installer-action@main 17 | - name: Install omnix 18 | run: nix --accept-flake-config profile install "github:juspay/omnix" 19 | - name: Build all flake outputs 20 | run: om ci 21 | - name: What GHC version? 22 | run: nix develop -c ghc --version 23 | -------------------------------------------------------------------------------- /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 | perSystem = { self', inputs', pkgs, system, ... }: { 12 | haskellProjects.default = { 13 | projectFlakeName = "haskell-multi-nix"; 14 | # Want to override dependencies? 15 | # See https://haskell.flake.page/dependency 16 | }; 17 | packages.default = self'.packages.bar; 18 | }; 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /bar/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022 Sridhar Ratnakumar 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included 12 | in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /foo/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022 Sridhar Ratnakumar 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included 12 | in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-parts": { 4 | "inputs": { 5 | "nixpkgs-lib": "nixpkgs-lib" 6 | }, 7 | "locked": { 8 | "lastModified": 1741352980, 9 | "narHash": "sha256-+u2UunDA4Cl5Fci3m7S643HzKmIDAe+fiXrLqYsR2fs=", 10 | "owner": "hercules-ci", 11 | "repo": "flake-parts", 12 | "rev": "f4330d22f1c5d2ba72d3d22df5597d123fdb60a9", 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": 1761337281, 24 | "narHash": "sha256-GgGRjxNecdqUnLBKP3LMHR7q3sZVWATCB5AJVMgcmw0=", 25 | "owner": "srid", 26 | "repo": "haskell-flake", 27 | "rev": "cec1fd0eb0f287f24d1fff03daee1b581f049c77", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "srid", 32 | "repo": "haskell-flake", 33 | "type": "github" 34 | } 35 | }, 36 | "nixpkgs": { 37 | "locked": { 38 | "lastModified": 1741865919, 39 | "narHash": "sha256-4thdbnP6dlbdq+qZWTsm4ffAwoS8Tiq1YResB+RP6WE=", 40 | "owner": "nixos", 41 | "repo": "nixpkgs", 42 | "rev": "573c650e8a14b2faa0041645ab18aed7e60f0c9a", 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": 1740877520, 55 | "narHash": "sha256-oiwv/ZK/2FhGxrCkQkB83i7GnWXPPLzoqFHpDD3uYpk=", 56 | "owner": "nix-community", 57 | "repo": "nixpkgs.lib", 58 | "rev": "147dee35aab2193b174e4c0868bd80ead5ce755c", 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # haskell-multi-nix 2 | 3 | Just a simple demo of Nixifying a *multi-package* Haskell project. 4 | 5 | ## Packages 6 | 7 | This project has two local Haskell packages: 8 | 9 | 1. `foo`: a Haskell library exporting `Foo.fooFunc`. 10 | 2. `bar`: a Haskell executable that depends on `foo` 11 | 12 | To build the `foo` library: 13 | 14 | ```sh 15 | nix build .#foo 16 | ``` 17 | 18 | To build the `bar` executable: 19 | 20 | ```sh 21 | nix build 22 | ``` 23 | 24 | To run the executable: 25 | 26 | ```sh 27 | nix run 28 | ``` 29 | 30 | ## Dev Shell 31 | 32 | The Nix development shell (`nix develop`) allows you to run the various `cabal` commands on the local packages. 33 | 34 | For example, this will compile and run the main executable: 35 | 36 | ```sh 37 | nix develop -c cabal -- run bar 38 | ``` 39 | 40 | ### ghcid 41 | 42 | Using [Multiple Home Units](https://well-typed.com/blog/2022/01/multiple-home-units/) you can use ghcid to auto-recompile and auto-rerun the `bar` program whenever **any** Haskell source changes, including from libraries (`foo`). To do this, you must pass `--enable-multi-repl` along with the list of libraries to reload, which list should be in the correct order. viz.: 43 | 44 | ``` 45 | ghcid -T Main.main -c 'cabal repl --enable-multi-repl bar foo' 46 | ``` 47 | 48 | Now, try modifying `./foo/src/Foo.hs` and ghcid should instantly re-compile and re-run `bar` with the new changes. A demo can be seen [here](https://x.com/sridca/status/1901283945779544362). 49 | 50 | ## How it works 51 | 52 | ### The `nixpkgs` tree 53 | 54 | The [`nixpkgs`](https://github.com/srid/haskell-multi-nix/tree/nixpkgs) release tag uses raw functions from nixpkgs. 55 | 56 | The Haskell infrastructure in nixpkgs provides a package set (an attrset) called `pkgs.haskellPackages`[^ver]. We add two more packages -- `foo` and `bar` (the local packages) -- to this package set. We do this by using the standard nixpkgs overlay API (specifically `extend`, which was created by the implicit `makeExtensible`) defined in [fixed-points.nix](https://github.com/NixOS/nixpkgs/blob/master/lib/fixed-points.nix). After having added the local packages, the result is a *new* package set, which is no different *in essense* to the original package set (we can also put our dependency overrides in the same, or different, overlay). Note that any package in a package set can depend on any other packages; thus, it becomes possible to make `bar` depend on `foo` (see "build-depends" in `./bar/bar.cabal`) even though they come from the same overlay. 57 | 58 | [^ver]: The package set `pkgs.haskellPackages` corresponds to the default GHC version. Non-default GHC versions have their own package sets, for e.g.: `pkgs.haskell.packages.ghc924` is the package set for GHC 9.2.4. 59 | 60 | ### The `master` tree 61 | 62 | The `master` branch uses [haskell-flake](https://github.com/srid/haskell-flake) which abstracts much of what we explained above, such that your flake.nix is as small as possible. 63 | --------------------------------------------------------------------------------