├── README.md ├── project0 ├── LICENSE ├── Main.hs ├── README.md ├── nixpkgs.json ├── project0.cabal ├── project0.nix ├── release0.nix ├── release1.nix └── release2.nix ├── project1 ├── LICENSE ├── Main.hs ├── QuickCheck.nix ├── README.md ├── optparse-applicative-2.nix ├── optparse-applicative.nix ├── project1.cabal ├── project1.nix ├── release0.nix ├── release1.nix ├── release2.nix ├── release2a.nix ├── release3.nix ├── release4.nix ├── release5.nix ├── release6.nix ├── turtle-2.nix └── turtle.nix ├── project2 ├── LICENSE ├── Main.hs ├── README.md ├── cbits │ └── check.c ├── project2.cabal ├── project2.nix └── release.nix ├── project3 ├── LICENSE ├── Main.hs ├── Project3.hs ├── README.md ├── Test.hs ├── cbits │ └── check.c ├── project3.cabal ├── project3.nix ├── release0.nix ├── release1.nix └── release2.nix └── project4 ├── Main.hs ├── README.md ├── nix ├── foldl.nix ├── managed.nix ├── optparse-applicative.nix ├── project4.nix └── turtle.nix ├── project4.cabal ├── release0.nix ├── release1.nix ├── release2.nix └── release3.nix /README.md: -------------------------------------------------------------------------------- 1 | # Nix and Haskell in production 2 | 3 | This guide documents how I use Nix for Haskell development. Feel free to open 4 | issues or pull requests if you would like to contribute or suggest improvements 5 | 6 | The purpose of this project is to support two Haskell workflows: 7 | 8 | * Workflow #1: Nix provisions the development environment 9 | * Nix provides all dependencies and the Haskell toolchain 10 | * You still build the root project using `cabal` ([overview](https://www.haskell.org/cabal/), [user's guide](https://cabal.readthedocs.io/)) 11 | * This approach is ideal for development as it supports incremental builds 12 | * Workflow #2: Nix builds the root project for you 13 | * This approach is ideal for continuous integration (especially Hydra) 14 | 15 | The emphasis of this guide is to be as robust as possible and gracefully handle 16 | writing Haskell projects at scale. Some of the suggestions in this guide might 17 | be overkill for a small Haskell project but are essential when managing multiple 18 | private Haskell projects across a team of developers. 19 | 20 | This guide is based partly on 21 | [the Haskell section of the `nixpkgs` manual][nixpkgs-haskell] 22 | and partly on experience using Nix and Haskell in production at 23 | [Awake Security][awake]. 24 | 25 | # Background 26 | 27 | Nix is not a `cabal` replacement and Nix actually complements `cabal` quite 28 | well. Nix is much more analogous to a `stack` replacement. `stack` does 29 | provide some support for Nix integration, but this document does not cover that. 30 | Instead, this document describes how to use Nix in conjunction with `cabal` for 31 | Haskell development 32 | 33 | The main benefits of using Nix over `stack` are: 34 | 35 | * Binary caches 36 | 37 | Nix lets you download precompiled Hackage packages whereas `stack` compiles 38 | them on your computer the first time you depend on them 39 | 40 | * Space efficiency 41 | 42 | `stack` creates a copy of each package for each resolver. This means that 43 | if you have two projects with different resolvers then they will not use 44 | the same copy of shared dependencies 45 | 46 | * Generality 47 | 48 | Nix is a language-independent build tool. This means you can use Nix to 49 | also build and customize non-Haskell dependencies (like `gtk`). This 50 | uniform language simplifies build tooling and infrastructure. 51 | 52 | * Larger ecosystem 53 | 54 | Nix provides a large ecosystem of tools that integrate with anything that 55 | Nix can build, such as Hydra (continuous integration), NixOS (an operating 56 | system), and NixOps (a deploy tool) 57 | 58 | * Flexibility 59 | 60 | Nix is a powerful tool in the hands of advanced users. You can make very 61 | deep and sweeping changes to your toolchain, such as recompiling everything 62 | with security hardening 63 | 64 | The main disadvantage of using Nix over `stack` are: 65 | 66 | * Verbosity 67 | 68 | Nix derivations for Haskell projects are significantly more complex than 69 | their corresponding `stack.yaml` files. The `release.nix` files in this 70 | repository are the Nix analog of a `stack.yaml` file and you can see for 71 | yourself the increase in complexity as the examples progress in difficulty. 72 | 73 | * Poor error messages 74 | 75 | Nix is an untyped language with no special Haskell integration, so error 76 | messages are unhelpful 77 | 78 | * Nix cannot incrementally compile Haskell libraries 79 | 80 | Note that you can still use Nix to provision a development environment and 81 | incrementally compile a Haskell package using cabal. However, if you use Nix 82 | to build the package then Nix will build the package from scratch for every 83 | minor change. In theory, this could be fixed to have Nix directly support 84 | incremental Haskell builds but this has not been done yet. 85 | 86 | * Worse user experience 87 | 88 | Nix does not provide many conveniences that `stack` does such as 89 | bootstrapping new projects or "file watch" 90 | 91 | Both Nix and `stack` use curated package sets instead of version bounds for 92 | dependency management. `stack` calls these package sets "resolvers" whereas 93 | Nix calls these package sets "channels". Nix provides stable channels with 94 | names like `NixOS-18.09` (analogous to `stack`'s LTS releases) and then an 95 | unstable channel named `nixpkgs-unstable` (analogous to `stack`'s nightly 96 | releases) 97 | 98 | # Related guides 99 | 100 | * [Nix Haskell Monorepo Tutorial](https://github.com/fghibellini/nix-haskell-monorepo) - 101 | Guide on how to scale Nix development to a larger repository containing all of 102 | a company's internally-developed Haskell packages 103 | 104 | # Related tools 105 | 106 | Before continuing, I'd like to mention some other tools for mixing Haskell with 107 | Nix: 108 | 109 | * [`tinc`](https://github.com/sol/tinc/blob/nixpkgs/NIX.md) - this uses 110 | `cabal`'s solver to select which Haskell packages to use instead of the 111 | curated Haskell package set from `nixpkgs` 112 | * [`styx`](https://github.com/jyp/styx) - This tool provides a `stack`-like 113 | interface to managing Haskell dependencies using Nix 114 | * [`haskell-overridez`](https://github.com/adetokunbo/haskell-overridez) - 115 | Tool that automates dependency management as described in this guide 116 | 117 | # Setup 118 | 119 | Before you begin, you must install Nix if you haven't already: 120 | 121 | ```bash 122 | $ curl -L https://nixos.org/nix/install | sh 123 | ``` 124 | 125 | You must also install `cabal2nix` and `nix-prefetch-git`: 126 | 127 | ```bash 128 | $ nix-env --install cabal2nix 129 | $ nix-env --install nix-prefetch-git 130 | ``` 131 | 132 | You also need to install `cabal` if you haven't done so already. You can either 133 | use your installed `cabal` or you can use `nix` to install `cabal` for you: 134 | 135 | ```bash 136 | $ nix-env --install cabal-install 137 | ``` 138 | 139 | Make sure that you have a fairly recent version of `cabal` installed since these 140 | examples will use GHC 8 which requires version 1.24 or later of `cabal`. You 141 | can check what version you have installed by running: 142 | 143 | ```bash 144 | $ cabal --version 145 | ``` 146 | 147 | Finally, run `cabal update` if you haven't done so already 148 | 149 | # Organization 150 | 151 | This tutorial is split into several tutorial projects in the `project*/` 152 | subdirectories. Read the `README.md` file in each subdirectory in 153 | order to follow the tutorial: 154 | 155 | * [Project 0 - Nix basics][proj0] 156 | * [Project 1 - Dependency management][proj1] 157 | * [Project 2 - Non-Haskell dependencies][proj2] 158 | * [Project 3 - Customizing Haskell projects ][proj3] 159 | * [Project 4 - Advanced dependency management][proj4] 160 | 161 | [awake]: https://awakesecurity.com/ 162 | [nixpkgs-haskell]: https://nixos.org/nixpkgs/manual/#haskell 163 | [proj0]: ./project0/README.md 164 | [proj1]: ./project1/README.md 165 | [proj2]: ./project2/README.md 166 | [proj3]: ./project3/README.md 167 | [proj4]: ./project4/README.md 168 | -------------------------------------------------------------------------------- /project0/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Gabriella Gonzalez 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright notice, 7 | this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright notice, 9 | this list of conditions and the following disclaimer in the documentation 10 | and/or other materials provided with the distribution. 11 | * Neither the name of Gabriella Gonzalez nor the names of other contributors 12 | may be used to endorse or promote products derived from this software 13 | without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 22 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /project0/Main.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | main :: IO () 4 | main = putStrLn "Hello, world!" 5 | -------------------------------------------------------------------------------- /project0/README.md: -------------------------------------------------------------------------------- 1 | # Building with `nix` 2 | 3 | 4 | 5 | This directory contains the simplest possible Haskell project and the simplest 6 | possible Nix derivation to build that project. You can think of a derivation 7 | as a language-agnostic recipe for how to build something (such as a Haskell 8 | package). 9 | 10 | You can build this Haskell package by running: 11 | 12 | ```bash 13 | $ nix-build release0.nix 14 | these derivations will be built: 15 | /nix/store/3l7f5v3aibz9rnxxafm6afvihjc04aiq-project0-1.0.0.drv 16 | building path(s) ‘/nix/store/x28vx2rfnffl1clmxn5054bxwqyln2j0-project0-1.0.0’ 17 | ... 18 | Configuring project0-1.0.0... 19 | Dependency base <5: using base-4.9.0.0 20 | ... 21 | Building project0-1.0.0... 22 | Preprocessing executable 'project0' for project0-1.0.0... 23 | [1 of 1] Compiling Main ( Main.hs, dist/build/project0/project0-tmp/Main.dyn_o ) 24 | Linking dist/build/project0/project0 ... 25 | ... 26 | Installing executable(s) in 27 | /nix/store/x28vx2rfnffl1clmxn5054bxwqyln2j0-project0-1.0.0/bin 28 | ... 29 | /nix/store/x28vx2rfnffl1clmxn5054bxwqyln2j0-project0-1.0.0 30 | ``` 31 | 32 | I've highlighted the parts that matter the most for our simple example. Don't 33 | expect the hashed paths in the above example output to necessarily match the 34 | ones you get (See below for more details about why they might differ). 35 | 36 | The `project0.cabal` file only specifies a single dependency of `base < 5` and 37 | Nix picks the latest [`base` package](http://hackage.haskell.org/package/base) 38 | below version 5 (in this case, [`base-4.9.0.0`](http://hackage.haskell.org/package/base)) 39 | to satisfy that dependency. Nix then builds the project, stores the build 40 | output in `/nix/store/x28vx2rfnffl1clmxn5054bxwqyln2j0-project0-1.0.0` and 41 | creates a symlink in the current directory named `result` pointing to that 42 | directory in the `/nix/store`: 43 | 44 | ```bash 45 | $ readlink result 46 | /nix/store/x28vx2rfnffl1clmxn5054bxwqyln2j0-project0-1.0.0 47 | ``` 48 | 49 | Right now the contents of that directory are just an executable file and an 50 | empty library directory: 51 | 52 | ```bash 53 | $ tree result 54 | result 55 | ├── bin 56 | │   └── project0 57 | └── lib 58 | └── links 59 | 60 | 3 directories, 1 file 61 | ``` 62 | 63 | As the project grows more complex we'll see additional build outputs in this 64 | directory. 65 | 66 | We can run the executable stored in the `result/bin` subdirectory: 67 | 68 | ```bash 69 | $ result/bin/project0 70 | Hello, world! 71 | ``` 72 | 73 | This output makes sense since our `Main.hs` file is just a simple 74 | "Hello, world!" program: 75 | 76 | ```haskell 77 | module Main where 78 | 79 | main :: IO () 80 | main = putStrLn "Hello, world!" 81 | ``` 82 | 83 | What happens if we try to build the project again? 84 | 85 | ```bash 86 | $ nix-build release0.nix 87 | these derivations will be built: 88 | /nix/store/vvw0v8ys7dadck747vj48vb0jgs7isqm-project0-1.0.0.drv 89 | building path(s) ‘/nix/store/ysrqcpdl51jaa4gqzx1xmb7m0h05rdwq-project0-1.0.0’ 90 | setupCompilerEnvironmentPhase 91 | ... 92 | [1 of 1] Compiling Main ( Main.hs, dist/build/project0/project0-tmp/Main.dyn_o ) 93 | Linking dist/build/project0/project0 ... 94 | ... 95 | /nix/store/ysrqcpdl51jaa4gqzx1xmb7m0h05rdwq-project0-1.0.0 96 | ``` 97 | 98 | This might seem odd at first since you'd expect Nix to reuse the cached result 99 | from our first build. However, Nix does not reuse the first build because our 100 | project subtly changed: our `nix-build` deposited a `result` symlink which was 101 | not there before! Our Nix derivation depends on the current project directory, 102 | so if anything in the current directory changes then Nix performs a complete 103 | rebuild of our project. 104 | 105 | We can verify this by removing the symlink and then performing the build again: 106 | 107 | ```bash 108 | $ rm result 109 | $ nix-build release0.nix 110 | /nix/store/x28vx2rfnffl1clmxn5054bxwqyln2j0-project0-1.0.0 111 | ``` 112 | 113 | This time we get a cache hit and reuse the first build since our directory is 114 | now bit-for-bit identical to when we first ran `nix-build`. 115 | 116 | These wasteful rebuilds are one reason that I don't recommend using 117 | `nix-build` to build the root Haskell project. Instead, the next section 118 | describes how to use `cabal` with Nix to avoid the issue of wasteful rebuilds. 119 | 120 | # Creating your own project 121 | 122 | If you ever need [to bootstrap your own project using `cabal init`](https://cabal.readthedocs.io/en/latest/developing-packages.html#quickstart), 123 | then run: 124 | 125 | ```bash 126 | $ nix-shell --packages ghc --run 'cabal init' 127 | ``` 128 | 129 | `cabal init` requires `ghc` to be on the executable search path, but we do not 130 | plan to install GHC globally. Instead, we can use a `nix-shell` to transiently 131 | provide `ghc` just for the duration of a `cabal init` command. Later on we 132 | rely on the project's Nix configuration to provide the desired GHC for 133 | project development. 134 | 135 | # Building with `cabal` 136 | 137 | You can open up a development environment for this project inside of a "Nix 138 | shell" by running: 139 | 140 | ```bash 141 | $ nix-shell --attr env release0.nix 142 | ``` 143 | 144 | Normally `nix-shell` wouldn't require the `--attr` flag since `nix-shell` is 145 | designed to automatically compute the necessary development environment from 146 | the original derivation. However, Haskell derivations are different and 147 | `nix-shell` doesn't work out of the box on them. 148 | 149 | Haskell derivations are records that include an `env` field which `nix-shell` 150 | can use to compute the correct development environment. In Nix a field is 151 | called an "attribute" so we pass the `--attr env` flag to specify that 152 | `nix-shell` should compute the development environment from the derivation 153 | record's `env` "attribute". 154 | 155 | Once we open up the development environment we can use `cabal` to build and run 156 | the `project0` executable: 157 | 158 | ```bash 159 | $ cabal configure 160 | Resolving dependencies... 161 | Configuring project0-1.0.0... 162 | $ cabal run project0 163 | Preprocessing executable 'project0' for project0-1.0.0... 164 | [1 of 1] Compiling Main ( Main.hs, dist/build/project0/project0-tmp/Main.o ) 165 | Linking dist/build/project0/project0 ... 166 | Running project0... 167 | Hello, world! 168 | ``` 169 | 170 | You might get the following warning if you are using `cabal-install` version 171 | `2.*`: 172 | 173 | ``` 174 | Warning: The configure command is a part of the legacy v1 style of cabal 175 | usage. 176 | 177 | Please switch to using either the new project style and the new-configure 178 | command or the legacy v1-configure alias as new-style projects will become the 179 | default in the next version of cabal-install. Please file a bug if you cannot 180 | replicate a working v1- use case with the new-style commands. 181 | 182 | For more information, see: https://wiki.haskell.org/Cabal/NewBuild 183 | ``` 184 | 185 | If you get that warning, use the corresponding `cabal v1-*` command (e.g. 186 | `cabal v1-build`) in the interim and see 187 | [this issue](https://github.com/haskell/cabal/issues/4646) for details about 188 | Cabal's "new-build" support for Nix. 189 | 190 | Unlike Nix, `cabal` will be smart and won't wastefully rebuild things that 191 | haven't changed. This means we can safely re-run `cabal` without rebuilding the 192 | entire project from scratch: 193 | 194 | ```bash 195 | $ cabal run project0 196 | Up to date 197 | Hello, world! 198 | ``` 199 | 200 | You can exit from the Nix shell using the `exit` command or typing `Ctrl-D`. 201 | 202 | This Nix shell provides all necessary dependencies for your project and the 203 | Haskell toolchain *except for `cabal`*. For example, inside the Nix shell you 204 | will see that you are using a `ghc` provided by Nix, regardless of whether or 205 | not you have a global `ghc` installed: 206 | 207 | ```bash 208 | $ which ghc # The exact hash in the path might differ 209 | /nix/store/dg7ak1hvlj66vgn4fwvddnnr4pfncd04-ghc-8.0.1/bin/ghc 210 | ``` 211 | 212 | The `cabal configure` step automatically picks up the `ghc` tool-chain and 213 | package database provisioned by Nix and uses them for all subsequent `cabal` 214 | commands. 215 | 216 | The `nixpkgs` manual notes that if you only have Haskell dependencies you 217 | can also just run the following command once: 218 | 219 | ``` 220 | $ nix-shell --attr env release0.nix --run 'cabal configure' 221 | ``` 222 | 223 | ... and then run all the other `cabal` commands without the Nix shell. However, 224 | if you have non-Haskell dependencies then this won't work. When in doubt, just 225 | get used to development inside of a Nix shell since that habit will translate 226 | well to non-Haskell projects managed by Nix. 227 | 228 | **NOTE:** I recommend using `cabal` to build the root project during Haskell 229 | package development, but subsequent examples will still use `nix-build` to 230 | keep the examples short. 231 | 232 | # Nix derivations 233 | 234 | The `release0.nix` file specifies how to build the project using Nix: 235 | 236 | ```nix 237 | let 238 | pkgs = import { }; 239 | 240 | in 241 | pkgs.haskellPackages.callPackage ./project0.nix { } 242 | ``` 243 | 244 | I don't recommend reusing the above derivation for your Haskell projects. There 245 | are several ways that we can improve upon this derivation that we'll address in 246 | later examples. 247 | 248 | This derivation begins by importing `nixpkgs`, which is a Nix channel. You can 249 | find all the officially supported Nix channels here: 250 | 251 | * [Nix channels][channels] 252 | 253 | The default Nix installation will subscribe you some channel (i.e. release) of 254 | Nixpkgs (a package repository for Nix). 255 | 256 | You can tell which release of Nixpkgs you are using by running this command: 257 | 258 | ```bash 259 | $ nix-instantiate --eval --expr 'builtins.readFile ' 260 | "18.03\n" 261 | ``` 262 | 263 | ... and you can also obtain the exact git revision of the Nixpkgs repository 264 | that the channel was cut from using this command: 265 | 266 | ```bash 267 | $ nix-instantiate --eval --expr 'builtins.readFile ' 268 | "411cc559c052feb6e20a01fc6d5fa63cba09ce9a" 269 | ``` 270 | 271 | You should probably use the default channel selected for you. If you are using 272 | a Linux operating system other than NixOS, you can safely change to a stable 273 | channel if you prefer by running: 274 | 275 | ```bash 276 | $ nix-channel --add https://nixos.org/channels/nixos-18.09-small nixpkgs 277 | $ nix-channel --update nixpkgs 278 | ``` 279 | 280 | ... replacing `18.09` with whatever stable release version you wish to use. 281 | 282 | However, you should be very careful about using a stable release on OS X because 283 | the public binary cache only caches OS X build products for the unstable 284 | channel. If you try to use a stable channel on OS X you run a very high risk of 285 | compiling things from scratch (including `ghc`). 286 | 287 | # Pinning `nixpkgs` 288 | 289 | Even "stable" channels are still not frozen. Stable channels are like major 290 | releases and they periodically receive minor releases for security patches, bug 291 | fixes, and new packages that successfully build. However, you have to 292 | specifically opt in to channel updates by running 293 | `nix-channel --update nixpkgs`. If you do nothing then your channel will 294 | remain frozen at whatever minor release you downloaded when you first installed 295 | Nix. When you do upgrade your channel you can only upgrade to the latest minor 296 | release. 297 | 298 | Nix's channel mechanism works okay for personal or open source development, 299 | but does not work well in a corporate environment, since you can't easily 300 | ensure that every person or deployment is on the exact same minor release. 301 | 302 | In a corporate environment, you can pin `nixpkgs` to a specific `git` revision 303 | as illustrated in `release1.nix`: 304 | 305 | ```nix 306 | let 307 | bootstrap = import { }; 308 | 309 | nixpkgs = builtins.fromJSON (builtins.readFile ./nixpkgs.json); 310 | 311 | src = bootstrap.fetchFromGitHub { 312 | owner = "NixOS"; 313 | repo = "nixpkgs"; 314 | inherit (nixpkgs) rev sha256; 315 | }; 316 | 317 | pkgs = import src { }; 318 | 319 | in 320 | pkgs.haskellPackages.callPackage ./project0.nix { } 321 | ``` 322 | 323 | ... where `nixpkgs.json` was generated using the `nix-prefetch-git` tool: 324 | 325 | ```bash 326 | $ nix-prefetch-git https://github.com/NixOS/nixpkgs.git 2c288548b93b657365c27a0132a43ba0080870cc > nixpkgs.json 327 | $ cat nixpkgs.json 328 | { 329 | "url": "https://github.com/NixOS/nixpkgs.git", 330 | "rev": "2c288548b93b657365c27a0132a43ba0080870cc", 331 | "date": "2017-01-02T00:10:04+01:00", 332 | "sha256": "1a365am90a1zy99k4qwddj8s3bdlyfisrsq4a3r00kghjcz89zld" 333 | } 334 | ``` 335 | 336 | Replace `2c288548b93b657365c27a0132a43ba0080870cc` with the `git` revision that 337 | you want to pin `nixpkgs` to. You can also omit the revision to pin to the 338 | current `master`. 339 | 340 | However, if you choose to go this route then you will need to set up an 341 | internal Hydra server to build and cache your project. 342 | 343 | Without an internal cache your developers will likely need to build these tools 344 | from scratch whenever your pinned `nixpkgs` drifts too far from the publicly 345 | cached channels. `ghc` in particular is very expensive to rebuild. 346 | 347 | This guide does not (yet) cover how to set up an internal Hydra server for this 348 | purpose, but may do so in a future draft. Until then, the remaining examples 349 | will not use a pinned `nixpkgs` for simplicity. 350 | 351 | [channels]: https://nixos.org/channels/ 352 | 353 | # `cabal2nix` 354 | 355 | The second half of our `release0.nix` derivation contains the instructions to 356 | build our project: 357 | 358 | ```nix 359 | let 360 | pkgs = import { }; 361 | 362 | in 363 | pkgs.haskellPackages.callPackage ./project0.nix { } 364 | ``` 365 | 366 | This references another file in this same project called `project0.nix`. This 367 | file was generated using `cabal2nix` by running: 368 | 369 | ```bash 370 | $ cabal2nix . > project0.nix 371 | ``` 372 | 373 | ... and the generated `project0.nix` file for this project is: 374 | 375 | ```nix 376 | { mkDerivation, base, lib }: 377 | mkDerivation { 378 | pname = "project0"; 379 | version = "1.0.0"; 380 | src = ./.; 381 | isLibrary = false; 382 | isExecutable = true; 383 | executableHaskellDepends = [ base ]; 384 | license = lib.licenses.bsd3; 385 | } 386 | ``` 387 | 388 | All that `cabal2nix` does is translate our `project0.cabal` file into a 389 | corresponding Nix expression. For comparison, here is the original 390 | `project0.cabal` file that `project0.nix` was generated from: 391 | 392 | ```cabal 393 | name: project0 394 | version: 1.0.0 395 | license: BSD3 396 | license-file: LICENSE 397 | cabal-version: >= 1.18 398 | build-type: Simple 399 | 400 | executable project0 401 | build-depends: base < 5 402 | main-is: Main.hs 403 | default-language: Haskell2010 404 | ``` 405 | 406 | Any time you update a Haskell project's `cabal` file you need to regenerate the 407 | `project0.nix` file using `cabal2nix`. 408 | 409 | # Hydra compatibility 410 | 411 | `release2.nix` illustrates the next improvement we can make to our project's 412 | Nix derivation: 413 | 414 | ```nix 415 | let 416 | pkgs = import { }; 417 | 418 | in 419 | { project0 = pkgs.haskellPackages.callPackage ./project0.nix { }; 420 | } 421 | ``` 422 | 423 | The only difference is that now our file returns a "set" (the Nix term for a 424 | dictionary) of derivations. This "set" only has one "attribute" (i.e. key) 425 | named `project0` whose value is the derivation to build our Haskell project. 426 | 427 | The main motivation for this change is that Hydra (Nix's continuous integration 428 | server) requires that project build files are sets of derivations with one 429 | attribute per build product. If you try to build a naked derivation with Hydra 430 | you will get weird errors. 431 | 432 | A second lesser reason for this change is that this makes it easy to add 433 | additional build products to your project. This comes in handy when you want to 434 | build and test dependencies or extra tools that you rely on. 435 | 436 | You can still build the project using `nix-build` either by specifying to build 437 | all derivations in the set: 438 | 439 | ```bash 440 | $ nix-build release2.nix 441 | ``` 442 | 443 | ... or by specifying the attribute of the derivation you want to build using 444 | the same `--attr` flag we introduced before for `nix-shell`: 445 | 446 | ```bash 447 | $ nix-build --attr project0 release2.nix 448 | ``` 449 | 450 | This `--attr` flag specifies that we only want to build the `project0` field 451 | of the record, and this flag comes in handy once the record has more than one 452 | field. 453 | 454 | You can also still open up a Nix shell, but you need to change the attribute you 455 | pass on the command line from `env` to `project0.env`: 456 | 457 | ```bash 458 | $ nix-shell --attr project0.env release2.nix 459 | ``` 460 | 461 | Like before, `nix-shell` and `nix-build` take slightly different attributes: 462 | we specify the `project0` attribute when using `nix-build` and the 463 | `project0.env` attribute when using `nix-shell`. 464 | 465 | You can also avoid having to type this every time you initialize the project by 466 | creating the following `shell.nix` file: 467 | 468 | ```nix 469 | (import ./release2.nix).project0.env 470 | ``` 471 | 472 | ... replacing `release2.nix` with the name of your project's derivation file. 473 | Then you can just type: 474 | 475 | ```bash 476 | $ nix-shell 477 | ``` 478 | 479 | ... and that will automatically use the contents of `shell.nix` 480 | 481 | Note that `cabal2nix` provides a `--shell` option to generate a `shell.nix` 482 | file suitable for the current project. However, this does not play nice with 483 | advanced dependency management (covered in the next section) so I do not 484 | recommend this approach in general. 485 | 486 | # Conclusion 487 | 488 | That concludes Nix workflow basics for Haskell development. The 489 | [next section](../project1/README.md) covers dependency management. 490 | -------------------------------------------------------------------------------- /project0/nixpkgs.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://github.com/NixOS/nixpkgs.git", 3 | "rev": "2c288548b93b657365c27a0132a43ba0080870cc", 4 | "date": "2017-01-02T00:10:04+01:00", 5 | "sha256": "1a365am90a1zy99k4qwddj8s3bdlyfisrsq4a3r00kghjcz89zld" 6 | } 7 | -------------------------------------------------------------------------------- /project0/project0.cabal: -------------------------------------------------------------------------------- 1 | name: project0 2 | version: 1.0.0 3 | license: BSD3 4 | license-file: LICENSE 5 | cabal-version: >= 1.18 6 | build-type: Simple 7 | 8 | executable project0 9 | build-depends: base < 5 10 | main-is: Main.hs 11 | default-language: Haskell2010 12 | -------------------------------------------------------------------------------- /project0/project0.nix: -------------------------------------------------------------------------------- 1 | { mkDerivation, base, lib }: 2 | mkDerivation { 3 | pname = "project0"; 4 | version = "1.0.0"; 5 | src = ./.; 6 | isLibrary = false; 7 | isExecutable = true; 8 | executableHaskellDepends = [ base ]; 9 | license = lib.licenses.bsd3; 10 | } 11 | -------------------------------------------------------------------------------- /project0/release0.nix: -------------------------------------------------------------------------------- 1 | let 2 | pkgs = import { }; 3 | 4 | in 5 | pkgs.haskellPackages.callPackage ./project0.nix { } 6 | -------------------------------------------------------------------------------- /project0/release1.nix: -------------------------------------------------------------------------------- 1 | let 2 | bootstrap = import { }; 3 | 4 | nixpkgs = builtins.fromJSON (builtins.readFile ./nixpkgs.json); 5 | 6 | src = bootstrap.fetchFromGitHub { 7 | owner = "NixOS"; 8 | repo = "nixpkgs"; 9 | inherit (nixpkgs) rev sha256; 10 | }; 11 | 12 | pkgs = import src { }; 13 | 14 | in 15 | pkgs.haskellPackages.callPackage ./project0.nix { } 16 | -------------------------------------------------------------------------------- /project0/release2.nix: -------------------------------------------------------------------------------- 1 | let 2 | pkgs = import { }; 3 | 4 | in 5 | { project0 = pkgs.haskellPackages.callPackage ./project0.nix { }; 6 | } 7 | -------------------------------------------------------------------------------- /project1/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Gabriella Gonzalez 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright notice, 7 | this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright notice, 9 | this list of conditions and the following disclaimer in the documentation 10 | and/or other materials provided with the distribution. 11 | * Neither the name of Gabriella Gonzalez nor the names of other contributors 12 | may be used to endorse or promote products derived from this software 13 | without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 22 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /project1/Main.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | module Main where 4 | 5 | import Turtle 6 | 7 | main :: IO () 8 | main = echo "Hello, world!" 9 | -------------------------------------------------------------------------------- /project1/QuickCheck.nix: -------------------------------------------------------------------------------- 1 | { mkDerivation, base, containers, random, lib, template-haskell 2 | , test-framework, tf-random, transformers 3 | }: 4 | mkDerivation { 5 | pname = "QuickCheck"; 6 | version = "2.8.2"; 7 | sha256 = "1ai6k5v0bibaxq8xffcblc6rwmmk6gf8vjyd9p2h3y6vwbhlvilq"; 8 | libraryHaskellDepends = [ 9 | base containers random template-haskell tf-random transformers 10 | ]; 11 | testHaskellDepends = [ 12 | base containers template-haskell test-framework 13 | ]; 14 | homepage = "https://github.com/nick8325/quickcheck"; 15 | description = "Automatic testing of Haskell programs"; 16 | license = lib.licenses.bsd3; 17 | } 18 | -------------------------------------------------------------------------------- /project1/README.md: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | 3 | This project now depends on the `turtle` package in order to illustrate some 4 | dependency management basics. The `Main.hs` executable is a contrived program 5 | that uses `turtle` gratuitously for the `echo` function: 6 | 7 | ```haskell 8 | {-# LANGUAGE OverloadedStrings #-} 9 | 10 | module Main where 11 | 12 | import Turtle 13 | 14 | main :: IO () 15 | main = echo "Hello, world!" 16 | ``` 17 | 18 | The `release0.nix` file is the same as for the previous project except that we 19 | now name our package `project1` instead of `project0`, and we've changed the 20 | name of the corresponding nix file to `project1.nix` rather than `project0.nix`: 21 | 22 | ```nix 23 | let 24 | pkgs = import { }; 25 | 26 | in 27 | { project1 = pkgs.haskellPackages.callPackage ./project1.nix { }; 28 | } 29 | ``` 30 | 31 | ... the contents of our `project1.nix` file have changed as well. This is because our 32 | `project1.cabal` file now has a new `turtle` dependency: 33 | 34 | ```cabal 35 | name: project1 36 | version: 1.0.0 37 | license: BSD3 38 | license-file: LICENSE 39 | cabal-version: >= 1.18 40 | build-type: Simple 41 | 42 | executable project1 43 | build-depends: base < 5, turtle 44 | main-is: Main.hs 45 | default-language: Haskell2010 46 | ``` 47 | 48 | We'll see the corresponding change in the `project1.nix` generated by 49 | `cabal2nix`: 50 | 51 | ```nix 52 | { mkDerivation, base, lib, turtle }: 53 | mkDerivation { 54 | pname = "project1"; 55 | version = "1.0.0"; 56 | src = ./.; 57 | isLibrary = false; 58 | isExecutable = true; 59 | executableHaskellDepends = [ base turtle ]; 60 | license = lib.licenses.bsd3; 61 | } 62 | ``` 63 | 64 | Notice how neither file specifies what version of `turtle` to depend on. This 65 | is because Nix resembles `stack` and provides a curated package set of 66 | Haskell packages that build together. If you don't specify a version then Nix 67 | will pick a version for you. 68 | 69 | You can find the latest curated package set here: 70 | 71 | * [Curated Hackage package set][hackage-packages] 72 | 73 | ... which corresponds roughly to the `nixpkgs-unstable` release. If you would 74 | like to see what package versions Nix selects for a stable release such as 75 | `nixos-18.09`, then change the branch name in the URL from `master` to 76 | `release-18.09`, like this: 77 | 78 | * [Curated Hackage package set for NixOS-18.09][hackage-packages-18.09] 79 | 80 | These curated package sets correspond roughly to Stackage resolvers. Stable 81 | releases like `nixos-18.09` correspond to Stackage LTS resolvers, and 82 | the `nixpkgs-unstable` release corresponds roughly to a Stackage nightly 83 | resolver. The main difference is that Nix's package set curation extends beyond 84 | Haskell packages: `nixpkgs` also curates non-Haskell dependencies, too. 85 | 86 | We can also see which version our project selects if we build our project 87 | using `nix-build`: 88 | 89 | ```bash 90 | $ nix-build --attr project1 release0.nix 91 | these derivations will be built: 92 | /nix/store/8g54hjpim7l9s41c9wpcn1h2q8m254m5-project1-1.0.0.drv 93 | building path(s) ‘/nix/store/pi47yvw46xv346brajyrblwqhjmglhaj-project1-1.0.0’ 94 | ... 95 | Configuring project1-1.0.0... 96 | Dependency base <5: using base-4.9.0.0 97 | Dependency turtle -any: using turtle-1.2.8 98 | ... 99 | /nix/store/pi47yvw46xv346brajyrblwqhjmglhaj-project1-1.0.0 100 | ``` 101 | 102 | The log output from `nix-build` notes that `turtle-1.2.8` was chosen for this 103 | build. Your results might vary depending on which version of the `nixpkgs` 104 | channel that you have installed. 105 | 106 | Note that `nixpkgs` only curates the Haskell package set to build against the 107 | default version of the GHC compiler. You can display the default compiler version 108 | by running the following command: 109 | 110 | ```shell 111 | $ nix-instantiate --eval --expr '(import { }).ghc.version' 112 | ``` 113 | 114 | If you try to change the compiler (as described below) you may need to modify the 115 | Haskell package set to get them to build correctly. 116 | 117 | # Changing versions 118 | 119 | Suppose that we want to build against `turtle-1.3.0` for whatever reason. This 120 | requires a much larger change to our project derivation which we can see in 121 | `release1.nix`: 122 | 123 | ```nix 124 | # Note: This should fail to build 125 | let 126 | config = { 127 | packageOverrides = pkgs: rec { 128 | haskellPackages = pkgs.haskellPackages.override { 129 | overrides = haskellPackagesNew: haskellPackagesOld: rec { 130 | project1 = 131 | haskellPackagesNew.callPackage ./project1.nix { }; 132 | 133 | turtle = 134 | haskellPackagesNew.callPackage ./turtle.nix { }; 135 | }; 136 | }; 137 | }; 138 | }; 139 | 140 | pkgs = import { inherit config; }; 141 | 142 | in 143 | { project1 = pkgs.haskellPackages.project1; 144 | } 145 | ``` 146 | 147 | By default, `import { }` will implicitly use an empty or user-defined 148 | configuration in `~/.nixpkgs/config.nix` or `$NIXPKGS_CONFIG`. The above 149 | configuration overrides that behavior with an explicit `config` defined within 150 | the same file. 151 | 152 | This new derivation now references a `turtle.nix` file generated from 153 | `cabal2nix` by running: 154 | 155 | ```bash 156 | $ cabal2nix cabal://turtle-1.3.2 > turtle.nix 157 | ``` 158 | 159 | If you try to run that and the command fails with this error message: 160 | 161 | ```bash 162 | cabal2nix: ~/.cabal/packages/hackage.haskell.org/00-index.tar: openBinaryFile: does not exist (No such file or directory) 163 | ``` 164 | 165 | ... then run `cabal update` and then the error should disappear. 166 | 167 | The generated `turtle.nix` file looks like this: 168 | 169 | ```nix 170 | { mkDerivation, ansi-wl-pprint, async, base, bytestring, clock 171 | , directory, doctest, foldl, hostname, managed, optional-args 172 | , optparse-applicative, process, lib, stm, system-fileio 173 | , system-filepath, temporary, text, time, transformers, unix 174 | , unix-compat 175 | }: 176 | mkDerivation { 177 | pname = "turtle"; 178 | version = "1.3.2"; 179 | sha256 = "0pbvkqqhiaddyhlqcrk48w7li81dijw92wwhchwqh1my1363n5pq"; 180 | libraryHaskellDepends = [ 181 | ansi-wl-pprint async base bytestring clock directory foldl hostname 182 | managed optional-args optparse-applicative process stm 183 | system-fileio system-filepath temporary text time transformers unix 184 | unix-compat 185 | ]; 186 | testHaskellDepends = [ base doctest ]; 187 | description = "Shell programming, Haskell-style"; 188 | license = lib.licenses.bsd3; 189 | } 190 | ``` 191 | 192 | `nixpkgs` uses a `callPackage` utility function to "tie the knot" when updating 193 | dependencies. When we change`turtle` this way every package that depends on 194 | `turtle` (including our `project1` package) will pick up this new version of 195 | `turtle`. 196 | 197 | The `nixpkgs` manual suggests an alternative approach of specifying package 198 | overrides in a shared `~/.nixpkgs/config.nix` configuration file. However, I do 199 | not recommend this approach: project build instructions should be checked into 200 | version control alongside the project so that they stay in sync with the 201 | project. 202 | 203 | At the time of this writing, if you try to build `release1.nix` then you will 204 | get the following build error: 205 | 206 | ```bash 207 | $ nix-build --attr project1 release1.nix 208 | these derivations will be built: 209 | /nix/store/r780xwf197a2gxn3008raq5k6xxid8mh-turtle-1.3.0.drv 210 | /nix/store/y6g5ya8lis6250fcj0mrw1kybjdara9i-project1-1.0.0.drv 211 | building path(s) ‘/nix/store/qmyqayhvy4rxhfkxpdvc1ayc1vyp8nmw-turtle-1.3.0’ 212 | ... 213 | Configuring turtle-1.3.0... 214 | Setup: Encountered missing dependencies: 215 | optparse-applicative ==0.13.* 216 | builder for ‘/nix/store/r780xwf197a2gxn3008raq5k6xxid8mh-turtle-1.3.0.drv’ failed with exit code 1 217 | cannot build derivation ‘/nix/store/y6g5ya8lis6250fcj0mrw1kybjdara9i-project1-1.0.0.drv’: 1 dependencies couldn't be built 218 | error: build of ‘/nix/store/y6g5ya8lis6250fcj0mrw1kybjdara9i-project1-1.0.0.drv’ failed 219 | ``` 220 | 221 | This error indicates that we can't upgrade `turtle-1.3.0` alone because 222 | `turtle-1.3.0` depends on `optparse-applicative-0.13.*` and the default version 223 | of `optparse-applicative` that Nix selects is not in this range. At the 224 | time of this writing, Nix picks `optparse-applicative-0.12.1.0` as the default 225 | version. 226 | 227 | We can override the `optparse-applicative` version using the exact same trick 228 | and `release2.nix` contains this additional override: 229 | 230 | ```nix 231 | # Note: This should also fail to build 232 | let 233 | config = { 234 | packageOverrides = pkgs: rec { 235 | haskellPackages = pkgs.haskellPackages.override { 236 | overrides = haskellPackagesNew: haskellPackagesOld: rec { 237 | optparse-applicative = 238 | haskellPackagesNew.callPackage ./optparse-applicative.nix { }; 239 | 240 | project1 = 241 | haskellPackagesNew.callPackage ./project1.nix { }; 242 | 243 | turtle = 244 | haskellPackagesNew.callPackage ./turtle.nix { }; 245 | }; 246 | }; 247 | }; 248 | }; 249 | 250 | pkgs = import { inherit config; }; 251 | 252 | in 253 | { project1 = pkgs.haskellPackages.project1; 254 | } 255 | ``` 256 | 257 | ... where `./optparse-applicative.nix` is also generated by `cabal2nix`: 258 | 259 | ```bash 260 | $ cabal2nix cabal://optparse-applicative-0.13.0.0 > optparse-applicative.nix 261 | ``` 262 | 263 | Now that we've overriden `optparse-applicative`, we'll run into a different 264 | build failure if we are using a sufficiently new revision of `nixpkgs`: 265 | 266 | ```bash 267 | $ nix-build --attr project1 release2.nix 268 | ... 269 | Configuring optparse-applicative-0.13.0.0... 270 | Setup: Encountered missing dependencies: 271 | QuickCheck ==2.8.* 272 | ... 273 | ``` 274 | 275 | Newer versions of `nixpkgs` select a newer version of `QuickCheck` than what 276 | `turtle-1.3.2` requires, so if we want to build against that specific version 277 | of `turtle` then we need to also pin `QuickCheck` in the same way in 278 | `release2a.nix`: 279 | 280 | ```nix 281 | # Note: This should also fail to build 282 | let 283 | config = { 284 | packageOverrides = pkgs: rec { 285 | haskellPackages = pkgs.haskellPackages.override { 286 | overrides = haskellPackagesNew: haskellPackagesOld: rec { 287 | optparse-applicative = 288 | haskellPackagesNew.callPackage ./optparse-applicative.nix { }; 289 | 290 | QuickCheck = 291 | haskellPackagesNew.callPackage ./QuickCheck.nix { }; 292 | 293 | project1 = 294 | haskellPackagesNew.callPackage ./project1.nix { }; 295 | 296 | turtle = 297 | haskellPackagesNew.callPackage ./turtle.nix { }; 298 | }; 299 | }; 300 | }; 301 | }; 302 | 303 | pkgs = import { inherit config; }; 304 | 305 | in 306 | { project1 = pkgs.haskellPackages.project1; 307 | } 308 | ``` 309 | 310 | ... where `QuickCheck.nix` is generated by running: 311 | 312 | ```bash 313 | $ cabal2nix cabal://QuickCheck-2.8.2 > QuickCheck.nix 314 | ``` 315 | 316 | However, that will still fail to build due to a test suite failure in 317 | `optparse-applicative-0.13.0.0`: 318 | 319 | ```bash 320 | $ nix-build --attr project1 release2a.nix 321 | these derivations will be built: 322 | /nix/store/55b9hxwvknznfdqcksdfp8fqxifgw00p-optparse-applicative-0.13.0.0.drv 323 | /nix/store/9m816lg47c6wcgmjqpfxsyml4hgc739d-turtle-1.3.0.drv 324 | /nix/store/las95xi2lzn1lfc75k6acv05yvcw1cc2-project1-1.0.0.drv 325 | building path(s) ‘/nix/store/bgam9vpvqx5j4x8fkasqwws54cqb0pbs-optparse-applicative-0.13.0.0’ 326 | ... 327 | Running 1 test suites... 328 | Test suite optparse-applicative-tests: RUNNING... 329 | ... 330 | === prop_drops_back_contexts from tests/test.hs:153 === 331 | *** Failed! Exception: 'tests/dropback.err.txt: openFile: does not exist (No such file or directory)' (after 1 test): 332 | 333 | === prop_context_carry from tests/test.hs:162 === 334 | *** Failed! Exception: 'tests/carry.err.txt: openFile: does not exist (No such file or directory)' (after 1 test): 335 | 336 | === prop_help_on_empty from tests/test.hs:171 === 337 | *** Failed! Exception: 'tests/helponempty.err.txt: openFile: does not exist (No such file or directory)' (after 1 test): 338 | 339 | === prop_help_on_empty_sub from tests/test.hs:180 === 340 | *** Failed! Exception: 'tests/helponemptysub.err.txt: openFile: does not exist (No such file or directory)' (after 1 test): 341 | ... 342 | Test suite optparse-applicative-tests: FAIL 343 | Test suite logged to: 344 | dist/test/optparse-applicative-0.13.0.0-optparse-applicative-tests.log 345 | 0 of 1 test suites (0 of 1 test cases) passed. 346 | builder for ‘/nix/store/55b9hxwvknznfdqcksdfp8fqxifgw00p-optparse-applicative-0.13.0.0.drv’ failed with exit code 1 347 | cannot build derivation ‘/nix/store/9m816lg47c6wcgmjqpfxsyml4hgc739d-turtle-1.3.0.drv’: 1 dependencies couldn't be built 348 | cannot build derivation ‘/nix/store/las95xi2lzn1lfc75k6acv05yvcw1cc2-project1-1.0.0.drv’: 1 dependencies couldn't be built 349 | error: build of ‘/nix/store/las95xi2lzn1lfc75k6acv05yvcw1cc2-project1-1.0.0.drv’ failed 350 | ``` 351 | 352 | However, we can instruct `cabal2nix` to disable the test suite for our 353 | `optparse-applicative` applicative dependency by running: 354 | 355 | ```bash 356 | $ cabal2nix --no-check cabal://optparse-applicative-0.13.0.0 > optparse-applicative-2.nix 357 | ``` 358 | 359 | `release3.nix` uses this test-free `optparse-applicative-2.nix` file: 360 | 361 | ```nix 362 | let 363 | config = { 364 | packageOverrides = pkgs: rec { 365 | haskellPackages = pkgs.haskellPackages.override { 366 | overrides = haskellPackagesNew: haskellPackagesOld: rec { 367 | optparse-applicative = 368 | haskellPackagesNew.callPackage ./optparse-applicative-2.nix { }; 369 | 370 | project1 = 371 | haskellPackagesNew.callPackage ./project1.nix { }; 372 | 373 | turtle = 374 | haskellPackagesNew.callPackage ./turtle.nix { }; 375 | }; 376 | }; 377 | }; 378 | }; 379 | 380 | pkgs = import { inherit config; }; 381 | 382 | in 383 | { project1 = pkgs.haskellPackages.project1; 384 | } 385 | ``` 386 | 387 | ... and now the build succeeds: 388 | 389 | ```bash 390 | $ nix-build --attr project1 release3.nix 391 | these derivations will be built: 392 | /nix/store/f17zxqqk582my4qfig78yvi6nv0vb588-turtle-1.3.0.tar.gz.drv 393 | /nix/store/vn2m1agg92fbmwj1h9168jywy5wmarah-turtle-1.3.0.drv 394 | /nix/store/c3vidydlyaa25kq2zs35kx80vv8sz1mk-project1-1.0.0.drv 395 | these paths will be fetched (1.60 MiB download, 16.92 MiB unpacked): 396 | /nix/store/9kyi7kr3gzpxns4qalda26ww6jfzbpc7-syb-0.6 397 | /nix/store/9v640kglbndlrfpr6wpsq626gb2amx40-libssh2-1.7.0-dev 398 | /nix/store/cm1561r3fs320571n7cicggmjlgy3rdi-unix-compat-0.4.2.0 399 | /nix/store/hfy7dnf85gp1j8dqlhc2v0fdpsmc02vc-nghttp2-1.10.0 400 | /nix/store/kmqlvjmkrg66f5pnbx5jknfiyqf09jgx-curl-7.51.0-dev 401 | /nix/store/l06y84abvix2qmsxs5dbxllfsv9aznfk-hscolour-1.24.1 402 | /nix/store/pq0dcikqwvxxpiknmwki77nyalba3n4a-doctest-0.11.0 403 | /nix/store/v0wwvy3ygb52flq49z1yk445w2126rhs-base-compat-0.9.1 404 | /nix/store/vxqdcfj9kf0k1qvladvxl6wm21np8czh-optparse-applicative-0.13.0.0 405 | /nix/store/w5v8fjg704a5r38sk5dnf3zz7r8vapac-libev-4.22 406 | /nix/store/wyvv44zpn9j2fn30m2k2kry2cycqqlqv-ghc-paths-0.1.0.9 407 | /nix/store/xs8caklddfbmnvkgwcn38m0d7ivxdlq9-mirrors-list 408 | /nix/store/zdkjkdq4fcc6cwnxddm5mxa64n4pavb8-nghttp2-1.10.0-dev 409 | ... 410 | building path(s) ‘/nix/store/frdmdrpr7rgv24i8p5qs5ifvcxz2jm6d-turtle-1.3.0’ 411 | ... 412 | Configuring turtle-1.3.0... 413 | Dependency ansi-wl-pprint ==0.6.*: using ansi-wl-pprint-0.6.7.3 414 | Dependency async >=2.0.0.0 && <2.2: using async-2.1.0 415 | Dependency base >=4.6 && <5: using base-4.9.0.0 416 | Dependency bytestring >=0.9.1.8 && <0.11: using bytestring-0.10.8.1 417 | Dependency clock >=0.4.1.2 && <0.8: using clock-0.7.2 418 | Dependency directory >=1.0.7 && <1.3: using directory-1.2.6.2 419 | Dependency doctest >=0.7 && <0.12: using doctest-0.11.0 420 | Dependency foldl >=1.1 && <1.3: using foldl-1.2.1 421 | Dependency hostname <1.1: using hostname-1.0 422 | Dependency managed >=1.0.3 && <1.1: using managed-1.0.5 423 | Dependency optional-args >=1.0 && <2.0: using optional-args-1.0.1 424 | Dependency optparse-applicative ==0.13.*: using optparse-applicative-0.13.0.0 425 | Dependency process >=1.0.1.1 && <1.5: using process-1.4.2.0 426 | Dependency stm <2.5: using stm-2.4.4.1 427 | Dependency system-fileio >=0.2.1 && <0.4: using system-fileio-0.3.16.3 428 | Dependency system-filepath >=0.3.1 && <0.5: using system-filepath-0.4.13.4 429 | Dependency temporary <1.3: using temporary-1.2.0.4 430 | Dependency text <1.3: using text-1.2.2.1 431 | Dependency time <1.7: using time-1.6.0.1 432 | Dependency transformers >=0.2.0.0 && <0.6: using transformers-0.5.2.0 433 | Dependency turtle -any: using turtle-1.3.0 434 | Dependency unix >=2.5.1.0 && <2.8: using unix-2.7.2.0 435 | Dependency unix-compat ==0.4.*: using unix-compat-0.4.2.0 436 | ... 437 | Building turtle-1.3.0... 438 | Preprocessing library turtle-1.3.0... 439 | [ 1 of 10] Compiling Turtle.Internal ( src/Turtle/Internal.hs, dist/build/Turtle/Internal.o ) 440 | [ 2 of 10] Compiling Turtle.Line ( src/Turtle/Line.hs, dist/build/Turtle/Line.o ) 441 | [ 3 of 10] Compiling Turtle.Shell ( src/Turtle/Shell.hs, dist/build/Turtle/Shell.o ) 442 | [ 4 of 10] Compiling Turtle.Options ( src/Turtle/Options.hs, dist/build/Turtle/Options.o ) 443 | [ 5 of 10] Compiling Turtle.Pattern ( src/Turtle/Pattern.hs, dist/build/Turtle/Pattern.o ) 444 | [ 6 of 10] Compiling Turtle.Format ( src/Turtle/Format.hs, dist/build/Turtle/Format.o ) 445 | [ 7 of 10] Compiling Turtle.Prelude ( src/Turtle/Prelude.hs, dist/build/Turtle/Prelude.o ) 446 | [ 8 of 10] Compiling Turtle.Bytes ( src/Turtle/Bytes.hs, dist/build/Turtle/Bytes.o ) 447 | [ 9 of 10] Compiling Turtle ( src/Turtle.hs, dist/build/Turtle.o ) 448 | [10 of 10] Compiling Turtle.Tutorial ( src/Turtle/Tutorial.hs, dist/build/Turtle/Tutorial.o ) 449 | Preprocessing test suite 'tests' for turtle-1.3.0... 450 | [1 of 1] Compiling Main ( test/Main.hs, dist/build/tests/tests-tmp/Main.dyn_o ) 451 | Linking dist/build/tests/tests ... 452 | Preprocessing test suite 'regression-broken-pipe' for turtle-1.3.0... 453 | [1 of 1] Compiling Main ( test/RegressionBrokenPipe.hs, dist/build/regression-broken-pipe/regression-broken-pipe-tmp/Main.dyn_o ) 454 | Linking dist/build/regression-broken-pipe/regression-broken-pipe ... 455 | running tests 456 | Running 2 test suites... 457 | Test suite tests: RUNNING... 458 | Test suite tests: PASS 459 | Test suite logged to: dist/test/turtle-1.3.0-tests.log 460 | Test suite regression-broken-pipe: RUNNING... 461 | Test suite regression-broken-pipe: PASS 462 | Test suite logged to: dist/test/turtle-1.3.0-regression-broken-pipe.log 463 | 2 of 2 test suites (2 of 2 test cases) passed. 464 | ... 465 | Configuring project1-1.0.0... 466 | Dependency base <5: using base-4.9.0.0 467 | Dependency turtle -any: using turtle-1.3.0 468 | ... 469 | /nix/store/dv0vxc8pwrl8sdrfhy4y2ajnb1nh6qqy-project1-1.0.0 470 | ``` 471 | 472 | We can look at the difference between `optparse-applicative.nix` and 473 | `optparse-applicative-2.nix` to see what changed: 474 | 475 | ```haskell 476 | $ diff optparse-applicative.nix optparse-applicative-2.nix 477 | 11a12 478 | > doCheck = false; 479 | ``` 480 | 481 | The latter file contains a `doCheck = false;` directive which instructs Nix to 482 | not build and run the test suite. 483 | 484 | # Github dependencies 485 | 486 | Suppose that we wish to retrieve our `turtle` dependency from Github instead of 487 | from Hackage. All we have to do is run: 488 | 489 | ```bash 490 | $ cabal2nix https://github.com/Gabriel439/Haskell-Turtle-Library.git --revision ba9c992933ae625cef40a88ea16ee857d1b93e13 > turtle-2.nix 491 | ``` 492 | 493 | ... replacing `ba9c992933ae625cef40a88ea16ee857d1b93e13` with the revision we 494 | wish to use. You can omit the revision if you want `cabal2nix` to select the 495 | revision of the current `master` branch. 496 | 497 | Result looks like this `turtle-2.nix`: 498 | 499 | ```nix 500 | { mkDerivation, ansi-wl-pprint, async, base, bytestring, clock 501 | , directory, doctest, fetchgit, foldl, hostname, managed 502 | , optional-args, optparse-applicative, process, lib, stm 503 | , system-fileio, system-filepath, temporary, text, time 504 | , transformers, unix, unix-compat 505 | }: 506 | mkDerivation { 507 | pname = "turtle"; 508 | version = "1.3.2"; 509 | src = fetchgit { 510 | url = "https://github.com/Gabriel439/Haskell-Turtle-Library.git"; 511 | sha256 = "0cbs3yi4glqhv3419hxihvpsgcj2h2sirbgym5d45hz4d32n9i67"; 512 | rev = "21b50f09e04b4e149b3c5a5f12405ed608cda2ab"; 513 | }; 514 | libraryHaskellDepends = [ 515 | ansi-wl-pprint async base bytestring clock directory foldl hostname 516 | managed optional-args optparse-applicative process stm 517 | system-fileio system-filepath temporary text time transformers unix 518 | unix-compat 519 | ]; 520 | testHaskellDepends = [ base doctest ]; 521 | description = "Shell programming, Haskell-style"; 522 | license = lib.licenses.bsd3; 523 | } 524 | ``` 525 | 526 | `release4.nix` shows an example of depending on `turtle` from Github. The only 527 | difference is that we now depend on the `turtle-2.nix` file: 528 | 529 | ```bash 530 | diff release3.nix release4.nix 531 | 13c13 532 | < haskellPackagesNew.callPackage ./turtle.nix { }; 533 | --- 534 | > haskellPackagesNew.callPackage ./turtle-2.nix { }; 535 | ``` 536 | 537 | ... and the difference between `turtle.nix` and `turtle-2.nix` is that the 538 | dependency list changed and there is a new `src` field pointing to the Github 539 | repository: 540 | 541 | ```diff 542 | 2,5c2,5 543 | < , directory, doctest, foldl, hostname, managed, optional-args 544 | < , optparse-applicative, process, lib, stm, system-fileio 545 | < , system-filepath, temporary, text, time, transformers, unix 546 | < , unix-compat 547 | --- 548 | > , directory, doctest, fetchgit, foldl, hostname, managed 549 | > , optional-args, optparse-applicative, process, lib, stm 550 | > , system-fileio, system-filepath, temporary, text, time 551 | > , transformers, unix, unix-compat 552 | 10c10,14 553 | < sha256 = "0pbvkqqhiaddyhlqcrk48w7li81dijw92wwhchwqh1my1363n5pq"; 554 | --- 555 | > src = fetchgit { 556 | > url = "https://github.com/Gabriel439/Haskell-Turtle-Library.git"; 557 | > sha256 = "0cbs3yi4glqhv3419hxihvpsgcj2h2sirbgym5d45hz4d32n9i67"; 558 | > rev = "21b50f09e04b4e149b3c5a5f12405ed608cda2ab"; 559 | > }; 560 | ``` 561 | 562 | # Source dependencies 563 | 564 | You can also depend on local source checkouts of a given dependency. For 565 | example, if you checkout the `turtle` repository in some other directory then 566 | all you have to do is run: 567 | 568 | ```haskell 569 | $ cabal2nix /path/to/turtle > turtle.nix 570 | ``` 571 | 572 | ... and then reference `./turtle.nix` in your `release.nix` file. Now your 573 | build will automatically pull in any changes you make to your source checkout of 574 | `turtle`. 575 | 576 | # Changing the compiler 577 | 578 | You can also override the GHC version used to compile your project, which 579 | `release5.nix` illustrates: 580 | 581 | ```nix 582 | { compiler ? "ghc802" }: 583 | 584 | let 585 | config = { 586 | packageOverrides = pkgs: rec { 587 | haskell = pkgs.haskell // { 588 | packages = pkgs.haskell.packages // { 589 | "${compiler}" = pkgs.haskell.packages."${compiler}".override { 590 | overrides = haskellPackagesNew: haskellPackagesOld: rec { 591 | optparse-applicative = 592 | haskellPackagesNew.callPackage ./optparse-applicative-2.nix { }; 593 | 594 | project1 = 595 | haskellPackagesNew.callPackage ./project1.nix { }; 596 | 597 | turtle = 598 | haskellPackagesNew.callPackage ./turtle-2.nix { }; 599 | }; 600 | }; 601 | }; 602 | }; 603 | }; 604 | }; 605 | 606 | pkgs = import { inherit config; }; 607 | 608 | in 609 | { project1 = pkgs.haskell.packages.${compiler}.project1; 610 | } 611 | ``` 612 | 613 | By default, the above project is built using GHC 8.0.2: 614 | 615 | ```bash 616 | $ nix-build --attr project1 release5.nix 617 | ... 618 | Using ghc version 8.0.2 found on system at: 619 | /nix/store/7nl6dii88xd761nnz3xyh11qcnrqvqri-ghc-8.0.2/bin/ghc 620 | Using ghc-pkg version 8.0.2 found on system at: 621 | /nix/store/7nl6dii88xd761nnz3xyh11qcnrqvqri-ghc-8.0.2/bin/ghc-pkg 622 | ... 623 | /nix/store/0mw2v9h6f3fz1p9bwy48qxsr5637550s-project1-1.0.0 624 | ``` 625 | 626 | ... but you can now override the compiler on the command line like this: 627 | 628 | ```bash 629 | $ nix-build --argstr compiler ghc7103 --attr project1 release5.nix 630 | ... 631 | building path(s) ‘/nix/store/2c5w9j0dam8m2pn35jvbq2namf8y723f-optparse-applicative-0.13.0.0’ 632 | setupCompilerEnvironmentPhase 633 | Build with /nix/store/ik664w3cxq2jzr5kby0gwmcm0k96xgmg-ghc-7.10.3. 634 | unpacking sources 635 | unpacking source archive /nix/store/b55m0akvijaxps1lzr424m8y4y4q6awv-optparse-applicative-0.13.0.0.tar.gz 636 | source root is optparse-applicative-0.13.0.0 637 | setting SOURCE_DATE_EPOCH to timestamp 1471231137 of file optparse-applicative-0.13.0.0/tests/test.hs 638 | patching sources 639 | compileBuildDriverPhase 640 | setupCompileFlags: -package-db=/private/var/folders/c9/zf_25xbx7bx8yhsxm4q4vw6m0000gn/T/nix-build-optparse-applicative-0.13.0.0.drv-0/package.conf.d -j8 -threaded 641 | [1 of 1] Compiling Main ( Setup.hs, /private/var/folders/c9/zf_25xbx7bx8yhsxm4q4vw6m0000gn/T/nix-build-optparse-applicative-0.13.0.0.drv-0/Main.o ) 642 | Linking Setup ... 643 | configuring 644 | configureFlags: --verbose --prefix=/nix/store/2c5w9j0dam8m2pn35jvbq2namf8y723f-optparse-applicative-0.13.0.0 --libdir=$prefix/lib/$compiler --libsubdir=$pkgid --with-gcc=clang --package-db=/private/var/folders/c9/zf_25xbx7bx8yhsxm4q4vw6m0000gn/T/nix-build-optparse-applicative-0.13.0.0.drv-0/package.conf.d --ghc-option=-optl=-Wl,-headerpad_max_install_names --disable-split-objs --disable-library-profiling --disable-executable-profiling --enable-shared --disable-coverage --enable-library-vanilla --enable-executable-dynamic --disable-tests 645 | Configuring optparse-applicative-0.13.0.0... 646 | Setup: At least the following dependencies are missing: 647 | semigroups >=0.10 && <0.19 648 | builder for ‘/nix/store/kz5ngjwspkky7677vfcjfkliglsxw8nq-optparse-applicative-0.13.0.0.drv’ failed with exit code 1 649 | cannot build derivation ‘/nix/store/4ll08plvc8wmx31mvvsxs83fg6wp849j-turtle-1.3.2.drv’: 1 dependencies couldn't be built 650 | cannot build derivation ‘/nix/store/q40kf7b4d5xwlrnyrflib3qzdc8vfqly-project1-1.0.0.drv’: 1 dependencies couldn't be built 651 | error: build of ‘/nix/store/q40kf7b4d5xwlrnyrflib3qzdc8vfqly-project1-1.0.0.drv’ failed 652 | ``` 653 | 654 | Note that the build may fail when you downgrade the compiler. For example, this 655 | build fails because of a missing `semigroups` dependency. This is because of the 656 | following clause in the `optparse-applicative.cabal` file: 657 | 658 | ``` 659 | if !impl(ghc >= 8) 660 | build-depends: semigroups >= 0.10 && < 0.19 661 | ``` 662 | 663 | ... which `cabal2nix` ignores because it assumes that the current GHC version is 664 | GHC 8. However, we can tweak our `optparse-applicative` dependency to manually 665 | add the `semigroups` dependency in `release6.nix`: 666 | 667 | ```haskell 668 | { compiler ? "ghc802" }: 669 | 670 | let 671 | config = { 672 | packageOverrides = pkgs: rec { 673 | haskell = pkgs.haskell // { 674 | packages = pkgs.haskell.packages // { 675 | "${compiler}" = pkgs.haskell.packages."${compiler}".override { 676 | overrides = haskellPackagesNew: haskellPackagesOld: rec { 677 | optparse-applicative = 678 | pkgs.haskell.lib.addBuildDepend 679 | (haskellPackagesNew.callPackage ./optparse-applicative-2.nix { }) 680 | haskellPackagesNew.semigroups; 681 | 682 | project1 = 683 | haskellPackagesNew.callPackage ./project1.nix { }; 684 | 685 | turtle = 686 | haskellPackagesNew.callPackage ./turtle-2.nix { }; 687 | }; 688 | }; 689 | }; 690 | }; 691 | }; 692 | }; 693 | 694 | pkgs = import { inherit config; }; 695 | 696 | in 697 | { project1 = pkgs.haskell.packages.${compiler}.project1; 698 | } 699 | ``` 700 | 701 | ... and now the build succeeds: 702 | 703 | ```bash 704 | $ nix-build --argstr compiler ghc7103 --attr project1 release6.nix 705 | ... 706 | Using ghc version 7.10.3 found on system at: 707 | /nix/store/ik664w3cxq2jzr5kby0gwmcm0k96xgmg-ghc-7.10.3/bin/ghc 708 | Using ghc-pkg version 7.10.3 found on system at: 709 | /nix/store/ik664w3cxq2jzr5kby0gwmcm0k96xgmg-ghc-7.10.3/bin/ghc-pkg 710 | ... 711 | /nix/store/9fmspa6dz93vg84d2c5bl9y1hszxmk7v-project1-1.0.0 712 | ``` 713 | 714 | The [fourth section](../project3/README.md) of this tutorial contains more details 715 | on how to tweak Haskell builds. 716 | 717 | # Conclusion 718 | 719 | This concludes basic dependency management in Nix. The 720 | [next section](../project2/README.md) covers using Nix to manage non-Haskell 721 | dependencies. 722 | 723 | [hackage-packages]: https://raw.githubusercontent.com/NixOS/nixpkgs/master/pkgs/development/haskell-modules/hackage-packages.nix 724 | [hackage-packages-18.09]: https://raw.githubusercontent.com/NixOS/nixpkgs/release-18.09/pkgs/development/haskell-modules/hackage-packages.nix 725 | -------------------------------------------------------------------------------- /project1/optparse-applicative-2.nix: -------------------------------------------------------------------------------- 1 | { mkDerivation, ansi-wl-pprint, base, process, QuickCheck, lib 2 | , transformers, transformers-compat 3 | }: 4 | mkDerivation { 5 | pname = "optparse-applicative"; 6 | version = "0.13.0.0"; 7 | sha256 = "1b0c5fdq8bd070g24vrjrwlq979r8dk8mys6aji9hy1l9pcv3inf"; 8 | libraryHaskellDepends = [ 9 | ansi-wl-pprint base process transformers transformers-compat 10 | ]; 11 | testHaskellDepends = [ base QuickCheck ]; 12 | doCheck = false; 13 | homepage = "https://github.com/pcapriotti/optparse-applicative"; 14 | description = "Utilities and combinators for parsing command line options"; 15 | license = lib.licenses.bsd3; 16 | } 17 | -------------------------------------------------------------------------------- /project1/optparse-applicative.nix: -------------------------------------------------------------------------------- 1 | { mkDerivation, ansi-wl-pprint, base, process, QuickCheck, lib 2 | , transformers, transformers-compat 3 | }: 4 | mkDerivation { 5 | pname = "optparse-applicative"; 6 | version = "0.13.0.0"; 7 | sha256 = "1b0c5fdq8bd070g24vrjrwlq979r8dk8mys6aji9hy1l9pcv3inf"; 8 | libraryHaskellDepends = [ 9 | ansi-wl-pprint base process transformers transformers-compat 10 | ]; 11 | testHaskellDepends = [ base QuickCheck ]; 12 | homepage = "https://github.com/pcapriotti/optparse-applicative"; 13 | description = "Utilities and combinators for parsing command line options"; 14 | license = lib.licenses.bsd3; 15 | } 16 | -------------------------------------------------------------------------------- /project1/project1.cabal: -------------------------------------------------------------------------------- 1 | name: project1 2 | version: 1.0.0 3 | license: BSD3 4 | license-file: LICENSE 5 | cabal-version: >= 1.18 6 | build-type: Simple 7 | 8 | executable project1 9 | build-depends: base < 5, turtle 10 | main-is: Main.hs 11 | default-language: Haskell2010 12 | -------------------------------------------------------------------------------- /project1/project1.nix: -------------------------------------------------------------------------------- 1 | { mkDerivation, base, lib, turtle }: 2 | mkDerivation { 3 | pname = "project1"; 4 | version = "1.0.0"; 5 | src = ./.; 6 | isLibrary = false; 7 | isExecutable = true; 8 | executableHaskellDepends = [ base turtle ]; 9 | license = lib.licenses.bsd3; 10 | } 11 | -------------------------------------------------------------------------------- /project1/release0.nix: -------------------------------------------------------------------------------- 1 | let 2 | pkgs = import { }; 3 | 4 | in 5 | { project1 = pkgs.haskellPackages.callPackage ./project1.nix { }; 6 | } 7 | -------------------------------------------------------------------------------- /project1/release1.nix: -------------------------------------------------------------------------------- 1 | # Note: This should fail to build 2 | let 3 | config = { 4 | packageOverrides = pkgs: rec { 5 | haskellPackages = pkgs.haskellPackages.override { 6 | overrides = haskellPackagesNew: haskellPackagesOld: rec { 7 | project1 = 8 | haskellPackagesNew.callPackage ./project1.nix { }; 9 | 10 | turtle = 11 | haskellPackagesNew.callPackage ./turtle.nix { }; 12 | }; 13 | }; 14 | }; 15 | }; 16 | 17 | pkgs = import { inherit config; }; 18 | 19 | in 20 | { project1 = pkgs.haskellPackages.project1; 21 | } 22 | -------------------------------------------------------------------------------- /project1/release2.nix: -------------------------------------------------------------------------------- 1 | # Note: This should also fail to build 2 | let 3 | config = { 4 | packageOverrides = pkgs: rec { 5 | haskellPackages = pkgs.haskellPackages.override { 6 | overrides = haskellPackagesNew: haskellPackagesOld: rec { 7 | optparse-applicative = 8 | haskellPackagesNew.callPackage ./optparse-applicative.nix { }; 9 | 10 | project1 = 11 | haskellPackagesNew.callPackage ./project1.nix { }; 12 | 13 | turtle = 14 | haskellPackagesNew.callPackage ./turtle.nix { }; 15 | }; 16 | }; 17 | }; 18 | }; 19 | 20 | pkgs = import { inherit config; }; 21 | 22 | in 23 | { project1 = pkgs.haskellPackages.project1; 24 | } 25 | -------------------------------------------------------------------------------- /project1/release2a.nix: -------------------------------------------------------------------------------- 1 | # Note: This should also fail to build 2 | let 3 | config = { 4 | packageOverrides = pkgs: rec { 5 | haskellPackages = pkgs.haskellPackages.override { 6 | overrides = haskellPackagesNew: haskellPackagesOld: rec { 7 | optparse-applicative = 8 | haskellPackagesNew.callPackage ./optparse-applicative.nix { }; 9 | 10 | QuickCheck = 11 | haskellPackagesNew.callPackage ./QuickCheck.nix { }; 12 | 13 | project1 = 14 | haskellPackagesNew.callPackage ./project1.nix { }; 15 | 16 | turtle = 17 | haskellPackagesNew.callPackage ./turtle.nix { }; 18 | }; 19 | }; 20 | }; 21 | }; 22 | 23 | pkgs = import { inherit config; }; 24 | 25 | in 26 | { project1 = pkgs.haskellPackages.project1; 27 | } 28 | -------------------------------------------------------------------------------- /project1/release3.nix: -------------------------------------------------------------------------------- 1 | let 2 | config = { 3 | packageOverrides = pkgs: rec { 4 | haskellPackages = pkgs.haskellPackages.override { 5 | overrides = haskellPackagesNew: haskellPackagesOld: rec { 6 | optparse-applicative = 7 | haskellPackagesNew.callPackage ./optparse-applicative-2.nix { }; 8 | 9 | project1 = 10 | haskellPackagesNew.callPackage ./project1.nix { }; 11 | 12 | turtle = 13 | haskellPackagesNew.callPackage ./turtle.nix { }; 14 | }; 15 | }; 16 | }; 17 | }; 18 | 19 | pkgs = import { inherit config; }; 20 | 21 | in 22 | { project1 = pkgs.haskellPackages.project1; 23 | } 24 | -------------------------------------------------------------------------------- /project1/release4.nix: -------------------------------------------------------------------------------- 1 | let 2 | config = { 3 | packageOverrides = pkgs: rec { 4 | haskellPackages = pkgs.haskellPackages.override { 5 | overrides = haskellPackagesNew: haskellPackagesOld: rec { 6 | optparse-applicative = 7 | haskellPackagesNew.callPackage ./optparse-applicative-2.nix { }; 8 | 9 | project1 = 10 | haskellPackagesNew.callPackage ./project1.nix { }; 11 | 12 | turtle = 13 | haskellPackagesNew.callPackage ./turtle-2.nix { }; 14 | }; 15 | }; 16 | }; 17 | }; 18 | 19 | pkgs = import { inherit config; }; 20 | 21 | in 22 | { project1 = pkgs.haskellPackages.project1; 23 | } 24 | -------------------------------------------------------------------------------- /project1/release5.nix: -------------------------------------------------------------------------------- 1 | { compiler ? "ghc802" }: 2 | 3 | let 4 | config = { 5 | packageOverrides = pkgs: rec { 6 | haskell = pkgs.haskell // { 7 | packages = pkgs.haskell.packages // { 8 | "${compiler}" = pkgs.haskell.packages."${compiler}".override { 9 | overrides = haskellPackagesNew: haskellPackagesOld: rec { 10 | optparse-applicative = 11 | haskellPackagesNew.callPackage ./optparse-applicative-2.nix { }; 12 | 13 | project1 = 14 | haskellPackagesNew.callPackage ./project1.nix { }; 15 | 16 | turtle = 17 | haskellPackagesNew.callPackage ./turtle-2.nix { }; 18 | }; 19 | }; 20 | }; 21 | }; 22 | }; 23 | }; 24 | 25 | pkgs = import { inherit config; }; 26 | 27 | in 28 | { project1 = pkgs.haskell.packages.${compiler}.project1; 29 | } 30 | -------------------------------------------------------------------------------- /project1/release6.nix: -------------------------------------------------------------------------------- 1 | { compiler ? "ghc802" }: 2 | 3 | let 4 | config = { 5 | packageOverrides = pkgs: rec { 6 | haskell = pkgs.haskell // { 7 | packages = pkgs.haskell.packages // { 8 | "${compiler}" = pkgs.haskell.packages."${compiler}".override { 9 | overrides = haskellPackagesNew: haskellPackagesOld: rec { 10 | optparse-applicative = 11 | pkgs.haskell.lib.addBuildDepend 12 | (haskellPackagesNew.callPackage ./optparse-applicative-2.nix { }) 13 | haskellPackagesNew.semigroups; 14 | 15 | project1 = 16 | haskellPackagesNew.callPackage ./project1.nix { }; 17 | 18 | turtle = 19 | haskellPackagesNew.callPackage ./turtle-2.nix { }; 20 | }; 21 | }; 22 | }; 23 | }; 24 | }; 25 | }; 26 | 27 | pkgs = import { inherit config; }; 28 | 29 | in 30 | { project1 = pkgs.haskell.packages.${compiler}.project1; 31 | } 32 | -------------------------------------------------------------------------------- /project1/turtle-2.nix: -------------------------------------------------------------------------------- 1 | { mkDerivation, ansi-wl-pprint, async, base, bytestring, clock 2 | , directory, doctest, fetchgit, foldl, hostname, managed 3 | , optional-args, optparse-applicative, process, lib, stm 4 | , system-fileio, system-filepath, temporary, text, time 5 | , transformers, unix, unix-compat 6 | }: 7 | mkDerivation { 8 | pname = "turtle"; 9 | version = "1.3.2"; 10 | src = fetchgit { 11 | url = "https://github.com/Gabriel439/Haskell-Turtle-Library.git"; 12 | sha256 = "0cbs3yi4glqhv3419hxihvpsgcj2h2sirbgym5d45hz4d32n9i67"; 13 | rev = "21b50f09e04b4e149b3c5a5f12405ed608cda2ab"; 14 | }; 15 | libraryHaskellDepends = [ 16 | ansi-wl-pprint async base bytestring clock directory foldl hostname 17 | managed optional-args optparse-applicative process stm 18 | system-fileio system-filepath temporary text time transformers unix 19 | unix-compat 20 | ]; 21 | testHaskellDepends = [ base doctest ]; 22 | description = "Shell programming, Haskell-style"; 23 | license = lib.licenses.bsd3; 24 | } 25 | -------------------------------------------------------------------------------- /project1/turtle.nix: -------------------------------------------------------------------------------- 1 | { mkDerivation, ansi-wl-pprint, async, base, bytestring, clock 2 | , directory, doctest, foldl, hostname, managed, optional-args 3 | , optparse-applicative, process, lib, stm, system-fileio 4 | , system-filepath, temporary, text, time, transformers, unix 5 | , unix-compat 6 | }: 7 | mkDerivation { 8 | pname = "turtle"; 9 | version = "1.3.2"; 10 | sha256 = "0pbvkqqhiaddyhlqcrk48w7li81dijw92wwhchwqh1my1363n5pq"; 11 | libraryHaskellDepends = [ 12 | ansi-wl-pprint async base bytestring clock directory foldl hostname 13 | managed optional-args optparse-applicative process stm 14 | system-fileio system-filepath temporary text time transformers unix 15 | unix-compat 16 | ]; 17 | testHaskellDepends = [ base doctest ]; 18 | description = "Shell programming, Haskell-style"; 19 | license = lib.licenses.bsd3; 20 | } 21 | -------------------------------------------------------------------------------- /project2/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Gabriella Gonzalez 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright notice, 7 | this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright notice, 9 | this list of conditions and the following disclaimer in the documentation 10 | and/or other materials provided with the distribution. 11 | * Neither the name of Gabriella Gonzalez nor the names of other contributors 12 | may be used to endorse or promote products derived from this software 13 | without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 22 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /project2/Main.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | foreign import ccall "check" check :: IO () 4 | 5 | main :: IO () 6 | main = check 7 | -------------------------------------------------------------------------------- /project2/README.md: -------------------------------------------------------------------------------- 1 | # Native dependencies 2 | 3 | Nix can also provision non-Haskell dependencies for your project. This project 4 | provides an example of a small program that invokes `libtar` to open and close 5 | a TAR file. 6 | 7 | The first difference is that we have to extend the `cabal` file to add an 8 | `extra-libraries` clause: 9 | 10 | ```cabal 11 | name: project2 12 | version: 1.0.0 13 | license: BSD3 14 | license-file: LICENSE 15 | cabal-version: >= 1.18 16 | build-type: Simple 17 | extra-source-files: cbits 18 | 19 | executable project2 20 | build-depends: base < 5 21 | default-language: Haskell2010 22 | c-sources: cbits/check.c 23 | extra-libraries: tar 24 | main-is: Main.hs 25 | ``` 26 | 27 | We also use the `c-sources` line so that Cabal can build and link in a small 28 | test C program located at `cbits/check.c` for demonstration purposes. This 29 | program uses `libtar` to open and close a tar file named `example.tar`: 30 | 31 | ```c 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | void check() { 38 | int ret; 39 | TAR *t; 40 | 41 | ret = tar_open(&t, "example.tar", NULL, O_RDONLY, 0, TAR_VERBOSE); 42 | if (ret == -1) { 43 | fprintf(stderr, "tar_open failed\n"); 44 | exit(1); 45 | } 46 | printf("Successfully opened example.tar\n"); 47 | 48 | ret = tar_close(t); 49 | if (ret == -1) { 50 | fprintf(stderr, "tar_close failed\n"); 51 | exit(1); 52 | } 53 | printf("Successfully closed example.tar\n"); 54 | } 55 | ``` 56 | 57 | ... and then we refer to the `check` function from the Haskell code like this: 58 | 59 | ```haskell 60 | module Main where 61 | 62 | foreign import ccall "check" check :: IO () 63 | 64 | main :: IO () 65 | main = check 66 | ``` 67 | 68 | Like before, we transform the `project2.cabal` file into a Nix expression by 69 | running `cabal2nix`: 70 | 71 | ```bash 72 | $ cabal2nix . > project2.nix 73 | ``` 74 | 75 | `cabal2nix` is smart and notices that our `project2.cabal` file has an 76 | `extra-libraries` section and adds `tar` as a dependency of our package in 77 | `project2.nix`: 78 | 79 | ```nix 80 | { mkDerivation, base, lib, tar }: 81 | mkDerivation { 82 | pname = "project2"; 83 | version = "1.0.0"; 84 | src = ./.; 85 | isLibrary = false; 86 | isExecutable = true; 87 | executableHaskellDepends = [ base ]; 88 | executableSystemDepends = [ tar ]; 89 | license = lib.licenses.bsd3; 90 | } 91 | ``` 92 | 93 | The `tar` dependency goes in the `executableSystemDepends` section, which 94 | indicates that this is a dependency on a native library and not a Haskell 95 | dependency. 96 | 97 | However, this alone is not enough to thread `libtar` to our package. We need 98 | to update our `release.nix` to specify which Nix package the `tar` dependency 99 | comes from: 100 | 101 | ```nix 102 | let 103 | config = { 104 | packageOverrides = pkgs: rec { 105 | haskellPackages = pkgs.haskellPackages.override { 106 | overrides = haskellPackagesNew: haskellPackagesOld: rec { 107 | project2 = 108 | haskellPackagesNew.callPackage ./project2.nix { 109 | tar = pkgs.libtar; 110 | }; 111 | }; 112 | }; 113 | }; 114 | }; 115 | 116 | pkgs = import { inherit config; }; 117 | 118 | in 119 | rec { 120 | project2 = pkgs.haskellPackages.project2; 121 | 122 | test = pkgs.runCommand "check" {} '' 123 | touch example 124 | tar cf example.tar example 125 | ${project2}/bin/project2 | tee $out 126 | ''; 127 | } 128 | ``` 129 | 130 | In fact, our `release.nix` file goes a step further and includes two attributes 131 | instead of one: 132 | 133 | * the `project2` attribute builds our project 134 | * the `test` attribute runs a simple test to verify that the program works 135 | 136 | If we only care about building the project, then we would just run: 137 | 138 | ```bash 139 | $ nix-build --attr project2 release.nix 140 | ``` 141 | 142 | ... but if we want to build and test our project, we can run: 143 | 144 | ```bash 145 | $ nix-build --attr test release.nix 146 | these derivations will be built: 147 | /nix/store/6z0jc3s2ya8j226rgma73w03pdphqwx8-project2-1.0.0.drv 148 | /nix/store/k82nnkiszzfcnjxq29gcbmgnh5cbfc51-check.drv 149 | building path(s) ‘/nix/store/83bf1b1cp0qs6jgryb6yr0jfb759x4k5-project2-1.0.0’ 150 | ... 151 | configureFlags: --verbose --prefix=/nix/store/83bf1b1cp0qs6jgryb6yr0jfb759x4k5-project2-1.0.0 --libdir=$prefix/lib/$compiler --libsubdir=$pkgid --with-gcc=gcc --package-db=/tmp/nix-build-project2-1.0.0.drv-0/package.conf.d --ghc-option=-optl=-Wl,-rpath=/nix/store/83bf1b1cp0qs6jgryb6yr0jfb759x4k5-project2-1.0.0/lib/ghc-8.0.1/project2-1.0.0 --ghc-option=-j1 --enable-split-objs --disable-library-profiling --disable-profiling --enable-shared --enable-library-vanilla --enable-executable-dynamic --enable-tests --extra-include-dirs=/nix/store/pb3jxhy4z54i24i9s0kyszdmxd2xajc5-libtar-1.2.20/include --extra-lib-dirs=/nix/store/pb3jxhy4z54i24i9s0kyszdmxd2xajc5-libtar-1.2.20/lib 152 | ... 153 | building path(s) ‘/nix/store/zc7ai4h44fydbm89a85b5bik5683klyk-check’ 154 | Successfully opened example.tar 155 | Successfully closed example.tar 156 | /nix/store/zc7ai4h44fydbm89a85b5bik5683klyk-check 157 | ``` 158 | 159 | Notice how the `configureFlags` passed to `project2` ensure that Nix's `libtar` 160 | is on `cabal`'s search path when building the library. Then the tests are run 161 | and the output is saved to `/nix/store/zc7ai4h44fydbm89a85b5bik5683klyk-check`, 162 | which we can also reference via the `result` symlink that `nix-build` creates: 163 | 164 | ```bash 165 | $ cat result 166 | Successfully opened example.tar 167 | Successfully closed example.tar 168 | ``` 169 | 170 | # Conclusion 171 | 172 | This concludes managing native dependencies using Nix. The next section covers 173 | [customizing Haskell projects](../project3/README.md). 174 | -------------------------------------------------------------------------------- /project2/cbits/check.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | void check() { 7 | int ret; 8 | TAR *t; 9 | 10 | ret = tar_open(&t, "example.tar", NULL, O_RDONLY, 0, TAR_VERBOSE); 11 | if (ret == -1) { 12 | fprintf(stderr, "tar_open failed\n"); 13 | exit(1); 14 | } 15 | printf("Successfully opened example.tar\n"); 16 | 17 | ret = tar_close(t); 18 | if (ret == -1) { 19 | fprintf(stderr, "tar_close failed\n"); 20 | exit(1); 21 | } 22 | printf("Successfully closed example.tar\n"); 23 | } 24 | -------------------------------------------------------------------------------- /project2/project2.cabal: -------------------------------------------------------------------------------- 1 | name: project2 2 | version: 1.0.0 3 | license: BSD3 4 | license-file: LICENSE 5 | cabal-version: >= 1.18 6 | build-type: Simple 7 | extra-source-files: cbits 8 | 9 | executable project2 10 | build-depends: base < 5 11 | default-language: Haskell2010 12 | c-sources: cbits/check.c 13 | extra-libraries: tar 14 | main-is: Main.hs 15 | -------------------------------------------------------------------------------- /project2/project2.nix: -------------------------------------------------------------------------------- 1 | { mkDerivation, base, lib, tar }: 2 | mkDerivation { 3 | pname = "project2"; 4 | version = "1.0.0"; 5 | src = ./.; 6 | isLibrary = false; 7 | isExecutable = true; 8 | executableHaskellDepends = [ base ]; 9 | executableSystemDepends = [ tar ]; 10 | license = lib.licenses.bsd3; 11 | } 12 | -------------------------------------------------------------------------------- /project2/release.nix: -------------------------------------------------------------------------------- 1 | let 2 | config = { 3 | packageOverrides = pkgs: rec { 4 | haskellPackages = pkgs.haskellPackages.override { 5 | overrides = haskellPackagesNew: haskellPackagesOld: rec { 6 | project2 = 7 | haskellPackagesNew.callPackage ./project2.nix { 8 | tar = pkgs.libtar; 9 | }; 10 | }; 11 | }; 12 | }; 13 | }; 14 | 15 | pkgs = import { inherit config; }; 16 | 17 | in 18 | rec { 19 | project2 = pkgs.haskellPackages.project2; 20 | 21 | test = pkgs.runCommand "check" {} '' 22 | touch example 23 | tar cf example.tar example 24 | ${project2}/bin/project2 | tee $out 25 | ''; 26 | } 27 | -------------------------------------------------------------------------------- /project3/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Gabriella Gonzalez 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright notice, 7 | this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright notice, 9 | this list of conditions and the following disclaimer in the documentation 10 | and/or other materials provided with the distribution. 11 | * Neither the name of Gabriella Gonzalez nor the names of other contributors 12 | may be used to endorse or promote products derived from this software 13 | without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 22 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /project3/Main.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import qualified Project3 4 | 5 | main :: IO () 6 | main = Project3.check 7 | -------------------------------------------------------------------------------- /project3/Project3.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE ForeignFunctionInterface #-} 2 | 3 | module Project3 where 4 | 5 | foreign import ccall "check" check :: IO () 6 | -------------------------------------------------------------------------------- /project3/README.md: -------------------------------------------------------------------------------- 1 | # Exploring Haskell packages 2 | 3 | 4 | 5 | This section covers how to tweak Haskell packages that Nix builds (typically 6 | to fix failing builds). 7 | 8 | Begin by running the following command to build the `morte` package: 9 | 10 | ```bash 11 | $ nix-build '' --attr haskellPackages.morte 12 | ... 13 | /nix/store/z8rf74cylf9dqj63yb8p3j37sn8n49zf-morte-1.6.2 14 | ``` 15 | 16 | `nix-build` creates a `result` symlink pointing to the built package. We can 17 | use `tree` to study the directory layout of the built package: 18 | 19 | ```bash 20 | $ tree result 21 | result 22 | ├── bin 23 | │   └── morte 24 | ├── lib 25 | │   └── ghc-8.0.1 26 | │   ├── morte-1.6.2 27 | │   │   ├── libHSmorte-1.6.2-9FIpoXNPAFhL3LxBszO4VO.a 28 | │   │   ├── libHSmorte-1.6.2-9FIpoXNPAFhL3LxBszO4VO-ghc8.0.1.so 29 | │   │   └── Morte 30 | │   │   ├── Context.dyn_hi 31 | │   │   ├── Context.hi 32 | │   │   ├── Core.dyn_hi 33 | │   │   ├── Core.hi 34 | │   │   ├── Import.dyn_hi 35 | │   │   ├── Import.hi 36 | │   │   ├── Lexer.dyn_hi 37 | │   │   ├── Lexer.hi 38 | │   │   ├── Parser.dyn_hi 39 | │   │   ├── Parser.hi 40 | │   │   ├── Tutorial.dyn_hi 41 | │   │   └── Tutorial.hi 42 | │   └── package.conf.d 43 | │   └── morte-1.6.2-9FIpoXNPAFhL3LxBszO4VO.conf 44 | ├── nix-support 45 | │   └── propagated-native-build-inputs 46 | └── share 47 | ├── doc 48 | │   └── x86_64-linux-ghc-8.0.1 49 | │   └── morte-1.6.2 50 | │   ├── html 51 | │   │   ├── doc-index.html 52 | │   │   ├── frames.html 53 | │   │   ├── haddock-util.js 54 | │   │   ├── hslogo-16.png 55 | │   │   ├── index-frames.html 56 | │   │   ├── index.html 57 | │   │   ├── mini_Morte-Context.html 58 | │   │   ├── mini_Morte-Core.html 59 | │   │   ├── mini_Morte-Import.html 60 | │   │   ├── mini_Morte-Lexer.html 61 | │   │   ├── mini_Morte-Parser.html 62 | │   │   ├── mini_Morte-Tutorial.html 63 | │   │   ├── minus.gif 64 | │   │   ├── Morte-Context.html 65 | │   │   ├── Morte-Core.html 66 | │   │   ├── morte.haddock 67 | │   │   ├── Morte-Import.html 68 | │   │   ├── Morte-Lexer.html 69 | │   │   ├── Morte-Parser.html 70 | │   │   ├── Morte-Tutorial.html 71 | │   │   ├── morte.txt 72 | │   │   ├── ocean.css 73 | │   │   ├── plus.gif 74 | │   │   ├── src 75 | │   │   │   ├── hscolour.css 76 | │   │   │   ├── Morte-Context.html 77 | │   │   │   ├── Morte-Core.html 78 | │   │   │   ├── Morte-Import.html 79 | │   │   │   ├── Morte-Lexer.html 80 | │   │   │   ├── Morte-Parser.html 81 | │   │   │   └── Morte-Tutorial.html 82 | │   │   └── synopsis.png 83 | │   └── LICENSE 84 | └── x86_64-linux-ghc-8.0.1 85 | └── morte-1.6.2 86 | ├── bench 87 | │   └── src 88 | │   ├── concat.mt 89 | │   ├── factorial.mt 90 | │   └── recursive.mt 91 | └── test 92 | └── src 93 | ├── example0.mt 94 | ├── example10.mt 95 | ├── example11.mt 96 | ├── example12.mt 97 | ├── example13.mt 98 | ├── example14.mt 99 | ├── example15.mt 100 | ├── example1.mt 101 | ├── example2.mt 102 | ├── example3.mt 103 | ├── example4.mt 104 | ├── example5.mt 105 | ├── example6.mt 106 | ├── example7.mt 107 | ├── example8.mt 108 | └── example9.mt 109 | 110 | 19 directories, 68 files 111 | ``` 112 | 113 | This package has a significantly more complex layout: 114 | 115 | * `bin/` 116 | 117 | This directory stores any executable programs that the Haskell package 118 | produces. Nix does not require that derivations deposit executables in this 119 | directory, but Nix does specially integrate with packages that do use this 120 | directory for their executables. For example, if you use NixOS and you add 121 | a package to `environment.systemPackages` then the `bin/` directory for the 122 | package is added to the system `$PATH`. Similarly, if you add a package as 123 | a `buildInput` to a Nix derivation, then any executables underneath `bin/` 124 | are added to the `$PATH` of the derivation's build script. 125 | 126 | * `lib/ghc-8.0.1/morte-1.6.2/` 127 | 128 | This contains all object code and interface files built by the Haskell 129 | library. By default, Nix generates both a static library (i.e. 130 | `libHSmorte-1.6.2-9FIpoXNPAFhL3LxBszO4VO.a`) and a dynamic library 131 | (i.e. `libHSmorte-1.6.2-9FIpoXNPAFhL3LxBszO4VO-ghc8.0.1.so`) for any 132 | Haskell package that provides a `library` section in the `*.cabal` file 133 | 134 | By default, these libraries are compiled with GHC's `-fsplit-objs` flag to 135 | enable split object files, which reduces the library size but increases the 136 | compile time. This is another reason why I recommend building the root 137 | project using `cabal` during development because by default `cabal` won't 138 | compile with split objects and will therefore build faster. However, 139 | `-fsplit-objs` is a good default when Nix builds the project, so you don't 140 | need to change this setting. 141 | 142 | Nix also stores a "singleton" GHC package database holding just one package 143 | such as `package.conf.d/morte-1.6.2-9FIpoXNPAFhL3LxBszO4VO.conf`. This is 144 | one difference between Nix and `stack`: `stack` uses up to three package 145 | databases (i.e. the global, snapshot, and project databases) whereas Nix 146 | uses a package database per package. In practice this difference usually 147 | doesn't matter, but for more exotic build scenarios (such as compiling 148 | different sets of packages with different flags) this gives Nix greater 149 | flexibility than `stack`. 150 | 151 | * `nix-support/propagated-native-build-inputs` 152 | 153 | Anything which depends on the `morte` library must also depend on packages 154 | listed in `nix-support/propagated-native-build-inputs`. This is how Nix 155 | ensures that you are not missing any libraries when linking packages. For 156 | example, this is what the file looks like for `morte`: 157 | 158 | ```bash 159 | cat result/nix-support/propagated-native-build-inputs 160 | /nix/store/ifgkzb0df0d6378399hny3ry8x530wim-Earley-0.11.0.1 /nix/store/3q914v2ijfj26fv5w9hd07a6lcnr4d89-http-client-0.4.31.1 /nix/store/xp55z3bpf92yaswyd6s9ajzr44w5m1r7-http-client-tls-0.2.4.1 /nix/store/39a8f70x5j09jnixj6cwv9l0n6jdncwb-microlens-0.4.7.0 /nix/store/rqd01fgj53wyc627wvns94j68a5iza07-microlens-mtl-0.1.10.0 /nix/store/x0g5n6k09f6zga000w9y0z1hx4i9ryyx-pipes-4.1.9 /nix/store/gkg8jgn8f8if6j83jhz3v6i5nnqwg8vc-system-fileio-0.3.16.3 /nix/store/x8chw4wp9r4dapsjmbgsbs3blgnmsxy5-system-filepath-0.4.13.4 /nix/store/zx0yg2d5jxwpdig83qica8hq4mv5l0hx-text-1.2.2.1 /nix/store/l4w7l07k9hr1qrmj9772zk60scw5xa4n-text-format-0.3.1.1 /nix/store/0crlsp45jgs2bs50bv68klpdz1b1jpk9-optparse-applicative-0.12.1.0 /nix/store/zx0yg2d5jxwpdig83qica8hq4mv5l0hx-text-1.2.2.1 161 | ``` 162 | 163 | All of those paths are packages that `morte` depends on, each of which 164 | provides object code which must be linked into any executable that depends 165 | on `morte` 166 | 167 | * `share/doc/x86_64-linux-ghc-8.0.1/morte-1.6.2/html` 168 | 169 | These are the generated haddocks for the project. You can open 170 | `share/doc/x86_64-linux-ghc-8.0.1/morte-1.6.2/html/index.html` in your 171 | browser to view `morte`'s documentation 172 | 173 | * `share/doc/x86_64-linux-ghc-8.0.1/morte-1.6.2/LICENSE` 174 | 175 | This is the project license 176 | 177 | * `x86_64-linux-ghc-8.0.1/morte-1.6.2` 178 | 179 | These are extra data files bundled with the project for running tests and 180 | benchmarks. `cabal` includes these files because `morte` has the following 181 | line in the `morte.cabal` file: 182 | 183 | ```cabal 184 | Data-Files: bench/src/*.mt test/src/*.mt 185 | ``` 186 | 187 | # Tweaking Haskell projects 188 | 189 | You can customize what Nix includes in a built Haskell package using the 190 | `pkg.haskell.lib` suite of utilities. For example, `release0.nix` contains an 191 | example of disabling haddocks for the `morte` package: 192 | 193 | ```nix 194 | let 195 | config = { 196 | packageOverrides = pkgs: rec { 197 | haskellPackages = pkgs.haskellPackages.override { 198 | overrides = haskellPackagesNew: haskellPackagesOld: rec { 199 | morte = pkgs.haskell.lib.dontHaddock haskellPackagesOld.morte; 200 | }; 201 | }; 202 | }; 203 | }; 204 | 205 | pkgs = import { inherit config; }; 206 | 207 | in 208 | { morte = pkgs.haskellPackages.morte; 209 | } 210 | ``` 211 | 212 | You can find the full list of `pkgs.haskell.lib` utilities in this file: 213 | 214 | * [Utilities for tweaking Haskell packages][haskell-lib] 215 | 216 | Some really convenient utilities include: 217 | 218 | * `pkgs.haskell.lib.dontCheck` - Disable tests (applies derivation option `doCheck = false;`) 219 | * `pkgs.haskell.lib.appendPatch` - Patch a Haskell package 220 | * `pkgs.haskell.lib.overrideCabal` - General tool for post-processing the output 221 | of `cabal2nix` 222 | 223 | For example, this project is almost identical to the previous project except 224 | that instead of using Nix to test the executable we now add a test suite to our 225 | Haskell package that tests our TAR code in `Test.hs`: 226 | 227 | ```haskell 228 | {-# LANGUAGE OverloadedStrings #-} 229 | 230 | module Main where 231 | 232 | import Control.Applicative (empty) 233 | import qualified Project3 234 | import qualified Turtle 235 | 236 | main :: IO () 237 | main = do 238 | Turtle.touch "example" 239 | Turtle.procs "bsdtar" ["cf", "example.tar", "example"] empty 240 | Project3.check 241 | ``` 242 | 243 | For whatever reason we use `bsdtar` instead of `tar` because we are gluttons for 244 | punishment. 245 | 246 | The `project3.nix` file generated by `cabal2nix` has no idea that our test suite 247 | depends on `bsdtar` so if we were to build and run the Haskell project without 248 | any modifications the test suite would fail due to a missing `bsdtar` 249 | executable. 250 | 251 | However, our `release1.nix` file shows how we can tweak our project to include 252 | `bsdtar` (provided by `libarchive`) for the test suite: 253 | 254 | ```nix 255 | let 256 | config = { 257 | packageOverrides = pkgs: rec { 258 | haskellPackages = pkgs.haskellPackages.override { 259 | overrides = haskellPackagesNew: haskellPackgesOld: rec { 260 | project3 = 261 | pkgs.haskell.lib.overrideCabal 262 | ( haskellPackagesNew.callPackage ./project3.nix { 263 | tar = pkgs.libtar; 264 | } 265 | ) 266 | ( oldDerivation: { 267 | testToolDepends = [ pkgs.libarchive ]; 268 | } 269 | ); 270 | }; 271 | }; 272 | }; 273 | }; 274 | 275 | pkgs = import { inherit config; }; 276 | 277 | in 278 | { project3 = pkgs.haskellPackages.project3; 279 | } 280 | ``` 281 | 282 | This uses the `pkgs.haskell.lib.overrideCabal` which is the most general 283 | utility for tweaking derivations generated by `cabal2nix`. You can find the 284 | full set of fields that we can use to extend the `cabal2nix` code at the top of 285 | this file: 286 | 287 | * [Haskell generic builder][generic-builder] 288 | 289 | These are also the same fields that you will see in an expression generated by 290 | `cabal2nix`, such as `executableHaskellDepends` or `isLibrary`. `overrideCabal` 291 | lets you tweak any field you see in that list. 292 | 293 | If you build and run the `release1.nix` project you will see that the test 294 | suite runs and passes, indicating that our code works with TAR files generated 295 | by `bsdtar`: 296 | 297 | ```bash 298 | $ nix-build --attr project3 release1.nix these derivations will be built: 299 | /nix/store/i7i38jx93qwxzwg9xakbd8lrfv9kbpi4-project3-1.0.0.drv 300 | building path(s) ‘/nix/store/2z661k6rnwyiimympn1anmghdybi7izc-project3-1.0.0’ 301 | ... 302 | Building project3-1.0.0... 303 | Preprocessing library project3-1.0.0... 304 | [1 of 1] Compiling Project3 ( Project3.hs, dist/build/Project3.o ) 305 | Preprocessing executable 'project3' for project3-1.0.0... 306 | [1 of 2] Compiling Project3 ( Project3.hs, dist/build/project3/project3-tmp/Project3.dyn_o ) 307 | [2 of 2] Compiling Main ( Main.hs, dist/build/project3/project3-tmp/Main.dyn_o ) 308 | Linking dist/build/project3/project3 ... 309 | Preprocessing test suite 'test' for project3-1.0.0... 310 | [1 of 2] Compiling Project3 ( Project3.hs, dist/build/test/test-tmp/Project3.dyn_o ) 311 | [2 of 2] Compiling Main ( Test.hs, dist/build/test/test-tmp/Main.dyn_o ) 312 | Linking dist/build/test/test ... 313 | running tests 314 | Running 1 test suites... 315 | Test suite test: RUNNING... 316 | Test suite test: PASS 317 | Test suite logged to: dist/test/project3-1.0.0-test.log 318 | 1 of 1 test suites (1 of 1 test cases) passed. 319 | ... 320 | /nix/store/2z661k6rnwyiimympn1anmghdybi7izc-project3-1.0.0 321 | ``` 322 | 323 | # Minimizing the closure 324 | 325 | In Nix, a package "closure" is the set of all transitive dependencies for that 326 | package. Haskell packages built using Nix have a very large closure because 327 | they all depend on `ghc`, which is a very large package. 328 | 329 | For example, `release2.nix` contains an example of building a `docker` container 330 | for the `project3` executable before and after closure minimization: 331 | 332 | ```nix 333 | let 334 | config = rec { 335 | packageOverrides = pkgs: rec { 336 | docker-container-large = pkgs.dockerTools.buildImage { 337 | name = "project3-container"; 338 | config.Cmd = [ "${haskellPackages.project3}/bin/project3" ]; 339 | }; 340 | 341 | docker-container-small = pkgs.dockerTools.buildImage { 342 | name = "project3-container"; 343 | config.Cmd = [ "${haskellPackages.project3-minimal}/bin/project3" ]; 344 | }; 345 | 346 | haskellPackages = pkgs.haskellPackages.override { 347 | overrides = haskellPackagesNew: haskellPackgesOld: rec { 348 | project3 = 349 | pkgs.haskell.lib.overrideCabal 350 | ( haskellPackagesNew.callPackage ./project3.nix { 351 | tar = pkgs.libtar; 352 | } 353 | ) 354 | ( oldDerivation: { 355 | testToolDepends = [ pkgs.libarchive ]; 356 | enableSharedExecutables = false; 357 | } 358 | ); 359 | 360 | project3-minimal = 361 | pkgs.haskell.lib.overrideCabal 362 | ( pkgs.haskell.lib.justStaticExecutables 363 | ( haskellPackagesNew.callPackage ./project3.nix { 364 | tar = pkgs.libtar; 365 | } 366 | ) 367 | ) 368 | ( oldDerivation: { 369 | testToolDepends = [ pkgs.libarchive ]; 370 | } 371 | ); 372 | }; 373 | }; 374 | }; 375 | }; 376 | 377 | pkgs = import { inherit config; system = "x86_64-linux"; }; 378 | 379 | in 380 | { project3 = pkgs.haskellPackages.project3; 381 | 382 | project3-minimal = pkgs.haskellPackages.project3-minimal; 383 | 384 | docker-container-small = pkgs.docker-container-small; 385 | 386 | docker-container-large = pkgs.docker-container-large; 387 | } 388 | ``` 389 | 390 | If you build the `docker-container-large` attribute you will get a container 391 | that contains the entire closure of the `project03` project, including a 392 | complete `ghc` installation. This is because `project03` is dynamically linked 393 | and depends on several dynamic libraries that are packaged with `ghc`. The 394 | final container is over 250 MB compressed and 1.5 GB uncompressed. 395 | 396 | ```bash 397 | $ nix-build --attr docker-container-large release2.nix 398 | ... 399 | building path(s) ‘/nix/store/ml441hr1l5gw9piq8ysnjdaazjxapsci-project3-container.tar.gz’ 400 | Adding layer 401 | Adding meta 402 | Cooking the image 403 | /nix/store/ml441hr1l5gw9piq8ysnjdaazjxapsci-project3-container.tar.gz 404 | $ du -hs $(readlink result) 405 | 251M /nix/store/ml441hr1l5gw9piq8ysnjdaazjxapsci-project3-container.tar.gz 406 | ``` 407 | 408 | If you build the `docker-container-small` attribute you will get a much 409 | smaller container that is only 11 MB compressed and 27 MB uncompressed: 410 | 411 | ```bash 412 | $ nix-build --attr docker-container-small release2.nix 413 | ... 414 | building path(s) ‘/nix/store/3mkrcqjnqv5vwid4qcaf3p1i70y87096-project3-container.tar.gz’ 415 | Adding layer 416 | Adding meta 417 | Cooking the image 418 | /nix/store/3mkrcqjnqv5vwid4qcaf3p1i70y87096-project3-container.tar.gz 419 | $ du -hs $(readlink result) 420 | 11M /nix/store/3mkrcqjnqv5vwid4qcaf3p1i70y87096-project3-container.tar.gz 421 | $ docker load -i result 422 | 73bcfb39a3b5: Loading layer [==================================================>] 27.8MB/27.8MB 423 | $ docker run -it project3-container 424 | tar_open failed 425 | ``` 426 | 427 | This works thanks to the `justStaticExecutables` utility, which statically 428 | links the built executable and removes other unnecessary directories from the 429 | built package. 430 | 431 | The combination of these two things significantly slims down the dependency 432 | tree. We can verify this using the handy `nix-store --query --requisites` 433 | command which lets you view all transitive dependencies for a given Nix package. 434 | 435 | Before the closure minimization, we get this dependency tree: 436 | 437 | ```bash 438 | $ nix-build --attr project3 release2.nix these derivations will be built: 439 | ... 440 | /nix/store/1nc7kaw3lp574hhi2bfxdb490q5kp2m8-project3-1.0.0 441 | 442 | $ nix-store --query --requisites result 443 | /nix/store/jm1n87rp8vr90j9ahcrfzr57nc2r8vgf-glibc-2.24 444 | /nix/store/6sp63j6m6vyspqm2d7zw15ipiym70kzc-attr-2.4.47 445 | /nix/store/03a2gwqj26hidgqsczpvk0g65p5fbhrs-attr-2.4.47-bin 446 | /nix/store/28wl3f34vfjpw0y5809bgr6382wqdscf-bash-4.3-p48 447 | /nix/store/24ln24075d8i5nq1sg558y667pyijx65-ghc-8.0.1-doc 448 | /nix/store/1rhcwhq2f8kkq01wrrr4w210n31iyq10-lzo-2.09 449 | /nix/store/81n6pgrx7lq5f7cjajv82wa422ccvbv4-openssl-1.0.2j 450 | /nix/store/8jwqlpyxp5c2rl5g7yspdfbh58dsciwx-xz-5.2.2 451 | /nix/store/ci7bn61pfds1y1ijkf3c85il0jdp87ar-acl-2.2.52 452 | /nix/store/isl3g7jbrl9a1kdljiyzvjjsqnmn060r-bzip2-1.0.6.0.1 453 | /nix/store/wz7l2zqdsa78jxnzkigv5gy2c7hxnbxh-zlib-1.2.8 454 | /nix/store/mh7n2d60dy0bj5qhhgwhn55skbsqcnsz-libxml2-2.9.4 455 | /nix/store/7crpicyhj61dkqa402f6m3fmb8iy23bn-libarchive-3.2.2-lib 456 | /nix/store/p4ks7972lzhqq9rcyc837c0a9ms2qspr-acl-2.2.52-bin 457 | /nix/store/fsxgigm2yb6xp3axxh342cx04kxrijg1-acl-2.2.52-dev 458 | /nix/store/igwsyzqig15qzgacq35vqdxsj0v3k1ba-attr-2.4.47-dev 459 | /nix/store/q4f9n8vv030pddpg1y2v5p38g0rkmffy-libarchive-3.2.2 460 | /nix/store/41zm7hyzc6qs2fzw2j2fs6c1j9bw7nfm-libarchive-3.2.2-dev 461 | /nix/store/djd2r4cnbcx4vfbj1qalg85vjbcslwxv-gcc-5.4.0-lib 462 | /nix/store/6xiq93r3dnny4m7svvb6hvq9qwjrixk8-gmp-6.1.1 463 | /nix/store/8rn45r9ndfq5h7mx58r35p2szky5ja6n-coreutils-8.25 464 | /nix/store/5kkjn2h1m852q8xh5kz0kfgi5nrbc1fz-perl-5.22.2 465 | /nix/store/321k7mdjv4fwf1rfss1r7nayni18iqaw-glibc-2.24-bin 466 | /nix/store/ppjqx7j8w22j0pahnq4gnzhk4vmibncn-pcre-8.39 467 | /nix/store/h9aqgpgspgjhygj63wpncfzvz5ys851n-gnugrep-2.25 468 | /nix/store/1q6v2661bkkzjx48q4n1d3q2ahlsha9q-linux-headers-4.4.10 469 | /nix/store/idwrh4bm5s4lnnb03d1j2b2rqg9x42h6-glibc-2.24-dev 470 | /nix/store/nm9r5lymydnsrlxjn30ym2kix6mbyr19-binutils-2.27 471 | /nix/store/wb8x0kjry7xvh4jqlx391lr0bffqrzhz-gcc-5.4.0 472 | /nix/store/yn4s58frawcqxcid79npmy2aq8cxcjj1-gcc-5.4.0-man 473 | /nix/store/dp2nf60lqzy1kbhd78ndf5nm3fb3qicd-gcc-wrapper-5.4.0 474 | /nix/store/glhpbq9nhyrrzzbnbdg41vn9h717rrr7-gmp-6.1.1-dev 475 | /nix/store/gz5ph3m624zivi687zvy82izi2z39aik-ncurses-6.0 476 | /nix/store/ff3h3dl87xl0q99b963xpwxacsqaqb43-ncurses-6.0-man 477 | /nix/store/i3hxdbjgbagyslsqnfl7zkpnn6q32hxv-ncurses-6.0-dev 478 | /nix/store/dg7ak1hvlj66vgn4fwvddnnr4pfncd04-ghc-8.0.1 479 | /nix/store/pb3jxhy4z54i24i9s0kyszdmxd2xajc5-libtar-1.2.20 480 | /nix/store/1nc7kaw3lp574hhi2bfxdb490q5kp2m8-project3-1.0.0 481 | ``` 482 | 483 | After closure minimization we get this dependency tree: 484 | 485 | ```bash 486 | $ nix-build --attr project3-minimal release2.nix 487 | ... 488 | /nix/store/i4fypk5m0rdwribpwacdd1nbzbfqcnpc-project3-minimal 489 | $ nix-store --query --requisites result 490 | /nix/store/jm1n87rp8vr90j9ahcrfzr57nc2r8vgf-glibc-2.24 491 | /nix/store/djd2r4cnbcx4vfbj1qalg85vjbcslwxv-gcc-5.4.0-lib 492 | /nix/store/6xiq93r3dnny4m7svvb6hvq9qwjrixk8-gmp-6.1.1 493 | /nix/store/pb3jxhy4z54i24i9s0kyszdmxd2xajc5-libtar-1.2.20 494 | /nix/store/i4fypk5m0rdwribpwacdd1nbzbfqcnpc-project3-minimal 495 | ``` 496 | 497 | Much smaller! Note that the executable is still not fully statically linked. 498 | Only the Haskell dependencies have been statically linked. Most of the 499 | remaining space is due to using `glibc`: 500 | 501 | ```bash 502 | $ du -hs /nix/store/jm1n87rp8vr90j9ahcrfzr57nc2r8vgf-glibc-2.24/ 503 | 23M /nix/store/jm1n87rp8vr90j9ahcrfzr57nc2r8vgf-glibc-2.24/ 504 | ``` 505 | 506 | The actual `project3` executable is tiny in comparison: 507 | 508 | ```bash 509 | $ du -hs /nix/store/i4fypk5m0rdwribpwacdd1nbzbfqcnpc-project3-minimal 510 | 788K /nix/store/i4fypk5m0rdwribpwacdd1nbzbfqcnpc-project3-minimal 511 | ``` 512 | 513 | If we wanted to get the size down further we'd need to use something like 514 | `musl` instead of `glibc`. 515 | 516 | # Conclusion 517 | 518 | This concludes the section on exploring and customizing Haskell packages. The 519 | next section covers [simplified dependency management](../project4/README.md). 520 | 521 | [haskell-lib]: https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/haskell-modules/lib/default.nix 522 | [generic-builder]: https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/haskell-modules/generic-builder.nix 523 | -------------------------------------------------------------------------------- /project3/Test.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | module Main where 4 | 5 | import Control.Applicative (empty) 6 | import qualified Project3 7 | import qualified Turtle 8 | 9 | main :: IO () 10 | main = do 11 | Turtle.touch "example" 12 | Turtle.procs "bsdtar" ["cf", "example.tar", "example"] empty 13 | Project3.check 14 | -------------------------------------------------------------------------------- /project3/cbits/check.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | void check() { 7 | int ret; 8 | TAR *t; 9 | 10 | ret = tar_open(&t, "example.tar", NULL, O_RDONLY, 0, TAR_VERBOSE); 11 | if (ret == -1) { 12 | fprintf(stderr, "tar_open failed\n"); 13 | exit(1); 14 | } 15 | printf("Successfully opened example.tar\n"); 16 | 17 | ret = tar_close(t); 18 | if (ret == -1) { 19 | fprintf(stderr, "tar_close failed\n"); 20 | exit(1); 21 | } 22 | printf("Successfully closed example.tar\n"); 23 | } 24 | -------------------------------------------------------------------------------- /project3/project3.cabal: -------------------------------------------------------------------------------- 1 | name: project3 2 | version: 1.0.0 3 | license: BSD3 4 | license-file: LICENSE 5 | cabal-version: >= 1.18 6 | build-type: Simple 7 | extra-source-files: cbits 8 | 9 | library 10 | build-depends: base < 5 11 | c-sources: cbits/check.c 12 | default-language: Haskell2010 13 | extra-libraries: tar 14 | exposed-modules: Project3 15 | 16 | executable project3 17 | build-depends: base < 5, project3 18 | default-language: Haskell2010 19 | main-is: Main.hs 20 | 21 | test-suite test 22 | build-depends: base < 5, project3, turtle 23 | main-is: Test.hs 24 | type: exitcode-stdio-1.0 25 | -------------------------------------------------------------------------------- /project3/project3.nix: -------------------------------------------------------------------------------- 1 | { mkDerivation, base, lib, tar, turtle }: 2 | mkDerivation { 3 | pname = "project3"; 4 | version = "1.0.0"; 5 | src = ./.; 6 | isLibrary = true; 7 | isExecutable = true; 8 | libraryHaskellDepends = [ base ]; 9 | librarySystemDepends = [ tar ]; 10 | executableHaskellDepends = [ base ]; 11 | testHaskellDepends = [ base turtle ]; 12 | license = lib.licenses.bsd3; 13 | } 14 | -------------------------------------------------------------------------------- /project3/release0.nix: -------------------------------------------------------------------------------- 1 | let 2 | config = { 3 | packageOverrides = pkgs: rec { 4 | haskellPackages = pkgs.haskellPackages.override { 5 | overrides = haskellPackagesNew: haskellPackagesOld: rec { 6 | morte = pkgs.haskell.lib.dontHaddock haskellPackagesOld.morte; 7 | }; 8 | }; 9 | }; 10 | }; 11 | 12 | pkgs = import { inherit config; }; 13 | 14 | in 15 | { morte = pkgs.haskellPackages.morte; 16 | } 17 | -------------------------------------------------------------------------------- /project3/release1.nix: -------------------------------------------------------------------------------- 1 | let 2 | config = { 3 | packageOverrides = pkgs: rec { 4 | haskellPackages = pkgs.haskellPackages.override { 5 | overrides = haskellPackagesNew: haskellPackgesOld: rec { 6 | project3 = 7 | pkgs.haskell.lib.overrideCabal 8 | ( haskellPackagesNew.callPackage ./project3.nix { 9 | tar = pkgs.libtar; 10 | } 11 | ) 12 | ( oldDerivation: { 13 | testToolDepends = [ pkgs.libarchive ]; 14 | } 15 | ); 16 | }; 17 | }; 18 | }; 19 | }; 20 | 21 | pkgs = import { inherit config; }; 22 | 23 | in 24 | { project3 = pkgs.haskellPackages.project3; 25 | } 26 | -------------------------------------------------------------------------------- /project3/release2.nix: -------------------------------------------------------------------------------- 1 | let 2 | config = rec { 3 | packageOverrides = pkgs: rec { 4 | docker-container-large = pkgs.dockerTools.buildImage { 5 | name = "project3-container"; 6 | config.Cmd = [ "${haskellPackages.project3}/bin/project3" ]; 7 | }; 8 | 9 | docker-container-small = pkgs.dockerTools.buildImage { 10 | name = "project3-container"; 11 | config.Cmd = [ "${haskellPackages.project3-minimal}/bin/project3" ]; 12 | }; 13 | 14 | haskellPackages = pkgs.haskellPackages.override { 15 | overrides = haskellPackagesNew: haskellPackgesOld: rec { 16 | project3 = 17 | pkgs.haskell.lib.overrideCabal 18 | ( haskellPackagesNew.callPackage ./project3.nix { 19 | tar = pkgs.libtar; 20 | } 21 | ) 22 | ( oldDerivation: { 23 | testToolDepends = [ pkgs.libarchive ]; 24 | enableSharedExecutables = false; 25 | } 26 | ); 27 | 28 | project3-minimal = 29 | pkgs.haskell.lib.overrideCabal 30 | ( pkgs.haskell.lib.justStaticExecutables 31 | ( haskellPackagesNew.callPackage ./project3.nix { 32 | tar = pkgs.libtar; 33 | } 34 | ) 35 | ) 36 | ( oldDerivation: { 37 | testToolDepends = [ pkgs.libarchive ]; 38 | } 39 | ); 40 | }; 41 | }; 42 | }; 43 | }; 44 | 45 | pkgs = import { inherit config; system = "x86_64-linux"; }; 46 | 47 | in 48 | { project3 = pkgs.haskellPackages.project3; 49 | 50 | project3-minimal = pkgs.haskellPackages.project3-minimal; 51 | 52 | docker-container-small = pkgs.docker-container-small; 53 | 54 | docker-container-large = pkgs.docker-container-large; 55 | } 56 | -------------------------------------------------------------------------------- /project4/Main.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | module Main where 4 | 5 | import Turtle 6 | 7 | main :: IO () 8 | main = echo "Hello, world!" 9 | -------------------------------------------------------------------------------- /project4/README.md: -------------------------------------------------------------------------------- 1 | # Advanced dependency management 2 | 3 | 4 | 5 | This section covers how to automate dependency management so that people less 6 | familiar with Nix can easily contribute to your project 7 | 8 | Let's begin by considering a variation on the example from `project1`: 9 | 10 | ```nix 11 | let 12 | config = { 13 | packageOverrides = pkgs: rec { 14 | haskellPackages = pkgs.haskellPackages.override { 15 | overrides = haskellPackagesNew: haskellPackagesOld: rec { 16 | foldl = haskellPackagesNew.callPackage ./nix/foldl.nix { }; 17 | 18 | optparse-applicative = 19 | pkgs.haskell.lib.dontCheck 20 | (haskellPackagesNew.callPackage ./nix/optparse-applicative.nix { }); 21 | 22 | project4 = 23 | haskellPackagesNew.callPackage ./nix/project4.nix { }; 24 | 25 | turtle = 26 | pkgs.haskell.lib.doJailbreak 27 | (haskellPackagesNew.callPackage ./nix/turtle.nix { }); 28 | }; 29 | }; 30 | }; 31 | }; 32 | 33 | pkgs = import { inherit config; }; 34 | 35 | in 36 | { project4 = pkgs.haskellPackages.project4; 37 | } 38 | ``` 39 | 40 | Here we pin the versions of three dependencies: `optparse-applicative`, `foldl`, 41 | and `turtle`. Additionally, we disable tests for `optparse-applicative` (since 42 | they fail) and jailbreak `turtle` because our new `foldl` dependency is out of 43 | bounds for `turtle`. 44 | 45 | This time we've moved all of our package derivations underneath the `nix/` 46 | subdirectory so that we can take advantage of the handy `builtins.readDir` 47 | function to automate some dependency boilerplate. 48 | 49 | `release1.nix` shows an example of automating the addition of any Haskell 50 | package that we store underneath the `nix/` subdirectory: 51 | 52 | ```nix 53 | let 54 | config = { 55 | packageOverrides = pkgs: rec { 56 | haskellPackages = pkgs.haskellPackages.override { 57 | overrides = haskellPackagesNew: haskellPackagesOld: 58 | let 59 | toPackage = file: _: { 60 | name = builtins.replaceStrings [ ".nix" ] [ "" ] file; 61 | 62 | value = haskellPackagesNew.callPackage (./. + "/nix/${file}") { }; 63 | }; 64 | 65 | packages = pkgs.lib.mapAttrs' toPackage (builtins.readDir ./nix); 66 | 67 | in 68 | packages // { 69 | optparse-applicative = 70 | pkgs.haskell.lib.dontCheck 71 | (haskellPackagesNew.callPackage ./nix/optparse-applicative.nix { }); 72 | 73 | turtle = 74 | pkgs.haskell.lib.doJailbreak 75 | (haskellPackagesNew.callPackage ./nix/turtle.nix { }); 76 | }; 77 | }; 78 | }; 79 | }; 80 | 81 | pkgs = import { inherit config; }; 82 | 83 | in 84 | { project4 = pkgs.haskellPackages.project4; 85 | } 86 | ``` 87 | 88 | In the above code, the`packages` sub-expression expands out to: 89 | 90 | ```haskell 91 | { foldl = 92 | haskellPackagesNew.callPackage ./nix/foldl.nix { }; 93 | 94 | optparse-applicative = 95 | haskellPackagesNew.callPackage ./nix/optparse-applicative.nix { }; 96 | 97 | project4 = 98 | haskellPackagesNew.callPackage ./nix/project4.nix { }; 99 | 100 | turtle = 101 | haskellPackagesNew.callPackage ./nix/turtle.nix { }; 102 | } 103 | ``` 104 | 105 | ... which we then merge with the record of package-specific tweaks: 106 | 107 | ```nix 108 | { foldl = 109 | haskellPackagesNew.callPackage ./nix/foldl.nix { }; 110 | 111 | optparse-applicative = 112 | haskellPackagesNew.callPackage ./nix/optparse-applicative.nix { }; 113 | 114 | project4 = 115 | haskellPackagesNew.callPackage ./nix/project4.nix { }; 116 | 117 | turtle = 118 | haskellPackagesNew.callPackage ./nix/turtle.nix { }; 119 | } // { 120 | optparse-applicative = 121 | pkgs.haskell.lib.dontCheck 122 | (haskellPackagesNew.callPackage ./nix/optparse-applicative.nix { }); 123 | 124 | turtle = 125 | pkgs.haskell.lib.doJailbreak 126 | (haskellPackagesNew.callPackage ./nix/turtle.nix { }); 127 | }; 128 | ``` 129 | 130 | The latter record takes precedence when merging the two, so the final result is: 131 | 132 | ```nix 133 | { foldl = 134 | haskellPackagesNew.callPackage ./nix/foldl.nix { }; 135 | 136 | project4 = 137 | haskellPackagesNew.callPackage ./nix/project4.nix { }; 138 | 139 | optparse-applicative = 140 | pkgs.haskell.lib.dontCheck 141 | (haskellPackagesNew.callPackage ./nix/optparse-applicative.nix { }); 142 | 143 | turtle = 144 | pkgs.haskell.lib.doJailbreak 145 | (haskellPackagesNew.callPackage ./nix/turtle.nix { }); 146 | }; 147 | ``` 148 | 149 | ... which matches the set of overrides in our original `release0.nix` 150 | 151 | ```nix 152 | let 153 | config = { 154 | packageOverrides = pkgs: rec { 155 | haskellPackages = pkgs.haskellPackages.override { 156 | overrides = haskellPackagesNew: haskellPackagesOld: 157 | let 158 | toPackage = file: _: { 159 | name = builtins.replaceStrings [ ".nix" ] [ "" ] file; 160 | 161 | value = haskellPackagesNew.callPackage "./nix/${file}" { }; 162 | }; 163 | 164 | packages = pkgs.lib.mapAttrs toPackage (builtins.readDir ./nix); 165 | 166 | in 167 | builtins.listToAttrs packages // { 168 | optparse-applicative = 169 | pkgs.haskell.lib.dontCheck 170 | (haskellPackagesNew.callPackage ./nix/optparse-applicative.nix { }); 171 | 172 | turtle = 173 | pkgs.haskell.lib.doJailbreak 174 | (haskellPackagesNew.callPackage ./nix/turtle.nix { }); 175 | }; 176 | }; 177 | }; 178 | }; 179 | 180 | pkgs = import { inherit config; }; 181 | 182 | in 183 | { project4 = pkgs.haskellPackages.project4; 184 | } 185 | ``` 186 | 187 | # Composing overrides 188 | 189 | In the above example we had to throw away some of our auto-generated overrides 190 | because we need to customize them. For example, we need to jailbreak `turtle`, 191 | so we have to repeat the import of the `./nix/turtle.nix` derivation. 192 | 193 | However, we can get the best of both worlds by decomposing our Haskell overrides 194 | into two separate sets of overrides: 195 | 196 | * The first set of overrides we auto-generate from the `nix/` subdirectory 197 | * In the second set of overrides we manually tweak Haskell packages 198 | 199 | In order to do this, we take advantage of a useful utility called 200 | `composeExtensions`. As the name suggests, this lets us combine two extensions 201 | into a single extension to our Haskell package overrides. 202 | 203 | `release2.nix` demonstrates how this works: 204 | 205 | ```nix 206 | let 207 | config = { 208 | packageOverrides = pkgs: rec { 209 | haskellPackages = 210 | let 211 | generatedOverrides = haskellPackagesNew: haskellPackagesOld: 212 | let 213 | toPackage = file: _: { 214 | name = builtins.replaceStrings [ ".nix" ] [ "" ] file; 215 | 216 | value = haskellPackagesNew.callPackage (./. + "/nix/${file}") { }; 217 | }; 218 | 219 | in 220 | pkgs.lib.mapAttrs' toPackage (builtins.readDir ./nix); 221 | 222 | manualOverrides = haskellPackagesNew: haskellPackagesOld: { 223 | optparse-applicative = 224 | pkgs.haskell.lib.dontCheck 225 | haskellPackagesOld.optparse-applicative; 226 | 227 | turtle = 228 | pkgs.haskell.lib.doJailbreak 229 | haskellPackagesOld.turtle; 230 | }; 231 | in 232 | pkgs.haskellPackages.override { 233 | overrides = 234 | pkgs.lib.composeExtensions generatedOverrides manualOverrides; 235 | }; 236 | }; 237 | }; 238 | 239 | pkgs = import { inherit config; }; 240 | 241 | in 242 | { project4 = pkgs.haskellPackages.project4; 243 | } 244 | ``` 245 | 246 | Composing two extensions applies them in order: first we override the Haskell 247 | derivations with the ones found in the `nix/` subdirectory and then we tweak 248 | some of them by jailbreaking or disabling tests. 249 | 250 | When we modify a derivation like `turtle` we now define the modification in 251 | terms of `haskellPackagesOld`: 252 | 253 | ```haskell 254 | turtle = pkgs.haskell.lib.doJailbreak haskellPackagesOld.turtle; 255 | ``` 256 | 257 | This is because `haskellPackagesOld.turtle` now refers to our updated `turtle` 258 | derivation from the preceding generated overrides. 259 | 260 | We can take advantage of this trick to organize our overrides into sections: 261 | one set of overrides for disabling tests, another set of overrides for 262 | jailbreaking packages, and another set of overrides for disabling haddocks. 263 | 264 | The following `release3.nix` shows how to take advantage of Nix's automation 265 | features to streamline such a configuration: 266 | 267 | ```nix 268 | let 269 | # Disable tests for these packages 270 | dontCheckPackages = [ 271 | "optparse-applicative" 272 | ]; 273 | 274 | # Jailbreak these packages 275 | doJailbreakPackages = [ 276 | "turtle" 277 | ]; 278 | 279 | # Disable haddocks for these packages 280 | dontHaddockPackages = [ 281 | ]; 282 | 283 | config = { 284 | packageOverrides = pkgs: rec { 285 | haskellPackages = 286 | let 287 | generatedOverrides = haskellPackagesNew: haskellPackagesOld: 288 | let 289 | toPackage = file: _: { 290 | name = builtins.replaceStrings [ ".nix" ] [ "" ] file; 291 | 292 | value = haskellPackagesNew.callPackage (./. + "/nix/${file}") { }; 293 | }; 294 | 295 | in 296 | pkgs.lib.mapAttrs' toPackage (builtins.readDir ./nix); 297 | 298 | makeOverrides = 299 | function: names: haskellPackagesNew: haskellPackagesOld: 300 | let 301 | toPackage = name: { 302 | inherit name; 303 | 304 | value = function haskellPackagesOld.${name}; 305 | }; 306 | 307 | in 308 | builtins.listToAttrs (map toPackage names); 309 | 310 | composeExtensionsList = 311 | pkgs.lib.fold pkgs.lib.composeExtensions (_: _: {}); 312 | 313 | # More exotic overrides go here 314 | manualOverrides = haskellPackagesNew: haskellPackagesOld: { 315 | }; 316 | in 317 | pkgs.haskellPackages.override { 318 | overrides = composeExtensionsList [ 319 | generatedOverrides 320 | (makeOverrides pkgs.haskell.lib.dontCheck dontCheckPackages ) 321 | (makeOverrides pkgs.haskell.lib.doJailbreak doJailbreakPackages) 322 | (makeOverrides pkgs.haskell.lib.dontHaddock dontHaddockPackages) 323 | manualOverrides 324 | ]; 325 | }; 326 | }; 327 | }; 328 | 329 | pkgs = import { inherit config; }; 330 | 331 | in 332 | { project4 = pkgs.haskellPackages.project4; 333 | } 334 | ``` 335 | 336 | This sort of automation simplifies the contribution for people less familiar 337 | with Nix. They can add new package versions by running: 338 | 339 | ```bash 340 | $ cabal2nix ... > nix/${PACKAGE_NAME}.nix 341 | ``` 342 | 343 | ... and they can control jailbreaking, tests, haddocks from simple lists at the 344 | top of the configuration file. Advanced users can still customize in more 345 | detail inside the `manualOverrides` section. 346 | 347 | The above example uses a handy `composeExtensionsList` function, which allows 348 | you to easily add or subtract sets of extensions. For example, you can turn 349 | haddock generation back on for all packages by just commenting out the line for 350 | haddock package overrides: 351 | 352 | ```nix 353 | overrides = composeExtensionsList [ 354 | generatedOverrides 355 | (makeOverrides pkgs.haskell.lib.dontCheck dontCheckPackages ) 356 | (makeOverrides pkgs.haskell.lib.doJailbreak doJailbreakPackages) 357 | # (makeOverrides pkgs.haskell.lib.dontHaddock dontHaddockPackages) 358 | manualOverrides 359 | ]; 360 | ``` 361 | 362 | Similarly, you might require some overrides to get your Haskell IDE to work. 363 | You can add such overrides to the list easily, like this: 364 | 365 | ```nix 366 | overrides = composeExtensionsList [ 367 | generatedOverrides 368 | (makeOverrides pkgs.haskell.lib.dontCheck dontCheckPackages ) 369 | (makeOverrides pkgs.haskell.lib.doJailbreak doJailbreakPackages) 370 | (makeOverrides pkgs.haskell.lib.dontHaddock dontHaddockPackages) 371 | (./import ~/myHaskellOverrides.nix) 372 | manualOverrides 373 | ]; 374 | ``` 375 | 376 | Nix is a programming language, so you can easily create these domain-specific 377 | languages for others to configure your Haskell build. 378 | 379 | # Conclusion 380 | 381 | This concludes the section on automating dependency management. This is the 382 | last section of the tutorial. 383 | -------------------------------------------------------------------------------- /project4/nix/foldl.nix: -------------------------------------------------------------------------------- 1 | { mkDerivation, base, bytestring, comonad, containers 2 | , contravariant, criterion, hashable, mwc-random, primitive 3 | , profunctors, lib, text, transformers, unordered-containers 4 | , vector, vector-builder 5 | }: 6 | mkDerivation { 7 | pname = "foldl"; 8 | version = "1.3.0"; 9 | sha256 = "1rinr1a18pjwlrk21d9sfg0f954cwdc3bk9jl276ypcf8ydy3yin"; 10 | libraryHaskellDepends = [ 11 | base bytestring comonad containers contravariant hashable 12 | mwc-random primitive profunctors text transformers 13 | unordered-containers vector vector-builder 14 | ]; 15 | benchmarkHaskellDepends = [ base criterion ]; 16 | description = "Composable, streaming, and efficient left folds"; 17 | license = lib.licenses.bsd3; 18 | } 19 | -------------------------------------------------------------------------------- /project4/nix/managed.nix: -------------------------------------------------------------------------------- 1 | { mkDerivation, base, lib, transformers }: 2 | mkDerivation { 3 | pname = "managed"; 4 | version = "1.0.4"; 5 | sha256 = "1nf38g8r9s8b37f6yw9w21b28pd6nvlxfl3gzfbn89m0zswrwv0v"; 6 | libraryHaskellDepends = [ base transformers ]; 7 | description = "A monad for managed values"; 8 | license = lib.licenses.bsd3; 9 | } 10 | -------------------------------------------------------------------------------- /project4/nix/optparse-applicative.nix: -------------------------------------------------------------------------------- 1 | { mkDerivation, ansi-wl-pprint, base, process, QuickCheck, lib 2 | , transformers, transformers-compat 3 | }: 4 | mkDerivation { 5 | pname = "optparse-applicative"; 6 | version = "0.13.0.0"; 7 | sha256 = "1b0c5fdq8bd070g24vrjrwlq979r8dk8mys6aji9hy1l9pcv3inf"; 8 | libraryHaskellDepends = [ 9 | ansi-wl-pprint base process transformers transformers-compat 10 | ]; 11 | testHaskellDepends = [ base QuickCheck ]; 12 | homepage = "https://github.com/pcapriotti/optparse-applicative"; 13 | description = "Utilities and combinators for parsing command line options"; 14 | license = lib.licenses.bsd3; 15 | } 16 | -------------------------------------------------------------------------------- /project4/nix/project4.nix: -------------------------------------------------------------------------------- 1 | { mkDerivation, base, foldl, lib, text, turtle }: 2 | mkDerivation { 3 | pname = "project4"; 4 | version = "1.0.0"; 5 | src = ../.; 6 | isLibrary = false; 7 | isExecutable = true; 8 | executableHaskellDepends = [ base foldl text turtle ]; 9 | license = lib.licenses.bsd3; 10 | } 11 | -------------------------------------------------------------------------------- /project4/nix/turtle.nix: -------------------------------------------------------------------------------- 1 | { mkDerivation, ansi-wl-pprint, async, base, bytestring, clock 2 | , directory, doctest, foldl, hostname, managed, optional-args 3 | , optparse-applicative, process, lib, stm, system-fileio 4 | , system-filepath, temporary, text, time, transformers, unix 5 | , unix-compat 6 | }: 7 | mkDerivation { 8 | pname = "turtle"; 9 | version = "1.3.2"; 10 | sha256 = "0pbvkqqhiaddyhlqcrk48w7li81dijw92wwhchwqh1my1363n5pq"; 11 | libraryHaskellDepends = [ 12 | ansi-wl-pprint async base bytestring clock directory foldl hostname 13 | managed optional-args optparse-applicative process stm 14 | system-fileio system-filepath temporary text time transformers unix 15 | unix-compat 16 | ]; 17 | testHaskellDepends = [ base doctest ]; 18 | description = "Shell programming, Haskell-style"; 19 | license = lib.licenses.bsd3; 20 | } 21 | -------------------------------------------------------------------------------- /project4/project4.cabal: -------------------------------------------------------------------------------- 1 | name: project4 2 | version: 1.0.0 3 | license: BSD3 4 | license-file: LICENSE 5 | cabal-version: >= 1.18 6 | build-type: Simple 7 | 8 | executable project4 9 | build-depends: base < 5, foldl, text, turtle 10 | main-is: Main.hs 11 | default-language: Haskell2010 12 | -------------------------------------------------------------------------------- /project4/release0.nix: -------------------------------------------------------------------------------- 1 | let 2 | config = { 3 | packageOverrides = pkgs: rec { 4 | haskellPackages = pkgs.haskellPackages.override { 5 | overrides = haskellPackagesNew: haskellPackagesOld: rec { 6 | foldl = haskellPackagesNew.callPackage ./nix/foldl.nix { }; 7 | 8 | optparse-applicative = 9 | pkgs.haskell.lib.dontCheck 10 | (haskellPackagesNew.callPackage ./nix/optparse-applicative.nix { }); 11 | 12 | project4 = 13 | haskellPackagesNew.callPackage ./nix/project4.nix { }; 14 | 15 | turtle = 16 | pkgs.haskell.lib.doJailbreak 17 | (haskellPackagesNew.callPackage ./nix/turtle.nix { }); 18 | }; 19 | }; 20 | }; 21 | }; 22 | 23 | pkgs = import { inherit config; }; 24 | 25 | in 26 | { project4 = pkgs.haskellPackages.project4; 27 | } 28 | -------------------------------------------------------------------------------- /project4/release1.nix: -------------------------------------------------------------------------------- 1 | let 2 | config = { 3 | packageOverrides = pkgs: rec { 4 | haskellPackages = pkgs.haskellPackages.override { 5 | overrides = haskellPackagesNew: haskellPackagesOld: 6 | let 7 | toPackage = file: _: { 8 | name = builtins.replaceStrings [ ".nix" ] [ "" ] file; 9 | 10 | value = haskellPackagesNew.callPackage (./. + "/nix/${file}") { }; 11 | }; 12 | 13 | packages = pkgs.lib.mapAttrs' toPackage (builtins.readDir ./nix); 14 | 15 | in 16 | packages // { 17 | optparse-applicative = 18 | pkgs.haskell.lib.dontCheck 19 | (haskellPackagesNew.callPackage ./nix/optparse-applicative.nix { }); 20 | 21 | turtle = 22 | pkgs.haskell.lib.doJailbreak 23 | (haskellPackagesNew.callPackage ./nix/turtle.nix { }); 24 | }; 25 | }; 26 | }; 27 | }; 28 | 29 | pkgs = import { inherit config; }; 30 | 31 | in 32 | { project4 = pkgs.haskellPackages.project4; 33 | } 34 | -------------------------------------------------------------------------------- /project4/release2.nix: -------------------------------------------------------------------------------- 1 | let 2 | config = { 3 | packageOverrides = pkgs: rec { 4 | haskellPackages = 5 | let 6 | generatedOverrides = haskellPackagesNew: haskellPackagesOld: 7 | let 8 | toPackage = file: _: { 9 | name = builtins.replaceStrings [ ".nix" ] [ "" ] file; 10 | 11 | value = haskellPackagesNew.callPackage (./. + "/nix/${file}") { }; 12 | }; 13 | 14 | in 15 | pkgs.lib.mapAttrs' toPackage (builtins.readDir ./nix); 16 | 17 | manualOverrides = haskellPackagesNew: haskellPackagesOld: { 18 | optparse-applicative = 19 | pkgs.haskell.lib.dontCheck 20 | haskellPackagesOld.optparse-applicative; 21 | 22 | turtle = 23 | pkgs.haskell.lib.doJailbreak 24 | haskellPackagesOld.turtle; 25 | }; 26 | in 27 | pkgs.haskellPackages.override { 28 | overrides = 29 | pkgs.lib.composeExtensions generatedOverrides manualOverrides; 30 | }; 31 | }; 32 | }; 33 | 34 | pkgs = import { inherit config; }; 35 | 36 | in 37 | { project4 = pkgs.haskellPackages.project4; 38 | } 39 | -------------------------------------------------------------------------------- /project4/release3.nix: -------------------------------------------------------------------------------- 1 | let 2 | # Disable tests for these packages 3 | dontCheckPackages = [ 4 | "optparse-applicative" 5 | ]; 6 | 7 | # Jailbreak these packages 8 | doJailbreakPackages = [ 9 | "turtle" 10 | ]; 11 | 12 | # Disable haddocks for these packages 13 | dontHaddockPackages = [ 14 | ]; 15 | 16 | config = { 17 | packageOverrides = pkgs: rec { 18 | haskellPackages = 19 | let 20 | generatedOverrides = haskellPackagesNew: haskellPackagesOld: 21 | let 22 | toPackage = file: _: { 23 | name = builtins.replaceStrings [ ".nix" ] [ "" ] file; 24 | 25 | value = haskellPackagesNew.callPackage (./. + "/nix/${file}") { }; 26 | }; 27 | 28 | in 29 | pkgs.lib.mapAttrs' toPackage (builtins.readDir ./nix); 30 | 31 | makeOverrides = 32 | function: names: haskellPackagesNew: haskellPackagesOld: 33 | let 34 | toPackage = name: { 35 | inherit name; 36 | 37 | value = function haskellPackagesOld.${name}; 38 | }; 39 | 40 | in 41 | builtins.listToAttrs (map toPackage names); 42 | 43 | composeExtensionsList = 44 | pkgs.lib.fold pkgs.lib.composeExtensions (_: _: {}); 45 | 46 | # More exotic overrides go here 47 | manualOverrides = haskellPackagesNew: haskellPackagesOld: { 48 | }; 49 | in 50 | pkgs.haskellPackages.override { 51 | overrides = composeExtensionsList [ 52 | generatedOverrides 53 | (makeOverrides pkgs.haskell.lib.dontCheck dontCheckPackages ) 54 | (makeOverrides pkgs.haskell.lib.doJailbreak doJailbreakPackages) 55 | (makeOverrides pkgs.haskell.lib.dontHaddock dontHaddockPackages) 56 | manualOverrides 57 | ]; 58 | }; 59 | }; 60 | }; 61 | 62 | pkgs = import { inherit config; }; 63 | 64 | in 65 | { project4 = pkgs.haskellPackages.project4; 66 | } 67 | --------------------------------------------------------------------------------