Basics

1
2
3
cmake_minimum_required(VERSION 3.20) # 声明最低版本
project(project1 CXX) # 配置项目元数据
add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL]) # 子文件夹

EXCLUDE_FROM_ALL关键字用于禁用子文件夹中targets的默认构建

和系统环境有关的变量 : CMAKE_SYSTEM_NAME系统名称, CMAKE_HOST_*, CMAKE_SIZEOF_VOID_P查询32-bit or 64-bit, CMAKE_CXX_BYTE_ORDER系统字节序。[关于交叉编译的官方文档]

查询host系统的特性

1
cmake_host_system_information(RESULT <VARIABLE> QUERY <KEY>…)

配置工具链

设置C++版本。设置某个target的版本set_property(TARGET <target> PROPERTY CXX_STANDARD <standard>)

修改全局变量设置整个project的版本CMAKE_CXX_STANDARD,该命令不强制使用该版本,需要

set(CMAKE_CXX_STANDARD_REQUIRED ON)来强制规定版本

set(CMAKE_CXX_EXTENSIONS OFF)

编译器支持的特性会在configuration stage存放在CMAKE_CXX_ COMPILE_FEATURES 变量中,可用于查看某些特性是否支持

1
2
3
4
list(FIND CMAKE_CXX_COMPILE_FEATURES cxx_variable_templates result)
if(result EQUAL -1)
 message(FATAL_ERROR "I really need variable templates.")
endif()

对版本的支持特性cxx_std_98, cxx_std_11, cxx_std_14, cxx_std_17, cxx_std_20, and cxx_std_23

运行测试

1
2
3
4
try_run(run_result compile_result
 ${CMAKE_BINARY_DIR}/test_output 
 ${CMAKE_SOURCE_DIR}/main.cpp
 RUN_OUTPUT_VARIABLE output)

Targets

target: 一个逻辑对象,包含属性,描述依赖关系

构建一个target会生成一个artifact(可执行文件或库文件)喂给其他target或作为最终构建结果的一部分

定义target的命令

  • add_executable(): 定义一二进制target
  • add_library(): 定义库target
  • add_custom_target(): 定义由某个命令的结构为target。用于计算checksum,收集静态分析报告等

1
2
add_custom_target(clean_stale_coverage_files 
 COMMAND find . -name "*.gcda" -type f -delete)

Properties

cmake可以在各个层级控制target(或非target)具有的properties。如set_property(), set_directory_properties(), set_target_properties(), set_source_files_properties()等。

Dependency graph

描述targets之间依赖关系命令

  • taerget_link_libraries(): 可以控制属性的传播,常用于定义实际的库和可执行文件
  • add_dependencies(): 常用于定于用户自己的顶级target来设置构建顺序

构建系统会从我们定义的顶层targets递归向下构建子target

例如对于以下应用:

描述如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
cmake_minimum_required(VERSION 3.20.0)
project(BankApp CXX)

add_executable(terminal_app terminal_app.cpp)
add_executable(gui_app gui_app.cpp)

target_link_libraries(terminal_app calculations)
target_link_libraries(gui_app calculations drawing)

add_library(calculations calculations.cpp)
add_library(drawing drawing.cpp)

add_custom_target(checksum ALL
                  COMMAND sh -c "cksum terminal_app>terminal.ck"
                  COMMAND sh -c "cksum gui_app>gui.ck"
                  BYPRODUCTS terminal.ck gui.ck 
                  COMMENT "Checking the sums..."
)
add_dependencies(checksum terminal_app gui_app)

可视化

命令cmake --graphviz=test.dot .生成可视化描述文件,然后用(在线)工具生成图像,如

Graphviz Online (dreampuf.github.io)

Targets properties

target的一部分属性可以修改,一部分是只读的。读写某个target的属性的命令

1
2
3
4
5
get_target_property(<var> <target> <property-name>)
set_target_properties(<target1> <target2> ...
 PROPERTIES <prop1-name> <value1>
 <prop2-name> <value2> ...)
set_property(TARGET <target> PROPERTY <name> <value>) # 等效命令,还可以设置其他作用域的变量属性,如 GLOBAL, DIRECTORY, SOURCE, INSTALL, TEST, and CACHE

Transitive usage requirements

transitive usage requirements即描述有依赖关系的targets,source target(被使用的target)的属性如何传递给

destination target(使用其他target)。

set_target_properties(), target_compile_definitions(), target_link_options()等命令中的可见性关键字用于设置属性存储在target中的位置,这决定了source target中的属性(包括头文件搜索路径,编译选项等属性)是否会传递给destination target,其含义如下:

  • PRIVATE sets the property of the source target.
  • INTERFACE sets the property of the destination targets.
  • PUBLIC sets the property of the source and destination targets.

cmake通过特定前缀来区分私有属性(private property)和公有属性(interface property),对于PUBLIC/INTERFACE的属性,其属性名将会有INTERFACE_前缀。在configuration stage中,cmake会将source target中的带有INTERFACE_前缀的属性复制到destination target中。

1
2
3
target_link_libraries(<target>
 <PRIVATE|PUBLIC|INTERFACE> <item>...
 [<PRIVATE|PUBLIC|INTERFACE> <item>...]...)

target_link_libraries()命令中的可见性关键字用于设置source target的属性将会被保存到destination target中的什么位置,这决定了属性能传递多远(默认为PUBLIC),其含义如下:

  • PRIVATE appends the source value to the private property of the destination.
  • INTERFACE appends the source value to the interface property of the destination.
  • PUBLIC appends to both properties of the destination.

属性的可见性总结如下图:

解决传播属性的冲突

多个source target可能传递同名属性到一个destination target上,为了解决冲突,我们需要在source target中定义INTERFACE_LIB_VErSION,然后在destination target中选择如下策略处理冲突

  • COMPATIBLE_INTERFACE_BOOL: 检查传播到target中的属性是否可以求值为同一个bool
  • COMPATIBLE_INTERFACE_STRING: 检查传播到target中的属性是否可以求值为同一个string
  • COMPATIBLE_INTERFACE_NUMBER_MAX: 选择传播到target中的最大的属性
  • COMPATIBLE_INTERFACE_NUMBER_MIN: 选择传播到target中的最小的属性

例子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
cmake_minimum_required(VERSION 3.20.0)
project(PropagatedProperties CXX)
add_library(source1 empty.cpp)
set_property(TARGET source1 PROPERTY INTERFACE_LIB_VERSION 4)
set_property(TARGET source1 APPEND PROPERTY
 COMPATIBLE_INTERFACE_STRING LIB_VERSION
)
add_library(source2 empty.cpp)
set_property(TARGET source2 PROPERTY INTERFACE_LIB_VERSION 4)
add_library(destination empty.cpp)
target_link_libraries(destination source1 source2)

Pseudo targets

Imported targets

从外部导入的项目中的target的某些属性和传递性也可以被设置

Alias targets

1
2
add_executable(<name> ALIAS <target>)
add_library(<name> ALIAS <target>)

alias targets的属性是只读的,且无法导出

Interface libraries

  • 用于表示header-only库
1
2
3
4
5
6
7
8
9
add_library(Eigen INTERFACE
 src/eigen.h src/vector.h src/matrix.h
)
target_include_directories(Eigen INTERFACE
 $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>
 $<INSTALL_INTERFACE:include/Eigen>
)

target_link_libraries(executable Eigen) # 使用Eigen库
  • 用于设置属性
1
2
3
4
5
add_library(warning_props INTERFACE)
target_compile_options(warning_props INTERFACE 
 -Wall -Wextra -Wpedantic
) 
target_link_libraries(executable warning_props)

add_custom_command()

as a generator

1
2
3
add_custom_command(OUTPUT person.pb.h person.pb.cc
 COMMAND protoc ARGS person.proto
 DEPENDS person.proto)
1
2
3
4
5
6
add_executable(main main.cpp constants.h)
target_include_directories(main PRIVATE
 ${CMAKE_BINARY_DIR})
add_custom_command(OUTPUT constants.h 
COMMAND cp 
ARGS "${CMAKE_SOURCE_DIR}/template.xyz" constants.h)

as a target hook

hook点包括:

  • PRE_BUILD will run before any other rules for this target (Visual Studio generators only; for others, it behaves like PRE_LINK).
  • PRE_LINK binds the command to be run just after all sources have been compiled but before the linking (or archiving) the target. It doesn’t work for custom targets.
  • POST_BUILD will run after all other rules have been executed for this target.
1
2
3
4
add_executable(main main.cpp)
add_custom_command(TARGET main POST_BUILD
 COMMAND cksum 
 ARGS "$<TARGET_FILE:main>" > "main.ck")

Generator expressions

generator expressions用于在configuration/generation stage动态求值,格式:

可以嵌套generator expressions或变量,如$<UPPER_CASE:$<PLATFORM_ID>>,$<UPPER_CASE:${my_variable}> 。各种不同作用的generator expression参考[官方文档]。一些例子:

1
2
target_compile_options(tgt $<$<CONFIG:DEBUG>:-ginline-points>)
target_compile_definitions(myProject PRIVATE $<$<CMAKE_SYSTEM_NAME:LINUX>:LINUX=1>)
1
target_link_libraries(myApp PRIVATE $<IF:$<CONFIG:Debug>,checkedAlgo,fastAlgo>)

编译配置

  • target_compile_features(): Require a compiler with specific features to compile this target.
  • target_sources(): Add sources to an already defined target. (可用于根据条件添加源文件)
  • target_include_directories(): Set up the preprocessor include paths.
  • target_compile_definitions(): Set up preprocessor definitions.
  • target_compile_options(): Compiler-specific options for the command line.
  • target_precompile_headers(): Optimize the compilation of external headers.

通过设置CMAKE_EXPORT_COMPILE_COMMANDS变量可以在build目录下生成包含详细编译命令的文件compile_commands.json

预处理配置

添加头文件的搜索路径

1
2
3
target_include_directories(<target> [SYSTEM] [AFTER|BEFORE]
 <INTERFACE|PUBLIC|PRIVATE> [item1...]
 [<INTERFACE|PUBLIC|PRIVATE> [item2...] ...])

AFTER|BEFORE用于表示这些路径被添加到 INCLUDE_DIRECTORIES这个属性的前还是后。

添加宏定义

1
2
3
4
set(VAR 8)
add_executable(defined definitions.cpp)
target_compile_definitions(defined PRIVATE ABC "DEF=${VAR}")
target_compile_definitions(hello PRIVATE -DFOO)# -D后可以加空格

Configuring the headers

1
2
3
4
5
// configure.h.in
#cmakedefine FOO_ENABLE
#cmakedefine FOO_STRING1 "@FOO_STRING@"
#cmakedefine FOO_STRING2 "${FOO_STRING}"
#cmakedefine FOO_UNDEFINED "@FOO_UNDEFINED@"
1
2
3
4
5
6
add_executable(configure configure.cpp)
set(FOO_ENABLE ON)
set(FOO_STRING1 "abc")
set(FOO_STRING2 "def")
configure_file(configure.h.in configured/configure.h) # 根据当前cmake中的变量生成配置文件
target_include_directories(configure PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
1
2
// configure.cpp
#include "configured/configure.h" //使用

Configuring the optimizer

使用target_compile_options()为target独立配置或使用作用于所有targets的全局变量CMAKE_CXX_FLAGS_DEBUG, CMAKE_CXX_FLAGS_RELEASE

Reducing compilation time

Precompilation of headers

1
2
add_executable(precompiled hello.cpp)
target_precompile_headers(precompiled PRIVATE <iostream>)
1
2
// hello.cpp
int main() { std::cout << "hello world" << std::endl;} 

Unity builds

通过将多个源文件合并作为一个统一的单元编译。注意各个文件之间的名字可能冲突。[官网文档](UNITY_BUILD — CMake 3.24.1 Documentation)

方式:通过set(CMAKE_UNITY_BUILD TRUE)全局配置或逐target配置

1
2
set_target_properties(<target1> <target2> ... 
 PROPERTIES UNITY_BUILD true)

Linking

Type of libraries

  • add_library(<NAME> STATIC [<source>...]): 创建静态库(a collection of raw object files stored in an archive)
  • add_library(<NAME> SHARED [<source>...]):
  • add_library(<NAME> MODULE [<source>...]): 用于创建运行时作为插件手动加载的动态库,必须使用LoadLibrary()/dlopen()/dlsym()加载。

[add_library()官方文档]

有关object libraries的传递特性 Oh No! More Modern CMake

PIC

由target的属性POSITION_INDEPENDENT_CODE决定。Shared libraries和modules默认为ON

管理依赖

find_package()

使用find_package()寻找系统上已安装的包。find_package()通过已有的.cmake脚本(安装cmake后会在<安装目录>/share/cmake-<version>/Modules/目录下存放一些常用包的寻找脚本)根据常用的包管理工具尝试在各种可能的路径下寻找包是否安装。

1
find_package(<Name> [version] [EXACT] [QUIET] [REQUIRED])
1
2
3
4
5
6
7
8
cmake_minimum_required(VERSION 3.20.0)
project(FindPackageProtobufTargets CXX)
find_package(Protobuf REQUIRED)
protobuf_generate_cpp(GENERATED_SRC GENERATED_HEADER message.proto) # protobuf包中定义的函数
add_executable(main main.cpp ${GENERATED_SRC} ${GENERATED_HEADER})
target_link_libraries(main PRIVATE protobuf::libprotobuf) # protobuf中定义了imported target
target_include_directories(main PRIVATE
 ${CMAKE_CURRENT_BINARY_DIR})

例如在上例中find_package(Protobuf REQUIRED)会通过阈预置的FindProto.cmake脚本取寻找系统上是否安装了Protobuf包。

将局部target提升为全局target(提升后无法取消)。

1
2
3
4
5
6
find_package(Boost ${BOOST_VERSION} EXACT REQUIRED 
	COMPNENTS program_optinons graph)
if (Boost_found)
	set_target_properties(Boost::boost Boost::program_options Boost::graph
		PROPERTIES IMPORTED_GLOBAL TRUE)
endif()

ExternalProject

ExternalProject用于配置依赖,对于使用ExternalProject_Add()添加的项目cmake将会执行如下步骤:mkdir-download-update-patch-configure-build-install-test

部分选项如下:

1
2
3
4
5
6
DOWNLOAD_COMMAND <cmd>...
URL <url1> [<url2>...]
GIT_REPOSITORY <url>
GIT_TAG <tag>
PATCH_COMMAND <cmd>...
...

例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
cmake_minimum_required(VERSION 3.20.0)
project(ExternalProjectGit CXX)
add_executable(welcome main.cpp)
configure_file(config.yaml config.yaml COPYONLY)
include(ExternalProject) # 需要引入该模块
ExternalProject_Add(external-yaml-cpp
 GIT_REPOSITORY https://github.com/jbeder/yaml-cpp.git
 GIT_TAG yaml-cpp-0.6.3
) # 将会下载,配置,构建,最后安装到机器上
target_link_libraries(welcome PRIVATE yaml-cpp)

缺点,整个过程是封闭的,我们的项目在构建时无法使用其中的targets。

FetchContent

FetchContent将会在configuration stage将外部项目的target引入主项目中

FetchContent_MakeAvailable()的实际执行过程

例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
cmake_minimum_required(VERSION 3.20.0)
project(ExternalProjectGit CXX)
add_executable(welcome main.cpp)
configure_file(config.yaml config.yaml COPYONLY)
include(FetchContent) # 需要引入相应模块
FetchContent_Declare(external-yaml-cpp
 GIT_REPOSITORY https://github.com/jbeder/yaml-cpp.git
 GIT_TAG yaml-cpp-0.6.3
)
FetchContent_MakeAvailable(external-yaml-cpp)
target_link_libraries(welcome PRIVATE yaml-cpp)

安装与打包

导出targets至另一个项目

install()命令将导入一个targets list。

export()命令将targets导出到一个.cmake文件中,这样,另一个项目可以通过include()命令将该.cmake文件包括来导入targets。需要注意这种方式导出的targets使用硬编码的绝对路径进行定位。

1
2
3
4
export(TARGETS [target1 [target2 [...]]] 
 [NAMESPACE <namespace>] 
 [APPEND] FILE <path>
 [EXPORT_LINK_INTERFACE_LIBRARIES])
1
2
3
4
5
set(EXPORT_DIR "${CMAKE_CURRENT_BINARY_DIR}/cmake")
export(TARGETS calc
 FILE "${EXPORT_DIR}/CalcTargets.cmake"
 NAMESPACE Calc::
)

安装项目

1
cmake --install <dir> [<options>]

<options>如下:

  • –config : This picks the build configuration for a multi-configuration generator.
  • –component : This limits the installation to the given component.
  • –default-directory-permissions : This sets the default permissions for the installed directories (in format).
  • –prefix : This specifies the non-default installation path (stored in the CMAKE_INSTALL_PREFIX variable). It defaults to /usr/local for Unix-like systems and c:/Program Files/${PROJECT_NAME} for Windows.
  • -v, –verbose: This makes the output verbose (this can also be achieved by setting the VERBOSE environment variable).