Ever wonder what the difference is between CMake and GNU Make? Answers online tend to focus on how CMake is a meta-build system, has a mouse clicky GUI interface available, and GNU make is older. While that's all true, it tells you nothing about what it's like to use either of them. So, let me go through some of the differences that actually matter...

CMake's Reason for Being

CMake was created specifically to solve the problem of developing multi-platform software (e.g., works on Windows, Linux, MacOS). A team working for the U.S. Library for Medicine had to write software that worked on many platforms, and so the first version of CMake was created to help make that happen (link). The C/C++ ecosystem has a long history of different compilers for different systems, each with their own command line parameters and ways of working. Prior to CMake, managing those differences was a nightmare.

CMake does its best to bridge the differences, allowing you to use the best compiler for each system. For example, you can use Visual Studio on Windows, GCC on Linux, and XCode on MacOS X. All of them from one build script.

This is the primary reason for using CMake over GNU Make (or autotools).

What are CMake & Make Like to Use?

With the old GNU Make, programs are built using a set of build rules. You usually have build rules for compiling individual source files, and then build rules for linking them together into a program. There's a bit more to it than that (e.g., variables containing command line parameters), and you can complicate the makefiles to your heart's content. But, in a nutshell, that's how it works.

CMake works differently. You define a set of targets (e.g., programs, link libraries), tell it what source files should be compiled into it, and also which libraries it should be linked to. CMake then generates build scripts for your chosen compiler(s) to build the code into working software. Again, there are lots of options and other things, but that's the basics.

Incremental Building

Large projects can take a long time to build. So, you only want it to build the files that change. If you change a header file, then you want only the source files that use the header file to be rebuilt. Both Make and CMake can do this.

Handling inter-file dependencies in Make feels like a secret code. You need to:

  • Add "-MMD -MP" to the GCC command line. This will generate a set of *.d dependency files, that contain GNU Make build rules mapping out all the dependencies
  • Next, a pattern substitution is used to convert the list of source files to their *.d counterparts. For example:
    DEP+=$(patsubst %.c, $(OBJDIR)/%.d, $(notdir ${SRC})) 
  • Finally, the *.d files are included at the bottom of the makefile:
    -include $(DEP)

With all of that done, GNU Make will now automatically rebuild only the files that are affected by modifications. As I said earlier, it's like a secret code. Once you know how, you just copy it from one makefile to another.

CMake has built in inter-file dependency handling, so it works out-of-the box. No effort needed.

Linking to External Libraries

These days, almost all software is built using external libraries of code. It makes sense to reuse code written by others.

When it comes to external libraries, CMake can check if a library's developer files are available or not. Even better, it can fetch an open-source  project's code from the internet, and build it for you. GNU Make has no dependency checking at all. You discover missing dependencies at compile or linking time. I'm sure you could write fancy scripts to check for dependencies (like configure scripts do), but that's a lot of work.

Tradeoffs

When using CMake you may feel more isolated from the compiler. You don't directly control the compiler's command line parameters. Well, you can set or override parameters, but you generally don't want to. That's because every compiler's parameters are different, so you don't want to be writing a full set of parameters for each compiler.

It's a bit like a manual vs automatic transmission in a car. Manual enthusiasts appreciate the exact control that a manual transmission gives. They can set the power exactly where they want, and will complain about automatics being laggy and sluggish. There's a delay as the automatic system tries to figure out what you're doing. "Thinking... thinking... I know, you need more power. Let me switch to a lower gear."

Those who prefer to drive an automatic don't care about precise control. They appreciate that the automatic reduces their cognitive load as well as the hand and foot work required to control the car. What they want, is to simply push the accelerator, and it goes.

In similar fashion, CMake can do more for you automatically, but that does mean trusting it to make the best decision most of the time. There's not much point in automation if you keep on overriding it manually...

Which One is Best?

Wrong question. Which build system is best for a particular project depends on a number of factors. If you need to go fast, then use whatever you're most familiar with. If, on the other hand, you're working on a project that will become multi-platform, then I'd suggest using CMake. That's what is was designed for.

If you're new to C++, then I recommend learning CMake. It's the de-facto standard, there are things that are easier to do in CMake than Make. More importantly, it's going to take time and effort to learn any build system (they're all complicated), so you might as well learn the one that's the de-facto standard.

Speaking of which, if you want (or need) to learn CMake and would like to save countless hours doing so, then check out The CMake Tutorial. It was written for you.

Click here to get The CMake Tutorial.

The CMake Tutorial Cover small