Building is a fairly general term without strict definition; It usually refers to the whole process that outputing the final product (an executable or a library) from source materials. Depending on the requirements, building will involve several of the following: pre-processing, compiling, linking, converting data files, automatically testing, packaging.
Among the tools that defining the building behaviors for Platform Dependent
products whose programming language usually be C
or C++
, CMake is the most popular one as it is open-source and cross-platform. The building process with CMake takes place in two stages:
- Given abstract,
platform and compiler independent
building procedures defined by developers, generating standardMakefile
orProject Files
for IDEs (Visual Studio, Xcode etc.) . - Invoke the desired
native build tool
to undertake the actual building process.
Here we introduce the usage of CMake. The environment of this tutorial is Windows 10
; The output of each command will be different from that running on Linux. For the syntax of CMake Language, you may visit CMake Syntax for details.
Hello World
The following is a good starting point for learning about CMake.
- Create a new folder
test
. - Under the directory, create the source file
main.cpp
.1
2
3
4
5
int main(){
printf("Hello World from test Main!\n");
return 0;
} - Create the CMake file named exactly
CMakeLists.txt
1
2
3cmake_minimum_required(VERSION 3.10)
project(Main)
add_executable(Main main.cpp) - Run
cmake .
to generate nativeproject files
. UnderWindows
, CMake will generate aVisual Studio
project by default. When finished, lots of contents created in the directory:1
2
3
4
5
6
7
8
9
10
11
12│ ALL_BUILD.vcxproj
│ ALL_BUILD.vcxproj.filters
│ CMakeCache.txt
│ CMakeLists.txt
│ cmake_install.cmake
│ main.cpp
│ Main.sln
│ Main.vcxproj
│ Main.vcxproj.filters
│ ZERO_CHECK.vcxproj
│ ZERO_CHECK.vcxproj.filters
└─CMakeFiles/ - Run
cmake --build .
to create executable. You could find theMain.exe
in<root-dir>/Debug
.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18│ ALL_BUILD.vcxproj
│ ALL_BUILD.vcxproj.filters
│ CMakeCache.txt
│ CMakeLists.txt
│ cmake_install.cmake
│ main.cpp
│ Main.sln
│ Main.vcxproj
│ Main.vcxproj.filters
│ ZERO_CHECK.vcxproj
│ ZERO_CHECK.vcxproj.filters
├─CMakeFiles/
├─Debug/
│ Main.exe
│ Main.ilk
│ Main.pdb
├─Main.dir/
└─x64/ - Run executable via
Debug\Main.exe
1
Hello World from test Main!
Explanation
Generate a Project Buildsystem
To generate a buildsystem
with CMake, the following must be defined:
Source Tree
: The top-level directory containing source files provided by the project. Then the generation will start withCMakeLists.txt
under this directory.Build Tree
: The top-level directory where buildsystem files and output artifacts being placed. CMake will also create aCMakeCache.txt
here to store persistent information.Generator
: The type of buildsystem to generate. If not specified, CMake will choose the proper one automatically. When using one of the Command-Line Build Tool Generators CMake expects that the environment needed by the compiler toolchain is already configured in the shell. When using one of the IDE Build Tool Generators, no particular environment is needed.
You could run CMake with one of the following command signatures to specify them.
cmake [<options>] <path-to-source>
: Then the current directory is the build tree,<path-to-source>
is the source tree. Both absolute an relative path is valid. The source tree must contain aCMakeLists.txt
file and must not contain aCMakeCache.txt
file.cmake [<options>] <path-to-existing-build>
: Then<path-to-existing-build>
is the build tree which must contain aCMakeCache.txt
file because CMake will load the source tree from it.cmake [<options>] -S <path-to-source> -B <path-to-build>
. Specify both<path-to-build>
and<path-to-source>
. The source tree must contain aCMakeLists.txt
file; The build tree will be created automatically if it does not already exist.
Options
For the full list of options please visit https://cmake.org/cmake/help/latest/manual/cmake.1.html#options or cmake --help
-C <initial-cache>
: Pre-load a script that contains a list ofset()
commands to initialize the cache values. The loaded entries take priority over the project’s default values.-D <var>:<type>=<value>
or-D <var>=<value>
: Create or update a CMake CACHE entry.-G <generator-name>
Specify a build system generator. Runcmake --help
to get the name of supported generators--log-level=<ERROR|WARNING|NOTICE|STATUS|VERBOSE|DEBUG|TRACE>
: Set the log level. Themessage()
command will only output messages of the specified log level or higher. The default log level isSTATUS
.
Build a Project
Use cmake --build <dir> [<options>] [-- <build-tool-options>]
to build an already-generated project binary tree.
--build <dir>
The binary directory when building.--parallel [<jobs>], -j [<jobs>]
: Specify the maximum number of concurrent processes. If<jobs>
is omitted, use the default number.--target <tgt>..., -t <tgt>...
: Build specific<tgt>
s .--config <cfg>
: For multi-configuration tools, choose specific<cfg>
.--clean-first
: Clean existing built target and re-build it.-target clean
: Clean existing built target only.
Output message
message([SEND_ERROR|STATUS|FATAL_ERROR|DEBUG|TRACE] "message text" ...)
CMake displays STATUS
to TRACE
messages on stdout with prefix --
; All other message types are sent to stderr. FATAL_ERROR
will terminate the process immediately whereas CMake Error
stops generation only but continues processing.
Set Project Name
project(<PROJECT-NAME> [VERSION <major>[.<minor>[.<patch>[.<tweak>]]]])
This command will set the name of the project and store it in PROJECT_NAME
. When called from the top-level CMakeLists.txt
, it will also store the project name in CMAKE_PROJECT_NAME
. Simultaneously, the variables PROJECT_SOURCE_DIR
, <PROJECT-NAME>_SOURCE_DIR
, PROJECT_BINARY_DIR
, <PROJECT-NAME>_BINARY_DIR
will be defined according to the absolute path of the corresponding directory.
Add Executable
add_executable(<name> [source1] [source2 ...])
This command will add an executable target called <name>
to be built from the source files listed in the command invocation; The source files can also be added later using target_sources()
. The <name>
must be globally unique within a project. By default the executable will be created in the build tree directory with the name <name>
or <name>.exe
depending on the native platform.
In-Source and Out-of-Source Build
Some build trees created with GNU autotools have a make distclean
command that removes Makefiles and others belonging to the generated build system. However, CMake has no way to track exactly which files are generated by itself. Therefore, it's recommended to adopt the out-of-source
build ---- placing the build tree separately from the source tree. Then one can clean the build by clear or delete the build tree without affect the original source files.
A Better Hello World
Reorganize the Project Directory
- Create
src/
to place source files - Create
CMakeLists.txt
undersrc/
- Create
build/
to place buildsystem
After that, the structure of our project will be
1 | | CMakeLists.txt |
Config Source and Binary Directory
1 | add_subdirectory(source_dir [binary_dir]) |
This command will add source_dir
run its CMakeLists.txt
; A relative path will be evaluated with respect to the current directory. The binary_dir
specifies the directory in which to place the output files. Both relative path and absolute path are valid; A relative path it will be evaluated with respect to the current output directory. If binary_dir
is not specified, the value of source_dir
before expanding will be used.
In top-level CMakeLists.txt
become: 1
2
3cmake_minimum_required(VERSION 3.10)
project(Main)
add_subdirectory(src/ main)
Config Source Directory
It's tedious to list all source files manually, regardless of using 1
add_executable(project source1.c source2.c)
or
1 | set(DIR source1.c source2.c) |
aux_source_directory(<dir> <variable>)
will collects the names of all the source files in <dir>
and stores the list in the <variable>
. Note that there is no way for the build system that knows when a new source file has been added; When a new source is just added to the directory, one would have to manually rerun CMake to generate a build system incorporating the new file.
In CMakeLists.txt
of src/
, add
1 | aux_source_directory(. SRC_DIR) |
Config Binary Directory
In CMakeLists.txt
of src/
, add 1
2
3aux_source_directory(. SRC_DIR)
add_executable(Main ${SRC_DIR})
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
Specify the C++ Standard
In top-level CMakeLists.txt
, add
1 | set(CMAKE_CXX_STANDARD 11) |
Add Configured Header File
Add a Version Number
Add the version number in project
.
1 | project(Main VERSION 1.0) |
Then the variables PROJECT_VERSION
, <PROJECT-NAME>_VERSION
, PROJECT_VERSION_MAJOR
, <PROJECT-NAME>_VERSION_MAJOR
, PROJECT_VERSION_MINOR
, <PROJECT-NAME>_VERSION_MINOR
, PROJECT_VERSION_PATCH
, <PROJECT-NAME>_VERSION_PATCH
, PROJECT_VERSION_TWEAK
, <PROJECT-NAME>_VERSION_TWEAK
will be defined accordingly.
Add a Configured Header File
It's also valid to specify the version number directly in the source code,; Using CMakeLists.txt
provides more flexibility. Under src/
, create a new file config.h.in
with the following content:
1 | #define PROJECT_VERSION_MAJOR @PROJECT_VERSION_MAJOR@ |
configure_file(<input> <output>)
will copy the <input>
to an <output>
file with the evaulated values referenced as @VAR@
or ${VAR}
. Each variable reference will be replaced with the current value of the variable, or the empty string if the variable is not defined.
The configured file will be written into the ${CMAKE_CURRENT_BINARY_DIR}
; We must add that directory to the list of paths to search for include files. In src/CMakeLists.txt
, add
1 | configure_file(config.h.in config.h) |
target_include_directories(<target> <INTERFACE|PUBLIC|PRIVATE> [items1...]) [<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])
specifies include directories to when compiling a given target. The named <target>
must have been defined by such as add_executable()
or add_library()
.
Finally, in main.cpp
, let's include the header file and print the version number.
1 |
|
A Complex Hello World
Add Math Library
Create math/
under src/
, add CMakeLists.txt
, MathFunctions.cpp
with MathFunctions.h
MathFunctions.h
1 | extern double power(double base, int exponent); |
MathFunctions.cpp
1 |
|
src/math/CMakeLists.txt
1 | aux_source_directory(. DIR_LIB_SRCS) |
After that, the structure will be
1 | | CMakeLists.txt |
Build and Use Static Library
In src/math/CMakeLists.txt
, add 1
2set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
add_library (MathFunctions ${DIR_LIB_SRCS})
add_library(<name> [STATIC | SHARED] [source1] [source2 ...])
will add a library target called <name>
.
The top-level CMakeLists.txt
become: 1
2
3
4cmake_minimum_required(VERSION 3.10)
project(Main VERSION 1.0)
add_subdirectory(src/math math) #process math first
add_subdirectory(src/ main)
The src/CMakeLists.txt
become: 1
2
3
4
5
6
7
8aux_source_directory(. SRC_DIR)
add_executable(Main ${SRC_DIR})
configure_file(config.h.in config.h)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
target_link_libraries(Main PUBLIC MathFunctions) # link the library
target_include_directories(Main PUBLIC ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/math) # include the directory of `MathFunctions.h`
The structure become
1 | | CMakeLists.txt |
Build and use Dynamic Library
src/math/CMakeLists.txt
become
1 | aux_source_directory(. DIR_LIB_SRCS) |
MathFunctions.h
become
1 | extern __declspec( dllexport ) double power(double base, int exponent);// |
src/CMakeLists.txt
become:
1 | aux_source_directory(. SRC_DIR) |
Then the structrue become
1 | | CMakeLists.txt |
When running with dynamic-linked library, you should put the .dll
s under the same folder of .exe
.
https://cmake.org/cmake/help/latest/guide/tutorial/ https://cliutils.gitlab.io/modern-cmake/