Lightning fast CI for Haskell projects

I’ve been working in a few projects at a time in Haskell for the past year, and one point that has been dragging is how much time it takes for a CI job to finish, given this, I started to experiment with other solutions to improve my build feedback loop.

As of a few weeks, my main goto for CI was TravisCI. There is a lot of Work done by others around how to build and test Haskell projects for MacOS, Linux, multiple stackage snapshots, etc. I was however never satisfied with how TravisCI wasted so much time doing the same things over and over again. Some of the builds would take a 20 minute time just to finish, stopping my feedback loop towards quickly closing PRs I was working at the time.

Looking for alternatives

After going through some alternatives, I landed at CircleCI via a tweet from them:

After I started experimenting with CircleCI, there were many things that I liked:

It allows me to specify my docker images as builders - This is huge, I know most of the time spent in TravisCI is compiling and installing tools and a minimal amount of time was testing my code. By giving me the opportunity to build images with those tools baked in, I could start a PR build from an environment that didn’t spend time compiling the stuff I used to test my libraries.

It allows you to run several jobs in parallel, and not just that, but also enables to build pipelines. This feature is excellent, I can keep my compilation matrix going, and not just that, I can add other jobs to release my software. I haven’t built an open-source app just yet, but I will keep this in mind for that.

I got builds to become 95% faster when the build uses a previous cached output (yes, that’s ~2 minutes instead of ~40 minutes). With this flow, I can do quick tweaks to my PR builds and merge them quickly.

Execution of TravisCI

Execution of CircleCI

What I use to build my software

I rely on two docker images:

romanandreg/haskell-tools:latest - This image contains hlint , stylish-haskell , brittany and refactor binaries to run all those tools as part of my CI pipeline.

romanandreg/stack:latest - It contains a stack binary and a cabal-1.24 binary (to run the resolver), and I used this image to build my project in various snapshots.

My CircleCI config file makes use of this docker images that have all the tools I need to build my project.

How I build my project

I heavily rely on Makefile s to build my projects. Why? I do this because I want to be able to run my CI scripts on my local machine. The CI scripts are no different from what I would use on local development.

But seriously, why Makefile and no other tool? Makefile s are the best-supported tool on Unix systems, you have it available everywhere, and it does its job very well; granted the syntax is awful, but I have learned to live with that.

Organization of my Makefile s

After much working with Makefile s, I’ve learned that is a bad idea to keep all your tasks in a single file, this way I cannot re-use well-defined tasks in many projects; re-usability suffers when I need to modify the files per project details. If some common tasks are self-contained in a file, I have to copy/paste that into a new project, and voila, things work as I need them to work. The files I’m currently using:

make/tools.make - Contains all tasks related to linting and formatting the code of a library

make/sdist.make - Contains tasks to build a release of the library, and test it with the stack nightly version

make/solver.make - Contains tasks to execute the resolver so that I can modify my stack.yaml file to support other snapshots dynamically.

I’m planning to add other Makefile s as I go, to script out the cabal-install setup for testing my projects with cabal-install. This functionality is something I need to port from my previous TravisCI build.

Where can you find an example

I believe the best two projects that are implementing this approach are teardown and etc , just go through their .circleci/config.yml file and their Makefile s.

Should you try CircleCI?

I highly recommend spending the time to try CircleCI, if you are typically waiting for a PR build to finish to push a new release up, this tool will provide a fantastic feedback loop for your workflow.