A few years ago, I wrote Makefiles are Software Too, which laid out some "generic requirements" for any build scripts (Make, Ant, Scons, Fabric, etc) that you're writing. I still think those are important, but over the past (nearly) four years I've worked with more systems, and I think there are some additional requirements we can add to the list.
As motivation for this list, consider the consequences of skipping the requirements below:
- A build that simply rebuilds everything, every time will waste a lot of developer time. It's almost always a good investment to set up a system that will only rebuild what is needed; the developer time spent investing and maintaining such a system will be repaid through faster builds and less time spent waiting.
- A build that doesn't rebuild everything, but does not do a good job
of tracking dependencies and often fails to rebuild a component
that should have been rebuilt results in strange bugs during
development. Developers who work with such a system will be in the
habit of doing
make cleanand you end up in situation #1.
- If you're in the horrible situation where
make cleandoesn't even work, developers will be in the habit of nuking their tree and pulling down a fresh source tree. This takes even more time and carries the risk of deleting work!
A Single Command Builds Everything
To build the "normal" product(s), it should not be necessary to do
make setup; make the_product; make installer. If you find yourself
running series of commands like this, you probably need either a
higher-level target (
make all) or your
bundle target should depend
the_product (and one or both of those targets should probably
Environment Variables are Encapsulated
The build script must encapsulate all environment variables within the script. (This is a corollary of the "single command" requirement above.) When writing build scripts, environment variables seem like a convenient way to add flexibility (I'm guilty of this), but in the end they add complexity for users of the script. I think it's ok if there are settings that can be customized for developers who need to do something different, but the script should provide reasonable defaults that "just work" for most of the developers (and the CI system).
What's even worse than environment variables required by the script is if there are parts of the toolchain that require variables that aren't set to a reasonable default by the script.
Rebuilding is Minimized
The build script should not unnecessarily rebuild source code in a
directory tree that has previously been built. (This is
I have worked on more than one system at past employers and clients
make targets that include a long chain of commands to rebuild
that target, where there are intermediate build products created along
the way that could have been separate targets. Since those
intermediate targets often would not need to be rebuilt every time,
build times could have been reduced.
The build script must be documented. If anything, the scripts I've seen in the past few years are become more complex than those I saw in the previous decade. In "Makefiles are Software Too", I said:
It doesn't have to be elaborate, but a five-line comment at the top of the script describing the available command-line variables would be nice.
A pattern I've seen that's even better is to provide a
command or the equivalent. This should echo the targets that are
available for the user and what they do, as well as any variables that
are commonly used to customize the build (e.g.
DEBUG=1 for debug
Build Trees are Cleanable
I actually think this is less important -- with two important assumptions:
- The version control system provides a way to get back to a clean
git clean -f). (Though this can have the danger of removing new files that haven't yet been added to VC.)
- Dependencies work reliably so that cleaning the tree is never needed.
If both of those requirements are met, then
make clean is
optional. But if such a target is provided, then it must work
reliably. And if there are multiple levels of "clean"
(e.g. GNU specifies
maintainer-clean), the behavior of each of these should be specified
in the documentation.
Cleaning Should Not be Needed
This is mentioned above, but it's really a separate generic
requirement. Cleaning (i.e. running
git clean -f, or
the equivalent) should be a rare event. If you find yourself doing
this, your build script is defective -- there's likely a problem with
dependencies, see below.
I originally listed this as a "bonus", but I think that this is more important now than it was in the last decade. Multicore systems are the default now, so the ability to spread a build across all the processing power available on the system is necessary. If all of the other generic requirements for build scripts are satisfied, it shouldn't be difficult to run a parallelized build. (And if your tool doesn't support building in parallel, you need to find a new tool!)
This also supports spreading large builds out over multiple machines, e.g. with distcc).
Dependencies are Automatic
Wherever possible, dependencies are handled automatically. This means
that, for example, your Makefile automatically knows that foo.o gets
built from foo.c, and it automatically generates a list of all of the
header files that foo.c includes. This is almost trivially easy to do
Note that, if I have to run the equivalent of
make depend, then
dependency generation is (by definition) not "automatic". Dependencies
must be generated on the fly and updated whenever anything changes.
Don't Implicitly Touch VC
In my opinion, the build system should not touch version control at all, but I accept that this is just my opinion. I've seen several builds that are based on fairly complex VC configurations, and having support for managing multiple-repository builds as part of the build scripts can be a useful feature.
As a requirement though, I think it is reasonable to say that the build system should not touch VC (neither getting updates nor putting changes) as part of the default target path. Any build commands that affect VC should be obviously documented as doing so. (It's dangerous to commit changes before you're ready. It's even more dangerous in some VC systems to pull down/merge into your workspace before you're ready. Yes, there are still people out there using CVS...)
Build Targets are Granular
This is complementary to the "Single Command" requirement. While we want to enable users to be able to build the whole product all at once, we also want to enable users to rebuild specific parts and avoid having the overhead of creating products they don't care about.
Consider the case where you've got a very large product, but your immediate task is to fix a bug in a smaller component. You've written a unit test that exposes the bug and/or the component can be run standalone for testing. While you're in an edit/compile/debug loop, you don't want the overhead of rebuilding the whole product, you just want to rebuild your component and the tests that go along with it.
The build script must provide targets so that users can rebuild specific components instead of the entire product.
The build script should support running unit or other
tests. (E.g. GNU's
make check.) Where applicable, tests must
be rebuilt automatically before being run (this is a corollary of
"Dependencies are Automatic").
The reason this belongs in the build script is that unit tests are part of the depdendency chain, and we mustn't be required to manually determine when unit tests need to be rebuilt. Whether to include other sorts of tests under the bailiwick of the build script is a decision for the implementor.
It would be nice to be able to run specific tests, but it doesn't seem like this needs to be provided through the build script.