├── .envrc ├── .github └── workflows │ └── tests.yml ├── .gitignore ├── CHANGES.md ├── LICENSE ├── README.md ├── compilers.json ├── default.nix ├── example.nix ├── lib ├── default.nix ├── ghc.nix ├── haskell.nix └── packages.nix ├── nix-hs.nix ├── nix ├── profile.nix ├── sources.json └── sources.nix ├── scripts └── cachix.sh ├── shell.nix ├── shell ├── default.nix └── overrides.nix └── test ├── hello-world ├── Setup.hs ├── app │ └── Main.hs ├── default.nix ├── hello-world.cabal ├── release.nix ├── shell.nix └── src │ └── Hello.hs ├── multi-package ├── default.nix └── shell.nix ├── overrides ├── Main.hs ├── Setup.hs ├── default.nix ├── overrides.cabal └── shell.nix └── test.sh /.envrc: -------------------------------------------------------------------------------- 1 | use nix 2 | watch_file nix/sources.json 3 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | on: pull_request 3 | jobs: 4 | tests: 5 | runs-on: ${{ matrix.os }} 6 | strategy: 7 | fail-fast: false 8 | matrix: 9 | os: [ubuntu-latest, macOS-latest] 10 | ghc: [ghc8104] 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: cachix/install-nix-action@v13 14 | - uses: cachix/cachix-action@v10 15 | with: 16 | name: nix-hs 17 | signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}' 18 | - run: test/test.sh '${{ matrix.ghc }}' 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /result 2 | /test/result 3 | /.direnv/ 4 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # Release History 2 | 3 | ## 21.05 (June 3, 2021) 4 | 5 | * The deprecated `enableFullyStaticExecutables` argument was removed 6 | along with direct support for `static-haskell.nix`. 7 | 8 | * The standalone shell file has been renamed from 9 | `nix/shell/default.nix` to `shell/default.nix`. 10 | 11 | * Removed `ghcide` from the interactive shell environment since it's 12 | been subsumed by `haskell-language-server`. 13 | 14 | * When generating a derivation for a Haskell package the list of 15 | source code files will run through a sensible filter which removes 16 | directories like `dist-newstyle`. 17 | 18 | This prevents the dreaded `dumping very large path (> 256 MiB)` 19 | warning. 20 | 21 | * Added another test package to demonstrate the `overrides` function 22 | in `test/overrides/default.nix`. 23 | 24 | ## 20.09 (November 22, 2020) 25 | 26 | * Update nixpkgs to pull in GHC 8.8.4 and 8.10.2. 27 | 28 | * Add [haskell-language-server](https://github.com/haskell/haskell-language-server) 29 | 30 | * Add [stan](https://github.com/kowainik/stan) 31 | 32 | * Use `callCabal2nixWithOptions` instead of a custom solution (#5) 33 | 34 | * The standalone shell file has been renamed from `nix/shell.nix` to 35 | `nix/shell/default.nix` and all of the interactive tool files have 36 | been moved into `nix/shell`. 37 | 38 | * Interactive tools such as `ghcide` and `ormolu` are built from a 39 | pinned version of nixpkgs. Everything else, including GHC and 40 | packages, are built from the nixpkgs passed to nix-hs. 41 | 42 | * Releases will now track NixOS 43 | 44 | * NOTE: the `enableFullyStaticExecutables` flag is deprecated and 45 | will be removed in the next release. It can easily be replaced by 46 | giving nix-hs a package set from `static-haskell.nix`. 47 | 48 | ## 2.0 (June 7, 2020) 49 | 50 | * Automatically build the latest versions of `ghcide` and `ormolu` 51 | instead of using the ones in `pkgs.haskellPackages`. 52 | 53 | * Added [cabal-fmt](https://github.com/phadej/cabal-fmt) to the list 54 | of interactive development tools. 55 | 56 | * Added files for `nix-shell` that can be used with `direnv` to load 57 | all interactive development tools into `PATH`. Useful when you 58 | are working on a project that does not use `nix-hs` but you want 59 | to easily use the tools it builds for you. 60 | 61 | To use one of these files put this line in an `.envrc` file: 62 | 63 | ```sh 64 | use nix /path/to/nix-hs/nix/shell.nix 65 | ``` 66 | 67 | * Build fully static binaries (via [static-haskell-nix][]) by 68 | setting `enableFullyStaticExecutables` to `true`. 69 | 70 | * Created a binary cache via Cachix to store dynamically and 71 | statically linked compilers and tools. 72 | 73 | * Added tests and configured GitHub actions 74 | 75 | [static-haskell-nix]: https://github.com/nh2/static-haskell-nix 76 | 77 | ## 1.0 (September 12, 2019) 78 | 79 | * Initial release 80 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017-2021 Peter J. Jones 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | 1. Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the 14 | distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Haskell + nixpkgs = nix-hs 2 | 3 | [![tests](https://github.com/pjones/nix-hs/workflows/tests/badge.svg)](https://github.com/pjones/nix-hs/actions?query=workflow%3Atests) 4 | [![GitHub tag (latest by date)](https://img.shields.io/github/v/tag/pjones/nix-hs?label=release)](https://github.com/pjones/nix-hs/releases) 5 | [![cachix](https://img.shields.io/badge/cachix-nix--hs-green)](https://app.cachix.org/cache/nix-hs) 6 | 7 | A thin layer over the existing [Haskell][] infrastructure in 8 | [nixpkgs][] which adds all of the tools needed for interactive 9 | development. Here are some of the features that you might find the 10 | most useful: 11 | 12 | * An interactive development environment (via `nix-shell`) that 13 | includes: 14 | 15 | * GHC (8.8.4, 8.10.4, 9.0.1) 16 | 17 | * `cabal-install` 3.4.0 18 | 19 | * `stack` 2.7.1 20 | 21 | * [hlint][] 3.3.1 22 | 23 | * [haskell-language-server][] 1.1.0 24 | 25 | * [ormolu][ormolu] 0.1.4.1 26 | 27 | * [cabal-fmt][cabal-fmt] 0.1.5.1 28 | 29 | * [stan][stan] 0.0.1.0 30 | 31 | * and a Hoogle database for all of your project's dependencies 32 | 33 | **NOTE:** The following tools do not currently support GHC 9.0.1: 34 | 35 | * haskell-language-server 36 | 37 | * ormolu 38 | 39 | * stan 40 | 41 | * Easy to use system for [overriding Haskell packages](#using-a-broken-package) (e.g., use 42 | a package not on Hackage, fix a package marked as broken, etc.) 43 | without having to write tons of Nix code. 44 | 45 | * Works seamlessly with [direnv][], [lorri][], and [niv][] if you 46 | already have those tools installed. 47 | 48 | * Switch GHC versions by passing an argument to `nix-build` or 49 | `nix-shell`. 50 | 51 | * [Strip dependencies](#access-to-binary-only-packages) from a package so you can deploy just a 52 | binary without tons of other packages coming along for the ride. 53 | 54 | * Create an interactive development environment [without adding 55 | nix-hs](#interactive-environments-without-nix-hs) as a project dependency. 56 | 57 | * Fetch pre-built tools from [the binary cache](#using-the-binary-cache). 58 | 59 | ## Geting Started 60 | 61 | Create a `default.nix` file that looks something like this: 62 | 63 | ```nix 64 | { pkgs ? import { } 65 | }: 66 | let 67 | nix-hs = 68 | import 69 | (fetchTarball 70 | "https://github.com/pjones/nix-hs/archive/release-21.05.tar.gz") 71 | { 72 | inherit pkgs; 73 | }; 74 | in 75 | nix-hs { 76 | cabal = ./test/hello-world/hello-world.cabal; 77 | } 78 | ``` 79 | 80 | And a `shell.nix` that looks like this: 81 | 82 | ```nix 83 | # Load an interactive environment: 84 | (import ./. {}).interactive 85 | ``` 86 | 87 | That's it! Now `nix-build` and `nix-shell` just work! 88 | 89 | ## Configuration 90 | 91 | In addition to the `cabal` argument to the `nix-hs` function, there 92 | are other ways to control how your package is built. 93 | 94 | ### Enable Flags from the Cabal File 95 | 96 | If you have a flag defined in your package's cabal file you can enable 97 | it using the `flags` argument: 98 | 99 | ```nix 100 | nix-hs { 101 | cabal = ./mypackage.cabal; 102 | flags = [ "someflagname" ]; 103 | } 104 | ``` 105 | 106 | ### Using a Broken Package 107 | 108 | If one of your package's dependencies can't be built you can try 109 | overriding it: 110 | 111 | ```nix 112 | nix-hs { 113 | cabal = ./mypackage.cabal; 114 | 115 | overrides = lib: self: super: with lib; { 116 | pipes-text = unBreak (dontCheck (doJailbreak super.pipes-text)); 117 | }; 118 | } 119 | ``` 120 | 121 | In the example above, the `overrides` function takes three arguments: 122 | 123 | 1. `lib`: An attribute set of library functions. These are the 124 | functions provided by the `pkgs.haskellPackages.lib` set plus a 125 | few more that you might find useful such as: 126 | 127 | - `unBreak`: Remove the `broken` flag from a package 128 | - `compilerName`: The nixpkgs name of the Haskell compiler 129 | being used (e.g. `ghc884`) 130 | - `pkgs`: The full nixpkgs package set, after overriding 131 | 132 | 2. `self`: The final set of Haskell packages after applying all 133 | overrides. This refers to the future version of the package set 134 | so if you're not careful you can fall into infinite recursion. 135 | When in doubt use `super` instead. 136 | 137 | 3. `super`: The set of Haskell packages that are being modified. 138 | Use this attribute set to refer to existing Haskell packages. 139 | You can also use `super` to access useful functions such as 140 | `callCabal2nix` and `callHackageDirect`. 141 | 142 | The `overrides` function should return an attribute set of Haskell 143 | packages. The set of returned packages will be merged into the final 144 | set used to build your package. 145 | 146 | An example package using the `overrides` function can be found in 147 | `test/overrides/default.nix`. The full list of additional `lib` 148 | functions is in `lib/haskell.nix`. 149 | 150 | ### Working with Multi-Package Cabal Projects 151 | 152 | If you have a project that contains multiple Cabal packages you can 153 | build them all with a single `default.nix`. The `cabal` argument to 154 | `nix-hs` can either be a path to a Cabal file *or* an attribute set of 155 | Cabal files: 156 | 157 | ```nix 158 | nix-hs { 159 | cabal = { 160 | package1 = ./package1/package1.cabal; 161 | package2 = ./package1/package2.cabal; 162 | }; 163 | } 164 | ``` 165 | ## Integrating Your Text Editor and Shell 166 | 167 | The best way to let your text editor and shell use the environment 168 | created from Nix is to use [direnv][]. Here's an example `.envrc` 169 | file: 170 | 171 | ```sh 172 | # Use lorri if it's installed, otherwise load shell.nix: 173 | if has lorri; then 174 | eval "$(lorri direnv)" 175 | else 176 | use nix 177 | fi 178 | 179 | # Reload if these files change: 180 | watch_file $(find . -name '*.cabal' -o -name '*.nix') 181 | ``` 182 | 183 | **NOTE:** Make sure you have a `shell.nix` file that exposes the 184 | `interactive` attribute of the derivation, like the example above. 185 | 186 | ## Interactive Environments Without nix-hs 187 | 188 | If you don't want to use `nix-hs` to control your `default.nix` you 189 | can still use it for building an interactive development environment. 190 | Just clone this repository and use the `shell/default.nix` file. 191 | 192 | For example, to drop into an interactive shell: 193 | 194 | ``` 195 | $ nix-shell /path/to/nix-hs/shell 196 | ``` 197 | 198 | Or 199 | 200 | ``` 201 | $ nix-shell --argstr compiler 8.8.3 /path/to/nix-hs/shell 202 | ``` 203 | 204 | Even better, use [direnv][] so your normal shell and text editor can 205 | see all the installed development tools. Here's an example `.envrc` 206 | file: 207 | 208 | ```sh 209 | use nix /path/to/nix-hs/shell 210 | ``` 211 | 212 | ## Access to Binary-Only Packages 213 | 214 | The derivation generated by the `nix-hs` function makes it easy to 215 | access a "binary only" derivation. This is perfect for deployments or 216 | Docker containers where you don't want to bring along all of your 217 | package's dependencies (including GHC). 218 | 219 | The `bin` attribute of the derivation gives you access to this binary 220 | only derivation. For example, to create a docker container put the 221 | following in `docker.nix`: 222 | 223 | ```nix 224 | { pkgs ? import { } 225 | }: 226 | 227 | let 228 | mypackage = (import ./. { inherit pkgs; }).bin; 229 | 230 | in pkgs.dockerTools.buildImage { 231 | name = "mypackage"; 232 | tag = "latest"; 233 | 234 | config = { 235 | Cmd = [ "${mypackage}/bin/hello" ]; 236 | }; 237 | } 238 | ``` 239 | 240 | ## Using the Binary Cache 241 | 242 | If you don't want to spend all day compiling the tools needed to build 243 | your Haskell package and its development environment you can use the 244 | `nix-hs` cache [on Cachix](https://app.cachix.org/cache/nix-hs). 245 | 246 | The cache is populated after each `git push` via a [GitHub action](https://github.com/pjones/nix-hs/actions). 247 | 248 | NOTE: Due to disk space limitations the cache is limited to the 249 | current LTS version of GHC. 250 | 251 | [haskell]: https://www.haskell.org/ 252 | [nixpkgs]: https://nixos.org/nix/ 253 | [direnv]: https://github.com/direnv/direnv 254 | [lorri]: https://github.com/target/lorri 255 | [niv]: https://github.com/nmattia/niv 256 | [ormolu]: https://github.com/tweag/ormolu 257 | [stan]: https://github.com/kowainik/stan 258 | [cabal-fmt]: https://github.com/phadej/cabal-fmt 259 | [haskell-language-server]: https://github.com/haskell/haskell-language-server 260 | [hlint]: https://github.com/ndmitchell/hlint 261 | -------------------------------------------------------------------------------- /compilers.json: -------------------------------------------------------------------------------- 1 | { 2 | "ghc901": {}, 3 | "ghc8104": {}, 4 | "ghc884": {} 5 | } 6 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | # ############################################################################### 2 | # 3 | # This file is part of the package nix-hs. It is subject to the license 4 | # terms in the LICENSE file found in the top-level directory of this 5 | # distribution and at: 6 | # 7 | # https://code.devalot.com/open/nix-hs 8 | # 9 | # No part of this package, including this file, may be copied, modified, 10 | # propagated, or distributed except according to the terms contained in 11 | # the LICENSE file. 12 | # 13 | { pkgs ? import { } }: 14 | import ./nix-hs.nix { inherit pkgs; } 15 | -------------------------------------------------------------------------------- /example.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import { } 2 | }: 3 | let 4 | nix-hs = 5 | import 6 | (fetchTarball 7 | "https://github.com/pjones/nix-hs/archive/release-21.05.tar.gz") 8 | { 9 | inherit pkgs; 10 | }; 11 | in 12 | nix-hs { 13 | cabal = ./test/hello-world/hello-world.cabal; 14 | } 15 | -------------------------------------------------------------------------------- /lib/default.nix: -------------------------------------------------------------------------------- 1 | # This file is part of the package nix-hs. It is subject to the license 2 | # terms in the LICENSE file found in the top-level directory of this 3 | # distribution and at: 4 | # 5 | # https://code.devalot.com/open/nix-hs 6 | # 7 | # No part of this package, including this file, may be copied, modified, 8 | # propagated, or distributed except according to the terms contained in 9 | # the LICENSE file. 10 | { pkgs 11 | , compiler ? "default" 12 | }: 13 | let 14 | lib = pkgs.lib; 15 | callPackage = lib.callPackageWith nix-hs; 16 | 17 | nix-hs = { 18 | inherit lib pkgs compiler; 19 | 20 | ghc = callPackage ./ghc.nix { }; 21 | haskell = callPackage ./haskell.nix { }; 22 | packages = callPackage ./packages.nix { }; 23 | }; 24 | in 25 | nix-hs 26 | -------------------------------------------------------------------------------- /lib/ghc.nix: -------------------------------------------------------------------------------- 1 | # This file is part of the package nix-hs. It is subject to the license 2 | # terms in the LICENSE file found in the top-level directory of this 3 | # distribution and at: 4 | # 5 | # https://code.devalot.com/open/nix-hs 6 | # 7 | # No part of this package, including this file, may be copied, modified, 8 | # propagated, or distributed except according to the terms contained in 9 | # the LICENSE file. 10 | # 11 | # Details and functions about the version of GHC being used. 12 | { pkgs 13 | , compiler ? "default" 14 | }: 15 | let 16 | # A function that can translate a user-entered compiler name by 17 | # turning it into the correct nixpkgs attribute name for GHC: 18 | toAttrName = compiler: 19 | let 20 | clean = str: 21 | pkgs.lib.removePrefix "ghc" 22 | (builtins.replaceStrings [ "." "-" ] [ "" "" ] str); 23 | in 24 | if compiler == "default" 25 | then "ghc${clean pkgs.haskellPackages.ghc.name}" 26 | else "ghc${clean compiler}"; 27 | 28 | # The package set for the selected version of GHC: 29 | packages = pkgs.haskell.packages.${toAttrName compiler}; 30 | in 31 | { 32 | # Calculate the nixpkgs attribute for a version of GHC: 33 | inherit toAttrName; 34 | 35 | # The attribute name for the selected version of GHC: 36 | attrName = toAttrName compiler; 37 | 38 | # The compiler's attribute set from nixpkgs: 39 | ghc = packages.ghc; 40 | 41 | # The complete package set for the selected version of GHC: 42 | inherit packages; 43 | 44 | # The compiler's name (e.g., ghc-8.10.4): 45 | name = packages.ghc.name; 46 | 47 | # The compiler's system name (e.g., x86_64-linux): 48 | system = packages.ghc.system; 49 | 50 | # A function that generates a derivation where the sole executable 51 | # just reports that the selected version of GHC isn't supported. 52 | unsupported = tool: 53 | pkgs.writeShellScriptBin tool '' 54 | echo >&2 "${tool} does not support GHC ${packages.ghc.version}" 55 | exit 1 56 | ''; 57 | } 58 | -------------------------------------------------------------------------------- /lib/haskell.nix: -------------------------------------------------------------------------------- 1 | # This file is part of the package nix-hs. It is subject to the license 2 | # terms in the LICENSE file found in the top-level directory of this 3 | # distribution and at: 4 | # 5 | # https://code.devalot.com/open/nix-hs 6 | # 7 | # No part of this package, including this file, may be copied, modified, 8 | # propagated, or distributed except according to the terms contained in 9 | # the LICENSE file. 10 | # 11 | # Functions for modifying Haskell derivations. 12 | { pkgs 13 | , ghc 14 | }: 15 | let 16 | lib = pkgs.lib; 17 | 18 | # A source cleaner for Haskell programs: 19 | haskellSourceFilter = src: 20 | lib.cleanSourceWith { 21 | inherit src; 22 | filter = name: type: 23 | let baseName = baseNameOf (toString name); in 24 | lib.cleanSourceFilter name type && 25 | !( 26 | baseName == "dist" 27 | || baseName == "dist-newstyle" 28 | || baseName == "TAGS" 29 | || lib.hasPrefix "." baseName 30 | ); 31 | }; 32 | in 33 | pkgs.haskell.lib // { 34 | 35 | # Expose the selected compiler's attribute name (e.g., ghc8104): 36 | compilerName = ghc.attrName; 37 | 38 | # Override a derivation so that its source is smaller: 39 | cleanSource = src: drv: 40 | pkgs.haskell.lib.overrideCabal drv (_: { 41 | src = haskellSourceFilter src; 42 | }); 43 | 44 | # Enable benchmarks (not sure why this isn't the default): 45 | doBenchmark = drv: 46 | pkgs.haskell.lib.doBenchmark drv; 47 | 48 | # Missing from Haskell lib: 49 | unBreak = drv: 50 | pkgs.haskell.lib.overrideCabal drv (_: { 51 | broken = false; 52 | patches = [ ]; 53 | }); 54 | 55 | # Append some build inputs: 56 | appendBuildInputs = buildInputs: drv: 57 | drv.overrideAttrs 58 | (orig: { buildInputs = orig.buildInputs ++ buildInputs; }); 59 | 60 | # Add more commands to the `postPatch` phase: 61 | appendPostPatch = text: drv: 62 | pkgs.haskell.lib.overrideCabal drv (orig: { 63 | postPatch = '' 64 | ${orig.postPatch or ""} 65 | ${text} 66 | ''; 67 | }); 68 | 69 | # Add data files to `drv` by running `f` and giving it the path to 70 | # where data files will be stored. It should return a shell 71 | # fragment. 72 | appendDataFiles = ghc: f: drv: 73 | let 74 | gname = ghc.name; 75 | gsystem = ghc.system; 76 | go = pkgs.haskell.lib.overrideCabal drv (orig: { 77 | postInstall = (orig.postInstall or "") 78 | + f "$data/share/${gname}/${gsystem}-${gname}/${drv.name}"; 79 | }); 80 | in 81 | if f != null then go else drv; 82 | 83 | # Append the elements of an attrset to a derivation's `passthru` attribute: 84 | appendPassthru = attrs: drv: 85 | drv.overrideAttrs (orig: { 86 | passthru = (orig.passthru or { }) // attrs; 87 | }); 88 | } 89 | -------------------------------------------------------------------------------- /lib/packages.nix: -------------------------------------------------------------------------------- 1 | # This file is part of the package nix-hs. It is subject to the license 2 | # terms in the LICENSE file found in the top-level directory of this 3 | # distribution and at: 4 | # 5 | # https://code.devalot.com/open/nix-hs 6 | # 7 | # No part of this package, including this file, may be copied, modified, 8 | # propagated, or distributed except according to the terms contained in 9 | # the LICENSE file. 10 | # 11 | # Functions for manipulating entire package sets. 12 | { 13 | # The `lib` attribute from nixpkgs. 14 | lib 15 | 16 | # Haskell library functions from nixpkgs plus additions from nix-hs: 17 | , haskell 18 | 19 | # The GHC functions from nix-hs: 20 | , ghc 21 | }: 22 | 23 | rec { 24 | # Overrides Haskell packages in the given nixpkgs set, returning a 25 | # new nixpkgs set. 26 | # 27 | # The given function takes three arguments and returns a new Haskell 28 | # package set (see `overrideHaskellPackages` for more details): 29 | # 30 | # 1. The final nixpkgs set being generated. 31 | # 2. The final Haskell package set being generated. 32 | # 3. The current Haskell package set. 33 | overrideHaskellPackagesIn = f: pkgs: 34 | let 35 | overlay = self: super: { 36 | haskell = super.haskell // { 37 | packages = super.haskell.packages // { 38 | ${ghc.attrName} = 39 | overrideHaskellPackages 40 | (f self) 41 | super.haskell.packages.${ghc.attrName}; 42 | }; 43 | }; 44 | }; 45 | in 46 | pkgs.appendOverlays [ overlay ]; 47 | 48 | # Override the given Haskell package set. 49 | # 50 | # The given function should take two arguments (self and super) and 51 | # return an attribute set of updated Haskell packages. 52 | # 53 | # Returns an updated Haskell package set. 54 | overrideHaskellPackages = f: packages: 55 | packages.override 56 | (orig: { 57 | overrides = 58 | lib.composeExtensions 59 | (orig.overrides or (_: _: { })) 60 | f; 61 | }); 62 | 63 | # The name of a package derived from its cabal file name: 64 | nameFromCabal = cabal: 65 | lib.removeSuffix ".cabal" (baseNameOf (toString cabal)); 66 | 67 | # Generate a Haskell package from a cabal file. 68 | derivationFromCabal = 69 | { cabal 70 | , flags 71 | , postPatch 72 | , addDataFiles 73 | , buildInputs 74 | , packages 75 | }: 76 | let 77 | # All flags as a string: 78 | flagsStr = 79 | lib.concatMapStringsSep " " (f: "-f${f}") flags; 80 | 81 | # Load the cabal file: 82 | cabal2nix = 83 | haskell.cleanSource (dirOf cabal) 84 | (packages.callCabal2nixWithOptions 85 | (nameFromCabal cabal) 86 | (dirOf cabal) 87 | flagsStr 88 | { }); 89 | 90 | # The final derivation all patched up: 91 | drv = 92 | haskell.appendDataFiles ghc addDataFiles 93 | (haskell.appendPostPatch postPatch 94 | (haskell.doBenchmark # To get all deps. 95 | (haskell.appendBuildInputs buildInputs cabal2nix))); 96 | in 97 | drv; 98 | } 99 | -------------------------------------------------------------------------------- /nix-hs.nix: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # 3 | # This file is part of the package nix-hs. It is subject to the license 4 | # terms in the LICENSE file found in the top-level directory of this 5 | # distribution and at: 6 | # 7 | # https://code.devalot.com/open/nix-hs 8 | # 9 | # No part of this package, including this file, may be copied, modified, 10 | # propagated, or distributed except according to the terms contained in 11 | # the LICENSE file. 12 | # 13 | { pkgs 14 | }: 15 | 16 | { 17 | # Path to one or more Cabal files. May be a single path or an attr 18 | # set of package names and paths to cabal files: 19 | cabal 20 | 21 | # Cabal `-f' flags to use: 22 | , flags ? [ ] 23 | 24 | # A nixpkgs version string for GHC, or "default": 25 | , compiler ? "default" 26 | 27 | # Optional: Override `haskellPackages' with a function. The 28 | # function takes three arguments and returns a new Haskell package 29 | # set. 30 | # 31 | # The arguments are: 32 | # 33 | # 1. `pkgs.haskell.lib` with some additions (see below). 34 | # 2. `self`: The package set you are currently building. 35 | # 3. `super`: The existing Haskell package set. 36 | # 37 | # For a list of additional functions that are available in the `lib` 38 | # argument, see `lib/haskell.nix`. 39 | # 40 | # Also, one additional attribute will be available in `lib`: pkgs, 41 | # the final nixpkgs set after all Haskell overrides have been 42 | # applied. This is important for passing on to other invocations of 43 | # nix-hs for any dependencies so they pick up the already patched 44 | # dependencies from this overrides function. 45 | # 46 | # An example overrides function can be found in: 47 | # 48 | # test/overrides/default.nix 49 | , overrides ? (lib: self: super: { }) 50 | 51 | # Extra nixpkgs packages that your Haskell package depend on: 52 | , buildInputs ? [ ] 53 | 54 | # Shell fragment to run after the `patchPhase': 55 | , postPatch ? "" 56 | 57 | # If not null, it should be a function that takes a path to the data 58 | # directory and returns shell code to install extra files. 59 | # 60 | # Note: The argument given to this function contains shell variables 61 | # so it can only be used in a shell snippet. 62 | # 63 | # Example: 64 | # 65 | # addDataFiles = path: '' 66 | # mkdir -p "${path}/www" 67 | # for file in ${ui}/*.js; do 68 | # install -m 0444 "$file" "${path}/www" 69 | # done 70 | # ''; 71 | , addDataFiles ? null 72 | }: 73 | let 74 | nix-hs = import ./lib { inherit pkgs compiler; }; 75 | 76 | # Create a derivation for the given cabal file. 77 | singlePackage = cabalFile: packages: 78 | let 79 | drv = 80 | nix-hs.packages.derivationFromCabal { 81 | inherit flags postPatch addDataFiles buildInputs packages; 82 | cabal = cabalFile; 83 | }; 84 | in 85 | nix-hs.haskell.appendPassthru 86 | { 87 | bin = nix-hs.haskell.justStaticExecutables drv; 88 | interactive = makeInteractive packages drv; 89 | } 90 | drv; 91 | 92 | # When we have more than one cabal file so we build an attribute set 93 | # of derivations where the keys are package names and the values are 94 | # the Haskell derivations. 95 | multiPackage = fileSet: packages: 96 | pkgs.lib.mapAttrs 97 | (_name: cabalFile: singlePackage cabalFile packages) 98 | fileSet; 99 | 100 | # Returns an attribute set that contains the generated Haskell packages. 101 | generatedPackages = packages: 102 | if builtins.isAttrs cabal 103 | then multiPackage cabal packages 104 | else { ${nix-hs.packages.nameFromCabal cabal} = singlePackage cabal packages; }; 105 | 106 | # Generates an interactive shell environment for the given Haskell 107 | # derivation (or derivations). 108 | makeInteractive = packages: drv: 109 | let 110 | shellDrv = import ./shell { inherit compiler; }; 111 | 112 | # Use the Hoogle package from the interactive shell environment 113 | # and not the one from the current package set since this is the 114 | # hoogle binary we want in the interactive shell. Bonus 115 | # feature: we automatically pick up any patches that make hoogle 116 | # work with the selected compiler. 117 | withPatchedHoogle = 118 | nix-hs.packages.overrideHaskellPackages 119 | (_self: _super: { hoogle = shellDrv.haskell.hoogle; }) 120 | packages; 121 | 122 | shellFor = inputs: 123 | withPatchedHoogle.shellFor { 124 | packages = _: inputs; 125 | withHoogle = true; 126 | doBenchmark = true; 127 | buildInputs = buildInputs ++ shellDrv.buildInputs; 128 | }; 129 | in 130 | if pkgs.lib.isDerivation drv 131 | then shellFor [ drv ] 132 | else shellFor (builtins.attrValues drv); 133 | 134 | # Extract the generated Haskell packages out of the final package set. 135 | extractPackages = packages: 136 | let 137 | drvs = 138 | pkgs.lib.mapAttrs 139 | (name: _value: packages.${name}) 140 | cabal; 141 | in 142 | if builtins.isAttrs cabal 143 | then { interactive = makeInteractive packages drvs; } // drvs 144 | else packages.${nix-hs.packages.nameFromCabal cabal}; 145 | 146 | # The haskell package set after applying the requested overrides and 147 | # adding in the package generated from the cabal file(s): 148 | haskellPackages = 149 | let 150 | # Augmented overrides function that accepts the final nixpkgs 151 | # set for the `pkgs` attribute which is passed in the Haskell 152 | # library functions attrset. 153 | overrides_ = self: 154 | overrides ({ 155 | pkgs = self; 156 | } // nix-hs.haskell); 157 | 158 | # Jump through some hoops to generate a patched Haskell package 159 | # set from the given nixpkgs set, passing along the final 160 | # nixpkgs set to the overriding function. 161 | packages = 162 | (nix-hs.packages.overrideHaskellPackagesIn 163 | overrides_ 164 | pkgs).haskell.packages.${nix-hs.ghc.attrName}; 165 | in 166 | nix-hs.packages.overrideHaskellPackages 167 | (_self: _super: generatedPackages packages) 168 | packages; 169 | 170 | in 171 | extractPackages haskellPackages 172 | -------------------------------------------------------------------------------- /nix/profile.nix: -------------------------------------------------------------------------------- 1 | # This file is part of the package nix-hs. It is subject to the license 2 | # terms in the LICENSE file found in the top-level directory of this 3 | # distribution and at: 4 | # 5 | # https://code.devalot.com/open/nix-hs 6 | # 7 | # No part of this package, including this file, may be copied, modified, 8 | # propagated, or distributed except according to the terms contained in 9 | # the LICENSE file. 10 | # 11 | # Helper file for post-processing Haskell profiles. 12 | { pkgs ? import { } 13 | , compiler ? "default" # Which version of GHC to use, or "default". 14 | }: 15 | 16 | let 17 | # Select a compiler: 18 | basePackages = if compiler == "default" then 19 | pkgs.haskellPackages 20 | else 21 | pkgs.haskell.packages."ghc${compiler}"; 22 | 23 | in pkgs.mkShell { 24 | buildInputs = [ 25 | basePackages.ghc # For hp2ps 26 | basePackages.ghc-prof-aeson-flamegraph 27 | pkgs.ghostscript # For ps2pdf 28 | pkgs.flamegraph # For flamegraph.pl 29 | ]; 30 | 31 | shellHook = '' 32 | process_haskell_hp_file() { 33 | dir=$(dirname "$1") 34 | base=$(basename "$1" ".hp") 35 | 36 | ( 37 | set -e 38 | cd $dir 39 | hp2ps -e11in -M -c "$base".hp 40 | ps2pdf "$base".ps 41 | rm -f "$base".ps "$base".aux 42 | ) 43 | } 44 | ''; 45 | } 46 | -------------------------------------------------------------------------------- /nix/sources.json: -------------------------------------------------------------------------------- 1 | { 2 | "niv": { 3 | "branch": "master", 4 | "description": "Easy dependency management for Nix projects", 5 | "homepage": "https://github.com/nmattia/niv", 6 | "owner": "nmattia", 7 | "repo": "niv", 8 | "rev": "f73bf8d584148677b01859677a63191c31911eae", 9 | "sha256": "0jlmrx633jvqrqlyhlzpvdrnim128gc81q5psz2lpp2af8p8q9qs", 10 | "type": "tarball", 11 | "url": "https://github.com/nmattia/niv/archive/f73bf8d584148677b01859677a63191c31911eae.tar.gz", 12 | "url_template": "https://github.com///archive/.tar.gz" 13 | }, 14 | "nixpkgs": { 15 | "branch": "nixos-21.05", 16 | "description": "Nix Packages collection", 17 | "homepage": "", 18 | "owner": "NixOS", 19 | "repo": "nixpkgs", 20 | "rev": "d25ea6a0d2a847fb52131da546f2a866656fbafa", 21 | "sha256": "0y2h71pvzrhc2svib4lwjl04hrzy8901ravvlyxlqdbal8hy3838", 22 | "type": "tarball", 23 | "url": "https://github.com/NixOS/nixpkgs/archive/d25ea6a0d2a847fb52131da546f2a866656fbafa.tar.gz", 24 | "url_template": "https://github.com///archive/.tar.gz" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /nix/sources.nix: -------------------------------------------------------------------------------- 1 | # This file has been generated by Niv. 2 | 3 | let 4 | 5 | # 6 | # The fetchers. fetch_ fetches specs of type . 7 | # 8 | 9 | fetch_file = pkgs: spec: 10 | if spec.builtin or true then 11 | builtins_fetchurl { inherit (spec) url sha256; } 12 | else 13 | pkgs.fetchurl { inherit (spec) url sha256; }; 14 | 15 | fetch_tarball = pkgs: name: spec: 16 | let 17 | ok = str: ! builtins.isNull (builtins.match "[a-zA-Z0-9+-._?=]" str); 18 | # sanitize the name, though nix will still fail if name starts with period 19 | name' = stringAsChars (x: if ! ok x then "-" else x) "${name}-src"; 20 | in 21 | if spec.builtin or true then 22 | builtins_fetchTarball { name = name'; inherit (spec) url sha256; } 23 | else 24 | pkgs.fetchzip { name = name'; inherit (spec) url sha256; }; 25 | 26 | fetch_git = spec: 27 | builtins.fetchGit { url = spec.repo; inherit (spec) rev ref; }; 28 | 29 | fetch_local = spec: spec.path; 30 | 31 | fetch_builtin-tarball = name: throw 32 | ''[${name}] The niv type "builtin-tarball" is deprecated. You should instead use `builtin = true`. 33 | $ niv modify ${name} -a type=tarball -a builtin=true''; 34 | 35 | fetch_builtin-url = name: throw 36 | ''[${name}] The niv type "builtin-url" will soon be deprecated. You should instead use `builtin = true`. 37 | $ niv modify ${name} -a type=file -a builtin=true''; 38 | 39 | # 40 | # Various helpers 41 | # 42 | 43 | # The set of packages used when specs are fetched using non-builtins. 44 | mkPkgs = sources: 45 | let 46 | sourcesNixpkgs = 47 | import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) {}; 48 | hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath; 49 | hasThisAsNixpkgsPath = == ./.; 50 | in 51 | if builtins.hasAttr "nixpkgs" sources 52 | then sourcesNixpkgs 53 | else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then 54 | import {} 55 | else 56 | abort 57 | '' 58 | Please specify either (through -I or NIX_PATH=nixpkgs=...) or 59 | add a package called "nixpkgs" to your sources.json. 60 | ''; 61 | 62 | # The actual fetching function. 63 | fetch = pkgs: name: spec: 64 | 65 | if ! builtins.hasAttr "type" spec then 66 | abort "ERROR: niv spec ${name} does not have a 'type' attribute" 67 | else if spec.type == "file" then fetch_file pkgs spec 68 | else if spec.type == "tarball" then fetch_tarball pkgs name spec 69 | else if spec.type == "git" then fetch_git spec 70 | else if spec.type == "local" then fetch_local spec 71 | else if spec.type == "builtin-tarball" then fetch_builtin-tarball name 72 | else if spec.type == "builtin-url" then fetch_builtin-url name 73 | else 74 | abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}"; 75 | 76 | # If the environment variable NIV_OVERRIDE_${name} is set, then use 77 | # the path directly as opposed to the fetched source. 78 | replace = name: drv: 79 | let 80 | saneName = stringAsChars (c: if isNull (builtins.match "[a-zA-Z0-9]" c) then "_" else c) name; 81 | ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}"; 82 | in 83 | if ersatz == "" then drv else ersatz; 84 | 85 | # Ports of functions for older nix versions 86 | 87 | # a Nix version of mapAttrs if the built-in doesn't exist 88 | mapAttrs = builtins.mapAttrs or ( 89 | f: set: with builtins; 90 | listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set)) 91 | ); 92 | 93 | # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295 94 | range = first: last: if first > last then [] else builtins.genList (n: first + n) (last - first + 1); 95 | 96 | # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257 97 | stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1)); 98 | 99 | # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269 100 | stringAsChars = f: s: concatStrings (map f (stringToCharacters s)); 101 | concatStrings = builtins.concatStringsSep ""; 102 | 103 | # fetchTarball version that is compatible between all the versions of Nix 104 | builtins_fetchTarball = { url, name, sha256 }@attrs: 105 | let 106 | inherit (builtins) lessThan nixVersion fetchTarball; 107 | in 108 | if lessThan nixVersion "1.12" then 109 | fetchTarball { inherit name url; } 110 | else 111 | fetchTarball attrs; 112 | 113 | # fetchurl version that is compatible between all the versions of Nix 114 | builtins_fetchurl = { url, sha256 }@attrs: 115 | let 116 | inherit (builtins) lessThan nixVersion fetchurl; 117 | in 118 | if lessThan nixVersion "1.12" then 119 | fetchurl { inherit url; } 120 | else 121 | fetchurl attrs; 122 | 123 | # Create the final "sources" from the config 124 | mkSources = config: 125 | mapAttrs ( 126 | name: spec: 127 | if builtins.hasAttr "outPath" spec 128 | then abort 129 | "The values in sources.json should not have an 'outPath' attribute" 130 | else 131 | spec // { outPath = replace name (fetch config.pkgs name spec); } 132 | ) config.sources; 133 | 134 | # The "config" used by the fetchers 135 | mkConfig = 136 | { sourcesFile ? if builtins.pathExists ./sources.json then ./sources.json else null 137 | , sources ? if isNull sourcesFile then {} else builtins.fromJSON (builtins.readFile sourcesFile) 138 | , pkgs ? mkPkgs sources 139 | }: rec { 140 | # The sources, i.e. the attribute set of spec name to spec 141 | inherit sources; 142 | 143 | # The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers 144 | inherit pkgs; 145 | }; 146 | 147 | in 148 | mkSources (mkConfig {}) // { __functor = _: settings: mkSources (mkConfig settings); } 149 | -------------------------------------------------------------------------------- /scripts/cachix.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env nix-shell 2 | #! nix-shell -i bash ../shell.nix 3 | # shellcheck shell=bash 4 | 5 | ################################################################################ 6 | set -e 7 | set -u 8 | set -x 9 | 10 | ################################################################################ 11 | top=$(realpath "$(dirname "$0")/..") 12 | 13 | ################################################################################ 14 | cachix_push() { 15 | compiler=$1 16 | static=$2 17 | 18 | args=("--argstr" "compiler" "$compiler") 19 | 20 | if [ "$static" = "static" ]; then 21 | args+=("--arg" "static" "true") 22 | fi 23 | 24 | nix-build \ 25 | --no-out-link \ 26 | "${args[@]}" \ 27 | "$top/test/hello-world" | 28 | cachix push nix-hs 29 | 30 | nix-store \ 31 | --query --references \ 32 | "$(nix-instantiate "${args[@]}" "$top/test/hello-world/shell.nix")" | 33 | xargs nix-store --realise | 34 | xargs nix-store --query --requisites | 35 | tee /dev/stderr | 36 | cachix push nix-hs 37 | } 38 | 39 | ################################################################################ 40 | compilers=() 41 | 42 | readarray -t compilers < <( 43 | jq -r \ 44 | 'map_values(select(.lts))|keys|join(" ")' \ 45 | <"$top/compilers.json" 46 | ) 47 | 48 | for compiler in "${compilers[@]}"; do 49 | cachix_push "$compiler" "dynamic" 50 | cachix_push "$compiler" "static" 51 | done 52 | 53 | # Local Variables: 54 | # mode: sh 55 | # sh-shell: bash 56 | # End: 57 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # 3 | # This file is part of the package nix-hs. It is subject to the license 4 | # terms in the LICENSE file found in the top-level directory of this 5 | # distribution and at: 6 | # 7 | # https://code.devalot.com/open/nix-hs 8 | # 9 | # No part of this package, including this file, may be copied, modified, 10 | # propagated, or distributed except according to the terms contained in 11 | # the LICENSE file. 12 | # 13 | { sources ? import ./nix/sources.nix 14 | , pkgs ? import sources.nixpkgs { } 15 | }: 16 | 17 | pkgs.mkShell { 18 | name = "nix-hs"; 19 | 20 | NIX_PATH = "nixpkgs=${sources.nixpkgs.url}"; 21 | 22 | buildInputs = with pkgs; [ 23 | jq 24 | cachix 25 | ]; 26 | } 27 | -------------------------------------------------------------------------------- /shell/default.nix: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # 3 | # This file is part of the package nix-hs. It is subject to the license 4 | # terms in the LICENSE file found in the top-level directory of this 5 | # distribution and at: 6 | # 7 | # https://code.devalot.com/open/nix-hs 8 | # 9 | # No part of this package, including this file, may be copied, modified, 10 | # propagated, or distributed except according to the terms contained in 11 | # the LICENSE file. 12 | # 13 | { compiler ? "default" 14 | , name ? "nix-hs" 15 | }: 16 | let 17 | sources = import ../nix/sources.nix; 18 | pkgs = import sources.nixpkgs { }; 19 | nix-hs = import ../lib { inherit pkgs compiler; }; 20 | 21 | overrides = 22 | nix-hs.packages.overrideHaskellPackages 23 | (import ./overrides.nix { 24 | inherit (pkgs) lib fetchFromGitHub; 25 | inherit nix-hs; 26 | }) 27 | (nix-hs.ghc.packages); 28 | 29 | packages = with overrides; [ 30 | cabal-fmt 31 | cabal-install 32 | haskell-language-server 33 | hasktags 34 | hlint 35 | hoogle 36 | ormolu 37 | stan 38 | ]; 39 | 40 | # A smarter version of `justStaticExecutables` that first checks if 41 | # the derivation looks like a Haskell package. Non-Haskell packages 42 | # are just passed through. 43 | justBin = drv: 44 | if drv ? override 45 | then nix-hs.haskell.justStaticExecutables drv 46 | else drv; 47 | in 48 | assert (pkgs.lib.assertMsg 49 | (pkgs.haskell.packages ? ${nix-hs.ghc.attrName}) 50 | "This version of nix-hs does not support ${nix-hs.ghc.attrName}."); 51 | 52 | pkgs.mkShell { 53 | name = "shell-env-for-${name}-${nix-hs.ghc.attrName}"; 54 | 55 | buildInputs = 56 | map justBin packages 57 | ++ [ overrides.ghc ] 58 | ++ [ pkgs.stack ]; 59 | 60 | passthru = { 61 | # Allow building individual packages (for testing) via `nix-build -A...`: 62 | haskell = overrides; 63 | }; 64 | } 65 | -------------------------------------------------------------------------------- /shell/overrides.nix: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # 3 | # This file is part of the package nix-hs. It is subject to the license 4 | # terms in the LICENSE file found in the top-level directory of this 5 | # distribution and at: 6 | # 7 | # https://code.devalot.com/open/nix-hs 8 | # 9 | # No part of this package, including this file, may be copied, modified, 10 | # propagated, or distributed except according to the terms contained in 11 | # the LICENSE file. 12 | # 13 | { nix-hs 14 | , lib 15 | , fetchFromGitHub 16 | }: 17 | let 18 | inherit (nix-hs.haskell) 19 | appendPatch 20 | dontCheck 21 | doJailbreak; 22 | 23 | in 24 | self: super: { 25 | 26 | # Nothing to see here. 27 | 28 | } // lib.optionalAttrs (lib.hasPrefix "ghc9" nix-hs.ghc.attrName) { 29 | # Packages that work fine with GHC 9.0.1: 30 | cryptohash-md5 = doJailbreak super.cryptohash-md5; 31 | cryptohash-sha1 = doJailbreak super.cryptohash-sha1; 32 | mono-traversable = dontCheck super.mono-traversable; 33 | retry = dontCheck super.retry; 34 | text-short = dontCheck super.text-short; 35 | 36 | # Packages that have specific versions for GHC 9.0.1: 37 | cryptonite = super.cryptonite_0_29; 38 | ghc-lib-parser = super.ghc-lib-parser_9_0_1_20210324; 39 | memory = super.memory_0_16_0; 40 | 41 | # No released version supports Cabal 3.4 or GHC 9.0.1, but the 42 | # latest commit does have support if you jailbreak it. 43 | cabal-fmt = 44 | let 45 | src = fetchFromGitHub { 46 | owner = "phadej"; 47 | repo = "cabal-fmt"; 48 | rev = "a7ef55eaf5db2f9a623e7db39ad9bc38e7bc138f"; 49 | sha256 = "1br6kzybldwgcj35g6fjicz30srbm5gfzajmz6pws4abrp76v8kl"; 50 | }; 51 | drv = super.callCabal2nix "cabal-fmt" src { }; 52 | in 53 | doJailbreak drv; 54 | 55 | # Needs patch from pending PR to build with GHC 9.0.1: 56 | # https://github.com/lspitzner/czipwith/pull/2 57 | czipwith = 58 | let 59 | patch = builtins.fetchurl { 60 | url = "https://patch-diff.githubusercontent.com/raw/lspitzner/czipwith/pull/2.diff"; 61 | sha256 = "101yq4j4q5lph7ra9acq2xm2irxr4kpf0q0vjkmby762xq4a74lc"; 62 | }; 63 | in 64 | appendPatch super.czipwith patch; 65 | 66 | # No support yet :( 67 | 68 | # There are some patches in the repo, but test's aren't passing. 69 | haskell-language-server = 70 | nix-hs.ghc.unsupported "haskell-language-server"; 71 | 72 | # Ormolu needs to support ghc-lib-parser_9_0_1_20210324 73 | # https://github.com/tweag/ormolu/issues/688 74 | ormolu = 75 | nix-hs.ghc.unsupported "ormolu"; 76 | 77 | # Stan and some of its dependencies are broken on GHC 9.0.1: 78 | stan = 79 | nix-hs.ghc.unsupported "stan"; 80 | } 81 | -------------------------------------------------------------------------------- /test/hello-world/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /test/hello-world/app/Main.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Hello (hello) 4 | 5 | main :: IO () 6 | main = hello 7 | -------------------------------------------------------------------------------- /test/hello-world/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | # Package sources: 3 | sources ? import ../../nix/sources.nix 4 | # nixpkgs: 5 | , pkgs ? import sources.nixpkgs { } 6 | # Which version of GHC to use: 7 | , compiler ? "default" 8 | }: 9 | let nix-hs = import ../../default.nix { inherit pkgs; }; 10 | 11 | in 12 | nix-hs { 13 | cabal = ./hello-world.cabal; 14 | inherit compiler; 15 | } 16 | -------------------------------------------------------------------------------- /test/hello-world/hello-world.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: >=1.10 2 | name: hello-world 3 | version: 0.1.0.0 4 | license-file: /dev/null 5 | build-type: Simple 6 | 7 | library 8 | build-depends: base 9 | hs-source-dirs: src 10 | exposed-modules: Hello 11 | 12 | executable hello 13 | main-is: Main.hs 14 | hs-source-dirs: app 15 | build-depends: base, aeson, hello-world 16 | default-language: Haskell2010 17 | -------------------------------------------------------------------------------- /test/hello-world/release.nix: -------------------------------------------------------------------------------- 1 | (import ./. { }).bin 2 | -------------------------------------------------------------------------------- /test/hello-world/shell.nix: -------------------------------------------------------------------------------- 1 | (import ./. {}).interactive 2 | -------------------------------------------------------------------------------- /test/hello-world/src/Hello.hs: -------------------------------------------------------------------------------- 1 | module Hello 2 | ( hello 3 | ) 4 | where 5 | 6 | hello :: IO () 7 | hello = putStrLn "Hello, World!" 8 | 9 | -------------------------------------------------------------------------------- /test/multi-package/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | # Package sources: 3 | sources ? import ../../nix/sources.nix 4 | # nixpkgs: 5 | , pkgs ? import sources.nixpkgs { } 6 | # Which version of GHC to use: 7 | , compiler ? "default" 8 | }: 9 | let nix-hs = import ../../default.nix { inherit pkgs; }; 10 | 11 | in 12 | nix-hs { 13 | inherit compiler; 14 | cabal = { 15 | hello-world = ../hello-world/hello-world.cabal; 16 | 17 | # We're loading the same cabal file twice but that's just to 18 | # demonstrate that you can refer to more than one cabal file using 19 | # the `cabal` attribute. 20 | world-hello = ../hello-world/hello-world.cabal; 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /test/multi-package/shell.nix: -------------------------------------------------------------------------------- 1 | (import ./. {}).interactive 2 | -------------------------------------------------------------------------------- /test/overrides/Main.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | main :: IO () 4 | main = putStrLn "Hello, World!" 5 | -------------------------------------------------------------------------------- /test/overrides/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /test/overrides/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | # Package sources: 3 | sources ? import ../../nix/sources.nix 4 | # nixpkgs: 5 | , pkgs ? import sources.nixpkgs { } 6 | # Which version of GHC to use: 7 | , compiler ? "default" 8 | }: 9 | let nix-hs = import ../../default.nix { inherit pkgs; }; 10 | 11 | in 12 | nix-hs { 13 | cabal = ./overrides.cabal; 14 | inherit compiler; 15 | 16 | # A function that overrides the Haskell package set, returning a new 17 | # package set: 18 | overrides = lib: self: super: { 19 | 20 | # Let's add the hello-world package to our package set. We'll 21 | # give it access to the final nixpkgs so that any Haskell packages 22 | # we override in here are also overridden for it. 23 | # 24 | # We also pass in the selected compiler name so the hello-world 25 | # package is built with the same compiler that this package is. 26 | hello-world = import ../hello-world { 27 | inherit (lib) pkgs; 28 | compiler = lib.compilerName; 29 | }; 30 | 31 | # If a package is broken because of failing tests we can un-break 32 | # the package and turn off its tests: 33 | pipes-text = lib.dontCheck (lib.unBreak super.pipes-text); 34 | 35 | # Selectively override a package based on the compiler: 36 | microlens = 37 | if lib.compilerName == "ghc8104" 38 | then lib.doJailbreak super.microlens 39 | else super.microlens; 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /test/overrides/overrides.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: >=1.24 2 | name: overrides 3 | version: 0.1.0.0 4 | license-file: /dev/null 5 | build-type: Simple 6 | 7 | executable overrides 8 | main-is: Main.hs 9 | build-depends: base, microlens, hello-world == 0.1.0.0 10 | default-language: Haskell2010 11 | -------------------------------------------------------------------------------- /test/overrides/shell.nix: -------------------------------------------------------------------------------- 1 | (import ./. {}).interactive 2 | -------------------------------------------------------------------------------- /test/test.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env nix-shell 2 | #! nix-shell -i bash ../shell.nix 3 | # shellcheck shell=bash 4 | 5 | ################################################################################ 6 | set -e 7 | set -u 8 | set -x 9 | 10 | ################################################################################ 11 | top=$(realpath "$(dirname "$0")") 12 | 13 | packages=( 14 | "$top/hello-world" 15 | "$top/overrides" 16 | "$top/multi-package" 17 | ) 18 | 19 | ################################################################################ 20 | run_build() { 21 | package=$1 22 | shift 23 | 24 | nix-build \ 25 | --no-out-link \ 26 | "${@}" \ 27 | "$package" 28 | } 29 | 30 | ################################################################################ 31 | run_tool() { 32 | package=$1 33 | shift 34 | 35 | tool=$1 36 | shift 37 | 38 | nix-shell \ 39 | "${@}" \ 40 | "$package" \ 41 | --run "$tool" 42 | } 43 | 44 | ################################################################################ 45 | run_test() { 46 | compiler=$1 47 | args=("--argstr" "compiler" "$compiler") 48 | 49 | for package in "${packages[@]}"; do 50 | # Build a package: 51 | run_build "$package" "${args[@]}" 52 | done 53 | 54 | # Check the `bin' attribute: 55 | run_build "$top/hello-world" "${args[@]}" -A bin 56 | 57 | # Create an interactive development environment. 58 | for package in "${packages[@]}"; do 59 | run_tool "$package/shell.nix" "cabal --version" "${args[@]}" 60 | done 61 | 62 | # Load an interactive development environment that isn't connected 63 | # to a nix-hs controlled project. 64 | run_tool "$top/../shell" "cabal-fmt --version" "${args[@]}" 65 | } 66 | 67 | ################################################################################ 68 | if [ $# -eq 0 ]; then 69 | for compiler in $(jq -r 'keys|join(" ")' "$top/../compilers.json"); do 70 | run_test "$compiler" 71 | done 72 | elif [ $# -eq 1 ]; then 73 | run_test "$1" 74 | else 75 | echo >&2 "ERROR: invalid arguments" 76 | exit 1 77 | fi 78 | 79 | # Local Variables: 80 | # mode: sh 81 | # sh-shell: bash 82 | # End: 83 | --------------------------------------------------------------------------------