编译环境:在 Wiondows 下,使用 VScode+WSL2+Ubuntu20.04+GCC 编译器
# 核心命令
# 项目配置
-
cmake_minimum_required
指定CMake
的最小版本要求
cmake_minimum_required(VERSION 3.10) |
project
定义项目名称,并可以指定项目使用的编程语言、web 主页地址、项目版本号等
project(MyProject VERSION 1.0 LANGUAGES CXX) |
-
set
设置变量
#方式 1 各源文件剪枝空格间隔,并赋值给 SOURCE(变量名任取,不要重复即可)变量 | |
set(SOURCES main.cpp foo.cpp bar.cpp) | |
#方式 2 各源文件 “;” 间隔 | |
set(SOURCES main.cpp;foo.cpp;bar.cpp) | |
# 使用变量,采用 ${} 方式 | |
add_executable(MyExecutable ${SOURCES}) |
- 指定使用
c++
标准
命令行编译时使用 c++ 标准,通过 -std=c++11
格式指定
g++ -std=c++11 -o demo main.cpp |
- 通过
set
命令指定
CMake 文件指定 c++ 标准
set(CMAKE_CXX_STANDARD 11) |
- 通过
project
命令指定
project(MyProject VERSION 1.0 LANGUAGES CXX CXX_STANDARD 11) |
-
C++
标准对应有宏-DCMAKE_CXX_STANDARD
,通过cmake
命令指定
cmake -DCMAKE_CXX_STANDARD=11 .. # 注意,.. 表示上一级目录,指定到 CMakeLists.txt 所在的目录 |
变量名 | 描述 |
---|---|
CMAKE_SOURCE_DIR | CMakeLists.txt 所在的目录 |
CMAKE_BINARY_DIR | 构建目录 |
CMAKE_CURRENT_SOURCE_DIR | 当前 CMakeLists.txt 所在的目录 |
CMAKE_CURRENT_BINARY_DIR | 当前构建目录 |
CMAKE_CURRENT_LIST_DIR | 当前 CMakeLists.txt 所在的目录 |
PROJECT_SOURCE_DIR | 表示项目根目录 |
PROJECT_BINARY_DIR | 表示项目构建目录 |
PROJECT_NAME | 表示项目名称 |
CMAKE_CXX_STANDARD | 表示 C++ 标准 |
# 文件管理
- 指定输出路径
在 CMake 中指定可执行程序输出的路径,也有对应的宏,叫做 EXECUTABLE_OUTPUT_PATH
,可通过 set
命令指定,例如:
set(HOME /home/zhang/Linux) | |
set(EXECUTABLE_OUTPUT_PATH ${HOME}/bin) |
- 第一行:定义一个变量存储一个路径
- 第二行:将拼接好的路径赋值给
EXECUTABLE_OUTPUT_PATH
变量
- 搜索文件
aux_source_group
查找 某路径
下的 所有源文件
aux_source_group("Source Files" variable) |
Source Files
:表示搜索的文件所在的目录名称variable
:将Source Files
目录下搜索到的源文件列表赋值给variable
变量
-
aux_source_group
查找文件
# aux_source_group(${PROJECT_SOURCE_DIR}/src SRC_LIST) | |
aux_source_group(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_LIST) |
PROJECT_SOURCE_DIR
:表示项目根目录CMAKE_CURRENT_SOURCE_DIR
:表示当前CMakeLists.txt
所在的目录
搜索CMakeLists.txt
所在的目录src
下所有源文件,并将结果赋值给SRC_LIST
变量。
-
file
查找文件
使用file
命令,查找某路径下的某格式文件
file(GLOB/GLOB_RECURSE 变量名 要搜索文件路径和文件类型) |
GLOB
:表示搜索指定目录下所有文件,将满足条件的文件列表赋值给变量GLOB_RECURSE
:表示递归搜索指定目录下所有文件,将满足条件的文件列表赋值给变量
- 包含头文件
在编译项目源文件时,需要将源文件对应的头文件包含进来,cmake 提供了include_directories
命令,用于指定头文件的搜索路径:
include_directories(headpath) |
# 构建控制
# 编译选项
在 CMake 中,可以通过 option
命令来定义编译选项,例如:
option(USE_OPENMP "Use OpenMP for parallelization" ON) |
USE_OPENMP
:表示编译选项的名称Use OpenMP for parallelization
:表示编译选项的描述
# 链接控制
在 CMake 中,可以通过 target_link_libraries
命令来指定链接库,例如:
target_link_libraries(<TARGET> PRIVATE MyLibrary) |
TARGET
:需要链接库的目标PRIVATE
:表示链接库的作用域,可以是PRIVATE
、PUBLIC
、INTERFACE
之一
# 工程实践
# 标准结构
# CMakeLists.txt | |
cmake_minimum_required(VERSION 3.10) | |
project(MyProject LANGUAGES CXX) | |
# 添加源文件 | |
set(SOURCES main.cpp foo.cpp bar.cpp) | |
add_executable(MyExecutable ${SOURCES}) | |
# 添加头文件搜索路径 | |
include_directories(${CMAKE_SOURCE_DIR}/include) | |
# 添加链接库 | |
target_link_libraries(MyExecutable PRIVATE MyLibrary) |
# 构建流程
mkdir build | |
cd build | |
cmake .. | |
make |
# 动静态库
# 生成静态库
add_library
命令用于创建库,其语法如下:
# 静态库 | |
add_library(MyLibrary STATIC [源文件1] [源文件2]) | |
# 动态库 | |
add_library(MyLibrary SHARED [源文件1] [源文件2]) |
MyLibrary
为生成 动静态
的 库名
,完整动静态文件名包含三部分: lib
+ 库名
+ 扩展名
SHARED
:表示生成动态库,动态库扩展名为 .so
(Linux)、 dll
(Windows)
STATIC
:表示生成静态库,静态库扩展名为 .a
(Linux)、 lib
(Windows)
静态库
默认不具有可执行权限,所以指定静态库路径的时候不可以使用 EXECUTABLE_OUTPUT_PATH
,而应该使用 LIBRARY_OUTPUT_PATH
,这个宏对于动态库和静态库都有效。
类别 | 编译 |
---|---|
静态库 | 编译时链接 |
动态库 | 运行时链接 |
# 使用动静态库
链接动静态库,需要使用 link_libraries
命令
link_libraries(<static lib> [<static lib>...]) |
参数1
指定要链接静态库的名字,可以是文件全名,也可以是文件名(不带 lib
前缀和扩展名 .a
)
参数2
要链接的其他静态库的名字
** 注意:** 如果该静态库不是系统提供的(自己制作或第三方提供静态库)可能出现找不到的情况,需要指定静态库的路径
link_directories(<path>) |
参数
指定静态库的路径
target_link_libraries
用于将动态库链接到目标可执行文件(因此位于 add_executable
或 add_library
命令之后)
target_link_libraries(<target> [PRIVATE|PUBLIC|INTERFACE] <item>...) |
<target>
:表示目标可执行文件或库的名称
[PRIVATE|PUBLIC|INTERFACE]
:表示链接库的访问权限,默认为 PUBLIC
, 可以是 PRIVATE
、 PUBLIC
、 INTERFACE
之一
<item>
:表示要链接的库的名称,可以是库的名称,也可以是库的路径
动态库
:具有传递性,即链接动态库的库,也会链接动态库所依赖的动态库,例如:如果 动态库A
链接 动态库B、C
, 动态库D
链接 动态库A
,此时链接 动态库D
相当于链接了 动态库B、C
中定义的方法。
target_link_libraries(A B C) | |
target_link_libraries(D A) |
权限 | 描述 |
---|---|
PRIVATE |
private 后面的库仅被 link 前面的 target 中,并且终结掉,第三方库不能感知 |
PUBLIC |
public 后面的库会被 link 到前面的 target 中,并且里面的符号会被导出,提供给第三方 |
INTERFACE |
interface 后面引入的库不会被连接到前面的 target 中,只会导出符号 |
# 进阶技巧
# 条件判断
if
if (EXISTS ${CMAKE_SOURCE_DIR}/include) | |
include_directories(${CMAKE_SOURCE_DIR}/include) | |
endif() |
elseif
if (EXISTS ${CMAKE_SOURCE_DIR}/include) | |
include_directories(${CMAKE_SOURCE_DIR}/include) |
foreach
命令用于循环
foreach(<loop_var> <items>) | |
<commands> | |
endforeach() |
<loop_var>
:表示循环变量
# 日志
message
命令用于输出日志信息,其语法如下:
message(<mode> "message to display" ...) |
<mode>
:表示输出信息的模式,可以是 (空)
、 STATUS
、 WARNING
、 AUTHOR_WARNING
、 SEND_ERROR
、 FATAL_ERROR
、 DEPRECATION
之一
"message to display"
:表示要输出的信息
模式 | 输出信息 |
---|---|
(空) | 输出重要信息 |
STATUS | 输出普通信息 |
WARNING | 输出警告信息 |
AUTHOR_WARNING | 输出作者警告信息 |
SEND_ERROR | 输出错误信息,继续执行 |
FATAL_ERROR | 输出错误信息,终止执行 |
DEPRECATION | 输出弃用信息 |
CMake 命令行工具会在 stdout 上显示 STATUS 消息,在 stderr 上显示其他消息。CMake GUI 工具会在消息窗口中显示所有消息。
# 变量操作
有时候项目源文件比较多并且不再同一目录下,这些源文件需要一起编译生成可执行文件,我们可以通过 file
命令对各自目录下的源文件进行搜索,最后还需要做一个字符串拼接操作,关于字符串拼接可以使用 set
命令也可以使用 list
命令。
set
命令
set
命令用于设置变量的值,其语法如下:
# 取出 ${} 中变量的字符串 ---> 拼接字符串 ---> 赋值给变量 | |
set(<变量名> ${变量名1} ${变量名2}... ) | |
# 将 string1 string2... 的字符串拼接 ---> 赋值给变量 | |
set(<变量名> string1 string2... ) |
set 实际是字符串拼接,该命令是将 变量名1
、 变量名2
… 的值进行拼接并赋值给 变量名
,如果 变量名
已经存在,则覆盖其值,如果 变量名
不存在,则创建该变量。
list
命令
字符串拼接
list(APPEND <list> [args...]) |
APPEND
:将 args
字符串依次添加到 list
的末尾, APPEND
之后写法与 set
一致,与 set
命令不同的是, list
命令不会覆盖 list
的值,而是将 args
字符串依次添加到 list
的末尾。
字符串移除
当通过 file
查找某个目录下的所有源文件时,其中有些使我们不需要的,例如下面的 main.cpp
$ tree | |
. | |
├── add.cpp | |
├── div.cpp | |
├── main.cpp | |
├── mult.cpp | |
└── sub.cpp |
其中 main
是测试文件,如果我们想把计算器相关的源文件生成动态库给别人使用,只需要 add.cpp
、 div.cpp
、 mult.cpp
、 sub.cpp
这四个文件,那么我们可以通过 list
命令将 main.cpp
移除。
list(REMOVE_ITEM <list> [value...]) |
REMOVE_ITEM
:将 list
中所有 value
字符串移除。
字符串查找
list(FIND <list> <value> <variable>) |
FIND
:在 list
中查找 value
字符串,如果找到,则将 value
字符串在 list
中的索引赋值给 variable
,如果未找到,则将 -1
赋值给 variable
。
字符串替换
list(REPLACE <list> <value> <newvalue>) |
REPLACE
:将 list
中所有 value
字符串替换为 newvalue
字符串。
命令 | 描述 | 使用示例 |
---|---|---|
APPEND | 将 args 添加到列表的末尾 | list(APPEND <list> [args...]) |
APPEND_STRING | 将 args 添加到列表的末尾 | list(APPEND_STRING <list> [args...]) |
APPEND_UNIQUE | 将 args 添加到列表的末尾,如果 args 已经存在,则不添加 | list(APPEND_UNIQUE <list> [args...]) |
CLEAR | 清除列表 | list(CLEAR <list>) |
GET | 获取列表中的元素 | list(GET <list> [<index>...] [<output variable>...]) |
INSERT | 在 列表 中的 某一位置 插入 (索引前插入) 元素 得到 新字符串 |
list(INSERT <list> <index> [<index>...] [<output variable>...]) |
LENGTH | 获取列表的长度并赋值给 变量 |
list(LENGTH <list> <variable>) |
POP_BACK | 删除 列表 中的最后一个元素获取 新字符串 |
list(POP_BACK <list> [<output variable>...]) |
POP_FRONT | 删除 列表 中的第一个元素获取 新字符串 |
list(POP_FRONT <list> [<output variable>...]) |
PREPEND | 将 args 添加到列表的开头 | list(PREPEND <list> [args...]) |
REMOVE_AT | 删除 列表 中的 某一位置 的元素获取 新字符串 |
list(REMOVE_AT <list> [<index>...] [<output variable>...]) |
REMOVE_ITEM | 删除 列表 中所有 value 字符串 |
list(REMOVE_ITEM <list> [value...]) |
REMOVE_DUPLICATES | 删除 列表 中重复的元素 |
list(REMOVE_DUPLICATES <list>) |
JOIN | 将 列表 中的元素用 连接符 连接成一个 新字符串 |
list(JOIN <list> <glue> <output variable>) |
REVERSE | 将 列表 中的元素反转 |
list(REVERSE <list>) |
SORT | 将 列表 中的元素排序 |
list(SORT <list>) |
# 宏定义
在程序测试中,我们经常需要定义一些宏,例如 DEBUG
,通过这些宏来控制程序是否打印调试信息
#include <iostream> | |
#define DEBUG | |
int main(){ | |
int n =10; | |
#ifdef DEBUG | |
std::cout << "debug n=" << n << std::endl; | |
#endif | |
return 0; | |
} |
程序通过 #ifdef DEBUG
判断 DEBUG
是否被定义,如果被定义,则打印调试信息。如果没有被定义,则不打印调试信息,该端代码相当于被注释掉了,因此无法看到日志输出。
为了使得 c++
测试更加方便,我们可以不在代码中定义宏,在 g++/gcc
命令中定义宏,例如
g++ -DDEBUG main.cpp -o main |
g++/gcc
命令通过 -D
指定定义宏的名字,这样在代码中就可以使用 DEBUG
宏了。
CMake
中使用 add_definitions
定义宏
add_definitions(-DDEBUG) |
add_definitions
命令用于定义宏,其中 -D
是 g++/gcc
命令的参数, DEBUG
是宏的名字。
# 嵌套 CMake
在 Linux 的目录是树状结构,所以嵌套 CMake 也是树状结构,最顶层的 CMakeLists.txt 文件称为 根节点
,其他 CMakeLists.txt 文件称为 子节点
。需要了解关于 CMakeLists.txt
的几个信息:
- 根节点的
CMakeLists.txt
中定义变量全局有效。 - 父节点的
CMakeLists.txt
中定义的变量,子节点可以访问。 - 子节点的
CMakeLists.txt
中定义的变量,父节点无法访问。
添加子目录
CMake 中父子节点是如何建立的?
add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL]) |
source_dir
: 子目录的路径,
binary_dir
: 指定输出文件的路径,一般不需要指定,默认即可。
EXCLUDE_FROM_ALL
: 在子路径下的目标默认不会被包含到父路径的 ALL
目标中,并且也会排除在 IDE 工程文件之外。用户必须显式构建在子路径下的目标。
获取父目录的路径
get_filename_component(PARENT_DIR ${CMAKE_CURRENT_SOURCE_DIR} PATH) |
get_filename_component
命令用于获取文件路径,其中 PARENT_DIR
是变量名, ${CMAKE_CURRENT_SOURCE_DIR}
是当前目录的路径, PATH
表示获取路径。