阅读更多
1 tutorial
1.1 step1: A Basic Starting Point
tutorial.cxx
:
1 |
|
CMakeLists.txt
:
cmake_minimum_required
: Used to specify the minimum version ofcmake
, to avoid compatibility issues (includes features from newer versions, but the actualcmake
version may be lower)project
: Used to set the project name and store it in theCMAKE_PROJECT_NAME
variable. Some environment variables incmake
will use the specified project name as a prefix, such as:PROJECT_SOURCE_DIR
,<PROJECT-NAME>_SOURCE_DIR
PROJECT_BINARY_DIR
,<PROJECT-NAME>_BINARY_DIR
set
: Used to set some variablesadd_executable
: Adds a target executable file
1 | cmake_minimum_required(VERSION 3.10) |
At this point, the file structure is as follows:
1 | . |
Test:
1 | mkdir build |
1.2 step2: Adding a Library and Adding Usage Requirements for a Library
Next, we will replace the standard library’s implementation with our own square root function. Create a MathFunctions
subdirectory, and in that subdirectory, add three files: MathFunctions.h
, mysqrt.cxx
, and CMakeLists.txt
.
The content of MathFunctions/MathFunctions.h
is as follows:
1 | double mysqrt(double x); |
MathFunctions/mysqrt.cxx
:
1 |
|
MathFunctions/CMakeLists.txt
:
1 | add_library(MathFunctions mysqrt.cxx) |
Add TutorialConfig.h.in
:
1 |
Modify tutorial.cxx
:
1 |
|
Modify CMakeLists.txt
:
option
: Used to add acmake
option, which can be enabled or disabled using the-D<OPTION-NAME>=ON/OFF
parameter- For example:
cmake .. -DUSE_MYMATH=OFF
- For example:
configure_file
: Commonly used to dynamically generate a header file based on cmake optionsif statement
: Control flowadd_subdirectory
: Used to add a subdirectory to the build tasklist
: Operations related to containerstarget_link_libraries
: Specifies library filestarget_include_directories
: Specifies header file search paths
1 | cmake_minimum_required(VERSION 3.10) |
At this point, the file structure is as follows:
1 | . |
Test:
1 | # Use customized sqrt |
1.3 step3: Installing
Now, we want to install the binaries, library files, and header files generated by make
.
Based on step2
, modify the MathFunctions/CMakeLists.txt
file and append the following content:
- Here, two relative paths
lib
andinclude
are specified. The prefix is determined by thecmake
variableCMAKE_INSTALL_PREFIX
, which defaults to/usr/local
.
1 | # add the install targets |
Based on step2
, modify the CMakeLists.txt
file and append the following content:
1 | # add the install targets |
Test:
1 | # Use default installation path |
1.4 step4: Testing
Next, add testing functionality. Based on step2
, modify the CMakeLists.txt
file and append the following content:
add_test
: Used to add a test, whereNAME
specifies the name of the test case andRUN
specifies the test commandfunction
: Used to define a methodset_tests_properties
: Used to set properties of test cases; here, a wildcard is specified for the test result
1 | enable_testing() |
Test:
1 | mkdir build |
1.5 step5: Adding System Introspection
The same library may have different implementations on different platforms. For example, platform A might have the function funcA
, while platform B does not. Therefore, we need a mechanism to detect such differences.
Next, add testing functionality. Based on step2
, modify the MathFunctions/CMakeLists.txt
file and append the following content:
include
: Loads acmake
module. Here, theCheckSymbolExists
module is loaded, which is used to check whether a specified symbol exists in a specified file.
1 | include(CheckSymbolExists) |
Modify MathFunctions/mysqrt.cxx
:
1 |
|
Test:
1 | mkdir build |
2 target
Probably the most important item is targets. Targets represent executables, libraries, and utilities built by CMake. Every add_library
, add_executable
, and add_custom_target
command creates a target.
In addition to storing their type, targets also keep track of general properties. These properties can be set and retrieved using the set_target_properties
and get_target_property
commands, or the more general set_property
and get_property
commands.
3 variables
In CMake, variables themselves are typeless. CMake variables are stored as strings when defined, but these strings can be interpreted as different types (such as boolean, integer, etc.) through CMake commands and syntax. Below are some common variable types and examples of how to use them in CMake:
-
String
1
2set(MY_STRING "Hello, World!")
message(STATUS "String value: ${MY_STRING}") -
Boolean
1
2
3
4set(MY_BOOL ON)
if(MY_BOOL)
message(STATUS "Boolean is ON")
endif() -
Integer
1
2
3set(MY_INT 10)
math(EXPR MY_INT_PLUS_ONE "${MY_INT} + 1")
message(STATUS "Integer value: ${MY_INT_PLUS_ONE}") -
List
1
2
3
4
5set(MY_LIST "item1;item2;item3")
list(APPEND MY_LIST "item4")
foreach(item IN LISTS MY_LIST)
message(STATUS "List item: ${item}")
endforeach()
3.1 Frequently-Used Variables
Refer to(cmake-variables)for details:
CMAKE_BINARY_DIR
,PROJECT_BINARY_DIR
,<PROJECT-NAME>_BINARY_DIR
: Refers to the directory where the project is built. This variable does not change when recursively processing subprojects- If the
-B
parameter is specified, it is the directory specified by-B
- If the
-B
parameter is not specified, it is the directory where thecmake
command is executed
- If the
CMAKE_SOURCE_DIR
,PROJECT_SOURCE_DIR
,<PROJECT-NAME>_SOURCE_DIR
: Refers to the top-level directory of the project. This variable does not change when recursively processing subprojectsCMAKE_CURRENT_SOURCE_DIR
: Refers to the project directory currently being processed bycmake
, usually the directory containing theCMakeLists.txt
file. This variable changes when recursively processing subprojects- Difference between CMAKE_CURRENT_SOURCE_DIR and CMAKE_CURRENT_LIST_DIR
- If a subproject is included via
include(src/CMakeLists.txt)
, assuming the root directory isproject
, during processing,CMAKE_CURRENT_SOURCE_DIR
isproject
whileCMAKE_CURRENT_LIST_DIR
isproject/src
CMAKE_CURRENT_BINARY_DIR
: Refers to the target directory where the build output is stored. This variable changes when recursively processing subprojects. It can be changed viaset
command orADD_SUBDIRECTORY(src bin)
, butset(EXECUTABLE_OUTPUT_PATH <new_path>)
does not change this variable, it only affects the final output pathCMAKE_CURRENT_LIST_DIR
: Refers to the directory containing the currently processedCMakeLists.txt
. This variable changes when recursively processing subprojectsCMAKE_CURRENT_LIST_FILE
: Refers to the path of the currently processedCMakeLists.txt
. This variable changes when recursively processing subprojectsCMAKE_MODULE_PATH
: The search path for modules used byinclude()
andfind_package()
commandsEXECUTABLE_OUTPUT_PATH
,LIBRARY_OUTPUT_PATH
: Define the directories where the final compiled executable and library files are storedPROJECT_NAME
: Refers to the project name set byset(PROJECT_NAME ...)
CMAKE_INCLUDE_PATH
,CMAKE_LIBRARY_PATH
: These can be system environment variables (set viaexport
in bash) or cmake variables (set byset()
or-DCMAKE_INCLUDE_PATH=
).CMAKE_INCLUDE_PATH
affects the search paths forfind_file
andfind_path
, whileCMAKE_LIBRARY_PATH
affects the search path forfind_library
CMAKE_MAJOR_VERSION
,CMAKE_MINOR_VERSION
,CMAKE_PATCH_VERSION
: Major, minor, and patch version numbers respectively; e.g., in2.4.6
,2
is major,4
is minor,6
is patchCMAKE_SYSTEM
: System name, e.g.,Linux-2.6.22
CMAKE_SYSTEM_NAME
: System name without version, e.g.,Linux
CMAKE_SYSTEM_VERSION
: System version, e.g.,2.6.22
CMAKE_SYSTEM_PROCESSOR
: Processor name, e.g.,i686
UNIX
:TRUE
on all UNIX-like platforms, including OS X and CygwinWIN32
:TRUE
on all Win32 platforms, including CygwinENV{NAME}
: Environment variable; set viaset(ENV{NAME} value)
and referenced by$ENV{NAME}
CMAKE_INSTALL_PREFIX
: Installation path prefixCMAKE_PREFIX_PATH
: Semicolon-separated list of directories specifying installation prefixes to be searched by thefind_package()
,find_program()
,find_library()
,find_file()
, andfind_path()
commands. Each command will add appropriate subdirectories (likebin
,lib
, orinclude
) as specified in its own documentation.
3.1.1 BUILD_SHARED_LIBS
This parameter is used to control the add_library
command, determining whether to generate a static or dynamic library when the type parameter is omitted.
3.2 Cached Variables
Sets the given cache <variable>
(cache entry). Since cache entries are meant to provide user-settable values this does not overwrite existing cache entries by default. Use the FORCE
option to overwrite existing entries.
1 | set(<variable> <value>... CACHE <type> <docstring> [FORCE]) |
1 | cat > CMakeLists.txt << 'EOF' |
Tips:
cmake -L -N build
: view all non-advanced cached variables.cmake -LA -N build
: view all cached variables, including advanced cached.
4 property
4.1 INCLUDE_DIRECTORIES
Where to find header files .h
, -I (GCC)
include_directories
: This method adds include search paths at a global scope. These search paths are added to all targets (including all sub-targets) and appended to theINCLUDE_DIRECTORIES
property of every target.target_include_directories
: This method adds include search paths to a specific target and appends them to that target’sINCLUDE_DIRECTORIES
property.
How to view the values of the INCLUDE_DIRECTORIES
property at both the global scope and the target scope
1 | get_property(dirs DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY INCLUDE_DIRECTORIES) |
4.2 LINK_DIRECTORIES
Where to find library files .so/.dll/.lib/.dylib/...
, -L (GCC)
4.3 LINK_LIBRARIES
Names of libraries to link: -l (GCC)
5 command
5.1 message
Used to print messages
Format: message([<mode>] "message text" ...)
Valid mode
values include:
FATAL_ERROR
: Fatal error, terminates the buildSEND_ERROR
: Continues the build but terminates generationWARNING
: Warning message, continues the buildAUTHOR_WARNING
: Warning message, continues the buildDEPRECATION
: WhenCMAKE_ERROR_DEPRECATED
orCMAKE_WARN_DEPRECATED
is enabled, prints error or warning messages if deprecated features are usedNOTICE
: Message printed viastderr
STATUS
: The message users should pay most attention toVERBOSE
: Information for project buildersDEBUG
: Information for project debuggingTRACE
: Lowest level of information
5.2 set
set
is used to set: cmake
variables or environment variables
Format:
cmake
variable:set(<variable> <value>... [PARENT_SCOPE])
- Environment variable:
set(ENV{<variable>} [<value>])
How to reference variables:
cmake
variable:${<variable>}
- Environment variable:
$ENV{<variable>}
5.3 option
option
is used to set build options
Format: option(<variable> "<help_text>" [value])
- The optional
value
can beON
orOFF
, withOFF
as the default
5.4 file
file
is used for file operations
Format:
file(READ <filename> <out-var> [...])
file({WRITE | APPEND} <filename> <content>...)
Operation types explained:
READ
: Reads a file into a variableWRITE
: Overwrites the file; creates it if it does not existAPPEND
: Appends to the file; creates it if it does not exist
5.5 list
List support many subcommands:
APPEND
PREPEND
POP_BACK
POP_FRONT
REMOVE_AT
REMOVE_ITEM
REMOVE_DUPLICATES
- …
5.6 add_executable
add_executable
is used to add an executable file. Example as follows:
1 | add_executable(<name> [WIN32] [MACOSX_BUNDLE] |
5.7 add_library
add_library
is used to generate library files. Format and example as follows:
<name>
: Name of thetarget
- The second parameter specifies the library type and can be omitted; controlled by the
BUILD_SHARED_LIBS
variableSTATIC
: Static librarySHARED
: Shared (dynamic) library
1 | add_library(<name> [STATIC | SHARED | MODULE] |
5.8 add_custom_target
Adds a target with the given name that executes the given commands:
ALL
: Indicate that this target should be added to the default build target so that it will be run every time.- If
add_subdirectory
is specified with theEXCLUDE_FROM_ALL
option, then theALL
option becomes ineffective.
- If
DEPENDS
: Reference files and outputs of custom commands created withadd_custom_command
command calls in the same directory (CMakeLists.txt
file). They will be brought up to date when the target is built.VERBATIM
: All arguments to the commands will be escaped properly for the build tool so that the invoked command receives each argument unchanged.
1 | add_custom_target(Name [ALL] [command1 [args1...]] |
5.8.1 add_custom_command
This defines a command to generate specified OUTPUT
file(s). A target created in the same directory (CMakeLists.txt
file) that specifies any output of the custom command as a source file is given a rule to generate the file using the command at build time
WORKING_DIRECTORY
: Execute the command with the given current working directory.VERBATIM
: All arguments to the commands will be escaped properly for the build tool so that the invoked command receives each argument unchanged.DEPENDS
: Specify files on which the command depends. If any dependency is anOUTPUT
of another custom command in the same directory (CMakeLists.txt
file), CMake automatically brings the other custom command into the target in which this command is built. IfDEPENDS
is not specified, the command will run whenever theOUTPUT
is missing; if the command does not actually create the OUTPUT, the rule will always run. Each argument is converted to a dependency as follows:- If the argument is the name of a target (created by the
add_custom_target()
,add_executable()
, oradd_library()
command) a target-level dependency is created to make sure the target is built before any target using this custom command. Additionally, if the target is an executable or library, a file-level dependency is created to cause the custom command to re-run whenever the target is recompiled. - If the argument is an absolute path, a file-level dependency is created on that path.
- If the argument is the name of a source file that has been added to a target or on which a source file property has been set, a file-level dependency is created on that source file.
- If the argument is a relative path and it exists in the current source directory, a file-level dependency is created on that file in the current source directory.
- Otherwise, a file-level dependency is created on that path relative to the current binary directory.
- If the argument is the name of a target (created by the
1 | add_custom_command(OUTPUT output1 [output2 ...] |
5.9 set_target_properties
Set properties for the specified target
1 | set_target_properties(xxx PROPERTIES |
5.10 target_compile_options
Set compile options for the specified target
1 | target_compile_options(xxx PUBLIC "-O3") |
5.11 add_dependencies
Add a dependency between top-level targets.
1 | add_dependencies(<target> [<target-dependency>]...) |
5.12 Link Libraries
5.12.1 link_libraries
Link libraries to all targets added later.
1 | link_libraries([item1 [item2 [...]]] |
5.12.2 target_link_libraries
Specify libraries or flags to use when linking a given target and/or its dependents. Usage requirements from linked library targets will be propagated. Usage requirements of a target’s dependencies affect compilation of its own sources.
Each <item>
may be:
- A library target name
- A full path to a library file
- A plain library name, like
thread
,dl
- A link flag
1 | target_link_libraries(<target> ... <item>... ...) |
5.12.2.1 Automatic Inclusion of Header File Paths in CMake with target_link_libraries
When using the target_link_libraries
command in CMake to link a target (such as a library), the related header file paths may automatically be included. This is because modern CMake manages projects based on the concept of “targets,” which allows targets to own and propagate attributes used for building and usage, such as include directories, definitions, compile options, etc.
This behavior is primarily achieved through “usage requirements.” When you set INTERFACE
, PUBLIC
, or PRIVATE
properties for a target, these properties affect both the target itself and any other targets that link to it:
PRIVATE
: These properties are only visible to the target that defines them and do not propagate to other targets that depend on this target.PUBLIC
: These properties apply to both the target that defines them and automatically propagate to any other targets that depend on this target.INTERFACE
: These properties do not apply to the target that defines them but do propagate to any other targets that depend on this target.
When a library specifies its header file paths in its CMake configuration file using the target_include_directories
with the PUBLIC
or INTERFACE
keywords, these paths automatically become part of the include paths for targets that depend on this library. Therefore, when you link to such a library using target_link_libraries
, you also implicitly gain access to these include paths.
This design greatly simplifies dependency management within projects, allowing maintainers to avoid explicitly specifying include paths, compiler, and linker configurations repeatedly. This is also one of the recommended best practices in modern CMake.
5.13 Link Directories
5.13.1 link_directories
Adds the paths in which the linker should search for libraries. Adds the paths in which the linker should search for libraries. Relative paths given to this command are interpreted as relative to the current source directory. The command will apply only to targets created after it is called.
By default the directories specified are appended onto the current list of directories. This default behavior can be changed by setting CMAKE_LINK_DIRECTORIES_BEFORE
to ON
. By using AFTER
or BEFORE
explicitly, you can select between appending and prepending, independent of the default.
1 | link_directories([AFTER|BEFORE] directory1 [directory2 ...]) |
See the below example, only /pathA/lib
will be added to the library search path.
1 | link_directories(/pathA/lib) |
5.13.2 target_link_directories
Add link directories to a target. Specifies the paths in which the linker should search for libraries when linking a given target. Each item can be an absolute or relative path, with the latter being interpreted as relative to the current source directory. These items will be added to the link command.
By using AFTER
or BEFORE
explicitly, you can select between appending and prepending, independent of the default.
1 | target_link_directories(<target> [BEFORE] |
5.14 Include Directories
5.14.1 include_directories
Add the given directories to those the compiler uses to search for include files. Relative paths are interpreted as relative to the current source directory.
The include directories are added to the INCLUDE_DIRECTORIES
directory property for the current CMakeLists
file. They are also added to the INCLUDE_DIRECTORIES
target property for each target in the current CMakeLists
file. The target property values are the ones used by the generators.
By default the directories specified are appended onto the current list of directories. This default behavior can be changed by setting CMAKE_INCLUDE_DIRECTORIES_BEFORE
to ON
. By using AFTER
or BEFORE
explicitly, you can select between appending and prepending, independent of the default.
1 | include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 ...]) |
5.14.2 target_include_directories
The INTERFACE
, PUBLIC
and PRIVATE
keywords are required to specify the scope of the following arguments. PRIVATE
and PUBLIC
items will populate the INCLUDE_DIRECTORIES
property of <target>
. PUBLIC
and INTERFACE
items will populate the INTERFACE_INCLUDE_DIRECTORIES
property of <target>
. The following arguments specify include directories.
1 | target_include_directories(<target> [SYSTEM] [AFTER|BEFORE] |
5.15 add_subdirectory
add_subdirectory
is used to add a subdirectory to current build target.
EXCLUDE_FROM_ALL
: If theEXCLUDE_FROM_ALL
argument is provided then theEXCLUDE_FROM_ALL
property will be set on the added directory. This will exclude the directory from a default build. You can use theadd_dependencies
andtarget_link_libraries
commands to explicitly add dependencies, thereby incorporating the relevant targets into the default build process.
1 | add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL] [SYSTEM]) |
5.16 include
include
: Used to include a cmake
subproject. For example, include(src/merger/CMakeLists.txt)
Both include
and add_subdirectory
commands can be used to include a cmake
subproject. The difference between them is:
add_subdirectory
: The subproject is treated as an independentcmake
project. AllCURRENT
-related variables are switched accordingly. Additionally, all relative paths in theCMakeLists.txt
file have their base paths switched to the directory specified byadd_subdirectory
.include
: The subproject is not treated as an independentcmake
project. Only theCMAKE_CURRENT_LIST_DIR
andCMAKE_CURRENT_LIST_FILE
CURRENT
variables are switched, whileCMAKE_CURRENT_BINARY_DIR
andCMAKE_CURRENT_SOURCE_DIR
remain unchanged. Moreover, the base paths for all relative paths in theCMakeLists.txt
file stay the same.
5.17 find_package
This section is reprinted and excerpted from In-depth Understanding of the Usage of find_package() in CMake
To conveniently include external dependencies in our project, cmake
officially predefines many modules to find dependency packages. These modules are stored in the path_to_your_cmake/share/cmake-<version>/Modules
directory (for example, /usr/local/lib/cmake-3.21.2-linux-x86_64/share/cmake-3.21/Modules
). Each file named Find<LibraryName>.cmake
helps locate a package. Note that the <LibraryName>
part in find_package(<LibraryName>)
and in Find<LibraryName>.cmake
must match exactly in case.
Taking the curl
library as an example, suppose our project needs to include this library to request web pages from websites to local. We see that the official distribution already defines FindCURL.cmake
. So we can directly use find_package
in our CMakeLists.txt
to reference it.
For system-predefined Find<LibraryName>.cmake
modules, the usage is as follows. Each module defines several variables (this information is explained in comments at the top of the Find<LibraryName>.cmake
file). Note that the naming of these variables is just a convention; whether the <LibraryName>
part is all uppercase or mixed case depends entirely on the Find<LibraryName>.cmake
file. Generally, it is uppercase. For example, the variables defined in FindDemo.cmake
are named DEMO_FOUND
.
<LibaryName>_FOUND
<LibaryName>_INCLUDE_DIR
<LibaryName>_LIBRARY
: The name defined by this module viaadd_library
<LibaryName>_STATIC_LIB
1 | find_package(CURL) |
You can use <LibraryName>_FOUND
to check whether the module was found. If it was not found, you can disable certain features, give warnings, or stop the build depending on the project’s needs. The example above shows how to report a fatal error and terminate the build. If <LibraryName>_FOUND
is true, then <LibraryName>_INCLUDE_DIR
is added to INCLUDE_DIRECTORIES
.
5.17.1 Add Non-Official Library
Using find_package
to include non-official libraries — this method only works for libraries that support CMake build and installation
Generally, for a third-party library xxx
that needs to be included, the usual steps are as follows:
1 | git clone https://github.com/xxx.git |
Suppose we need to include the glog
library for logging, but we do not find FindGlog.cmake
in the Module
directory. Therefore, we need to install the glog
library ourselves before referencing it.
1 | git clone https://github.com/google/glog.git |
At this point, we can include the glog
library in the same way as we included the curl
library.
1 | find_package(GLOG) |
5.17.2 Module Mode & Config Mode
From the above, we have learned the basic usage of including dependency libraries via cmake
. Knowing what to do is important, but understanding why is equally crucial. find_package
is somewhat of a black box to us, so how exactly does it find the paths of the libraries we depend on? Here we need to talk about two modes of find_package
: one is the Module
mode — the way we included the curl
library; the other is the Config
mode — the way we included the glog
library. Below, we explain the operation mechanisms of these two modes in detail.
In Module
mode, cmake
needs to find a file called Find<LibraryName>.cmake
. This file is responsible for locating the library paths and adding the include and library paths to our project. cmake
searches for this file in two places: one is the share/cmake-<version>/Modules
directory under the cmake installation directory mentioned earlier (for example, /usr/local/lib/cmake-3.21.2-linux-x86_64/share/cmake-3.21/Modules
), and the other is the directory specified by the CMAKE_MODULE_PATH
variable.
If the search in Module
mode fails and the corresponding Find<LibraryName>.cmake
file is not found, cmake
switches to Config
mode. In this mode, it mainly looks for <LibraryName>Config.cmake
or <lower-case-package-name>-config.cmake
files to include the libraries we need. For example, for the glog
library we just installed, after installation, it generates a glog-config.cmake
file in the /usr/local/lib/cmake/glog/
directory, which is one of the search paths of the find_package
function.
5.17.3 Create Customized Find<LibraryName>.cmake
Suppose we have written a new function library and we want other projects to reference it via find_package
. What should we do?
We create a ModuleMode
folder in the current directory, where we write a simple function library that calculates the sum of two integers. The library is installed manually using a Makefile
. The library files are installed in the /usr/lib
directory, and the header files are placed in /usr/include
. The Makefile
file is as follows:
1 | # 1. Preparation: compilation method, target file name, and dependency library path definitions. |
Compile and install:
1 | make |
Next, we return to our cmake
project and create a FindAdd.cmake
file under the cmake
folder. Our goal is to locate the directory containing the library’s header files and the location of the shared library file.
1 | # Find the locations of header files and shared library files in specified directories; multiple target paths can be specified |
At this point, we can include our custom library just like curl
. Add the following to CMakeLists.txt
:
1 | # Add the `cmake` folder under the project directory to `CMAKE_MODULE_PATH` so that `find_package` can locate our custom function library. |
5.18 find_library
find_library
is used to locate library files. Example usage:
- The search stops as soon as one of the specified possible names matches.
CMAKE_FIND_LIBRARY_SUFFIXES
controls whether to prioritize static or shared libraries.set(CMAKE_FIND_LIBRARY_SUFFIXES ".so;.a")
: Prioritize shared libraries (default)set(CMAKE_FIND_LIBRARY_SUFFIXES ".a;.so")
: Prioritize static libraries
1 | set(CMAKE_FIND_LIBRARY_SUFFIXES ".so;.a") |
5.19 find_path
find_path
is used to find the directory containing a given file. Example usage:
- The search stops as soon as one of the specified possible names matches.
1 | # Multiple possible names can be provided after `NAMES`. The result is saved to the variable `BRPC_INCLUDE_PATH`. |
5.20 aux_source_directory
Find all source files in a directory
5.21 PUBLIC vs. PRIVATE
In CMake, PUBLIC and PRIVATE are used to specify the visibility of target properties and dependencies. Here’s what they mean:
PUBLIC: A property or dependency marked as PUBLIC is visible to all targets that depend on the current target. This means that the property or dependency will be propagated to any targets that link against the current target.
- For example, suppose you have a library target called “foo” that depends on another library called “bar”. If you mark the dependency on “bar” as PUBLIC, any target that links against “foo” will also link against “bar”.
PRIVATE: A property or dependency marked as PRIVATE is only visible to the current target. This means that the property or dependency will not be propagated to any targets that depend on the current target.
- For example, suppose you have a library target called “foo” that uses a header file called “bar.h”. If you mark the header file as PRIVATE, any targets that depend on “foo” will not be able to access “bar.h”.
To summarize, PUBLIC properties and dependencies are visible to all targets that depend on the current target, while PRIVATE properties and dependencies are only visible to the current target.
Example as follows:
1 | . |
1 | # CMakeLists.txt |
If PUBLIC
in target_link_libraries(libfoo PUBLIC libbar)
is changed to PRIVATE
, the compilation will fail because main
does not explicitly depend on libbar
, and the header file bar.h
will not be found.
5.22 External Project
5.22.1 ExternalProject_Add
The ExternalProject_Add()
function creates a custom target to drive download, update/patch, configure, build, install and test steps of an external project.
All the options can be divided into following categories:
- Directory Options
- Download Step Options
- URL
- Git
- Subversion
- Mercurial
- CVS
- Update Step Options
- Patch Step Options
- Configure Step Options
- Build Step Options
- Install Step Options
- Test Step Options
- Output Logging Options
- Terminal Access Options
- Target Options
- Miscellaneous Options
6 Tips
6.1 Command Line
cmake --help
Generators
: Default isUnix Makefiles
build
cmake <path-to-source>
: Current directory is used as<build_path>
cmake -S <path-to-source>
: Current directory is used as<build_path>
cmake -B <build_path>
: Current directory is used as<path-to-source>
cmake -B <build_path> <path-to-source>
cmake -B <build_path> -S <path-to-source>
cmake --build <build_path>
: Equivalent to runningmake
inside<build_path>
cmake --build <build_path> -j $(( (cores=$(nproc))>1?cores/2:1 ))
: Equivalent to runningmake -j $(( (cores=$(nproc))>1?cores/2:1 ))
inside<build_path>
cmake --install <build_path>
: Equivalent to runningmake install
inside<build_path>
6.2 Print
6.2.1 Print All Variables
1 | get_cmake_property(_variableNames VARIABLES) |
6.2.2 Print All Envs
1 | execute_process(COMMAND "${CMAKE_COMMAND}" "-E" "environment") |
6.2.3 Print All Compile Command
TheseWhen using the default generator Unix Makefiles
, the following three methods are equivalent:
cmake -B <build_path> -DCMAKE_VERBOSE_MAKEFILE=ON
make VERBOSE=1
cmake --build <build_path> -- VERBOSE=1
6.2.4 Cmake Debug Options
--trace
: Put cmake in trace mode.--trace-expand
: Put cmake in trace mode with variable.--debug-find
: Put cmake find in a debug mode.
6.3 Compile Options
6.3.1 Specify Compiler
Command:
1 | cmake -DCMAKE_CXX_COMPILER=/usr/local/bin/g++ -DCMAKE_C_COMPILER=/usr/local/bin/gcc .. |
CMakeLists.txt:
1 | set(CMAKE_C_COMPILER "/path/to/gcc") |
6.3.2 Add Compile Flags
Command:
1 | cmake -DCMAKE_C_FLAGS="${CMAKE_C_FLAGS} -O3" -DCMAKE_CXX_FLAGS="${CMAKE_CXX_FLAGS} -O3" .. |
CMakeLists.txt:
1 | add_compile_options(-O3 -Wall -fopt-info-vec) |
CMAKE_BUILD_TYPE
options values:
Debug
Release
RelWithDebInfo
MinSizeRel
6.3.3 Use CMake Options and Variables as Macro
1 | mkdir -p option_as_macro_demo |
Output:
1 | OPT_1: 1 |
6.3.4 Macro Definition
Command:
1 | cmake -B build -DCMAKE_C_FLAGS="${CMAKE_C_FLAGS} -DUSE_XXX -DVALUE_YYY=5" -DCMAKE_CXX_FLAGS="${CMAKE_CXX_FLAGS} -DUSE_XXX -DVALUE_YYY=5" |
CMakeLists.txt:
1 | add_compile_definitions(FLAG1 FLAG2="Debug") |
6.3.5 Add Extra Search Path
The only way I can find so far to add extra search path out of the CMakeLists.txt
are using following environments.
CMAKE_INCLUDE_PATH
won’t work, it only affects the search path offind_file
andfind_path
CMAKE_LIBRARY_PATH
won’t work, it only affects the search path offind_library
1 | export C_INCLUDE_PATH= |
6.3.6 Add Extra Runtime Search Path
1 | set_target_properties(<target> PROPERTIES LINK_FLAGS "-Wl,-rpath,'/usr/lib/jvm/default-java/lib/server'") |
You can check if it works by this command:
1 | readelf -d <binary> | grep 'RPATH\|RUNPATH' |
6.3.7 Set Library Search Path
Sometimes, you may use find_package
to include libraries, but multiple versions of the same library might exist in different paths on your system. By using the -DCMAKE_PREFIX_PATH
option, you can specify which paths CMake should search.
1 | cmake -B build -DCMAKE_PREFIX_PATH=/usr/local |
6.3.8 Build Type
1 | # If you want to build for debug (including source information, i.e. -g) when compiling, use |
All possible values of CMAKE_BUILD_TYPE
include:
Debug
Release
RelWithDebInfo
MinSizeRel
6.4 Include All Source File
6.4.1 file
1 | # Search for all .cpp and .h files in the current directory |
If your project has a more complex structure and you wish to recursively search all subdirectories for files, you can replace the file(GLOB ...)
command with file(GLOB_RECURSE ...)
:
1 | file(GLOB_RECURSE MY_PROJECT_SOURCES "*.cpp") |
6.4.2 aux_source_directory
If there are multiple source files in the same directory, listing them one by one in the add_executable
command can be very tedious and costly to maintain later.
1 | add_executable(Demo main.cxx opt1.cxx opt2.cxx) |
We can use the aux_source_directory(<dir> <variable>)
command, which scans all source files in a directory and assigns the list of files to a variable.
1 | # Find all source files in the current directory |
6.5 Library
6.5.1 Build Static Library By Default
Add following config to project’s root CMakeLists.txt
, then all sub modules (imported via add_subdirectory
) will be built in static way.
1 | set(BUILD_SHARED_LIBS FALSE) |
6.5.2 Import Library From Unified Thirdparty Directory
Suppose you have put all libraries in ${THIRDPARTY_DIR}/lib
, then you can use the following config to import it.
1 | add_library(protobuf STATIC IMPORTED) |
6.5.3 Priority: Static Libraries vs. Dynamic Libraries
6.5.3.1 find_package
The factors influencing the choice between static and dynamic libraries by the find_package
command may include:
- The provided CMake configuration files by the package: Some packages offer their own CMake configuration files, such as
<PackageName>Config.cmake
(Located at/usr/local/lib/cmake
). These configuration files may explicitly specify the use of static or dynamic libraries, or make the selection based on the value of theBUILD_SHARED_LIBS
variable (maybe).- For boost, it offers a variable named
Boost_USE_STATIC_LIBS
(Defined at/usr/local/lib/cmake/Boost-1.84.0/BoostConfig.cmake
) to control whether to use static version or dynamic version.
- For boost, it offers a variable named
- Default behavior: Certain packages may use specific library types based on conventions or default settings. For instance, if a package typically provides dynamic libraries and does not have explicit configuration options to choose static libraries, the
find_package
command might default to using dynamic libraries.
Example:
1 | set(Boost_USE_STATIC_LIBS ON) |
6.5.3.2 find_library
To control whether find_library
should prefer static libraries or dynamic libraries, you typically set the CMAKE_FIND_LIBRARY_SUFFIXES
variable. This variable specifies the suffixes that find_library
searches for when looking for libraries.
Here’s how you can control find_library
to prioritize static libraries or dynamic libraries:
Prioritize static libraries:
1 | set(CMAKE_FIND_LIBRARY_SUFFIXES ".a;.so") |
Prioritize dynamic libraries:
1 | set(CMAKE_FIND_LIBRARY_SUFFIXES ".so;.a") |
6.6 Custom Command/Target
6.6.1 How to make custom command as part of build?
There are three ways:
-
Use
add_custom_target
with auto build (The keywordALL
):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16mkdir -p custom_command_demo_1
cd custom_command_demo_1
cat > CMakeLists.txt << 'EOF'
cmake_minimum_required(VERSION 3.20)
project(custom_command_demo_1)
add_custom_target(${PROJECT_NAME} ALL
COMMAND ${CMAKE_COMMAND} -E echo "This is a custom command demo, first message"
COMMAND ${CMAKE_COMMAND} -E echo "This is a custom command demo, second message"
COMMAND ${CMAKE_COMMAND} -E echo "This is a custom command demo, third message"
VERBATIM
)
EOF
cmake -B build && cmake --build build -
Use
add_custom_command
andadd_custom_target
with auto build (The keywordALL
):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22mkdir -p custom_command_demo_2
cd custom_command_demo_2
cat > CMakeLists.txt << 'EOF'
cmake_minimum_required(VERSION 3.20)
project(custom_command_demo_2)
add_custom_command(
OUTPUT say_hello.txt
COMMAND ${CMAKE_COMMAND} -E echo "This is a custom command demo, first message"
COMMAND ${CMAKE_COMMAND} -E echo "This is a custom command demo, second message"
COMMAND ${CMAKE_COMMAND} -E echo "This is a custom command demo, third message"
COMMAND ${CMAKE_COMMAND} -E echo "Write this to a file" > say_hello.txt
VERBATIM
)
add_custom_target(${PROJECT_NAME} ALL
DEPENDS say_hello.txt
)
EOF
cmake -B build && cmake --build build -
Use
add_custom_target
andadd_dependencies
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32mkdir -p custom_command_demo_3
cd custom_command_demo_3
cat > CMakeLists.txt << 'EOF'
cmake_minimum_required(VERSION 3.20)
project(custom_command_demo_3)
file(GLOB MY_PROJECT_SOURCES "*.cpp")
add_executable(${PROJECT_NAME} ${MY_PROJECT_SOURCES})
target_compile_options(${PROJECT_NAME} PRIVATE -static-libstdc++)
target_link_options(${PROJECT_NAME} PRIVATE -static-libstdc++)
# This target won't be executed by default
add_custom_target(say_hello_target
COMMAND ${CMAKE_COMMAND} -E echo "This is a custom command demo, first message"
COMMAND ${CMAKE_COMMAND} -E echo "This is a custom command demo, second message"
COMMAND ${CMAKE_COMMAND} -E echo "This is a custom command demo, third message"
VERBATIM
)
# Link target say_hello_target to the default build target
add_dependencies(${PROJECT_NAME} say_hello_target)
EOF
cat > custom_command_demo_3.cpp << 'EOF'
int main() {
return 0;
}
EOF
cmake -B build && cmake --build build -
Use
add_custom_command
andadd_custom_target
andadd_dependencies
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38mkdir -p custom_command_demo_4
cd custom_command_demo_4
cat > CMakeLists.txt << 'EOF'
cmake_minimum_required(VERSION 3.20)
project(custom_command_demo_4)
file(GLOB MY_PROJECT_SOURCES "*.cpp")
add_executable(${PROJECT_NAME} ${MY_PROJECT_SOURCES})
target_compile_options(${PROJECT_NAME} PRIVATE -static-libstdc++)
target_link_options(${PROJECT_NAME} PRIVATE -static-libstdc++)
add_custom_command(
OUTPUT say_hello.txt
COMMAND ${CMAKE_COMMAND} -E echo "This is a custom command demo, first message"
COMMAND ${CMAKE_COMMAND} -E echo "This is a custom command demo, second message"
COMMAND ${CMAKE_COMMAND} -E echo "This is a custom command demo, third message"
COMMAND ${CMAKE_COMMAND} -E echo "Write this to a file" > say_hello.txt
VERBATIM
)
# This target won't be executed by default
add_custom_target(say_hello_target
DEPENDS say_hello.txt
)
# Link target say_hello_target to the default build target
add_dependencies(${PROJECT_NAME} say_hello_target)
EOF
cat > custom_command_demo_4.cpp << 'EOF'
int main() {
return 0;
}
EOF
cmake -B build && cmake --build build
6.6.2 PRE_BUILD/PRE_LINK/POST_BUILD Events
1 | mkdir -p build_events_demo |
6.6.3 Best Practice
Here we choose add_custom_command
, add_custom_target
:
add_custom_target
: Adds a target with the given name that executes the given commands. The target has no output file and is always considered out of date even if the commands try to create a file with the name of the target.add_custom_command
: This defines a command to generate specifiedOUTPUT
file(s). A target created in the same directory (CMakeLists.txt
file) that specifies any output of the custom command as a source file is given a rule to generate the file using the command at build time.- If you use
Ninja
as build tool, the standand output of the command will be buffered and put to screen once the command is finished. But we can use2>&1 | tee /dev/tty
to enable real-time output to the screen.
- If you use
1 | mkdir -p cmake_with_java_demo |
6.7 compile_commands.json
6.7.1 Manually Generate compile_commands.json
Specify the parameter -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
in cmake
. After the build is complete, a compile_commands.json
file will be generated in the build directory, containing the compilation commands for each source file.
6.7.2 Auto generate compile_commands.json and copy to project source root
Refer to Copy compile_commands.json to project root folder
1 | add_custom_target( |
6.8 How to work with ccache
Use cmake variable CMAKE_
1 | cmake -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -B build |
Or, you can use env variable of the same name.
1 | export CMAKE_C_COMPILER_LAUNCHER=ccache |
How to ensure ccache truely works:
grep -rni 'ccache' build
: Find ccache related configsccache -z && ccache -s && cmake --build build && ccache -s
: Check cache hits
6.9 How to uninstall
After installation, there will be a install_manifest.txt
recording all the installed files. So we can perform uninstallation by this file.
1 | xargs rm < install_manifest.txt |
6.10 Ignore -Werror
1 | cmake --compile-no-warning-as-error -DWERROR=0 ... |
6.11 Filename Postfix for Libraries
1 | cmake -DCMAKE_DEBUG_POSTFIX="d" ... |
Or
1 | set_target_properties(<target> PROPERTIES DEBUG_POSTFIX "d") |
6.12 How to view dependencies
1 | cmake -B build --graphviz=build/graph/graph.dot |
7 Install
We can get binary distributions from Get the Software:
1 | wget https://github.com/Kitware/CMake/releases/download/v3.21.2/cmake-3.21.2-linux-x86_64.tar.gz |