6 Months of Testing C++ Build Systems: Here’s What You Need to Know

Six months. Countless builds. And me, stuck in a loop-doing the same thing over and over with different buils systems—so you don’t have to. Let’s figure out which C++ build system is really worth your time. I put the most popular build systems to the test—CMake, Make, Meson, Bazel, and more. Some were great, and others? Frustrating.

Want to know which build system could save you time and sanity? And which to avoid? Let me show you what I learnt over the last 6 months…

First, let’s do a quick tour of the build systems first, and then I’ll give you some of the lessons that I learnt on the way.

CMake

I used CMake as the reference for comparing all the build systems. Why? Because it’s my current go-to for new projects, and I know how to use it. CMake doesn’t build the code itself. Instead, it generates a build script for whatever compiler toolchain you want to use, and then uses that to do the actual build. So you can use the best compiler for each operating system. This is great for software that has to work on many platforms. CMake is by far the most used C++ build system, making it the de-facto standard.

There’s a lot of anti-CMake hate out there, and I used to hate it too. But it’s improved a lot over the decades. And, once you learn how to use it, it’s very powerful. Simple tasks are easy. Here’s the build script for a simple multi-file “Hello World” program:

cmake_minimum_required(VERSION 3.24)
project(hello_world)

 

add_executable(${PROJECT_NAME} Main.cpp Hello.cpp)

Just three lines. Very easy to understand. And here’s a different project that links to Raylib. The script is longer, but CMake will fetch the Raylib library’s source-code from the internet and build it for you automatically if needed, which is nice:

cmake_minimum_required(VERSION 3.24)
project(hello_window)

 

# Workaround for CLang and RayLib’s compound literals
# See: https://github.com/raysan5/raylib/issues/1343
set(CMAKE_CXX_STANDARD 11)

 

# Special linking needed for Raylib with Emscripten
# IMPORTANT: Do this *before* importing RayLib
if(EMSCRIPTEN)
    set(PLATFORM Web CACHEINTERNAL“”)
    list(APPEND CMAKE_EXE_LINKER_FLAGS“-s USE_GLFW=3 -sASYNCIFY”)
    set(CMAKE_EXECUTABLE_SUFFIX“.html”)
endif()

 

# Dependencies
include(FetchContent)

 

set(RAYLIB_VERSION 4.5.0)
FetchContent_Declare(
    raylib
    URL https://github.com/raysan5/raylib/archive/refs/tags/${RAYLIB_VERSION}.tar.gz
    FIND_PACKAGE_ARGS ${RAYLIB_VERSION} EXACT
)
set(BUILD_EXAMPLES OFFCACHEINTERNAL“”)
FetchContent_MakeAvailable(raylib)

 

# Our Project
set(SOURCE_FILES
    Main.cpp)

 

add_executable(${PROJECT_NAME}${SOURCE_FILES})
target_link_libraries(${PROJECT_NAME} raylib)

 

# Checks if OSX and links appropriate frameworks (Only required on
# MacOS)
if (APPLE)
    target_link_libraries(${PROJECT_NAME}“-framework IOKit”)
    target_link_libraries(${PROJECT_NAME}“-framework Cocoa”)
    target_link_libraries(${PROJECT_NAME}“-framework OpenGL”)
endif()

I swiped both of these examples from The CMake Tutorial.

How to Compare Build Systems Fairly

How can we compare the build systems fairly? By building the same code on all of them. And that’s exactly what I did. I tried to make each build system build these same two examples, and do the same thing, within reason.

GNU Make

The first build system I compared was good old GNU Make, which is part of the GCC compiler toolchain. What struck me most from doing the comparison, was just how much more code is needed to build the same code using Make. This is especially true if you want to replicated what CMake does for you automatically, such s automatic dependency checking for incremental builds, and having release and debug versions. Here are the build scripts for the first example, side-by-side:

CMake vs Make Code Length Comparison I can’t even fit all of it on my (ultra-high-def) screen. Sure, a lot of it is “boiler-plate” code that you can copy and paste to new projects. But still, look at the difference in script length between CMake and Make. Make cannot fetch and build dependencies. Okay, it can with some clever shell scripts, but not out-of-the-box.

Make can be used for multi-platform development, but you’re restricted to using one compiler for all systems, and that can cause problems. On the plus side, Make does give you fine control over how each file is built.

Click here and here for the full CMake vs Make comparison (it’s split into two videos).

Meson

The next build system was Meson. Meson is a relatively new C++ build system written in Python. It’s popularity has grown fairly rapidly, and and it has a pretty vocal fan club. Like CMake, it doesn’t do the build itself, but generates build scripts for whatever compiler you’re using.

I have to say, I like Meson. Within half a day I had replicated the CMake scripts for both examples in Meson. The documentation isn’t the best, so I did have to scour the internet to figure out a few things. But, on the whole it was pretty good.

If I were to pick something other than CMake, it would be meson.

Click here for the full CMake vs Meson video.

Ninja

Next up, I looked at ninja. This is one you should NOT use. Okay, you should use it, but not directly. Ninja’s own designers explicitly tell you that you’re supposed to generate Ninja build scripts using build tools like CMake and Meson. From Ninja’s homepage:

It is designed to have its input files generated by a higher-level build system, and it is designed to run builds as fast as possible.

Ninja build files are human-readable but not especially convenient to write by hand.

Why? Because they’ve sacrificed programmability for speed. Ninja is fast; especially for incremental builds. They achieved this by stripping out all the fancy pattern matching and scripting ability that make build systems so powerful. As a result, writing Ninja scripts by hand is difficult.

Have a look at some generated Ninja build scripts, and you’ll see why. So, take the advice of Ninja’s designers, and use Ninja via CMake, Meson, or another build system that supports it.

Click here for the full CMake vs Ninja video.

Visual Studio Projects

Next, I built the same two projects using Visual Studio Projects, and this was easy. Visual Studio is very beginner friendly because you don’t have to write any build scripts at all. Instead, you add your files using the GUI, and Visual Studio takes care of the rest.

It’s also got a package manager called nuget, and it can fetch dependencies like Raylib for you…provided that a nuget package exists for it.

On the down side, you’re locked into using only Visual Studio, which is fine if you only want your software running on Windows. But if you want your code to run on multiple platforms, then you need a proper build system like CMake.

Click here for the full CMake vs Visual Studio Projects comparison.

Bazel

The final build system I tried was Bazel. It’s design goals are very ambitious. They want it to be: fast, multi-programming-language, multi-platform, scalable, and extensible. So, basically the build system to rule all build systems. Very ambitious.

Alas, my experience with Bazel was not so great. Things felt more complicated than needed from the get-go. To install it on Windows I had to install Choco, in order to install Basilisk, which then installed the latest version of Bazel. Ugh!

Building basic “Hello World” example was easy.

Then things fell apart. I encountered cryptic error messages, poor documentation, scouring the internet for clues that left me confused. I worked late into the evening to get the Raylib example working, and ended up going to bed late, tired, and very frustrated.

I tried again the next morning, and got it working on Linux. I simply couldn’t get it building on Windows with Bazel. Something was broken.

It reminded me of what CMake felt like a few decades ago. So, there’s hope that it’ll be a lot better a decade or two from now. In the mean-time, I’ll pass on this one.

Click here for the full CMake vs Bazel comparison video.

Which C/C++ Build System(s) Would I Recommend?

If you’re just starting out, then Visual Studio or XCode are the easiest options, although I highly recommend you move on to using proper build systems, ASAP.

I highly recommend learning CMake. if anything, it’s so common that you’re going to encounter it. Being able to use CMake is a good skill to have. I wrote a CMake tutorial/course to help get you up to speed fast.

After that, I think Meson is the most promising, and Bazel might be awesome a decade or two from now.

I’d only use Make for projects where I really need fine-grained control over how each file is built. Or, for a platform that doesn’t support CMake.

Get the Code

If you want the source-code and build scripts for everything you’ve seen in this video, you’re in luck. I’ve put together a summary document together with all the code and build scripts that I wrote for these comparisons.

The summary also mentions build systems that I didn’t cover. Anyway, they’re available to download in the Kea Campus for creator and higher members.

I realize that not everybody wants to sign up to a membership program. That’s partially because some big bad companies (who will remain nameless) have made everyone scared of subscriptions by making it next to impossible to cancel. I don’t do that, by the way…

Anyway, to cater to those who want the summary and code without subscribing, it’s also available as a small one time purchase. Click here to get it. You can use the example code as templates for your own projects.

Lessons From Comparing C++ Build Systems

  1. C++ build systems have come a long way… but they still “suck”. There’s still a lot of room for improvement, although i think that a fair bit of the “build system” pain is self-inflicted. More on that later.
  2. I discovered that once you know one build system, learning another becomes easier. That’s because you understand what their purpose is, what they can do, and how it’s done. So learning another one becomes easier.
  3. A good build system is one that gets out of your way, and allows you docus on writing code. While comparing the build systems was interesting, doing the same thing over and over with different tools reminded me of just how precious my time is. I want to write awesome software, not fight with the build system. So I appreciate tools that I find easy to use, and hate having my time wasted. I also grew to resent comparing build systems, when what I really want to do is write code.
  4. Finally, the whole experience reinforced my belief in making and keeping things simple. You can help the build system get out of your way, by keeping things simple in the first place. As with every other profession, programmers can quickly blame their tools. But, the build system wasn’t the one that decided to add 100 configurable options to the build system. Nor was it the build system who decided to add complicated build rules. We do that.

    We’re the ones who bolt system on top of system, and complicate thing in order to look smart. Expecting the build system to first enable creating a complex mess, and then clean it up for us, is stupid.

I’ve long been an advocate for putting energy into making (and then keeping) things simple. It’s hard, but it’s worth it. Simple systems are easier to build, easier to maintain, and more reliable. So, actively look at what you can remove; from eveything, not just the build system. Simplify requirements before implementing them. Nothing is a bigger waste of time than implementing something that should not even exist.

Always ask if something is necessary. Every feature needs to earn its place. Every added complexity needs to be justified in the value that it brings to the overall product. You should be actively looking at what you can remove.

Reminder

A quick reminder that you can get a summary and example code for all the build systems mentioned in this video over here. Get it, use the templates to write awesome software, and support this channel at the same time. Everyone wins.

Now that I’ve wrapped up the C++ build system comparison, I can go write more actual code. :-)

Leave a Comment

Your email address will not be published. Required fields are marked *

 


Shopping Cart
Scroll to Top