Search code examples
shake-build-system

How should I interpolate environment variables in Shake file patterns?


In my Makefiles, I prefer having the output directory defined by a environment variable rather than hard-coded (with some reasonable default value if its unset). For example, a Make rule would look like

$(OUTPUT_DIR)/some_file: deps
     #build commands

I have yet to figure out how to achieve a similar goal in Shake. I like using getEnvWithDefault to grab the value of the environment variable or a reasonable default, but no amount of bashing it with binds or lambdas have allowed me to combine it with (*>).

How might it be possible to interpolate an environment variable in a FilePattern for use with (*>)?


Solution

  • The function getEnvWithDefault runs in the Action monad, and the name of the rule has to be supplied in a context where you cannot access the Action monad, so you can't translate this pattern the way you tried. There are a few alternatives:

    Option 1: Use lookupEnv before calling shake

    To exactly match the behaviour of Make you can write:

    main = do
        outputDir <- fromMaybe "output" <$> lookupEnv "OUTPUT_DIR"
        shakeArgs shakeOptions $ do
            (outputDir </> "some_file") *> \out -> do
                 need deps
                 -- build commands
    

    Here we use the lookupEnv function (from System.Environment) to grab the environment variable before we start running Shake. We can then define a file that precisely matches the environment variable.

    Option 2: Don't force the output in the rule

    Alternatively, we can define a rule that builds some_file regardless of what directory it is in, and then use the tracked getEnvWithDefault when we say which file we want to build:

    main = shakeArgs shakeOptions $ do
        "//some_file" *> \out -> do
            need deps
            -- build commands
        action $ do
            out <- getEnvWithDefault "OUTPUT_DIR"
            need [out </> "some_file"]
    

    Here the rule pattern can build anything, and the caller picks what the output should be. I prefer this variant, but there is a small risk that if the some_file pattern overlaps in some way you might get name clashes. Introducing a unique name, so all outputs are named something like $OUTPUT_DIR/my_outputs/some_file eliminates that risk, but is usually unnecessary.