Writing a large build system with GNU makeBy David R?thlisberger. Comments welcome at david@. Last updated 2 Jan 2015. This article is Creative Commons licensed. This is a summary of techniques for implementing a large build system, written in plain GNU Plain CMake might be another reasonable choice: it’s as easy for users as autotools’s Any other build system… you should think long and hard about it. If you need to target multiple wildly-different systems, then you probably know more about the topic than I do, so this article is also not for you. But note that I do mean different: a build system that knows how to cross-compile to multiple architectures, but always targeting similar Linux-based systems, doesn’t necessarily require autotools (nor even autoconf). Provide the standard targets
See “Standard Targets for Users” in the GNU There is also the Standard environment variablesRecipes that compile C or C++ code must obey the environment (or Why bother? This makes it trivial for a developer to use tools like distcc or colorgcc, or to experiment with different compiler options or even a different compiler like clang or its static analysis tools. The Fine-grained dependenciesUse “non-recursive make” so that a single The technique is described in Peter Miller’s 1997 paper “Recursive Make Considered Harmful”. See also Emile van Bergen’s implementation notes. You can often simplify Emile’s technique, if each of your sub-projects has approximately the same layout, by using static pattern rules and templates in the root makefile to eliminate boilerplate from the sub-project makefile fragments. This might seem like a lot of hassle, but note that automake doesn’t solve this problem for you either; “recursive automake” suffers from the same problems as “recursive make”, and there is far less information available about how to write a non-recursive autotools-based build system. Neither Peter nor Emile address the issue of rebuilding a binary or shared library when a source file is removed; this is handled by the technique described below in the section “Clean output”. Automatic dependencies on C and C++ header files
Use implicit rulesPut all your clever logic in implicit rules:
to keep the rest of the makefile (the part that specifies what to build, not how) as simple as possible. Customise the behaviour for specific targets by specifying variables used by the implicit rule (but don’t use Dependencies on third-party packages: pkg-configIf you depend on third-party libraries, don’t build them using the same top-level makefile that you use to build your own project, even if you need to maintain your own patches to those libraries. Use a separate top-level makefile to patch and build all the third-party packages you require. This makefile can use recursive Then use a packaging system like RPM to deploy these packages to developers’ machines. You’ll save a lot of developer time by not having to rebuild those third-party packages each time you build your own project. The makefiles for your main codebase should use
Detect changes in environmentRecord the exact compilation command line (including The technique is described by DJB and used in git’s Makefile. Clean outputAutomake generates makefiles with “silent rules”. So does CMake. You can implement this in your own makefiles the same way automake does: disable command echoing and add an Alternately, you can build on the technique from the previous section (“Detect changes in environment”): Save the compilation command into an executable file, and have your
If one of those steps fails, you can copy the line of output and paste it into your shell to re-run just that step; add “-v” to show the full compilation command. This idea comes from the “redo” build system. Separate build directoryAutomake and CMake both allow the user to choose a build directory separate from the source directory. This allows you to build separate “debug” and “release” versions from the same source directory, or to cross-compile to several different targets, without the builds trampling on each other. GNU
and —if
Instead of implementing build directory support in your makefiles, consider leaving it in your users’ hands. Users can overlay a writable build directory over a read-only source directory using a union filesystem. There are several implementations of union filesystems: aufs, funionfs, unionfs-fuse. At least one of them is likely to be available in your Linux distribution. Choose an implementation that supports copy on write (modifying a file from the read-only branch will create a corresponding file in the read-write branch that hides the read-only file) and that allows you to modify the source files in the read-only branch via their original location (outside of the union filesystem) while the union filesystem is mounted. Building for multiple systemsEach architecture that you want to build on has its own toolchain. These toolchains can have different names for the compiler, linker, etc., or take different command-line arguments. The autotools’ solution is a If this is the only reason you need autoconf, you can instead have your makefile include an architecture-specific makefile fragment that defines Building on different operating systems (Linux, BSD, other Unixes…) sometimes requires changes to the source code: Some functions might not be available and need to be emulated, or a given function behaves in incompatible ways across operating systems. Autoconf’s solution is to generate a “config.h” file full of In “The Practice of Programming”, Kernighan & Pike recommend that you use the intersection of different systems’ features, not the union of features, to avoid conditional compilation. If you do have to deal with differences between systems, hide those differences behind an interface, and implement that interface in source files that are separate from the rest of your codebase. Note that you can use autoconf without automake (but not vice-versa). Note also that you can use autoconf to generate a libtoolLibtool is one of the least understood parts of the autotools (at least by me). It is also the one that causes the most trouble, perhaps because of the wrapper scripts it generates that force you to learn the libtool way instead of the way you already know of running and debugging executables. Like most GNU tools, libtool has a decent manual but, as with the other autotools, the additional complexity simply isn’t necessary for many projects. As far as I can tell, libtool offers the following functionality: Libtool hardcodes the “runpath” in your shared object files and generates wrapper scripts for your executables so that you can run, for example, unit tests from the build location as well as running the complete program from the final installed location. Instead, you could just set the runpath yourself in your makefile’s recipes (if you have to; it might be better to install into the system’s standard locations), and use Libtool figures out the right compiler & linker commands to generate a shared library. Instead, put the specific commands in the implicit rules in your system-specific makefile fragments (see the previous section, “Building for multiple systems”). This is something you only need to figure out once for each system you want to target. Libtool supports systems that don’t support shared libraries, falling back to static libraries. Many of us don’t need to support such systems. Libtool provides a “simple library version number abstraction”. It still requires a lot of discipline and work from the library developers — maybe just as much work as maintaining library versioning without libtool. Enable sensible
|
|