cmake
cmake
1. 概述
cmake 是一个项目构建工具,并且是跨平台的。关于项目构建我们所熟知的还有Makefile(通过 make 命令进行项目的构建),大多是IDE软件都集成了make,比如:VS 的 nmake、linux 下的 GNU make、Qt 的 qmake等,如果自己动手写 makefile,会发现,makefile 通常依赖于当前的编译平台,而且编写 makefile 的工作量比较大,解决依赖关系时也容易出错。
cmake 允许开发者指定整个工程的编译流程,根据编译平台,自动生成本地化的Makefile和工程文件,最后用户只需make编译即可,所以可以把cmake看成一款自动生成 Makefile的工具,其编译流程如下图:

- 蓝色虚线表示使用makefile构建项目的过程
- 红色实线表示使用cmake构建项目的过程
介绍完cmake的作用之后,再来总结一下它的优点:
- 跨平台
- 能够管理大型项目
- 简化编译构建过程和编译过程
- 可扩展:可以为 cmake 编写特定功能的模块,扩充 cmake 功能
执行
cmakecmake默认在本目录下寻找cmakeLists.txt执行- 执行
cmake会在当前目录下生成大量文件,因此一般使用指定路径的形式执行,将生成内容和代码分开存放
# 创建存放生成内容的文件夹,并在其中执行cmake
mkdir build
# 如果需要指定路径直接在后面添加相对路径
cmake ..
# Windows平台未生成Makefile时使用
cmake .. -G"MinGW Makefiles"cmake命令可选参数:- -D[指定变量]:指定一些在
cmakeLists.txt中使用的变量,比如:设置使用的C++标准cmake CMakeLists.txt文件路径 -DCMAKE_CXX_STANDARD=17
- -D[指定变量]:指定一些在
如果是
clion编译,并且希望能在Windows平台下直接运行exe文件,需要在cmakeLists.txt中添加以下行:
# 静态链接exe文件需求库libgcc
set(CMAKE_EXE_LINKER_FLAGS "-static-libgcc -static-libstdc++")2. 语法
2.1 常见文档内容
- 常见基本文档构成
# 可选,不加可能有警告,指定使用的 cmake 的最低版本
cmake_minimum_required(VERSION 3.0)
# 定义工程名称,并可指定工程的版本、工程描述、web主页地址、支持的语言(默认情况支持所有语言),如果不需要这些都是可以忽略的,只需要指定出工程名字即可
project(CALC)
# 定义工程会生成一个可执行程序
# add_executable(可执行程序名 所有源文件名称)
# 样式1
add_executable(app add.c div.c main.c mult.c sub.c)
# 样式2
add_executable(app add.c;div.c;main.c;mult.c;sub.c)- 注释
# 使用 # 进行行注释,可以放在任何位置
#[[使用 #[[]]
进行多行注释]]- 输出:在cmake中可以为用户显示一条消息,该命令的名字为
message
# cmake在stdout上显示STATUS消息,在stderr上显示其他所有消息。CMake的GUI会在它的log区域显示所有消息
# (无) :重要消息
# STATUS :非重要消息
# WARNING:CMake 警告, 会继续执行
# AUTHOR_WARNING:CMake 警告 (dev), 会继续执行
# SEND_ERROR:CMake 错误, 继续执行,但是会跳过生成的步骤
# FATAL_ERROR:CMake 错误, 终止所有处理过程
message([STATUS|WARNING|AUTHOR_WARNING|FATAL_ERROR|SEND_ERROR] "message to display" ...)
# 输出一般日志信息
message(STATUS "source path: ${PROJECT_SOURCE_DIR}")
# 输出警告信息
message(WARNING "source path: ${PROJECT_SOURCE_DIR}")
# 输出错误信息
message(FATAL_ERROR "source path: ${PROJECT_SOURCE_DIR}")2.2 定义变量
- 在上面的例子中一共提供了5个源文件,假设这五个源文件需要反复被使用,每次都直接将它们的名字写出来确实是很麻烦,此时我们就需要定义一个变量,将文件名对应的字符串存储起来,在cmake里定义变量需要使用
set
# SET 指令的语法是:
# [] 中的参数为可选项, 如不需要可以不写
# VAR:变量名
# VALUE:变量值
SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])
# 方式1: 各个源文件之间使用空格间隔
# set(SRC_LIST add.c div.c main.c mult.c sub.c)
# 方式2: 各个源文件之间使用分号 ; 间隔
set(SRC_LIST add.c;div.c;main.c;mult.c;sub.c)
# 用于空格和;是分隔符,需要时要用双引号包裹
set(LIST a b c "d e f")
# 默认结果是字符串
add_executable(app ${SRC_LIST})重名和预定义问题
set设置的名称可以重名,重名会被覆盖cmake有两种变量:normal和cachenormal就是我们普通使用的变量,比如set(VAR "xyz")cache是cmake缓存的变量,比如cmake -D定义的变量,通常这些变量会存在CMakeCache.txt里面,第一次跑cmake的时候会生成这个文件- 两种变量可以同名,当变量
${VAR}展开时,cmake会先尝试去查找normal变量,如果没找到定义则会去使用cache变量里查找 - 可以使用
set(VAR "xyz" CACHE STRINGH "this is a comment of VAR")设置一个cache变量。注意,这条指令并不会直接将VAR赋值,而是去查找是否已经有名字为VAR的cache变量,如果已经存在了则不会产生任何影响,如果不存在才会创建这个cache变量。如果想要强制将其值设置成"xyz",可以在最后加FORCE参数set(VAR "xyz" CACHE STRINGH "this is a comment of VAR" FORCE)
set设置的变量类似于提供相关键值对存储,对于函数直接赋值给之前未使用set定义的变量不会报错
修改原有内置变量值
#增加-std=c++11
set(CMAKE_CXX_STANDARD 11)2.3 cmake内置变量
cmake项目基本信息
CMAKE_PROJECT_NAME:当前项目的名称PROJECT_NAME:最近通过project()命令激活的项目名称PROJECT_SOURCE_DIR:包含最顶层cmakeLists.txt文件的目录,即项目的源代码根目录PROJECT_BINARY_DIR:项目的构建目录,如果是外部构建,这将与CMAKE_BINARY_DIR不同CMAKE_BUILD_TYPE:指定构建类型(比如Release或Debug)CMAKE_SOURCE_DIR:项目的顶层源目录CMAKE_CURRENT_SOURCE_DIR:当前处理的cmakeLists.txt所在的目录EXECUTABLE_OUTPUT_PATH:重新定义生成的目标二进制可执行文件的存放位置LIBRARY_OUTPUT_PATH:重新定义生成的目标库文件的存放位置CMAKE_BINARY_DIR:项目的顶层构建目录CMAKE_CURRENT_BINARY_DIR:使用cmake命令的shell所在文件夹CMAKE_INSTALL_PREFIX:安装目录前缀CMAKE_MODULE_PATH:指定额外的cmake模块搜索路径CMAKE_PREFIX_PATH:用于查找库文件的路径前缀CMAKE_COMMAND:也就是CMake可运行文件本身的全路径
编译器和工具设置
CMAKE_C_COMPILER:C编译器的完整路径CMAKE_CXX_COMPILER:C++编译器的完整路径CMAKE_C_FLAGS:C编译器的命令行选项CMAKE_CXX_FLAGS:C++编译器的命令行选项CMAKE_VERBOSE_MAKEFILE:如果为TRUE,构建过程将显示更多的信息CMAKE_C_STANDARD:指定使用的C标准CMAKE_CXX_STANDARD:指定使用的C++标准
系统信息
CMAKE_SYSTEM:系统名称,例如Linux-2.6.32-573.el6.x86_64。CMAKE_SYSTEM_NAME:不包含版本的系统名称,如Linux、Windows。CMAKE_SYSTEM_PROCESSOR:目标系统的处理器,例如x86_64
特殊变量
CMAKE_VERSION:cmake的版本BUILD_SHARED_LIBS:一个布尔变量,用于控制默认链接库的类型(静态或动态)
其他项目变量
PROJECT_NAME:当前通过project()命令设置的项目名称PROJECT_VERSION:通过project()命令设置的项目版本PROJECT_VERSION_MAJOR:项目的主版本号PROJECT_VERSION_MINOR:项目的次版本号PROJECT_VERSION_PATCH:项目的补丁版本号PROJECT_VERSION_TWEAK:项目的微调版本号(如果指定)PROJECT_DESCRIPTION:项目的描述信息(需要cmake 3.9及以上版本)PROJECT_HOMEPAGE_URL:项目的主页URL(需要cmake 3.12及以上版本)
PROJECT_VERSION、PROJECT_VERSION_MAJOR、PROJECT_VERSION_MINOR、PROJECT_VERSION_PATCH和PROJECT_VERSION_TWEAK变量的设置依赖于project()命令中指定的版本信息
3. 常用操作
3.1 批量添加源文件和头文件
- 如果一个项目里边的源文件很多,在编写
CMakeLists.txt文件的时候不可能将项目目录的各个文件一一罗列出来,这样太麻烦也不现实。所以,在cmake中为我们提供了搜索或添加文件的命令,可以使用 aux_source_directory命令搜索源文件include_directories命令添加头文件file命令搜索匹配文件(可递归)
aux_source_directory命令和include_directories命令
# 查找某个路径下的源文件
# dir:要搜索的目录
# variable:将从dir目录下搜索到的源文件列表存储到该变量中
aux_source_directory(< dir > < variable >)
# 查找某个路径下的头文件
include_directories(headpath)
# 搜索 src 目录下的源文件
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_LIST)
# 将 include路径添加为头文件路径
include_directories(${PROJECT_SOURCE_DIR}/include)
file命令
# 搜索文件
# GLOB: 将指定目录下搜索
# GLOB_RECURSE:递归搜索指定目录
file(GLOB/GLOB_RECURSE 变量名 要搜索的文件路径和文件类型)
# 搜索当前目录的src目录下所有的源文件绝对路径,并存储到变量中
file(GLOB MAIN_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
file(GLOB MAIN_HEAD ${CMAKE_CURRENT_SOURCE_DIR}/include/*.h)3.2 字符串操作
字符串合并
- 如果我们通过
file命令对各个目录下的源文件进行搜索,最后还需要做一个字符串拼接的操作,关于字符串拼接可以使用set命令也可以使用list命令 list命令的功能比set要强大,字符串拼接只是它的其中一个功能,所以需要在它第一个参数的位置指定出我们要做的操作,APPEND表示进行数据追加,后边的参数和set就一样了
set(a 1)
set(b 2)
# 方法1:使用set拼接
set(a ${a} ${b})
# list(APPEND <list> [<element> ...])
# 方法2:使用list拼接
list(APPEND a ${a} ${b})
# 结果为 1 2
message(a)- 在
CMake中,使用set命令可以创建一个list。一个在list内部是一个由分号;分割的一组字符串。例如,set(var a b c d e)命令将会创建一个list:a;b;c;d;e,但是最终打印变量值的时候得到的是abcde
字符串移除
- 我们在通过
file搜索某个目录就得到了该目录下所有的源文件,但是其中有些源文件并不是我们所需要的,想要把源文件从搜索到的数据中剔除出去也可以使用list命令
# list(REMOVE_ITEM <list> <value> [<value> ...])
# 由于获取到的路径是绝对路径,也使用绝对路径过滤
# 移除 main.cpp
list(REMOVE_ITEM SRC_1 ${PROJECT_SOURCE_DIR}/main.cpp)其他字符串方法
# 获取 list 的长度。
list(LENGTH <list> <output variable>)
LENGTH:子命令LENGTH用于读取列表长度
<list>:当前操作的列表
<output variable>:新创建的变量,用于存储列表的长度。
# 读取列表中指定索引的的元素,可以指定多个索引
list(GET <list> <element index> [<element index> ...] <output variable>)
# <list>:当前操作的列表
# <element index>:列表元素的索引
# 从0开始编号,索引0的元素为列表中的第一个元素;
# 索引也可以是负数,-1表示列表的最后一个元素,-2表示列表倒数第二个元素,以此类推
# 当索引(不管是正还是负)超过列表的长度,运行会报错
# <output variable>:新创建的变量,存储指定索引元素的返回结果,也是一个列表。
# 将列表中的元素用连接符(字符串)连接起来组成一个字符串
list (JOIN <list> <glue> <output variable>)
# <list>:当前操作的列表
# <glue>:指定的连接符(字符串)
# <output variable>:新创建的变量,存储返回的字符串
# 查找列表是否存在指定的元素,若果未找到,返回-1
list(FIND <list> <value> <output variable>)
# <list>:当前操作的列表
# <value>:需要再列表中搜索的元素
# <output variable>:新创建的变量
# 如果列表<list>中存在<value>,那么返回<value>在列表中的索引
# 如果未找到则返回-1。
# 将元素追加到列表中
list (APPEND <list> [<element> ...])
# 在list中指定的位置插入若干元素
list(INSERT <list> <element_index> <element> [<element> ...])
# 将元素插入到列表的0索引位置
list (PREPEND <list> [<element> ...])
# 将列表中最后元素移除
list (POP_BACK <list> [<out-var>...])
# 将列表中第一个元素移除
list (POP_FRONT <list> [<out-var>...])
# 将指定的元素从列表中移除
list (REMOVE_ITEM <list> <value> [<value> ...])
# 将指定索引的元素从列表中移除
list (REMOVE_AT <list> <index> [<index> ...])
# 移除列表中的重复元素
list (REMOVE_DUPLICATES <list>)
# 列表翻转
list(REVERSE <list>)
# 列表排序
list (SORT <list> [COMPARE <compare>] [CASE <case>] [ORDER <order>])
# COMPARE:指定排序方法。有如下几种值可选:
# - STRING:按照字母顺序进行排序,为默认的排序方法
# - FILE_BASENAME:如果是一系列路径名,会使用basename进行排序
# - NATURAL:使用自然数顺序排序
# CASE:指明是否大小写敏感。有如下几种值可选:
# - SENSITIVE: 按照大小写敏感的方式进行排序,为默认值
# - INSENSITIVE:按照大小写不敏感方式进行排序
# ORDER:指明排序的顺序。有如下几种值可选:
# - ASCENDING:按照升序排列,为默认值
# - DESCENDING:按照降序排列3.3 添加宏定义
- 在进行程序测试的时候,我们可以在代码中添加一些宏定义,通过这些宏来控制这些代码是否生效
- 为了让测试更灵活,我们可以不在代码中定义这个宏,而是在测试的时候去把它定义出来
- 在
gcc/g++命令中通过参数-D指定出要定义的宏的名字,这样就相当于在代码中定义了一个宏 - 在
CMake中我们也可以做类似的事情,对应的命令叫做add_definitions
- 在
# add_definitions(-D宏名称)
# 自定义 DEBUG 宏
add_definitions(-DDEBUG)3.4 流程控制
3.4.1 基本逻辑表达式
# NOT是一个取反操作
NOT <condition>
# 如果cond1和cond2同时为True,返回True否则返回False
if(<cond1> AND <cond2>)
# 如果cond1和cond2两个条件中至少有一个为True,返回True,否则返回False
if(<cond1> OR <cond2>)比较
- 基于数值的比较
# LESS:如果左侧数值小于右侧,返回True
if(<variable|string> LESS <variable|string>)
# GREATER:如果左侧数值大于右侧,返回True
if(<variable|string> GREATER <variable|string>)
# EQUAL:如果左侧数值等于右侧,返回True
if(<variable|string> EQUAL <variable|string>)
# LESS_EQUAL:如果左侧数值小于等于右侧,返回True
if(<variable|string> LESS_EQUAL <variable|string>)
# GREATER_EQUAL:如果左侧数值大于等于右侧,返回True
if(<variable|string> GREATER_EQUAL <variable|string>)- 基于字符串的比较
# STRLESS:如果左侧字符串小于右侧,返回True
if(<variable|string> STRLESS <variable|string>)
# STRGREATER:如果左侧字符串大于右侧,返回True
if(<variable|string> STRGREATER <variable|string>)
# STREQUAL:如果左侧字符串等于右侧,返回True
if(<variable|string> STREQUAL <variable|string>)
# STRLESS_EQUAL:如果左侧字符串小于等于右侧,返回True
if(<variable|string> STRLESS_EQUAL <variable|string>)
# STRGREATER_EQUAL:如果左侧字符串大于等于右侧,返回True
if(<variable|string> STRGREATER_EQUAL <variable|string>)文件操作
- 判断文件或者目录是否存在
#如果文件或者目录存在返回True,否则返回False
if(EXISTS path-to-file-or-directory)- 判断是不是目录
# 此处目录的 path 必须是绝对路径
# 如果目录存在返回True,目录不存在返回False。
if(IS_DIRECTORY path)- 判断是不是软连接
# 此处的 file-name 对应的路径必须是绝对路径
# 如果软链接存在返回True,软链接不存在返回False。
# 软链接相当于 Windows 里的快捷方式
if(IS_SYMLINK file-name)- 判断是不是绝对路径
- 关于绝对路径:
- 如果是Linux,该路径需要从根目录开始描述
- 如果是Windows,该路径需要从盘符开始描述
# 如果是绝对路径返回True,如果不是绝对路径返回False
if(IS_ABSOLUTE path)其他
- 判断某个元素是否在列表中
CMake 版本要求:大于等于3.3
如果这个元素在列表中返回True,否则返回False
if(<variable|string> IN_LIST <variable>)- 比较两个路径是否相等
CMake 版本要求:大于等于3.24
如果这个元素在列表中返回True,否则返回False
if(<variable|string> PATH_EQUAL <variable|string>)- 关于路径的比较其实就是另个字符串的比较,如果路径格式书写没有问题也可以通过下面这种方式进行比较:
if(<variable|string> STREQUAL <variable|string>)- 我们在书写某个路径的时候,可能由于误操作会多写几个分隔符,比如把
/a/b/c写成/a//b///c,此时通过STREQUAL对这两个字符串进行比较肯定是不相等的,但是通过PATH_EQUAL去比较两个路径,得到的结果确实相等的
3.4.2 选择判断
- 基本结构
if(<condition>) # 条件一般为逻辑判断结果
<commands>
elseif(<condition>) # 可选快, 可以重复
<commands>
else() # 可选快
<commands>
endif()3.4.3 循环
foreach
- 基本结构
# 通过foreach我们就可以对items中的数据进行遍历,然后通过loop_var将遍历到的当前的值取出
foreach(<loop_var> <items>)
<commands>
endforeach()- 固定次数循环
# loop_var:存储每次循环取出的值
# RANGE:关键字,表示要遍历范围
# start:这是一个正整数,表示范围的起始值,也就是说最小值为 start
# stop:这是一个正整数,表示范围的结束值,也就是说最大值为 stop
# step:控制每次遍历的时候以怎样的步长增长,默认为1,可以不设置
foreach(<loop_var> RANGE <start> <stop> [<step>])
# 例子
foreach(item RANGE 10)
# 结果从0到10即[0: stop]
message(STATUS "当前遍历的值为: ${item}" )
endforeach()
foreach(item RANGE 10 30 2)
#结果从10到30之间的偶数即[start: stop],步长为2
message(STATUS "当前遍历的值为: ${item}" )
endforeach()- 遍历列表
# IN:关键字,表示在 xxx 里边
# LISTS:关键字,对应的是列表list,通过set、list可以获得
# ITEMS:关键字,对应的也是列表
# loop_var:存储每次循环取出的值
foreach(<loop_var> IN [LISTS [<lists>]] [ITEMS [<items>]])
# 例子
# 创建 list
set(WORD a b c d)
set(NAME ace sabo luffy)
# 遍历 list
foreach(item IN LISTS WORD NAME)
# 结果为两个列表中的所有值
message(STATUS "当前遍历的值为: ${item}" )
endforeach()
# 另一种方法,结果相同
foreach(item IN ITEMS ${WORD} ${NAME})
message(STATUS "当前遍历的值为: ${item}" )
endforeach()- 合并遍历
- 指定的
loop_var个数和含义- 如果指定了多个变量名,它们的数量应该和列表的数量相等
- 如果只给出了一个 loop_var,那么它将一系列的 loop_var_N 变量来存储对应列表中的当前项,也就是说 loop_var_0 对应第一个列表,loop_var_1 对应第二个列表,以此类推......
- 如果遍历的多个列表中一个列表较短,当它遍历完成之后将不会再参与后续的遍历(因为其它列表还没有遍历完),之后结果为空字符串
# loop_var:存储每次循环取出的值,可以根据要遍历的列表的数量指定多个变量,用于存储对应的列表当前取出的那个值
# IN:关键字,表示在 xxx 里边
# ZIP_LISTS:关键字,对应的是列表list,通过set 、list可以获得
foreach(<loop_var>... IN ZIP_LISTS <lists>)
# 例子
# 通过list给列表添加数据
list(APPEND WORD hello world "hello world")
list(APPEND NAME ace sabo luffy zoro sanji)
# 遍历列表
foreach(item1 item2 IN ZIP_LISTS WORD NAME)
message(STATUS "当前遍历的值为: item1 = ${item1}, item2=${item2}" )
endforeach()
message("=============================")
# 遍历列表
foreach(item IN ZIP_LISTS WORD NAME)
message(STATUS "当前遍历的值为: item1 = ${item_0}, item2=${item_1}" )
endforeach()
# 结果
-- 当前遍历的值为: item1 = hello, item2=ace
-- 当前遍历的值为: item1 = world, item2=sabo
-- 当前遍历的值为: item1 = hello world, item2=luffy
-- 当前遍历的值为: item1 = , item2=zoro
-- 当前遍历的值为: item1 = , item2=sanji
=============================
-- 当前遍历的值为: item1 = hello, item2=ace
-- 当前遍历的值为: item1 = world, item2=sabo
-- 当前遍历的值为: item1 = hello world, item2=luffy
-- 当前遍历的值为: item1 = , item2=zoro
-- 当前遍历的值为: item1 = , item2=sanjiwhile
while的语法格式如下:
while(<condition>) # 条件一般为逻辑判断结果
<commands>
endwhile()- 弹出所有字符串例子
# 创建一个列表 NAME
set(NAME luffy sanji zoro nami robin)
# 得到列表长度
list(LENGTH NAME LEN)
# 循环
while(${LEN} GREATER 0)
message(STATUS "names = ${NAME}")
# 弹出列表头部元素
list(POP_FRONT NAME)
# 更新列表长度
list(LENGTH NAME LEN)
endwhile()3.5 执行命令行操作
3.5.1 cmake配置阶段
execute_process对应在CMake配置阶段(运行cmake命令时)使用外部命令并捕获输出常用选项
| 选项 | 描述 |
|---|---|
COMMAND <cmd> [args...] | 要执行的命令和参数(可多个COMMAND,按顺序执行) |
WORKING_DIRECTORY <dir> | 命令执行的工作目录(默认是当前构建目录) |
OUTPUT_VARIABLE <var> | 捕获命令的标准输出到变量 <var> |
ERROR_VARIABLE <var> | 捕获命令的错误输出到变量 <var> |
RESULT_VARIABLE <var> | 捕获命令的返回值(0 表示成功,非 0 为错误码) |
OUTPUT_STRIP_TRAILING_WHITESPACE | 删除输出末尾的换行和空格 |
ERROR_STRIP_TRAILING_WHITESPACE | 删除错误输出末尾的换行和空格 |
ECHO_OUTPUT_VARIABLE | 将输出实时打印到终端(CMake 3.18+) |
ENCODING <encoding> | 指定输出编码(如 UTF-8,CMake 3.8+) |
ERROR_QUIET | 忽略错误 |
- 示例
execute_process(
COMMAND git log -1 --format=%h
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE GIT_HASH
ERROR_QUIET
RESULT_VARIABLE GIT_RESULT
)3.5.2 make构建阶段
add_custom_command会在CMake构建阶段(运行cmake --build或make时),生成文件或为目标附加构建前后的自定义操作(如编译资源、复制文件)有两种模式:
- 生成文件模式(
OUTPUT参数):也就是查看某个文件是否存在,执行命令用于得到对应文件
add_custom_command( OUTPUT <output-file> # 生成的文件的路径 COMMAND <cmd> [args...] # 生成文件的命令 DEPENDS [<deps>...] # 依赖的文件或目标 WORKING_DIRECTORY <dir> COMMENT "Custom message..." # 构建时显示的信息 VERBATIM # 避免转义参数 ) # 例子 add_custom_command( OUTPUT generated.cpp COMMAND python ${CMAKE_SOURCE_DIR}/generate_code.py DEPENDS generate_code.py COMMENT "Generating source code..." ) add_executable(app generated.cpp) # 依赖 generated.cpp- 目标附加模式(
TARGET参数):将命令附加到目标上,在目标构建时执行命令,可以设置执行时机:PRE_BUILD:在目标编译前执行(某些生成器可能不支持,如Visual Studio)PRE_LINK:在编译后、链接前执行POST_BUILD:在目标构建完成后执行(最常用)
add_custom_command( TARGET <target> # 目标名称(如可执行文件) PRE_BUILD|PRE_LINK|POST_BUILD # 执行时机 COMMAND <cmd> [args...] WORKING_DIRECTORY <dir> COMMENT "Custom message..." BYPRODUCTS <files>... # 声明生成的副产品(如 Ninja 生成器需要) ) # 例子 add_executable(my_app main.cpp) add_custom_command( TARGET my_app POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:my_app> ${CMAKE_BINARY_DIR}/bin/ COMMENT "Copying executable to bin/" )- 生成文件模式(
3.5.3 跨平台操作
在cmake中内置了常用的文件操作功能,即
cmake -E工具,可以跨平台执行文件操作(无需关心复制、删除等操作的差异)。为了更好地跨平台,建议使用多条命令代替&&操作常用命令
命令 描述 copy <src> <dst>复制文件或目录 copy_directory <src> <dst>递归复制目录 remove <file>删除文件 remove_directory <dir>删除目录 echo [<text>...]输出文本 make_directory <dir>创建目录 touch <file>创建空文件或更新修改时间 time <command> [args...]测量命令执行时间 add_custom_command( COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/assets ${CMAKE_BINARY_DIR}/bin/assets )
3.5.4 独立显式调用
cmake还支持定义独立的命令行指令,add_custom_target在CMake构建阶段,需显式调用(如cmake --build . --target <name>),可以定义独立的自定义任务(如运行测试、打包、清理临时文件)常用选项
选项 描述 COMMAND <cmd> [args...]要执行的命令 DEPENDS <targets/files...>依赖的目标或文件(确保它们先构建) WORKING_DIRECTORY <dir>命令执行的工作目录 COMMENT "message..."构建时显示的信息 ALL将此目标添加到默认构建(即 cmake --build .会构建它)VERBATIM避免参数转义问题 BYPRODUCTS <files...>声明生成的副产品( Ninja生成器需要)add_custom_target( run_tests COMMAND ctest --output-on-failure DEPENDS my_app # 确保 my_app 已构建 WORKING_DIRECTORY ${CMAKE_BINARY_DIR} COMMENT "Running tests..." ) add_custom_target( clean_all COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_BINARY_DIR}/bin COMMAND ${CMAKE_COMMAND} -E remove ${CMAKE_BINARY_DIR}/*.log COMMENT "Cleaning build artifacts..." )
4. 库文件
4.1 制作库文件
- 在cmake中,如果要制作库,需要使用的命令如下:
# 基本设置
cmake_minimum_required(VERSION 3.0)
project(CALC)
# 添加源文件和头文件
...
# 静态库
add_library(库名称 STATIC 源文件1 [源文件2] ...)
# 动态库
add_library(库名称 源文件1 [源文件2] ...)
# 也可以这样生成动态库
add_library(库名称 SHARED 源文件1 [源文件2] ...)在Linux中,静态库名字分为三部分:lib+库名字+.a,此处只需要指定出库的名字就可以了,另外两部分在生成该文件的时候会自动填充,Windows也是一样
4.2 使用库文件
- 添加库存放路径
- 如果还不知道存放路径,可以通过
find /usr -name '库名'查找
- 如果还不知道存放路径,可以通过
# 如果该静态库不是系统提供的,可能出现静态库找不到的情况
# 此时可以将静态库的路径也指定出来
ink_directories(<lib path>)使用静态库文件
- 连接静态库,静态库会到程序中,连接操作要在生成可执行文件之前
# 参数:指定出要链接的静态库的名字列表
# 可以是全名 libxxx.a
# 也可以是掐头(lib)去尾(.a)之后的名字 xxx
link_libraries(<static lib> [<static lib>...])
# 也可以使用target_link_libraries命令,命令写在生成可执行文件之后cmake_minimum_required(VERSION 3.0)
project(CALC)
# 搜索指定目录下源文件
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
# 包含头文件路径
include_directories(${PROJECT_SOURCE_DIR}/include)
# 包含静态库路径
link_directories(${PROJECT_SOURCE_DIR}/lib)
# 链接静态库
link_libraries(calc)
# 生成可执行文件
add_executable(app ${SRC_LIST})使用动态库文件
- 连接动态库,动态库会在需要的时候被加入内存中,连接操作在生成可执行文件之后
# target:指定要加载动态库的文件的名字
# 该文件可能是一个源文件
# 该文件可能是一个动态库文件
# 该文件可能是一个可执行文件
# PRIVATE|PUBLIC|INTERFACE:动态库的访问权限,默认为PUBLIC
target_link_libraries(
<target>
<PRIVATE|PUBLIC|INTERFACE> <item>...
[<PRIVATE|PUBLIC|INTERFACE> <item>...]...)- 如果各个动态库之间没有依赖关系,无需做任何设置,三者没有没有区别,一般无需指定,使用默认的
PUBLIC即可 - 动态库的链接具有传递性,如果动态库 A 链接了动态库B、C,动态库D链接了动态库A,此时动态库D相当于也链接了动态库B、C,并可以使用动态库B、C中定义的方法
PUBLIC:在public后面的库会被Link到前面的target中,并且里面的符号也会被导出,提供给第三方使用PRIVATE:在private后面的库仅被link到前面的target中,不会传递INTERFACE:在interface后面引入的库不会被链接到前面的target中,只会导出符号,不知道使用的函数来自哪个库
cmake_minimum_required(VERSION 3.0)
project(TEST)
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
# 指定源文件或者动态库对应的头文件路径
include_directories(${PROJECT_SOURCE_DIR}/include)
# 指定要链接的动态库的路径
link_directories(${PROJECT_SOURCE_DIR}/lib)
# 添加并生成一个可执行程序
add_executable(app ${SRC_LIST})
# 指定要链接的动态库
target_link_libraries(app pthread calc)5. 嵌套的cmake
5.1 节点关系
- 众所周知,
Linux的目录是树状结构,所以嵌套的CMake也是一个树状结构,最顶层的CMakeLists.txt是根节点,其次都是子节点。因此,我们需要了解一些关于CMakeLists.txt文件变量作用域的一些信息:- 根节点
CMakeLists.txt中的变量全局有效 - 父节点
CMakeLists.txt中的变量可以在子节点中使用 - 子节点
CMakeLists.txt中的变量只能在当前节点中使用
- 根节点
5.2 添加子目录
- 可以通过
add_subdirectory命令在父目录注册子目录
# source_dir:指定了CMakeLists.txt源文件和代码文件的位置,其实就是指定子目录
# binary_dir:指定了输出文件的路径,一般不需要指定,忽略即可
# EXCLUDE_FROM_ALL:在子路径下的目标默认不会被包含到父路径的ALL目标里,并且也会被排除在IDE工程文件之外。用户必须显式构建在子路径下的目标
add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])