In the first part of this series I introduced the idea of what I termed abstract build policies — higher-level methods for describing how to produce the DAG of a build from a description of the desired inputs (sources) and results (targets). An abstract build policy allows developers to specify their sources and targets in abstract terms and let the build system sort out the details. For example, the autotools allow a developer to specify that they want to generate a shared library from a set of C source files:
lib_LTLIBRARIES = libexample.la libexample_la_SOURCES = example.c example.h ...
But what's really going on under the hood?
Instead of requiring developers to describe the complete DAG of all sources-to-targets steps in a build, the autotools conspire to allow the developer to operate in terms of a set of higher-order abstractions:
- systems:: The build is occurring on a particular kernel/OS/hardware combination (the build-system) to run on another (the host-system) and may — if producing a compiler, etc. — generate system-specific entities for another (the target-system).
- variants:: The user invoking the build may choose to add optional part of the build, remove other parts, and cause yet other parts certain parts to occur in an alternative fashion.
- sources:: The inputs to the build, some of which may themselves need to be generated from predecessor sources.
- targets::
The kinds from a restricted set (e.g., shared library), names
(
libexample.so), and other properties (somewhat abstract installation location or lack thereof) of entities to result from the build.
The key aspect of the abstraction is that the developer describes her build only in terms of the details relevant for the particular build. "This set of source files produces a shared library." The autotools handle the nitty-gritty of populating the DAG with all the nasty little details only real toolchain-weenies actually care about.
In a perfect world (or for sufficiently simple/well-written code), the
developer doesn't care whether the code is being built with gcc or the Sun
Workshop compiler, for OpenBSD or AIX. If the code depends upon OS features
which vary from system to system, then the developer needs to account for those
variations, but only those variations. On Linux accessing /proc involves
text-file parsing, on Solaris using a library interface, but the developer
still doesn't care which compiler the build invokes or which flags the linker
requires to coax it into producing a shared library.
The key limitation of the autotools is that they provide no facility for
generating new abstractions. As I said in part 1, they provide a set of
abstract build policies, but no a real build policy abstraction. The beauty
and downfall of the autotools is that they produce truly portable build
descriptions — build descriptions which depend only on the POSIX shell, POSIX
utilities, POSIX make, and a toolchain for the source language. But providing
even as simple a new abstract policy as one for producing a new target type or
compiling a new source language is impossible without wholesale modification of
the autotools core. And even then a new policy has little power to leverage
intermediate abstractions, ultimately needing to itself generate portable POSIX
make rules.
As originally promised for this part, part 3 will look at build tools which allow definition of new abstractions and what that implies.
Commentary most sage