本文并非入门保姆教程,仅是个人使用CMake过程中踩过的坑的一些总结
CMake 详细说明参考官方文档 https://cmake.org/cmake/help/latest/index.html
,其中latest
为最新版本版本,不同 CMake 版本,API 有差异,请根据当前项目设置的最低版本来参考,高版本 API 在低版本无法使用。3.20
之后的文档会标记该 API 的生效版本
cmake 的优势不是性能和易用性,而是
通用性
与跨平台
。感谢 C 委员会的大力推广,几乎支持市面上所有通用编译环境,以及大部分开源三方库均支持 cmake
核心概念
Target
:目标单元,在CMake中,target是一个非常核心的概念,与其他现代化工程系统中的target类似,由add_library
/add_executable
/add_custom_target
这三种方式生成,前两者很好理解,库和执行文件,第三种则比较特殊,通常用于执行自定义命令,如:调用protoc编译proto文件、编译完成时拷贝数据等等- Generator:生成器,可以理解为编译系统,如:
Ninja
/Unix Makefiles
/Visual Studio
/Xcode
- cmake-commands:cmake 命令,通常写在
CMakeLists.txt
/*.cmake
文件中调用的内置语法和函数都称之为 cmake 命令 - cmake-generator-expressions:生成器表达式,一种特殊的表达式,
编译过程才生效
- Command-Line:cmake 控制台命令,即在终端控制台使用的命令,可以用于触发配置和编译之外,还可以用于文件操作以及解压缩等
版本选择
每个可以独立编译的 CMakeLists.txt
首行都应该加上最低版本限制,避免出现运行的 CMake 版本过低导致不明错误,如:
cmake_minimum_required(VERSION 3.14)
关于 cmake 版本主要需要考虑操作系统以及 IDE 的兼容,实际使用时尽可能使用更新版本的 cmake 可以避免一些不必要的错误。
更高版本的 cmake 意味着可以使用更先进的 API,同时部分 OEM 系统也可能无法支持,根据项目使用场景合理选择 cmake 版本,在选定一个最小版本之后翻阅文档时也应该以该版本的文档为准。
目标编译系统
cmake 与Google GN
类似,属于meta-build
(源编译)系统,有自己的交互语法,使用时需要先将自身的语法翻译成其他编译系统,这个翻译过程称之为configure
(配置),在执行配置命令时可以通过-G XXX
来指定翻译的目标编译系统,在未指定目标编译时 cmake 会默认指定一个Generator
,如下表:
默认编译系统 | |
---|---|
Linux | Unix Makefiles(Makefile) |
macOS | Unix Makefiles(Makefile) |
Windows | Visual Studio(.sln/.vcxproj) |
除了上述平台编译系统外,cmake 还支持跨平台编译系统Google Ninja
,Ninja 属于目标编译系统,且效率很高,默认会根据系统处理器内核数来分配编译线程数,如果条件允许,尽可能使用Ninja作为目标编译系统
,使用时需要安装ninja
到环境变量PATH
中,在 cmake 配置时加上-G Ninja
即可,当存在CMakeCache.txt
时修改-G
需要先将缓存文件CMakeCache.txt
删除,CMakeCache.txt
在编译根目录
工作流
cmake 的运行大致分两到三步,下述样例属于cmake 控制台命令
:
- 配置:输入源文件目录,指定目标编译系统,添加编译选项,生成目标编译系统
- 编译:输入目标编译系统,执行编译
- 安装(可选):将编译产物安装到指定位置(需要 CMakeLists.txt 中编写安装规则)
# 新建编译缓存路径
mkdir -p out/debug && cd out/debug
# 执行配置
cmake ../.. -G Ninja -DCMAKE_BUILD_TYPE=Debug
# 执行编译
cmake --build .
# 安装(高版本cmake支持)
cmake --install .
# 低版本cmake可用
cmake --build . --target install
步骤二编译时也可以使用目标编译系统的编译命令触发编译,需要编写跨平台编译脚本时,使用
cmake --build .
可以适配任何环境
配置
配置阶段的参数主要为以下几种:
-G Generator
:用于指定目标编译系统,未指定时取 cmake默认编译系统。如果条件允许,推荐使用-G Ninja
-DCMAKE_BUILD_TYPE=Debug/Release/RelWithDebInfo/MinSizeRel
:用于指定编译类型-DCMAKE_<LANG>_COMPILER=clang
:用于指定语言编译器,默认由 cmake 搜索指定,如:-DCMAKE_CXX_COMPILER=clang
。一般来说,CMAKE_C_COMPILER
和CMAKE_CXX_COMPILER
分别指定 C 和 C 的编译器,如修改则需要同时指定-DCMAKE_TOOLCHAIN_FILE=toolchain.cmake
:用于指定交叉编译工具链,一般用于非本地平台编译,如 Android,ARM 平台编译等-DKey=Value
:用于配置CMakeLists.txt
或者工具链中的option
选项等
cmake 执行配置时从指定路径下的CMakeLists.txt
开始加载,遇到第一个project(xxx)
时开始检查编译环境中的编译器,执行完所有代码后将全局变量保存至CMakeCache.txt
文件,再次执行配置时不会再修改全局变量,所以遇到一些非预期错误时,请先删除缓存路径下的CMakeCache.txt
文件。
编译与安装
执行编译时可以通过添加参数 --
来为目标编译器添加编译选项,如:为 gcc 添加多线程编译可以添加
cmake --build . -- -j8
执行安装时,需要确认CMakeLists.txt
文件中已编写安装规则,通常需要指定安装那些文件,以及这些文件相对于CMAKE_PREFIX_PATH
的位置
如已指定-G Ninja,则无需使用-j,Ninja默认启用多线程编译
常用语法
以下为最基础的样例
代码语言:text复制# 指定最低cmake版本要求
cmake_minimum_required(VERSION 3.14)
# 创建项目标识
project(mylib)
# 添加名为mylib的目标,类型为动态库
add_library(mylib SHARED lib.cc lib.h)
# 添加名为myexe的目标,类型为可自行文件
add_executable(myexe main.cc)
# 为myexe添加对mylib的链接关联
target_link_libraries(myexe PUBLIC mylib)
在实际跨平台项目中,由于涉及到平台差分,不同编译器具有不同的编译选项,以及复杂的工程目录结构,远比样例代码复杂,以下为常见项目结构
代码语言:text复制.
├── CMakeLists.txt // 入口cmake文件
├── include // 本项目用于导出的头文件
├── mylib
│ └── CMakeLists.txt // 源文件cmake配置
├── test
│ └── CMakeLists.txt // 测试代码cmake配置
└── third_party
└── crbase // crbase三方库
在项目结构较为复杂时,建议使用多个多级 CMakeLists.txt
来描述,如在入口 cmake 文件中通过 add_subdirectory()
来关联子目录,如:
# 入口cmake文件
cmake_minimum_required(VERSION 3.10)
project(mylib)
option(build_with_test "是否编译测试代码" ON)
add_subdirectory(third_party/crbase ${CMAKE_BINARY_DIR}/crbase)
add_subdirectory(mylib)
if (build_with_test)
add_subdirectory(test)
endif ()
########## 文件分隔符 ##########
# 源文件cmake配置
add_library(mylib SHARED lib.cc lib.h)
target_link_libraries(mulib PUBLIC crbase)
########## 文件分隔符 ##########
# 测试代码cmake配置
add_executable(test tests.cc)
target_link_libraries(test PUBLIC mylib)
每一个
add_subdirectory(subpath [subpath])
都会在缓存路径新建一个对应的文件夹,定义在<subpath>/CMakeLists.txt
中的target产物也在该缓存文件夹
平台差分
跨平台项目中通常会遇到不同平台参与编译的头文件不一样,或者编译选项不同,在 cmake 里有一些描述平台的系统变量:
CMAKE_SYSTEM_NAME
:描述目标平台名称,可以理解为运行编译产物的操作系统,如:Windows | Darwin | Linux | Android | iOS
,交叉编译时由工具链指定CMAKE_SYSTEM_PROCESSOR
:描述目标处理器类型,交叉编译时由工具链指定CMAKE_HOST_SYSTEM_NAME
:描述本地平台名称,可以理解为执行编译动作的操作系统,如:Windows | Darwin | Linux
CMAKE_HOST_SYSTEM_PROCESSOR
:描述本地处理器类型
为了跨平台差分使用方便,一般会在一个地方检测当前需要编译的平台变量,如:
代码语言:text复制# 检测当前编译平台
# iOS没有官方工具链,三方工具链部分设置的名称为iOS,部分为IOS,此处统一改成小写
string(TOLOWER ${CMAKE_SYSTEM_NAME} __system_name)
if (${__system_name} STREQUAL "darwin")
set(OS_MACOS TRUE)
elseif (${__system_name} STREQUAL "linux")
set(OS_LINUX TRUE)
elseif (${__system_name} STREQUAL "windows")
set(OS_WINDOWS TRUE)
elseif (${__system_name} STREQUAL "android")
set(OS_ANDROID TRUE)
elseif (${__system_name} STREQUAL "ios")
set(OS_IOS TRUE)
else ()
message(FATAL_ERROR "Unsupported system : [${CMAKE_SYSTEM_NAME}]")
endif ()
unset(__system_name)
后续需要根据不同平台走进不同分支时可以:
代码语言:text复制if (OS_MACOS)
message(STATUS "Target system is macOS")
elseif (OS_LINUX)
message(STATUS "Target system is Linux")
elseif (OS_WINDOWS)
message(STATUS "Target system is Windows")
elseif (OS_ANDROID)
message(STATUS "Target system is Android")
elseif (OS_IOS)
message(STATUS "Target system is iOS")
endif ()
在交叉编译时,
CMAKE_SYSTEM_NAME
与CMAKE_HOST_SYSTEM_NAME
是不同的,正因为编译环境与运行环境不同,所以才叫交叉编译
变量以及内置变量
为了便于阅读和维护,通常会使用变量来保存一些内容,cmake 中变量分为常规变量
、缓存变量
和环境变量
,普通变量直接设置尽在当前 CMakeLists.txt
及子项目(通过 add_subdirectory
添加的项目)中生效,可取消设置,缓存变量则会写到 CMakeCache.txt
缓存文件中全局可用,如:
# 常规变量
# set(<variable> <value>... [PARENT_SCOPE])
set(NORMAL_VAR "normal variable")
unset(NORMAL_VAR)
# 缓存变量
# set(<variable> <value>... CACHE <type> <docstring> [FORCE])
set(CACHE_VAR "cache variable" CAHCE STRING "description")
# 环境变量
# set(ENV{<variable>} [<value>])
set(ENV{PATH} "$ENV{PATH}:${CMAKE_CURRENT_LIST_DIR}")
# 获取变量
message(STATUS "NORMAL_VAR = ${NORMAL_VAR}")
message(STATUS "CACHE_VAR = ${CACHE_VAR}")
message(STATUS "ENV_PATH = $ENV{PATH}")
可以通过 ${<variable>}
获取变量的值,部分命令及表达式使用的是变量名。cmake 中内置很多系统变量,用于查询或修改系统设置,完整文档参考 cmake-variables,除了上述 平台差分中提到的四个变量,常用的变量还有:
CMAKE_PROJECT_NAME
:顶层项目名称,由project(xxx)
指定PROJECT_NAME
:多级项目时最后一个项目名称,由project(xxx)
指定CMAKE_SOURCE_DIR
:获取入口
cmake 文件所在路径,相对路径时建议使用CMAKE_CURRENT_LIST_DIR
CMAKE_CURRENT_LIST_DIR
:获取当前
cmake 文件(可以是CMakeLists.txt
,也可是xxx.cmake
)所在路径,CMAKE_CURRENT_LIST_DIR
更为常用CMAKE_BINARY_DIR
:顶层缓存路径,即执行 cmake 配置的路径CMAKE_CURRENT_BINARY_DIR
:当前缓存路径,add_subdirectory(subproject subpath)
添加的subpath
PROJECT_BINARY_DIR
:当前项目缓存路径,即最后一个 project 所在路径CMAKE_BUILD_TYPE
:编译类型,常用有Debug
/Release
,RelWithDebInfo
/MinSizeRel
不常用CMAKE_<LANG>_FLAGS
:编译选项,<LANG>
为编译语言,如:CMAKE_C_FLAGS
/CMAKE_CXX_FLAGS
CMAKE_<LANG>_COMPILER
:编译器信息
编译选项
在 cmake 中添加编译选项主要通过CMAKE_<LANG>_FLAGS
来设置编译选项,CMAKE_C_FLAGS
/CMAKE_CXX_FLAGS
分别指 C 和 C 编译选项。链接选项有CMAKE_STATIC_LINKER_FLAGS
/ CMAKE_SHARED_LINKER_FLAGS
/ CMAKE_EXE_LINKER_FLAGS
分别指静态库、动态库、可执行文件的链接选项。CMAKE_XXX_FLAGS
为字符串类型,通常使用方式为
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DMACRO_OPTION=1")
上述编译参数为传统方式,整个**
CMakeLists.txt
**生效,已不推荐使用,新版有类似面向对象的参数模式
该使用方式不利于修改,实际使用中一般会选择 cmake 其他命令还辅助添加,以下为常用命令,注意最低cmake版本要求
:
target_compile_features
:编译特征支持检查target_precompile_headers(3.16 )
:预编译头文件target_compile_definitions
/add_definitions
:宏定义target_compile_options
/add_compile_options
:编译选项,通常用于修改编译器参数,需要搭配编译器一起使用target_include_directories
/include_directories
:头文件查找路径target_link_directories(3.13 )
/link_directories
:库文件查找路径target_link_libraries(3.13 )
/link_libraries
:链接库名称target_link_options(3.13 )
/add_link_options(3.13 )
:链接选项
# 分别添加`C11`和`C 14`特征支持检查
target_compile_features(mylib PUBLIC c_std_11 cxx_std_14)
# 添加预编译头文件,通常用于编译提速
target_precompile_headers(mylib PRIVATE precompile.h)
# 相当于-DFoo=1
target_compile_definitions(mylib PUBLIC -DFoo=1)
# 表达式编译选项
target_compile_options(mylib PUBLIC -fno-exceptions
PRIVATE $<$<COMPILE_LANGUAGE:C>:${__CFLAGS_C}> # C编译选项
PRIVATE $<$<COMPILE_LANGUAGE:CXX>:${__CFLAGS_CXX}> # C 编译选项
PRIVATE $<$<CXX_COMPILER_ID:GNU>:${__CFLAGS_CXX_GNU}> # GNU编译器生效
$<$<CXX_COMPILER_ID:Clang>:${__CFLAGS_CXX_CLANG}> # Clang编译器生效
$<$<CXX_COMPILER_ID:AppleClang>:${__CFLAGS_CXX_CLANG}>
)
# 添加头文件搜索路径,相当于 -Iinclude
target_include_directories(mylib PUBLIC include)
# 添加库文件查找路径,相当于 -Llib
target_link_directories(mylib PUBLIC lib)
# 添加库链接,相当于 -lfoo
target_link_libraries(mylib PUBLIC foo)
# 添加链接选项,启用lld链接器
target_link_options(mylib PUBLIC -fuse-ld=lld)
属性继承 (手动划重点)
代码语言:c 复制
target_
开头的一些设置参数的函数是CMake3之后支持的,可以理解为它将target定义成了一个对象,对象中包含了若干成员(编译参数)
// 以下为解释target罗列的伪代码
struct Target {
std::list<meta> include_directories;
std::list<meta> compile_options;
// others
};
std::list<meta> global_include_directories;
std::list<meta> global_compile_options;
target_
开头的命令表示针对参数指定的目标生效
,如:Target.include_directories- 不带
target_
开头的命令表示当前 CMakeLists.txt极其子CMakeLists.txt全局生效
,如:global_include_directories - 一般来说尽量避免添加全局参数,容易造成属性污染。合理利用属性继承能让依赖变得清晰
以target_include_directories
为例,详细说明参考官方文档
target_include_directories(<target> [SYSTEM] [AFTER|BEFORE]
<INTERFACE|PUBLIC|PRIVATE> [items1...]
[<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])
其中SYSTEM
和AFTER|BEFORE
参数不常用,重点讨论继承关系PUBLIC|PRIVATE|INTERFACE
PUBLIC
-显式依赖
:表示当前目标生效,且依赖目标也生效。PRIVATE
-隐式依赖
:表示仅当前目标生效,依赖目标不生效。INTERFACE
:用于INTERFACE
类型的目标(如:导入库,空库),依赖目标生效,继承方式等同PUBLIC
。
# mylib实际包含路径为 include src
target_include_directories(mylib
PUBLIC include
PRIVATE src
)
# mylib实际依赖库为 libpublic libprivate
target_link_libraries(mylib
PUBLIC libpublic
PRIVATE libprivate
)
# 为myexe添加mylib链接
# myexe实际包含的路径为 include,不包含 src
# myexe实际依赖库为 libpublic,不包含 libprivate
target_link_libraries(myexe PUBLIC mylib)
其余 target_
开头的属性设置均适用该继承方式,一般来说,INTERFACE
使用情况较少,通常用于符号导出等特殊场景。在项目 API 中未包含三方库时,可以选择 PRIVATE
依赖以中断该继承,如果不明确依赖关系,可以写成 PUBLIC
。
强烈建议所有编译选项尽可能以target为单位
强烈建议所有编译选项尽可能以target为单位
强烈建议所有编译选项尽可能以target为单位
如 libA 需要 include pathA,且 libA 的头文件中包含了 pathA 的定义,libB 依赖 libA 时无需再手动添加 include pathA,target 应当做到自给自足
自定义目标
在 cmake 中,除了库和执行文件可以作为目标,一些自定义操作
也可以作为目标,例如编译前需要下载数据,编译完成时将数据拷贝至指定目录等。
通过可以使用 add_custom_command
/ add_custom_target
命令来添加自定义操作。
生成文件的自定义目标
假设用于需要将一个文件转换成另外一个文件,如:protobuf 通过 IDL 生成源文件,样例如下:
代码语言:text复制add_custom_command(
OUTPUT out.cc
COMMAND protoc -i in.proto -o out.cc
DEPENDS in.proto
VERBATIM)
COMMAND
之后为实际运行的命令,参数需要根据执行程序作响应调整。根据这一个特征,可以扩展很多实用操作,如:编译前下载源代码,下载测试数据等
基于编译事件的自定义目标
假设用户期望在库B
编译完成时,将依赖库A
拷贝至库B
生成路径,样例如下:
add_library(libA SHARED source_a.cc)
add_library(libB SHARED source_b.cc)
target_link_libraries(libB PUBLIC libA)
# 添加编译完成事件 POST_BUILD
add_custom_command(TARGET libB POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
$<TARGET_FILE:libA>
$<TARGET_FILE_DIR:libB>
)
针对编译事件,cmake 支持 PRE_BUILD | PRE_LINK | POST_BUILD
三个时机,
PRE_BUILD
:在Visual Studio
编译系统中,时机为所有编译开始之前,其他系统时仅在PRE_LINK
之前PRE_LINK
:源文件编译成中间之后,链接成目标文件之前POST_BUILD
:链接成目标文件之后
生成器表达式
在 cmake 中,除了常规的命令行,如if(xxx)
,还支持一种特殊语法 生成器表达式
,生成器表达式与常规命令不同,常规命令在 配置
阶段生效,而生成器表达式在 编译
阶段才针对生成器进行计算评估。
上文中自定义命令 add_custom_command
里的 $<TARGET_FILE:libA>
就是经典的使用场景,配置时我们并不确定具体生成的文件路径,可以在执行阶段解析为实际变量,再例如:
target_compile_options(mylib PUBLIC -fno-exceptions
PRIVATE $<$<COMPILE_LANGUAGE:C>:${__CFLAGS_C}> # C编译选项
PRIVATE $<$<COMPILE_LANGUAGE:CXX>:${__CFLAGS_CXX}> # C 编译选项
PRIVATE $<$<CXX_COMPILER_ID:GNU>:${__CFLAGS_CXX_GNU}> # GNU编译器生效
$<$<CXX_COMPILER_ID:Clang>:${__CFLAGS_CXX_CLANG}> # Clang编译器生效
$<$<CXX_COMPILER_ID:AppleClang>:${__CFLAGS_CXX_CLANG}>
)
$<CXX_COMPILER_ID:Clang>
表示执行时当 CXX_COMPILER_ID == Clang
则返回 1
,否则返回 0
,再配合外围的 $<cond:flags>
解析得到当编译器为 Clang 时,表达式返回 ${__CFLAGS_CXX_CLANG}
,否则表达式返回空。表达式支持很多条件,具体参考 cmake-generator-expressions
,灵活运用表达式可以让 cmake避免一堆长长的if/else,使代码变得非常清晰简洁
交叉编译与工具链
交叉编译
的本质是使用指定的编译器
编译生成指定处理器平台
的中间文件,并链接指定的系统库
文件,生成最终的目标文件。与本地编译的流程并无不同,交叉
指的是执行编译过程的操作系统与运行程序的操作系统不是同一个。如:Android 系统中并无可运行的编译器,生成 Android 可执行的 ELF 文件需要借助其他操作系统。
工具链
通常用于指定系统名称、目标处理器类型、编译器、库搜索路径以及编译参数等信息,使用时在 cmake 配置阶段使用变量 CMAKE_TOOLCHAIN_FILE
指定,如:
cmake .. -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake -DCMAKE_BUILD_TYPE=Debug
本地编译时 cmake 会根据系统环境配置一些必要信息,无需指定 CMAKE_TOOLCHAIN_FILE
,遇到交叉编译时通常选择交叉编译工具链,部分 SDK 已经提供 cmake 工具链,如:Android NDK
、TDA4
等,未提供工具链的 SDK,可以通过指定编译器路径等信息来编译,也可以基于 SDK 编写工具链文件便于后续项目使用。编写 cmake 工具链参考官方文档 cmake-toolchains。
在 cmake 中,交叉编译与工具链
并非
因果关系。交叉编译除了可是使用工具链,也可以在配置阶段通过参数指定编译器等信息实现交叉编译;工具链除了可以用于交叉编译,也可用于编译系统扩展,如:vcpkg 中可用于查找内置的三方库的工具链文件
扩展工具链
微软开源项目中的工具链文件 vcpkg.cmake 为扩展查找三方库的经典样例
交叉编译工具链(高阶)
Android NDK 中交叉编译工具链 ${ANDROID_NDK}/build/cmake/android.toolchain.cmake
几乎包含交叉编译中涉及的所有改动,iOS 由于没有 Apple 没有提供官方支持,仅有开源项目 ios-cmake 可用,下文为 Linux 下 ARM 编译工具链样例:
# 指定目标系统名称,不指定时取CMAKE_SYSTEM_HOST_NAME
set(CMAKE_SYSTEM_NAME Linux)
# 指定目标处理器类型,在部分编译器中需要额外添加编译参数,不指定时取CMAKE_SYSTEM_HOST_PROCESSOR
set(CMAKE_SYSTEM_PROCESSOR arm)
# 指定系统库路径,相当于向编译器指定 --sysroot
set(CMAKE_SYSROOT /home/devel/rasp-pi-rootfs)
# 除了指定CMAKE_SYSROOT,还可以通过设置CMAKE_FIND_ROOT_PATH指定搜索路径
# 指定编译器
set(tools /home/devel/gcc-4.7-linaro-rpi-gnueabihf)
set(CMAKE_C_COMPILER ${tools}/bin/arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER ${tools}/bin/arm-linux-gnueabihf-g )
# 交叉编译中,该选项需要特别注意
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
扩展编译系统(待完善)
- FetchContent(cmake3.11 )
- ExternalProject
ExternalProject 扩展编译系统通常用于下载编译导入三方库,一般会配合两段使用,以导入 GoogleTest 为例,在根目录新建文本文件 CMakeLists.txt.in
并输入:
cmake_minimum_required(VERSION 2.8.2)
project(googletest-download NONE)
include(ExternalProject)
ExternalProject_Add(googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG master
SOURCE_DIR "${CMAKE_BINARY_DIR}/googletest-src"
BINARY_DIR "${CMAKE_BINARY_DIR}/googletest-build"
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
TEST_COMMAND ""
)
上述用于加载 ExternalProject
,下载指定源码的 master 版本,以及存放路径,还可以指定补丁等。在 CMakeLists.txt
中加入:
configure_file(
${CMAKE_CURRENT_LIST_DIR}/CMakeLists.txt.in
${CMAKE_BINARY_DIR}/googletest-download/CMakeLists.txt
)
# Configure and build the downloaded googletest source
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" .
RESULT_VARIABLE result
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/googletest-download )
if(result)
message(FATAL_ERROR "CMake step for googletest failed: ${result}")
endif()
execute_process(COMMAND ${CMAKE_COMMAND} --build .
RESULT_VARIABLE result
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/googletest-download)
if(result)
message(FATAL_ERROR "Build step for googletest failed: ${result}")
endif()
# Prevent overriding the parent project's compiler/linker settings on Windows
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
# Add googletest directly to our build. This defines the gtest and gtest_main
# targets.
add_subdirectory(${CMAKE_BINARY_DIR}/googletest-src
${CMAKE_BINARY_DIR}/googletest-build
EXCLUDE_FROM_ALL)
上述用于触发 GoogleTest
编译,并将产物配置导入当前项目,该方式不需要编译前下载 GoogleTest 源代码,且可以产物形式导入到项目中。ExternalProject_Add
也可以直接用于下载源代码,以源码形式添加到项目中一起编译,具体根据项目需要选择使用方式。
除去ExternalProject
,cmake 在 3.11 版本中加入了FetchContent
,功能更实用
合理利用扩展系统,避免将一些三方库或者数据直接塞进仓库中,能避免仓库产生不必要的体积膨胀
策略(Policies)
cmake 中有一个策略机制,用于兼容旧版 cmake,在一些特殊场景下,