Saturday, April 06, 2013

Build Vala applications with Shake build system

I'm going to introduce you a very nice alternative to make: the Shake build system, by setting up a builder for your Vala application project.

First of all, you need to know that Shake is a library written in Haskell, and it's meant to be a better replacement for make. Let's start by installing cabal and then shake:
apt-get install cabal-install
cabal update
cabal install shake

TL;DR; this is the final Build.hs file:
#!/usr/bin/env runhaskell
import Development.Shake
import Development.Shake.FilePath
import Development.Shake.Sys
import Control.Applicative hiding ((*>))

app = "bestValaApp"
sources = words "file1.vala file2.vala file3.vala"
packages = words "gtk+-3.0 glib-2.0 gobject-2.0"

cc = "cc"
valac = "valac"
pkgconfig = "pkg-config"

-- derived
csources = map (flip replaceExtension ".c") sources
cobjects = map (flip replaceExtension ".o") csources

main = shakeArgs shakeOptions $ do
  want [app]
  app *> \out -> do
    need cobjects
    pkgconfigflags <- pkgConfig $ ["--libs"] ++ packages
    sys cc "-fPIC -o" [out] pkgconfigflags cobjects
  cobjects **> \out -> do
    let cfile = replaceExtension out ".c"
    need [cfile]
    pkgconfigflags <- pkgConfig $ ["--cflags"] ++ packages
    sys cc "-ggdb -fPIC -c -o" [out, cfile] pkgconfigflags
  csources *>> \_ -> do
    let valapkgflags = prependEach "--pkg" packages
    need sources
    sys valac "-C -g" valapkgflags sources
-- utilities
prependEach x = foldr (\y a -> x:y:a) []
pkgConfig args = (words . fst) <$> (systemOutput pkgconfig args)

Just tweak app, sources and packages to match your needs, chmod +x Build.hs then run ./Build.hs .

The words function splits a string by spaces to get a list of strings, e.g. ["file1.vala", "file2.vala", "file3.vala"].

The csources variable maps .vala file names to .c file names. Same goes for cobjects. It's the equivalent of $(subst .vala,.c,$(SOURCES)) you'd do with make.

There it comes the main. The shakeArgs shakeOptions part will run shake with default options. Shake provides handy command line options similar to make, run ./Build.hs -h for help.

The want [app] tells shake we want to build the app object by default. That's equivalent to the usual first make rule all: $(APP).

Then we define how to build the executable app with app *> \out -> do. We tell shake the dependencies with need cobjects. This is similar to $(APP): $(COBJECTS) in make but not equivalent. In shake dependencies are not static like in many other build systems. This is one of the most interesting shake features.
The rest is quite straightforward to understand.

Then we define how to build each .o object with cobjects **> \out -> do. Here the out variable contains the actual .o required to be built, equivalent to $@ in make. Then we need [cfile], in order to simulate %.o: %.c like in make.

One more feature shake has out-of-the-box that make doesn't is how to generate more files from a single command. With make you'd use a .stamp file due to valac generating several .c files out of .vala files. Then use the .stamp as dependency.
With shake instead we consistently define how to build .c files with csources *>> \_ -> do, then shake will do the rest.

The shake project is very active. You can read this tutorial to learn Haskell basics, and the reference docs of shake. The author homepage has links to cool presentations of the shake build system.