├── .github └── workflows │ └── gh-pages.yaml ├── .gitignore ├── Dockerfile ├── LICENSE.md ├── Makefile ├── README.md ├── book.toml ├── code ├── 01-getting-started │ ├── 01-introduction.md │ ├── 02-installation.md │ └── 03-resources.md ├── 02-nix-commands │ ├── 01-install-global-packages.md │ ├── 02-use-packages-in-nix-shell.md │ └── 03-nix-repl.md ├── 03-nix-basics │ ├── 01-primitives.md │ ├── 02-expressions.md │ ├── 03-files.md │ ├── 03-files │ │ ├── hello-2.txt │ │ ├── hello.nix │ │ └── hello.txt │ ├── 04-import.md │ └── 04-import │ │ ├── bar.nix │ │ ├── default.nix │ │ └── foo.nix ├── 04-derivations │ ├── 01-derivation-basics.md │ ├── 01-derivation-basics │ │ ├── hello-sleep.nix │ │ └── hello.nix │ ├── 02-dependencies.md │ ├── 02-dependencies │ │ ├── greet.nix │ │ └── upper-greet.nix │ ├── 03-fibonacci.md │ ├── 03-fibonacci │ │ ├── fib-serialized.nix │ │ └── fib.nix │ ├── 04-raw-derivation.md │ ├── 04-raw-derivation │ │ └── build-hello.sh │ ├── 05-standard-derivation.md │ └── 06-build-phases.md ├── 05-package-management │ ├── 01-dependency-management.md │ ├── 02-basic-haskell.md │ ├── 03-version-conflicts.md │ ├── 04-transitive-version-conflicts.md │ ├── 05-multi-versioned-haskell-packages.md │ ├── 06-other-strategies.md │ ├── haskell-project-v1 │ │ ├── haskell │ │ │ ├── .gitignore │ │ │ ├── Main.hs │ │ │ ├── Setup.hs │ │ │ ├── cabal.project │ │ │ └── haskell-project.cabal │ │ └── nix │ │ │ ├── 01-naive │ │ │ └── default.nix │ │ │ ├── 02-nixpkgs │ │ │ ├── default.nix │ │ │ └── shell.nix │ │ │ ├── 03-haskell.nix │ │ │ ├── default.nix │ │ │ ├── project.nix │ │ │ └── shell.nix │ │ │ ├── sources.json │ │ │ └── sources.nix │ ├── haskell-project-v2 │ │ ├── haskell │ │ │ ├── .gitignore │ │ │ ├── Main.hs │ │ │ ├── Setup.hs │ │ │ ├── cabal.project │ │ │ └── haskell-project.cabal │ │ └── nix │ │ │ ├── 01-nixpkgs-conflict │ │ │ ├── default.nix │ │ │ └── shell.nix │ │ │ ├── 02-nixpkgs-override │ │ │ ├── default.nix │ │ │ └── shell.nix │ │ │ ├── 03-haskell.nix │ │ │ ├── default.nix │ │ │ ├── project.nix │ │ │ └── shell.nix │ │ │ ├── sources.json │ │ │ └── sources.nix │ └── haskell-project-v3 │ │ ├── haskell │ │ ├── .gitignore │ │ ├── Main.hs │ │ ├── Setup.hs │ │ ├── cabal.project │ │ └── haskell-project.cabal │ │ └── nix │ │ ├── 01-nixpkgs-conflict │ │ ├── default.nix │ │ └── shell.nix │ │ ├── 02-nixpkgs-not-found │ │ ├── default.nix │ │ └── shell.nix │ │ ├── 03-nixpkgs-transitive-deps │ │ ├── default.nix │ │ └── shell.nix │ │ ├── 04-nixpkgs-infinite-recursion │ │ ├── default.nix │ │ └── shell.nix │ │ ├── 05-nixpkgs-override │ │ ├── default.nix │ │ └── shell.nix │ │ ├── 06-haskell.nix │ │ ├── default.nix │ │ ├── project.nix │ │ └── shell.nix │ │ ├── 07-haskell.nix-materialized │ │ ├── default.nix │ │ ├── plan-hash.txt │ │ ├── plan │ │ │ ├── .plan.nix │ │ │ │ └── haskell-project.nix │ │ │ └── default.nix │ │ ├── project.nix │ │ ├── shell.nix │ │ └── sync-materialized.sh │ │ ├── sources.json │ │ └── sources.nix ├── 06-infrastructure │ ├── 01-caching-nix.md │ ├── 02-caching-haskell.md │ ├── haskell-project-v4 │ │ ├── haskell │ │ │ ├── .gitignore │ │ │ ├── Main.hs │ │ │ ├── Setup.hs │ │ │ ├── cabal.project │ │ │ └── haskell-project.cabal │ │ └── nix │ │ │ ├── 01-gitignore-src │ │ │ ├── default.nix │ │ │ ├── plan-hash.txt │ │ │ ├── plan │ │ │ │ ├── .plan.nix │ │ │ │ │ └── haskell-project.nix │ │ │ │ └── default.nix │ │ │ ├── project.nix │ │ │ ├── shell.nix │ │ │ └── sync-materialized.sh │ │ │ ├── sources.json │ │ │ └── sources.nix │ └── images │ │ ├── create-cache.png │ │ ├── create-token-2.png │ │ └── create-token.png ├── SUMMARY.md ├── index.md └── nixpkgs.nix ├── config └── .keep ├── nix.conf ├── scripts ├── docker.sh └── setup.sh ├── shell.nix └── theme ├── highlight.css └── highlight.js /.github/workflows/gh-pages.yaml: -------------------------------------------------------------------------------- 1 | name: Publish GitHub Pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-20.04 11 | steps: 12 | - uses: actions/checkout@v2 13 | 14 | - uses: actions-rs/toolchain@v1 15 | with: 16 | toolchain: stable 17 | 18 | - name: Setup mdBook 19 | uses: peaceiris/actions-mdbook@v1 20 | with: 21 | mdbook-version: '0.4.5' 22 | 23 | - run: mdbook build 24 | 25 | - name: Deploy 26 | uses: peaceiris/actions-gh-pages@v3 27 | with: 28 | github_token: ${{ secrets.GITHUB_TOKEN }} 29 | publish_dir: ./book 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | result* 2 | book/ 3 | config/cachix* 4 | index.html 5 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:20.04 2 | 3 | ARG UID=1000 4 | ARG GID=1000 5 | 6 | RUN groupadd -g $GID -o nix && \ 7 | useradd -m -u $UID -g $GID -o -s /bin/bash nix && \ 8 | usermod -aG sudo nix && \ 9 | DEBIAN_FRONTEND="noninteractive" apt-get update && \ 10 | apt-get install -y git curl wget sudo xz-utils && \ 11 | echo "nix ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/nix 12 | 13 | ENV USER nix 14 | USER nix 15 | 16 | WORKDIR /home/nix 17 | 18 | COPY --chown=nix:nix ./nix.conf /home/nix/.config/nix/nix.conf 19 | 20 | RUN curl -L https://nixos.org/nix/install | sh 21 | 22 | RUN . /home/nix/.nix-profile/etc/profile.d/nix.sh && \ 23 | nix-channel --add https://nixos.org/channels/nixos-20.09 nixpkgs && \ 24 | nix-channel --update && \ 25 | nix-env -iA cachix -f https://cachix.org/api/v1/install && \ 26 | cachix use iohk 27 | 28 | RUN echo "cd ~/nix-workshop && source ./scripts/setup.sh" >> /home/nix/.profile 29 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ## License 2 | 3 | Copyright (c) 2020-2021 Scrive AB. 4 | This work is licensed under both the 5 | [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/) 6 | and 7 | the [MIT license](https://opensource.org/licenses/MIT). 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | nix-shell --run "mdbook build" 3 | 4 | serve: 5 | nix-shell --run "mdbook serve" 6 | 7 | docker: 8 | ./scripts/docker.sh 9 | 10 | .PHONY: build serve docker 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Scrive Nix Workshop 2 | 3 | Welcome to Scrive Nix Workshop! This book contains the workshop materials 4 | used for training Haskell developers at [Scrive](https://www.scrive.com/). 5 | (We are [hiring Haskell developers](https://careers.scrive.com/jobs/996814-haskell-developer)!) 6 | 7 | We are open sourcing this content so that the Nix community can also benefit 8 | from the content and make contribution to improve it. 9 | 10 | The content in this workshop is currently organized based on the workshop sessions 11 | we have held at Scrive. With the materials open sourced, we welcome to changes 12 | to make this more catered for the general audience in the Nix community. 13 | We hope that the current materials are still useful for more people to learn 14 | about Nix. 15 | 16 | Nix is a rather difficult subject with a steep learning curve, even for us! 17 | So this is also our chance to learn from the rest of the Nix community. 18 | If there is any mistake or misconception in this book, do let us know! 19 | 20 | ## Build Instructions 21 | 22 | A HTML version of this workshop is published at 23 | [https://scrive.github.io/nix-workshop/](https://scrive.github.io/nix-workshop/). 24 | The source code of this workshop is available on 25 | [GitHub](https://github.com/scrive/nix-workshop). 26 | This workshop material is built using 27 | [mdbook](https://rust-lang.github.io/mdBook/). 28 | 29 | To view this book, first [install Nix](https://nixos.org/download.html), 30 | and then run: 31 | 32 | ```bash 33 | make serve 34 | ``` 35 | 36 | You can then browse to [http://localhost:3000](http://localhost:3000) to view the book webpage. 37 | 38 | ## License 39 | 40 | Copyright (c) 2020-2021 Scrive AB. 41 | This work is licensed under both the 42 | [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/) 43 | and 44 | the [MIT license](https://opensource.org/licenses/MIT). 45 | -------------------------------------------------------------------------------- /book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Soares Chen"] 3 | language = "en" 4 | multilingual = false 5 | src = "code" 6 | title = "Scrive Nix Workshop" 7 | 8 | [build] 9 | create-missing = false 10 | build-dir = "./book" 11 | 12 | [output.html] 13 | default-theme = "navy" 14 | preferred-dark-theme = "navy" 15 | -------------------------------------------------------------------------------- /code/01-getting-started/01-introduction.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | What is Nix? 4 | 5 | ## Programming Language 6 | 7 | - Dynamically typed - Similar semantics to JavaScript and Lisp. 8 | 9 | - Functional programming - Higher order functions, immutability, etc. 10 | 11 | - Lazy - Values are not evaluated until needed. 12 | 13 | ## Package Manager 14 | 15 | - Packages as special Nix objects that produce derivations and build artifacts. 16 | 17 | - One package can serve as build input of another package. 18 | 19 | - Multiple versions of the "same" package can be present on the same system. 20 | 21 | ## Build System 22 | 23 | - Packages are built from source code. 24 | 25 | - Build artifacts of packages are cached based on content address (SHA256 checksum). 26 | 27 | - Multi language / multi repository build system. 28 | - Language agnostic. 29 | - Construct your own build system pipeline. 30 | 31 | ## Operating System 32 | 33 | - Nix itself is a pseudo operating system. 34 | - Rich set of Nix packages that can typically be found in OS packages. 35 | 36 | - Nix packages can co-exist non-destructively with native OS packages. 37 | 38 | - All Nix artifacts are stored in `/nix`. 39 | 40 | - Global "installation" is merely a set of symlinks to Nix artifacts in 41 | `/nix/store`. 42 | 43 | - Lightweight activation of global Nix packages. 44 | 45 | - Add `~/.nix-profile/bin/` to `$PATH`. 46 | 47 | - Call `source ~/.nix-profile/etc/profile.d/nix.sh` to activate Nix. 48 | 49 | - Otherwise Nix is almost invisible to users if it is not activated. 50 | 51 | - NixOS is a full Linux operating system. 52 | 53 | ## Reproducibility 54 | 55 | - Key differentiation of Nix as compared to other solutions. 56 | 57 | - Nix packages are built inside a lightweight sandbox. 58 | 59 | - No containerization. 60 | 61 | - Sanitize all environment variables. 62 | 63 | - Special `$HOME` directory at `/homeless-shelter`. 64 | 65 | - Reset date to Unix time 0. 66 | 67 | - Very difficult to accidentally escape the sandbox. 68 | 69 | - Content-addressable storage. 70 | 71 | - Addresses of Nix packages are based on a checksum of the source code, 72 | plus other factors such as CPU architecture and operating system. 73 | 74 | - If the checksum of the source code changes, the addresses of the derivation 75 | and any build artifacts also change. 76 | 77 | - If the address of a dependency changes, the addresses of the 78 | derivation and build artifact also change. -------------------------------------------------------------------------------- /code/01-getting-started/02-installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | Download available at https://nixos.org/download.html. 4 | 5 | Simplest way is to run: 6 | 7 | ```bash 8 | $ curl -L https://nixos.org/nix/install | sh 9 | ``` 10 | 11 | After installation, you might need to relogin to your shell to 12 | reload the environment. Otherwise, run the following to use 13 | Nix immediately: 14 | 15 | ```bash 16 | source ~/.nix-profile/etc/profile.d/nix.sh 17 | ``` 18 | 19 | You may want to configure to load this automatically in `~/.bashrc` or similar 20 | file. 21 | 22 | 23 | ## Update 24 | 25 | If you have installed Nix before but have not updated it for a while, 26 | you should update it with: 27 | 28 | ```bash 29 | nix-channel --update 30 | ``` 31 | 32 | This helps ensure we are installing the latest version of packages 33 | in global installation and global imports. -------------------------------------------------------------------------------- /code/01-getting-started/03-resources.md: -------------------------------------------------------------------------------- 1 | # Learning Resources for Nix 2 | 3 | - [Official Nix Manuals](https://nixos.org/learn.html): 4 | 5 | - [Nix Manual](https://nixos.org/manual/nix/unstable) - Information about `nix`-the-command and the Nix Language. 6 | - [Nixpkgs Manual](https://nixos.org/manual/nixpkgs/unstable) - Information about the Nix Package Set (`nixpkgs`). How to extend and customise packages, how each language ecosystem is packaged, etc. 7 | - [NixOS Manual](https://nixos.org/manual/nixos/unstable) - Information about the NixOS operating system. How to install/configure/update NixOS, the DSL for describing configuration options, etc. 8 | 9 | - [nix.dev](https://nix.dev/) - Pragmatic guide on how to use Nix productively. 10 | 11 | - [Awesome Nix](https://github.com/nix-community/awesome-nix) - Curated list of 12 | Nix resources. 13 | 14 | - [Nix Pills](https://nixos.org/guides/nix-pills/) - Alternative Nix tutorial. Takes a bottom-up approach, explaining Nix and Nixpkgs design patterns along the way. 15 | 16 | - [Gabriel439's Nix and Haskell tutorial](https://github.com/Gabriel439/haskell-nix) 17 | -------------------------------------------------------------------------------- /code/02-nix-commands/01-install-global-packages.md: -------------------------------------------------------------------------------- 1 | # Install Global Packages 2 | 3 | For newcomers, we can think of Nix as a supercharged package manager. Similar to traditional package managers, we can use Nix to install packages globally. Nix "installs" a global package by adding symlinkgs to `~/.nix-profile/bin`, which should be automatically included in your shell's `$PATH`. 4 | 5 | ```bash 6 | $ nix-env -i hello 7 | installing 'hello-2.10' 8 | building '/nix/store/mlfrpy1ahv3arh2n23p45vdpm0p4nl1x-user-environment.drv'... 9 | created 39 symlinks in user environment 10 | ``` 11 | 12 | ```bash 13 | $ hello 14 | Hello, world! 15 | 16 | $ which hello 17 | /home/user/.nix-profile/bin/hello 18 | 19 | $ readlink $(which hello) 20 | /nix/store/ylhzcjbchfihsrpsg0dxx9niwzp35y63-hello-2.10/bin/hello 21 | ``` 22 | 23 | ## Uninstall 24 | 25 | ```bash 26 | $ nix-env --uninstall hello 27 | uninstalling 'hello-2.10' 28 | ``` 29 | 30 | While convenient, global packages pollute the global environment of our system. [Next](02-use-packages-in-nix-shell.html) we will look at how Nix shell can provide a local shell environment that provide the same dependencies that we need. 31 | -------------------------------------------------------------------------------- /code/02-nix-commands/02-use-packages-in-nix-shell.md: -------------------------------------------------------------------------------- 1 | # Nix Shell 2 | 3 | You can use Nix packages without installing them globally on your machine. This is a good way to bring in the tools you need for individual projects. 4 | 5 | ```bash 6 | $ nix-shell -p hello 7 | 8 | [nix-shell:nix-workshop]$ hello 9 | Hello, world! 10 | ``` 11 | 12 | ## Using Multiple Packages 13 | 14 | ```bash 15 | $ nix-shell -p nodejs ghc cabal-install 16 | 17 | [nix-shell:nix-workshop]$ which node 18 | /nix/store/ndkzg5kpyp92mlzh5h66l4j393x6b256-nodejs-12.19.0/bin/node 19 | 20 | [nix-shell:nix-workshop]$ which ghc 21 | /nix/store/sbqnpfnx4w8jb7jq2yb71pifihwqy2a5-ghc-8.8.4/bin/ghc 22 | 23 | [nix-shell:nix-workshop]$ which cabal 24 | /nix/store/060x141b9fz2pm6yz4zn3i0ncavbdbf7-cabal-install-3.2.0.0/bin/cabal 25 | ``` 26 | 27 | ## Using `shell.nix` 28 | 29 | It is common practice for Nix-using projects to provide a `shell.nix` file that specifies the shell environment. The `nix-shell` command reads this file, allowing us to create reproducible shell environments without using `-p`. These environments can provide access to any tool written in any language, without polluting the global environment. We will cover the use of `shell.nix` in a [later chapter](../04-derivations/04-raw-derivation.md#nix-shell). 30 | -------------------------------------------------------------------------------- /code/02-nix-commands/03-nix-repl.md: -------------------------------------------------------------------------------- 1 | 2 | # Nix Repl 3 | 4 | ```bash 5 | nix repl 6 | Welcome to Nix version 2.3.8. Type :? for help. 7 | 8 | nix-repl> "Hello World!" 9 | "Hello World!" 10 | ``` 11 | -------------------------------------------------------------------------------- /code/03-nix-basics/01-primitives.md: -------------------------------------------------------------------------------- 1 | # Nix Primitives 2 | 3 | ## Strings 4 | 5 | ```nix 6 | nix-repl> "hello" 7 | "hello" 8 | ``` 9 | 10 | ## Booleans 11 | 12 | ```nix 13 | nix-repl> true 14 | true 15 | 16 | nix-repl> false 17 | false 18 | 19 | nix-repl> true && false 20 | false 21 | 22 | nix-repl> true || false 23 | true 24 | ``` 25 | 26 | ## Null 27 | 28 | ```nix 29 | nix-repl> null 30 | null 31 | 32 | nix-repl> true && null 33 | error: value is null while a Boolean was expected, at (string):1:1 34 | ``` 35 | 36 | ## Numbers 37 | 38 | ```nix 39 | nix-repl> 1 40 | 1 41 | 42 | nix-repl> 2 43 | 2 44 | 45 | nix-repl> 1 + 2 46 | 3 47 | ``` 48 | 49 | ## String Interpolation 50 | 51 | ```nix 52 | nix-repl> name = "John" 53 | 54 | nix-repl> name 55 | "John" 56 | 57 | nix-repl> "Hello, ${name}!" 58 | "Hello, John!" 59 | ``` 60 | 61 | ## Multiline Strings 62 | 63 | ```nix 64 | nix-repl> '' 65 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. 66 | Nullam augue ligula, pharetra quis mi porta. 67 | 68 | - ${name} 69 | '' 70 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n Nullam augue ligula, pharetra quis mi porta.\n\n- John\n" 71 | ``` 72 | 73 | ## String Concatenation 74 | 75 | ```nix 76 | nix-repl> "Hello " + "World" 77 | "Hello World" 78 | 79 | nix-repl> "Hello " + 123 80 | error: cannot coerce an integer to a string, at (string):1:1 81 | ``` 82 | 83 | ## Set / Object 84 | 85 | ```nix 86 | nix-repl> object = { foo = "foo val"; bar = "bar val"; } 87 | 88 | nix-repl> object 89 | { bar = "bar val"; foo = "foo val"; } 90 | 91 | nix-repl> object.foo 92 | "foo val" 93 | 94 | nix-repl> object.bar 95 | "bar val" 96 | ``` 97 | 98 | ## Merge Objects 99 | 100 | ```nix 101 | nix-repl> a = { foo = "foo val"; bar = "bar val"; } 102 | 103 | nix-repl> b = { foo = "override"; baz = "baz val"; } 104 | 105 | nix-repl> a // b 106 | { bar = "bar val"; baz = "baz val"; foo = "override"; } 107 | ``` 108 | 109 | ## Inherit 110 | 111 | ```nix 112 | nix-repl> foo = "foo val" 113 | 114 | nix-repl> bar = "bar val" 115 | 116 | nix-repl> { foo = foo; bar = bar; } 117 | { bar = "bar val"; foo = "foo val"; } 118 | 119 | nix-repl> { inherit foo bar; } 120 | { bar = "bar val"; foo = "foo val"; } 121 | ``` 122 | 123 | ## Inherit From Object 124 | 125 | ```nix 126 | nix-repl> let 127 | object = { 128 | foo = "foo val"; 129 | bar = "bar val"; 130 | }; 131 | in 132 | { 133 | foo = object.foo; 134 | 135 | baz = "baz val"; 136 | } 137 | { baz = "baz val"; foo = "foo val"; } 138 | ``` 139 | 140 | 141 | ```nix 142 | nix-repl> let 143 | object = { 144 | foo = "foo val"; 145 | bar = "bar val"; 146 | }; 147 | in 148 | { 149 | inherit (object) foo; 150 | 151 | baz = "baz val"; 152 | } 153 | { baz = "baz val"; foo = "foo val"; } 154 | ``` 155 | 156 | 157 | 158 | ## List 159 | 160 | ```nix 161 | nix-repl> list = [ "hello" 123 { foo = "foo"; } ] 162 | 163 | nix-repl> list 164 | [ "hello" 123 { ... } ] 165 | 166 | nix-repl> builtins.elemAt list 2 167 | { foo = "foo"; } 168 | ``` 169 | 170 | ## List Concatenation 171 | 172 | ```nix 173 | nix-repl> [ 1 2 ] ++ [ "foo" "bar" ] 174 | [ 1 2 "foo" "bar" ] 175 | ``` 176 | -------------------------------------------------------------------------------- /code/03-nix-basics/02-expressions.md: -------------------------------------------------------------------------------- 1 | # Nix Expressions 2 | 3 | ## If Expression 4 | 5 | ```nix 6 | nix-repl> if true then 0 else 1 7 | 0 8 | 9 | nix-repl> if false then 0 else "foo" 10 | "foo" 11 | 12 | nix-repl> if null then 1 else 2 13 | error: value is null while a Boolean was expected 14 | ``` 15 | 16 | ## Let Expression 17 | 18 | ```nix 19 | nix-repl> let 20 | foo = "foo val"; 21 | bar = "bar val, ${foo}"; 22 | in 23 | { inherit foo bar; } 24 | { bar = "bar val, foo val"; foo = "foo val"; } 25 | ``` 26 | 27 | 28 | ## With Expression 29 | 30 | ```nix 31 | nix-repl> let 32 | object = { 33 | foo = "foo val"; 34 | bar = "bar val"; 35 | }; 36 | in 37 | [ 38 | object.foo 39 | object.bar 40 | ] 41 | [ "foo val" "bar val" ] 42 | ``` 43 | 44 | ```nix 45 | nix-repl> let 46 | object = { 47 | foo = "foo val"; 48 | bar = "bar val"; 49 | }; 50 | in 51 | with object; [ 52 | foo 53 | bar 54 | ] 55 | [ "foo val" "bar val" ] 56 | ``` 57 | 58 | 59 | ## Function 60 | 61 | ```nix 62 | nix-repl> greet = name: "Hello, ${name}!" 63 | 64 | nix-repl> greet "Alice" 65 | "Hello, Alice!" 66 | 67 | nix-repl> greet "Bob" 68 | "Hello, Bob!" 69 | ``` 70 | 71 | ## Curried Function 72 | 73 | ```nix 74 | nix-repl> secret-greet = code: name: 75 | if code == "secret" 76 | then "Hello, ${name}!" 77 | else "Nothing here" 78 | 79 | nix-repl> secret-greet "secret" "John" 80 | "Hello, John!" 81 | 82 | nix-repl> nothing = secret-greet "wrong" 83 | 84 | nix-repl> nothing "Alice" 85 | "Nothing here" 86 | 87 | nix-repl> nothing "Bob" 88 | "Nothing here" 89 | ``` 90 | 91 | ## Named Arguments 92 | 93 | ```nix 94 | nix-repl> greet = { name, title }: "Hello, ${title} ${name}" 95 | 96 | nix-repl> greet { title = "Ms."; name = "Alice"; } 97 | "Hello, Ms. Alice" 98 | 99 | nix-repl> greet { name = "Alice"; } 100 | error: anonymous function at (string):1:2 called without required argument 'title', at (string):1:1 101 | ``` 102 | 103 | ## Default Arguments 104 | 105 | ```nix 106 | nix-repl> greet = { name ? "Anonymous", title ? "Ind." }: "Hello, ${title} ${name}" 107 | 108 | nix-repl> greet {} 109 | "Hello, Ind. Anonymous" 110 | 111 | nix-repl> greet { name = "Bob"; } 112 | "Hello, Ind. Bob" 113 | 114 | nix-repl> greet { title = "Mr."; } 115 | "Hello, Mr. Anonymous" 116 | ``` 117 | 118 | ## Lazy Evaluation 119 | 120 | ```nix 121 | nix-repl> err = throw "something went wrong" 122 | 123 | nix-repl> err 124 | error: something went wrong 125 | 126 | nix-repl> if true then 1 else err 127 | 1 128 | 129 | nix-repl> if false then 1 else err 130 | error: something went wrong 131 | 132 | nix-repl> object = { foo = err; bar = "bar val"; } 133 | 134 | nix-repl> object.bar 135 | "bar val" 136 | 137 | nix-repl> object.foo 138 | error: something went wrong 139 | ``` 140 | 141 | ### Sequencing 142 | 143 | ```nix 144 | nix-repl> builtins.seq err true 145 | error: something went wrong 146 | ``` 147 | 148 | ```nix 149 | nix-repl> builtins.seq object true 150 | true 151 | 152 | nix-repl> builtins.deepSeq object true 153 | error: something went wrong 154 | ``` -------------------------------------------------------------------------------- /code/03-nix-basics/03-files.md: -------------------------------------------------------------------------------- 1 | # File Management in Nix 2 | 3 | ## String to File 4 | 5 | ```nix 6 | nix-repl> builtins.toFile "hello.txt" "Hello World!" 7 | "/nix/store/r4mvpxzh7rgrm4j831b2yi90zq64grqm-hello.txt" 8 | ``` 9 | 10 | ```bash 11 | $ cat /nix/store/r4mvpxzh7rgrm4j831b2yi90zq64grqm-hello.txt 12 | Hello World! 13 | ``` 14 | 15 | 16 | ## Path 17 | 18 | ```nix 19 | nix-repl> ./. 20 | /path/to/nix-workshop 21 | 22 | nix-repl> ./code/01-getting-started 23 | /path/to/nix-workshop/code/01-getting-started 24 | 25 | nix-repl> ./not-found 26 | /path/to/nix-workshop/not-found 27 | ``` 28 | 29 | ## Path Concatenation 30 | 31 | ```nix 32 | nix-repl> ./. + "code/01-getting-started" 33 | /path/to/nix-workshop/code/01-getting-started 34 | ``` 35 | 36 | ## Read File 37 | 38 | ```nix 39 | nix-repl> builtins.readFile ./code/03-nix-basics/03-files/hello.txt 40 | "Hello World!" 41 | 42 | nix-repl> builtins.readFile /nix/store/r4mvpxzh7rgrm4j831b2yi90zq64grqm-hello.txt 43 | "Hello World!" 44 | 45 | nix-repl> builtins.readFile (builtins.toFile "hello" "Hello World!") 46 | "Hello World!" 47 | ``` 48 | 49 | ## Path 50 | 51 | ```nix 52 | nix-repl> builtins.path { path = ./.; } 53 | "/nix/store/s0c3cc8k6dy51zx9xicfprsl9r35zvf6-nix-workshop" 54 | ``` 55 | 56 | ```nix 57 | nix-repl> "${./.}" 58 | "/nix/store/s0c3cc8k6dy51zx9xicfprsl9r35zvf6-nix-workshop" 59 | ``` 60 | 61 | ``` 62 | $ ls /nix/store/s0c3cc8k6dy51zx9xicfprsl9r35zvf6-nix-workshop 63 | 01-getting-started 02-nix-commands ... 64 | ``` 65 | 66 | The exact address changes every time the directory is updated. 67 | 68 | ## Named Path 69 | 70 | ```nix 71 | nix-repl> workshop = builtins.path { path = ./.; name = "first-scrive-workshop"; } 72 | 73 | nix-repl> workshop 74 | "/nix/store/fp0lw035xhxqwgfqifxlb430lyw48r7m-first-scrive-workshop" 75 | 76 | nix-repl> builtins.readFile (workshop + "/code/03-nix-basics/03-files/hello.txt") 77 | "Hello World!" 78 | ``` 79 | 80 | ## Content-Addressable Path 81 | 82 | The files [hello.txt](03-files/hello.txt) and [hello-2.txt](03-files/hello-2.txt) 83 | both have the same content `"Hello World!"`, but they produce different artifacts 84 | in the Nix store, i.e. the name of a Nix artifact depends on the name of the 85 | original file / directory. 86 | 87 | ```nix 88 | nix-repl> builtins.path { path = ./code/03-nix-basics/03-files/hello.txt; } 89 | "/nix/store/925f1jb1ajrypjbyq7rylwryqwizvhp0-hello.txt" 90 | 91 | nix-repl> builtins.path { path = ./code/03-nix-basics/03-files/hello-2.txt; } 92 | "/nix/store/bghk1lsjcylfm05j00zj5j42lv09i79z-hello-2.txt" 93 | ``` 94 | 95 | Solution: give a fixed name to path artifacts: 96 | 97 | ```nix 98 | nix-repl> builtins.path { 99 | name = "hello.txt"; 100 | path = ./code/03-nix-basics/03-files/hello-2.txt; 101 | } 102 | "/nix/store/925f1jb1ajrypjbyq7rylwryqwizvhp0-hello.txt" 103 | ``` 104 | 105 | 106 | ## Fetch URL 107 | 108 | ```nix 109 | nix-repl> example = builtins.fetchurl "https://scrive.com/robots.txt" 110 | 111 | nix-repl> example 112 | [0.0 MiB DL] downloading 'https://scrive.com/robots.txt'"/nix/store/r98i29hkzwyykm984fpr4ldbai2r8lhj-robots.txt" 113 | 114 | nix-repl> example 115 | "/nix/store/r98i29hkzwyykm984fpr4ldbai2r8lhj-robots.txt" 116 | ``` 117 | 118 | ```bash 119 | $ cat /nix/store/r98i29hkzwyykm984fpr4ldbai2r8lhj-robots.txt 120 | User-agent: * 121 | Sitemap: https://scrive.com/sitemap.xml 122 | Disallow: /amnesia/ 123 | Disallow: /api/ 124 | ``` 125 | 126 | URLs are only fetched once locally! 127 | 128 | ## Fetch Tarball 129 | 130 | ```bash 131 | nix-repl> nodejs-src = builtins.fetchTarball 132 | "https://nodejs.org/dist/v14.15.0/node-v14.15.0-linux-x64.tar.xz" 133 | nix-repl> nodejs-src 134 | "/nix/store/6wkj0blipzdqbsvwv03qy57n4l33scpw-source" 135 | ``` 136 | 137 | ```bash 138 | $ ls /nix/store/6wkj0blipzdqbsvwv03qy57n4l33scpw-source 139 | bin CHANGELOG.md include lib LICENSE README.md share 140 | ``` 141 | 142 | ## SHA256 Checksum 143 | 144 | Make sure that the content retrieved is the same for all users. 145 | 146 | ```bash 147 | nix-repl> nodejs-src = builtins.fetchTarball { 148 | name = "nodejs-src"; 149 | url = "https://nodejs.org/dist/v14.15.0/node-v14.15.0-linux-x64.tar.xz"; 150 | sha256 = "14jmakaxmlllyyprydc6826s7yk50ipvmwwrkzf6pdqis04g7a9v"; 151 | } 152 | nix-repl> nodejs-src 153 | "/nix/store/6wkj0blipzdqbsvwv03qy57n4l33scpw-source" 154 | ``` 155 | -------------------------------------------------------------------------------- /code/03-nix-basics/03-files/hello-2.txt: -------------------------------------------------------------------------------- 1 | Hello World! -------------------------------------------------------------------------------- /code/03-nix-basics/03-files/hello.nix: -------------------------------------------------------------------------------- 1 | builtins.toFile "hello.txt" "Hello World!" -------------------------------------------------------------------------------- /code/03-nix-basics/03-files/hello.txt: -------------------------------------------------------------------------------- 1 | Hello World! -------------------------------------------------------------------------------- /code/03-nix-basics/04-import.md: -------------------------------------------------------------------------------- 1 | # Importing Modules 2 | 3 | ## Importing Nix Modules 4 | 5 | We have the following files in 6 | [03-nix-basics/04-import](03-nix-basics/04-import): 7 | 8 | - [foo.nix](./04-import/foo.nix) 9 | 10 | ```nix 11 | {{#include ./04-import/foo.nix}} 12 | ``` 13 | 14 | - [bar.nix](./04-import/bar.nix) 15 | 16 | ```nix 17 | {{#include ./04-import/bar.nix}} 18 | ``` 19 | 20 | - [default.nix](./04-import/default.nix) 21 | 22 | 23 | ```nix 24 | {{#include ./04-import/default.nix}} 25 | ``` 26 | 27 | 28 | ```nix 29 | nix-repl> import ./code/03-nix-basics/04-import/foo.nix 30 | "foo val" 31 | 32 | nix-repl> import ./code/03-nix-basics/04-import/bar.nix 33 | [ "bar val 1" "bar val 2" ] 34 | 35 | nix-repl> import ./code/03-nix-basics/04-import 36 | { bar = [ ... ]; foo = "foo val"; } 37 | ``` 38 | 39 | ## Importing Global Modules 40 | 41 | ```nix 42 | nix-repl> nixpkgs = import {} 43 | 44 | nix-repl> nixpkgs.lib.stringLength "hello" 45 | 5 46 | ``` 47 | 48 | ## Importing a Tarball 49 | 50 | Let's say we have the same [foo.nix](./code/04-import/foo.nix) now available 51 | as a [gist](https://gist.github.com/soareschen/d41e9b764018da4d2336644329c915e4). 52 | We can import that file by asking Nix to fetch the tarball generated by GitHub: 53 | 54 | ```nix 55 | nix-repl> gist = builtins.fetchTarball "https://gist.github.com/soareschen/d41e9b764018da4d2336644329c915e4/archive/476c45eaba13e23316cdca781bda7ec68676397b.tar.gz" 56 | 57 | nix-repl> foo = import (gist + "/foo.nix") 58 | 59 | nix-repl> foo 60 | "foo val" 61 | ``` 62 | 63 | ## Pinning a Tarball Version 64 | 65 | We have tested our remote `foo.nix` hosted on GitHub Gist, expecting it 66 | to have the same content as the local [foo.nix](./04-import/foo.nix) we have. 67 | But the gist can be updated, yet we want to ensure that the content remains 68 | the same. This can be done by pinning the SHA256 checksum of `foo.nix` to 69 | what we expect using `nix-prefetch-url`: 70 | 71 | ```bash 72 | $ nix-prefetch-url --type sha256 --unpack "https://gist.github.com/soareschen/d41e9b764018da4d2336644329c915e4/archive/476c45eaba13e23316cdca781bda7ec68676397b.tar.gz" 73 | unpacking... 74 | [0.0 MiB DL] 75 | path is '/nix/store/vik2vk9ifbyps9pvhqa89px0c76cvaxz-476c45eaba13e23316cdca781bda7ec68676397b.tar.gz' 76 | 1ig5g6gvys26ka11z0wx08l72h8g5rr7p4fywk905sabdknf92yx 77 | ``` 78 | 79 | We can then copy the SHA256 checksum to make sure that the file content at the URL 80 | never unexpectedly changes: 81 | 82 | ```nix 83 | nix-repl> gist = builtins.fetchTarball { 84 | url = "https://gist.github.com/soareschen/d41e9b764018da4d2336644329c915e4/archive/476c45eaba13e23316cdca781bda7ec68676397b.tar.gz"; 85 | sha256 = "1ig5g6gvys26ka11z0wx08l72h8g5rr7p4fywk905sabdknf92yx"; 86 | } 87 | ``` 88 | 89 | ## Pinning Nixpkgs 90 | 91 | We can use the same approach to pin `nixpkgs` itself. By pinning `nixpkgs` to a 92 | specific commit ID and SHA256 checksum, we can be sure that everyone that uses 93 | our Nix module are using the exact version of `nixpkgs` that we have specified. 94 | 95 | For instance, we can pin the `nixpkgs` version we use to commit 96 | [`c1e5f8723ceb684c8d501d4d4ae738fef704747e`](https://github.com/NixOS/nixpkgs/tree/c1e5f8723ceb684c8d501d4d4ae738fef704747e): 97 | 98 | ```bash 99 | $ nix-prefetch-url --type sha256 --unpack \ 100 | > "https://github.com/NixOS/nixpkgs/archive/c1e5f8723ceb684c8d501d4d4ae738fef704747e.tar.gz" 101 | unpacking... 102 | [19.6 MiB DL] 103 | path is '/nix/store/7ik3kdki828cnva46vnis87ha6axjk7n-c1e5f8723ceb684c8d501d4d4ae738fef704747e.tar.gz' 104 | 02k3l9wnwpmq68xmmfy4wb2panqa1rs04p1mzh2kiwn0449hl86j 105 | ``` 106 | 107 | Now we can import our pinned `nixpkgs`: 108 | 109 | ```nix 110 | nix-repl> nixpkgs-src = builtins.fetchTarball { 111 | url = "https://github.com/NixOS/nixpkgs/archive/c1e5f8723ceb684c8d501d4d4ae738fef704747e.tar.gz"; 112 | sha256 = "02k3l9wnwpmq68xmmfy4wb2panqa1rs04p1mzh2kiwn0449hl86j"; 113 | } 114 | 115 | nix-repl> nixpkgs = import nixpkgs-src {} 116 | 117 | nix-repl> nixpkgs.lib.stringLength "hello" 118 | 5 119 | ``` 120 | 121 | Pinning nixpkgs is highly encouraged when developing Nix modules. Without 122 | pinning, your users may run your modules on a version of nixpkgs that is several 123 | months old. 124 | 125 | We will go through in a later chapter on how to use 126 | [`niv`](https://github.com/nmattia/niv) to automate the management of pinned 127 | remote Nix packages. 128 | -------------------------------------------------------------------------------- /code/03-nix-basics/04-import/bar.nix: -------------------------------------------------------------------------------- 1 | [ "bar val 1" "bar val 2" ] -------------------------------------------------------------------------------- /code/03-nix-basics/04-import/default.nix: -------------------------------------------------------------------------------- 1 | let 2 | foo = import ./foo.nix; 3 | bar = import ./bar.nix; 4 | in 5 | { inherit foo bar; } -------------------------------------------------------------------------------- /code/03-nix-basics/04-import/foo.nix: -------------------------------------------------------------------------------- 1 | "foo val" -------------------------------------------------------------------------------- /code/04-derivations/01-derivation-basics.md: -------------------------------------------------------------------------------- 1 | # Nix Derivation Basics 2 | 3 | First import a pinned version of `nixpkgs` so that we all get the same result: 4 | 5 | ```nix 6 | nix-repl> nixpkgs-src = builtins.fetchTarball { 7 | url = "https://github.com/NixOS/nixpkgs/archive/c1e5f8723ceb684c8d501d4d4ae738fef704747e.tar.gz"; 8 | sha256 = "02k3l9wnwpmq68xmmfy4wb2panqa1rs04p1mzh2kiwn0449hl86j"; 9 | } 10 | 11 | nix-repl> nixpkgs = import nixpkgs-src {} 12 | ``` 13 | 14 | We use the pinned version of `nixpkgs` so that everyone following the 15 | tutorial will get the exact same derivation. 16 | 17 | ## Standard Derivation 18 | 19 | ```nix 20 | nix-repl> hello-drv = nixpkgs.stdenv.mkDerivation { 21 | name = "hello.txt"; 22 | unpackPhase = "true"; 23 | installPhase = '' 24 | echo -n "Hello World!" > $out 25 | ''; 26 | } 27 | 28 | nix-repl> hello-drv 29 | «derivation /nix/store/ad6c51ia15p9arjmvvqkn9fys9sf1kdw-hello.txt.drv» 30 | ``` 31 | 32 | ```bash 33 | $ cat /nix/store/ad6c51ia15p9arjmvvqkn9fys9sf1kdw-hello.txt.drv 34 | Derive([("out","/nix/store/f6qq9bwv0lxw5glzjmin1y1r1s3kangv-hello.txt","","")],...) 35 | 36 | $ nix show-derivation /nix/store/ad6c51ia15p9arjmvvqkn9fys9sf1kdw-hello.txt.drv 37 | { 38 | "/nix/store/ad6c51ia15p9arjmvvqkn9fys9sf1kdw-hello.txt.drv": { 39 | "outputs": { 40 | "out": { 41 | "path": "/nix/store/z449wrqvwncs8clk7bsliabv1g1ci3n3-hello.txt" 42 | } 43 | }, 44 | "inputSrcs": [ 45 | "/nix/store/9krlzvny65gdc8s7kpb6lkx8cd02c25b-default-builder.sh" 46 | ], 47 | ... 48 | } 49 | } 50 | ``` 51 | 52 | ## Building Derivation 53 | 54 | We can now build our derivation: 55 | 56 | ```bash 57 | $ nix-build /nix/store/ad6c51ia15p9arjmvvqkn9fys9sf1kdw-hello.txt.drv 58 | /nix/store/z449wrqvwncs8clk7bsliabv1g1ci3n3-hello.txt 59 | 60 | $ cat /nix/store/z449wrqvwncs8clk7bsliabv1g1ci3n3-hello.txt 61 | Hello World! 62 | ``` 63 | 64 | This may take some time to load on your computer, as Nix fetches the essential 65 | build tools that are commonly needed to build Nix packages. 66 | 67 | We can also build the derivation within Nix repl using the `:b` command: 68 | 69 | ```nix 70 | nix-repl> :b hello-drv 71 | [1 built, 0.0 MiB DL] 72 | 73 | this derivation produced the following outputs: 74 | out -> /nix/store/z449wrqvwncs8clk7bsliabv1g1ci3n3-hello.txt 75 | ``` 76 | 77 | ## Tracing Derivation 78 | 79 | Our `hello-drv` produce the same output as `hello.txt` in previous chapter, 80 | but produce different output in the Nix store. (previously we had 81 | `/nix/store/925f1jb1ajrypjbyq7rylwryqwizvhp0-hello.txt`) 82 | 83 | We can trace the dependencies of the derivation back to its source: 84 | 85 | ```bash 86 | $ nix-store --query --deriver /nix/store/z449wrqvwncs8clk7bsliabv1g1ci3n3-hello.txt 87 | /nix/store/ad6c51ia15p9arjmvvqkn9fys9sf1kdw-hello.txt.drv 88 | 89 | $ nix-store --query --deriver /nix/store/925f1jb1ajrypjbyq7rylwryqwizvhp0-hello.txt 90 | unknown-deriver 91 | ``` 92 | 93 | Our `hello.txt` built from `stdenv.mkDerivation` is built from a derivation 94 | artifact `hello.txt.drv`, but our `hello.txt` created from `builtins.path` 95 | has no deriver. 96 | In other words, the Nix artifacts are different because they are produced from 97 | different derivations. 98 | 99 | We can further trace the dependencies of `hello.txt.drv`: 100 | 101 | ```bash 102 | $ nix-store -qR /nix/store/ad6c51ia15p9arjmvvqkn9fys9sf1kdw-hello.txt.drv 103 | /nix/store/01n3wxxw29wj2pkjqimmmjzv7pihzmd7-which-2.21.tar.gz.drv 104 | /nix/store/03f77phmfdmsbfpcc6mspjfff3yc9fdj-setup-hook.sh 105 | ... 106 | ``` 107 | 108 | That's a lot of dependencies! Where are they being used? We will learn about it 109 | in the next chapter. 110 | 111 | ## Derivation in a Nix File 112 | 113 | We save the same earlier derivation we defined inside a Nix file named 114 | [`hello.nix`](01-derivation-basics/hello.nix). Now we can build our derivation directly: 115 | 116 | ```bash 117 | $ nix-build 04-derivations/01-derivation-basics/hello.nix 118 | /nix/store/z449wrqvwncs8clk7bsliabv1g1ci3n3-hello.txt 119 | ``` 120 | 121 | We can also get the derivation without building it using `nix-instantiate`: 122 | 123 | ```bash 124 | $ nix-instantiate 04-derivations/01-derivation-basics/hello.nix 125 | warning: you did not specify '--add-root'; the result might be removed by the garbage collector 126 | /nix/store/ad6c51ia15p9arjmvvqkn9fys9sf1kdw-hello.txt.drv 127 | ``` 128 | 129 | Ignore the warning from `nix-instantiate`, as we don't care whether the derivation 130 | is deleted during Nix garbage collection. 131 | 132 | Notice that both the derivation and the build output have the same hash 133 | as the earlier result we had in `nix repl`. 134 | 135 | ## Caching Nix Build Artifacts 136 | 137 | We create [`hello-sleep.nix`](01-derivation-basics/hello-sleep.nix) as a variant of 138 | `hello.nix` which sleeps for 10 seconds in its `buildPhase`. 139 | (We will go through how each phase works in the next chapter) 140 | The 10 seconds sleep simulates the time taken to compile a program. 141 | We can see what happens when we try to build the same Nix derivation 142 | multiple times. 143 | 144 | First, instantiating a derivation is not affected by the build time: 145 | 146 | ```bash 147 | $ time nix-instantiate 04-derivations/01-derivation-basics/hello-sleep.nix 148 | /nix/store/58ngrpwgv6hl633a1iyjbmjqlbdqjw92-hello.txt.drv 149 | 150 | real 0m0,217s 151 | user 0m0,179s 152 | sys 0m0,032s 153 | ``` 154 | 155 | The first time we build `hello-sleep.nix`, it is going to take about 10 seconds. 156 | We can also see the logs we printed during the build phase is shown: 157 | 158 | ```bash 159 | $ time nix-build 04-derivations/01-derivation-basics/hello-sleep.nix 160 | these derivations will be built: 161 | /nix/store/58ngrpwgv6hl633a1iyjbmjqlbdqjw92-hello.txt.drv 162 | building '/nix/store/58ngrpwgv6hl633a1iyjbmjqlbdqjw92-hello.txt.drv'... 163 | unpacking sources 164 | patching sources 165 | configuring 166 | no configure script, doing nothing 167 | building 168 | Building hello world... 169 | Finished building hello world! 170 | installing 171 | post-installation fixup 172 | shrinking RPATHs of ELF executables and libraries in /nix/store/lm801yriwjj4298ry74hdv5j0rpkpacq-hello.txt 173 | strip is /nix/store/bnjps68g8ax6abzvys2xpx12imrx8949-binutils-2.31.1/bin/strip 174 | patching script interpreter paths in /nix/store/lm801yriwjj4298ry74hdv5j0rpkpacq-hello.txt 175 | checking for references to /build/ in /nix/store/lm801yriwjj4298ry74hdv5j0rpkpacq-hello.txt... 176 | /nix/store/lm801yriwjj4298ry74hdv5j0rpkpacq-hello.txt 177 | 178 | real 0m12,202s 179 | user 0m0,371s 180 | sys 0m0,084s 181 | ``` 182 | 183 | But the next time we build `hello-sleep.nix`, it will take no time to build, 184 | and there is no build output: 185 | 186 | ```bash 187 | $ time nix-build 03-nix-basics/05-derivation/hello-sleep.nix 188 | /nix/store/lm801yriwjj4298ry74hdv5j0rpkpacq-hello.txt 189 | 190 | real 0m0,310s 191 | user 0m0,256s 192 | sys 0m0,047s 193 | ``` 194 | 195 | Nix determines whether a derivation needs to be rebuilt based on the input 196 | derivation. For our case, in both calls to `hello-sleep.nix`, 197 | `nix-build` instantiates the derivation behind the scene: i.e. 198 | `/nix/store/58ngrpwgv6hl633a1iyjbmjqlbdqjw92-hello.txt.drv`. 199 | So it determines that the result has previously already 200 | been built, and reuses the same Nix artifact. 201 | 202 | ## Derivation as File 203 | 204 | With the duck-typing nature of Nix, derivations act just like files in Nix. 205 | We can actually treat the `hello-drv` we defined earlier as a file and 206 | read from it: 207 | 208 | ```nix 209 | nix-repl> builtins.readFile hello-drv 210 | querying info about missing paths"Hello World!" 211 | ``` 212 | 213 | How does that work? Internally Nix lazily builds a 214 | derivation when it is evaluated, and turns it into 215 | a file path. We can verify that by using `builtins.toPath`: 216 | 217 | ```nix 218 | nix-repl> builtins.toPath hello-drv 219 | "/nix/store/z449wrqvwncs8clk7bsliabv1g1ci3n3-hello.txt" 220 | ``` 221 | 222 | With this property, we can also import derivations 223 | from a Nix file, and then use it as if the derivation 224 | has been built: 225 | 226 | ```nix 227 | nix-repl> hello = import ./code/04-derivations/01-derivation-basics/hello.nix 228 | 229 | nix-repl> builtins.readFile hello 230 | querying info about missing paths"Hello World!" 231 | ``` 232 | 233 | We can even use a derivation as a string. Nix automatically 234 | builds the derivation when it is evaluated as a string: 235 | 236 | ```nix 237 | nix-repl> "path of hello: ${hello}" 238 | "path of hello: /nix/store/z449wrqvwncs8clk7bsliabv1g1ci3n3-hello.txt" 239 | ``` 240 | -------------------------------------------------------------------------------- /code/04-derivations/01-derivation-basics/hello-sleep.nix: -------------------------------------------------------------------------------- 1 | let 2 | nixpkgs-src = builtins.fetchTarball { 3 | url = "https://github.com/NixOS/nixpkgs/archive/c1e5f8723ceb684c8d501d4d4ae738fef704747e.tar.gz"; 4 | sha256 = "02k3l9wnwpmq68xmmfy4wb2panqa1rs04p1mzh2kiwn0449hl86j"; 5 | }; 6 | 7 | nixpkgs = import nixpkgs-src {}; 8 | in 9 | nixpkgs.stdenv.mkDerivation { 10 | name = "hello.txt"; 11 | unpackPhase = "true"; 12 | 13 | buildPhase = '' 14 | echo "Building hello world..." 15 | sleep 10 16 | echo "Finished building hello world!" 17 | ''; 18 | 19 | installPhase = '' 20 | echo -n "Hello World!" > $out 21 | ''; 22 | } -------------------------------------------------------------------------------- /code/04-derivations/01-derivation-basics/hello.nix: -------------------------------------------------------------------------------- 1 | let 2 | nixpkgs-src = builtins.fetchTarball { 3 | url = "https://github.com/NixOS/nixpkgs/archive/c1e5f8723ceb684c8d501d4d4ae738fef704747e.tar.gz"; 4 | sha256 = "02k3l9wnwpmq68xmmfy4wb2panqa1rs04p1mzh2kiwn0449hl86j"; 5 | }; 6 | 7 | nixpkgs = import nixpkgs-src {}; 8 | in 9 | nixpkgs.stdenv.mkDerivation { 10 | name = "hello.txt"; 11 | unpackPhase = "true"; 12 | installPhase = '' 13 | echo -n "Hello World!" > $out 14 | ''; 15 | } -------------------------------------------------------------------------------- /code/04-derivations/02-dependencies.md: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | 3 | Previously we have built toy derivations with dummy output. In practice, 4 | Nix derivations are used for building programs, with build artifacts such as 5 | compiled binaries being the derivation output. 6 | 7 | We can demonstrate this property by "building" a greet program. 8 | First we have to import a pinned version of nixpkgs as before. 9 | To simplify the process we abstract it out into a [nixpkgs.nix](../nixpkgs.nix) 10 | at the root directory. 11 | 12 | Now we build a greet program in [greet.nix](./02-dependencies/greet.nix): 13 | 14 | ```nix 15 | {{#include ./02-dependencies/greet.nix}} 16 | ``` 17 | 18 | ```bash 19 | $ nix-build code/04-derivations/02-dependencies/greet.nix 20 | these derivations will be built: 21 | /nix/store/97lmyym0isl0ism7pfnv1b0ls4cahpi8-greet.drv 22 | building '/nix/store/97lmyym0isl0ism7pfnv1b0ls4cahpi8-greet.drv'... 23 | unpacking sources 24 | patching sources 25 | configuring 26 | no configure script, doing nothing 27 | building 28 | building greet... 29 | installing 30 | post-installation fixup 31 | shrinking RPATHs of ELF executables and libraries in /nix/store/l6xy4qjr8x3ni16skfilw0fvnda13szq-greet 32 | strip is /nix/store/bnjps68g8ax6abzvys2xpx12imrx8949-binutils-2.31.1/bin/strip 33 | stripping (with command strip and flags -S) in /nix/store/l6xy4qjr8x3ni16skfilw0fvnda13szq-greet/bin 34 | patching script interpreter paths in /nix/store/l6xy4qjr8x3ni16skfilw0fvnda13szq-greet 35 | /nix/store/l6xy4qjr8x3ni16skfilw0fvnda13szq-greet/bin/greet: interpreter directive changed from "/usr/bin/env bash" to "/nix/store/qdp56fi357fgxxnkjrwx1g67hrk775im-bash-4.4-p23/bin/bash" 36 | checking for references to /build/ in /nix/store/l6xy4qjr8x3ni16skfilw0fvnda13szq-greet... 37 | /nix/store/l6xy4qjr8x3ni16skfilw0fvnda13szq-greet 38 | ``` 39 | 40 | Now we can run greet: 41 | 42 | ```bash 43 | $ result/bin/greet John 44 | Hello, John! 45 | ``` 46 | 47 | Let's try to see what's inside the produced `greet` script: 48 | 49 | ```bash 50 | $ cat result/bin/greet 51 | #!/nix/store/qdp56fi357fgxxnkjrwx1g67hrk775im-bash-4.4-p23/bin/bash 52 | echo "Hello, $1!" 53 | ``` 54 | 55 | The shebang to the bash shell has been modified to pin to the Nix version of Bash. 56 | 57 | ## Upper Greet 58 | 59 | Our greet program can now be used as a dependency to other derivations. 60 | Let's create an `upper-greet` derivation that convert any greet result to 61 | upper case. 62 | 63 | [upper-greet.nix](./02-dependencies/upper-greet.nix): 64 | 65 | ```nix 66 | {{#include ./02-dependencies/upper-greet.nix}} 67 | ``` 68 | 69 | ### Show Derivation 70 | 71 | First we instantiate `upper-greet.drv` without building it yet: 72 | 73 | ```bash 74 | drv=$(nix-instantiate 04-derivations/02-dependencies/upper-greet.nix) 75 | ``` 76 | 77 | We can use `nix show-derivation` to find out the dependency graph of the 78 | derivation of `upper-greet`: 79 | 80 | ```bash 81 | $ nix show-derivation $drv 82 | { 83 | "/nix/store/n61g8616l7g7zv32q52yrzmzr850mjp0-upper-greet.drv": { 84 | "outputs": { 85 | "out": { 86 | "path": "/nix/store/dj2vp64gbja0bp65lngrw9q4lrm1a8r3-upper-greet" 87 | } 88 | }, 89 | "inputSrcs": [ 90 | "/nix/store/9krlzvny65gdc8s7kpb6lkx8cd02c25b-default-builder.sh" 91 | ], 92 | "inputDrvs": { 93 | "/nix/store/7gby8zic1p851ap63q1vdpwy7z1db85c-coreutils-8.32.drv": [ 94 | "out" 95 | ], 96 | "/nix/store/97lmyym0isl0ism7pfnv1b0ls4cahpi8-greet.drv": [ 97 | "out" 98 | ], 99 | "/nix/store/l54djrh1n7d8zdfn26w7v6zjh5wp7faa-bash-4.4-p23.drv": [ 100 | "out" 101 | ], 102 | "/nix/store/x9why09hwx2pcnmw0fw7hhh1511hyskl-stdenv-linux.drv": [ 103 | "out" 104 | ] 105 | }, 106 | ... 107 | "env": { 108 | "buildInputs": "", 109 | "buildPhase": "echo \"building upper-greet...\"\nsleep 3\n", 110 | "builder": "/nix/store/qdp56fi357fgxxnkjrwx1g67hrk775im-bash-4.4-p23/bin/bash", 111 | ... 112 | "installPhase": "mkdir -p $out/bin\n\ncat <<'EOF' > $out/bin/upper-greet\n#!/usr/bin/env bash\n/nix/store/l6xy4qjr8x3ni16skfilw0fvnda13szq-greet/bin/greet \"$@\" | /nix/store/2shqhfsyzz4rnfyysbzgyp5kbfk29750-coreutils-8.32/bin/tr [a-z] [A-Z]\nEOF\n\nchmod +x $out/bin/upper-greet\n", 113 | "name": "upper-greet", 114 | ... 115 | ``` 116 | 117 | We can see that `greet.drv` is included as one of `inputDrvs`. This means that when 118 | `upper-greet.drv` is being built, `greet.drv` will have to be built first. 119 | 120 | The output path of `upper-greet.drv` is listed in `outputs`. This shows that 121 | the output hash of a derivation is fixed, regardless of the content of the 122 | build result. 123 | 124 | This is also why the output path of `greet.drv` is used directly in `env.installPhase` 125 | of `upper-greet.drv`, even for the case when `greet.drv` has not been built. 126 | 127 | ### Build Derivation 128 | 129 | 130 | ```bash 131 | $ nix-build 04-derivations/02-dependencies/upper-greet.nix 132 | these derivations will be built: 133 | /nix/store/n61g8616l7g7zv32q52yrzmzr850mjp0-upper-greet.drv 134 | building '/nix/store/n61g8616l7g7zv32q52yrzmzr850mjp0-upper-greet.drv'... 135 | unpacking sources 136 | patching sources 137 | configuring 138 | no configure script, doing nothing 139 | building 140 | building upper-greet... 141 | installing 142 | post-installation fixup 143 | shrinking RPATHs of ELF executables and libraries in /nix/store/dj2vp64gbja0bp65lngrw9q4lrm1a8r3-upper-greet 144 | strip is /nix/store/bnjps68g8ax6abzvys2xpx12imrx8949-binutils-2.31.1/bin/strip 145 | stripping (with command strip and flags -S) in /nix/store/dj2vp64gbja0bp65lngrw9q4lrm1a8r3-upper-greet/bin 146 | patching script interpreter paths in /nix/store/dj2vp64gbja0bp65lngrw9q4lrm1a8r3-upper-greet 147 | /nix/store/dj2vp64gbja0bp65lngrw9q4lrm1a8r3-upper-greet/bin/upper-greet: interpreter directive changed from "/usr/bin/env bash" to "/nix/store/qdp56fi357fgxxnkjrwx1g67hrk775im-bash-4.4-p23/bin/bash" 148 | checking for references to /build/ in /nix/store/dj2vp64gbja0bp65lngrw9q4lrm1a8r3-upper-greet... 149 | /nix/store/dj2vp64gbja0bp65lngrw9q4lrm1a8r3-upper-greet 150 | ``` 151 | 152 | As expected, the greet results are turned into upper case. 153 | 154 | ```bash 155 | $ result/bin/upper-greet John 156 | HELLO, JOHN! 157 | ``` 158 | 159 | The absolute paths to `greet` and `coreutils` are extended: 160 | 161 | ```bash 162 | $ cat result/bin/upper-greet 163 | #!/nix/store/qdp56fi357fgxxnkjrwx1g67hrk775im-bash-4.4-p23/bin/bash 164 | /nix/store/l6xy4qjr8x3ni16skfilw0fvnda13szq-greet/bin/greet "$@" | /nix/store/2shqhfsyzz4rnfyysbzgyp5kbfk29750-coreutils-8.32/bin/tr [a-z] [A-Z] 165 | ``` 166 | 167 | ### Runtime Dependency 168 | 169 | If we query the references of the `upper-greet` output (not the derivation), 170 | we can see that `greet` is still a _runtime_ dependency of `upper-greet`. 171 | 172 | ```bash 173 | $ nix-store --query --references /nix/store/dj2vp64gbja0bp65lngrw9q4lrm1a8r3-upper-greet 174 | /nix/store/2shqhfsyzz4rnfyysbzgyp5kbfk29750-coreutils-8.32 175 | /nix/store/qdp56fi357fgxxnkjrwx1g67hrk775im-bash-4.4-p23 176 | /nix/store/l6xy4qjr8x3ni16skfilw0fvnda13szq-greet 177 | ``` 178 | 179 | We can use `nix why-depends` to find out why Nix thinks `greet` is a runtime 180 | dependency to `upper-greet`: 181 | 182 | ```bash 183 | $ nix why-depends /nix/store/dj2vp64gbja0bp65lngrw9q4lrm1a8r3-upper-greet /nix/store/l6xy4qjr8x3ni16skfilw0fvnda13szq-greet 184 | /nix/store/dj2vp64gbja0bp65lngrw9q4lrm1a8r3-upper-greet 185 | ╚═══bin/upper-greet: …ash-4.4-p23/bin/bash./nix/store/l6xy4qjr8x3ni16skfilw0fvnda13szq-greet/bin/greet "$@" | /nix/sto… 186 | => /nix/store/l6xy4qjr8x3ni16skfilw0fvnda13szq-greet 187 | ``` 188 | -------------------------------------------------------------------------------- /code/04-derivations/02-dependencies/greet.nix: -------------------------------------------------------------------------------- 1 | let 2 | nixpkgs = import ../../nixpkgs.nix; 3 | 4 | greet = nixpkgs.stdenv.mkDerivation { 5 | name = "greet"; 6 | unpackPhase = "true"; 7 | 8 | buildPhase = '' 9 | echo "building greet..." 10 | sleep 3 11 | ''; 12 | 13 | installPhase = '' 14 | mkdir -p $out/bin 15 | 16 | cat <<'EOF' > $out/bin/greet 17 | #!/usr/bin/env bash 18 | echo "Hello, $1!" 19 | EOF 20 | 21 | chmod +x $out/bin/greet 22 | ''; 23 | }; 24 | in 25 | greet -------------------------------------------------------------------------------- /code/04-derivations/02-dependencies/upper-greet.nix: -------------------------------------------------------------------------------- 1 | let 2 | nixpkgs = import ../../nixpkgs.nix; 3 | 4 | inherit (nixpkgs) coreutils; 5 | 6 | greet = import ./greet.nix; 7 | in 8 | nixpkgs.stdenv.mkDerivation { 9 | name = "upper-greet"; 10 | 11 | unpackPhase = "true"; 12 | 13 | buildPhase = '' 14 | echo "building upper-greet..." 15 | sleep 3 16 | ''; 17 | 18 | installPhase = '' 19 | mkdir -p $out/bin 20 | 21 | cat <<'EOF' > $out/bin/upper-greet 22 | #!/usr/bin/env bash 23 | ${greet}/bin/greet "$@" | ${coreutils}/bin/tr [a-z] [A-Z] 24 | EOF 25 | 26 | chmod +x $out/bin/upper-greet 27 | ''; 28 | } 29 | -------------------------------------------------------------------------------- /code/04-derivations/03-fibonacci/fib-serialized.nix: -------------------------------------------------------------------------------- 1 | let 2 | nixpkgs = import ../../nixpkgs.nix; 3 | 4 | inherit (nixpkgs) stdenv; 5 | 6 | prefixed-fib = prefix: 7 | let fib = n: 8 | assert builtins.isInt n; 9 | assert n >= 0; 10 | let 11 | n-str = builtins.toString n; 12 | in 13 | if n == 0 || n == 1 14 | then 15 | stdenv.mkDerivation { 16 | name = "${prefix}-fib-${n-str}"; 17 | unpackPhase = "true"; 18 | 19 | buildPhase = '' 20 | echo "Producing base case fib(${n-str})..." 21 | sleep 3 22 | echo "The answer to fib(${n-str}) is ${n-str}" 23 | ''; 24 | 25 | installPhase = '' 26 | mkdir -p $out 27 | echo "${n-str}" > $out/answer 28 | ''; 29 | } 30 | else 31 | let 32 | fib-1 = fib (n - 1); 33 | fib-2 = fib (n - 2); 34 | 35 | n-1-str = builtins.toString (n - 1); 36 | n-2-str = builtins.toString (n - 2); 37 | 38 | fib-1-answer = nixpkgs.lib.removeSuffix "\n" 39 | (builtins.readFile "${fib-1}/answer"); 40 | fib-2-answer = nixpkgs.lib.removeSuffix "\n" 41 | (builtins.readFile "${fib-2}/answer"); 42 | in 43 | stdenv.mkDerivation { 44 | name = "${prefix}-fib-${n-str}"; 45 | unpackPhase = "true"; 46 | 47 | buildPhase = '' 48 | echo "Calculating the answer of fib(${n-str}).." 49 | echo "Given fib(${n-1-str}) = ${fib-1-answer}," 50 | echo "and given fib(${n-2-str}) = ${fib-2-answer}.." 51 | 52 | sleep 3 53 | 54 | answer=$(( ${fib-1-answer} + ${fib-2-answer} )) 55 | echo "The answer to fib(${n-str}) is $answer" 56 | ''; 57 | 58 | installPhase = '' 59 | mkdir -p $out 60 | echo "$answer" > $out/answer 61 | ''; 62 | } 63 | ; 64 | in fib; 65 | in 66 | prefixed-fib -------------------------------------------------------------------------------- /code/04-derivations/03-fibonacci/fib.nix: -------------------------------------------------------------------------------- 1 | let 2 | nixpkgs = import ../../nixpkgs.nix; 3 | 4 | inherit (nixpkgs) stdenv; 5 | 6 | prefixed-fib = prefix: 7 | let fib = n: 8 | assert builtins.isInt n; 9 | assert n >= 0; 10 | let 11 | n-str = builtins.toString n; 12 | in 13 | if n == 0 || n == 1 14 | then 15 | stdenv.mkDerivation { 16 | name = "${prefix}-fib-${n-str}"; 17 | unpackPhase = "true"; 18 | 19 | buildPhase = '' 20 | echo "Producing base case fib(${n-str})..." 21 | sleep 3 22 | echo "The answer to fib(${n-str}) is ${n-str}" 23 | ''; 24 | 25 | installPhase = '' 26 | mkdir -p $out 27 | echo "${n-str}" > $out/answer 28 | ''; 29 | } 30 | else 31 | let 32 | fib-1 = fib (n - 1); 33 | fib-2 = fib (n - 2); 34 | 35 | n-1-str = builtins.toString (n - 1); 36 | n-2-str = builtins.toString (n - 2); 37 | in 38 | stdenv.mkDerivation { 39 | name = "${prefix}-fib-${n-str}"; 40 | unpackPhase = "true"; 41 | 42 | buildPhase = '' 43 | fib_1=$(cat ${fib-1}/answer) 44 | fib_2=$(cat ${fib-2}/answer) 45 | 46 | echo "Calculating the answer of fib(${n-str}).." 47 | echo "Given fib(${n-1-str}) = $fib_1," 48 | echo "and given fib(${n-2-str}) = $fib_2.." 49 | 50 | sleep 3 51 | 52 | answer=$(( $fib_1 + $fib_2 )) 53 | echo "The answer to fib(${n-str}) is $answer" 54 | ''; 55 | 56 | installPhase = '' 57 | mkdir -p $out 58 | echo "$answer" > $out/answer 59 | ''; 60 | } 61 | ; 62 | in fib; 63 | in 64 | prefixed-fib -------------------------------------------------------------------------------- /code/04-derivations/04-raw-derivation.md: -------------------------------------------------------------------------------- 1 | # Raw Derivation 2 | 3 | We have previously used `stdenv.mkDerivation` to define toy derivations without 4 | looking into how derivations work. Here we will go deeper into Nix derivations, 5 | starting with the most basic derivation construct, `builtins.derivation`. 6 | 7 | From the repl, we can see that `builtins.derivation` is a function: 8 | 9 | ```nix 10 | nix-repl> builtins.derivation 11 | «lambda @ /nix/store/qxayqjmlpqnmwg5yfsjjayw220ls8i2r-nix-2.3.8/share/nix/corepkgs/derivation.nix:4:1» 12 | ``` 13 | 14 | Since `builtins.derviation` is more primitive as compared to `stdenv.mkDerivation`, 15 | the way we can build a derivation is also more involved: 16 | 17 | ```nix 18 | nix-repl> builtins.derivation { 19 | name = "hello"; 20 | system = builtins.currentSystem; 21 | builder = "${nixpkgs.bash}/bin/bash"; 22 | args = [ 23 | "-c" 24 | '' 25 | echo "Hello World!" > $out 26 | '' 27 | ]; 28 | } 29 | «derivation /nix/store/hbsv13kn5imfri16f6g2l5c2jy6dfmxl-hello.drv» 30 | ``` 31 | 32 | ### System 33 | 34 | First we have to supply a `system` attribute, which we set it to 35 | the current OS we are running on. It is most common to have the 36 | system values as `"x86_64-linux"` or `"x86_64-darwin"`. 37 | 38 | ```nix 39 | nix-repl> builtins.currentSystem 40 | "x86_64-linux" 41 | ``` 42 | 43 | The `system` attribute is required because Nix supports cross compilation. 44 | So we can also define derivations that are built on different platforms 45 | than the one we are on. 46 | 47 | ### Builder 48 | 49 | The `builder` attribute expects a file path to an executable script that is 50 | called when the derivation is built. To keep things simple, we use the bash 51 | shell from `nixpkgs.bash` as the builder program. 52 | 53 | The `args` attribute is used to specify the command line line arguments 54 | passed to the builder program. Since bash itself do not know how to 55 | build the program we want, we pass the command string using `-c` 56 | to execute the bash script `echo "Hello World!" > $out` 57 | 58 | Now we can try to build the derivation and see that it works: 59 | 60 | ```bash 61 | $ nix-build /nix/store/hbsv13kn5imfri16f6g2l5c2jy6dfmxl-hello.drv 62 | these derivations will be built: 63 | /nix/store/hbsv13kn5imfri16f6g2l5c2jy6dfmxl-hello.drv 64 | building '/nix/store/hbsv13kn5imfri16f6g2l5c2jy6dfmxl-hello.drv'... 65 | /nix/store/dsgf85gxzw167v320sy08as72c0hk8wd-hello 66 | 67 | $ cat /nix/store/dsgf85gxzw167v320sy08as72c0hk8wd-hello 68 | Hello World! 69 | ``` 70 | 71 | ## Explicit Dependencies 72 | 73 | Inside `builtins.derivation`, almost all dependencies have to be provided 74 | explicitly, even the bash shell that we are running on. Since we specify 75 | `bash` as the builder program, it is also shown in the list of `inputDrvs` 76 | of our derivation. 77 | 78 | ```bash 79 | $ nix show-derivation /nix/store/hbsv13kn5imfri16f6g2l5c2jy6dfmxl-hello.drv 80 | { 81 | "/nix/store/hbsv13kn5imfri16f6g2l5c2jy6dfmxl-hello.drv": { 82 | "outputs": { 83 | "out": { 84 | "path": "/nix/store/dsgf85gxzw167v320sy08as72c0hk8wd-hello" 85 | } 86 | }, 87 | "inputSrcs": [], 88 | "inputDrvs": { 89 | "/nix/store/l54djrh1n7d8zdfn26w7v6zjh5wp7faa-bash-4.4-p23.drv": [ 90 | "out" 91 | ] 92 | }, 93 | "platform": "x86_64-linux", 94 | "builder": "/nix/store/qdp56fi357fgxxnkjrwx1g67hrk775im-bash-4.4-p23/bin/bash", 95 | "args": [ 96 | "-c", 97 | "echo \"Hello World!\" > $out\n" 98 | ], 99 | "env": { 100 | "builder": "/nix/store/qdp56fi357fgxxnkjrwx1g67hrk775im-bash-4.4-p23/bin/bash", 101 | "name": "hello", 102 | "out": "/nix/store/dsgf85gxzw167v320sy08as72c0hk8wd-hello", 103 | "system": "x86_64-linux" 104 | } 105 | } 106 | } 107 | 108 | ``` 109 | 110 | ## Inspecting the Build Environment 111 | 112 | We can use the `env` command to inspect the environment variables inside our build script. 113 | Let's try and build a derivation that prints the environment to the terminal. 114 | 115 | ```nix 116 | nix-repl> builtins.derivation { 117 | name = "env"; 118 | system = builtins.currentSystem; 119 | builder = "${nixpkgs.bash}/bin/bash"; 120 | args = [ 121 | "-c" 122 | '' 123 | set -x 124 | ls -la . 125 | ls -la / 126 | env 127 | touch $out 128 | '' 129 | ]; 130 | } 131 | «derivation /nix/store/4nq2kgcmryhwjh5sg05jgwsd4ixh81ia-env.drv» 132 | ``` 133 | 134 | ```bash 135 | $ nix-build /nix/store/4nq2kgcmryhwjh5sg05jgwsd4ixh81ia-env.drv 136 | + nix-build /nix/store/4nq2kgcmryhwjh5sg05jgwsd4ixh81ia-env.drv 137 | these derivations will be built: 138 | /nix/store/4nq2kgcmryhwjh5sg05jgwsd4ixh81ia-env.drv 139 | building '/nix/store/4nq2kgcmryhwjh5sg05jgwsd4ixh81ia-env.drv'... 140 | + ls -la . 141 | bash: line 1: ls: command not found 142 | + ls -la / 143 | bash: line 2: ls: command not found 144 | + env 145 | bash: line 3: env: command not found 146 | + touch /nix/store/blcl4m2vgga6i86kh13nqlvx1l2ha7v5-env 147 | bash: line 4: touch: command not found 148 | builder for '/nix/store/4nq2kgcmryhwjh5sg05jgwsd4ixh81ia-env.drv' failed with exit code 127 149 | error: build of '/nix/store/4nq2kgcmryhwjh5sg05jgwsd4ixh81ia-env.drv' failed 150 | ``` 151 | 152 | Not good, with `builtins.derivation`, not even basic commands like `ls`, `env`, and `touch` 153 | are provided. (As seen previously, `echo` is provided though) 154 | 155 | Instead, we also have to specify our build dependencies explicitly with `nixpkgs.coreutils` 156 | providing the basic shell commands: 157 | 158 | 159 | ```nix 160 | nix-repl> builtins.derivation { 161 | name = "env"; 162 | system = builtins.currentSystem; 163 | builder = "${nixpkgs.bash}/bin/bash"; 164 | args = [ 165 | "-c" 166 | '' 167 | set -x 168 | ${nixpkgs.coreutils}/bin/ls -la . 169 | ${nixpkgs.coreutils}/bin/ls -la / 170 | ${nixpkgs.coreutils}/bin/env 171 | ${nixpkgs.coreutils}/bin/touch $out 172 | '' 173 | ]; 174 | } 175 | «derivation /nix/store/c4bp5bvx73fz9jf1si64i00as30k9fga-env.drv» 176 | ``` 177 | 178 | ```bash 179 | $ nix-build /nix/store/c4bp5bvx73fz9jf1si64i00as30k9fga-env.drv 180 | these derivations will be built: 181 | /nix/store/c4bp5bvx73fz9jf1si64i00as30k9fga-env.drv 182 | building '/nix/store/c4bp5bvx73fz9jf1si64i00as30k9fga-env.drv'... 183 | + /nix/store/2shqhfsyzz4rnfyysbzgyp5kbfk29750-coreutils-8.32/bin/ls -la . 184 | total 8 185 | drwx------ 2 nixbld nixbld 4096 Nov 30 19:09 . 186 | drwxr-x--- 9 nixbld nixbld 4096 Nov 30 19:09 .. 187 | + /nix/store/2shqhfsyzz4rnfyysbzgyp5kbfk29750-coreutils-8.32/bin/ls -la / 188 | total 32 189 | drwxr-x--- 9 nixbld nixbld 4096 Nov 30 19:09 . 190 | drwxr-x--- 9 nixbld nixbld 4096 Nov 30 19:09 .. 191 | drwxr-xr-x 2 nixbld nixbld 4096 Nov 30 19:09 bin 192 | drwx------ 2 nixbld nixbld 4096 Nov 30 19:09 build 193 | drwxr-xr-x 4 nixbld nixbld 4096 Nov 30 19:09 dev 194 | drwxr-xr-x 2 nixbld nixbld 4096 Nov 30 19:09 etc 195 | drwxr-xr-x 3 nixbld nixbld 4096 Nov 30 19:09 nix 196 | dr-xr-xr-x 410 nobody nogroup 0 Nov 30 19:09 proc 197 | drwxrwxrwt 2 nixbld nixbld 4096 Nov 30 19:09 tmp 198 | + /nix/store/2shqhfsyzz4rnfyysbzgyp5kbfk29750-coreutils-8.32/bin/env 199 | out=/nix/store/3rv14i75j4wyp6n9fila5rll4f99yksi-env 200 | builder=/nix/store/qdp56fi357fgxxnkjrwx1g67hrk775im-bash-4.4-p23/bin/bash 201 | NIX_LOG_FD=2 202 | system=x86_64-linux 203 | PWD=/build 204 | HOME=/homeless-shelter 205 | TMP=/build 206 | NIX_STORE=/nix/store 207 | TMPDIR=/build 208 | name=env 209 | TERM=xterm-256color 210 | TEMPDIR=/build 211 | SHLVL=1 212 | NIX_BUILD_CORES=8 213 | TEMP=/build 214 | PATH=/path-not-set 215 | NIX_BUILD_TOP=/build 216 | _=/nix/store/2shqhfsyzz4rnfyysbzgyp5kbfk29750-coreutils-8.32/bin/env 217 | + /nix/store/2shqhfsyzz4rnfyysbzgyp5kbfk29750-coreutils-8.32/bin/touch /nix/store/3rv14i75j4wyp6n9fila5rll4f99yksi-env 218 | /nix/store/3rv14i75j4wyp6n9fila5rll4f99yksi-env 219 | ``` 220 | 221 | ### Nix Sandbox 222 | 223 | From above we can see that the build environment inside a Nix build script 224 | is _sandboxed_. 225 | 226 | According to 227 | [Nix manual](https://nixos.org/manual/nix/unstable/command-ref/conf-file.html): 228 | 229 | > If set to true, builds will be performed in a sandboxed environment, i.e., they’re isolated from the normal file system hierarchy and will only see their dependencies in the Nix store, the temporary build directory, private versions of /proc, /dev, /dev/shm and /dev/pts (on Linux), and the paths configured with the sandbox-paths option. This is useful to prevent undeclared dependencies on files in directories such as /usr/bin. In addition, on Linux, builds run in private PID, mount, network, IPC and UTS namespaces to isolate them from other processes in the system (except that fixed-output derivations do not run in private network namespace to ensure they can access the network). 230 | 231 | Nix sandbox should be enabled by default. You can check your sandbox configuration with: 232 | 233 | ```bash 234 | $ nix show-config | grep sandbox 235 | extra-sandbox-paths = 236 | sandbox = true 237 | sandbox-build-dir = /build 238 | sandbox-dev-shm-size = 50% 239 | sandbox-fallback = true 240 | sandbox-paths = /bin/sh=/nix/store/w0xp1k96c1dvmx6m4wl1569cdzy47w5r-busybox-1.31.1-x86_64-unknown-linux-musl/bin/busybox 241 | ``` 242 | 243 | ## Capturing Build Environment 244 | 245 | We can capture the build environment as a file by saving the output of `env` to `$out`. 246 | 247 | ```nix 248 | nix-repl> builtins.derivation { 249 | name = "env"; 250 | system = builtins.currentSystem; 251 | builder = "${nixpkgs.bash}/bin/bash"; 252 | args = [ 253 | "-c" 254 | "${nixpkgs.coreutils}/bin/env > $out" 255 | ]; 256 | } 257 | «derivation /nix/store/39ah25v6iwlka3jl2angxrlx00mk2ijd-env.drv» 258 | ``` 259 | 260 | ```bash 261 | $ nix-build /nix/store/39ah25v6iwlka3jl2angxrlx00mk2ijd-env.drv 262 | these derivations will be built: 263 | /nix/store/39ah25v6iwlka3jl2angxrlx00mk2ijd-env.drv 264 | building '/nix/store/39ah25v6iwlka3jl2angxrlx00mk2ijd-env.drv'... 265 | /nix/store/6kjgg8j3y44g1ja95swqdd1v8xp6mwi1-env 266 | 267 | $ cat /nix/store/6kjgg8j3y44g1ja95swqdd1v8xp6mwi1-env 268 | out=/nix/store/6kjgg8j3y44g1ja95swqdd1v8xp6mwi1-env 269 | builder=/nix/store/qdp56fi357fgxxnkjrwx1g67hrk775im-bash-4.4-p23/bin/bash 270 | NIX_LOG_FD=2 271 | system=x86_64-linux 272 | PWD=/build 273 | HOME=/homeless-shelter 274 | TMP=/build 275 | NIX_STORE=/nix/store 276 | TMPDIR=/build 277 | name=env 278 | TERM=xterm-256color 279 | TEMPDIR=/build 280 | SHLVL=1 281 | NIX_BUILD_CORES=8 282 | TEMP=/build 283 | PATH=/path-not-set 284 | NIX_BUILD_TOP=/build 285 | _=/nix/store/2shqhfsyzz4rnfyysbzgyp5kbfk29750-coreutils-8.32/bin/env 286 | ``` 287 | 288 | 289 | ## Nix Shell 290 | 291 | Nix achieves reproducible build by carefully setting/unsetting the appropriate 292 | environment variables, so that our derivations are always built with the same 293 | environment regardless of where it is being built. 294 | 295 | However since the derivation is built in a sandboxed environment, it may be difficult 296 | to debug when there are build errors, or rapid prototyping with the source code 297 | changed frequently. 298 | 299 | We can get almost the same environment as inside nix build by entering a _Nix shell_. 300 | 301 | ``` 302 | $ nix-shell --pure --run env /nix/store/39ah25v6iwlka3jl2angxrlx00mk2ijd-env.drv 303 | __ETC_PROFILE_SOURCED=1 304 | DISPLAY=:1 305 | out=/nix/store/6kjgg8j3y44g1ja95swqdd1v8xp6mwi1-env 306 | builder=/nix/store/qdp56fi357fgxxnkjrwx1g67hrk775im-bash-4.4-p23/bin/bash 307 | USER=user 308 | system=x86_64-linux 309 | PWD=/path/to/nix-workshop 310 | HOME=/home/user 311 | TMP=/run/user/1000 312 | NIX_STORE=/nix/store 313 | TMPDIR=/run/user/1000 314 | name=env 315 | IN_NIX_SHELL=pure 316 | TERM=xterm-256color 317 | TEMPDIR=/run/user/1000 318 | SHLVL=3 319 | NIX_BUILD_CORES=8 320 | TEMP=/run/user/1000 321 | LOGNAME=user 322 | PATH=/nix/store/lf467z8nr5y50q1vqnlbhpv2jachx3cs-bash-interactive-4.4-p23/bin:/home/user/.nix-profile/bin:... 323 | NIX_BUILD_TOP=/run/user/1000 324 | _=/usr/bin/env 325 | ``` 326 | 327 | Our pure Nix environment look pretty similar to the environment we captured in `nix-build`. 328 | There are however a few differences, in particular with `$PATH`. 329 | 330 | [According the manual](https://nixos.org/manual/nix/unstable/command-ref/nix-shell.html) 331 | for the `--pure` option in `nix-shell`: 332 | 333 | > If this flag is specified, the environment is almost entirely cleared before the interactive shell is started, so you get an environment that more closely corresponds to the “real” Nix build. A few variables, in particular `HOME`, `USER` and `DISPLAY`, are retained. Note that (depending on your Bash installation) `/etc/bashrc` is still sourced, so any variables set there will affect the interactive shell. 334 | 335 | We can compare the differences by diffing the output of both environments: 336 | 337 | ```bash 338 | $ drv=/nix/store/39ah25v6iwlka3jl2angxrlx00mk2ijd-env.drv 339 | $ diff --color <(cat $(nix-build $drv)) <(nix-shell $drv --pure --run env) 340 | ``` 341 | 342 | In contrast, the default impure Nix shell keeps all existing environment variables, and only 343 | add or override variables that are introduced by the derivation. 344 | 345 | ```bash 346 | $ nix-shell $drv --run env 347 | ``` 348 | 349 | 350 | ## Environment Variables 351 | 352 | If we observe the captured build environment, almost all attributes we passed to 353 | `builtins.derivation` are converted into environment variables. 354 | 355 | In fact, we can define any number of attributes to be used as environment variables 356 | inside our build script. 357 | 358 | ```nix 359 | nix-repl> builtins.derivation { 360 | name = "foo"; 361 | foo = "foo val"; 362 | system = builtins.currentSystem; 363 | builder = "${nixpkgs.bash}/bin/bash"; 364 | args = [ 365 | "-c" 366 | "echo $foo > $out" 367 | ]; 368 | } 369 | «derivation /nix/store/v1i0khcvxy5bkyv2iq0kqzhcbfcfml8m-foo.drv» 370 | ``` 371 | 372 | We can see from the build output that the value of `$foo` is in fact 373 | captured. 374 | 375 | ```bash 376 | $ nix-build /nix/store/v1i0khcvxy5bkyv2iq0kqzhcbfcfml8m-foo.drv 377 | these derivations will be built: 378 | /nix/store/v1i0khcvxy5bkyv2iq0kqzhcbfcfml8m-foo.drv 379 | building '/nix/store/v1i0khcvxy5bkyv2iq0kqzhcbfcfml8m-foo.drv'... 380 | /nix/store/zmgp33rl2sh3l32syhq4h8gph3f4s1k9-foo 381 | 382 | $ cat /nix/store/zmgp33rl2sh3l32syhq4h8gph3f4s1k9-foo 383 | foo val 384 | ``` 385 | 386 | We can also get the same `$foo` variable set when entering Nix shell: 387 | 388 | ```bash 389 | $ nix-shell --pure --run 'echo $foo' /nix/store/v1i0khcvxy5bkyv2iq0kqzhcbfcfml8m-foo.drv 390 | foo val 391 | ``` 392 | 393 | 394 | ## Setting Dependencies as Variables 395 | 396 | We can set out dependencies as custom attributes in a derivation 397 | and then refer to them as environment variables during the build. 398 | 399 | For example, we can add the `greet` package we defined earlier 400 | and set it as `$greet` in the shell. 401 | 402 | ```nix 403 | nix-repl> greet = import ./04-derivations/02-dependencies/greet.nix 404 | 405 | nix-repl> builtins.derivation { 406 | inherit greet; 407 | name = "greet-alice"; 408 | system = builtins.currentSystem; 409 | builder = "${nixpkgs.bash}/bin/bash"; 410 | args = [ 411 | "-c" 412 | "$greet/bin/greet Alice > $out" 413 | ]; 414 | } 415 | «derivation /nix/store/68gdf6z0rjcyl8xcwix3gfafndsa50jj-greet-alice.drv» 416 | ``` 417 | 418 | ```bash 419 | $ nix-build /nix/store/68gdf6z0rjcyl8xcwix3gfafndsa50jj-greet-alice.drv 420 | these derivations will be built: 421 | /nix/store/68gdf6z0rjcyl8xcwix3gfafndsa50jj-greet-alice.drv 422 | building '/nix/store/68gdf6z0rjcyl8xcwix3gfafndsa50jj-greet-alice.drv'... 423 | /nix/store/dd290zmn983fs1w33nnq9gyh3cnj2jif-greet-alice 424 | 425 | $ cat /nix/store/dd290zmn983fs1w33nnq9gyh3cnj2jif-greet-alice 426 | Hello, Alice! 427 | ``` -------------------------------------------------------------------------------- /code/04-derivations/04-raw-derivation/build-hello.sh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrive/nix-workshop/a713625698b01127709f298a2f79fa86530ecbfc/code/04-derivations/04-raw-derivation/build-hello.sh -------------------------------------------------------------------------------- /code/04-derivations/05-standard-derivation.md: -------------------------------------------------------------------------------- 1 | # Standard Derivation 2 | 3 | `builtins.derivation` provides the minimal functionality to define a Nix 4 | derivation. However all dependencies have to be manually managed, which 5 | can be pretty cumbersome. In practice, most Nix derivations are built 6 | on top of `stdenv.mkDerivation`, which provide many battery-included 7 | functionalities that helps make defining derivations easy. 8 | 9 | The tradeoff is that `stdenv.mkDerivation` is much more complex than 10 | `builtins.derivation`. With the detour to understand 11 | `builtins.derivation` first, we can hopefully have an easier time 12 | understanding `stdenv.mkDerivation` 13 | 14 | 15 | ## Inspecting Build Environment 16 | 17 | We can inspect the standard environment in similar way. 18 | 19 | ```nix 20 | nix-repl> nixpkgs.stdenv.mkDerivation { 21 | name = "inspect"; 22 | unpackPhase = "true"; 23 | 24 | buildPhase = '' 25 | set -x 26 | ls -la . 27 | ls -la / 28 | env 29 | set +x 30 | ''; 31 | 32 | installPhase = "touch $out"; 33 | } 34 | «derivation /nix/store/vdyp9cxs0li87app03vm8zbxmq0lhw5l-inspect.drv» 35 | ``` 36 | 37 | ```bash 38 | $ nix-build /nix/store/vdyp9cxs0li87app03vm8zbxmq0lhw5l-inspect.drv 39 | these derivations will be built: 40 | /nix/store/vdyp9cxs0li87app03vm8zbxmq0lhw5l-inspect.drv 41 | building '/nix/store/vdyp9cxs0li87app03vm8zbxmq0lhw5l-inspect.drv'... 42 | unpacking sources 43 | patching sources 44 | configuring 45 | no configure script, doing nothing 46 | building 47 | ++ ls -la . 48 | total 16 49 | drwx------ 2 nixbld nixbld 4096 Nov 30 19:42 . 50 | drwxr-x--- 9 nixbld nixbld 4096 Nov 30 19:42 .. 51 | -rw-r--r-- 1 nixbld nixbld 5013 Nov 30 19:42 env-vars 52 | ++ ls -la / 53 | total 32 54 | drwxr-x--- 9 nixbld nixbld 4096 Nov 30 19:42 . 55 | drwxr-x--- 9 nixbld nixbld 4096 Nov 30 19:42 .. 56 | drwxr-xr-x 2 nixbld nixbld 4096 Nov 30 19:42 bin 57 | drwx------ 2 nixbld nixbld 4096 Nov 30 19:42 build 58 | drwxr-xr-x 4 nixbld nixbld 4096 Nov 30 19:42 dev 59 | drwxr-xr-x 2 nixbld nixbld 4096 Nov 30 19:42 etc 60 | drwxr-xr-x 3 nixbld nixbld 4096 Nov 30 19:42 nix 61 | dr-xr-xr-x 405 nobody nogroup 0 Nov 30 19:42 proc 62 | drwxrwxrwt 2 nixbld nixbld 4096 Nov 30 19:42 tmp 63 | ++ env 64 | ... 65 | unpackPhase=true 66 | propagatedBuildInputs= 67 | stdenv=/nix/store/ajq5dfwn4hzlx1qf2xxwb6rj8a7s65nm-stdenv-linux 68 | TZ=UTC 69 | OLDPWD=/build 70 | out=/nix/store/a226brzfy71vr6vkfy4m188qs9f7k7g7-inspect 71 | CONFIG_SHELL=/nix/store/qdp56fi357fgxxnkjrwx1g67hrk775im-bash-4.4-p23/bin/bash 72 | buildInputs= 73 | builder=/nix/store/qdp56fi357fgxxnkjrwx1g67hrk775im-bash-4.4-p23/bin/bash 74 | ... 75 | buildPhase=set -x 76 | ls -la . 77 | ls -la / 78 | env 79 | set +x 80 | 81 | PATH=/nix/store/cr86kfhzfwa558mzav4rnfkbz00hw27w-patchelf-0.12/bin:/nix/store/ppfvi0cfcpdr83klw5kx6si2l260n1gh-gcc-wrapper-9.3.0/bin:... 82 | NIX_BUILD_TOP=/build 83 | depsBuildTargetPropagated= 84 | NIX_ENFORCE_PURITY=1 85 | SIZE=size 86 | nativeBuildInputs= 87 | LD=ld 88 | patches= 89 | depsTargetTargetPropagated= 90 | _=/nix/store/2shqhfsyzz4rnfyysbzgyp5kbfk29750-coreutils-8.32/bin/env 91 | ++ set +x 92 | installing 93 | post-installation fixup 94 | shrinking RPATHs of ELF executables and libraries in /nix/store/a226brzfy71vr6vkfy4m188qs9f7k7g7-inspect 95 | strip is /nix/store/bnjps68g8ax6abzvys2xpx12imrx8949-binutils-2.31.1/bin/strip 96 | patching script interpreter paths in /nix/store/a226brzfy71vr6vkfy4m188qs9f7k7g7-inspect 97 | checking for references to /build/ in /nix/store/a226brzfy71vr6vkfy4m188qs9f7k7g7-inspect... 98 | /nix/store/a226brzfy71vr6vkfy4m188qs9f7k7g7-inspect 99 | ``` 100 | 101 | As we can see, our standard environment is quite more complicated than the minimal environment 102 | provided by `builtins.derivation`. We also have a number of executables added to `$PATH`, 103 | which we can use without specifying them as dependencies. 104 | 105 | ## Capturing the Build Environment 106 | 107 | ```nix 108 | nix-repl> nixpkgs.stdenv.mkDerivation { 109 | name = "env"; 110 | unpackPhase = "true"; 111 | installPhase = "env > $out"; 112 | } 113 | «derivation /nix/store/5rgcvwndbc4525ypbb0r1vgqpbxgcy2g-env.drv» 114 | ``` 115 | 116 | ```bash 117 | $ cat $(nix-build /nix/store/5rgcvwndbc4525ypbb0r1vgqpbxgcy2g-env.drv) 118 | ... 119 | unpackPhase=true 120 | propagatedBuildInputs= 121 | stdenv=/nix/store/ajq5dfwn4hzlx1qf2xxwb6rj8a7s65nm-stdenv-linux 122 | TZ=UTC 123 | OLDPWD=/build 124 | out=/nix/store/rkjhcjhdj6ba7r7n7fasq8gmzxi5hk72-env 125 | CONFIG_SHELL=/nix/store/qdp56fi357fgxxnkjrwx1g67hrk775im-bash-4.4-p23/bin/bash 126 | buildInputs= 127 | builder=/nix/store/qdp56fi357fgxxnkjrwx1g67hrk775im-bash-4.4-p23/bin/bash 128 | ... 129 | ``` 130 | 131 | ## Build Inputs 132 | 133 | `stdenv.mkDerivation` also provides a convenient way of adding dependencies 134 | to appropriate environment variables with the `buildInputs` attribute. 135 | 136 | ```nix 137 | nix-repl> nixpkgs.stdenv.mkDerivation { 138 | name = "greet-alice"; 139 | buildInputs = [ greet ]; 140 | 141 | unpackPhase = "true"; 142 | installPhase = "greet Alice > $out"; 143 | } 144 | «derivation /nix/store/in40c5fl13ziqzds3wfg2ag7ax2xmq5l-greet-alice.drv» 145 | ``` 146 | 147 | ```bash 148 | $ nix-build /nix/store/in40c5fl13ziqzds3wfg2ag7ax2xmq5l-greet-alice.drv 149 | these derivations will be built: 150 | /nix/store/in40c5fl13ziqzds3wfg2ag7ax2xmq5l-greet-alice.drv 151 | building '/nix/store/in40c5fl13ziqzds3wfg2ag7ax2xmq5l-greet-alice.drv'... 152 | ... 153 | /nix/store/kp32rzq63barqa55q3mf761gsggi2bq6-greet-alice 154 | 155 | $ cat /nix/store/kp32rzq63barqa55q3mf761gsggi2bq6-greet-alice 156 | Hello, Alice! 157 | ``` 158 | 159 | We can check that `greet` is added to `$PATH` using Nix shell: 160 | 161 | ```bash 162 | $ drv=/nix/store/in40c5fl13ziqzds3wfg2ag7ax2xmq5l-greet-alice.drv 163 | 164 | $ nix-shell $drv --pure --run "command -v greet" 165 | /nix/store/l6xy4qjr8x3ni16skfilw0fvnda13szq-greet/bin/greet 166 | 167 | $ nix-shell $drv --pure --run 'echo $PATH' | tr ':' '\n' 168 | ... 169 | /nix/store/2shqhfsyzz4rnfyysbzgyp5kbfk29750-coreutils-8.32/bin 170 | /nix/store/l6xy4qjr8x3ni16skfilw0fvnda13szq-greet/bin 171 | ... 172 | ``` 173 | 174 | `stdenv` also adds the build inputs to other variables. 175 | 176 | ``` 177 | $ nix-shell $drv --pure --run 'echo $NIX_LDFLAGS' 178 | -rpath /nix/store/kp32rzq63barqa55q3mf761gsggi2bq6-greet-alice/lib64 -rpath /nix/store/kp32rzq63barqa55q3mf761gsggi2bq6-greet-alice/lib 179 | ``` 180 | 181 | Note that the paths /nix/store/kp32rzq63barqa55q3mf761gsggi2bq6-greet-alice/lib 182 | does not exist, but `stdenv` still sets the variables anyway. 183 | 184 | ## Stdenv Script 185 | 186 | How do `stdenv.mkDerivation` do the magic compared to `builtins.derivation`? 187 | We can find out by first inspecting the derivation: 188 | 189 | ```bash 190 | $ nix show-derivation $drv 191 | { 192 | "/nix/store/in40c5fl13ziqzds3wfg2ag7ax2xmq5l-greet-alice.drv": { 193 | ... 194 | "builder": "/nix/store/qdp56fi357fgxxnkjrwx1g67hrk775im-bash-4.4-p23/bin/bash", 195 | "args": [ 196 | "-e", 197 | "/nix/store/9krlzvny65gdc8s7kpb6lkx8cd02c25b-default-builder.sh" 198 | ], 199 | "env": { 200 | "buildInputs": "/nix/store/l6xy4qjr8x3ni16skfilw0fvnda13szq-greet", 201 | "builder": "/nix/store/qdp56fi357fgxxnkjrwx1g67hrk775im-bash-4.4-p23/bin/bash", 202 | ... 203 | "installPhase": "greet Alice > $out", 204 | "name": "greet-alice", 205 | ... 206 | "stdenv": "/nix/store/ajq5dfwn4hzlx1qf2xxwb6rj8a7s65nm-stdenv-linux", 207 | ... 208 | } 209 | 210 | } 211 | } 212 | ``` 213 | 214 | `stdenv` is also using `bash` as the builder, and have it evaluate the 215 | script at `/nix/store/9krlzvny65gdc8s7kpb6lkx8cd02c25b-default-builder.sh`. 216 | Let's see what's inside there: 217 | 218 | ``` 219 | $ cat /nix/store/9krlzvny65gdc8s7kpb6lkx8cd02c25b-default-builder.sh 220 | source $stdenv/setup 221 | genericBuild 222 | ``` 223 | 224 | So the magic is hidden inside `$stdenv/setup`, with the `$stdenv` 225 | variable set to `/nix/store/ajq5dfwn4hzlx1qf2xxwb6rj8a7s65nm-stdenv-linux`. 226 | 227 | We can open it and see what's inside. 228 | 229 | ```bash 230 | nix-shell $drv --run 'cat $stdenv/setup' 231 | ``` 232 | 233 | There are quite a lot of shell scripts happening. If we search through the script, 234 | we can see that environment variables such as `buildInputs`, `buildPhase`, and 235 | `installPhase` are being referred inside `$stdenv/setup`. 236 | 237 | In other words, instead of having to figure how to setup various environment 238 | variables to work with various dependencies, `$stdenv/setup` provides a higher 239 | level abstraction of doing the setup for us. We just have to define 240 | the build inputs and steps that we need, and `$stdenv/setup` will fill 241 | in the missing pieces from us. 242 | 243 | 244 | In fact, `$stdenv/setup` is also being sourced when we enter a Nix shell of a 245 | `stdenv` derivation. 246 | From the [nix-shell manual](https://nixos.org/manual/nix/unstable/command-ref/nix-shell.html): 247 | 248 | > The command nix-shell will build the dependencies of the specified derivation, but not the derivation itself. It will then start an interactive shell in which all environment variables defined by the derivation path have been set to their corresponding values, and the script $stdenv/setup has been sourced. This is useful for reproducing the environment of a derivation for development. 249 | 250 | ## Deriving Environment at Build Time 251 | 252 | One question we might ask is, why is `stdenv` doing the heavy lifting steps only 253 | at build time inside a shell script. We could as well parse the dependencies 254 | inside Nix at evaluation time, and produce a derivation with everything 255 | setup already. 256 | 257 | However recall from the [previous example](./03-fibonacci.md) of 258 | `fib-serialized.nix`. If we try to peek into the content of a dependency derivation, 259 | that would instead become an evaluation time dependency. If `stdenv` is 260 | looking into the content of all dependencies inside Nix, then we can 261 | only know how to build the derivation after all dependencies have been built. 262 | 263 | Instead, `stdenv` avoids this to allow the derivation dependencies to 264 | be built in parallel by Nix. With that, we can only read the content 265 | of our dependencies at build time, which happens inside the build script. -------------------------------------------------------------------------------- /code/04-derivations/06-build-phases.md: -------------------------------------------------------------------------------- 1 | # Build Phases 2 | 3 | `stdenv` provides many different phases, with default behavior of 4 | what to run if no script for that phase is provided. 5 | 6 | Many of the phases follow the build steps introduced by 7 | [Autotools](https://www.gnu.org/software/automake/manual/html_node/Autotools-Introduction.html). 8 | When we are building non-C/C++ projects, only a few phases are essential. 9 | Still, it is useful to take a quick look at what phases are there, 10 | and what they offers. 11 | 12 | The [nixpkgs manual](https://nixos.org/manual/nixpkgs/unstable/#sec-stdenv-phases) 13 | has the full list of phases. 14 | 15 | ## The `phases` Attribute 16 | 17 | We can force `stdenv` to run only specific phases by specifying them 18 | in the `phases` attribute. 19 | 20 | 21 | ```nix 22 | nix-repl> nixpkgs.stdenv.mkDerivation { 23 | name = "hello"; 24 | phases = [ "installPhase" ]; 25 | installPhase = "echo 'Hello World!' > $out"; 26 | } 27 | «derivation /nix/store/m09hj2xs3yc45y3d4rdm8wks7cay00ak-hello.drv» 28 | ``` 29 | 30 | ```bash 31 | $ nix-build /nix/store/m09hj2xs3yc45y3d4rdm8wks7cay00ak-hello.drv 32 | these derivations will be built: 33 | /nix/store/m09hj2xs3yc45y3d4rdm8wks7cay00ak-hello.drv 34 | building '/nix/store/m09hj2xs3yc45y3d4rdm8wks7cay00ak-hello.drv'... 35 | installing 36 | /nix/store/hpj3y6as9s07444qi6nap0f5dp5k84b6-hello 37 | 38 | $ cat /nix/store/hpj3y6as9s07444qi6nap0f5dp5k84b6-hello 39 | Hello World! 40 | ``` 41 | 42 | You may notice that the build log for this version of `hello` is much shorter 43 | than the usual output of standard derivations. Messages such as 44 | `post-installation fixup` are not shown here. 45 | 46 | This is because those are implicit steps performed in phases such as `fixupPhase`. 47 | We will go through later why those phases are there. But as you can see, these 48 | phases can be disabled by explicitly specifying the `phases` to run. 49 | 50 | There is also no need to explicitly skip required phases like `unpackPhase`, 51 | which we previously set to `true`. 52 | 53 | ## Unpack Phase 54 | 55 | The unpack phase is used to unpack source code into temporary directories 56 | to be used for compilation. By default, `unpackPhase` unpacks the source 57 | code specified in `$src`, and if none is provided, it will abort with an error. 58 | 59 | ```nix 60 | nix-repl> nixpkgs.stdenv.mkDerivation { 61 | name = "hello"; 62 | installPhase = "echo 'Hello World!' > $out"; 63 | } 64 | «derivation /nix/store/cx3p30jp0y0l8ixl426drsp81vcqagpr-hello.drv» 65 | ``` 66 | 67 | ```bash 68 | $ nix-build /nix/store/cx3p30jp0y0l8ixl426drsp81vcqagpr-hello.drv 69 | these derivations will be built: 70 | /nix/store/cx3p30jp0y0l8ixl426drsp81vcqagpr-hello.drv 71 | building '/nix/store/cx3p30jp0y0l8ixl426drsp81vcqagpr-hello.drv'... 72 | unpacking sources 73 | variable $src or $srcs should point to the source 74 | builder for '/nix/store/cx3p30jp0y0l8ixl426drsp81vcqagpr-hello.drv' failed with exit code 1 75 | error: build of '/nix/store/cx3p30jp0y0l8ixl426drsp81vcqagpr-hello.drv' failed 76 | ``` 77 | 78 | This is why when we don't have any source code, we have to explicitly skip 79 | the `unpackPhase` by telling it to run `true` instead. 80 | 81 | Let's try to see what is done in `unpackPhase` when we give it some source directories. 82 | 83 | 84 | ```nix 85 | nix-repl> nixpkgs.stdenv.mkDerivation { 86 | name = "fibonacci-src"; 87 | src = ./04-derivations/03-fibonacci; 88 | installPhase = '' 89 | set -x 90 | 91 | pwd 92 | ls -la . 93 | cp -r ./ $out/ 94 | 95 | set +x 96 | ''; 97 | } 98 | «derivation /nix/store/8l0s01nk0fc1zicb9qkdmpwsw01qr5p8-fibonacci.drv» 99 | ``` 100 | 101 | ```bash 102 | $ nix-build /nix/store/8l0s01nk0fc1zicb9qkdmpwsw01qr5p8-fibonacci.drv 103 | these derivations will be built: 104 | /nix/store/8l0s01nk0fc1zicb9qkdmpwsw01qr5p8-fibonacci.drv 105 | building '/nix/store/8l0s01nk0fc1zicb9qkdmpwsw01qr5p8-fibonacci.drv'... 106 | unpacking sources 107 | unpacking source archive /nix/store/a5f73yy0a8dn0p12pfriqbqyag0ksfkq-03-fibonacci 108 | source root is 03-fibonacci 109 | patching sources 110 | configuring 111 | no configure script, doing nothing 112 | building 113 | no Makefile, doing nothing 114 | installing 115 | ++ pwd 116 | /build/03-fibonacci 117 | ++ ls -la . 118 | total 16 119 | drwxr-xr-x 2 nixbld nixbld 4096 Jan 1 1970 . 120 | drwx------ 3 nixbld nixbld 4096 Dec 1 09:19 .. 121 | -rw-r--r-- 1 nixbld nixbld 1772 Jan 1 1970 fib-serialized.nix 122 | -rw-r--r-- 1 nixbld nixbld 1602 Jan 1 1970 fib.nix 123 | ++ cp -r ./ /nix/store/qqd4msyqya0xhqxcyra0lf7v09z2q522-fibonacci/ 124 | ++ set +x 125 | post-installation fixup 126 | shrinking RPATHs of ELF executables and libraries in /nix/store/qqd4msyqya0xhqxcyra0lf7v09z2q522-fibonacci 127 | strip is /nix/store/bnjps68g8ax6abzvys2xpx12imrx8949-binutils-2.31.1/bin/strip 128 | patching script interpreter paths in /nix/store/qqd4msyqya0xhqxcyra0lf7v09z2q522-fibonacci 129 | checking for references to /build/ in /nix/store/qqd4msyqya0xhqxcyra0lf7v09z2q522-fibonacci... 130 | /nix/store/qqd4msyqya0xhqxcyra0lf7v09z2q522-fibonacci 131 | 132 | $ ls -la /nix/store/qqd4msyqya0xhqxcyra0lf7v09z2q522-fibonacci 133 | total 5728 134 | dr-xr-xr-x 2 user user 4096 Jan 1 1970 . 135 | drwxr-xr-x 5403 user user 5849088 Dez 1 10:19 .. 136 | -r--r--r-- 1 user user 1602 Jan 1 1970 fib.nix 137 | -r--r--r-- 1 user user 1772 Jan 1 1970 fib-serialized.nix 138 | ``` 139 | 140 | As we can see, `unpackPhase` copies the content of the source code specified in `$src` 141 | into the temporary build directory. It also modifies the chmod permissions to 142 | allow write permission to the files and directories. 143 | 144 | ## Patch Phase 145 | 146 | ## Configure Phase 147 | 148 | ## Build Phase 149 | 150 | ## Check Phase 151 | 152 | ## Install Phase 153 | 154 | ## Fixup Phase -------------------------------------------------------------------------------- /code/05-package-management/01-dependency-management.md: -------------------------------------------------------------------------------- 1 | # Dependency Management 2 | 3 | We have seen in previous chapters that Nix makes it easy to construct 4 | complex build dependencies with several benefits: 5 | 6 | - Non-related dependencies can be built in parallel. 7 | - Reproducible builds make it easy to cache and reuse dependencies. 8 | 9 | However Nix does not provide any mechanism for dependency _resolution_, 10 | e.g. choose from multiple _versions_ of dependencies and 11 | determining the most suitable versions. 12 | 13 | As an example, we will build hypothetical Nix packages resembling 14 | [Minecraft crafting recipes](https://minecraft.gamepedia.com/Crafting) 15 | with versioning schemes following [semver](https://semver.org/). 16 | Let's first try to build our first version of `pickaxe`, which is 17 | made of wood: 18 | 19 | ``` 20 | pickaxe 21 | - 1.0.0 22 | - stick ^1.0.1 23 | - planks ~2.1.0 24 | stick 25 | - 1.0.3 26 | - 1.1.2 27 | - 2.0.0 28 | planks 29 | - 2.1.0 30 | - 2.1.1 31 | - 2.2.1 32 | ``` 33 | 34 | The first step in deciding the appropriate versions to be used to build 35 | `pickaxe-1.0.0` is to rule out invalid versions. With that, `stick-2.0.0` 36 | is ruled out because it is outside of the `^1.0.1` range. Similarly 37 | `planks-2.2.1` is outside the bound for `~2.1.0`. 38 | 39 | 40 | After filtering out the invalid versions, there are still multiple versions 41 | of `stick` and `planks` available. As a result there can be multiple 42 | version candidates for building `pickaxe-1.0.0`. For example, we can use 43 | `stick-1.0.3` and `planks-2.1.1`. But are those the best versions to be used? 44 | 45 | Depending on the dependency resolution algorithm used, we may get different 46 | answers. Though in general, we can usually expect the algorithm to choose 47 | the latest versions that are compatible with the required range. So we should 48 | expect to get `stick-1.1.2` and `planks-2.1.1` as the answers. 49 | 50 | ## Nested Dependencies 51 | 52 | In reality, dependency resolution can be more complicated because of 53 | _nested_ dependencies. Let's say both `stick` and `planks` both depend 54 | on `wood`: 55 | 56 | ``` 57 | stick 58 | - 1.0.3 59 | - wood ^1.5.0 60 | - 1.1.2 61 | - wood ~2.0.0 62 | 63 | planks 64 | - 2.1.0 65 | - wood ^2.0.0 66 | - 2.1.1 67 | - wood ~2.3.0 68 | 69 | wood 70 | - 1.5.0 71 | - 2.0.1 72 | - 2.3.2 73 | ``` 74 | 75 | In such case, the only solution is to use `stick-1.1.2` and `planks-2.1.0`, 76 | because the other version combinations do not have a common `wood` version 77 | usable by both `stick` and `planks`. 78 | 79 | ## Package Managers 80 | 81 | Dependency resolution is a complex topic on its own. Different languages 82 | have their own package managers that deal with dependency resolution 83 | differently. e.g. cabal-install, npm, mvn, etc. There are also OS-level 84 | package managers that have to deal with dependencies resolution. 85 | e.g. apt (for Debian and Ubuntu), rpm (Fedora), pacman (Arch Linux, Manjaro), etc. 86 | 87 | To support package management across multiple languages and multiple 88 | platforms, Nix has its own unique challenge of managing dependencies. 89 | At this point, Nix itself do not provide any mechanism for resolving 90 | dependencies. Instead Nix users have to come out with their own 91 | higher level design patterns to resolve dependencies, such as in 92 | nixpkgs. 93 | 94 | ## Package Registry 95 | 96 | For a dependency resolution algorithm to determine what versions of 97 | dependency to use, it must first refer to a _registry_ that contains 98 | all versions available to all packages. Each package manager have 99 | their own registry, e.g. Hackage, npm registry, Debian registry, etc. 100 | 101 | Package registries are usually mutable databases that are constantly 102 | updated. This creates an issue with _reproducibility_: the result 103 | given back from a dependency resolution algorithm depends on 104 | the mutable state of the registry at the time the algorithm is 105 | executed. 106 | 107 | In other words, say if we try to resolve the dependencies of 108 | `pickaxe-1.0.0` today, we may get `stick-1.1.2` and `planks-2.1.0`. 109 | But if we resolve the same dependencies tomorrow, we might get 110 | `stick-1.1.3` because new version of `stick` is published. 111 | To make it worse, `stick-1.1.3` may contain unexpected changes 112 | that causes `pickaxe-1.0.0` to break. 113 | 114 | ## Version Pinning 115 | 116 | Even without Nix, there is a strong use case to pin the versions 117 | to a particular snapshot of the registry. This is to make sure that, no matter when 118 | we try to resolve the dependencies, we will always get 119 | back the same dependencies. 120 | 121 | ### Package Lock 122 | 123 | One common approach is to create a lock file containing the result of 124 | running the dependency resolution algorithm, and include the lock file into the version control system (e.g. GIT). For instance the lock file could be `package-lock.json` (for npm) or 125 | `cabal.project.freeze` (for haskell projects). With the lock files available, we can even 126 | skip dependency resolution in the future, and just use the result in the 127 | lock file. 128 | 129 | ### Registry Snapshot 130 | 131 | An alternative approach would be to specify the snapshot of the 132 | package registry itself. For example, cabal accepts an 133 | `index-state` option for us to specify a timestamp of the 134 | Hackage snapshot that it should resolve the dependencies from. 135 | With that we can specify the timestamp of the time we first 136 | build our dependencies, and not worry about new versions of 137 | packages being added in the future. 138 | 139 | However there can still be other variables 140 | that can affect the outcome. For example, the package manager 141 | itself may update the dependency resolution algorithm, so we 142 | may still get different results depending on the version of 143 | package manager used. 144 | 145 | ## Upgrading Dependencies 146 | 147 | The strategies for pinning dependencies does not eliminate the 148 | need to resolving the plans in the first place, or the need 149 | to upgrade or install new dependencies. 150 | 151 | In the ideal world, we would like to be able to just specify 152 | the dependencies we need, and have the package manager give 153 | us the best versions that just work. But reality is messy, 154 | and dependencies can have breaking changes all the time. 155 | 156 | ### Versioning Schemes 157 | 158 | There are many attempts at coming up with versioning schemes 159 | that carry breakage information with them, such as 160 | [semver](https://semver.org/) and 161 | [PVP](https://pvp.haskell.org/). 162 | However they require package authors to manually follow 163 | the rules, and rules can be broken, intentionally or not. 164 | 165 | ## Exponential Versions 166 | 167 | In reality, each combination of dependency versions produce 168 | a unique version of the full package that needs to be tested. 169 | There is never just one version of `pickaxe-1.0.0`, but 170 | exponential number of versions of `pickaxe-1.0.0` depending 171 | on the versions of `stick`, `planks`, and their transitive 172 | dependencies. 173 | 174 | To make matters worse, real world software also tend to have 175 | implicit dependencies on the runtime environment, such as 176 | executables, shared libraries, and operating system APIs. 177 | 178 | So for each of the versions of `pickaxe-1.0.0` with pinned 179 | dependencies, we would also have multiple versions of that 180 | software for different platforms, e.g. Linux, MacOS, Windows, 181 | Android, iOS, etc. Even among these platforms, there are 182 | also multiple releases of the platform, e.g. Debian 10, 183 | Ubuntu 20.04, MacOS Big Sur, etc. 184 | 185 | ## Mono Versioning 186 | 187 | Despite all these complexities, we still like to pretend 188 | that there is only one or few versions of `pickaxe-1.0.0` 189 | ever existed. One way to tame down this complexity is 190 | through _mono versioning_. 191 | 192 | ### Monorepo 193 | 194 | The simplest kind of mono versioning is by having a single repository 195 | that contains all its components and dependencies. For each commit 196 | in this repository, there is exactly one version each component 197 | and dependecy. We simply ignore the possibility of other 198 | valid combinations of component versions, and not support them. 199 | 200 | ### Lockfiles in Monorepo 201 | 202 | Package managers such as `godep` check the source code of dependencies 203 | into a monorepo. 204 | As an alternative, we can check just the lockfiles into the repository, 205 | and have the package managers fetch them separately. 206 | 207 | Checking the lock file is still effectively mono-versioning the 208 | dependencies. For each commit in the repository, we support only the 209 | exact dependencies specified in the lockfile of that commit. We simply 210 | pretend that no other versions of the dependencies are available. 211 | 212 | ### Mono Registry 213 | 214 | Taking the idea to extreme, we can also freeze all dependencies in a package 215 | registry and provide only one version of each dependency at any point in time. 216 | 217 | This is the approach for registries such as Stackage, which guarantees that 218 | all Haskell dependencies only have one version that always work. 219 | 220 | Mono registry tend to work more cleanly together with monorepo. In a project, 221 | we can specify just the snapshot version of the mono registry that we are 222 | using, and there is no need for messy details such as generating the lockfiles 223 | in the first place. 224 | 225 | ### Mono Environment 226 | 227 | Nixpkgs is also a mono registry for the standard packages in Nix. For each 228 | version of nixpkgs, there is exactly one version of packages such as `bash`, 229 | `gcc`, etc. But since these packages used to be provided by operating 230 | systems, we can say that nixpkgs is also providing a _mono environment_ 231 | to our software. 232 | 233 | When we create a monorepo with pinned nixpkgs, we are not only providing 234 | exactly one version of each dependencies, but also exactly one version 235 | of the environment to run on. 236 | 237 | Mono environment restricts the specification of our software so that it 238 | does not just run on platforms such as any version of Linux or any 239 | version of Debian 10. We just pretend that there is exact one version 240 | of OS as specified in nixpkgs. 241 | 242 | 243 | ## Pros and Cons of Mono Versioning 244 | 245 | There is a fundamental difference in philosophy between multi-versioning 246 | and mono-versioning that makes it difficult for the two camps to reconcile. 247 | At its core, there are a few factors involved. 248 | 249 | ### Stability 250 | 251 | Mono-versioning places much higher value in stability. People in this camp 252 | want to make sure each version of the software always work. They achieve 253 | that by significantly limiting the number of versions of the software, 254 | and thoroughly testing softwares before upgrading any version. 255 | 256 | Mono-versioning tend to put emphasis in LTS (long term support) releases, 257 | where its components are guaranteed to not have any breaking changes 258 | and be given years of support. 259 | 260 | ### Rapid Releases 261 | 262 | Mono versioning tend to suffer in providing slower releases. When a 263 | new version of component is available, it has to be tested to not 264 | break any other component before the new version can be released. 265 | 266 | In contrast, multi-versioning allows a new component to be released 267 | immediately. This allows software to independently upgrade the 268 | dependencies, at the risk of it may break on some of the software. 269 | 270 | ## Blurring the Line 271 | 272 | There is no clear cut off whether the mono-versioning or multi-versioning 273 | approaches are better. In practice, we tend to take a hybrid approach 274 | in large software projects. 275 | 276 | For example, in a company each team may have different monorepos for their 277 | projects to manage their own dependencies. The full release of the software 278 | suite is then a multi-versioning combination of each team's projects, 279 | which can break during development. Finally in production, the exact 280 | versions of each subprojects are pinned to specific versions before 281 | deployment. 282 | 283 | Although Nix is more suitable for mono-versioning development, some of 284 | its features also make it easier to manage multi-versioned projects, 285 | by building a mono "Nixified" version of the projects. 286 | -------------------------------------------------------------------------------- /code/05-package-management/03-version-conflicts.md: -------------------------------------------------------------------------------- 1 | # Version Conflicts in Haskell Dependencies 2 | 3 | In the previous chapter we have created a trivial Haskell project with no 4 | dependency other than `base`. Let's look at what happen when our Haskell 5 | project have some dependencies, which happen to conflict with the 6 | dependencies available in `nixpkgs`. 7 | 8 | We first define a new Haskell project, 9 | [haskell-project-v2](./haskell-project-v2/haskell/haskell-project.cabal): 10 | 11 | ``` 12 | {{#include ./haskell-project-v2/haskell/haskell-project.cabal}} 13 | ``` 14 | 15 | We add a new dependency `yaml` and explicitly requiring version `0.11.3.0`. 16 | At the time of writing, the latest version of `yaml` available on 17 | [Hackage](https://hackage.haskell.org/package/yaml) is `0.11.5.0`. 18 | However let's pretend there are 19 | [breaking changes](https://github.com/snoyberg/yaml/issues/173) 20 | in `0.11.5.0`, and we only support `0.11.3.0`. 21 | 22 | ## Nixpkgs 23 | 24 | Let's try to follow our previous approach to define our Haskell derivation 25 | using `callCabal2nix`. If we do that and try to build it, we would 26 | run into an error: 27 | 28 | ```bash 29 | $ nix-build 05-package-management/haskell-project-v2/nix/01-nixpkgs-conflict/ 30 | building '/nix/store/3gab06pwqjc16wdqhj5akxk21g1z0qnx-cabal2nix-haskell-project.drv'... 31 | these derivations will be built: 32 | /nix/store/242x69pl2la3lb201qd57rghisrwclpy-haskell-project-0.1.0.0.drv 33 | building '/nix/store/242x69pl2la3lb201qd57rghisrwclpy-haskell-project-0.1.0.0.drv'... 34 | ... 35 | Setup: Encountered missing or private dependencies: 36 | yaml ==0.11.3.0 37 | 38 | builder for '/nix/store/242x69pl2la3lb201qd57rghisrwclpy-haskell-project-0.1.0.0.drv' failed with exit code 1 39 | error: build of '/nix/store/242x69pl2la3lb201qd57rghisrwclpy-haskell-project-0.1.0.0.drv' failed 40 | ``` 41 | Why is that so? 42 | 43 | If we try to enter Nix shell, it will still succeed. But if we try to build 44 | our Haskell project in Nix shell, we will find out that the package `yaml` 45 | has to be explicitly built by cabal: 46 | 47 | ```bash 48 | $ nix-shell 05-package-management/haskell-project-v2/nix/01-nixpkgs-conflict/shell.nix 49 | 50 | [nix-shell]$ cd 05-package-management/haskell-project-v2/haskell/ 51 | 52 | [nix-shell]$ cabal --dry-run build all 53 | Resolving dependencies... 54 | Build profile: -w ghc-8.10.2 -O1 55 | In order, the following would be built (use -v for more details): 56 | - yaml-0.11.3.0 (lib) (requires build) 57 | - haskell-project-0.1.0.0 (exe:hello) (first run) 58 | ``` 59 | 60 | ## Problem with Mono-versioning 61 | 62 | If we look into 63 | [nixpkgs source code](https://raw.githubusercontent.com/NixOS/nixpkgs/c1e5f8723ceb684c8d501d4d4ae738fef704747e/pkgs/development/haskell-modules/hackage-packages.nix), 64 | we can in fact see that the version of `yaml` available in nixpkgs is the latest, 65 | `0.11.5.0`. 66 | 67 | As discussed earlier, with the mono-versioning approach by nixpkgs, there is 68 | exactly one version of each package available. Mono-versioning conflicts 69 | can happen when we need packages that are either older or newer than the version 70 | provided by nixpkgs. 71 | 72 | In theory we could switch to a version of nixpkgs that has `yaml-0.11.3.0`, 73 | however we would then have to buy into the versions of other Haskell packages 74 | available at that time. 75 | 76 | ## Overriding Versions 77 | 78 | Nixpkgs provides a workaround for mono-versioning conflicts, by using the 79 | [override pattern](https://nixos.org/guides/nix-pills/override-design-pattern.html). 80 | We can override the version of `yaml` to the one we want as follows: 81 | 82 | ```nix 83 | {{#include ./haskell-project-v2/nix/02-nixpkgs-override/default.nix}} 84 | ``` 85 | 86 | Essentially, we refer to the original haskell package set provided as 87 | `hsPkgs-original`, and we call `hsPkgs-original.override` to produce 88 | a new package set `hsPkgs` with `yaml` overridden to `0.11.3.0`. 89 | 90 | Using `callHackage`, we can fetch the version of `yaml` from the 91 | Hackage snapshot in nixpkgs. With that we can just provide the 92 | string `"0.11.3.0"` to specify the version that we want. Note 93 | however that this only works if the version can be found in the 94 | given Hackage snapshot, which may be outdated over time. 95 | 96 | An issue with overriding dependencies this way is that the override 97 | affects the entire Haskell package set. This means that all other 98 | Haskell packages that depend on `yaml` will also get `0.11.3.0` 99 | instead of `0.11.5.0`. As a result, this may have the unintended 100 | ripple effect of breaking other Haskell packages that we depends 101 | on. 102 | 103 | ## Building Overridden Package 104 | 105 | If we try to build our Haskell derivation with overridden `yaml`, 106 | it would work this time: 107 | 108 | ```bash 109 | $ nix-build 05-package-management/haskell-project-v2/nix/02-nixpkgs-override/ 110 | these derivations will be built: 111 | /nix/store/l0v04zz36b5s5r3qc2jisvggyc0gkj5w-remove-references-to.drv 112 | /nix/store/bg5z6b7m24fxqn8qq2l2w8c0w30wkbp3-yaml-0.11.3.0.drv 113 | /nix/store/p5sr404mzr8bnqqprv72lxczdr9cnnim-haskell-project-0.1.0.0.drv 114 | ... 115 | /nix/store/0m0mr11ncii3z4zkn9z0xkwk4nswprqm-haskell-project-0.1.0.0 116 | ``` 117 | 118 | We can also enter the Nix shell to verify that this time, cabal will 119 | not try to build `yaml` for us: 120 | 121 | ```bash 122 | $ nix-shell 05-package-management/haskell-project-v2/nix/02-nixpkgs-override/shell.nix 123 | 124 | [nix-shell]$ cd 05-package-management/haskell-project-v2/haskell/ 125 | 126 | [nix-shell]$ cabal --dry-run build all 127 | Resolving dependencies... 128 | Build profile: -w ghc-8.10.2 -O1 129 | In order, the following would be built (use -v for more details): 130 | - haskell-project-0.1.0.0 (exe:hello) (first run) 131 | ``` 132 | 133 | ## Haskell.nix 134 | 135 | In comparison with the mono-versioned nixpkgs, Haskell.nix is much more flexible 136 | in allowing any version of Haskell packages that are supported by cabal. 137 | So we can leave the Nix project unchanged and still build it successfully: 138 | 139 | ```bash 140 | $ nix-build 05-package-management/haskell-project-v2/nix/03-haskell.nix/ 141 | trace: No index state specified, using the latest index state that we know about (2020-12-04T00:00:00Z)! 142 | building '/nix/store/v4pf9jffq0dh6xang25qviwb77947s7s-plan-to-nix-pkgs.drv'... 143 | Using index-state 2020-12-04T00:00:00Z 144 | Warning: The package list for 'hackage.haskell.org-at-2020-12-04T000000Z' is 145 | 18603 days old. 146 | Run 'cabal update' to get the latest list of available packages. 147 | Warning: Requested index-state2020-12-04T00:00:00Z is newer than 148 | 'hackage.haskell.org-at-2020-12-04T000000Z'! Falling back to older state 149 | (2020-12-03T20:14:57Z). 150 | Resolving dependencies... 151 | Build profile: -w ghc-8.10.2 -O1 152 | In order, the following would be built (use -v for more details): 153 | - base-compat-0.11.2 (lib) (requires download & build) 154 | - base-orphans-0.8.3 (lib) (requires download & build) 155 | ... 156 | - aeson-1.5.4.1 (lib) (requires download & build) 157 | - yaml-0.11.3.0 (lib) (requires download & build) 158 | - haskell-project-0.1.0.0 (exe:hello) (first run) 159 | these derivations will be built: 160 | these derivations will be built: 161 | ... 162 | /nix/store/y8vbr0b6y8bzgmadj0rfjp3d2rzx5wgs-yaml-lib-yaml-0.11.3.0-config.drv 163 | /nix/store/fhmib5kqsxl82r1z23mm59njw2dn0c8v-yaml-lib-yaml-0.11.3.0-ghc-8.10.2-env.drv 164 | /nix/store/j837v0cxk9dxqpxfjfngii007hq8wn3w-yaml-lib-yaml-0.11.3.0.drv 165 | /nix/store/nxwvfjaj40adyq002khld7ngnq3wggn7-haskell-project-exe-hello-0.1.0.0-config.drv 166 | /nix/store/y27wbd58f5d1k3lzbzpr5qcc4pgqrxg2-haskell-project-exe-hello-0.1.0.0-ghc-8.10.2-env.drv 167 | /nix/store/b4i7xhnha8007zqxd4gidsf7xyy338an-haskell-project-exe-hello-0.1.0.0.drv 168 | ... 169 | /nix/store/phm2jk6xnvxsgp640r66cwgipc62kbc5-haskell-project-exe-hello-0.1.0.0 170 | 171 | $ /nix/store/phm2jk6xnvxsgp640r66cwgipc62kbc5-haskell-project-exe-hello-0.1.0.0/bin/hello 172 | Hello, Haskell! 173 | ``` 174 | -------------------------------------------------------------------------------- /code/05-package-management/04-transitive-version-conflicts.md: -------------------------------------------------------------------------------- 1 | # Transitive Haskell Version Conflicts 2 | 3 | Let's look at another example of nixpkgs-based Haskell project, where version 4 | conflicts are propagated to transitive dependencies. 5 | 6 | We have another Haskell project, 7 | [haskell-project-v3](./haskell-project-v3/haskell/haskell-project.cabal) 8 | with a dependency on the latest `QuickCheck-2.14.2`: 9 | 10 | ``` 11 | {{#include ./haskell-project-v3/haskell/haskell-project.cabal}} 12 | ``` 13 | 14 | Now try building the project with the default `callCabal2nix`: 15 | 16 | ```nix 17 | {{#include ./haskell-project-v3/nix/01-nixpkgs-conflict/default.nix}} 18 | ``` 19 | 20 | We should see that the building failed, because the 21 | [version of nixpkgs](https://raw.githubusercontent.com/NixOS/nixpkgs/c1e5f8723ceb684c8d501d4d4ae738fef704747e/pkgs/development/haskell-modules/hackage-packages.nix) 22 | we are using only has `QuickCheck-2.13.2` in it. 23 | 24 | ```bash 25 | $ nix-build 05-package-management/haskell-project-v3/nix/01-nixpkgs-conflict 26 | building '/nix/store/12qzjbk3514sj9v4j99brbxr4b83bzy5-cabal2nix-haskell-project.drv'... 27 | installing 28 | these derivations will be built: 29 | /nix/store/d5jyli1v9y12il9wyzs5x6gyx6q68gig-haskell-project-0.1.0.0.drv 30 | ... 31 | Setup: Encountered missing or private dependencies: 32 | QuickCheck ==2.14.2 33 | 34 | builder for '/nix/store/d5jyli1v9y12il9wyzs5x6gyx6q68gig-haskell-project-0.1.0.0.drv' failed with exit code 1 35 | error: build of '/nix/store/d5jyli1v9y12il9wyzs5x6gyx6q68gig-haskell-project-0.1.0.0.drv' failed 36 | ``` 37 | 38 | ## Using Latest Hackage Package 39 | 40 | Now we try using the override pattern in the previous chapter to override the 41 | version of `QuickCheck`: 42 | 43 | ```nix 44 | {{#include ./haskell-project-v3/nix/02-nixpkgs-not-found/default.nix}} 45 | ``` 46 | 47 | This time the build actually failed with another error: 48 | 49 | ```bash 50 | $ nix-build 05-package-management/haskell-project-v3/nix/01-nixpkgs-conflict 51 | building '/nix/store/12qzjbk3514sj9v4j99brbxr4b83bzy5-cabal2nix-haskell-project.drv'... 52 | installing 53 | these derivations will be built: 54 | /nix/store/d5jyli1v9y12il9wyzs5x6gyx6q68gig-haskell-project-0.1.0.0.drv 55 | ... 56 | Configuring haskell-project-0.1.0.0... 57 | CallStack (from HasCallStack): 58 | $, called at libraries/Cabal/Cabal/Distribution/Simple/Configure.hs:1024:20 in Cabal-3.2.0.0:Distribution.Simple.Configure 59 | configureFinalizedPackage, called at libraries/Cabal/Cabal/Distribution/Simple/Configure.hs:477:12 in Cabal-3.2.0.0:Distribution.Simple.Configure 60 | configure, called at libraries/Cabal/Cabal/Distribution/Simple.hs:625:20 in Cabal-3.2.0.0:Distribution.Simple 61 | confHook, called at libraries/Cabal/Cabal/Distribution/Simple/UserHooks.hs:65:5 in Cabal-3.2.0.0:Distribution.Simple.UserHooks 62 | configureAction, called at libraries/Cabal/Cabal/Distribution/Simple.hs:180:19 in Cabal-3.2.0.0:Distribution.Simple 63 | defaultMainHelper, called at libraries/Cabal/Cabal/Distribution/Simple.hs:116:27 in Cabal-3.2.0.0:Distribution.Simple 64 | defaultMain, called at Setup.hs:2:8 in main:Main 65 | Setup: Encountered missing or private dependencies: 66 | QuickCheck ==2.14.2 67 | 68 | builder for '/nix/store/d5jyli1v9y12il9wyzs5x6gyx6q68gig-haskell-project-0.1.0.0.drv' failed with exit code 1 69 | error: build of '/nix/store/d5jyli1v9y12il9wyzs5x6gyx6q68gig-haskell-project-0.1.0.0.drv' failed 70 | soares@soares-workstation:~/scrive/nix-workshop/code$ nix-build 05-package-management/haskell-project-v3/nix/02-nixpkgs-not-found 71 | building '/nix/store/ybvimf0jbsbx997588kbipkpbq97iv3d-all-cabal-hashes-component-QuickCheck-2.14.2.drv'... 72 | tar: */QuickCheck/2.14.2/QuickCheck.json: Not found in archive 73 | tar: */QuickCheck/2.14.2/QuickCheck.cabal: Not found in archive 74 | tar: Exiting with failure status due to previous errors 75 | builder for '/nix/store/ybvimf0jbsbx997588kbipkpbq97iv3d-all-cabal-hashes-component-QuickCheck-2.14.2.drv' failed with exit code 2 76 | cannot build derivation '/nix/store/73pfc98xfaj7l818nznr8r4gbls5xmls-cabal2nix-QuickCheck-2.14.2.drv': 1 dependencies couldn't be built 77 | error: build of '/nix/store/73pfc98xfaj7l818nznr8r4gbls5xmls-cabal2nix-QuickCheck-2.14.2.drv' failed 78 | (use '--show-trace' to show detailed location information) 79 | ``` 80 | 81 | What happened this time? If we check the 82 | [commit log](https://github.com/NixOS/nixpkgs/commit/c1e5f8723ceb684c8d501d4d4ae738fef704747e) 83 | of our nixpkgs version, we will find out that the nixpkgs we have is commited 84 | on 9 November 2020, but on 85 | [Hackage](https://hackage.haskell.org/package/QuickCheck) 86 | `QuickCheck-2.14.2` is only released on 14 November 2020. 87 | 88 | ## Hackage Index in Nixpkgs 89 | 90 | Inside the override call, when we call `callHackage` to get a Haskell package 91 | from Hackage, we are really just downloading the Haskell source from Hackage 92 | based on the snapshot cached in nixpkgs. 93 | 94 | Recall from the principal of reproducibility, with just a version number, there 95 | is no way Nix can tell if we will always get the exact same source code from 96 | Hackage every time a source code is requested. In theory the 97 | `QuickCheck-2.14.2` we fetched today may be totally different from the 98 | `QuickCheck-2.14.2` we fetch tomorrow, or when it is fetched by someone else. 99 | 100 | Nixpkgs solves this by computing the content hash of every Hackage package 101 | at the time of snapshot. So we can know for sure that the same Hackage pacakge 102 | we fetch with the same nixpkgs will always give back the same result. 103 | Nixpkgs also does implicit patching on some Hackage package, if their 104 | default configuration breaks. 105 | 106 | One option for us may be to simply update to the latest version of nixpkgs 107 | so that it contains `QuickCheck-2.14.2`. However nixpkgs do not immediately 108 | update the Hackage snapshot every time a new Haskell pacakge is published. 109 | Rather there is usually 1~2 weeks lag as the Haskell packages are updated 110 | in bulk. So we can't rely on that if we want to use a Haskell package 111 | just published an hour ago. 112 | 113 | Furthermore, updating nixpkgs also means all other packages in nixpkgs 114 | also being updated. That may result in breaking some of our own Nix 115 | packages. 116 | 117 | In theory we could use a stable Nix channel like `nixos-20.09` 118 | instead of `nixpkgs-unstable` so that it is safer to update nixpkgs. 119 | But stability always needs tradeoff with rapid releases, 120 | so it will take even longer before the Hackage snapshot update 121 | is propagated there. 122 | 123 | ## CallHackageDirect 124 | 125 | If we want to get the latest Hackage package beyond what is available 126 | in nixpkgs, we can instead use `callHackageDirect` to directly 127 | download the package from Hackage, skipping nixpkgs entirely: 128 | 129 | ```nix 130 | {{#include ./haskell-project-v3/nix/03-nixpkgs-transitive-deps/default.nix}} 131 | ``` 132 | 133 | `callHackageDirect` works similarly to other ways of fetching source code, 134 | such as `builtins.fetchTarball` and `builtins.fetchgit`. In fact, 135 | we can also override a Haskell dependency with a GitHub commit or 136 | source tarball. Here we just need to provide an additional information, 137 | which is the SHA256 checksum of the package. 138 | 139 | There is currently straightforward way to compute the hash, but we can 140 | first supply a dummy hash such as 141 | `0000000000000000000000000000000000000000000000000000`, 142 | then copy the correct hash from the hash mismatch error when 143 | the derivation is built. 144 | 145 | ## Transitive Conflicts 146 | 147 | If we try to build it this time however, we are greeted with another error: 148 | 149 | ```bash 150 | $ nix-build 05-package-management/haskell-project-v3/nix/03-nixpkgs-transitive-deps 151 | these derivations will be built: 152 | /nix/store/6my008rqdjb9kbmx0pr80c0zc0fyqqyh-QuickCheck-2.14.2.drv 153 | /nix/store/lvclw4q2jmk89v5ppkfw3mr72qb8ch2d-haskell-project-0.1.0.0.drv 154 | building '/nix/store/6my008rqdjb9kbmx0pr80c0zc0fyqqyh-QuickCheck-2.14.2.drv'... 155 | ... 156 | Configuring QuickCheck-2.14.2... 157 | CallStack (from HasCallStack): 158 | $, called at libraries/Cabal/Cabal/Distribution/Simple/Configure.hs:1024:20 in Cabal-3.2.0. 159 | ... 160 | defaultMain, called at Setup.lhs:8:10 in main:Main 161 | Setup: Encountered missing or private dependencies: 162 | splitmix ==0.1.* 163 | 164 | builder for '/nix/store/6my008rqdjb9kbmx0pr80c0zc0fyqqyh-QuickCheck-2.14.2.drv' failed with exit code 1 165 | cannot build derivation '/nix/store/lvclw4q2jmk89v5ppkfw3mr72qb8ch2d-haskell-project-0.1.0.0.drv': 1 dependencies couldn't be built 166 | error: build of '/nix/store/lvclw4q2jmk89v5ppkfw3mr72qb8ch2d-haskell-project-0.1.0.0.drv' failed 167 | ``` 168 | 169 | So it turns out that `QuickCheck-2.14.2` depends on `splitmix ==0.1.*`, but nixpkgs only 170 | have `splitmix-0.0.5`, despite `splitmix-0.1` has been released since May 2020. 171 | 172 | We can see this as the effect of transitive dependency update. If `splitmix` is upgraded 173 | to version `0.1`, it will break many packages that directly depend on it, which 174 | in turns breaks other packages that indirectly depend on it. With nixpkgs's 175 | mono-versioning approach, there is no easy way around this other than upgrading 176 | all affecting packages at once, or upgrading none of them. Mono-versioning 177 | is hard! 178 | 179 | Still, we can workaround this by overriding `splitmix` as well: 180 | 181 | ```nix 182 | {{#include ./haskell-project-v3/nix/04-nixpkgs-infinite-recursion/default.nix}} 183 | ``` 184 | 185 | ## Infinite Recursion 186 | 187 | If we build this, we once again get another error: the infamous infinite recursion 188 | error: 189 | 190 | ```bash 191 | $ nix-build --show-trace 05-package-management/haskell-project-v3/nix/04-nixpkgs-infinite-recursion/ 192 | error: while evaluating the attribute 'buildInputs' of the derivation 'haskell-project-0.1.0.0' at /nix/store/kk346951sg2anjjh8cgfbmrijg983z5q-nixpkgs-src/pkgs/development/haskell-modules/generic-builder.nix:291:3: 193 | while evaluating the attribute 'propagatedBuildInputs' of the derivation 'QuickCheck-2.14.2' at /nix/store/kk346951sg2anjjh8cgfbmrijg983z5q-nixpkgs-src/pkgs/development/haskell-modules/generic-builder.nix:291:3: 194 | while evaluating the attribute 'buildInputs' of the derivation 'splitmix-0.1.0.3' at /nix/store/kk346951sg2anjjh8cgfbmrijg983z5q-nixpkgs-src/pkgs/development/haskell-modules/generic-builder.nix:291:3: 195 | while evaluating the attribute 'propagatedBuildInputs' of the derivation 'async-2.2.2' at /nix/store/kk346951sg2anjjh8cgfbmrijg983z5q-nixpkgs-src/pkgs/development/haskell-modules/generic-builder.nix:291:3: 196 | while evaluating the attribute 'buildInputs' of the derivation 'hashable-1.3.0.0' at /nix/store/kk346951sg2anjjh8cgfbmrijg983z5q-nixpkgs-src/pkgs/development/haskell-modules/generic-builder.nix:291:3: 197 | infinite recursion encountered, at undefined position 198 | ``` 199 | 200 | So `QuickCheck` depends on `splitmix`, but `splitmix` tests also indirectly depend on 201 | `QuickCheck`, causing a cyclic dependency. Unfortunately the callPackage 202 | pattern do not have a way to deal with cyclic dependencies, so we have to 203 | manually find ways around it. 204 | 205 | Fortunately in this case, it is simple to avoid it by no running unit tests on 206 | `splitmix`: 207 | 208 | 209 | ```nix 210 | {{#include ./haskell-project-v3/nix/05-nixpkgs-override/default.nix}} 211 | ``` 212 | 213 | Now our package finally builds. 214 | 215 | ## Haskell.nix 216 | 217 | In comparison, there is no manual intervention needed for Haskell.nix-based 218 | derivation: 219 | 220 | ```nix 221 | {{#include ./haskell-project-v3/nix/06-haskell.nix/project.nix}} 222 | ``` 223 | 224 | Hoepfully this shows why we prefer to use Haskell.nix, especially if we 225 | want it to work the same way as cabal and get the latest Haskell packages 226 | from Hackage. 227 | -------------------------------------------------------------------------------- /code/05-package-management/06-other-strategies.md: -------------------------------------------------------------------------------- 1 | # Other Package Management Strategies 2 | 3 | So far we have covered two approaches to managing dependencies in Nix. 4 | Nixpkgs takes a mono-versioning approach by providing exactly one version 5 | of each package in a particular version of nixpkgs. In contrast, Haskell.nix 6 | captures the entire Hackage snapshot and uses Cabal to resolve the 7 | dependencies with the plan derivation being an evaluation-time Nix dependency. 8 | 9 | However these two are not the only approaches to managing dependencies 10 | in Nix. In fact, other languages like Node.js and Rust have come out with 11 | an alternative approach, which is much closer to generating traditional 12 | package lock files. 13 | 14 | ## Size of Package Registry 15 | 16 | Compared to mainstream languages like Node.js, the Haskell ecosystem is much 17 | smaller and has much less packages available. Despite that, the state 18 | of the Hackage registry is pretty large, taking over 100 MiB after compression. 19 | 20 | Capturing the entire state of the package registry is obviously not scaleable. 21 | If the Haskell ecosystem were to grow to the size of Node.js, not only 22 | the nixpkgs and Haskell packages in nixpkgs have to be re-architected, 23 | but also Cabal itself has to come up with better ways of managing dependencies. 24 | 25 | Compared to Cabal, package managers like npm do not simply download 26 | the entire state of the package registries. Instead, the package registries 27 | provide APIs for the package managers to query the available versions of 28 | specific packages. This way, the local package managers only have to query 29 | the relevant packages they need, and ignore the rest of the packages exist 30 | in the registry. 31 | 32 | This approach has the advantage of significantly reducing the bandwidth 33 | required to compute the dependency graph. However since it requires network 34 | communication, this cannot be used inside a Nix build. After all, there 35 | is no way Nix can know if a package registry always give back the same 36 | answer given the same set of queries. 37 | 38 | ## Generating Nix Plans 39 | 40 | Languages like Node.js and Rust come out with tools like 41 | [node2nix](https://github.com/svanderburg/node2nix) and 42 | [cargo2nix](https://github.com/cargo2nix/cargo2nix) to 43 | convert the dependency resolution results directly into Nix expressions. 44 | 45 | For these tools to work, they have to run outside of a Nix build to query 46 | the package registry, and then generate the Nix expressions. Typically, 47 | the Nix expressions are also derived directly from the lock files such as 48 | `package-lock.json` and `Cargo.lock`. 49 | 50 | The generated Nix expressions directly construct the dependency graph 51 | required to build a Node.js or Rust project. When `nix-build` is called, 52 | Nix can then easily use the dependency graph to download and build the 53 | dependencies concurrently. 54 | 55 | ## Similar Approach In Haskell 56 | 57 | While there is a similarly named 58 | [cabal2nix](https://github.com/NixOS/cabal2nix) command for Haskell, 59 | that is actually tied to the mono-versioning approach of nixpkgs. 60 | As a result, cabal2nix never computes any dependency graph, but 61 | instead simply generates a Nix expression that ask for the dependencies 62 | from nixpkgs. 63 | 64 | There is no reason why Haskell cannot adopt similar approaches as 65 | node2nix or cargo2nix. It might be possible that such approaches are 66 | more difficult to achieve, due to how Cabal and Hackage index works. 67 | But that should not be an excuse to stop us from trying. 68 | 69 | Whether to use Nix or not, if we want Haskell to gain wider adoption, 70 | then the package manager Cabal needs 71 | [significant improvement](https://mail.haskell.org/pipermail/hf-discuss/2020-December/000003.html) 72 | to be made. We need to improve Cabal to not just have better integrations 73 | with tools like Nix and IDEs, but also make it much easier for users to 74 | use. 75 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v1/haskell/.gitignore: -------------------------------------------------------------------------------- 1 | dist-newstyle/ 2 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v1/haskell/Main.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | main :: IO () 4 | main = putStrLn "Hello, Haskell!" 5 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v1/haskell/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v1/haskell/cabal.project: -------------------------------------------------------------------------------- 1 | packages: . 2 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v1/haskell/haskell-project.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 2.4 2 | name: haskell-project 3 | version: 0.1.0.0 4 | license: ISC 5 | build-type: Simple 6 | 7 | executable hello 8 | main-is: Main.hs 9 | build-depends: base >=4.13 10 | default-language: Haskell2010 11 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v1/nix/01-naive/default.nix: -------------------------------------------------------------------------------- 1 | let 2 | sources = import ../sources.nix {}; 3 | nixpkgs = import sources.nixpkgs {}; 4 | 5 | hsPkgs = nixpkgs.haskell.packages.ghc8102; 6 | 7 | src = builtins.path { 8 | name = "haskell-project-src"; 9 | path = ../../haskell; 10 | filter = path: type: 11 | let 12 | basePath = builtins.baseNameOf path; 13 | in 14 | basePath != "dist-newstyle" 15 | ; 16 | }; 17 | in 18 | nixpkgs.stdenv.mkDerivation { 19 | inherit src; 20 | 21 | name = "haskell-project"; 22 | 23 | buildInputs = [ 24 | hsPkgs.ghc 25 | hsPkgs.cabal-install 26 | ]; 27 | 28 | builPhase = '' 29 | cabal build all 30 | ''; 31 | 32 | installPhase = '' 33 | cabal install --installdir=$out --install-method=copy 34 | ''; 35 | } 36 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v1/nix/02-nixpkgs/default.nix: -------------------------------------------------------------------------------- 1 | let 2 | sources = import ../sources.nix {}; 3 | nixpkgs = import sources.nixpkgs {}; 4 | 5 | hsPkgs = nixpkgs.haskell.packages.ghc8102; 6 | 7 | src = builtins.path { 8 | name = "haskell-project-src"; 9 | path = ../../haskell; 10 | filter = path: type: 11 | let 12 | basePath = builtins.baseNameOf path; 13 | in 14 | basePath != "dist-newstyle" 15 | ; 16 | }; 17 | 18 | project = hsPkgs.callCabal2nix "haskell-project" src; 19 | in 20 | hsPkgs.callPackage project {} 21 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v1/nix/02-nixpkgs/shell.nix: -------------------------------------------------------------------------------- 1 | let 2 | sources = import ../sources.nix {}; 3 | nixpkgs = import sources.nixpkgs {}; 4 | 5 | hsPkgs = nixpkgs.haskell.packages.ghc8102; 6 | 7 | project = import ./default.nix; 8 | in 9 | nixpkgs.mkShell { 10 | name = "cabal-shell"; 11 | inputsFrom = [ project.env ]; 12 | buildInputs = [ 13 | hsPkgs.cabal-install 14 | ]; 15 | } 16 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v1/nix/03-haskell.nix/default.nix: -------------------------------------------------------------------------------- 1 | let 2 | project = import ./project.nix; 3 | in 4 | project.haskell-project.components.exes.hello 5 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v1/nix/03-haskell.nix/project.nix: -------------------------------------------------------------------------------- 1 | let 2 | sources = import ../sources.nix {}; 3 | 4 | haskell-nix = import sources."haskell.nix" {}; 5 | 6 | nixpkgs = haskell-nix.pkgs; 7 | 8 | src = builtins.path { 9 | name = "haskell-project-src"; 10 | path = ../../haskell; 11 | filter = path: type: 12 | let 13 | basePath = builtins.baseNameOf path; 14 | in 15 | basePath != "dist-newstyle" 16 | ; 17 | }; 18 | 19 | project = nixpkgs.haskell-nix.cabalProject { 20 | inherit src; 21 | 22 | compiler-nix-name = "ghc8102"; 23 | }; 24 | in 25 | project 26 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v1/nix/03-haskell.nix/shell.nix: -------------------------------------------------------------------------------- 1 | let 2 | project = import ./project.nix; 3 | in 4 | project.shellFor { 5 | withHoogle = false; 6 | } 7 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v1/nix/sources.json: -------------------------------------------------------------------------------- 1 | { 2 | "haskell.nix": { 3 | "branch": "master", 4 | "description": "Alternative Haskell Infrastructure for Nixpkgs", 5 | "homepage": "https://input-output-hk.github.io/haskell.nix", 6 | "owner": "input-output-hk", 7 | "repo": "haskell.nix", 8 | "rev": "180779b7f530dcd2a45c7d00541f0f3e3d8471b5", 9 | "sha256": "0y5r0k5wmn1zcip52ifkdy8vpj252q21fzqcy1hgxq9a6n2mh3yi", 10 | "type": "tarball", 11 | "url": "https://github.com/input-output-hk/haskell.nix/archive/180779b7f530dcd2a45c7d00541f0f3e3d8471b5.tar.gz", 12 | "url_template": "https://github.com///archive/.tar.gz" 13 | }, 14 | "niv": { 15 | "branch": "master", 16 | "description": "Easy dependency management for Nix projects", 17 | "homepage": "https://github.com/nmattia/niv", 18 | "owner": "nmattia", 19 | "repo": "niv", 20 | "rev": "ba57d5a29b4e0f2085917010380ef3ddc3cf380f", 21 | "sha256": "1kpsvc53x821cmjg1khvp1nz7906gczq8mp83664cr15h94sh8i4", 22 | "type": "tarball", 23 | "url": "https://github.com/nmattia/niv/archive/ba57d5a29b4e0f2085917010380ef3ddc3cf380f.tar.gz", 24 | "url_template": "https://github.com///archive/.tar.gz" 25 | }, 26 | "nixpkgs": { 27 | "branch": "nixpkgs-unstable", 28 | "description": "Nix Packages collection", 29 | "homepage": "", 30 | "owner": "NixOS", 31 | "repo": "nixpkgs", 32 | "rev": "c1e5f8723ceb684c8d501d4d4ae738fef704747e", 33 | "sha256": "02k3l9wnwpmq68xmmfy4wb2panqa1rs04p1mzh2kiwn0449hl86j", 34 | "type": "tarball", 35 | "url": "https://github.com/NixOS/nixpkgs/archive/c1e5f8723ceb684c8d501d4d4ae738fef704747e.tar.gz", 36 | "url_template": "https://github.com///archive/.tar.gz" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v1/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: name: spec: 10 | let 11 | name' = sanitizeName name + "-src"; 12 | in 13 | if spec.builtin or true then 14 | builtins_fetchurl { inherit (spec) url sha256; name = name'; } 15 | else 16 | pkgs.fetchurl { inherit (spec) url sha256; name = name'; }; 17 | 18 | fetch_tarball = pkgs: name: spec: 19 | let 20 | name' = sanitizeName name + "-src"; 21 | in 22 | if spec.builtin or true then 23 | builtins_fetchTarball { name = name'; inherit (spec) url sha256; } 24 | else 25 | pkgs.fetchzip { name = name'; inherit (spec) url sha256; }; 26 | 27 | fetch_git = name: spec: 28 | let 29 | ref = 30 | if spec ? ref then spec.ref else 31 | if spec ? branch then "refs/heads/${spec.branch}" else 32 | if spec ? tag then "refs/tags/${spec.tag}" else 33 | abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!"; 34 | in 35 | builtins.fetchGit { url = spec.repo; inherit (spec) rev; inherit ref; }; 36 | 37 | fetch_local = spec: spec.path; 38 | 39 | fetch_builtin-tarball = name: throw 40 | ''[${name}] The niv type "builtin-tarball" is deprecated. You should instead use `builtin = true`. 41 | $ niv modify ${name} -a type=tarball -a builtin=true''; 42 | 43 | fetch_builtin-url = name: throw 44 | ''[${name}] The niv type "builtin-url" will soon be deprecated. You should instead use `builtin = true`. 45 | $ niv modify ${name} -a type=file -a builtin=true''; 46 | 47 | # 48 | # Various helpers 49 | # 50 | 51 | # https://github.com/NixOS/nixpkgs/pull/83241/files#diff-c6f540a4f3bfa4b0e8b6bafd4cd54e8bR695 52 | sanitizeName = name: 53 | ( 54 | concatMapStrings (s: if builtins.isList s then "-" else s) 55 | ( 56 | builtins.split "[^[:alnum:]+._?=-]+" 57 | ((x: builtins.elemAt (builtins.match "\\.*(.*)" x) 0) name) 58 | ) 59 | ); 60 | 61 | # The set of packages used when specs are fetched using non-builtins. 62 | mkPkgs = sources: system: 63 | let 64 | sourcesNixpkgs = 65 | import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) { inherit system; }; 66 | hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath; 67 | hasThisAsNixpkgsPath = == ./.; 68 | in 69 | if builtins.hasAttr "nixpkgs" sources 70 | then sourcesNixpkgs 71 | else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then 72 | import {} 73 | else 74 | abort 75 | '' 76 | Please specify either (through -I or NIX_PATH=nixpkgs=...) or 77 | add a package called "nixpkgs" to your sources.json. 78 | ''; 79 | 80 | # The actual fetching function. 81 | fetch = pkgs: name: spec: 82 | 83 | if ! builtins.hasAttr "type" spec then 84 | abort "ERROR: niv spec ${name} does not have a 'type' attribute" 85 | else if spec.type == "file" then fetch_file pkgs name spec 86 | else if spec.type == "tarball" then fetch_tarball pkgs name spec 87 | else if spec.type == "git" then fetch_git name spec 88 | else if spec.type == "local" then fetch_local spec 89 | else if spec.type == "builtin-tarball" then fetch_builtin-tarball name 90 | else if spec.type == "builtin-url" then fetch_builtin-url name 91 | else 92 | abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}"; 93 | 94 | # If the environment variable NIV_OVERRIDE_${name} is set, then use 95 | # the path directly as opposed to the fetched source. 96 | replace = name: drv: 97 | let 98 | saneName = stringAsChars (c: if isNull (builtins.match "[a-zA-Z0-9]" c) then "_" else c) name; 99 | ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}"; 100 | in 101 | if ersatz == "" then drv else ersatz; 102 | 103 | # Ports of functions for older nix versions 104 | 105 | # a Nix version of mapAttrs if the built-in doesn't exist 106 | mapAttrs = builtins.mapAttrs or ( 107 | f: set: with builtins; 108 | listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set)) 109 | ); 110 | 111 | # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295 112 | range = first: last: if first > last then [] else builtins.genList (n: first + n) (last - first + 1); 113 | 114 | # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257 115 | stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1)); 116 | 117 | # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269 118 | stringAsChars = f: s: concatStrings (map f (stringToCharacters s)); 119 | concatMapStrings = f: list: concatStrings (map f list); 120 | concatStrings = builtins.concatStringsSep ""; 121 | 122 | # https://github.com/NixOS/nixpkgs/blob/8a9f58a375c401b96da862d969f66429def1d118/lib/attrsets.nix#L331 123 | optionalAttrs = cond: as: if cond then as else {}; 124 | 125 | # fetchTarball version that is compatible between all the versions of Nix 126 | builtins_fetchTarball = { url, name ? null, sha256 }@attrs: 127 | let 128 | inherit (builtins) lessThan nixVersion fetchTarball; 129 | in 130 | if lessThan nixVersion "1.12" then 131 | fetchTarball ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; })) 132 | else 133 | fetchTarball attrs; 134 | 135 | # fetchurl version that is compatible between all the versions of Nix 136 | builtins_fetchurl = { url, name ? null, sha256 }@attrs: 137 | let 138 | inherit (builtins) lessThan nixVersion fetchurl; 139 | in 140 | if lessThan nixVersion "1.12" then 141 | fetchurl ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; })) 142 | else 143 | fetchurl attrs; 144 | 145 | # Create the final "sources" from the config 146 | mkSources = config: 147 | mapAttrs ( 148 | name: spec: 149 | if builtins.hasAttr "outPath" spec 150 | then abort 151 | "The values in sources.json should not have an 'outPath' attribute" 152 | else 153 | spec // { outPath = replace name (fetch config.pkgs name spec); } 154 | ) config.sources; 155 | 156 | # The "config" used by the fetchers 157 | mkConfig = 158 | { sourcesFile ? if builtins.pathExists ./sources.json then ./sources.json else null 159 | , sources ? if isNull sourcesFile then {} else builtins.fromJSON (builtins.readFile sourcesFile) 160 | , system ? builtins.currentSystem 161 | , pkgs ? mkPkgs sources system 162 | }: rec { 163 | # The sources, i.e. the attribute set of spec name to spec 164 | inherit sources; 165 | 166 | # The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers 167 | inherit pkgs; 168 | }; 169 | 170 | in 171 | mkSources (mkConfig {}) // { __functor = _: settings: mkSources (mkConfig settings); } 172 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v2/haskell/.gitignore: -------------------------------------------------------------------------------- 1 | dist-newstyle/ 2 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v2/haskell/Main.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | main :: IO () 4 | main = putStrLn "Hello, Haskell!" 5 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v2/haskell/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v2/haskell/cabal.project: -------------------------------------------------------------------------------- 1 | packages: . 2 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v2/haskell/haskell-project.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 2.4 2 | name: haskell-project 3 | version: 0.1.0.0 4 | license: ISC 5 | build-type: Simple 6 | 7 | executable hello 8 | main-is: Main.hs 9 | build-depends: base >=4.13 10 | , yaml == 0.11.3.0 11 | default-language: Haskell2010 12 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v2/nix/01-nixpkgs-conflict/default.nix: -------------------------------------------------------------------------------- 1 | let 2 | sources = import ../sources.nix {}; 3 | nixpkgs = import sources.nixpkgs {}; 4 | 5 | hsPkgs = nixpkgs.haskell.packages.ghc8102; 6 | 7 | src = builtins.path { 8 | name = "haskell-project-src"; 9 | path = ../../haskell; 10 | filter = path: type: 11 | let 12 | basePath = builtins.baseNameOf path; 13 | in 14 | basePath != "dist-newstyle" 15 | ; 16 | }; 17 | 18 | project = hsPkgs.callCabal2nix "haskell-project" src; 19 | in 20 | hsPkgs.callPackage project {} 21 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v2/nix/01-nixpkgs-conflict/shell.nix: -------------------------------------------------------------------------------- 1 | let 2 | sources = import ../sources.nix {}; 3 | nixpkgs = import sources.nixpkgs {}; 4 | 5 | hsPkgs = nixpkgs.haskell.packages.ghc8102; 6 | 7 | project = import ./default.nix; 8 | in 9 | nixpkgs.mkShell { 10 | name = "cabal-shell"; 11 | inputsFrom = [ project.env ]; 12 | buildInputs = [ 13 | hsPkgs.cabal-install 14 | ]; 15 | } 16 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v2/nix/02-nixpkgs-override/default.nix: -------------------------------------------------------------------------------- 1 | let 2 | sources = import ../sources.nix {}; 3 | nixpkgs = import sources.nixpkgs {}; 4 | 5 | hsLib = nixpkgs.haskell.lib; 6 | hsPkgs-original = nixpkgs.haskell.packages.ghc8102; 7 | 8 | hsPkgs = hsPkgs-original.override { 9 | overrides = hsPkgs-old: hsPkgs-new: { 10 | yaml = hsPkgs-new.callHackage 11 | "yaml" "0.11.3.0" {}; 12 | }; 13 | }; 14 | 15 | src = builtins.path { 16 | name = "haskell-project-src"; 17 | path = ../../haskell; 18 | filter = path: type: 19 | let 20 | basePath = builtins.baseNameOf path; 21 | in 22 | basePath != "dist-newstyle" 23 | ; 24 | }; 25 | 26 | project = hsPkgs.callCabal2nix "haskell-project" src; 27 | in 28 | hsPkgs.callPackage project {} 29 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v2/nix/02-nixpkgs-override/shell.nix: -------------------------------------------------------------------------------- 1 | let 2 | sources = import ../sources.nix {}; 3 | nixpkgs = import sources.nixpkgs {}; 4 | 5 | hsPkgs = nixpkgs.haskell.packages.ghc8102; 6 | 7 | project = import ./default.nix; 8 | in 9 | nixpkgs.mkShell { 10 | name = "cabal-shell"; 11 | inputsFrom = [ project.env ]; 12 | buildInputs = [ 13 | hsPkgs.cabal-install 14 | ]; 15 | } 16 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v2/nix/03-haskell.nix/default.nix: -------------------------------------------------------------------------------- 1 | let 2 | project = import ./project.nix; 3 | in 4 | project.haskell-project.components.exes.hello 5 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v2/nix/03-haskell.nix/project.nix: -------------------------------------------------------------------------------- 1 | let 2 | sources = import ../sources.nix {}; 3 | 4 | haskell-nix = import sources."haskell.nix" {}; 5 | 6 | nixpkgs = haskell-nix.pkgs; 7 | 8 | src = builtins.path { 9 | name = "haskell-project-src"; 10 | path = ../../haskell; 11 | filter = path: type: 12 | let 13 | basePath = builtins.baseNameOf path; 14 | in 15 | basePath != "dist-newstyle" 16 | ; 17 | }; 18 | 19 | project = nixpkgs.haskell-nix.cabalProject { 20 | inherit src; 21 | 22 | compiler-nix-name = "ghc8102"; 23 | }; 24 | in 25 | project 26 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v2/nix/03-haskell.nix/shell.nix: -------------------------------------------------------------------------------- 1 | let 2 | project = import ./project.nix; 3 | in 4 | project.shellFor { 5 | withHoogle = false; 6 | } 7 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v2/nix/sources.json: -------------------------------------------------------------------------------- 1 | { 2 | "haskell.nix": { 3 | "branch": "master", 4 | "description": "Alternative Haskell Infrastructure for Nixpkgs", 5 | "homepage": "https://input-output-hk.github.io/haskell.nix", 6 | "owner": "input-output-hk", 7 | "repo": "haskell.nix", 8 | "rev": "180779b7f530dcd2a45c7d00541f0f3e3d8471b5", 9 | "sha256": "0y5r0k5wmn1zcip52ifkdy8vpj252q21fzqcy1hgxq9a6n2mh3yi", 10 | "type": "tarball", 11 | "url": "https://github.com/input-output-hk/haskell.nix/archive/180779b7f530dcd2a45c7d00541f0f3e3d8471b5.tar.gz", 12 | "url_template": "https://github.com///archive/.tar.gz" 13 | }, 14 | "niv": { 15 | "branch": "master", 16 | "description": "Easy dependency management for Nix projects", 17 | "homepage": "https://github.com/nmattia/niv", 18 | "owner": "nmattia", 19 | "repo": "niv", 20 | "rev": "ba57d5a29b4e0f2085917010380ef3ddc3cf380f", 21 | "sha256": "1kpsvc53x821cmjg1khvp1nz7906gczq8mp83664cr15h94sh8i4", 22 | "type": "tarball", 23 | "url": "https://github.com/nmattia/niv/archive/ba57d5a29b4e0f2085917010380ef3ddc3cf380f.tar.gz", 24 | "url_template": "https://github.com///archive/.tar.gz" 25 | }, 26 | "nixpkgs": { 27 | "branch": "nixpkgs-unstable", 28 | "description": "Nix Packages collection", 29 | "homepage": "", 30 | "owner": "NixOS", 31 | "repo": "nixpkgs", 32 | "rev": "c1e5f8723ceb684c8d501d4d4ae738fef704747e", 33 | "sha256": "02k3l9wnwpmq68xmmfy4wb2panqa1rs04p1mzh2kiwn0449hl86j", 34 | "type": "tarball", 35 | "url": "https://github.com/NixOS/nixpkgs/archive/c1e5f8723ceb684c8d501d4d4ae738fef704747e.tar.gz", 36 | "url_template": "https://github.com///archive/.tar.gz" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v2/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: name: spec: 10 | let 11 | name' = sanitizeName name + "-src"; 12 | in 13 | if spec.builtin or true then 14 | builtins_fetchurl { inherit (spec) url sha256; name = name'; } 15 | else 16 | pkgs.fetchurl { inherit (spec) url sha256; name = name'; }; 17 | 18 | fetch_tarball = pkgs: name: spec: 19 | let 20 | name' = sanitizeName name + "-src"; 21 | in 22 | if spec.builtin or true then 23 | builtins_fetchTarball { name = name'; inherit (spec) url sha256; } 24 | else 25 | pkgs.fetchzip { name = name'; inherit (spec) url sha256; }; 26 | 27 | fetch_git = name: spec: 28 | let 29 | ref = 30 | if spec ? ref then spec.ref else 31 | if spec ? branch then "refs/heads/${spec.branch}" else 32 | if spec ? tag then "refs/tags/${spec.tag}" else 33 | abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!"; 34 | in 35 | builtins.fetchGit { url = spec.repo; inherit (spec) rev; inherit ref; }; 36 | 37 | fetch_local = spec: spec.path; 38 | 39 | fetch_builtin-tarball = name: throw 40 | ''[${name}] The niv type "builtin-tarball" is deprecated. You should instead use `builtin = true`. 41 | $ niv modify ${name} -a type=tarball -a builtin=true''; 42 | 43 | fetch_builtin-url = name: throw 44 | ''[${name}] The niv type "builtin-url" will soon be deprecated. You should instead use `builtin = true`. 45 | $ niv modify ${name} -a type=file -a builtin=true''; 46 | 47 | # 48 | # Various helpers 49 | # 50 | 51 | # https://github.com/NixOS/nixpkgs/pull/83241/files#diff-c6f540a4f3bfa4b0e8b6bafd4cd54e8bR695 52 | sanitizeName = name: 53 | ( 54 | concatMapStrings (s: if builtins.isList s then "-" else s) 55 | ( 56 | builtins.split "[^[:alnum:]+._?=-]+" 57 | ((x: builtins.elemAt (builtins.match "\\.*(.*)" x) 0) name) 58 | ) 59 | ); 60 | 61 | # The set of packages used when specs are fetched using non-builtins. 62 | mkPkgs = sources: system: 63 | let 64 | sourcesNixpkgs = 65 | import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) { inherit system; }; 66 | hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath; 67 | hasThisAsNixpkgsPath = == ./.; 68 | in 69 | if builtins.hasAttr "nixpkgs" sources 70 | then sourcesNixpkgs 71 | else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then 72 | import {} 73 | else 74 | abort 75 | '' 76 | Please specify either (through -I or NIX_PATH=nixpkgs=...) or 77 | add a package called "nixpkgs" to your sources.json. 78 | ''; 79 | 80 | # The actual fetching function. 81 | fetch = pkgs: name: spec: 82 | 83 | if ! builtins.hasAttr "type" spec then 84 | abort "ERROR: niv spec ${name} does not have a 'type' attribute" 85 | else if spec.type == "file" then fetch_file pkgs name spec 86 | else if spec.type == "tarball" then fetch_tarball pkgs name spec 87 | else if spec.type == "git" then fetch_git name spec 88 | else if spec.type == "local" then fetch_local spec 89 | else if spec.type == "builtin-tarball" then fetch_builtin-tarball name 90 | else if spec.type == "builtin-url" then fetch_builtin-url name 91 | else 92 | abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}"; 93 | 94 | # If the environment variable NIV_OVERRIDE_${name} is set, then use 95 | # the path directly as opposed to the fetched source. 96 | replace = name: drv: 97 | let 98 | saneName = stringAsChars (c: if isNull (builtins.match "[a-zA-Z0-9]" c) then "_" else c) name; 99 | ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}"; 100 | in 101 | if ersatz == "" then drv else ersatz; 102 | 103 | # Ports of functions for older nix versions 104 | 105 | # a Nix version of mapAttrs if the built-in doesn't exist 106 | mapAttrs = builtins.mapAttrs or ( 107 | f: set: with builtins; 108 | listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set)) 109 | ); 110 | 111 | # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295 112 | range = first: last: if first > last then [] else builtins.genList (n: first + n) (last - first + 1); 113 | 114 | # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257 115 | stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1)); 116 | 117 | # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269 118 | stringAsChars = f: s: concatStrings (map f (stringToCharacters s)); 119 | concatMapStrings = f: list: concatStrings (map f list); 120 | concatStrings = builtins.concatStringsSep ""; 121 | 122 | # https://github.com/NixOS/nixpkgs/blob/8a9f58a375c401b96da862d969f66429def1d118/lib/attrsets.nix#L331 123 | optionalAttrs = cond: as: if cond then as else {}; 124 | 125 | # fetchTarball version that is compatible between all the versions of Nix 126 | builtins_fetchTarball = { url, name ? null, sha256 }@attrs: 127 | let 128 | inherit (builtins) lessThan nixVersion fetchTarball; 129 | in 130 | if lessThan nixVersion "1.12" then 131 | fetchTarball ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; })) 132 | else 133 | fetchTarball attrs; 134 | 135 | # fetchurl version that is compatible between all the versions of Nix 136 | builtins_fetchurl = { url, name ? null, sha256 }@attrs: 137 | let 138 | inherit (builtins) lessThan nixVersion fetchurl; 139 | in 140 | if lessThan nixVersion "1.12" then 141 | fetchurl ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; })) 142 | else 143 | fetchurl attrs; 144 | 145 | # Create the final "sources" from the config 146 | mkSources = config: 147 | mapAttrs ( 148 | name: spec: 149 | if builtins.hasAttr "outPath" spec 150 | then abort 151 | "The values in sources.json should not have an 'outPath' attribute" 152 | else 153 | spec // { outPath = replace name (fetch config.pkgs name spec); } 154 | ) config.sources; 155 | 156 | # The "config" used by the fetchers 157 | mkConfig = 158 | { sourcesFile ? if builtins.pathExists ./sources.json then ./sources.json else null 159 | , sources ? if isNull sourcesFile then {} else builtins.fromJSON (builtins.readFile sourcesFile) 160 | , system ? builtins.currentSystem 161 | , pkgs ? mkPkgs sources system 162 | }: rec { 163 | # The sources, i.e. the attribute set of spec name to spec 164 | inherit sources; 165 | 166 | # The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers 167 | inherit pkgs; 168 | }; 169 | 170 | in 171 | mkSources (mkConfig {}) // { __functor = _: settings: mkSources (mkConfig settings); } 172 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v3/haskell/.gitignore: -------------------------------------------------------------------------------- 1 | dist-newstyle/ 2 | secret.key 3 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v3/haskell/Main.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | main :: IO () 4 | main = putStrLn "Hello, Haskell!" 5 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v3/haskell/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v3/haskell/cabal.project: -------------------------------------------------------------------------------- 1 | packages: . 2 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v3/haskell/haskell-project.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 2.4 2 | name: haskell-project 3 | version: 0.1.0.0 4 | license: ISC 5 | build-type: Simple 6 | 7 | executable hello 8 | main-is: Main.hs 9 | build-depends: base >=4.13 10 | , QuickCheck == 2.14.2 11 | default-language: Haskell2010 12 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v3/nix/01-nixpkgs-conflict/default.nix: -------------------------------------------------------------------------------- 1 | let 2 | sources = import ../sources.nix {}; 3 | nixpkgs = import sources.nixpkgs {}; 4 | 5 | hsPkgs = nixpkgs.haskell.packages.ghc8102; 6 | 7 | src = builtins.path { 8 | name = "haskell-project-src"; 9 | path = ../../haskell; 10 | filter = path: type: 11 | let 12 | basePath = builtins.baseNameOf path; 13 | in 14 | basePath != "dist-newstyle" 15 | ; 16 | }; 17 | 18 | project = hsPkgs.callCabal2nix "haskell-project" src; 19 | in 20 | hsPkgs.callPackage project {} 21 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v3/nix/01-nixpkgs-conflict/shell.nix: -------------------------------------------------------------------------------- 1 | let 2 | sources = import ../sources.nix {}; 3 | nixpkgs = import sources.nixpkgs {}; 4 | 5 | hsPkgs = nixpkgs.haskell.packages.ghc8102; 6 | 7 | project = import ./default.nix; 8 | in 9 | nixpkgs.mkShell { 10 | name = "cabal-shell"; 11 | inputsFrom = [ project.env ]; 12 | buildInputs = [ 13 | hsPkgs.cabal-install 14 | ]; 15 | } 16 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v3/nix/02-nixpkgs-not-found/default.nix: -------------------------------------------------------------------------------- 1 | let 2 | sources = import ../sources.nix {}; 3 | nixpkgs = import sources.nixpkgs {}; 4 | 5 | hsLib = nixpkgs.haskell.lib; 6 | hsPkgs-original = nixpkgs.haskell.packages.ghc8102; 7 | 8 | hsPkgs = hsPkgs-original.override { 9 | overrides = hsPkgs-old: hsPkgs-new: { 10 | QuickCheck = hsPkgs-new.callHackage "QuickCheck" "2.14.2" {}; 11 | }; 12 | }; 13 | 14 | src = builtins.path { 15 | name = "haskell-project-src"; 16 | path = ../../haskell; 17 | filter = path: type: 18 | let 19 | basePath = builtins.baseNameOf path; 20 | in 21 | basePath != "dist-newstyle" 22 | ; 23 | }; 24 | 25 | project = hsPkgs.callCabal2nix "haskell-project" src; 26 | in 27 | hsPkgs.callPackage project {} 28 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v3/nix/02-nixpkgs-not-found/shell.nix: -------------------------------------------------------------------------------- 1 | let 2 | sources = import ../sources.nix {}; 3 | nixpkgs = import sources.nixpkgs {}; 4 | 5 | hsPkgs = nixpkgs.haskell.packages.ghc8102; 6 | 7 | project = import ./default.nix; 8 | in 9 | nixpkgs.mkShell { 10 | name = "cabal-shell"; 11 | inputsFrom = [ project.env ]; 12 | buildInputs = [ 13 | hsPkgs.cabal-install 14 | ]; 15 | } 16 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v3/nix/03-nixpkgs-transitive-deps/default.nix: -------------------------------------------------------------------------------- 1 | let 2 | sources = import ../sources.nix {}; 3 | nixpkgs = import sources.nixpkgs {}; 4 | 5 | hsLib = nixpkgs.haskell.lib; 6 | hsPkgs-original = nixpkgs.haskell.packages.ghc8102; 7 | 8 | hsPkgs = hsPkgs-original.override { 9 | overrides = hsPkgs-old: hsPkgs-new: { 10 | QuickCheck = hsPkgs-new.callHackageDirect { 11 | pkg = "QuickCheck"; 12 | ver = "2.14.2"; 13 | sha256 = "0rx4lz5rj0s1v451cq6qdxhilq4rv9b9lnq6frm18h64civ2pwbq"; 14 | } {}; 15 | }; 16 | }; 17 | 18 | src = builtins.path { 19 | name = "haskell-project-src"; 20 | path = ../../haskell; 21 | filter = path: type: 22 | let 23 | basePath = builtins.baseNameOf path; 24 | in 25 | basePath != "dist-newstyle" 26 | ; 27 | }; 28 | 29 | project = hsPkgs.callCabal2nix "haskell-project" src; 30 | in 31 | hsPkgs.callPackage project {} 32 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v3/nix/03-nixpkgs-transitive-deps/shell.nix: -------------------------------------------------------------------------------- 1 | let 2 | sources = import ../sources.nix {}; 3 | nixpkgs = import sources.nixpkgs {}; 4 | 5 | hsPkgs = nixpkgs.haskell.packages.ghc8102; 6 | 7 | project = import ./default.nix; 8 | in 9 | nixpkgs.mkShell { 10 | name = "cabal-shell"; 11 | inputsFrom = [ project.env ]; 12 | buildInputs = [ 13 | hsPkgs.cabal-install 14 | ]; 15 | } 16 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v3/nix/04-nixpkgs-infinite-recursion/default.nix: -------------------------------------------------------------------------------- 1 | let 2 | sources = import ../sources.nix {}; 3 | nixpkgs = import sources.nixpkgs {}; 4 | 5 | hsLib = nixpkgs.haskell.lib; 6 | hsPkgs-original = nixpkgs.haskell.packages.ghc8102; 7 | 8 | hsPkgs = hsPkgs-original.override { 9 | overrides = hsPkgs-old: hsPkgs-new: { 10 | QuickCheck = hsPkgs-new.callHackageDirect { 11 | pkg = "QuickCheck"; 12 | ver = "2.14.2"; 13 | sha256 = "0rx4lz5rj0s1v451cq6qdxhilq4rv9b9lnq6frm18h64civ2pwbq"; 14 | } {}; 15 | 16 | splitmix = hsPkgs-new.callHackage 17 | "splitmix" "0.1.0.3" {}; 18 | }; 19 | }; 20 | 21 | src = builtins.path { 22 | name = "haskell-project-src"; 23 | path = ../../haskell; 24 | filter = path: type: 25 | let 26 | basePath = builtins.baseNameOf path; 27 | in 28 | basePath != "dist-newstyle" 29 | ; 30 | }; 31 | 32 | project = hsPkgs.callCabal2nix "haskell-project" src; 33 | in 34 | hsPkgs.callPackage project {} 35 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v3/nix/04-nixpkgs-infinite-recursion/shell.nix: -------------------------------------------------------------------------------- 1 | let 2 | sources = import ../sources.nix {}; 3 | nixpkgs = import sources.nixpkgs {}; 4 | 5 | hsPkgs = nixpkgs.haskell.packages.ghc8102; 6 | 7 | project = import ./default.nix; 8 | in 9 | nixpkgs.mkShell { 10 | name = "cabal-shell"; 11 | inputsFrom = [ project.env ]; 12 | buildInputs = [ 13 | hsPkgs.cabal-install 14 | ]; 15 | } 16 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v3/nix/05-nixpkgs-override/default.nix: -------------------------------------------------------------------------------- 1 | let 2 | sources = import ../sources.nix {}; 3 | nixpkgs = import sources.nixpkgs {}; 4 | 5 | hsLib = nixpkgs.haskell.lib; 6 | hsPkgs-original = nixpkgs.haskell.packages.ghc8102; 7 | 8 | hsPkgs = hsPkgs-original.override { 9 | overrides = hsPkgs-old: hsPkgs-new: { 10 | QuickCheck = hsPkgs-new.callHackageDirect { 11 | pkg = "QuickCheck"; 12 | ver = "2.14.2"; 13 | sha256 = "0rx4lz5rj0s1v451cq6qdxhilq4rv9b9lnq6frm18h64civ2pwbq"; 14 | } {}; 15 | 16 | splitmix = hsLib.dontCheck 17 | (hsPkgs-new.callHackage 18 | "splitmix" "0.1.0.3" {}); 19 | }; 20 | }; 21 | 22 | src = builtins.path { 23 | name = "haskell-project-src"; 24 | path = ../../haskell; 25 | filter = path: type: 26 | let 27 | basePath = builtins.baseNameOf path; 28 | in 29 | basePath != "dist-newstyle" 30 | ; 31 | }; 32 | 33 | project = hsPkgs.callCabal2nix "haskell-project" src; 34 | in 35 | hsPkgs.callPackage project {} 36 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v3/nix/05-nixpkgs-override/shell.nix: -------------------------------------------------------------------------------- 1 | let 2 | sources = import ../sources.nix {}; 3 | nixpkgs = import sources.nixpkgs {}; 4 | 5 | hsPkgs = nixpkgs.haskell.packages.ghc8102; 6 | 7 | project = import ./default.nix; 8 | in 9 | nixpkgs.mkShell { 10 | name = "cabal-shell"; 11 | inputsFrom = [ project.env ]; 12 | buildInputs = [ 13 | hsPkgs.cabal-install 14 | ]; 15 | } 16 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v3/nix/06-haskell.nix/default.nix: -------------------------------------------------------------------------------- 1 | let 2 | project = import ./project.nix; 3 | in 4 | project.haskell-project.components.exes.hello 5 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v3/nix/06-haskell.nix/project.nix: -------------------------------------------------------------------------------- 1 | let 2 | sources = import ../sources.nix {}; 3 | 4 | haskell-nix = import sources."haskell.nix" {}; 5 | 6 | nixpkgs = haskell-nix.pkgs; 7 | 8 | src = builtins.path { 9 | name = "haskell-project-src"; 10 | path = ../../haskell; 11 | filter = path: type: 12 | let 13 | basePath = builtins.baseNameOf path; 14 | in 15 | basePath != "dist-newstyle" 16 | ; 17 | }; 18 | 19 | project = nixpkgs.haskell-nix.cabalProject { 20 | inherit src; 21 | 22 | compiler-nix-name = "ghc8102"; 23 | }; 24 | in 25 | project 26 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v3/nix/06-haskell.nix/shell.nix: -------------------------------------------------------------------------------- 1 | let 2 | project = import ./project.nix; 3 | in 4 | project.shellFor { 5 | withHoogle = false; 6 | } 7 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v3/nix/07-haskell.nix-materialized/default.nix: -------------------------------------------------------------------------------- 1 | let 2 | project = import ./project.nix { 3 | useMaterialization = true; 4 | }; 5 | in 6 | project.haskell-project.components.exes.hello 7 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v3/nix/07-haskell.nix-materialized/plan-hash.txt: -------------------------------------------------------------------------------- 1 | 1i7pl65lx8zp9n8rcg8d6x7bqnapdw6mgmz9q728wgkkjn484swa 2 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v3/nix/07-haskell.nix-materialized/plan/.plan.nix/haskell-project.nix: -------------------------------------------------------------------------------- 1 | { system 2 | , compiler 3 | , flags 4 | , pkgs 5 | , hsPkgs 6 | , pkgconfPkgs 7 | , errorHandler 8 | , config 9 | , ... }: 10 | { 11 | flags = {}; 12 | package = { 13 | specVersion = "2.4"; 14 | identifier = { name = "haskell-project"; version = "0.1.0.0"; }; 15 | license = "ISC"; 16 | copyright = ""; 17 | maintainer = ""; 18 | author = ""; 19 | homepage = ""; 20 | url = ""; 21 | synopsis = ""; 22 | description = ""; 23 | buildType = "Simple"; 24 | isLocal = true; 25 | detailLevel = "FullDetails"; 26 | licenseFiles = []; 27 | dataDir = ""; 28 | dataFiles = []; 29 | extraSrcFiles = []; 30 | extraTmpFiles = []; 31 | extraDocFiles = []; 32 | }; 33 | components = { 34 | exes = { 35 | "hello" = { 36 | depends = [ 37 | (hsPkgs."base" or (errorHandler.buildDepError "base")) 38 | (hsPkgs."QuickCheck" or (errorHandler.buildDepError "QuickCheck")) 39 | ]; 40 | buildable = true; 41 | mainPath = [ "Main.hs" ]; 42 | }; 43 | }; 44 | }; 45 | } // rec { src = (pkgs.lib).mkDefault ../.; } -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v3/nix/07-haskell.nix-materialized/plan/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs = hackage: 3 | { 4 | packages = { 5 | "ghc-prim".revision = (((hackage."ghc-prim")."0.6.1").revisions).default; 6 | "mtl".revision = (((hackage."mtl")."2.2.2").revisions).default; 7 | "rts".revision = (((hackage."rts")."1.0").revisions).default; 8 | "QuickCheck".revision = (((hackage."QuickCheck")."2.14.2").revisions).default; 9 | "QuickCheck".flags.templatehaskell = true; 10 | "QuickCheck".flags.old-random = false; 11 | "deepseq".revision = (((hackage."deepseq")."1.4.4.0").revisions).default; 12 | "random".revision = (((hackage."random")."1.2.0").revisions).default; 13 | "splitmix".revision = (((hackage."splitmix")."0.1.0.3").revisions).default; 14 | "splitmix".flags.optimised-mixer = false; 15 | "template-haskell".revision = (((hackage."template-haskell")."2.16.0.0").revisions).default; 16 | "containers".revision = (((hackage."containers")."0.6.2.1").revisions).default; 17 | "bytestring".revision = (((hackage."bytestring")."0.10.10.0").revisions).default; 18 | "base".revision = (((hackage."base")."4.14.1.0").revisions).default; 19 | "transformers".revision = (((hackage."transformers")."0.5.6.2").revisions).default; 20 | "pretty".revision = (((hackage."pretty")."1.1.3.6").revisions).default; 21 | "ghc-boot-th".revision = (((hackage."ghc-boot-th")."8.10.2").revisions).default; 22 | "array".revision = (((hackage."array")."0.5.4.0").revisions).default; 23 | "integer-gmp".revision = (((hackage."integer-gmp")."1.0.3.0").revisions).default; 24 | }; 25 | compiler = { 26 | version = "8.10.2"; 27 | nix-name = "ghc8102"; 28 | packages = { 29 | "ghc-prim" = "0.6.1"; 30 | "mtl" = "2.2.2"; 31 | "rts" = "1.0"; 32 | "deepseq" = "1.4.4.0"; 33 | "template-haskell" = "2.16.0.0"; 34 | "containers" = "0.6.2.1"; 35 | "bytestring" = "0.10.10.0"; 36 | "base" = "4.14.1.0"; 37 | "transformers" = "0.5.6.2"; 38 | "pretty" = "1.1.3.6"; 39 | "ghc-boot-th" = "8.10.2"; 40 | "array" = "0.5.4.0"; 41 | "integer-gmp" = "1.0.3.0"; 42 | }; 43 | }; 44 | }; 45 | extras = hackage: 46 | { packages = { haskell-project = ./.plan.nix/haskell-project.nix; }; }; 47 | modules = [ 48 | ({ lib, ... }: 49 | { packages = { "haskell-project" = { flags = {}; }; }; }) 50 | ]; 51 | } -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v3/nix/07-haskell.nix-materialized/project.nix: -------------------------------------------------------------------------------- 1 | { useMaterialization ? true }: 2 | let 3 | sources = import ../sources.nix {}; 4 | 5 | haskell-nix = import sources."haskell.nix" {}; 6 | 7 | nixpkgs = haskell-nix.pkgs; 8 | 9 | src = builtins.path { 10 | name = "haskell-project-src"; 11 | path = ../../haskell; 12 | filter = path: type: 13 | let 14 | basePath = builtins.baseNameOf path; 15 | in 16 | basePath != "dist-newstyle" 17 | ; 18 | }; 19 | 20 | project = nixpkgs.haskell-nix.cabalProject { 21 | inherit src; 22 | 23 | compiler-nix-name = "ghc8102"; 24 | 25 | index-state = "2020-12-04T00:00:00Z"; 26 | 27 | materialized = if useMaterialization 28 | then ./plan else null; 29 | 30 | plan-sha256 = if useMaterialization 31 | then nixpkgs.lib.removeSuffix "\n" 32 | (builtins.readFile ./plan-hash.txt) 33 | else null; 34 | 35 | exactDeps = true; 36 | }; 37 | in 38 | project 39 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v3/nix/07-haskell.nix-materialized/shell.nix: -------------------------------------------------------------------------------- 1 | let 2 | project = import ./project.nix; 3 | in 4 | project.shellFor { 5 | withHoogle = false; 6 | } 7 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v3/nix/07-haskell.nix-materialized/sync-materialized.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | plan=$(nix-build -j4 --no-out-link --arg useMaterialization false -A plan-nix project.nix) 4 | 5 | rm -rf plan 6 | 7 | cp -r $plan plan 8 | 9 | find plan -type d -exec chmod 755 {} \; 10 | 11 | nix-hash --base32 --type sha256 plan > plan-hash.txt 12 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v3/nix/sources.json: -------------------------------------------------------------------------------- 1 | { 2 | "haskell.nix": { 3 | "branch": "master", 4 | "description": "Alternative Haskell Infrastructure for Nixpkgs", 5 | "homepage": "https://input-output-hk.github.io/haskell.nix", 6 | "owner": "input-output-hk", 7 | "repo": "haskell.nix", 8 | "rev": "180779b7f530dcd2a45c7d00541f0f3e3d8471b5", 9 | "sha256": "0y5r0k5wmn1zcip52ifkdy8vpj252q21fzqcy1hgxq9a6n2mh3yi", 10 | "type": "tarball", 11 | "url": "https://github.com/input-output-hk/haskell.nix/archive/180779b7f530dcd2a45c7d00541f0f3e3d8471b5.tar.gz", 12 | "url_template": "https://github.com///archive/.tar.gz" 13 | }, 14 | "niv": { 15 | "branch": "master", 16 | "description": "Easy dependency management for Nix projects", 17 | "homepage": "https://github.com/nmattia/niv", 18 | "owner": "nmattia", 19 | "repo": "niv", 20 | "rev": "ba57d5a29b4e0f2085917010380ef3ddc3cf380f", 21 | "sha256": "1kpsvc53x821cmjg1khvp1nz7906gczq8mp83664cr15h94sh8i4", 22 | "type": "tarball", 23 | "url": "https://github.com/nmattia/niv/archive/ba57d5a29b4e0f2085917010380ef3ddc3cf380f.tar.gz", 24 | "url_template": "https://github.com///archive/.tar.gz" 25 | }, 26 | "nixpkgs": { 27 | "branch": "nixpkgs-unstable", 28 | "description": "Nix Packages collection", 29 | "homepage": "", 30 | "owner": "NixOS", 31 | "repo": "nixpkgs", 32 | "rev": "c1e5f8723ceb684c8d501d4d4ae738fef704747e", 33 | "sha256": "02k3l9wnwpmq68xmmfy4wb2panqa1rs04p1mzh2kiwn0449hl86j", 34 | "type": "tarball", 35 | "url": "https://github.com/NixOS/nixpkgs/archive/c1e5f8723ceb684c8d501d4d4ae738fef704747e.tar.gz", 36 | "url_template": "https://github.com///archive/.tar.gz" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /code/05-package-management/haskell-project-v3/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: name: spec: 10 | let 11 | name' = sanitizeName name + "-src"; 12 | in 13 | if spec.builtin or true then 14 | builtins_fetchurl { inherit (spec) url sha256; name = name'; } 15 | else 16 | pkgs.fetchurl { inherit (spec) url sha256; name = name'; }; 17 | 18 | fetch_tarball = pkgs: name: spec: 19 | let 20 | name' = sanitizeName name + "-src"; 21 | in 22 | if spec.builtin or true then 23 | builtins_fetchTarball { name = name'; inherit (spec) url sha256; } 24 | else 25 | pkgs.fetchzip { name = name'; inherit (spec) url sha256; }; 26 | 27 | fetch_git = name: spec: 28 | let 29 | ref = 30 | if spec ? ref then spec.ref else 31 | if spec ? branch then "refs/heads/${spec.branch}" else 32 | if spec ? tag then "refs/tags/${spec.tag}" else 33 | abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!"; 34 | in 35 | builtins.fetchGit { url = spec.repo; inherit (spec) rev; inherit ref; }; 36 | 37 | fetch_local = spec: spec.path; 38 | 39 | fetch_builtin-tarball = name: throw 40 | ''[${name}] The niv type "builtin-tarball" is deprecated. You should instead use `builtin = true`. 41 | $ niv modify ${name} -a type=tarball -a builtin=true''; 42 | 43 | fetch_builtin-url = name: throw 44 | ''[${name}] The niv type "builtin-url" will soon be deprecated. You should instead use `builtin = true`. 45 | $ niv modify ${name} -a type=file -a builtin=true''; 46 | 47 | # 48 | # Various helpers 49 | # 50 | 51 | # https://github.com/NixOS/nixpkgs/pull/83241/files#diff-c6f540a4f3bfa4b0e8b6bafd4cd54e8bR695 52 | sanitizeName = name: 53 | ( 54 | concatMapStrings (s: if builtins.isList s then "-" else s) 55 | ( 56 | builtins.split "[^[:alnum:]+._?=-]+" 57 | ((x: builtins.elemAt (builtins.match "\\.*(.*)" x) 0) name) 58 | ) 59 | ); 60 | 61 | # The set of packages used when specs are fetched using non-builtins. 62 | mkPkgs = sources: system: 63 | let 64 | sourcesNixpkgs = 65 | import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) { inherit system; }; 66 | hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath; 67 | hasThisAsNixpkgsPath = == ./.; 68 | in 69 | if builtins.hasAttr "nixpkgs" sources 70 | then sourcesNixpkgs 71 | else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then 72 | import {} 73 | else 74 | abort 75 | '' 76 | Please specify either (through -I or NIX_PATH=nixpkgs=...) or 77 | add a package called "nixpkgs" to your sources.json. 78 | ''; 79 | 80 | # The actual fetching function. 81 | fetch = pkgs: name: spec: 82 | 83 | if ! builtins.hasAttr "type" spec then 84 | abort "ERROR: niv spec ${name} does not have a 'type' attribute" 85 | else if spec.type == "file" then fetch_file pkgs name spec 86 | else if spec.type == "tarball" then fetch_tarball pkgs name spec 87 | else if spec.type == "git" then fetch_git name spec 88 | else if spec.type == "local" then fetch_local spec 89 | else if spec.type == "builtin-tarball" then fetch_builtin-tarball name 90 | else if spec.type == "builtin-url" then fetch_builtin-url name 91 | else 92 | abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}"; 93 | 94 | # If the environment variable NIV_OVERRIDE_${name} is set, then use 95 | # the path directly as opposed to the fetched source. 96 | replace = name: drv: 97 | let 98 | saneName = stringAsChars (c: if isNull (builtins.match "[a-zA-Z0-9]" c) then "_" else c) name; 99 | ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}"; 100 | in 101 | if ersatz == "" then drv else ersatz; 102 | 103 | # Ports of functions for older nix versions 104 | 105 | # a Nix version of mapAttrs if the built-in doesn't exist 106 | mapAttrs = builtins.mapAttrs or ( 107 | f: set: with builtins; 108 | listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set)) 109 | ); 110 | 111 | # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295 112 | range = first: last: if first > last then [] else builtins.genList (n: first + n) (last - first + 1); 113 | 114 | # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257 115 | stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1)); 116 | 117 | # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269 118 | stringAsChars = f: s: concatStrings (map f (stringToCharacters s)); 119 | concatMapStrings = f: list: concatStrings (map f list); 120 | concatStrings = builtins.concatStringsSep ""; 121 | 122 | # https://github.com/NixOS/nixpkgs/blob/8a9f58a375c401b96da862d969f66429def1d118/lib/attrsets.nix#L331 123 | optionalAttrs = cond: as: if cond then as else {}; 124 | 125 | # fetchTarball version that is compatible between all the versions of Nix 126 | builtins_fetchTarball = { url, name ? null, sha256 }@attrs: 127 | let 128 | inherit (builtins) lessThan nixVersion fetchTarball; 129 | in 130 | if lessThan nixVersion "1.12" then 131 | fetchTarball ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; })) 132 | else 133 | fetchTarball attrs; 134 | 135 | # fetchurl version that is compatible between all the versions of Nix 136 | builtins_fetchurl = { url, name ? null, sha256 }@attrs: 137 | let 138 | inherit (builtins) lessThan nixVersion fetchurl; 139 | in 140 | if lessThan nixVersion "1.12" then 141 | fetchurl ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; })) 142 | else 143 | fetchurl attrs; 144 | 145 | # Create the final "sources" from the config 146 | mkSources = config: 147 | mapAttrs ( 148 | name: spec: 149 | if builtins.hasAttr "outPath" spec 150 | then abort 151 | "The values in sources.json should not have an 'outPath' attribute" 152 | else 153 | spec // { outPath = replace name (fetch config.pkgs name spec); } 154 | ) config.sources; 155 | 156 | # The "config" used by the fetchers 157 | mkConfig = 158 | { sourcesFile ? if builtins.pathExists ./sources.json then ./sources.json else null 159 | , sources ? if isNull sourcesFile then {} else builtins.fromJSON (builtins.readFile sourcesFile) 160 | , system ? builtins.currentSystem 161 | , pkgs ? mkPkgs sources system 162 | }: rec { 163 | # The sources, i.e. the attribute set of spec name to spec 164 | inherit sources; 165 | 166 | # The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers 167 | inherit pkgs; 168 | }; 169 | 170 | in 171 | mkSources (mkConfig {}) // { __functor = _: settings: mkSources (mkConfig settings); } 172 | -------------------------------------------------------------------------------- /code/06-infrastructure/02-caching-haskell.md: -------------------------------------------------------------------------------- 1 | # Caching Haskell Nix Packages 2 | 3 | Similar to the previous chapter, we can cache our Haskell.nix project in similar way. 4 | 5 | ```bash 6 | $ drv=$(nix-instantiate ./code/05-package-management/haskell-project-v3/nix/07-haskell.nix-materialized) 7 | 8 | $ nix-build $drv && nix-store -qR --include-outputs $drv | grep -v .drv | cachix push $CACHIX_STORE 9 | ... 10 | /nix/store/8vrdfinxxnwczn4jzknm44bsn3k5nghl-haskell-project-exe-hello-0.1.0.0 11 | compressing and pushing /nix/store/8vrdfinxxnwczn4jzknm44bsn3k5nghl-haskell-project-exe-hello-0.1.0.0 (3.60 MiB) 12 | compressing and pushing /nix/store/6apx83l6ss3hkn0kd4z4rkjbkgs0w4w2-default-Setup-setup (18.07 MiB) 13 | compressing and pushing /nix/store/3pfy3dd8ch77km1wkwd6cdgqn57d4347-haskell-project-exe-hello-0.1.0.0-config (304.02 KiB) 14 | compressing and pushing /nix/store/b2j1nrsjr8cpzmk58d476fc2snz17w75-ghc-8.10.2 (1.71 GiB) 15 | ... 16 | All done. 17 | ``` 18 | 19 | As simple as it might look, the naive approach however has some flaws, 20 | especially when dealing with private projects. 21 | 22 | ## Leaking Source Code 23 | 24 | The first issue with pushing everything is source code contamination, i.e. 25 | the source code of the project leaking to the cache. For instance, suppose 26 | we modify the [main](../05-package-management/haskell-project-v3/haskell/Main.hs) 27 | function to print "Hello, World!" instead of "Hello, Haskell!": 28 | 29 | ```bash 30 | $ sed -i 's/Hello, Haskell!/Hello, World!/g' ./code/05-package-management/haskell-project-v3/haskell/Main.hs 31 | $ cat ./code/05-package-management/haskell-project-v3/haskell/Main.hs 32 | module Main where 33 | 34 | main :: IO () 35 | main = putStrLn "Hello, World!" 36 | ``` 37 | 38 | If we try to rebuild our Haskell project and push it to Cachix, we can notice 39 | that the modified source code is also pushed as well. 40 | (Notice the `drv=$(nix-instantiate ...)` assignment has to be re-run to get the 41 | new derivation with the modified source) 42 | 43 | ```bash 44 | $ drv=$(nix-instantiate ./code/05-package-management/haskell-project-v3/nix/07-haskell.nix-materialized) 45 | $ nix-build $drv && nix-store -qR --include-outputs $drv | grep -v .drv | cachix push $CACHIX_STORE 46 | ... 47 | /nix/store/hkqkig7y1dx96qbdwkhk0anb0xdmx6hm-haskell-project-exe-hello-0.1.0.0 48 | compressing and pushing /nix/store/hkqkig7y1dx96qbdwkhk0anb0xdmx6hm-haskell-project-exe-hello-0.1.0.0 (3.60 MiB) 49 | compressing and pushing /nix/store/wq6ry5x7b5x3ld0d7wd2wx3vkxp4wi66-haskell-project-src (1.49 KiB) 50 | All done. 51 | ``` 52 | 53 | We can list the files in `/nix/store/6a049f3fv8x2rdxv34k14cxrwi9an43f-haskell-project-src` 54 | and verify that it indeed contains our modified source code. Yikes! 55 | 56 | ```bash 57 | $ ls -la /nix/store/6a049f3fv8x2rdxv34k14cxrwi9an43f-haskell-project-src 58 | total 180 59 | dr-xr-xr-x 2 user user 4096 Jan 1 1970 . 60 | drwxr-xr-x 1 user user 151552 Jan 7 15:41 .. 61 | -r--r--r-- 1 user user 15 Jan 1 1970 .gitignore 62 | -r--r--r-- 1 user user 65 Jan 1 1970 Main.hs 63 | -r--r--r-- 1 user user 46 Jan 1 1970 Setup.hs 64 | -r--r--r-- 1 user user 12 Jan 1 1970 cabal.project 65 | -r--r--r-- 1 user user 307 Jan 1 1970 haskell-project.cabal 66 | 67 | $ cat /nix/store/6a049f3fv8x2rdxv34k14cxrwi9an43f-haskell-project-src/Main.hs 68 | module Main where 69 | 70 | main :: IO () 71 | main = putStrLn "Hello, World!" 72 | ``` 73 | 74 | Pushing source code to Cachix might not be a big deal for open source projects. 75 | However this may be an issue for propritary projects with strict IP policies. 76 | This could be partially mitigated by paying for a private Cachix store. But we 77 | just have to be aware of it and be careful. 78 | 79 | ## Leaking Secrets 80 | 81 | Even for the case of open source projects, indiscriminately pushing everything 82 | to Cachix still carries another risk, which is accidentally leaking secrets 83 | such as authentication credentials. 84 | 85 | Suppose that we have some security credentials stored locally in the `secret.key` 86 | file in the project directory. Since the file is included in `.gitignore`, it is 87 | not pushed to the git repository. 88 | 89 | ```bash 90 | $ echo secret > ./code/05-package-management/haskell-project-v3/haskell/secret.key 91 | $ ls -la ./code/05-package-management/haskell-project-v3/haskell/ 92 | total 32 93 | drwxrwxr-x 2 user user 4096 Jan 7 15:58 . 94 | drwxrwxr-x 4 user user 4096 Dec 8 08:23 .. 95 | -rw-rw-r-- 1 user user 26 Jan 7 15:58 .gitignore 96 | -rw-r--r-- 1 user user 67 Jan 7 15:45 Main.hs 97 | -rw-r--r-- 1 user user 46 Dec 7 08:37 Setup.hs 98 | -rw-rw-r-- 1 user user 12 Dec 7 08:37 cabal.project 99 | -rw-rw-r-- 1 user user 307 Jan 7 09:35 haskell-project.cabal 100 | -rw-rw-r-- 1 user user 7 Jan 7 15:58 secret.key 101 | ``` 102 | 103 | But is `secret.key` being included when pushing to Cachix? Let's find out: 104 | 105 | ``` 106 | $ drv=$(nix-instantiate ./code/05-package-management/haskell-project-v3/nix/07-haskell.nix-materialized) 107 | $ nix-build $drv && nix-store -qR --include-outputs $drv | grep -v .drv | cachix push $CACHIX_STORE 108 | ... 109 | compressing and pushing /nix/store/nrmyzkww87ndyp44jkn56hrra8m9d9vy-haskell-project-exe-hello-0.1.0.0 (3.60 MiB) 110 | compressing and pushing /nix/store/ryz8an9z9bw7j1357k9b5w99fxvnhb74-haskell-project-src (1.69 KiB) 111 | All done. 112 | 113 | $ ls -la /nix/store/ryz8an9z9bw7j1357k9b5w99fxvnhb74-haskell-project-src 114 | total 188 115 | dr-xr-xr-x 2 user user 4096 Jan 1 1970 . 116 | drwxr-xr-x 1 user user 155648 Jan 7 16:00 .. 117 | -r--r--r-- 1 user user 26 Jan 1 1970 .gitignore 118 | -r--r--r-- 1 user user 67 Jan 1 1970 Main.hs 119 | -r--r--r-- 1 user user 46 Jan 1 1970 Setup.hs 120 | -r--r--r-- 1 user user 12 Jan 1 1970 cabal.project 121 | -r--r--r-- 1 user user 307 Jan 1 1970 haskell-project.cabal 122 | -r--r--r-- 1 user user 7 Jan 1 1970 secret.key 123 | 124 | $ cat /nix/store/ryz8an9z9bw7j1357k9b5w99fxvnhb74-haskell-project-src/secret.key 125 | secret 126 | ``` 127 | 128 | That's not good! Our local security credentials have been leaked to Cachix! 129 | If we also have a public Cachix store, the credentials can potentially be obtained 130 | by anyone! 131 | 132 | The real culprit is in how we create our source derivation in 133 | [`project.nix`](../05-package-management/haskell-project-v3/nix/07-haskell.nix-materialized/project.nix): 134 | 135 | ```nix 136 | src = builtins.path { 137 | name = "haskell-project-src"; 138 | path = ../../haskell; 139 | filter = path: type: 140 | let 141 | basePath = builtins.baseNameOf path; 142 | in 143 | basePath != "dist-newstyle" 144 | ; 145 | }; 146 | ``` 147 | 148 | Previously, we made a naive attempt of filtering our source directory and 149 | excluding only the `dist-newstyle` directory to avoid rebuilding the Nix 150 | build when the directory is modified by local `cabal` runs. However if 151 | we want to push our source code to Cachix, we better be much more careful. 152 | 153 | ## Gitignore.nix 154 | 155 | One way we can protect local secrets is by filtering out all gitignored 156 | files so that our source code is close to a fresh git checkout when copied 157 | into the Nix store. This can be done using Nix helper libraries such as 158 | [gitignore.nix](https://github.com/hercules-ci/gitignore.nix). 159 | 160 | Using gitignore.nix, we can now create a new 161 | [haskell-project-v4](./haskell-project-v4) project with the source 162 | filtered with gitignore.nix: 163 | 164 | ```nix 165 | gitignore = (import sources."gitignore.nix" { 166 | inherit (nixpkgs) lib; 167 | }).gitignoreSource; 168 | 169 | src = nixpkgs.lib.cleanSourceWith { 170 | name = "haskell-project-src"; 171 | src = gitignore ../../haskell; 172 | }; 173 | ``` 174 | 175 | We first add `gitignore.nix` into `sources` using `niv`, and then import 176 | it as above. Following that, we use `gitignore ../../haskell` to 177 | filter the gitignored files in the `haskell` directory. We then 178 | use `nixpkgs.lib.cleanSourceWith` as a hack to give the filtered source a 179 | name `haskell-project-src`, so that we can grep for it during inspection. 180 | 181 | Now if we try to build our derivation, we should get the project source with 182 | the local secret filtered out: 183 | 184 | ```bash 185 | $ drv=$(nix-instantiate ./code/06-infrastructure/haskell-project-v4/nix/01-gitignore-src) 186 | $ nix-store -qR --include-outputs $drv | grep haskell-project-src 187 | /nix/store/mhlj5xql8g6ib1wna4g9pc6cpraiz1q8-haskell-project-src-root 188 | 189 | $ ls -la /nix/store/mhlj5xql8g6ib1wna4g9pc6cpraiz1q8-haskell-project-src-root 190 | total 140 191 | dr-xr-xr-x 2 nix nix 4096 Jan 1 1970 . 192 | drwxr-xr-x 1 nix nix 114688 Jan 11 11:21 .. 193 | -r--r--r-- 1 nix nix 26 Jan 1 1970 .gitignore 194 | -r--r--r-- 1 nix nix 67 Jan 1 1970 Main.hs 195 | -r--r--r-- 1 nix nix 46 Jan 1 1970 Setup.hs 196 | -r--r--r-- 1 nix nix 12 Jan 1 1970 cabal.project 197 | -r--r--r-- 1 nix nix 307 Jan 1 1970 haskell-project.cabal 198 | ``` 199 | 200 | ### Caveats 201 | 202 | Gitignore.nix can help us filter out files specified in `.gitignore`. 203 | However it might still be possible that developers would add new secrets 204 | locally without adding them to `.gitignore`. In such case, the secret 205 | can still potentially leak to Cachix. 206 | 207 | The best way to prevent secrets from leaking is to build from a published 208 | git or tarball URL. That way it will be less likely for us to accidentally 209 | mix up and leak the secrets in our local file systems. This will 210 | however require more complex project organization, as we have to place 211 | the Nix code separately from the source code. 212 | 213 | Otherwise, it is still recommended to avoid pushing source code to 214 | Cachix in the first place, both for proprietary and open source projects. 215 | After all, users will almost always build a Nix project with their own 216 | local source code, or source that are fetched directly from git or 217 | remote URLs. There is rarely a need to use Cachix to distribute source 218 | code to our users. 219 | 220 | ## Filtering Out Source 221 | 222 | One simple way to filter out the source code is to filter out the name 223 | of the source derivation using `grep` before pushing to Cachix: 224 | 225 | ```bash 226 | $ nix-store -qR --include-outputs $drv \ 227 | | grep -v .drv | grep -v haskell-project-src \ 228 | | cachix push $CACHIX_STORE 229 | ``` 230 | 231 | Note however this may only work if no other paths pushed to Cachix depends 232 | on the source code. This is because Cachix automatically pushes the whole 233 | closure of a Nix path. For instance this would not work if we try to push 234 | the `.drv` file of the build derivation to Cachix, because that would 235 | also capture the source derivation as part of the closure. 236 | 237 | This approach also would not work if there are some intermediate derivations 238 | that make copy of the original source code and modify them to produce 239 | new source derivation. The intermediate derivation may have a different 240 | name, or even a generic one, which it would be difficult for us to filter 241 | out without inspecting the derivation source. 242 | 243 | As a result, it is best to make use of the `patchPhase` in 244 | `stdenv.mkDerivation` to modify the source code if necessary. 245 | 246 | ## Caching Nix Shell 247 | 248 | Another way to exclude source code from derivation is by creating a Nix shell 249 | derivation and cache that instead. Haskell.nix provides a `shellFor` 250 | function that creates a Nix shell derivation from the original 251 | Haskell.nix project we defined. 252 | 253 | 254 | ```nix 255 | {{#include ./haskell-project-v4/nix/01-gitignore-src/shell.nix}} 256 | ``` 257 | 258 | If we inspect the derivation tree from `shell.nix`, we can confirm that 259 | indeed the source code not present in the list. And so we can 260 | safely push only the Haskell.nix dependencies to Cachix. 261 | 262 | 263 | ```bash 264 | drv=$(nix-instantiate ./code/06-infrastructure/haskell-project-v4/nix/01-gitignore-src/shell.nix) 265 | 266 | $ nix-store -qR --include-outputs $drv | grep haskell-project-src 267 | ``` 268 | 269 | We first use `nix-shell --run true $drv` to build only the dependencies of our shell derivation and 270 | push them to Cachix. 271 | 272 | ```bash 273 | $ nix-shell --run true $drv && nix-store -qR --include-outputs $drv | grep -v .drv | cachix push $CACHIX_STORE 274 | ... 275 | All done. 276 | ``` 277 | 278 | If we want to cache the final build artifact as well, we can still run `nix-build $drv` and 279 | then push _only_ the build output to Cachix. 280 | 281 | ```bash 282 | $ nix-build ./code/06-infrastructure/haskell-project-v4/nix/01-gitignore-src | cachix push $CACHIX_STORE 283 | ... 284 | compressing and pushing /nix/store/9in65nlw9s255x8zh5g7hlvbnl23rqbz-haskell-project-exe-hello-0.1.0.0 (3.60 MiB) 285 | All done. 286 | ``` 287 | 288 | ## Double Check Leaking with Code Changes 289 | 290 | Our attempt to cache only the Nix shell derivation seems to exclude the source code, 291 | but is it really excluded? If we are not careful, we could easily let Nix give a 292 | generic name like `source` to our source derivation. In that case it would not 293 | be possible to detect it through `grep` if our source code has leaked through. 294 | 295 | As a result, it is best to double check what is being cached by slightly modifying 296 | our source code, and then try pushing to Cachix again. 297 | 298 | ```bash 299 | $ sed -i 's/Hello, Haskell!/Hello, World!/g' ./code/06-infrastructure/haskell-project-v4/haskell/Main.hs 300 | $ cat ./code/06-infrastructure/haskell-project-v4/haskell/Main.hs 301 | module Main where 302 | 303 | main :: IO () 304 | main = putStrLn "Hello, World!" 305 | 306 | $ drv=$(nix-instantiate ./code/06-infrastructure/haskell-project-v4/nix/01-gitignore-src/shell.nix) 307 | $ nix-shell --run true $drv && nix-store -qR --include-outputs $drv | grep -v .drv | cachix push $CACHIX_STORE 308 | All done. 309 | 310 | $ nix-build ./code/06-infrastructure/haskell-project-v4/nix/01-gitignore-src | cachix push $CACHIX_STORE 311 | these derivations will be built: 312 | /nix/store/52qqdj4pq564ivyawpvfzsz2s3kv9wmp-haskell-project-exe-hello-0.1.0.0.drv 313 | ... 314 | compressing and pushing /nix/store/fdb6b3dj79gqff0lz0xf34lrs4gpb5a0-haskell-project-exe-hello-0.1.0.0 (3.60 MiB) 315 | All done. 316 | ``` 317 | 318 | As we expect, even though `Main.hs` has been modified, there is no new source 319 | artifact being pushed to Cachix. Only `nix-build` produced a new binary, which 320 | is then pushed to Cachix. 321 | 322 | You can apply the same method on your own project to double check if your 323 | source code is leaking to Cachix. Even if you do not care about the source 324 | code leaking, this can still serve as a good way to check if any secret 325 | is leaking. 326 | 327 | ## Caching Multiple Projects 328 | 329 | The technique for caching Nix shell can only work if we have projects made of a 330 | single Nix derivation. If we instead have a large project with multiple source 331 | repositories, it is much harder to filter out the source code if the derivations 332 | depend on each others. 333 | 334 | In such cases, the simple way is to use `grep -v` and hope that it can filter 335 | out all the source derivations. Otherwise you may need to use project-specific 336 | techniques to make sure that only intended Nix artifacts are being cached. 337 | 338 | ## Conclusion 339 | 340 | As we seen in this chapter, caching build results is not as straighforward if 341 | there are things that we want to _prevent_ from being cached, such as proprietary 342 | source code or local secrets. This is probably not a big issue right now, because 343 | many people may not even be aware that their source code and secrets are leaking! 344 | 345 | Even without considering leaking secrets, there are still too many different ways 346 | of caching build results in Nix. While this provides more flexibility for us 347 | to control what to cache, the learning curve is way too high for new users 348 | who just want to get their Nix builds cached. 349 | 350 | Nix and Cachix may need to implement additional features to help make caching 351 | easier, and to protect sensitive data. For example, Cachix may add a command 352 | line option to exclude paths matching specific pattern to never be pushed. 353 | -------------------------------------------------------------------------------- /code/06-infrastructure/haskell-project-v4/haskell/.gitignore: -------------------------------------------------------------------------------- 1 | dist-newstyle/ 2 | secret.key 3 | -------------------------------------------------------------------------------- /code/06-infrastructure/haskell-project-v4/haskell/Main.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | main :: IO () 4 | main = putStrLn "Hello, Haskell!" 5 | -------------------------------------------------------------------------------- /code/06-infrastructure/haskell-project-v4/haskell/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /code/06-infrastructure/haskell-project-v4/haskell/cabal.project: -------------------------------------------------------------------------------- 1 | packages: . 2 | -------------------------------------------------------------------------------- /code/06-infrastructure/haskell-project-v4/haskell/haskell-project.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 2.4 2 | name: haskell-project 3 | version: 0.1.0.0 4 | license: ISC 5 | build-type: Simple 6 | 7 | executable hello 8 | main-is: Main.hs 9 | build-depends: base >=4.13 10 | , QuickCheck == 2.13.2 11 | default-language: Haskell2010 12 | -------------------------------------------------------------------------------- /code/06-infrastructure/haskell-project-v4/nix/01-gitignore-src/default.nix: -------------------------------------------------------------------------------- 1 | let 2 | project = import ./project.nix { 3 | useMaterialization = true; 4 | }; 5 | in 6 | project.haskell-project.components.exes.hello 7 | -------------------------------------------------------------------------------- /code/06-infrastructure/haskell-project-v4/nix/01-gitignore-src/plan-hash.txt: -------------------------------------------------------------------------------- 1 | 1i7pl65lx8zp9n8rcg8d6x7bqnapdw6mgmz9q728wgkkjn484swa 2 | -------------------------------------------------------------------------------- /code/06-infrastructure/haskell-project-v4/nix/01-gitignore-src/plan/.plan.nix/haskell-project.nix: -------------------------------------------------------------------------------- 1 | { system 2 | , compiler 3 | , flags 4 | , pkgs 5 | , hsPkgs 6 | , pkgconfPkgs 7 | , errorHandler 8 | , config 9 | , ... }: 10 | { 11 | flags = {}; 12 | package = { 13 | specVersion = "2.4"; 14 | identifier = { name = "haskell-project"; version = "0.1.0.0"; }; 15 | license = "ISC"; 16 | copyright = ""; 17 | maintainer = ""; 18 | author = ""; 19 | homepage = ""; 20 | url = ""; 21 | synopsis = ""; 22 | description = ""; 23 | buildType = "Simple"; 24 | isLocal = true; 25 | detailLevel = "FullDetails"; 26 | licenseFiles = []; 27 | dataDir = ""; 28 | dataFiles = []; 29 | extraSrcFiles = []; 30 | extraTmpFiles = []; 31 | extraDocFiles = []; 32 | }; 33 | components = { 34 | exes = { 35 | "hello" = { 36 | depends = [ 37 | (hsPkgs."base" or (errorHandler.buildDepError "base")) 38 | (hsPkgs."QuickCheck" or (errorHandler.buildDepError "QuickCheck")) 39 | ]; 40 | buildable = true; 41 | mainPath = [ "Main.hs" ]; 42 | }; 43 | }; 44 | }; 45 | } // rec { src = (pkgs.lib).mkDefault ../.; } -------------------------------------------------------------------------------- /code/06-infrastructure/haskell-project-v4/nix/01-gitignore-src/plan/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs = hackage: 3 | { 4 | packages = { 5 | "ghc-prim".revision = (((hackage."ghc-prim")."0.6.1").revisions).default; 6 | "mtl".revision = (((hackage."mtl")."2.2.2").revisions).default; 7 | "rts".revision = (((hackage."rts")."1.0").revisions).default; 8 | "QuickCheck".revision = (((hackage."QuickCheck")."2.14.2").revisions).default; 9 | "QuickCheck".flags.templatehaskell = true; 10 | "QuickCheck".flags.old-random = false; 11 | "deepseq".revision = (((hackage."deepseq")."1.4.4.0").revisions).default; 12 | "random".revision = (((hackage."random")."1.2.0").revisions).default; 13 | "splitmix".revision = (((hackage."splitmix")."0.1.0.3").revisions).default; 14 | "splitmix".flags.optimised-mixer = false; 15 | "template-haskell".revision = (((hackage."template-haskell")."2.16.0.0").revisions).default; 16 | "containers".revision = (((hackage."containers")."0.6.2.1").revisions).default; 17 | "bytestring".revision = (((hackage."bytestring")."0.10.10.0").revisions).default; 18 | "base".revision = (((hackage."base")."4.14.1.0").revisions).default; 19 | "transformers".revision = (((hackage."transformers")."0.5.6.2").revisions).default; 20 | "pretty".revision = (((hackage."pretty")."1.1.3.6").revisions).default; 21 | "ghc-boot-th".revision = (((hackage."ghc-boot-th")."8.10.2").revisions).default; 22 | "array".revision = (((hackage."array")."0.5.4.0").revisions).default; 23 | "integer-gmp".revision = (((hackage."integer-gmp")."1.0.3.0").revisions).default; 24 | }; 25 | compiler = { 26 | version = "8.10.2"; 27 | nix-name = "ghc8102"; 28 | packages = { 29 | "ghc-prim" = "0.6.1"; 30 | "mtl" = "2.2.2"; 31 | "rts" = "1.0"; 32 | "deepseq" = "1.4.4.0"; 33 | "template-haskell" = "2.16.0.0"; 34 | "containers" = "0.6.2.1"; 35 | "bytestring" = "0.10.10.0"; 36 | "base" = "4.14.1.0"; 37 | "transformers" = "0.5.6.2"; 38 | "pretty" = "1.1.3.6"; 39 | "ghc-boot-th" = "8.10.2"; 40 | "array" = "0.5.4.0"; 41 | "integer-gmp" = "1.0.3.0"; 42 | }; 43 | }; 44 | }; 45 | extras = hackage: 46 | { packages = { haskell-project = ./.plan.nix/haskell-project.nix; }; }; 47 | modules = [ 48 | ({ lib, ... }: 49 | { packages = { "haskell-project" = { flags = {}; }; }; }) 50 | ]; 51 | } -------------------------------------------------------------------------------- /code/06-infrastructure/haskell-project-v4/nix/01-gitignore-src/project.nix: -------------------------------------------------------------------------------- 1 | { useMaterialization ? true }: 2 | let 3 | sources = import ../sources.nix {}; 4 | 5 | haskell-nix = import sources."haskell.nix" {}; 6 | 7 | nixpkgs = haskell-nix.pkgs; 8 | 9 | gitignore = (import sources."gitignore.nix" { 10 | inherit (nixpkgs) lib; 11 | }).gitignoreSource; 12 | 13 | src = nixpkgs.lib.cleanSourceWith { 14 | name = "haskell-project-src"; 15 | src = gitignore ../../haskell; 16 | }; 17 | 18 | project = nixpkgs.haskell-nix.cabalProject { 19 | inherit src; 20 | 21 | compiler-nix-name = "ghc8102"; 22 | 23 | index-state = "2020-12-04T00:00:00Z"; 24 | 25 | materialized = if useMaterialization 26 | then ./plan else null; 27 | 28 | plan-sha256 = if useMaterialization 29 | then nixpkgs.lib.removeSuffix "\n" 30 | (builtins.readFile ./plan-hash.txt) 31 | else null; 32 | 33 | exactDeps = true; 34 | }; 35 | in 36 | project 37 | -------------------------------------------------------------------------------- /code/06-infrastructure/haskell-project-v4/nix/01-gitignore-src/shell.nix: -------------------------------------------------------------------------------- 1 | { useMaterialization ? true }: 2 | let 3 | project = import ./project.nix { 4 | inherit useMaterialization; 5 | }; 6 | in 7 | project.shellFor { 8 | withHoogle = false; 9 | } 10 | -------------------------------------------------------------------------------- /code/06-infrastructure/haskell-project-v4/nix/01-gitignore-src/sync-materialized.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | plan=$(nix-build -j4 --no-out-link --arg useMaterialization false -A plan-nix project.nix) 4 | 5 | rm -rf plan 6 | 7 | cp -r $plan plan 8 | 9 | find plan -type d -exec chmod 755 {} \; 10 | 11 | nix-hash --base32 --type sha256 plan > plan-hash.txt 12 | -------------------------------------------------------------------------------- /code/06-infrastructure/haskell-project-v4/nix/sources.json: -------------------------------------------------------------------------------- 1 | { 2 | "gitignore.nix": { 3 | "branch": "master", 4 | "description": "Nix function for filtering local git sources", 5 | "homepage": "", 6 | "owner": "hercules-ci", 7 | "repo": "gitignore.nix", 8 | "rev": "c4662e662462e7bf3c2a968483478a665d00e717", 9 | "sha256": "1npnx0h6bd0d7ql93ka7azhj40zgjp815fw2r6smg8ch9p7mzdlx", 10 | "type": "tarball", 11 | "url": "https://github.com/hercules-ci/gitignore.nix/archive/c4662e662462e7bf3c2a968483478a665d00e717.tar.gz", 12 | "url_template": "https://github.com///archive/.tar.gz" 13 | }, 14 | "haskell.nix": { 15 | "branch": "master", 16 | "description": "Alternative Haskell Infrastructure for Nixpkgs", 17 | "homepage": "https://input-output-hk.github.io/haskell.nix", 18 | "owner": "input-output-hk", 19 | "repo": "haskell.nix", 20 | "rev": "180779b7f530dcd2a45c7d00541f0f3e3d8471b5", 21 | "sha256": "0y5r0k5wmn1zcip52ifkdy8vpj252q21fzqcy1hgxq9a6n2mh3yi", 22 | "type": "tarball", 23 | "url": "https://github.com/input-output-hk/haskell.nix/archive/180779b7f530dcd2a45c7d00541f0f3e3d8471b5.tar.gz", 24 | "url_template": "https://github.com///archive/.tar.gz" 25 | }, 26 | "niv": { 27 | "branch": "master", 28 | "description": "Easy dependency management for Nix projects", 29 | "homepage": "https://github.com/nmattia/niv", 30 | "owner": "nmattia", 31 | "repo": "niv", 32 | "rev": "ba57d5a29b4e0f2085917010380ef3ddc3cf380f", 33 | "sha256": "1kpsvc53x821cmjg1khvp1nz7906gczq8mp83664cr15h94sh8i4", 34 | "type": "tarball", 35 | "url": "https://github.com/nmattia/niv/archive/ba57d5a29b4e0f2085917010380ef3ddc3cf380f.tar.gz", 36 | "url_template": "https://github.com///archive/.tar.gz" 37 | }, 38 | "nixpkgs": { 39 | "branch": "nixpkgs-unstable", 40 | "description": "Nix Packages collection", 41 | "homepage": "", 42 | "owner": "NixOS", 43 | "repo": "nixpkgs", 44 | "rev": "c1e5f8723ceb684c8d501d4d4ae738fef704747e", 45 | "sha256": "02k3l9wnwpmq68xmmfy4wb2panqa1rs04p1mzh2kiwn0449hl86j", 46 | "type": "tarball", 47 | "url": "https://github.com/NixOS/nixpkgs/archive/c1e5f8723ceb684c8d501d4d4ae738fef704747e.tar.gz", 48 | "url_template": "https://github.com///archive/.tar.gz" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /code/06-infrastructure/haskell-project-v4/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: name: spec: 10 | let 11 | name' = sanitizeName name + "-src"; 12 | in 13 | if spec.builtin or true then 14 | builtins_fetchurl { inherit (spec) url sha256; name = name'; } 15 | else 16 | pkgs.fetchurl { inherit (spec) url sha256; name = name'; }; 17 | 18 | fetch_tarball = pkgs: name: spec: 19 | let 20 | name' = sanitizeName name + "-src"; 21 | in 22 | if spec.builtin or true then 23 | builtins_fetchTarball { name = name'; inherit (spec) url sha256; } 24 | else 25 | pkgs.fetchzip { name = name'; inherit (spec) url sha256; }; 26 | 27 | fetch_git = name: spec: 28 | let 29 | ref = 30 | if spec ? ref then spec.ref else 31 | if spec ? branch then "refs/heads/${spec.branch}" else 32 | if spec ? tag then "refs/tags/${spec.tag}" else 33 | abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!"; 34 | in 35 | builtins.fetchGit { url = spec.repo; inherit (spec) rev; inherit ref; }; 36 | 37 | fetch_local = spec: spec.path; 38 | 39 | fetch_builtin-tarball = name: throw 40 | ''[${name}] The niv type "builtin-tarball" is deprecated. You should instead use `builtin = true`. 41 | $ niv modify ${name} -a type=tarball -a builtin=true''; 42 | 43 | fetch_builtin-url = name: throw 44 | ''[${name}] The niv type "builtin-url" will soon be deprecated. You should instead use `builtin = true`. 45 | $ niv modify ${name} -a type=file -a builtin=true''; 46 | 47 | # 48 | # Various helpers 49 | # 50 | 51 | # https://github.com/NixOS/nixpkgs/pull/83241/files#diff-c6f540a4f3bfa4b0e8b6bafd4cd54e8bR695 52 | sanitizeName = name: 53 | ( 54 | concatMapStrings (s: if builtins.isList s then "-" else s) 55 | ( 56 | builtins.split "[^[:alnum:]+._?=-]+" 57 | ((x: builtins.elemAt (builtins.match "\\.*(.*)" x) 0) name) 58 | ) 59 | ); 60 | 61 | # The set of packages used when specs are fetched using non-builtins. 62 | mkPkgs = sources: system: 63 | let 64 | sourcesNixpkgs = 65 | import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) { inherit system; }; 66 | hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath; 67 | hasThisAsNixpkgsPath = == ./.; 68 | in 69 | if builtins.hasAttr "nixpkgs" sources 70 | then sourcesNixpkgs 71 | else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then 72 | import {} 73 | else 74 | abort 75 | '' 76 | Please specify either (through -I or NIX_PATH=nixpkgs=...) or 77 | add a package called "nixpkgs" to your sources.json. 78 | ''; 79 | 80 | # The actual fetching function. 81 | fetch = pkgs: name: spec: 82 | 83 | if ! builtins.hasAttr "type" spec then 84 | abort "ERROR: niv spec ${name} does not have a 'type' attribute" 85 | else if spec.type == "file" then fetch_file pkgs name spec 86 | else if spec.type == "tarball" then fetch_tarball pkgs name spec 87 | else if spec.type == "git" then fetch_git name spec 88 | else if spec.type == "local" then fetch_local spec 89 | else if spec.type == "builtin-tarball" then fetch_builtin-tarball name 90 | else if spec.type == "builtin-url" then fetch_builtin-url name 91 | else 92 | abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}"; 93 | 94 | # If the environment variable NIV_OVERRIDE_${name} is set, then use 95 | # the path directly as opposed to the fetched source. 96 | replace = name: drv: 97 | let 98 | saneName = stringAsChars (c: if isNull (builtins.match "[a-zA-Z0-9]" c) then "_" else c) name; 99 | ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}"; 100 | in 101 | if ersatz == "" then drv else ersatz; 102 | 103 | # Ports of functions for older nix versions 104 | 105 | # a Nix version of mapAttrs if the built-in doesn't exist 106 | mapAttrs = builtins.mapAttrs or ( 107 | f: set: with builtins; 108 | listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set)) 109 | ); 110 | 111 | # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295 112 | range = first: last: if first > last then [] else builtins.genList (n: first + n) (last - first + 1); 113 | 114 | # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257 115 | stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1)); 116 | 117 | # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269 118 | stringAsChars = f: s: concatStrings (map f (stringToCharacters s)); 119 | concatMapStrings = f: list: concatStrings (map f list); 120 | concatStrings = builtins.concatStringsSep ""; 121 | 122 | # https://github.com/NixOS/nixpkgs/blob/8a9f58a375c401b96da862d969f66429def1d118/lib/attrsets.nix#L331 123 | optionalAttrs = cond: as: if cond then as else {}; 124 | 125 | # fetchTarball version that is compatible between all the versions of Nix 126 | builtins_fetchTarball = { url, name ? null, sha256 }@attrs: 127 | let 128 | inherit (builtins) lessThan nixVersion fetchTarball; 129 | in 130 | if lessThan nixVersion "1.12" then 131 | fetchTarball ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; })) 132 | else 133 | fetchTarball attrs; 134 | 135 | # fetchurl version that is compatible between all the versions of Nix 136 | builtins_fetchurl = { url, name ? null, sha256 }@attrs: 137 | let 138 | inherit (builtins) lessThan nixVersion fetchurl; 139 | in 140 | if lessThan nixVersion "1.12" then 141 | fetchurl ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; })) 142 | else 143 | fetchurl attrs; 144 | 145 | # Create the final "sources" from the config 146 | mkSources = config: 147 | mapAttrs ( 148 | name: spec: 149 | if builtins.hasAttr "outPath" spec 150 | then abort 151 | "The values in sources.json should not have an 'outPath' attribute" 152 | else 153 | spec // { outPath = replace name (fetch config.pkgs name spec); } 154 | ) config.sources; 155 | 156 | # The "config" used by the fetchers 157 | mkConfig = 158 | { sourcesFile ? if builtins.pathExists ./sources.json then ./sources.json else null 159 | , sources ? if isNull sourcesFile then {} else builtins.fromJSON (builtins.readFile sourcesFile) 160 | , system ? builtins.currentSystem 161 | , pkgs ? mkPkgs sources system 162 | }: rec { 163 | # The sources, i.e. the attribute set of spec name to spec 164 | inherit sources; 165 | 166 | # The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers 167 | inherit pkgs; 168 | }; 169 | 170 | in 171 | mkSources (mkConfig {}) // { __functor = _: settings: mkSources (mkConfig settings); } 172 | -------------------------------------------------------------------------------- /code/06-infrastructure/images/create-cache.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrive/nix-workshop/a713625698b01127709f298a2f79fa86530ecbfc/code/06-infrastructure/images/create-cache.png -------------------------------------------------------------------------------- /code/06-infrastructure/images/create-token-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrive/nix-workshop/a713625698b01127709f298a2f79fa86530ecbfc/code/06-infrastructure/images/create-token-2.png -------------------------------------------------------------------------------- /code/06-infrastructure/images/create-token.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrive/nix-workshop/a713625698b01127709f298a2f79fa86530ecbfc/code/06-infrastructure/images/create-token.png -------------------------------------------------------------------------------- /code/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | - [Scrive Nix Workshop](./index.md) 4 | 5 | # Getting Started 6 | 7 | - [Introduction](./01-getting-started/01-introduction.md) 8 | 9 | - [Installation](./01-getting-started/02-installation.md) 10 | 11 | - [Resources](./01-getting-started/03-resources.md) 12 | 13 | # Nix Commands 14 | 15 | - [Installing Global Packages](./02-nix-commands/01-install-global-packages.md) 16 | 17 | - [Using Packages in Nix shell](./02-nix-commands/02-use-packages-in-nix-shell.md) 18 | 19 | - [Nix Repl](./02-nix-commands/03-nix-repl.md) 20 | 21 | # Nix Basics 22 | 23 | - [Nix Primitives](./03-nix-basics/01-primitives.md) 24 | 25 | - [Expressions](./03-nix-basics/02-expressions.md) 26 | 27 | - [Files](./03-nix-basics/03-files.md) 28 | 29 | - [Importing Modules](./03-nix-basics/04-import.md) 30 | 31 | # Derivations 32 | 33 | - [Derivation Basics](./04-derivations/01-derivation-basics.md) 34 | 35 | - [Dependencies](./04-derivations/02-dependencies.md) 36 | 37 | - [Fibonacci](./04-derivations/03-fibonacci.md) 38 | 39 | - [Raw Derivation](./04-derivations/04-raw-derivation.md) 40 | 41 | - [Standard Derivation](./04-derivations/05-standard-derivation.md) 42 | 43 | - [Build Phases](./04-derivations/06-build-phases.md) 44 | 45 | # Package Management 46 | 47 | - [Dependency Management](./05-package-management/01-dependency-management.md) 48 | 49 | - [Basic Haskell Project](./05-package-management/02-basic-haskell.md) 50 | 51 | - [Version Conflicts](./05-package-management/03-version-conflicts.md) 52 | 53 | - [Transitive Version Conflicts](./05-package-management/04-transitive-version-conflicts.md) 54 | 55 | - [Multi-versioned Haskell Packages](./05-package-management/05-multi-versioned-haskell-packages.md) 56 | 57 | - [Other Package Management Strategies](./05-package-management/06-other-strategies.md) 58 | 59 | # Infrastructure 60 | 61 | - [Caching Nix Packages](./06-infrastructure/01-caching-nix.md). 62 | 63 | - [Caching Haskell Nix Packages](./06-infrastructure/02-caching-haskell.md) 64 | -------------------------------------------------------------------------------- /code/index.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /code/nixpkgs.nix: -------------------------------------------------------------------------------- 1 | let 2 | nixpkgs-src = builtins.fetchTarball { 3 | url = "https://github.com/NixOS/nixpkgs/archive/fcc81bc974fabd86991b8962bd30a47eb43e7d34.tar.gz"; 4 | sha256 = "1ysjmn79pl7srlzgfr35nsxq43rm1va8dqp60h09nlmw2fsq9zrc"; 5 | }; 6 | 7 | nixpkgs = import nixpkgs-src {}; 8 | in 9 | nixpkgs 10 | -------------------------------------------------------------------------------- /config/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scrive/nix-workshop/a713625698b01127709f298a2f79fa86530ecbfc/config/.keep -------------------------------------------------------------------------------- /nix.conf: -------------------------------------------------------------------------------- 1 | sandbox = true 2 | substituters = https://cache.nixos.org https://hydra.iohk.io 3 | trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= hydra.iohk.io:f/Ea+s+dFdN+3Y/G+FDgSq+a5NEWhJGzdjvKNGv0/EQ= 4 | -------------------------------------------------------------------------------- /scripts/docker.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euxo pipefail 4 | 5 | docker build \ 6 | --tag nix-workshop \ 7 | --build-arg UID=$(id -u) \ 8 | --build-arg GID=$(id -g) \ 9 | . 10 | 11 | docker run --rm -it \ 12 | -v "$(pwd):/home/nix/nix-workshop" \ 13 | nix-workshop \ 14 | /bin/bash --login 15 | -------------------------------------------------------------------------------- /scripts/setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | CACHIX_STORE=$(cat config/cachix-store) 4 | cachix use $CACHIX_STORE 5 | cachix authtoken $(cat config/cachix-token) 6 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | let 2 | nixpkgs = import ./code/nixpkgs.nix; 3 | in 4 | nixpkgs.mkShell { 5 | buildInputs = [ 6 | nixpkgs.mdbook 7 | ]; 8 | } -------------------------------------------------------------------------------- /theme/highlight.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Railscasts-like style (c) Visoft, Inc. (Damien White) 4 | 5 | */ 6 | 7 | :root { 8 | --inline-code-color: #e6e1dc; 9 | } 10 | 11 | .hljs { 12 | display: block; 13 | overflow-x: auto; 14 | padding: 0.5em; 15 | background: #232323; 16 | color: #e6e1dc; 17 | } 18 | 19 | .hljs-comment, 20 | .hljs-quote { 21 | color: #bc9458; 22 | font-style: italic; 23 | } 24 | 25 | .hljs-keyword, 26 | .hljs-selector-tag { 27 | color: #c26230; 28 | } 29 | 30 | .hljs-string, 31 | .hljs-number, 32 | .hljs-regexp, 33 | .hljs-variable, 34 | .hljs-template-variable { 35 | color: #a5c261; 36 | } 37 | 38 | .hljs-subst { 39 | color: #519f50; 40 | } 41 | 42 | .hljs-tag, 43 | .hljs-name { 44 | color: #e8bf6a; 45 | } 46 | 47 | .hljs-type { 48 | color: #da4939; 49 | } 50 | 51 | 52 | .hljs-symbol, 53 | .hljs-bullet, 54 | .hljs-built_in, 55 | .hljs-builtin-name, 56 | .hljs-attr, 57 | .hljs-link { 58 | color: #6d9cbe; 59 | } 60 | 61 | .hljs-params { 62 | color: #d0d0ff; 63 | } 64 | 65 | .hljs-attribute { 66 | color: #cda869; 67 | } 68 | 69 | .hljs-meta { 70 | color: #9b859d; 71 | } 72 | 73 | .hljs-title, 74 | .hljs-section { 75 | color: #ffc66d; 76 | } 77 | 78 | .hljs-addition { 79 | background-color: #144212; 80 | color: #e6e1dc; 81 | display: inline-block; 82 | width: 100%; 83 | } 84 | 85 | .hljs-deletion { 86 | background-color: #600; 87 | color: #e6e1dc; 88 | display: inline-block; 89 | width: 100%; 90 | } 91 | 92 | .hljs-selector-class { 93 | color: #9b703f; 94 | } 95 | 96 | .hljs-selector-id { 97 | color: #8b98ab; 98 | } 99 | 100 | .hljs-emphasis { 101 | font-style: italic; 102 | } 103 | 104 | .hljs-strong { 105 | font-weight: bold; 106 | } 107 | 108 | .hljs-link { 109 | text-decoration: underline; 110 | } 111 | --------------------------------------------------------------------------------