As if writing source code for industrial systems in the C family of languages isn’t enough of a challenge, getting it to build properly can be an adventure too. Especially if multiple platforms are targeted. More often than not, different compilers will be used under different platforms, which will have different invocation syntaxes for similar options and/or will need to be given different options under different platforms (sometimes depending on the project). But that’s not the only difference between platforms to be taken into account when building. Other things to consider could be differences in filesystem layout, path separators, executable file requirements (.exe|.bat|.cmd file extensions under Windows vs. executable flag under *nix), shared library file extensions (.so vs. .dylib vs. .dll), different shell commands and usage syntaxes (rm in *nix vs. del|rd in Windows), different syntaxes for setting and accessing environment variables, and many-many other things. System libraries are a very common concern too – a library may or may not be available on a given platform, the available library can have different versions under different platforms, some system API functions may or may not be available and if available they could differ in parameters/semantics across platforms, etc.
The brute force approach to tackle this problem is making dedicated build scripts for each supported platform. Issues with such approach include, but are not limited to the following:
- Making a dedicated build script for a platform requires deep knowledge about that platform’s specifics from the project’s developer
- A build script needs to be created from scratch when a new platform is to be supported (unless that new platform is highly similar to another one that’s already supported, in which case a little tweaking might just be enough)
- Such efforts aren’t very reusable, i.e. platform-specific gymnastics often need to be performed over again when a new project is started by the same team
- Maintenance can be hard and daunting. A single build feature change needs to be applied for all build scripts, differently and carefully. That usually will demand the maintainer to know build scripting stuff for all platforms at hand, otherwise he/she will require help from other people for some platforms
You can find numerous projects that use (or previously used) this approach of platform-specific build scripts out there. In earlier times that has been the only affordable choice, so they didn’t have any compelling alternative back then. For example you can take a look at http://svn.wxwidgets.org/viewvc/wx/wxWidgets/trunk/build/ and http://expat.cvs.sourceforge.net/viewvc/expat/expat/ (CMake added relatively recently for Expat).
Another approach is to use one of the available cross-platform build systems that abstract away platform differences providing portable build specification mechanisms and handling them appropriately. Here is a brief overview of some that can be suitable for C++ projects:
- CMake: Probably the most popular cross-platform build system for C/C++ projects, CMake is a feature-rich build tool having permissive license (New BSD License) and written in C++. Strictly speaking, CMake is not a build system but a meta build system: instead of invoking compilers, linkers and other such tools directly, it generates platform-specific build files (such as Makefiles, MSVC project files, etc.) as needed, then invokes native build tools (such as make or Visual Studio) to do the actual build. Thus it’s basically an interpreter for translating build scripts written in CMake’s language into the given platform’s native build script language, and delegating the task to the native build tool. Gives a feeling of being messy and intrusive though to some people, including myself.
- SCons: A somewhat low-level build system written entirely on Python, with user build scripts being fully Python as well. That plus having permissive license (MIT License), means you can do pretty much anything with it, and Python being a widely known general-purpose language certainly helps there. The downside of that is the build scripts have to be well-formed Python code which means more syntax clutter compared to some other build systems that use their own specific language. Users also commonly find themselves writing a good deal of Python code to get some nontrivial build tasks done.
- Boost.Build v2: Sophisticated build system with many features and excellent abstraction of platform specifics, distributed under a permissive license (Boost Software License). Consists of an engine written in C named b2 (previously bjam), and a set of files written in Boost.Jam language that comprise the meat of the build system. Built-in support for most common build concepts and the concise syntax make common tasks with Boost.Build achievable with delightfully small build scripts, while gaining out-of-the-box support for many popular target platforms. Implementing something that is not supported out-of-the-box is harder than it could have been, though. The extensions need to be written in the cumbersome and often surprising Boost.Jam language, which is not very feature-rich and doesn’t have standard libraries by itself, and the fact that the documentation is lacking makes things even harder). There is the Python rewrite of Boost.Build initiative ongoing which will make writing custom extensions for Boost.Build much easier, among other benefits. Boost.Build is mostly centered around the needs of C/C++ projects, but can be used for some other languages as well, with varying degrees of out-of-the-box support.
* Another list of build systems overview, comparing them to Bakefile: http://www.bakefile.org/wiki/ComparisonsWithOtherTools