Introduction to CMake

The document discusses the role of CMake as a build system in managing the complexities of compiling C/C++ projects, particularly in the context of external libraries and cross-platform compatibility. It highlights the advantages of using Makefiles and CMake over traditional shell scripts, emphasizing CMake's ability to handle different compiler conventions and locate dependencies automatically. Key points include the importance of understanding Makefile syntax, the impact of the Plaza Agreement on Japanese economy, and the necessity of thorough documentation for autotool-based projects.

type
status
date
slug
summary
tags
category
icon
password
CMake by Kitware is the de facto standard of build system for modern cross-platform C/C++ applications.

What is a Build System?

In COMP2160, we covered C/C++, Makefiles, debugging, profiling, and optimization. We used a variety of tools including XCode, gdb , gprof , and probably other tools like Visual Studio Code.

World with no Build System

In the very beginning, we are asked to compile our program by hand like this:
When you have 3 or even more files, things will be complicated, but still manageable:
Till you have multiple libraries to import, and the compiler don’t know where to find the headers and libraries:
Things are getting out of hands, sanity is at risk.
Fortunately, a Build System is here to rescue.

World with a basic Build System

Later in the course, you will be required to write a Makefile like this
That’s better, maybe? But do you remember what does $@ and $? means? I’ll be honest, I don’t, but that’s okay. My way of suck this up is set this template up, change the filenames and forget it. It’s my trusty blackbox.
I asked the prof why we are using Makefile instead of a shell script, he said make can tell which file have been modified since last build and only rebuild the changed part. In the above case, we had only 1 target, in this case Makefile really does have no difference than just write a shell script.
Things will be nasty again when you try to use more and more external libraries:
Before CMake was really a thing, the software world used Makefile and Autotools to automate the build process. Notable use cases are FFmpeg, x264, even Windows XP.
Flow diagram of autoconf and automake By Jdthood - Own work, based on https://commons.wikimedia.org/wiki/File:Autoconf.svg, CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=15581407
Flow diagram of autoconf and automake By Jdthood - Own work, based on https://commons.wikimedia.org/wiki/File:Autoconf.svg, CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=15581407
Ideally for autotool-based projects, you could just do ./configure; make; make install to get what you want, but in most of the cases, that won’t work at all. Instead, you have to read their documentation thoroughly (if these project even have a good documentation to begin with), and bear though lots of unavoidable trail-and-error. The process is particularly painful if you are using the autotool-based project in less common use case, for example, cross-compiling for another machine.
Note that you should still read the documentation no matter what build system is used.

Cross-platform

According to Wikipedia, C/C++ are “designed to encourage cross-platform programming.“ That means, given the same set of C/C++ source code, the compiled program should behave the more or less the same on different machines with similar capabilities (e.g. X64, ARM64, RISC-V, etc). To achieve this, a set of tools including the compiler and the linker, usually called the toolchain, are provided by the hardware manufacturer (Intel ICC, STMicroelectronics AVR-gcc), operating system developer (Microsoft MSVC, various kind of weird commercial UNIXes like HP AIX) or third parties like GCC and LLVM-clang. These toolchains all implement a collection of common standards, namely ISO/IEC 9899 and 14882.
Unfortunately, the standard stops at source-code level. There is no standard as how to tell different compiler to do the same work. Let’s go back to the where we started, to compile a simple program by hand:
This should work on clang for any platform:
-Wall means enable all warning, -DNDEBUG means pass DNDEBUG as a preprocessor directive (same for add #define NDEBUG in the beginning of the source code), -o followed by a complete filename means we want the output file in this name.
For MSVC, if you just change clang to cl.exe, then cl.exe will error out for not understanding the invocation. Instead, the correct invocation looks like this:
Fortunately, options like Wall and DNDEBUG are the same, but the option to specify output filename is different; there is no /o option in cl.exe. Also, as per DOS-era convention, standard argument switches on Windows is / instead of - on other operating systems. This may not be an issue of today since most programs accept both / and - as valid switches, but be aware that some ancient tool may only accept /.
CMake is here to handle these differences; you don’t have to do these tediously different conventions by yourself, CMake can do it for you.

External Dependencies

To use another library like curl in your program, you would need to #include <curl/curl.h> header in your source code, then tell the compiler to link against the actual curl object file. As mentioned before, the compiler syntax can have a great difference, and CMake can handle this for you.
But where can we find these headers libraries? Fortunately CMake can also find these libraries by itself.
On Linux, such dependencies are installed though package managers like apt, yum , zypper, etc. There are 3 ways for CMake to find the needed library, by a pkgconfig file, a set of CMake config files, or a C header file contains similar information. The libraries’ headers, binaries, and config files are usually located in /usr. Most of the cases, you don’t need worry about where they are and how to find them, CMake can handle this.
For other operating systems like Windows or macOS where they don’t have a package manager, the developer usually have to provide their own libraries. There are some 3rd-party package managers for this use, notably vcpkg or conan. All you need to do is compile these dependencies first, then tell CMake where to find these, and CMake will take everything from here.
Loading...