Getting Started with CMake - 1 - Basic
Table of Contents
What can CMake do?
There's no better words to describe a product, than those from their official website: https://cmake.org/
CMake is an open-source, cross-platform family of tools designed to build, test and package software. CMake is used to control the software compilation process using simple platform and compiler independent configuration files, and generate native makefiles and workspaces that can be used in the compiler environment of your choice
A few key takeways from above description:
- CMake is cross-platform, so ideally you can code once and ship your code to Windows, Linux, Mac, etc.
- It uses Domain Specific Language (DSL) to describe its configuration files, so a new language to learn (its syntax not that complex, though)
- It merely generates project files for other build system, it doesn't directly call the compiler and compile you code. So you will need another build system to actually build your code (e.g. make, ninja, msbuild, etc.). Some people call it "meta-build system" for this reason.
Install CMake
There are plenty of tutorials online that best suits your platform. I better not duplicating them here.
To verify you have it correctly installed, run cmake --version
. You should see the CMake version as output.
How to use CMake?
Let's prepare a classic Hello, World! C project to demonstrate.
hello_world/
└── main.c
Just a recap, without a build system, we would manually compile the code with a compiler like this:
# gcc
gcc -c main.c -o main.o # compile
gcc main.o -o hello_world # link
./hello_world # run
# or clang
clang -c main.c -o main.o # compile
clang main.o -o hello_world # link
./hello_world # run
Side Note: clang is (almost) a drop-in replacement of gcc. You should read both
clang
andgcc
's manual to learn the most common options, right now. It actually make life much easier. I preferclang
overgcc
because it has better error messages.
make & Makefile
Let's quickly go over the Makefile we would need without CMake. To use make
, we need to write a Makefile
that describes how to build our project.
# File name: Makefile
build: main.o
$(CC) main.o -o hello_world
main.o: main.c
$(CC) -c main.c -o main.o
clean:
rm main.o hello_world
To build your project, run make build
, an executable called hello_world
will be generated within the same directory, this is called a in-source build, where the output is in the same location as source files, which is typical for projects that use make
build system. The downside of this is that you will have a lot of generated files in your source directory, which makes it hard to maintain. Run make clean
to remove all generated files.
You can see that Makefile
is quite imperative. It tells the build system exactly what command to use, what object file to generate, etc.
In this case you can just use
gcc main.c -o hello_world
to compile and link your code in one command. But for any other real project you will want to separate compile and link stage. So that you only need to recompile the changed files, instead of recompiling everything.
Problem with make
For this simple project, this Makefile
doesn't look particularly bad, but as project grows and more source files are involved, it will become much less readable and maintainable as you will certainly start using automatic variables and your Makefile
will probably start to look like this:
$(LIBNAME): $(OBJECTS)
echo $(LIBCOMMAND) $@ ...
$(RM) $@
$(LIBCOMMAND) $@ $(OBJECTS)
$(RANLIB) $@
It's filled with macro and variables and it's hard to tell what's going on.
However, it's still worth it to learn Makefile automatic variables as it's very common: https://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html
CMake & CMakeLists.txt
Now let's build our Hello, World! project with CMake. To use CMake, we need to write a CMakeLists.txt
that describes how to build our project.
# File name: CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(HelloWorld)
add_executable(hello_world main.c)
This 3 lines of code is the minimal code you need for any CMake project. It's much more declarative than the Makefile
we wrote above. It tells CMake what to build, and CMake will figure out how to build it.
I highly recommend checking CMake documentation about what each command does: https://cmake.org/cmake/help/latest/ . Unfortunately since CMake tries to abstract away the build system, it's command might not be very intuitive. It will take more time to learn CMake than Makefile, but it's worth it.
Now, to build your project, we need to do something a little different than that of make
. We need to create a build directory, and run CMake from there. This is called an out-of-source build, where the output is in a separate directory from source files. This is the typical and recommended way to build CMake project. Also, it allows you to build as many different configuration as you want, without interfering with each other. For example you can have a debug version and a release version at the same time. (We will cover the Debug/Release build later).
Let's open a terminal and run:
# This 2 steps create a directory to store generated files
mkdir build
cd build
# This step calls CMake to generate targeted build system files, by reading CMakeLists.txt file from `..` directory
# On Linux, by default it will generate for make build system
# run cmake -G to check all supported build systems
cmake ..
A few notes here:
- You can name the
build
directory anything. Usually people name itbuild
, orbuild/debug
,build/release
, etc for different build configurations. - You can just run
cmake .
at the root directory to do a in-source build, but it's not recommended. - The
..
part incmake ..
is just telling where to find theCMakeLists.txt
file, as you can probably guess. If current directory isbuild
, runcmake ..
, if current directory isbuild/debug
, runcmake ../..
, etc.
It's all flexible and not hard-coded magic words.
Now, back to building our project, after running cmake ..
, you can see some file generated under build
, Makefile
in this case. Don't try to edit CMake generated files as they are managed by CMake and will not be very human friendly.
To actually build the executable, run make
in directory build/
. You should see the executable generated in the build
directory. Run ./hello_world
to run it.
Or just run
cmake --build .
and let CMake build with whatever build system it uses.
You can also just runcmake --build .
to automatically regenerate build system files if needed. No need tocmake ..
first.
There is no clean
command in CMake, you can just delete the build
directory and run cmake ..
again to regenerate the build system files.
Congratulations! You have successfully built your first CMake project.
Conclusion
- A C/C++ project is typically built with either make or CMake on non-Windows platform (on Windows, Visual Studio is simply better).
- make is a build system where it directly calls compiler to generated the executables. Where CMake is a "meta-build system" that generates build system files for other build systems to use.
- make looks for file called
Makefile
, CMake looks for file calledCMakeLists.txt
- make is imperative, CMake is declarative (mostly)