简介
cmake自定义模块(Module)
- 项目一般从CMakelists.txt开始编写,但是随着时间的推移,CMakeLists.txt文件会变得越来越大,这个时候我们就需要将CMakeLists.txt文件拆开分成几个小的模块,这样做的好处是
- 主文件CMakeList.txt易读性强
- CMake模块能够在其他项目中重复使用
- 和CMake的函数相比,模块能够帮助我们限制变量的作用域
cmake 指定C++版本
在 CmakeLists.txt 中增加对 C++ 版本的定义。方法如下:
SET(CMAKE_CXX_STANDARD 11)
生成 Makefile 的时候指定,方法如下:
cmake -DCMAKE_CXX_STANDARD=11 ..
for循环
- foreach:
1
2
3
4
5set(VAR a b c)
foreach(f ${VAR})
message(${f})
endforeach()
cmake 加入调试信息
- 首先,加入:
SET(CMAKE_BUILD_TYPE "Debug")
- 然后在下面加入:
SET(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb")
SET(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")
execute_process()
execute_process 命令将从当前正在执行的CMake进程中派生一个或多个子进程,从而提供了在配置项目时运行任意命令的方法。可以在一次调用 execute_process 时执行多个命令。但请注意,每个命令的输出将通过管道传输到下一个命令中。该命令接受多个参数:
COMMAND
:子进程命令行。WORKING_DIRECTORY
:指定应该在哪个目录中执行命令。TIMEOUT
:如果在指定的时间内(以秒为单位计算,允许有小数位)子进程执行仍未完成,则将会被中断。RESULT_VARIABLE
:包含进程运行的结果。这要么是一个整数表示执行成功,要么是一个带有错误条件的字符串。RESULTS_VARIABLE
:变量将被设置为以分号分隔的列表形式包含所有进程的结果,按给定命令参数的顺序排列。每个条目都是对应子项的整数返回码或描述错误条件的字符串。OUTPUT_VARIABLE
和ERROR_VARIABLE
将包含执行命令的标准输出和标准错误。由于命令的输出是通过管道传输的,因此只有最后一个命令的标准输出才会保存到OUTPUT_VARIABLE中。INPUT_FILE
指定标准输入重定向的文件名。OUTPUT_FILE
指定标准输出重定向的文件名。ERROR_FILE
指定标准错误输出重定向的文件名设置OUTPUT_QUIET和ERROR_QUIET
后,CMake将静默地忽略标准输出和标准错误。COMMAND_ECHO <where>
:正在运行的命令将被回送到<where>
,而<where>
将被设置为STDERR、STDOUT或NONE中的一个。设置OUTPUT_STRIP_TRAILING_WHITESPACE
,可以删除运行命令的标准输出中的任何尾随空 格。设置ERROR_STRIP_TRAILING_WHITESPACE
,可以删除运行命令的错误输出中的任何尾随空格。ENCODING <name>
:适用于windows平台,编码名称有NONE、AUTO、ANSI、OEM、UTF8或UTF-8。ECHO_OUTPUT_VARIABLE, ECHO_ERROR_VARIABLE
:标准输出或标准错误不会被专门重定向到配置的变量。COMMAND_ERROR_IS_FATAL <ANY|LAST>
:ANY
:如果命令列表中的任何命令失败,execute_process()
命令将因错误而停止。LAST
:如果命令列表中的最后一个命令失败,则execute_process()
命令会因错误而停止。列表中前面的命令不会导致致命错误。
- 如果在同一管道中同时指定了多个
OUTPUT_*
或ERROR_*
选项,则优先级顺序是未知的(应避免这种情况)。 - 如果未指定任何
OUTPUT_*
或ERROR_*
选项,则命令CMake所在进程共享输出管道。
示例:
1
2
3
4
5
6
7
8execute_process(
COMMAND
${PYTHON_EXECUTABLE} "-c" "import ${_module_name}; print(${_module_name}.__version__)"
OUTPUT_VARIABLE _stdout
ERROR_VARIABLE _stderr
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_STRIP_TRAILING_WHITESPACE
)
CMake 定义宏
add_definitions()
采用
add_definitions
定义宏,对于无值宏:add_definitions(-DMG_ENABLE_OPENSSL)
对应于C语言中的
#define MG_ENABLE_OPENSSL
而对于有值宏:
add_definitions(-DLIBEVENT_VERSION_NUMBER=0x02010800)
对应于C语言:
#define LIBEVENT_VERSION_NUMBER 0x02010800
add_compile_definitions()
add_compile_definitions
定义宏,但是这个指令只要高版本的cmake支持,所以推荐使用第1种,比如:add_compile_definitions(MG_ENABLE_OPENSSL=1)
- 对应于C语言中的
#define MG_ENABLE_OPENSSL 1
小结
- CMakeLists.txt 文件中定义的宏在后面C/C++程序中是可以直接使用的
CMake 调试
- 示例:
1
2
3
4
5include(CMakePrintHelpers)
# 打印变量 PROJECT_SOURCE_DIR
cmake_print_variables(PROJECT_SOURCE_DIR)
cmake_print_properties(TARGETS arwen-webserver PROPERTIES POSITION_INDEPENDENT_CODE)
CMake 常用命令
aux_source_directory()
- 简介:查找在某个路径下的所有源文件。
- 搜集所有在指定路径下的源文件的文件名,将输出结果列表储存在指定的变量中。
- 该命令主要用在那些使用显式模板实例化的工程上。模板实例化文件可以存储在Templates子目录下,然后可以使用这条命令自动收集起来;这样可以避免手工罗列所有的实例。
- 语法:
aux_source_directory(< dir > < variable >)
add_executable()
- 简介:使用指定的源文件来生成目标可执行文件。这里的目标可执行文件分为三类:普通可执行目标文件、导入可执行目标文件、别名可执行目标文件。分别对应下面的三种命令格式。
- 语法:
add_executable (<name> [WIN32] [MACOSX_BUNDLE] [EXCLUDE_FROM_ALL] [source1] [source2 ...])
add_executable (<name> IMPORTED [GLOBAL])
add_executable (<name> ALIAS <target>)
CMake 常用操作
生成动态库或静态库
+
添加动态链接库
如果使用gcc进行单个c文件编译的时候,有时候后面会需要添加如-L -l之类的参数,如下
gcc -o test test.c -L/usr/local/lib/ -lopencv
在这里
-L
是制定动态库的位置,-l
则是指定打算连接的动态库名字
那么在进行项目编译的时候就需要通过cmake来通过Cmakelist.txt引导make进行项目编译。
那么就需要在cmakelists.txt文件内添加动态链接库的配置,具体参数如下
- 首先申明动态库的位置,
LINK_DIRECTORIES({your_library_path})
,例如:LINK_DIRECTORIES(/usr/local/lib)
- 然后制定具体的动态库的名称,
target_link_libraries(${PROJECT_NAME} -l{library_name})
,例如:- 指定hiredis的动态链接文件 libhiredis.dylib :
target_link_libraries(${PROJECT_NAME} -lhiredis)
- 指定hiredis的动态链接文件 libhiredis.dylib :
- 首先申明动态库的位置,
引入外部静态库
include_directories(${ROOT_DIR}/3party/include)
- 用于指定头文件的搜索路径
link_directories(${ROOT_DIR}/3party/lib)
- 指定静态库或者动态库的搜索路径
target_link_libraries(sample libmath.a)
- 指定要连接的静态库,必须要有,第二个参数也可以换成math,那么将自动去搜索libmath.a或者libmath.so或者libmath.dyld动态库
arwen 机械臂项目 CMakeLists.txt 笔记
cmake_minimum_required()
- 为项目设置cmake的最低要求版本
- 示例:
cmake_minimum_required(VERSION 3.10)
project()
- 为整个项目设置名称、版本并启用语言
- 示例:
project(arwen)
set()
- 将常规变量、缓存变量或环境变量设置为给定值。
- 示例:
set(CMAKE_CXX_STANDARD 17)
include_directories()
- 向构建中添加包含目录
- 示例:
include_directories(./include)
${PROJECT_SOURCE_DIR}
- 内置变量,当前项目的顶级源目录。
${PROJECT_BINARY_DIR}
- 内置变量,为项目构建目录的完整路径(Full path to build directory for project.)
execute_process()
- 执行一个或多个子进程
- 示例:
execute_process( COMMAND ${CMAKE_COMMAND} -E make_directory ${LOG_DIR})
${CMAKE_COMMAND}
- 可执行文件“cmake(1)”的完整路径
add_subdirectory()
- 向构建中添加子目录。
- 示例:
add_subdirectory(src)
file()
- 文件操作命令
- 规则:
file(WRITE <filename> <content>...)
file(APPEND <filename> <content>...)
- 详解:
- 将
<content>
写入名为<filename>
的文件中。如果这个文件不存在,则创建该文件。如果该文件已经存在,则WRITE
模式下将会覆盖该文件,APPEND
模式下将会追加到该文件的末尾.由<filename>
指定的路径中任何不存在的目录将被创建 - 如果文件是一个构建输入,使用
configure_file()
命令仅在其内容更改时更新文件。
- 将
add_library()
- 该指令的主要作用就是将指定的源文件生成链接文件,然后添加到工程中去。该指令常用的语法如下:
1
2
3add_library(<name> [STATIC | SHARED | MODULE]
[EXCLUDE_FROM_ALL]
[source1] [source2] [...]) - 参数:
- 其中
<name>
表示库文件的名字,该库文件会根据命令里列出的源文件来创建。 - 而
STATIC
、SHARED
和MODULE
的作用是指定生成的库文件的类型。STATIC
库是目标文件的归档文件,在链接其它目标的时候使用。SHARED
库会被动态链接(动态链接库),在运行时会被加载。MODULE
库是一种不会被链接到其它目标中的插件,但是可能会在运行时使用dlopen-系列的函数。- 默认状态下,库文件将会在于源文件目录树的构建目录树的位置被创建,该命令也会在这里被调用
- 而语法中的
source1
source2
分别表示各个源文件。
- 其中
- 使用指定的源文件向项目添加库
- 示例:
add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES})
link_directories()
- 该指令的作用主要是指定要链接的库文件的路径,该指令有时候不一定需要。因为find_package和find_library指令可以得到库文件的绝对路径。该指令的常用语法如下:
1
2
3link_directories(
lib
) - 不过你自己写的动态库文件放在自己新建的目录下时,可以用该指令指定该目录的路径以便工程能够找到。
- 指定链接器将在其中查找库的目录
- 示例:
link_directories(${PROJECT_BINARY_DIR}/lib)
target_link_libraries()
- 该指令的作用为将目标文件与库文件进行链接。该指令的语法如下:
1
2target_link_libraries(<target> [item1] [item2] [...]
[[debug|optimized|general] <item>] ...) - 参数:
- 指令中的
<target>
是指通过add_executable()
和add_library()
指令生成已经创建的目标文件。 - 而
[item]
表示库文件没有后缀的名字。默认情况下,库依赖项是传递的。当这个目标链接到另一个目标时,链接到这个目标的库也会出现在另一个目标的连接线上。 - 这个传递的接口存储在
interface_link_libraries
的目标属性中,可以通过设置该属性直接重写传递接口。
- 指令中的
- 指定链接给定目标和/或其依赖项时要使用的库或标志
- 示例:
target_link_libraries(${PROJECT_NAME} PUBLIC ${Etherlab_LIBRARIES})
target_include_directories()
- 向目标添加包含目录
- 示例:
target_include_directories( ${PROJECT_NAME} PUBLIC ${PRJECT_SOURCE_DIR})
find_package()
- 加载外部项目的设置。
- 示例:
find_package(Etherlab REQUIRED)
set_target_properties()
- 目标可以具有影响如何构建它们的属性。
- 示例:
set_target_properties(arwen-planning PROPERTIES LINKER_LANGUAGE CXX)
modern cmake >= 3.11
简介
- CMake可以编译源代码,制作程序库,产生适配器(wrapper),可以用任意的顺序构建执行档
- CMake支持in-place建构(二进档和源代码在同一个目录树中)和out-of-place构建(二进档在别的目录里),因此可以很容易从一个源代码目录树中建构出多个二进档.
- CMake也支持静态与动态程式库的建构
- CMake是一种工具,它读入用于描述软件如何构建的脚本,CMake将评估这些脚本并构建出表达软件项目的内部结构,如果它处理完这些脚本并没有发现错误,CMake将生成用于实际构建项目的构建文件(可能是脚本或者IDE工程文件)
- CMake支持多种构建工具,包括一系列命令行构建工具和主流IDE,运行cmake –help可以看到在当前平台下CMake所支持的构建工具.
- CMake中将这些构建系统称作generator.因此,CMake不是构建系统,而是构建系统生成器,具体的构建工作还是要交给Make, Ninja等构建系统去做
- 运行CMake时,它会运行多项检查,在检查和评估构建脚本期间,它会将一些值缓存到CMakeCache.txt当中,这让我们在增量式开发中,可以跳过长时间的检查过程,但是CMake的缓存也会带来一些问题,比如:修改了某些配置没有办法及时的反映在下次构建中,所以有时构建出问题可以尝试删除CMake缓存文件后重新构建
什么是CMakeList.txt文件?
- 放置所有CMake命令的文件,当cmake在某个文件夹下运行时,会寻找该文件,如果找不到则报错
为什么要使用cmake?
- 跨平台
- 可配置
- 众多开源项目都只支持(或仅支持)CMake构建
构建CMake项目
CMake构建的两种形式:就地构建(in-source)和源码外构建(out-of-source)
- 就地构建:在源文件目录下构建,产生的二进制文件将于源文件生成在一个文件夹里
- 源码外构建:单独创建一个build文件夹,在该文件夹下构建二进制文件,与源代码文件分开
源码树和构建树
- 源码树是源代码目录,里面放着源代码文件和CMakeLists.txt文件
- 构建树就是存放构建系统文件和输出文件的目录
- 通过变量
CMAKE_BINARY_DIR
访问到构建树的顶层文件夹(执行cmake命令的目录) - 通过变量
CMAKE_SOURCE_DIR
访问到源码树的顶层文件夹(根CMakeLists.txt文件所在的目录). - 注:对于就地构建,源码树和构建树是等价的
CMake的执行过程分为三个阶段:
英文:
- Configure:
- Parse top level CMakeLists.txt,
- Generates cache variables,
- Only relevant in GUI builds
- Generate:
- Generate native build tool files,
- Can do cross compilation passing toolchain files
- Build:
- Uses native build tools to compile the sources,
- All it takes is cmake –build
中文:
- 配置阶段:
- CMake将解析源码树顶层的
CMakeLists.txt
,并生成一个CMakeCache.txt
以存储cache变量 - 对于CMake-GUI,该阶段由点击Configure出发,
- 对于命令版本的CMake,该阶段与生成阶段结合在一起
- CMake打印消息:
Configuring done
以表示配置阶段结束
- CMake将解析源码树顶层的
- 生成阶段:
- CMake将根据
CMakeLists.txt
和CMakeCache.txt
生成构建系统文件 - 在CMake-GUI中,该阶段由点击Generate出发
- CMake打印消息
Generating done
以表示生成阶段结束
- CMake将根据
- 构建阶段:
- 这一阶段由构建系统负责,在这个阶段中将生成项目的目标
CMake 基础
- 在经过一番搜索和学习,我开始了解 Modern CMake 的一些用法与理念,它主张放弃传统的基于变量的方法,而采用基于 target 的结构化模式,使其成为一个更可维护、更直观、更易集成、更具意义的方案。
1.1 Modern CMake 介绍
这部分内容是
An Introduction to Modern CMake
的总结,并不会讲的非常详细,希望通过几句话来高度总结各个用法,旨在了解 CMake 有哪能力,如果对某些部分感兴趣请大家自行查阅具体内容。为什么需要一个好的构建系统?如果你有以下需求,那么使用 CMake 可以从中获益
- 你想避免硬编码路径
- 你需要在多台电脑上建立一个软件包
- 你想使用CI(持续集成)
- 你需要支持不同的操作系统(甚至可能只是Unix的不同版本)
- 你想支持多个编译器
- 你想使用IDE,但也许并不是所有时间都想使用
- 你想要更好地组织程序的结构
- 你想使用别人的库
- 你想使用工具,比如Clang-Tidy,来帮助你编写代码
- 你想使用调试器
为什么一定是 CMake?其他工具不行吗?
- 各个 IDE 都支持 CMake,使用 CMake 可以有更大程度的方便, save you life
- 目前很多 C/C++ 开源项目都在使用 CMake,通过 find CMake 或者 CMake config 很容易将它们集成至你的项目中
- 使用 CMake 来管理发布,让别人更容易使用你的库
为什么要用现代 CMake?
- 通常认为 3.1+ 版本是现代 CMake。CMake 是向下兼容的,可以放心使用
- 至少应该使用你的编译器出来之后的 CMake 版本,因为它需要知道该版本的编译标志等等
- 高版本 CMake 有更多特性,可以节省数百行和数小时的 CMakeLists.txt 编写工作,长运来看更容易维护
1.2 运行CMake
经典用法
1
2
3
4~/package $ mkdir build
~/package $ cd build
~/package/build $ cmake ..
~/package/build $ make新版本可以简单一点
1
2~/package $ cmake -S . -B build
~/package $ cmake --build build安装命令
1
2
3
4
5
6
7
8#From the build directory (pick one)
~/package/build $ make install
~/package/build $ cmake --build . --target install
~/package/build $ cmake --install . # CMake 3.15+ only
#From the source directory (pick one)
~/package $ cmake --build build --target install
~/package $ cmake --install build # CMake 3.15+ only推荐使用
--build
用法- 通过
-v
显示执行的构建命令:cmake --build build -v
- 通过
--target
来选择目标:cmake --build --target install
- 通过
设置 CC 和 CXX 环境变量来选择 C/C++ 编译器
~/package/build $ CC=clang CXX=clang++ cmake ..
选择不同的工具进行构建
- make 通常是默认的,通过 cmake –help 查看支持的构建器
cmake -G"My Tool"
设置构建器,例如cmake -S . -B buildXcode -G"Xcode"
通过
-D
来设置选项,例如cmake -S . -B build -DCMAKE_INSTALL_PREFIX=dist
--trace
打印CMake configure
阶段的输出,例如cmake -S . -B build --trace
不好的 CMake 的用法
- 不要使用全局函数,例如
link_directories
,include_libraries
,add_definitions
等,请你忘记它们 - 不要滥用
PUBLIC
,除非有依赖传递,否则请你使用PRIVATE
替换PUBLIC
- 不要使用
GLOB
来添加文件 - 不要直接链接文件,而是链接目标
- 链接时千万不要跳过
PUBLIC/PRIVATE
,这会导致未来的链接都没有关键字
- 不要使用全局函数,例如
良好的 CMake 用法
- 把 CMake 视作代码,保持它的整洁和可读性
- 围绕 target 构建你的 CMake。将需要的信息打包在 target 里,然后链接那个目标
- 导出你的接口
- 写 Config.cmake 文件,这是一个库作者应该做的,可以方便别人使用你的库
- 使用 ALAS 目标,以保持使用一致性
- 将常用的功能提取成函数或者宏,通常函数更好
- 使用小写的函数名,全大写是变量
- 使用 cmake_policy 或者 range of versions
1.3 基础语法
最低版本
cmake_minimum_required(VERSION 3.1)
指定最低版本cmake_minimum_required(VERSION 3.7...3.18)
CMake 3.12+ 后,可以指定版本范围
Project
VERSION
指定版本,并设置一系列变量,例如MyProject_VERSION
等DESCRIPTION
项目的描述LANGUAGES
支持C/CXX/Fortran/ASM/CUDA(3.8+)/CSharp(3.8+)/SWIFT(3.15+)
, C/C++ 为默认值
示例:
1
2
3project(MyProject VERSION 1.0
DESCRIPTION "Very nice project"
LANGUAGES CXX)生成可执行文件
add_executable(one two.cpp three.h)
生成库
- 库类型包括
STATIC
、SHARED
、MODULE
、OBJECT
等。如果没有指定库类型,那么BUILD_SHARED_LIBS
的值将决定编译STATIC
或者SHARED
- 有些库并不需要编译,例如
header only
库。基于target
的思想,我们可以将它们打包在一种叫INTERFACE
的库中。
- 库类型包括
示例
add_library(one STATIC two.cpp three.h)
给 target 添加属性
target_include_directories
为 target 添加include
路径PUBLIC
对于可执行文件而言意义不大,对于库来说,它让 CMake 知道链接这个目标的目标也需要这个 include 目录。也就是 “我自己要用,其他链接我的也要用”,具有传递性PRIVATE
表示 “我自己用,其他人不用”INTERFACE
表示 “我自己不用,其他链接我的要用”- target 的属性包括 include 文件夹、需要链接的库、编译选项、宏定义等,这些都可以通过
target_link_libraries
进行传递
示例
1
2
3
4
5# PUBLIC 表示外部也需要这个 include 目录
target_include_directories(one PUBLIC include)
add_library(another STATIC another.cpp another.h)
# 由于具有传递性,another 可以连接 one 的 include 目录
target_link_libraries(another PUBLIC one)
1.4 变量与缓存
局部变量
set(MY_VARIABLE "value")
设置局部变量,其作用域为当前文件夹,以及add_subdirectory
进入的文件夹set(MY_VARIABLE "value" PARENT_SCOPE)
将作用域设置为父目录,通常用在子目录向父目录传递信息set(MY_LIST "one" "two")
会在变量值中间加入“;”
,等价于set(MY_LIST "one;two")
缓存变量
- 通过
set(MY_CACHE_VARIABLE "VALUE" CACHE STRING "Description")
设置缓存变量 - 缓存变量将持久存在于 CMakeCache.txt 中
- 通过
cmake -DXXX
命令传递的参数为缓存变量
- 通过
环境变量
set(ENV{variable_name} value)
设置环境变量$ENV{variable_name}
获取环境变量
属性
- 属性有点像变量,但它依附在某个 target 或者文件、目录上。许多属性的初始值来自于
CMAKE_
开头的变量,例如设置CMAKE_CXX_STANDARD
,将会设置 target 的CXX_STANDARD
属性初始值。 set_property
用于设置属性,get_property
用于获取属性
- 属性有点像变量,但它依附在某个 target 或者文件、目录上。许多属性的初始值来自于
1.5 用CMake编程
控制流
- 利用 if 语句来控制程序流
- if 语句中支持的关键字包括:
- 一元判断:
NOT、‘TARGET’、EXISTS、DEFINED
等 - 二元判断:
STRQUAL,AND,OR,MATCH,VERSION_LESS,VERSION_LESS_EQUAL
- 一元判断:
示例
1
2
3
4
5
6
7
8
9
10if(variable)
# If variable is `ON`, `YES`, `TRUE`, `Y`, or non zero number
else()
# If variable is `0`, `OFF`, `NO`, `FALSE`, `N`, `IGNORE`, `NOTFOUND`, `""`, or ends in `- NOTFOUND`
endif()
# If variable does not expand to one of the above, CMake will expand it then try again
if(NOT TARGET libA OR EXISTS "test.xml")
# If libA or test.xml exist
endif()生成器表达式, generator-expressions
- CMake 大多数命令在 configure 阶段就被执行了,而生成器表达可以在 build 阶段,或者 install 阶段被执行
示例
1
2
3
4
5target_include_directories(MyTarget
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)宏与函数
- 它们的区别:函数里头的变量作用域为该函数,而宏没有作用域
- 推荐用函数,因为它不会 “泄露” 变量。即使想要从函数中传递某些变量给外面,可以主动通过 PARENT_SCOPE 来设置
参数
- 可以通过
cmake_parse_arguments
来解析函数参数
- 可以通过
1.6 与你的代码进行交互
- 从 CMake 中获取信息,并生成代码。configure_file 命令可以实现这个。
- 读取文件,例如从 version.h 中获取版本信息。file 命令可以实现这个。
1.7 如何组织你的工程
示例
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- project
- .gitignore
- README.md
- LICENCE.md
- CMakeLists.txt
- cmake
- FindSomeLib.cmake
- something_else.cmake
- include
- project
- lib.hpp
- src
- CMakeLists.txt
- lib.cpp
- apps
- CMakeLists.txt
- app.cpp
- tests
- CMakeLists.txt
- testlib.cpp
- docs
- CMakeLists.txt
- extern
- googletest
- scripts
- helper.py在 configure 阶段运行命令
execute_process
在 configure 阶段运行命令
示例:
1
2
3
4
5
6
7
8
9
10find_package(Git QUIET)
if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git")
execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
RESULT_VARIABLE GIT_SUBMOD_RESULT)
if(NOT GIT_SUBMOD_RESULT EQUAL "0")
message(FATAL_ERROR "git submodule update --init failed with ${GIT_SUBMOD_RESULT}, please checkout submodules")
endif()
endif()在 build 阶段运行命令
示例:
1
2
3
4
5
6
7
8
9find_package(PythonInterp REQUIRED)
add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/include/Generated.hpp"
COMMAND "${PYTHON_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/scripts/GenerateHeader.py" --argument
DEPENDS some_target)
add_custom_target(generate_header ALL
DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/include/Generated.hpp")
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/include/Generated.hpp DESTINATION include)cmake -E <mode>
运行内置的一些命令,例如解压、复制等
添加特性
1.1 C++11及以后
CMake 3.8+: Meta compiler features
- target_compile_features(myTarget PUBLIC cxx_std_11) 开启 c++11 特性,当然你也可以选择 cxx_std_14 和 cxx_std_17
- set_target_properties(myTarget PROPERTIES CXX_EXTENSIONS OFF),关闭扩展特性
CMake 3.1+: 全局和属性设置
- 全局变量设置
1
2
3set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF) - 属性设置
1
2
3
4
5set_target_properties(myTarget PROPERTIES
CXX_STANDARD 11
CXX_STANDARD_REQUIRED YES
CXX_EXTENSIONS NO
)
- 全局变量设置
其他
1.1
地址无关代码(Position independent code; PIC),CMake 会自动将 -fPIC 添加到 SHARED 和 MODULE 库中,当然你也可以显示指定
- 作用于全局变量,
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
- 或者作用于 target,
set_target_properties(lib1 PROPERTIES POSITION_INDEPENDENT_CODE ON)
- 作用于全局变量,
小型库
- 如果你需要 dl 库,那么这么写最简单
target_link_libraries(libA PRIVATE ${CMAKE_DL_LIBS})
- 如果你需要 dl 库,那么这么写最简单
有些库就没有这么方便了,例如 m, pthread 等,但你可以这么写
1
2
3
4find_library(MATH_LIBRARY m)
if(MATH_LIBRARY)
target_link_libraries(MyTarget PUBLIC ${MATH_LIBRARY})
endif()Interprocedural optimization,运行时优化(link time optimization),即 -flto
- CMake 3.9+ 支持设置全局变量开启 set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON)
- CMake 3.8+ 支持设置属性
1
2set_target_properties(myTarget PROPERTIES
INTERPROCEDURAL_OPTIMIZATION ON) - 通过 check_ipo_supported 来检查当前版本是否支持 LTO
1
2
3
4
5
6include(CheckIPOSupported)
check_ipo_supported(RESULT result)
if(result)
set_target_properties(foo PROPERTIES
INTERPROCEDURAL_OPTIMIZATION TRUE)
endif()
在 CMake 中可以配合其他工具
- CCache,使用它来加快编译速度
- Clang tidy,对你的代码做静态扫描
- Include what you use, 检查冗余头文件
CMake Modules 非常有用,简单介绍一些常用的
- CMakeDependentOption,根据一组变量来设置选项,例如
1
2include(CMakeDependentOption)
cmake_dependent_option(BUILD_TESTS "Build your tests" ON "VAL1;VAL2" OFF) - CMakePrintHelpers,用于打印属性或变量
- CheckCXXCompilerFlag,用于判断是否支持某个编译选项
- WriteCompilerDetectionHeader,与 CheckCXXCompilerFlag 类似,但它更强大。它可以找到编译器支持的特性列表,并生成 C++ 头文件,让你知道哪些特性可用
- try_compile/try_run,可以让尝试编译(运行)一些代码,这可以让你获得系统能力的信息
- CMakeDependentOption,根据一组变量来设置选项,例如
调试CMake代码
- 打印永远是调试的最简单的办法
1
2
3
4
5
6
7
8message(STATUS "MY_VARIABLE=${MY_VARIABLE}")
include(CMakePrintHelpers)
cmake_print_variables(MY_VARIABLE)
cmake_print_properties(
TARGETS my_target
PROPERTIES POSITION_INDEPENDENT_CODE) - –trace-source=”filename” 让你观察 CMake 文件到底发生了什么,例如
cmake -S . -B build --trace-source=CMakeLists.txt
- 打印永远是调试的最简单的办法
引入其他项目
- Submodule
- 通过
git submodule add ../../owner/repo.git extern/repo
将extern
添加为子仓库 - 可以在 CMake 中内置子仓库更新命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18find_package(Git QUIET)
if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git")
# Update submodules as needed
option(GIT_SUBMODULE "Check submodules during build" ON)
if(GIT_SUBMODULE)
message(STATUS "Submodule update")
execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
RESULT_VARIABLE GIT_SUBMOD_RESULT)
if(NOT GIT_SUBMOD_RESULT EQUAL "0")
message(FATAL_ERROR "git submodule update --init failed with ${GIT_SUBMOD_RESULT}, please checkout submodules")
endif()
endif()
endif()
if(NOT EXISTS "${PROJECT_SOURCE_DIR}/extern/repo/CMakeLists.txt")
message(FATAL_ERROR "The submodules were not downloaded! GIT_SUBMODULE was turned off or failed. Please update submodules and try again.")
endif()
- 通过
- Submodule
DownloadProject
- 在 CMake 3.11 以前,主要下载命令都是在 build 阶段完成的(例如 ExternalProject_Add)。这就导致你无法使用 add_subdirectory。
- CMake 3.11之前,如果想要在 configure 阶段下载,参考 Crascit/DownloadProject
Fetch(CMake 3.11+)
- CMake 3.11 后,你可以使用 FetchContent 在 configure 阶段下载文件或者项目
测试
1.1
通常不在子目录时编译测试代码
示例
1
2
3if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND BUILD_TESTING)
add_subdirectory(tests)
endif()通过
add_test(NAME TestName COMMAND TargetName)
注册测试GoogleTest
- 推荐通过以 git submodule 的形式引入 GoogleTest
- 可以通过作者提供的 AddGoogleTest 工具引入 GoogleTest
- 通过 FetchContent 引入 GoogleTest
Catch
- Catch 是个 header only 库,如果直接将它的 include 引入你的仓库中,你需要做一些准备,以便使用
1
2
3
4# Prepare "Catch" library for other executables
set(CATCH_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/extern/catch)
add_library(Catch2::Catch IMPORTED INTERFACE)
set_property(Catch2::Catch PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${CATCH_INCLUDE_DIR}") - 通过 ExternalProject、FetchContent 或者 git submodule 引入 Catch,那么直接 add_subdirectory 即可
- Catch 是个 header only 库,如果直接将它的 include 引入你的仓库中,你需要做一些准备,以便使用
Exporting and Installing
1.1
- 关于 Installing,参考 CMake之install方法的使用
- 关于 Exporting,可以将 build directories 信息导出,以供其他工程使用(注意与 Installing 的区别:Installing 导出的是安装目录的信息)
- 关于 Package ,没怎么用过,似乎是将你需要的内容进行打包
寻找库
1.1
- 介绍了引入 CUDA、OpenMP、Boost、MPI、ROOT、Minuit2 等库的标准姿势