cmake 是一个跨平台编译工具,它面向各种平台提供适配的编译系统配置文件,进而调用这些编译系统完成编译工作。cmake 进入3.x 版本,指令大量更新,一些老的指令开始被新的指令集替代,并加入了一些更加高效的指令/参数。本文归纳了cmake 3.x 版本的常用指令,方便使用时备查。
关于cmake工具的简单介绍和VS Code cmake Tools 环境配置,可以参考我的另一篇博客。本文参考资料:【公开课】现代CMake高级教程 - Bilibili 小彭老师,CMake Reference Documentation
ps: 本文是大全类文档,适合备查,完整学习
cmake
强烈建议跟随官方tutorial,会有非常好的效果
cmake 项目构建流程
典型的cmake 项目构建流程如下:
-
配置阶段 Configure:根据编写的
CmakeLists.txt
文件,以及选择的编译系统,生成该系统的构建规则文件。(例如,对make
生成Makefile
,对MSVC
生成sln
(可以在VS中打开))cmake -B build
- 以上指令可以在当前目录下创建
build
目录,而不需要事先创建并进入 - 通过
-D
设置缓存变量,格式-D<OPTION>=<VALUE>
,如:- 编译器路径:
CMAKE_C_COMPILTER
,CMAKE_CXX_COMPILER
等 - 安装路径(在Configure阶段配置):
CMAKE_INSTALL_PREFIX
- 构建模式:
CMAKE_BUILD_TYPE
- 自定义缓存变量:
-Dvar:type=value
,可以将option
设定为OFF
,即表示不启用(会覆盖CMakeLists中的默认选项)
- 编译器路径:
-G
指定生成器(Generator,即构建系统),可以通过--help
查看支持的列表-A
指定架构(For MSVC build system)-T
指定工具链,例如使用ClangCL
:-T ClangCL,host=x64
(For MSVC build system)- 推荐使用
Ninja
作为生成器,效率较高
- 以上指令可以在当前目录下创建
-
编译阶段 Build:根据生成的构建规则,调用构建系统进行构建,这一步真正输出项目目标(可执行文件、共享库等)
cmake --build build
-
构建完成后,可以在
build
目录下找到输出结果(MSVC并不是直接放在build
下,而是在构建模式对应目录下(Debug
,Release
),这与其他不同)
项目配置变量
项目配置变量是控制项目构建,以及包含项目信息的关键变量。这些变量可以在命令行配置(缓存变量),也可以在CMakeLists内部修改。
项目构建模式
一般有四种项目构建模式:
- Debug 调试模式:不优化,生成调试信息
-O0 -g
- Release 发布模式:最优化,性能最佳
-O3 -DNDEBUG
MinSizeRel
最小体积发布 :生成项目文件小,性能优化不完全-Os -DNDEBUG
RelWithDebInfo
带调试信息发布-O2 -g -DNDEBUG
- 默认为Debug模式
可以通过在CMakeLists中增加默认选项脚本的方法修改默认选项为Release
if (NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()
项目信息
使用命令project
初始化项目信息
project(<PROJECT-NAME> [<language-name>...])
project(<PROJECT-NAME>
[VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
[DESCRIPTION <project-description-string>]
[HOMEPAGE_URL <url-string>]
[LANGUAGES <language-name>...])
- language: 指定项目使用语言,默认
C CXX
- 支持
C CXX ASM FORTRAN CUDA OBJC OBJCXX ISPC
- 支持
VERSION
字段:设置项目版本号,会自动配置相关变量
该命令将初始化名为project_name
的项目,并给相关变量赋值:
PROJECT_NAME
: 项目名称CMAKE_PROJECT_NAME
:根项目名称PROJECT_SOURCE_DIR
:项目源码路径,即初始化project的CMakeLists.txt
所在路径PROJECT_BINARY_DIR
:项目输出路径,通常是./build
路径CMAKE_CURRENT_SOURCE_DIR
:当前源码路径CMAKE_CURRENT_BINARY_DIR
:当前输出路径,即当前CMakeLists.txt
所在路径,子模块中指子模块路径- 更多属性,请参考project — CMake 3.25.1 Documentation
项目语言标准
一般通过如下指令设置标准(推荐放在project
指令前,会在project语言启用时检测)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)
- 设置C标准为C11, C++设置CXX对应属性
CMAKE_C_STANDARD_REQUIRED
在启用语言时检查编译器是否支持该标准CMAKE_C_EXTENSIONS
是否启用GNU拓展语言特性(对跨平台有影响)
添加构建目标
构建目标主要有两种类型:
可执行文件 executable
使用add_executable
命令添加可执行文件目标:
add_executable(<name> [WIN32] [MACOSX_BUNDLE]
[EXCLUDE_FROM_ALL]
[source1] [source2 ...])
- 添加一个
name
的可执行文件目标,源文件来自source
- 可以是列表或者空格分开的多个文件名
- 也可以是变量
${var_name}
,目录的所有文件可以使用aux_source_directory
添加
- 其中,
source
可以省略(>3.11)
,并在后面以target_sources
的形式给出
库 library
add_library(<name> [STATIC | SHARED | MODULE]
[EXCLUDE_FROM_ALL]
[<source>...])
STATIC/SHARED
指定生成静态库/动态库,默认静态
另外还有对象库object library
add_library(<name> OBJECT [<source>...])
-
对象库不输出实际的库文件,而是可以直接被加入到其他构建目标中,如:
add_library(... $<TARGET_OBJECTS:objlib> ...) add_executable(... $<TARGET_OBJECTS:objlib> ...)
或者使用
target_link_libraries()
链接到对象库(CMAKE >3.12) -
注意:动态库不能直接引用静态库,因为动态库开启PIC,而静态库没有
- 解决方案:使用对象库替代静态库,或者为静态库配置PIC
set_property(TARGET target_name PROPERTY POSITION_INDEPENDENT_CODE ON)
- 解决方案:使用对象库替代静态库,或者为静态库配置PIC
-
另外,Windows平台如果要使用动态库,则需要添加宏
-
声明前添加
#ifdef _MSC_VER __declspce(dllimport) #endif
-
定义前加
#ifdef _MSC_VER __declspce(dllexport) #endif
-
配置构建目标
设置对象属性
set_target_properties(target_name PROPERTIES
property_name value
...)
使用set_target_properties
设置编译目标属性
这些属性包括(详细信息参考cmake-properties(7) — CMake 3.25.1 Documentation):
- 语言标准版本
- 编译器设置
- 目标输出位置
*_OUTPUT_DIRECTORY
链接三方库
使用target_link_libraries()
链接第三方库
target_link_libraries(<target> ... <item>... ...)
其中,item可以是:
- 库对象名(使用
add_library( IMPORTED)
引入) - 指向库文件的完整路径
- 库文件名,这会调用链接器自动搜索(在Linux下可以依据lib搜索)
优化方案(对使用cmake的三方库,限Linux):
find_package(LIBRARY_NAME REQUIRED)
target_link_libraries(<target> [PUBLIC] LIBRARY_NAME::library_target_name)
- 使用
find_package
会在/usr/lib/cmake
下增加.cmake
配置文件,cmake会自动搜索并链接相应的库。 - 另外,在链接库的时候,还会按照配置文件进行引用传播,引用其的对象自动引用其头文件目录。
编译时定义
使用target_compile_definitions
来配置编译时宏定义,这些定义可以在C/C++的宏中被使用
target_compile_definitions(target PUBLIC def)
- 将def设置到target的编译过程中
安装 install
install(TARGETS <target>... [destination])
以上命令用于安装对象到destination
子命令还可以是:
FILES
安装文件,如头文件等DIRECTORY
目录- 详见install — CMake 3.25.1 Documentation
部署 ctest
使用enable_testing
以启用ctest,ctest会自动执行命令,并匹配输出,自动化完成测试
以下示例增加一个ctest,并设置匹配的输出结果
add_test(NAME Usage COMMAND Tutorial)
set_tests_properties(Usage
PROPERTIES PASS_REGULAR_EXPRESSION "Usage:.*number")
如果程序输出:Usage:[any]number
,就算正确通过
还可以将测试集写为函数,增强代码复用性
function (do_test target arg result)
add_test(NAME Comp${arg} COMMAND ${target} ${arg})
set_tests_properties(Comp${arg}
PROPERTIES PASS_REGULAR_EXPRESSION "${arg} is ${result}")
endfunction()
function (do_math_test target)
do_test(${target} 4 2)
do_test(${target} -25 "(-nan|nan|0)")
endfunction()
do_math_test(Tutorial)
Scripting Commands
- cmake命令不区分大小写,不过小写命令更推荐使用
- cmake 变量,参数等区分大小写
基础设置变量
使用set
命令来设置变量:
set(<variable> <value>... [PARENT_SCOPE])
- 如果value为空格分隔的字符串(不加引号),则被认为是列表
- 列表也可以用
"a;b"
(以分号分隔,加引号)等效
CMakeLists 文件结构
通常情况下,CMakeLists文件结构如下所示:
cmake_minimum_required(VERSION 3.0)
project(project_name)
...
cmake_minimum_required
指定了该项目生成需要的最小版本project()
指定项目名称,该命令以下部分都是该项目的配置
批量添加文件
aux_source_directory(<variable> <source_dir>)
此命令将添加source_dir
下的所有匹配文件到variable
中。(匹配文件根据项目语言决定)
文件操作指令
-
file
是cmake中的文件操作指令,可以完成复制、创建等一系列工作,也可以用于查找文件:file(GLOB <variable> CONFIGURE_DEPENDS <globbing-expressions>)
CONFIGURE_DEPENDS
可以在文件更新时自动更新cmake- 后面是查找文件的表达式,可以是通配符,如
*.cpp *.h
等
-
configure_file
可以完成文件配置,按照模板文件生成目标文件configure_file(Config.h.in ${PROJECT_SOURCE_DIR}/Config.h)
该命令会将模板文件
Config.h.in
中内容替换为实际配置内容,并将头文件写入source
目录下的Config.h
中,这样,可以完成对文件内容的修改,如启用选项,编辑#define
常量,或其他宏补全效果。常用模板格式如下:
#define FOO_STR "@FOO_STR@" #cmakedefine FOO_ENABLE
该命令会将
@
号包含的部分转换为cmake 执行环境中变量,使用cmakedefine
,如果cmake环境中存在变量,则会将其修改为#define FOO_ENABLE
即,如果在CMakeLists中设置
set(FOO_STR "Hello world") option(FOO_ENABLE True)
则模板文件中的定义将被输出为:
#define FOO_STR "Hello world" #define FOO_ENABLE
输出字符串
使用message
命令输出字符串
message([<mode>] "message text" ...)
其中模式可以为下列等:
STATUS
状态信息,前面带--
WARNING
警告信息,黄色警告FATAL_ERROR
致命错误INFO
默认,前面不带任何,白色,输出到stderr
列表 list
使用list
命令来构建,操作列表
Reading
list(LENGTH <list> <out-var>)
list(GET <list> <element index> [<index> ...] <out-var>)
list(JOIN <list> <glue> <out-var>)
list(SUBLIST <list> <begin> <length> <out-var>)
Search
list(FIND <list> <value> <out-var>)
Modification
list(APPEND <list> [<element>...])
list(FILTER <list> {INCLUDE | EXCLUDE} REGEX <regex>)
list(INSERT <list> <index> [<element>...])
list(POP_BACK <list> [<out-var>...])
list(POP_FRONT <list> [<out-var>...])
list(PREPEND <list> [<element>...])
list(REMOVE_ITEM <list> <value>...)
list(REMOVE_AT <list> <index>...)
list(REMOVE_DUPLICATES <list>)
list(TRANSFORM <list> <ACTION> [...])
Ordering
list(REVERSE <list>)
list(SORT <list> [...])
详见list — CMake 3.25.1 Documentation,[下一小节](#选项 option)会有应用选项的列表操作
选项 option
使用option
来生成选项,这些选项**会在ccmake
**或cmake-gui
中被显示
option(VAR_NAME [Description] [default_value])
option
中的默认选项会被命令行中的-D
选项覆盖
例如,
option(USE_MYMATH "Enable MyMath Library" True)
在cmake-gui
中,会显示如下,并可以被配置
一般项目情形下,需要使用option
来控制某个子功能是否启动,可以使用以下命令(接上例USE_MYMATH
):
if (USE_MYMATH)
add_subdirectory(MathFunctions)
list(APPEND EXTRA_LIBS MathFunctions)
list(APPEND EXTRA_INCLUDES MathFunctions)
endif()
如果选项中启用USE_MYMATH
,才会进行库引用
生成器表达式
使用生成器表达式可以简化指令
$<$<var_name:value>:statement>
只有在变量=值时,才会表现为statement
,否则,生成器表达式的值为空
变量与作用域
默认变量传播规则:parent->child
如果要child->parent,则增加选项PARENT_SCOPE
Usage Requirements
在外部引用项目时,需要满足一些特定条件,这些条件称为usage requirements
只要项目开发者编写Usage Requirements,使用者就可以直接通过link完成寻找包和链接功能,而不需要另外的配置(添加include等),是现代cmake的主要构成。
同时,这些引用也是会传递的,不需要进行额外配置(但要保证配置正确)
在配置项目时,一般有三种配置选项PRIVATE|PUBLIC|INTERFACE
,其中,PRIVATE
仅限于项目自身编译时使用,INTERFACE
是Usage Requirements,它要求所有使用该库的项目添加该动作。PUBLIC
则是两者兼有。
例如,
add_library(MathFunctions mysqrt.cxx)
target_include_directories(MathFunctions INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
以上指令会使所有引用该库(MathFunctions
)的项目自动引用该项目的头文件