Michael Walker

Continuous Integration with Jenkins and Stack

Playing around with the cool new Haskell build tool.
Published on

This weekend I decided to try my hand at setting up a personal continuous integration server using stack and Jenkins. Having not used either of these tools before, this blog post is mostly about my first impressions. More in-depth discussion of stack can be found here and here.

Stack

Stack is the cool new build tool on the block. I admit, when I heard of it my reaction was somewhat less than positive. I thought “sure, cabal has its issues, but with sandboxes now it’s pretty good.” Needless to say, later that evening I saw someone advised to try deleting and remaking a cabal sandbox to work around some issue.

So what do I know.

Having skimmed through the documentation, I felt I was ready to try stackifying my simplest project: my website. It’s a static site generated by Hakyll, there aren’t many dependencies and it’s not doing anything clever in the .cabal file, so it seemed the perfect choice to get my feet wet.

Firstly, stack needs configuring. That may sound obvious, but it was something I felt slightly dubious about. Another configuration file format? Well, fortunately stack can generate its configuration based on the .cabal file if there is one.

First impressions were less than stellar, I have to admit:

 >>>  stack build
Unable to find a stack.yaml file in the current directory (/home/barrucadu/projects/
barrucadu.co.uk/) or its ancestors
Recommended action: stack init

Huh, isn’t ‘saying you should do something but not doing it for you’ one of the issues with cabal that stack sets out to solve?

 :( >>>  stack init
Writing default config file to: /home/barrucadu/projects/barrucadu.co.uk/stack.yaml
Basing on cabal files:
- /home/barrucadu/projects/barrucadu.co.uk/barrucadu-co-uk.cabal

Downloaded lts-2.18 build plan.    
Caching build plan
Fetched package index.                                                                                    
Populated index cache.
Checking against build plan lts-2.18
Selected resolver: lts-2.18
Wrote project config to: /home/barrucadu/projects/barrucadu.co.uk/stack.yaml

Ok, it’s generated a config, not sure what lts-2.18 is, but let’s go ahead…

 >>>  stack build
GHC version mismatched, found 7.10.1 (x86_64), but expected version 7.8.4 (x86_64)
(based on resolver setting in /home/barrucadu/projects/barrucadu.co.uk/stack.yaml).
Try running stack setup

…ah.

I can totally see why the default configuration follows Stackage long-term support releases. It makes sense. It’s just a fairly poor first impression if you have a newer version of GHC installed.

A quick stack init --help told me that I should pass the --prefer-nightly flag to get more recent Stackage snapshots available, so I tried that and GHC 7.10 was supported. I’m not sure how I feel about having a curated set of packages. On the one hand, everything is guaranteed to work with everything else; on the other hand, things can fall behind. Getting sick of things I use falling behind on new features and bugfixes was the reason I switched from fixed-release Linux distributions to rolling-release ones! It is possible to use explicit dependency solving à la cabal, but for now the nightly Stackage release seems fine to me.

 >>>  stack init --prefer-nightly
...

 >>>  stack build
...

 >>>  stack exec hakyll build
...
Success

What about sandboxes‽” I hear you cry!

You’re right, I didn’t make a sandbox when I built my website. Surely that means that there are now dependencies of Hakyll spewed all over my home directory, interfering with everything! Well, yes and no.

Stack “sandboxes” everything by default, but it also shares built packages if the versions are compatible. This is similar to how cabal sandbox can link sandboxes together, except it happens completely transparently and automatically. That is nice.

Jenkins

Jenkins, previously Hudson, has long been the standard open-source choice for running a continuous integration system for yourself or your organisation. I was a bit wary flipping through some tutorials I found online, as the XML configuration (of course it’s XML, what else would a Java project use for configuration?) was frequently mentioned. Fortunately, I haven’t had to touch that, the web interface has been more than sufficient for the small use I have made of it so far.

First task is to get it running. Arch Linux has a binary package, so this was as simple as pacman -S jenkins and systemctl start jenkins. I then set up a reverse proxy with nginx which was less simple than I assumed, so I’ll include the configuration here:

server {
    listen 80;
    listen [::]:80;
  
    server_name ci.barrucadu.co.uk;
  
    charset utf-8;
  
    access_log logs/barrucadu.co.uk/ci.access.log;
    error_log  logs/barrucadu.co.uk/ci.error.log;
  
    location / {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
  
        proxy_read_timeout 300;
        proxy_connect_timeout 300;
        proxy_pass http://127.0.0.1:8090;
        proxy_redirect http://127.0.0.1:8090 http://ci.barrucadu.co.uk;
    }
} 

I’m used to just needing a proxy_read_timeout, proxy_connect_timeout, and proxy_pass, but Jenkins complained at me until I added the rest.

There were a few places where the web interface caught me out: like the credentials in the main menu not actually being for the same sort of credentials as for user authentication, or the configuration for the job description renderer being in the security menu. Mostly, though, it was all fairly self-explanatory and I didn’t have many problems figuring out how to do things.

So What..?

The Good: I now have this website and BookDB being built and deployed automatically when I push to GitHub. Nice! I also have various libraries being built and their test suites (if any) run against different GHC versions. Not yet figured out building haddocks and pushing to my github pages, but I’m sure I can get that up and running easily enough.

The Bad: Stack needs a bit of hand-holding if you depend on a package not in the curated snapshots. Invoking stack init --solver effectively behaves like cabal, but it hard-codes the version of every single dependency in the stack.yaml file. Alternatively, just the dependencies not in the snapshot can be listed. It’s unfortunate that the regular stack init can’t deal with this case when generating the initial configuration.

Packages I found not available in the snapshots: irc, web-routes, web-routes-wai. Unsurprisingly, my irc-ctcp and irc-conduit were also missing.

Furthermore, I couldn’t figure out how to arrange my library builds in Jenkins like in Travis. I would like to have one top-level job for each library, with multiple sub-tasks displayed. The sub-tasks are executed in parallel and correspond to differing GHC versions. All sub-tasks are run to completion, and if any one fails the overall job fails. At first the Multijob plugin seemed like the right fit, but it makes “phases” (the sub-tasks) sequential. A phase can contain jobs which are then performed in parallel, sadly the contents of a phase are not displayed unless you look at the configuration, only the sequential phases are.

If anyone knows how to do this, I’d be happy to hear of a way. I’d rather not have three different jobs for each library at the top-level listing.

The Ugly: Stack makes it really hard to use GHC versions outside of the ones Stackage supports; I had to write a wrapper script for ghc-pkg to get 7.6 working (and I still haven’t gotten test suites working with 7.6), and I couldn’t get 7.4 working at all. That unfortunately makes it not suitable for my primary use-case: which is testing that all my libraries build against a range of GHC versions.

I’m told halcyon works nicely in that case, so a better set-up might be stack for distribution/packaging and halcyon for continuous integration. I’ll have to play around with it.


Stack makes building packages pretty slick, having actually used it, I’m now more a fan. It’s definitely got some rough edges, as can be seen by the number of github issues, and is a pain when you need a package outside of the curated collection, but it’s under active development and things are rapidly improving.

I probably won’t be dropping cabal just yet, but I’ll certainly be including stack.yaml files with my projects from now on. It seems that stack is going to be the future of Haskell development (or at least, some people are pushing very hard for that).

The future is looking bright.