Generic Requirements for Build Scripts

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:

  1. 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.
  2. 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 clean and you end up in situation #1.
  3. If you're in the horrible situation where make clean doesn'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 on the_product (and one or both of those targets should probably depend on setup).

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 make's raison d'etre.)

I have worked on more than one system at past employers and clients with 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.

Documentation Exists

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 make help 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 mode).

Build Trees are Cleanable

I actually think this is less important -- with two important assumptions:

  1. The version control system provides a way to get back to a clean tree (e.g. git clean -f). (Though this can have the danger of removing new files that haven't yet been added to VC.)
  2. 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 clean, distclean, mostlyclean, and 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 make clean, 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 with GCC's -MD flag.

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.

Run Tests

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.

Posted on 2013-02-12 by brian in engineering .
Comments on this post are closed. If you have something to share, please send me email.