Tuesday, September 28, 2010

Speeding up your cabal builds - Part II

Last time, I blogged about how linking your binaries against an internal library might speed up your cabal builds. This time, I show how you can avoid building certain binaries at all.

In our company, we work at a rather large Haskell project. The cabal file specifies more then ten binaries, so it takes rather long to build all of them. But often, you only need one or two of these binaries, so building them all is a waste of time.

Unfortunately, cabal does not allow you to build only a subset of your binaries. One workaround is the set the buildable flag in your .cabal file to false for the binaries you don’t want to build. However, this approach is rather unflexible because you need to edit the .cabal file and do a cabal configure after every change.

The solution I present in this article allows you to specify the binaries to build as arguments to the cabal build command. For example, if you want to build only binary B, you invoke cabal as cabal build B and cabal only builds binary B.

To get this working, all you need to do is writing a custom Setup.hs file:

import Data.List
import System.Exit
import Control.Exception

import Distribution.Simple
import Distribution.Simple.Setup
import Distribution.PackageDescription hiding (Flag)
import Distribution.PackageDescription.Parse
import Distribution.Verbosity (normal)

_CABAL_FILE_ = "DociGateway.cabal"

-- enable only certain binaries (specified on the commandline)                                                                                   
myPreBuildHook ::  Args -> BuildFlags -> IO HookedBuildInfo
myPreBuildHook [] flags = return emptyHookedBuildInfo
myPreBuildHook args flags =
    do let verbosity = case buildVerbosity flags of
                         Flag v -> v
                         NoFlag -> normal
       descr <- readPackageDescription verbosity _CABAL_FILE_
       let execs = map fst (condExecutables descr)
           unbuildableExecs = execs \\ args
       mapM_ (checkExistingExec execs) args
       putStrLn ("Building only " ++ intercalate ", " args)
       return (Nothing, map (\e -> (e, unbuildable)) unbuildableExecs)
    where
      unbuildable = emptyBuildInfo { buildable = False }
      checkExistingExec all x =
          if not (x `elem` all)
             then do putStrLn ("Unknown executable: " ++ x)
                     throw (ExitFailure 1)
             else return ()

main = defaultMainWithHooks $ simpleUserHooks { preBuild = myPreBuildHook }

That’s all! Don’t forget the set the Build-Type in your .cabal file to Custom. I’ve tested this approach with cabal-install version 0.8.2, using version 1.8.6 of the Cabal library.

Happy hacking and have fun!

Author: Stefan Wehr