How should I interpolate environment variables in Shake file patterns? - shake-build-system

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 (*>)?

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.

Related

How to build for different environments using shake-build?

Is there a built-in way to pass command-line arguments to a "shakefile"? I'd like to pass --env production|development|staging and then use it within my rules to (slightly) alter the build-steps for each environment.
There are two halves to this problem - first getting the flags into Shake, and secondly, using them to influence the behaviour.
You can get arguments into Shake using any Haskell command line parser, but Shake ships with support for one built it, which can often be easier:
data Flags = Production | Dev | Staging deriving Eq
flags = [Option "" ["production"] (NoArg $ Right Production) "Build production."
,Option "" ["dev"] (NoArg $ Right Dev) "Build dev."
,Option "" ["staging"] (NoArg $ Right Staging) "Build staging."
]
main = shakeArgsWith shakeOptionsn flags $ \flags targets -> do
want targets
... do whatever you want with the flags ...
return rules
For using the flags to influence, you might want to:
Completely isolate build outputs from each flag, in which case changing the directory and setting shakeFiles differently in each case makes each one fully distinct.
Use the flags to change the output paths, so you always have rules dev/main.js and prod/main.js, and then you consult the flags when doing want to pick up the right rules.
Put the flags into an Oracle and have them as tracked settings, so when you flip from prod to dev some things rebuild.
If the builds are 80%+ distinct I'd go for 1. If you change flags very rarely 3 can work. Otherwise, I tend to opt for 2. But they all work, so picking the simplest to start with is also not unreasonable.

Can a shake rule determine which "needs" have changed since the last build?

I am building a shake based build system for a large Ruby (+ other things) code base, but I am struggling to deal with Ruby commands that expect to be passed a list of files to "build".
Take Rubocop (a linting tool). I can see three options:
need all Ruby files individually; if they change, run rubocop against the individual file that changed for each file that changed (very slow on first build or if many ruby files change because rubocop has a large start up time)
need all Ruby files; if any change, run rubocop against all the ruby files (very slow if only one or two files have changed because rubocop is slow to work out if a file has changed or not)
need all Ruby files; if any change, pass rubocop the list of changed dependencies as detected by Shake
The first two rules are trivial to build in shake, but my problem is I cannot work out how to represent this last case as a shake rule. Can anyone help?
There are two approaches to take with Shake, using batch or needHasChanged. For your situation I'm assuming rubocop just errors out if there are lint violations, so a standard one-at-a-time rule would be:
"*.rb-lint" %> \out -> do
need [out -<.> "rb"]
cmd_ "rubocop" (out -<.> "rb")
writeFile' out ""
Use batch
The function batch describes itself as:
Useful when a command has a high startup cost - e.g. apt-get install foo bar baz is a lot cheaper than three separate calls to apt-get install.
And the code would be roughly:
batch 3 ("*.rb-lint-errors" %>)
(\out -> do need [out -<.> "rb"]; return out) $
(\outs -> do cmd_ "rubocop" [out -<.> "rb" | out <- outs]
mapM_ (flip writeFile' "") pits)
Use needHasChanged
The function needHasChanged describes itself as:
Like need but returns a list of rebuilt dependencies since the calling rule last built successfully.
So you would write:
"stamp.lint" *> \out -> do
changed <- needHasChanged listOfAllRubyFiles
cmd_ "rubocop" changed
writeFile' out ""
Comparison
The advantage of batch is that it is able to run multiple batches in parallel, and you can set a cap on how much to batch. In contrast needHasChanged is simpler but is very operational. For many problems, both are reasonable solutions. Both these functions are relatively recent additions to Shake, so make sure you are using 0.17.2 or later, to ensure it has all the necessary bug fixes.

How to override Shake configuration on the command-line

I maintain small configuration files per project read via usingConfigFile. I'd like to be able to override any of those settings on the command line. It seems using shakeArgsWith (rather than shakeArgs) is the first step on the way but I don't see an obvious way to wire that through to the values produced by getConfig. Is there a standard approach for doing this?
There isn't a standard approach, but I know several larger build systems have invented something. A combination of shakeArgsWith, readConfigFile and usingConfig should do it. Something like (untested):
main = shakeArgsWith shakeOptions [] $ \_ args -> return $ Just $ do
file <- readConfigFile "myfile.cfg"
usingConfig $ Map.union (argsToSettings args) file
myNormalRules
Where argsToSettings is some function that parses your arguments and turns them into settings - e.g. breaking on the first = symbol or similar.

What is the equivalent of default: in Shake

I'm trying to use Shake as replacement for Make in small project. Currently it's mostly used for aliasing shell commands. Is there any way to specify default phony action? Something similar to default: in makefile. For example, given:
phony "build" $ do
...
I'd like to declare that if given no targets, it run the action for build.
Short answer: you can write want ["build"].
Long answer: there are a few things going on here:
action defines something that will run by default.
The function want is defined as action . need, so it lets you declare some files that you want to build by default. I rarely use action directly, since it's almost always a want you are doing.
The function withoutActions takes a build system and removes all the actions from it. You almost never need to call that, since...
Provided you are using shakeArgs or shakeArgsWith (which almost everyone should be) then if there are any file-like command line arguments it calls withoutActions and want on the args you passed. If there are no file-like arguments the action statements are not removed and run.

Portable access to sysconfdir via config.h

I'd like my application to have portable access to the configuration files installed during make install (dist_sysconf_DATA). Is it possible to access $(sysconfdir) via config.h?
It is, but you should not do this according to official voices (as in, I am not gonna search the manual for it now) so as to continue supporting overriding it for specific objects to be built.
make CPPFLAGS="-USYSCONFDIR -DSYSCONFDIR=/blah" thisoneobject.o
Hence, what one is supposed to do:
AM_CPPFLAGS = -DSYSCONFDIR=\"${sysconfdir}\"
If you're using autoheader, adding this to your configure.ac will output a SYSCONFDIR macro in your config.h, and it will be defined with the value $(sysconfdir) or ${prefix}/etc.
if test "x$sysconfdir" = 'x${prefix}/etc'; then
if test "x$prefix" = 'xNONE'; then
sysconfdir=$ac_default_prefix/etc
else
sysconfdir=$prefix/etc
fi
fi
AC_DEFINE_UNQUOTED([SYSCONFDIR], ["$sysconfdir"], [location of system configuration directory])
But I would strongly recommend against doing that, and instead, stick with using the -DSYSCONFDIR flag. It's less code and therefore less prone to something going wrong. Using a condition in configure.ac such I mentioned may not be portable or take into account every case that might be encountered. Using -DSYSCONFDIR is the best option. Sometimes appearance just doesn't matter.
What I believe is most commonly done (and this is what I do)
Add the following in your Makefile.am
AM_CPPFLAGS = -DSYSCONFIR='"$(sysconfdir)"'
And now you can access SYSCONFDIR in source

Resources