Search code examples
shake-build-system

Handling multiple build configurations in parallel


How can I build one set of source files using two different configurations without having to rebuild everything?

My current setup adds an option --config=rel which will load all options from build_rel.cfg and compile everything to the directory build_rel/.

data Flags = FlagCfg String                                                                                                                                
    deriving (Show, Eq)                                                                                                                                    

flags = [Option ['c'] ["config"]                                                                                                                           
                (ReqArg (\x -> Right $ FlagCfg x) "CFG")                                                                                                   
                "Specify which configuration to use for the build"]                                                                                                                                              

main :: IO ()                                                                                                                                              
main = shakeArgsWith shakeOptions { shakeChange=ChangeModtimeAndDigest }                                                                                   
                     flags $                                                                                                                               
                     \flags targets -> return $ Just $do                                                                                                   

    let buildDir = "build" ++                                                                                                                              
                   foldr (\a def -> case (a, def) of  
                              (FlagCfg cfg, "") -> '_':cfg
                              otherwise         -> def)                                                                                    
                    "" flags                                                                                                                            

    -- Settings are read from a config file.                                                                                                               
    usingConfigFile $ buildDir ++ ".cfg"
    ...

If I then run

build --config=rel
build --config=dev

I will end up with two builds

build_rel/
build_dev/

However, every time I switch configuration I end up rebuilding everything. I would guess this is because all my oracles have "changed". I would like all oracles to be specific to my two different build directories so that changes will not interfere between builds using different configurations.

I know there is a -m option to specify where the database should be stored but I would rather not have to specify two options that have to sync all the time.

build --config=rel -m build_rel

Is there a way to update the option shakeFiles after the --config option is parsed?

Another idea was to parameterize all my Oracles to include my build configuration but then I noticed that usingConfigFile uses an Oracle and I would have to reimplement that as well. Seems clunky.

Is there some other way to build multiple targets without having to rebuild everything? It seems like such a trivial thing to do but still, I can't figure it out.


Solution

  • There are a few solutions:

    Separate databases

    If you want the two directories to be entirely unrelated, with nothing shared between them, then changing the database as well makes most sense. There's currently no "good" way to do that (either pass two flags, or pre-parse some of the command line). However, it should be easy enough to add:

    shakeArgsOptionsWith
        :: ShakeOptions
        -> [OptDescr (Either String a)]
        -> (ShakeOptions -> [a] -> [String] -> IO (Maybe (ShakeOptions, Rules ())))
        -> IO ()
    

    Which would then let you control both settings from a single flag.

    Single database

    If you want a single database, you could load all the config files, and specify config like release.destination = ... and debug.destination = ..., then rule for */output.txt would lookup the config based on the prefix of the rule, e.g. release/output.txt would look up release.destination. The advantage here is that anything that does not change between debug and release (e.g. documentation) could potentially be shared.