Skip to content

## I finally set up XMonad to build with Stack!

2018-05-13 — XMonad — 3 min read

The XMonad window manager is configured in Haskell. That means that when you want to apply a new configuration you actually build xmonad itself incorporating code from your configuration file. It sounds more painful than it is - when you install xmonad you get an executable called `xmonad` that handles the details of bootstrapping your custom build. The command `xmonad --recompile` builds `~/.xmonad/xmonad.hs`, and subsequent invocations of `xmonad` run the executable that is produced.

When you configure xmonad you are actually writing your own version of the program. Because you can write arbitrary code the possibilities for customization are endless! As with any software project, you get maximum expressive power when you bring in third-party libraries. `xmonad-contrib` is a popular choice - but you can import any Haskell library that you want. With libraries come the problem of managing library packages. In the past I used the `cabal` command to globally install library packages. From time to time I would clear out my installed packages, or change something while working on another Haskell project, and then my window manager would stop working. I wanted a better option.

Stack is my preferred dependency management and build tool for Haskell projects. Stack automatically fetches project dependencies, and maintains isolated sets of installed packages for each project. With stack I declare dependencies in a .cabal file, and stack ensures that I have up-to-date copies of all libraries whenever xmonad builds itself.

To use stack I needed to hook into xmonad's build process. I used this blog post as a starting point. That post provides instructions for using the `stack ghc` command to invoke ghc in an environment prepared by stack. But I have some custom code modules in `~/.xmonad/lib/`, and I had problems getting ghc to find those when running `stack ghc`. So I opted to set up a fully-fledged stack project which is built with the usual `stack build` command. You can take a look at my ~/.xmonad/ directory to get the high-level view.

The key is the build script. Starting in xmonad v0.13 if there is an executable called `build` in your `~/.xmonad/` directory then xmonad will defer to that script. `build` gets a path as an argument which is where the compiled xmonad executable should be placed. My script looks like this:

    #!/bin/shset -estack build :my-xmonad --verbosity errorstack install :my-xmonad --local-bin-path bin/ --verbosity errormv bin/my-xmonad "$1"

My `my-xmonad.cabal` file declares an executable named `my-xmonad` (which is actually my customized version of xmonad). This script builds that executable, installs the it to `~/.xmonad/bin/my-xmonad` (thanks to the `--local-bin-path` argument to `stack install`), and finally moves the executable to the location given by the first argument to the `build` script.

You can detailed on working with stack from stack's user guide. If you want to get going quickly I created a stack project template to set up xmonad with stack. Here is what you do:

- Install xmonad and stack using your preferred package manager - you need xmonad v0.13 or later.
- Run `stack new my-xmonad https://raw.githubusercontent.com/hallettj/dot-xmonad/master/home/.xmonad/xmonad.hsfiles`
- If you are setting up a new xmonad configuration then `mv my-xmonad ~/.xmonad`. Otherwise copy files from `my-xmonad/` to `~/.xmonad/` and then delete `my-xmonad/`. The relevant files are:
  - `my-xmonad.cabal`, your project manifest
  - `build`
  - `lib/` - this directory must exist or the project will not build!
  - `xmonad.hs`, in case you do not have your own
  - `.gitignore`, in case you want to version-control your xmonad config
- `chmod a+x ~/.xmonad/build` - if the build script is not marked as executable xmonad will not execute it.
- In `~/.xmonad/` run `stack setup` to install the version of ghc that stack wants to use. (Stack installs ghc in a sandbox so that it does not conflict with any other ghc installation on your machine.)

Names of any custom modules that you have in `~/.xmonad/lib` need to be listed in the `other-modules` section in `~/.xmonad/my-xmonad.cabal`. If you want to add library dependencies beyond `xmonad` and `xmonad-contrib` then add them to the `build-depends` section in the same file. Stack pulls dependencies from Stackage, which hosts curated sets of packages. The `resolver` setting in `stack.yaml` determines the version of each dependency that you get, and the version of ghc that stack will use to compile xmonad. If you want to use libraries that are not hosted on Stackage you will need to list package names with exact version numbers in the `extra-deps` section in `stack.yaml` according to these instructions.

Test everything by running `xmonad --recompile` from any directory. If that works then you are all set!