For CMake
, I had always understood it to be a project build tool, until I encountered --build
and realized that CMake
also unifies the compilation phase across different platforms. To understand CMake
‘s build and compile process, one must first understand generators.
1. Generators
The CMake
generator is responsible for writing input files for the underlying build system (e.g., Makefile
). Running cmake --help
will display the available generators. For cmake 3.26.5
, the generators supported by my system include:
Generators
The following generators are available on this platform (* marks default):
Green Hills MULTI = Generates Green Hills MULTI files
(experimental, work-in-progress).
* Unix Makefiles = Generates standard UNIX makefiles.
Ninja = Generates build.ninja files.
Ninja Multi-Config = Generates build-<Config>.ninja files.
Watcom WMake = Generates Watcom WMake makefiles.
CodeBlocks - Ninja = Generates CodeBlocks project files.
CodeBlocks - Unix Makefiles = Generates CodeBlocks project files.
CodeLite - Ninja = Generates CodeLite project files.
CodeLite - Unix Makefiles = Generates CodeLite project files.
Eclipse CDT4 - Ninja = Generates Eclipse CDT 4.0 project files.
Eclipse CDT4 - Unix Makefiles= Generates Eclipse CDT 4.0 project files.
Kate - Ninja = Generates Kate project files.
Kate - Unix Makefiles = Generates Kate project files.
Sublime Text 2 - Ninja = Generates Sublime Text 2 project files.
Sublime Text 2 - Unix Makefiles
= Generates Sublime Text 2 project files.
As discussed in this article, CMake
includes different types of generators, such as command-line generators, IDE
generators, and others.
1. Command-Line Generators
These generators are used for command-line build tools, such as Make
and Ninja
. Before using CMake
to generate a build system, the selected toolchain must be configured.
Supported generators include:
-
Borland Makefiles -
MSYS Makefiles -
MinGW Makefiles -
NMake Makefiles -
NMake Makefiles JOM -
Ninja -
Unix Makefiles -
Watcom WMake
2. IDE Generators
These generators are used for integrated development environments, which include their own compilers. For example, Visual Studio
and Xcode
both have their own compilers.
Supported generators include:
-
Visual Studio 6 -
Visual Studio 7 -
Visual Studio 7 .NET 2003 -
Visual Studio 8 2005 -
Visual Studio 9 2008 -
Visual Studio 10 2010 -
Visual Studio 11 2012 -
Visual Studio 12 2013 -
Xcode
3. Other Generators
These generators create configurations and work with other IDE
tools and must be included in the IDE
or command-line generators.
Supported generators include:
-
CodeBlocks -
CodeLite -
Eclipse CDT4 -
KDevelop3 -
Kate -
Sublime Text 2
To invoke the CMake
generator, you can use the -G
command-line switch.
2. CMake Build and Compile
When using CMake
to build and compile related projects on the Linux
platform, the following commands are typically used:
mkdir build
cd build
cmake ..
cmake --build .
Among these, cmake ..
and cmake --build .
are commonly used commands for building and compiling C/C++
projects.
[Analysis]
cmake ..
: Builds the project, usingcmake
to generate theMakefile
file (the main function ofcmake
) and other files required by the build system
cmake --build .
: Compiles and links the project in the current directory
Compilation: Syntax analysis, semantic analysis, etc., generates platform-specific assembly code
Linking: Symbol resolution, address relocation
[Extension]
First, using the command line:
cmake <source tree>
, for example:cmake ..
, generates project files in your build directory (external build method), referred to asproject files
in the official documentation, also known asbuild tree or binary tree
, which includesMakefile
and some other related files/directories/subdirectories.Next, naturally compile and link the generated project files in the build directory using
cmake --build .
.Finally, the
.
following--build
refers to the path of the generated project file’sbuild tree
. Generally, if you clearly know which builder (Build Generator) is used in your system, such asUnix Makefiles
, you can directly usemake
for compilation and linking. This--build
format is often used in automated scripts or inIDE
environments.Note:
<source tree>
refers to the source files + the path where the top-levelCMakeLists
resides;cmake ..
assumes the path is in the previous level.
Of course, there is another way to write the CMake
build and compile for C/C++
projects, as follows:
Note: The current directory is the top-level directory where
CMakeLists.txt
resides
-
Build the Project
cmake . -Bbuild -G "Unix Makefiles"
-
-B: Specifies the build directory where the generated
Makefile
or project files will be saved. This directory can be a relative or absolute path. For example:cmake -B build .
will create a build directory namedbuild
and save the generatedMakefile
or project files in that directory. You can also use an absolute path to specify the build directory, for example:cmake -B /path/to/build .
will create a build directory namedbuild
and save the generatedMakefile
or project files in the/path/to/build
directory. In short, the-B
option is used to specify the build directory whereCMake
generatedMakefile
or project files will be saved. This allows us to separate the source code from the build process for better project management. -
-G: Refers to
Generator
, which means specifying the generator. A generator is a build system-specific tool for convertingCMake
generatedMakefile
or project files into actual executable files, static libraries, or dynamic libraries. Different build systems have different generators, and each generator can convertCMake
generated files into files suitable for a specific build system. For example, theUnix Makefile
generator generatesMakefile
files suitable forUnix/Linux
systems, theNinja
generator generates project files suitable for theNinja
build system, and theVisual Studio
generator generates project files suitable forVisual Studio IDE
, etc. Generators work by readingCMake
generated files and converting them into files that the build system can use. In short, a generator is a build system-specific tool for convertingCMake
generatedMakefile
or project files into actual executable files, static libraries, or dynamic libraries. Different build systems have different generators, and each generator can convertCMake
generated files into files suitable for a specific build system. You can use the-G
option to specify the generator, such asUnix Makefiles
,Ninja
,Visual Studio
, etc. For example: -
cmake -G "Unix Makefiles" ..
generatesMakefile
files suitable forUnix/Linux
systems. -
cmake -G Ninja ..
generates project files suitable for theNinja
build system. -
cmake -G "Visual Studio 16" ..
generates project files suitable forVisual Studio 2016
. -
cmake -G "CodeBlocks - Unix Makefiles" ..
generatesMakefile
files suitable for theCode::Blocks IDE
. -
cmake -G "Xcode" ..
generates project files suitable forXcode IDE
.
-
Compile the Project
cmake --build build
The above command compiles and links the project in the build
directory; on Linux
, the default command used for project compilation is make
. But here we use cmake --build build
to achieve compilation and linking.
Note: Why do we use
cmake --build
instead of directly usingmake
? The main reason is for cross-platform compatibility. There is a concept incmake
calledcmake generator
, which allowscmake
to support different underlyings, such asMakefile
series,Ninja
series, etc. For example, to useNinja
, simply add-G Ninja
, i.e.,cmake -G Ninja
. Without the commandcmake --build build
, you would need to call the underlying command, such asmake
orninja
. But nowcmake
provides a unified command interface, and using this format allows for correct compilation regardless of the underlying system.
3. CMake Build and Compile Practical Demonstration
Let us demonstrate how to use CMake
for cross-platform building and compiling of project files through the multi-directory file project example from [CMake Learning Notes] | Modular Project Management (1).
The project structure is as follows:
# Project file directory structure
[root@localhost multi_dir]# tree -L 2
.
├── app
│ └── main.c
├── build
├── CMakeLists.txt
├── hello
│ ├── CMakeLists.txt
│ ├── include
│ └── src
└── world
├── CMakeLists.txt
├── include
└── src
8 directories, 4 files
The main CMakeLists.txt
file in the top-level directory: CMakeLists.txt
.
# CMake minimum required version
cmake_minimum_required(VERSION 3.0)
# Project information, can be anything
project(HelloWorld)
# Set C/C++ version (e.g., c99, c++11, c++17, etc.), below indicates using c99 version
set(CMAKE_C_STANDARD 99)
# Specify directories to add to the compiler's header file search path; the specified directory is interpreted as a relative path to the current source path.
# Of course, absolute paths and custom variables can also be used. By default, the include_directories command adds the directory
# to the end of the list (AFTER option). However, you can change its default behavior by setting the CMAKE_INCLUDE_DIRECTORIES_BEFORE
# variable to ON, which will add the directory to the front of the list. You can also specify whether to add to the front or back of the list
# each time you call the include_directories command using AFTER or BEFORE options.
include_directories(hello/include world/include)
# Set the variable DIR_SRCS, whose value is the source file main.c under app/
set(DIR_SRCS ./app/main.c)
# Add subdirectories hello and world; this way, the CMakeLists.txt files and source code under each subdirectory will also be processed
add_subdirectory(hello)
add_subdirectory(world)
# Specify static library path, ${PROJECT_SOURCE_DIR} represents the path of the main CMakeLists.txt file,
# i.e., the project root directory file path
link_directories(${PROJECT_SOURCE_DIR}/world)
# Link the static library libworld.a generated by the subdirectory; it is generally specified by omitting the prefix (lib) and suffix (.a)
link_libraries(world)
# Specify the generated target
add_executable(HelloWorld ${DIR_SRCS})
# Link the dynamic library libhello.so generated by the subdirectory; it is generally specified by omitting the prefix (lib) and suffix (.so)
target_link_libraries(HelloWorld hello)
The source files are as follows:
Header file in the hello
subdirectory: hello/include/hello.h
.
#ifndef HELLOWORLD_HELLO_H
#define HELLOWORLD_HELLO_H
extern void hello(void);
#endif //HELLOWORLD_HELLO_H
Source file in the hello
subdirectory: hello/src/hello.c
.
#include "hello.h"
#include <stdio.h>
void hello()
{
printf("hello.\n");
}
hello
subdirectory CMakeLists.txt
file: hello/CMakeLists.txt
.
# Add header file path
include_directories(./include)
# Set the variable DIR_SRCS, whose value is the source file hello.c under hello/src/
set(DIR_SRCS ./src/hello.c)
# Generate dynamic link library
add_library(hello SHARED ${DIR_SRCS})
Header file in the world
subdirectory: world/include/world.h
.
#ifndef HELLOWORLD_WORLD_H
#define HELLOWORLD_WORLD_H
extern void world(void);
#endif //HELLOWORLD_WORLD_H
Source file in the world
subdirectory: world/src/world.c
.
#include "world.h"
#include <stdio.h>
void world()
{
printf("world.\n");
}
world
subdirectory CMakeLists.txt
file: world/CMakeLists.txt
.
# Add header file path
include_directories(./include)
# Set the variable DIR_SRCS, whose value is the source file world.c under world/src/
set(DIR_SRCS ./src/world.c)
# Generate static link library
add_library(world STATIC ${DIR_SRCS})
Main source file in the app
subdirectory: app/main.c
.
#include "hello.h"
#include "world.h"
int main()
{
hello();
world();
return 0;
}
Next, let us demonstrate how to use CMake
to invoke different underlying Generator
for cross-platform building and compiling of different project files.
1. Unix Makefiles
This section demonstrates using the Unix Makefiles
generator to generate Makefile
files suitable for Unix/Linux
systems. Then, through cmake --build build
, we invoke the underlying command make
to compile and link the project. The example is as follows:
[root@localhost multi_dir]# ls
app CMakeLists.txt hello world
# cmake uses Unix Makefiles to build the project
[root@localhost multi_dir]# cmake . -Bbuild -G "Unix Makefiles"
-- The C compiler identification is GNU 8.5.0
-- The CXX compiler identification is GNU 8.5.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done (0.4s)
-- Generating done (0.0s)
-- Build files have been written to: /backup/cmake/multi_dir/build
[root@localhost multi_dir]# ls
app build CMakeLists.txt hello world
[root@localhost multi_dir]# ls build/
CMakeCache.txt CMakeFiles cmake_install.cmake hello Makefile world
# cmake uses make to compile the project, generating the executable file HelloWorld
[root@localhost multi_dir]# cmake --build build
[ 16%] Building C object world/CMakeFiles/world.dir/src/world.c.o
[ 33%] Linking C static library libworld.a
[ 33%] Built target world
[ 50%] Building C object hello/CMakeFiles/hello.dir/src/hello.c.o
[ 66%] Linking C shared library libhello.so
[ 66%] Built target hello
[ 83%] Building C object CMakeFiles/HelloWorld.dir/app/main.c.o
[100%] Linking C executable HelloWorld
[100%] Built target HelloWorld
[root@localhost multi_dir]# ls
app build CMakeLists.txt hello world
[root@localhost multi_dir]#
[root@localhost multi_dir]# ls build/
CMakeCache.txt CMakeFiles cmake_install.cmake hello HelloWorld Makefile world
[root@localhost multi_dir]# ./build/HelloWorld
hello.
world.
2. Ninja
This section demonstrates using the Ninja
generator to generate Makefile
files suitable for the Ninja
build system. Then, through cmake --build build
, we invoke the underlying command ninja
to compile and link the project.
Note: The prerequisite for
cmake
to use theNinja
generator is that theninja
executable must be successfully installed on the machine. The installation method can be done via the command line or by compiling from source; specific installation methods can be searched online.
The example is as follows:
# cmake uses Ninja to build the project
[root@localhost multi_dir]# cmake . -Bbuild -G Ninja
-- The C compiler identification is GNU 8.5.0
-- The CXX compiler identification is GNU 8.5.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done (0.4s)
-- Generating done (0.0s)
-- Build files have been written to: /backup/cmake/multi_dir/build
[root@localhost multi_dir]# ls
app build CMakeLists.txt hello world
[root@localhost multi_dir]#
[root@localhost multi_dir]# ls build/
build.ninja CMakeCache.txt CMakeFiles cmake_install.cmake hello world
# cmake uses ninja to compile the project, generating the executable file HelloWorld
[root@localhost multi_dir]# cmake --build build
[6/6] Linking C executable HelloWorld
[root@localhost multi_dir]# ls
app build CMakeLists.txt hello world
[root@localhost multi_dir]#
[root@localhost multi_dir]# ls build/
build.ninja CMakeCache.txt CMakeFiles cmake_install.cmake hello HelloWorld world
[root@localhost multi_dir]# ./build/HelloWorld
hello.
world.