Build Systems: A No-Win Situation
A working understanding of what build systems are meant to do, why, and vaguely how, is a powerful weapon to behold. Unfortunately, the answers to these questions are confined, as tragically fundamental knowledge often is, in the skulls of an elite and dwindling minority, unanxious to undermine the firm footing of their remaining time on Earth. This leaves the ambitious mortals—you and I—to hunt and peck our way through mailing list archives and long-dead codebases for any trace of an answer to the age-old question, “Why the fuck won’t this compile?”
The simplest build system just encapsulates the execution of a particular compilation into a single command. This, along with incremental building, is the extent to which most people will ever need to utilize their build system, hence the dearth of common knowledge beyond this level. Most build systems also carry a DSL with them that can be used to conveniently express dependencies and conditionally configure the various bits of the toolchain that go into making your final output, whatever that may be. It’s typically a compiled library or executable, but it could be anything, or nothing at all. In game development, the practice of “baking” art and other game assets into more compact or otherwise useful formats is often generalized into what we call the “asset pipeline,” which is usually nothing more than a recoloring of the same build system used for the game’s source code. Other common exercises of a build system include code generation, style enforcement, test running, static analysis, and intelligent dependency resolution. Marshalling the execution of dreadfully repetitive tasks like these, whose outputs must coalesce into some built product(s), is the general use case, and this usually just means that you want the build system to compile and link some source code as fast as possible.
A commitment to documenting and maintaining the requirements of a build can ensure that it will function “out of the box” wherever it needs to, or else blow up in an informative way. On small or self-contained projects, preening the build like this is a difficult cost to justify, given its limited utility at that scale. As a project grows, however, usually once time accumulated half-assery causes the requirements of your build to become too complex and ambiguous to easily satisfy, the need for a more robust build process becomes impossible to avoid. You often won’t know that you're dependent on a particular version of a library until the next version takes a giant dump on your codebase. Meta build systems, like Autotools, CMake, and Premake, can reasonably mitigate some otherwise ball-busting obstacles in this direction, specifically those relating to configuration management and platform dependence, but their use is not without cost. Their abstractions leak badly, and can make it seem just as cumbersome to maintain a single meta build definition as it is to manually manage platform-specific project files. The leaky abstraction forces one to contaminate the meta build with platform specific knowledge anyway, so it’s actually not uncommon to find projects that distribute Autotools files, a Visual Studio workspace, and an Xcode project in the same repo. The jury is out on whether such practices speak to a developer’s ineptitude, the sorry state of build systems, or both.
Having just spent two hellish weeks formally defining a sizable cross-platform project and its dependencies in CMake, I'm sure it could compile and run on a bootstrapped didgeridoo, which is cool, but I'm not totally sold on the meta build system idea. CMake is of variable quality, but I chose it because it seems well supported, despite the scattered and sometimes outdated documentation. It’s probably the most widely used of what’s available, so there’s at least a chance that StackOverflow can save your ass if all else fails. There might be better options outside of FOSS, but as a starving indie developer, I'm not left with much of a choice. I did have to dig into the CMake source code in order to diagnose a bug I encountered with the Xcode generator though. Apps for iOS need to be built with Xcode, and CMake’s inbuilt Xcode and iOS support is problematic at best. As far as I can tell, the only saving grace of a meta build system over something more vanilla is that it consolidates all of the platform specific build tweaks into the same place, and otherwise normalizes everything else that it can. I like to stay DRY, but I'm more than a little dismayed at how much work it is, and will continue to be, to independently maintain working builds on 5+ platforms. On the other hand, I can’t help but smile when I think of what such barriers do for my job security. I should be a consultant.