|
| 1 | +--- |
| 2 | +slug: 2023-07-14-devx |
| 3 | +title: The IOG's Developer Experience Shell |
| 4 | +authors: [yvan] |
| 5 | +tags: [devx, haskell.nix, hix, iogx, nix] |
| 6 | +--- |
| 7 | + |
| 8 | +# The IOG's Developer Experience Shell |
| 9 | + |
| 10 | +The IOG's Developer Experience Shell, or `DevX` for short, is an opinionated development environment tailored for Haskell. |
| 11 | +Built on top of Nix expressions, it provides a reproducible and seamless, full-featured developer shell with tools such as `cabal-install`, `ghc`, `hls`, and `hlint`. |
| 12 | + |
| 13 | +The DevX shell's approach is a "one shell that fits all" solution for IOG's project needs. |
| 14 | +This article aims to give you a technical deep dive on how to use it in your own Haskell project and how we managed such a substantial closure in a convenient way. |
| 15 | + |
| 16 | +`DevX` is built upon `haskell.nix`, a framework for building Haskell packages with Nix. |
| 17 | +In short, `haskell.nix` turns your Cabal or Stack project and its dependencies into a Nix expression. |
| 18 | +We will explain how to use `haskell.nix` on its own using `hix`. |
| 19 | + |
| 20 | +We will conclude by introducing `iogx`, a Flake Template for Haskell Projects that we use at IOG. |
| 21 | + |
| 22 | +## Getting started |
| 23 | + |
| 24 | +The `devx` GitHub repository provides a comprehensive [README](https://github.com/input-output-hk/devx) that documents the different outputs that `flake.nix` provides. |
| 25 | +But, as this article assumes you might have not so much knowledge of how to deal with Nix, we will quickly discuss three ergonomic ways of using this tool with no Nix knowledge required. |
| 26 | + |
| 27 | +### Direnv |
| 28 | + |
| 29 | +`direnv` is an quite handy tool that allows you to load/unload the development environment automatically based on your current directory. |
| 30 | +It's an excellent way to avoid polluting your `.profile` or shell rc files. |
| 31 | + |
| 32 | +You can learn how to install `direnv` [here](https://direnv.net). |
| 33 | +You may then need to hook up direnv in your shell; you can consult the dedicated [`direnv` and `devx`](./docs/direnv.md) guide for detailed instructions on integration. |
| 34 | + |
| 35 | +You will also need to have Nix installed to using DevX shell. |
| 36 | +Then just add an `.envrc` file with the following content in the root of your project: |
| 37 | +``` |
| 38 | +# https://github.com/nix-community/nix-direnv A fast, persistent use_nix/use_flake implementation for direnv: |
| 39 | +if ! has nix_direnv_version || ! nix_direnv_version 2.3.0; then |
| 40 | + source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/2.3.0/direnvrc" "sha256-Dmd+j63L84wuzgyjITIfSxSD57Tx7v51DMxVZOsiUD8=" |
| 41 | +fi |
| 42 | +# https://github.com/input-output-hk/devx Slightly opinionated shared GitHub Action for Cardano-Haskell projects |
| 43 | +use flake "github:input-output-hk/devx#ghc8107" |
| 44 | +``` |
| 45 | +`DevX` supports a variety of compiler versions, typically the latest for each series from 8.10 to 9.6. Along with the core GHC version, [it also offers various flavours, denoted as suffixes to the compiler names](https://github.com/input-output-hk/devx#compilers-and-flavours). |
| 46 | + |
| 47 | +The `nix-direnv` prefix is not necessary, but it's a good way to prevent Nix from garbage collecting your shell while the directory exists! |
| 48 | + |
| 49 | +### GitHub Action |
| 50 | + |
| 51 | +We also provide a `DevX` GitHub Action, leveraging a unique technique of pre-evaluating the closure of the development environment, compressing it, and uploading it to `ghcr.io`. |
| 52 | +This approach drastically reduces the instantiation time of the developer shell, and will talk about it a bit more in details later in this article. |
| 53 | + |
| 54 | +Here's how you might utilize the DevX GitHub Action in your workflow: |
| 55 | +``` |
| 56 | +- name: Build |
| 57 | + uses: input-output-hk/actions/devx@latest |
| 58 | + with: |
| 59 | + platform: 'x86_64-linux' |
| 60 | + target-platform: '-windows' |
| 61 | + compiler-nix-name: 'ghc8107' |
| 62 | + minimal: false |
| 63 | + iog: true |
| 64 | +- name: Build |
| 65 | + shell: devx {0} |
| 66 | + run: | |
| 67 | + cabal update |
| 68 | + cabal build |
| 69 | +``` |
| 70 | + |
| 71 | +### VSCode DevContainer and GitHub CodeSpace |
| 72 | + |
| 73 | +Finally, we also offer support for VSCode DevContainers and GitHub CodeSpaces. |
| 74 | +To make the DevX developer shell available in a VSCode DevContainer or GitHub CodeSpace, simply add a file named `.devcontainer/devcontainer.json` with the following content: |
| 75 | +``` |
| 76 | +{ |
| 77 | + "image":"ghcr.io/input-output-hk/devx-devcontainer:ghc8107", |
| 78 | + "customizations":{ |
| 79 | + "vscode":{ |
| 80 | + "extensions":[ |
| 81 | + "haskell.haskell" |
| 82 | + ], |
| 83 | + "settings":{ |
| 84 | + "haskell.manageHLS":"PATH" |
| 85 | + } |
| 86 | + } |
| 87 | + } |
| 88 | +} |
| 89 | +``` |
| 90 | +You can follow the [Microsoft tutorial](https://code.visualstudio.com/docs/devcontainers/tutorial) to set-up your VSCode local DevContainer or you can give it a try by [opening a GitHub Codespace](https://codespaces.new/input-output-hk/cardano-base?quickstart=1) on [`cardano-base`](https://github.com/input-output-hk/cardano-base) repository! |
| 91 | + |
| 92 | +## Benchmarking |
| 93 | + |
| 94 | +To showcase the efficiency and speed that our download closure hack brings compared to a call to `nix develop`, we'll present a short benchmarking comparison. |
| 95 | + |
| 96 | +What isn't measured is the time for evaluating the closure, compressing it, and uploading it to `ghcr.io`, as it's done by our CI and represents the caching part of the process. |
| 97 | +We measure the speed of downloading, decompressing, and performing a `nix import` on the closure against the speed of `nix develop`, so we essentially save the time of the evaluation of the Nix expression: |
| 98 | + |
| 99 | +**TODO: add the benchmarks ...** |
| 100 | + |
| 101 | +## Hix |
| 102 | + |
| 103 | +Hix is a command-line tool designed to facilitate the addition of `haskell.nix` support to existing Haskell projects. |
| 104 | +You need Nix installed and configured with `experimental-features = [ "nix-command" "flakes" ];` (details can be found at the [NixOS Wiki](https://nixos.wiki/wiki/Flakes)). |
| 105 | + |
| 106 | +### Initializing Hix and Using Nix |
| 107 | + |
| 108 | +The `hix init` command is used to add a `flake.nix` and `nix/hix.nix` file to the project. |
| 109 | + |
| 110 | +Once this is done, you can utilize regular Nix tools. |
| 111 | +For instance, to run `cabal build` on the `hello` package from Hackage, you would execute the following: |
| 112 | +```shell |
| 113 | +cabal unpack hello |
| 114 | +cd hello-1.0.0.2 |
| 115 | +nix run "github:input-output-hk/haskell.nix#hix" -- init |
| 116 | +nix develop |
| 117 | +cabal build |
| 118 | +``` |
| 119 | + |
| 120 | +You can view the contents of the flake using `nix flake show`. |
| 121 | +To build a component with Nix, use `nix build .#hello:exe:hello`, and to build and run a component, `nix run .#hello:exe:hello` would be the command of choice. |
| 122 | + |
| 123 | +### Installing Hix |
| 124 | + |
| 125 | +Hix can be installed with the following command: |
| 126 | + |
| 127 | +```shell |
| 128 | +nix-env -iA hix -f https://github.com/input-output-hk/haskell.nix/tarball/master |
| 129 | +``` |
| 130 | + |
| 131 | +To update Hix to the latest version, simply run `hix update`. |
| 132 | + |
| 133 | +### Using Hix Commands |
| 134 | + |
| 135 | +Commands like `hix develop`, `hix flake`, `hix build`, and `hix run` work similarly to their Nix counterparts. |
| 136 | +However, instead of utilizing the `flake.nix`, a boilerplate Haskell.nix `flake.nix` file is added to `.hix-flake/flake.nix`. |
| 137 | +This approach can be beneficial if the project already includes a `flake.nix` or if there's no intention to maintain one. |
| 138 | + |
| 139 | +Once this step is done, you can execute commands without the need for `hix init`: |
| 140 | + |
| 141 | +```shell |
| 142 | +hix develop |
| 143 | +hix flake show |
| 144 | +hix build .#hello:exe:hello |
| 145 | +hix run .#hello:exe:hello |
| 146 | +``` |
| 147 | + |
| 148 | +### Using `hix-shell` and `hix-build` |
| 149 | + |
| 150 | +The `hix-shell` and `hix-build` commands emulate the behaviour of `nix-build` and `hix-shell` if a boilerplate `default.nix` and `shell.nix` were present. |
| 151 | +These commands are executed as follows: |
| 152 | +```shell |
| 153 | +hix-shell --run 'cabal build all' |
| 154 | +hix-build -A hsPkgs.hello.components.exes.hello |
| 155 | +``` |
| 156 | + |
| 157 | +Hix streamlines the use of Haskell.nix, offering an easier way to integrate it into your existing Haskell projects. |
| 158 | + |
| 159 | +## IOGX: A Flake Template for Haskell Projects at IOG |
| 160 | + |
| 161 | +IOGX is a flake template that offers a development experience for Haskell projects at IOG. Its vision is to provide a JSON-like, declarative interface to Nix, enabling developers unfamiliar with the Nix language to maintain and enhance the Nix sources independently with minimal effort. |
| 162 | + |
| 163 | +### Getting Started with IOGX |
| 164 | + |
| 165 | +Kick-start your project with IOGX by running the following command: |
| 166 | +``` |
| 167 | +nix flake init --template github:input-output-hk/iogx |
| 168 | +``` |
| 169 | +This command generates a `flake.nix` and a `nix` folder containing various file templates. |
| 170 | +These files form the *filesystem-based* API of IOGX. |
| 171 | + |
| 172 | +The next steps involve populating the templates in the `nix` folder, leaving `flake.nix` mostly untouched. |
| 173 | +Based on the contents of the `nix` folder, IOGX populates your flake outputs. |
| 174 | + |
| 175 | +### IOGX Features |
| 176 | + |
| 177 | +IOGX comes packed with numerous features that enhance the Haskell development process: |
| 178 | + |
| 179 | +- **GHC Build Matrices**: IOGX enables defining a set of GHC versions, for each of which it generates `devShells`, `packages`, `apps`, `checks`, and `hydraJobs`. These outputs also include profiled builds and Windows cross-compiled builds. |
| 180 | + |
| 181 | +- **Extensible Development Shells**: Every `devShell` comes with a complete Haskell toolchain that can be easily extended with new packages, custom scripts, environment variables, and hooks. |
| 182 | + |
| 183 | +- **Automatic Hydra Jobset**: By default, your `hydraJobs` will include every Haskell component in your project. Test suites will run in CI automatically. |
| 184 | + |
| 185 | +- **Easy Code Formatting**: IOGX employs [`pre-commit-hooks`](https://github.com/cachix/pre-commit-hooks.nix) for source tree formatting. These hooks are easily configurable and run automatically in CI unless explicitly disabled. |
| 186 | + |
| 187 | +- **Read The Docs Support**: If your project requires a [Read The Docs](https://readthedocs.org) site, IOGX will include the necessary tools and scripts and add the relevant derivations to CI. |
| 188 | + |
| 189 | +To delve deeper into IOGX and its API, refer to the comprehensive [API Reference](https://github.com/input-output-hk/iogx#3-api-reference). IOGX aims to facilitate a pleasant and efficient development process for Haskell projects at IOG, minimizing the need for extensive Nix language knowledge. |
| 190 | + |
| 191 | +## Final Thoughts |
| 192 | + |
| 193 | +**TODO ...** |
0 commit comments