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.
xmonad --recompile builds
and subsequent invocations of
xmonad run the executable that is produced.
When you configure xmonad you are actually writing your own version of the
Because you can write arbitrary code the possibilities for customization are
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
and I had problems getting ghc to find those when running
So I opted to set up a fully-fledged stack project which is built with the
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:
1 2 3 4 5 6 7
my-xmonad.cabal file declares an executable named
(which is actually my customized version of xmonad).
This script builds that executable,
installs the it to
(thanks to the
--local-bin-path argument to
and finally moves the executable to the location given by the first argument to
- Install xmonad and stack using your preferred package manager - you need xmonad v0.13 or later.
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
~/.xmonad/and then delete
my-xmonad/. The relevant files are:
my-xmonad.cabal, your project manifest
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.
stack setupto 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
If you want to add library dependencies beyond
then add them to the
build-depends section in the same file.
Stack pulls dependencies from Stackage,
which hosts curated sets of packages.
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!