CMake工程指南
目录
一、CMake快速开始
1.1 介绍
1.2 CMake安装
1.3 VS Code CMake 插件安装
1.4 快速样例
二、CMake 命令行工具介绍
2.1 CMake构建流程图
2.2 生成构建系统
2.3 编译链接
2.4 测试
2.5 安装
2.6 打包
2.7 脚本模式
2.8 调用外部命令
2.9 查看帮助
三、CMake 工程实践场景
3.1 可执行文件(编译-链接-安装)
3.1.1 单步操作
3.1.2 重点命令解释
3.1.2.1 cmake_minimum_required
3.1.2.2 project
3.1.2.3 include
3.1.2.4 install
3.1.2.5 add_executable
3.2 静态库(编译-链接-引用)
3.2.1 单步实操
3.2.2 重点命令解释
3.2.2.1 CMake的3大核心-目标-属性-API
3.2.2.1.1 目标-Target
3.2.2.1.2 属性-Property
3.2.2.1.3 操作目标和属性的核心API
3.2.2.2 add_library
3.2.2.3 target_include_directories
3.2.2.4 target_link_libraries
3.2.2.5 set_target_properties和 get_target_properties
3.2.2.6 add_subdirectory
3.2.2.7 file
3.2.3 CMake 内部静态库的⽣成与定位流程
3.3 静态库(编译-链接-安装)
3.3.1 单步实操
3.3.2 重点命令解释
3.3.2.1 export
3.3.2.2 configure_package_config_file
3.3.2.3 write_basic_package_version_file
3.4 静态库(查找-使用)
3.4.1 单步实操
3.4.2 重点命令解释
3.4.2.1 find_package
3.5 动态库(编译-链接-引⽤)
3.5.1 单步实操
3.5.2 重点命令解释
3.5.3 CMake 内部动态库的生成与定位流程
3.6 动态库(编译-链接-安装)
3.6.1 单步实操
3.7 动态库(查找-使用)
3.8 查找并使用第三方库
3.8.1 Module 模式介绍
3.8.2 Config 模式介绍
3.8.3 包名大小写规范
3.8.4 使用Module 模式查找JsonCpp 库
3.8.5 使用Config 模式查找JsonCpp 库
3.9 调用外部命令
3.10 CTest
3.11 CPack
3.11.1 CPack 核心功能
3.11.2 使用CPack 打包发布bin+so
3.11.3 CPack 打包后⼆进制在运⾏时是如何找到动态库的
四. 常用语法介绍
4.1 message 函数
4.2 变量
4.2.1 变量的作用域
4.2.1.1 函数作用域(Function Scope)
4.2.1.2 目录作用域(Directory Scope)
4.2.1.3 持久缓存(Persistent Cache)
4.2.1.4 变量作⽤域规则总结
4.2.2 普通变量:
4.2.3 缓存变量
4.2.4 环境变量(Environment Variables)
4.2.5 三种变量的对比
4.3 if 判断
4.3.1 if 语法格式如下:
4.3.2 支持的语法有:
4.3.3 基本表达式
4.4 循环
4.4.1 foreach 循环
4.4.2 while 循环
4.5 LIST
4.6 函数
4.6.1 函数定义与调用
4.6.2 参数传递
4.6.3 变量作用域
4.6.4 返回值
五、使用CMake 管理构建过程的开源项目介绍
5.1 JsonCpp
5.1.1 GitHub地址:
5.1.2 CMake注释地址:
5.1.3 CMake 语法介绍:
5.1.3.1 基础语法示例
5.1.3.2 新老cmake版本示例
5.1.3.3 添加编译选项示例
5.1.3.4 CTest 示例
5.1.3.5 编译静态库和动态库示例
5.1.3.6 安装pkg-config配置文件
5.1.3.7 安装库文件示例
5.1.3.8 安装头文件示例
5.2 Curl
5.2.1 Github:
5.2.2 CMake注释地址:
5.2.3 CMake 语法介绍:
5.2.3.1 基础语法示例
5.2.3.2 跨平台示例
5.2.3.3 目录结构示例
5.2.3.4 查找第三方模块示例
5.2.3.5 编译静态库和动态库示例
5.2.3.6 安装静态库和动态库
5.2.3.7 安装文件示例:
5.3 CMakeQT-CustomWindow
5.3.1 GitHub地址:
5.3.2 CMake 注释地址:
5.3.3 CMake 语法介绍:
5.3.3.1 安装Qt-5
5.3.3.2 基础语法示例
六、常见问题
6.1 Visual Studio Code 里的 include "xxx.h" 下标记红线如何解决
6.1.1.1 修复方案⼀:
编辑6.1.1.2 修复方案二:
附录1:CMake源文件树构建树⾃动推导模式介绍
附录2:CMake GNUInstallDirs模块类型和安装⽬录映射关系
附录3:CMake INTERFACE, PUBLIC 和 PRIVATE 解释
附录4:CMake 中 include 和 add_subdirectory 命令的区别
附录5:Visual Studio Code Remote SSH模式介绍
一、CMake快速开始
1.1 介绍
CMake 语法简单易上手,功能强大,使用广泛,IDE⽀持度高,已经是C/C++事实上的构建标 准,也是一个十分重要的C/C++工程管理工具。
优势 | 传统方式 | CMake方式 | 改进效果 |
---|---|---|---|
解决跨平台构建难题 | 人工编辑Makefile等配置⽂件 | CMake自动帮我们⽣成构建配置⽂件 | —处配置,到处构建 |
语法简单易上手 | Makefile 等语法复杂 | 语法简单,表达能⼒强⼤ | ⼤幅减少学习成本,提升研发效率 |
解决包管理难题 | ⼿动查找包 | ⾃动查找包 | 包管理规规范化 |
IDE对CMake⽀持 度⾼ | 每个IDE都有⾃⼰的构建⽅式 | 主流IDE都支持使用cmake来构建程序 | —处配置,多IDE⽀持 |
下图展示了:主流C++商业级开发IDE 对CMake的支持情况
1.2 CMake安装
注意:本篇博客cmake命令执行环境为:
编辑环境:VS Code
编译环境:VS Code Remote SSH模式 + Ubuntu
cmake版本最低要求是:3.18
CMake 官方源代码下载地址:https://cmake.org/download/
CMake 官方英文文档地址:https://cmake.org/cmake/help/latest/index.html
Step 1:使用ubuntu自带apt 安装:
shellsudo apt install cmake
Step 2:验证安装:
安装完成后,可通过以下命令验证 CMake 是否安装成功以及查看其版本。
代码块cmake --version
shellcmake version 3.28.3CMake suite maintained and supported by Kitware (kitware.com/cmake).
1.3 VS Code CMake 插件安装
VS Code CMake 插件 官方文档:https://code.visualstudio.com/docs/cpp/cmake-linux
-
语法⾼亮和代码补全:对 CMakeLists.txt ⽂件提供语法⾼亮显⽰,使代码结构更加清晰易 读。同时,⽀持代码补全功能,当你输⼊ CMake 命令或变量时,插件会⾃动提⽰可能的选项,减少⼿动输⼊的错误和时间。
-
智能分析和错误检查:能够对 CMakeLists.txt ⽂件进⾏智能分析,检查其中的语法错误和潜 在问题,并在编辑器中实时显⽰错误提⽰和警告信息,帮助你及时发现和解决问题。
Step 0:打开 VS Code,点击左侧活动栏中的 扩展图标(或按 Ctrl+Shift+X )。
Step 1:在搜索框中输⼊ CMake ,我们选择安装以下4个插件:
- CMake
- CMake Tools
- CMake Language Support
- CMake IntelliSence

1.4 快速样例
本节我们将使用⼀个例子来演示下使用CMake 构建⼀个最简单的C++ 程序,大致了解下CMake的使用方式。
我们创建⼀个新的 hello_world ⼯程。
下图展⽰了使⽤CMake 来管理hello world 程序⽣成的过程

Step 0:目录结构
tree hello_world
├── CMakeLists.txt
└── main.cpp
Step 1:新建⽂件-main.cpp
main.cpp#include <iostream>int main(){std::cout << "hello world!" << std::endl;return 0;}
CMakeLists.txt
# 1 设置能运⾏此⼯程的cmake的最低版本
cmake_minimum_required(VERSION 3.18)
# 2 设置项⽬名称
project(helloWorld)
# 3 添加构建⽬标
add_executable(main main.cpp)
CMake 是⼀个不断迭代的工具(目前最新4.x,历史有3.x),不同版本可能会引⼊新的语法、命令、模块或行为变更。如果项⽬中使用了高版本 CMake 才⽀持的特性(例如特定的函数、⽣成器表达式、目标属性等),而用户本地安装的cmake版本低于项⽬要求的版本,就会出现无法解释或者产⽣不可预知的⾏为。为了防止以上情况出现:
CMake 给我们提供了 cmake_minimum_required ,这个命令会在配置阶段( cmake 命令执行时)检查当前 CMake 版本:
- 若当前版本低于最低要求,CMake 会直接终⽌并报错,明确提⽰ “需要⾄少 X.X 版本”,避免后续因版本不兼容导致的模糊错误。
- 若当前安装的版本满⾜要求,则继续执⾏后续配置流程。
CMake 里的 "目标" 是什么?
在 CMake 中,“⽬标(Target)” 代表了⼀个需要被生成的实体,如可执行文件、静态库/动态 库等,和Makefile⾥的⽬标是⼀个意思,他是现在CMake里最核心的3个概念之⼀。(后面我们会说)
Step 3:运行cmake
代码块
# 运⾏ CMake 命令 就在这⼀步 ⽣成Makefile
cmake ..
代码块
-- The C compiler identification is AppleClang 17.0.0.17000013
-- The CXX compiler identification is AppleClang 17.0.0.17000013
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
.....
-- Build files have been written to:
/Users/lixiaoshen/Desktop/CMakeDemo/hello_world/build
代码块
make
代码块
[ 50%] Building CXX object CMakeFiles/main.dir/main.cpp.o
[100%] Linking CXX executable main
[100%] Built target main
代码块
./hello
代码块
lixiaoshen@lixiaoshens-MacBook-Pro build % ./hello
hello world!
lixiaoshen@lixiaoshens-MacBook-Pro build %
二、CMake 命令行工具介绍
2.1 CMake构建流程图

# 1. 创建构建目录并进⼊mkdir build && cd build# 2. 配置项⽬cmake ..# 3. 构建项⽬make或者cmake --build .# 4. 执⾏测试(如果有)make test或者ctest .# 5. 安装项⽬make install或者cmake --install .# 6. ⽣成安装包make package或者cpack# 7 解压 ⽣成的tar 包tar xvf TestCMakeTools-0.1.1-Linux.tar.gz
Step 0:目录结构
tree cmake_tools
├── build
├── CMakeLists.txt
├── main.cpp
└── test.cpp
Step 1:新建文件-main.cpp
#include <iostream>
int main()
{
std::cout << "hello world!" << std::endl;
return 0;
}
Step 2:新建⽂件-test.cpp
#include <iostream>
#include <cassert>
int main()
{
assert( 1 + 2 == 3);
std::cout << "test passed!" << std::endl;
return 0;
}
代码块
# 1 设置能运⾏此cmake ⼯程的最低cmake版本要求
cmake_minimum_required(VERSION 3.18)
# 2 设置项⽬名称
project(helloWorld)
# 3 添加构建⽬标
# g++ main.cpp -o main
add_executable(main main.cpp)
#⽣成测试⼆进制可执⾏程序
add_executable(testAdd test.cpp)
# 4 开启测试功能 & 集成测试逻辑
include(CTest)
add_test(
NAME Case_Add
COMMAND testAdd
)
# 5 安装⼆进制可执⾏程序到本地
include(GNUInstallDirs)
install(TARGETS main)
# 6 开启打包功能 & 打包⼆进制可执⾏程序
include(CPack)
# cpack 默认收集install 对应的⽬标,然后会把收集到的⽬标 打包在压缩包⾥
2.2 生成构建系统
通过 cmake 可以看到cmake命令⽀持的详细参数,常⽤的参数如下:
代码块
cmake [options] <path-to-source>
cmake [options] <path-to-existing-build>
cmake [options] -S <path-to-source> -B <path-to-build>
参数 | 含义 |
---|---|
-S | 指定源文件根目录,必须包含⼀个CMakeLists.txt⽂件 |
-B | 指定构建⽬录,构建⽣成的中间⽂件和⽬标⽂件的⽣成路径,通常会 保存⼀个CMakeCache.txt来存储⼀些变量。 |
源文件目录用-S 选项指定
在 CMake 中,源⽂件树(Source Tree) 指的是项⽬源代码的⽬录结构,CMake 通过 CMakeLists.txt ⽂件来组织和管理这个结构。通常使⽤顶层CMakeLists.txt 来标识。
构建目录用 -B 选项指定
在 CMake 中,构建树(Build Tree) 是指项⽬构建过程中⽣成的临时文件⽬=目录,它与源文件树 (Source Tree) 相对应。构建树包含编译过程中生成的中间文件(如⽬标⽂件、依赖信息)和最终产物(如可执⾏⽂件、库⽂件)。通常使⽤CMakeCache.txt来标识。
构建时,根据构建中间⽂件是独⽴保存还是放在当前源代码⽬录下, 构建过程分为以下2种:
源内构建:
在源代码树包含的顶级CMakeLists.txt的⽬录下进⾏直接构建。
代码块
cmake .
源外构建:
使用 -B 参数单独指定⼀个build ⽬录,然后在子目录里制定源文件目录也就是包含CMakeLists.txt 的目录:
代码块
cd build && cmake ../ 隐含 ./为构建目录 ../ 为源文件目录
为了维护⼀个纯净的源文件树,可通过使用⼀个单独目录来进行源外构建,也支持在源代码目录下进行构建,但不建议这样做。
安装树 install
⽤于正式存放项目生成的⼆进制程序,静态库,动态库等产物,以便于其他应用程序引用。 GNUInstallDirs 里定义个各个系统下的默认安装路径。linux下默认为
代码块
prefix = /usr/local
CMAKE_INSTALL_PREFIX=${prefix}
2.3 编译链接
代码块
cmake --build <dir> = Build a CMake-generated project binary tree.
生成构建文件以后,就可以使用cmake 来编译链接,也可以使用第三方构建系统进行。因为生成的makefile就在构建树的根目录下,所以可以直接在此目录运行make。
代码块
cmake --build ./ 或者 make
代码块
[ 25%] Building CXX object CMakeFiles/main.dir/main.cpp.o
[ 50%] Linking CXX executable main
[ 50%] Built target main
[ 75%] Building CXX object CMakeFiles/testApp.dir/test.cpp.o
[100%] Linking CXX executable testApp
[100%] Built target testApp
2.4 测试
正常我们在编写功能代码之后,也会编写对应的单元测试代码,然后使用 ctest 来调用我们的单元测试。
代码块
Usage
ctest [options]
如果cmake的配置文件CMakeLists.txt里包含CTest功能,则生成的makefile里也会包含test 伪目标,可以使用make 来执行。
代码块
ctest
或者
make test
代码块
Test project /home/bit/workspace/CMakeClass/cmake_tools/build
Start 1: testAdd
1/1 Test #1: testAdd .......................... Passed 0.00 sec
100% tests passed, 0 tests failed out of 1
Total Test time (real) = 0.00 sec
因为我们的最简hello_world 工程没实现ctest 相关的代码,这里只是演示下命令行。
2.5 安装
cmake --install <dir> = Install a CMake-generated project binary
在单元测试通过之后,我们可以使⽤cmake的安装命令把库和⼆进制发布到本机的标准路径,供大家⼀起开发使用。如果cmake的配置文件CMakeLists.txt里包含install函数,则生成的makefile⾥也会包含install 伪目标,可以使用make 来执行。
cmake --install .
或者
make install
代码块
[100%] Built target main
Install the project...
-- Install configuration: ""
-- Installing: /usr/local/bin/main
2.6 打包
代码块
Usage
cpack [options]
代码块
cpack 或者 make package
代码块
CPack: Create package using STGZ
CPack: Install projects
CPack: - Run preinstall target for: TestCMakeTools
CPack: - Install project: TestCMakeTools []
CPack: Create package
CPack: - package:
/home/bit/workspace/CMakeClass/cmake_tools/build/TestCMakeTools-0.1.1-Linux.sh
generated.
CPack: Create package using TGZ
CPack: Install projects
CPack: - Run preinstall target for: TestCMakeTools
CPack: - Install project: TestCMakeTools []
CPack: Create package
CPack: - package:
/home/bit/workspace/CMakeClass/cmake_tools/build/TestCMakeTools-0.1.1-
Linux.tar.gz generated.
CPack: Create package using TZ
CPack: Install projects
CPack: - Run preinstall target for: TestCMakeTools
CPack: - Install project: TestCMakeTools []
CPack: Create package
CPack: - package:
/home/bit/workspace/CMakeClass/cmake_tools/build/TestCMakeTools-0.1.1-
Linux.tar.Z generated.
同样我们的最简hello_world ⼯程没实现cpack规则,其中就包括没有指定cpack生成器。
2.7 脚本模式
代码块
cmake -P <file> = Process script mode.
CMake脚本模式,不会生成构建产物,也不会生成中间过程。适合处理各种与构建系统无关的⾃动化任务,通过编写简洁的脚本⽂件,你可以实现环境检查、文件处理、部署打包等功能。
以下是hello_world 工程里cmake⽣成用于安装目标的主makefile代码片段,在主makefile里使用 cmake的脚本模式调用了 cmake_install.cmake,在这里执行文件的拷贝等操作。
代码块
# Special rule for the target install
install: preinstall
@$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Install
the project..."
/usr/local/bin/cmake -P cmake_install.cmake
.PHONY : install
2.8 调用外部命令
代码块
cmake -E = CMake command mode.
以下是hello_world 工程里主makefile里的 RM命令,其实就是使用命令模式调用了rm -f 外部命令
代码块
# The command to remove a file.
RM = /usr/bin/cmake -E rm -f
2.9 查看帮助
代码块
cmake --help
代码块
Usage
cmake [options] <path-to-source>
cmake [options] <path-to-existing-build>
cmake [options] -S <path-to-source> -B <path-to-build>
Specify a source directory to (re-)generate a build system for it in the current working directory. Specify an existing build directory to re-generate its build system.
Options
-S <path-to-source> = Explicitly specify a source directory.
-B <path-to-build> = Explicitly specify a build directory.
-C <initial-cache> = Pre-load a script to populate the cache.
-D <var>[:<type>]=<value> = Create or update a cmake cache entry.
-U <globbing_expr> = Remove matching entries from CMake cache.
-G <generator-name> = Specify a build system generator.
-T <toolset-name> = Specify toolset name if supported by
generator.
............
三、CMake 工程实践场景
上⼀章我们介绍了cmake的基础命令行,介绍了如何⽣成构建⽂件,编译链接,安装等。本章节我们会带领大家从一行一行手写CMake文件的方式开始,逐步熟悉如何用CMake 来管理各个场景下的工程需求,比如编译⼆进制,静态库,动态库,打包发布程序等。过程中也会逐步的介绍一些核心的语法和函数,并用一些简单的例子来演示和验证。
3.1 可执行文件(编译-链接-安装)
本例子我们会编译和链接⼀个最简单的输出 hello bits的C++程序可执行程序,并本地运行,然后安装到系统目录下。过程中会介绍⼀些遇到的新的CMake函数。
3.1.1 单步操作
Step 0:目录结构
tree hello
├── CMakeLists.txt
├── build
└── main.cpp
Step 1:新建文件 - CMakeLists.txt
CMakeLists.txt
# 1 设置最低cmake 版本
cmake_minimum_required(VERSION 3.18)
#2 设置项⽬名称
project(InstallHello
VERSION 1.2.3
LANGUAGES C CXX
)
# 3 添加构建⽬标
add_executable(hello main.cpp)
# 4 安装到本地
include(GNUInstallDirs)
install(TARGETS hello)
# printf
message(STATUS "PROJECT_NAME: ${PROJECT_NAME}")
# print version
message(STATUS "PROJECT_VERSION:" ${PROJECT_VERSION})
message(STATUS "PROJECT_VERSION_MAJOR:" ${PROJECT_VERSION_MAJOR})
message(STATUS "PROJECT_VERSION_MINOR:" ${PROJECT_VERSION_MINOR})
message(STATUS "PROJECT_VERSION_PATCH:" ${PROJECT_VERSION_PATCH})
# 打印 默认安装路径
message(STATUS "CMAKE_INSTALL_PREFIX:" ${CMAKE_INSTALL_PREFIX})
CMake 自带的GNUInstallDirs模块严格定义了各个平台下的默认的安装目录, 大多数场景应该严格遵守。
src/main.cpp
// main.cpp
#include <iostream>
int main() {
std::cout << "Hello odoaobo" << std::endl;
return 0;
}
Step 3:运行CMake
代码块
mkdir build && cd build
cmake ../
代码块
-- CMAKE_INSTALL_PREFIX: /usr/local
-- CMAKE_INSTALL_BINDIR: bin
-- Configuring done (0.1s)
-- Generating done (0.0s)
代码块
cmake --build .
代码块
[ 50%] Building CXX object CMakeFiles/hello.dir/src/main.cpp.o
[100%] Linking CXX executable hello
[100%] Built target hello
Step 5:运行
代码块
./hello
代码块
Hello odoaobo
代码块
cmake --install .
代码块
[100%] Built target hello
Install the project...
-- Install configuration: ""
-- Installing: /usr/local/bin/hello
Step 7:运行
代码块
Hello odoaobo
在 Linux 系统中,“安装” 指的是将编译好的⼆进制,库文件(通常是.a .so文件)及其头⽂件复制到系统标准路径或自定义路径,以便其他程序可以链接和使用该库。放在标准路径下的动态库和⼆进制,在其他程序链接时,甚⾄可以不用单独指定 gcc的 -L 和 -I 参数,gcc就可以在默认路径下自动查找,从而简化编译流程。普通的⼆进制安装到默认路径下,也不用的在独立指定PATH,shell 解释器也可以找到⼆进制,并运行。
3.1.2 重点命令解释
下⾯我们会进⼀步介绍上⾯出现在CMakeLists.txt的新函数
3.1.2.1 cmake_minimum_required
函数作用:
指定项⽬所需的最低 CMake 版本,应放在顶级 CMakeLists.txt 的第⼀⾏。
基本形式
代码块
cmake_minimum_required(VERSION <min>[...<policy_max>] [FATAL_ERROR])
参数解释:
参数 | 含义 |
---|---|
VERSION | 关键字,表⽰后⾯跟的是版本号 |
<min> | 版本号,<major>[.<minor> 指定所需的最低 CMake 版本(如 3.18)。 |
FATAL_ERROR(可选) | 若指定,当 CMake 版本不满⾜时会终⽌配置并报错(CMake 2.6+ 默认为 此⾏为)。 |
版本号说明:
不同 Linux 发行版(如 Ubuntu、CentOS、Fedora 等)的软件仓库中,预装的 CMake 版本可能随系统版本更新而变化。系统版本越新,预装的 CMake 版本通常也越新。典型的版本对照表如下:
Linux 发行版 | 版本 | 预装 CMake 版本 |
---|---|---|
Ubuntu LTS | 22.04 (Jammy) | 3.22(通过 apt 安装) |
24.04 (Noble) | 3.28(默认版本更高) | |
Debian | 12 (Bookworm) | 3.22 |
Fedora | 39 | 3.28+ |
CentOS Stream | 9 | 3.22+ |
Arch Linux | 滚动更新 | 最新稳定版(如 3.28+) |
3.1.2.2 project
函数作用:
指定项目名字,放在顶级CMakeLists文件的第二行,子目录中⼀般无需调用。
基本形式
代码块
project(<PROJECT-NAME>)
完整形式
代码块
project(<PROJECT-NAME> [<language-name>...])
project(<PROJECT-NAME>
[VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
[DESCRIPTION <project-description-string>]
[HOMEPAGE_URL <url-string>]
[LANGUAGES <language-name>...])
关键参数
参数 | 含义 |
---|---|
<PROJECT-NAME> | 项⽬名称(如 MyProject),⽤于⽣成默认变量(如 PROJECT_NAME)。 |
VERSION(可选) | 项⽬版本号(如 1.0.0),会⾃动定义 PROJECT_VERSION 等变量。 |
DESCRIPTION(可选) | 项⽬描述信息(用于生成文档或包配置)。 |
HOMEPAGE_URL | 项⽬主页 URL |
LANGUAGES(可选) | 指定项⽬⽀持的语⾔(如 C、CXX、Fortran、ASM 等)。 |
变量 | 描述 |
---|---|
PROJECT_NAME | 项目名称(如 MyProject)。 |
CMAKE_PROJECT_NAME | 顶级项⽬名称(与 PROJECT_NAME 相同)。 |
PROJECT_VERSION | 完整版本号(如 1.2.3)。 |
PROJECT_VERSION_MAJOR | 主版本号(如 1)。 |
PROJECT_VERSION_MINOR | 次版本号(如 2)。 |
PROJECT_VERSION_PATCH | 修订号(如 3)。 |
PROJECT_SOURCE_DIR | 顶级 CMakeLists.txt 所在⽬录(即源⽂件树根⽬录)。 |
PROJECT_BINARY_DIR | 构建⽬录(如 build/)。 |
1 ⼤多数项目都只有⼀个名字,复杂项目如c++的boost 库,每⼀个子功能都设置⼀个project。
2 项目的顶级 CMakeLists.txt 文件必须包含对 project() 命令的直接调用。
PROJECT_NAME 变量的使用场景:
1 动态库的输出名称
2 cmake配置文件的名称
3 命名空间的名称
PROJECT_VERSION 变量的使用场景:
1 打印变量
2 生成pkg-config或者.cmake对应的版本配置文件
3 动态库/静态库的的版本号
代码块
cmake_minimum_required(VERSION 3.10)
# 设置项⽬名称
project(hello VERSION 1.2.3 LANGUAGES CXX)
# 设置 C++ 标准
set(CMAKE_CXX_STANDARD 11)
# 添加可执⾏⽂件
add_executable(hello src/main.cpp)
#add_executable(app src/app.cpp)
# 安装可执⾏⽂件
include(GNUInstallDirs)
install(TARGETS hello)
#install(TARGETS hello RUNTIME DESTINATION bin)
# 输出⼀些调试信息
# name
message(STATUS "PROJECT_NAME: ${PROJECT_NAME}")
message(STATUS "CMAKE_PROJECT_NAME: ${CMAKE_PROJECT_NAME}")
message(STATUS "PROJECT_LANGUAGES: ${PROJECT_LANGUAGES}")
# version
message(STATUS "PROJECT_VERSION: ${PROJECT_VERSION}")
message(STATUS "PROJECT_VERSION_MAJOR: ${PROJECT_VERSION_MAJOR}")
message(STATUS "PROJECT_VERSION_MINOR: ${PROJECT_VERSION_MINOR}")
message(STATUS "PROJECT_VERSION_PATCH: ${PROJECT_VERSION_PATCH}")
# dir
message(STATUS "CMAKE_SOURCE_DIR: ${CMAKE_SOURCE_DIR}")
message(STATUS "CMAKE_BINARY_DIR: ${CMAKE_BINARY_DIR}")
message(STATUS "PROJECT_SOURCE_DIR: ${PROJECT_SOURCE_DIR}")
message(STATUS "PROJECT_BINARY_DIR: ${PROJECT_BINARY_DIR}")
message(STATUS "CMAKE_CURRENT_SOURCE_DIR: ${CMAKE_CURRENT_SOURCE_DIR}")
message(STATUS "CMAKE_CURRENT_BINARY_DIR: ${CMAKE_CURRENT_BINARY_DIR}")
# file
message(STATUS "CMAKE_CURRENT_LIST_FILE: ${CMAKE_CURRENT_LIST_FILE}")
message(STATUS "CMAKE_CURRENT_LIST_DIR: ${CMAKE_CURRENT_LIST_DIR}")
# 输出当前的 CMAKE_INSTALL_PREFIX
message(STATUS "CMAKE_INSTALL_PREFIX: ${CMAKE_INSTALL_PREFIX}")
# 输出当前的 CMAKE_INSTALL_BINDIR
message(STATUS "CMAKE_INSTALL_BINDIR: ${CMAKE_INSTALL_BINDIR}")
代码块
cmake ../
cmake ../
lixiaoshen@lixiaoshens-MacBook-Pro build % cmake ../
-- CMAKE_SOURCE_DIR: /Users/lixiaoshen/Desktop/CMakeDemo/hello
-- CMAKE_BINARY_DIR: /Users/lixiaoshen/Desktop/CMakeDemo/hello/build
-- PROJECT_NAME: hello
-- CMAKE_PROJECT_NAME: hello
-- PROJECT_VERSION: 1.2.3
-- PROJECT_LANGUAGES:
-- PROJECT_VERSION_MAJOR: 1
-- PROJECT_VERSION_MINOR: 2
-- PROJECT_VERSION_PATCH: 3
-- PROJECT_SOURCE_DIR: /Users/lixiaoshen/Desktop/CMakeDemo/hello
-- PROJECT_BINARY_DIR: /Users/lixiaoshen/Desktop/CMakeDemo/hello/build
-- CMAKE_CURRENT_SOURCE_DIR: /Users/lixiaoshen/Desktop/CMakeDemo/hello
-- CMAKE_CURRENT_BINARY_DIR: /Users/lixiaoshen/Desktop/CMakeDemo/hello/build
-- CMAKE_CURRENT_LIST_FILE:
/Users/lixiaoshen/Desktop/CMakeDemo/hello/CMakeLists.txt
-- CMAKE_CURRENT_LIST_DIR: /Users/lixiaoshen/Desktop/CMakeDemo/hello
-- CMAKE_INSTALL_PREFIX: /usr/local
-- CMAKE_INSTALL_BINDIR: bin
3.1.2.3 include
函数作用:
加载指定的脚本文件或者模块到当前CMakeLists执行上下文中并运行。
基本形式
代码块
include(<file|module> [OPTIONAL] [RESULT_VARIABLE <var>] [NO_POLICY_SCOPE])
关键参数
参数 | 含义 |
---|---|
<file|module> | 要运行的的文件或模块名称。 |
从文件或模块加载并运行 CMake 代码。
1 文件的搜索路径:
- 如果指定的是相对路径,则相对当前的正在执行的CMakeLists.txt 所在的目录。
- 如果是绝对路径,那直接从对应的磁盘文件读取文件并加载执行。
2 Module 搜索路径顺序:
- 首先在当前目录查找指定文件。
- 然后在 CMAKE_MODULE_PATH 变量指定的目录中查找。
3 执行逻辑:
- 在当前执行上下文执行被包含的camke代码。
4 CMake 进入子目录之后,内置变量的变化情况
变量名 | 进入子目录后是否变化? | 说明 |
---|---|---|
CMAKE_CURRENT_SOURCE_DIR | 不变化 | 还是父目录的源代码树的目录 |
CMAKE_CURRENT_BINARY_DIR | 不变化 | 还是父目录的构建树的目录 |
CMAKE_CURRENT_LIST_FILE | 变化 | 变为子目录的 CMakeLists.txt 文件全路径 |
CMAKE_CURRENT_LIST_DIR | 变化 | 变为子目录的 CMakeLists.txt ⽂件⽬录 |
举例⼦: root/CMakeLists.txt 中include(⼦⽬录sub/sub.cmake) cmake 在从root 切换到sub时的变 量变化情况
变量名 | 执行CMakeLists.txt时的值 | 执行sub.cmake时的值 | 原因 |
---|---|---|---|
CMAKE_CURRENT_SOUR CE_DIR | root | root | 因为执行上下文依然是A目录 |
CMAKE_CURRENT_LIST_ FILE | root/CMakeLists.txt | root/sub/sub.cmake | |
CMAKE_CURRENT_LIST_ DIR | root | root/sub |
我们构造⼀个新的⼯程 test_include 然后打印对应的变量,观察下变量值的变化情况:
Step 0:目录结构
tree test_include
test_include
├── CMakeLists.txt
├── build
└── sub
└── sub.cmake
Step 1:新建⽂件-CMakeLists.txt
顶层CMakeLists.txt
# 1 设置版本要求
cmake_minimum_required(VERSION 3.18)
# 2 设置项⽬名称
project(TestInclude)
# 3 打印 内置路径变量
message(STATUS "from top-level CMakeLists.txt")
# 打印 当前正在执⾏的源代码⽬录--- 也就是CMakeLists.txt所在的⽬录
message(STATUS "CMAKE_CURRENT_SOURCE_DIR:" ${CMAKE_CURRENT_SOURCE_DIR})
# 打印 当前正在执⾏的cmake 脚本的 完整名称
message(STATUS "CMAKE_CURRENT_LIST_FILE:" ${CMAKE_CURRENT_LIST_FILE})
# 打印当前正在执⾏的cmake 脚本的 全⽬录
message(STATUS "CMAKE_CURRENT_LIST_DIR:" ${CMAKE_CURRENT_LIST_DIR})
# 4 包含⼦⽬录cmake脚本
include(sub/sub.cmake)
Step 2:新建文件-sub.cmake
src/test.cmake
message(STATUS "from sub/sub.cmake")
# 打印 当前正在执⾏的源代码⽬录--- 也就是CMakeLists.txt所在的⽬录
message(STATUS "CMAKE_CURRENT_SOURCE_DIR:" ${CMAKE_CURRENT_SOURCE_DIR})
# 打印 当前正在执⾏的cmake 脚本的 完整名称
message(STATUS "CMAKE_CURRENT_LIST_FILE:" ${CMAKE_CURRENT_LIST_FILE})
# 打印当前正在执⾏的cmake 脚本的 全⽬录
message(STATUS "CMAKE_CURRENT_LIST_DIR:" ${CMAKE_CURRENT_LIST_DIR})
Step 3:运行cmake
代码块
mkdir build && cd build
cmake ../
代码块
-- from top-level CMakeLists.txt
-- CMAKE_CURRENT_SOURCE_DIR:/home/bit/workspace/CMakeClass/test_include
-- CMAKE_CURRENT_LIST_FILE:/home/bit/workspace/CMakeClass/test_include/
CMakeLists. txt
-- CMAKE_CURRENT_LIST_DIR:/home/bit/workspace/CMakeClass/test_include
-- from sub/sub.cmake
-- CMAKE_CURRENT_SOURCE_DIR:/home/bit/workspace/CMakeClass/test_include
-- CMAKE_CURRENT_LIST_FILE:/home/bit/workspace/CMakeClass/test_include/sub/
sub.cmake
-- CMAKE_CURRENT_LIST_DIR:/home/bit/workspace/CMakeClass/test_include/sub
对⽐发现,只有CMAKE_CURRENT_LIST_FILE和CMAKE_CURRENT_LIST_DIR 能真实定位正在执⾏的cmake⽂件,不论是include 还是add_subdirectory命令。
3.1.2.4 install
函数作用:
安装(简单理解为cp) 将 二进制,静态库,动态库,头文件,配置文件 部署到指定目录。
基本形式
代码块
install(TARGETS <targets>... [EXPORT <export-name>]
[RUNTIME DESTINATION <dir>]
[LIBRARY DESTINATION <dir>]
[ARCHIVE DESTINATION <dir>]
[INCLUDES DESTINATION <dir>]
[...])
install(FILES <files>... DESTINATION <dir>
[PERMISSIONS <permissions>...]
[CONFIGURATIONS <configs>...]
[COMPONENT <component>]
[...])
install(DIRECTORY <dirs>... DESTINATION <dir>
[FILE_PERMISSIONS <permissions>...]
[DIRECTORY_PERMISSIONS <permissions>...]
[...])
install(EXPORT <export-name> DESTINATION <dir>
[NAMESPACE <namespace>::]
[FILE <filename>]
[...])
关键参数
参数 | 含义 |
---|---|
TARGETS | 安装使用add_executable和add_library 构建的目标文件。 |
FILES | 安装文件 |
DIRECTORY | 安装整个目录 |
EXPORT | 安装导出目录,用于发布自己的程序,供别人使用。 |
DESTINATION | 指定安装路径,路径可以是绝对路径,也可以是相对路径(相对于 CMAKE_INSTALL_PREFIX )。 |
从上⾯测试include 的工程打印结果可以看到 CMAKE_INSTALL_PREFIX的 默认路径为 /usr/local
3.1.2.5 add_executable
函数作用:
指示 cmake 从源代码生成⼀个可执行文件。
基本形式
代码块
add_executable(<target_name> [WIN32] [MACOSX_BUNDLE]
[EXCLUDE_FROM_ALL]
[source1] [source2 ...])
关键参数
参数 | 含义 |
---|---|
target_name | 可执行文件的名称(不包含扩展名,如 myapp),项目内部唯⼀ |
<sources> | 源文件列表(如 src/main.cpp) |
默认情况下,将在与调用命令的CMakeLists.txt的目录相对应的 build tree directory 中创建可执行文件。 也可以使用 RUNTIME_OUTPUT_DIRECTORY 目标属性的更改默认的输出位置。
3.2 静态库(编译-链接-引用)
上⼀节,我们⽤⼀个简单的hello_world程序演示了如何使用cmake来构建⼀个⼆进制程序。这一节,我们继续和⼤家⼀起学习,如何使用cmake生成和使用⼀个项目内部的静态库。
在最佳工程实践里,工程规模大一点的工程中,我们往往会把⼀些⽐较独⽴的功能(比如如网络、数据库、算法,或者⼀些基础组件,redis-client, mysql-client,jsoncpp, libcurl)封装为独⽴ 的库,由不同的团队来维护,在公司或者开源社区共享,达到复用目的。
本节我们把加法和减法函数2个函数 封装成⼀个MyMath的数学静态库,并在main⼆进制⾥引⽤我 们静态库里的加法和减法函数,来演示下如何使用CMake来管理这⼀场景。
3.2.1 单步实操
我们创建⼀个新的⼯程 my_math 来演示如何生成静态库。
Step 0:目录结构
tree my_math
my_math
├── CMakeLists.txt
├── app
│ ├── CMakeLists.txt
│ └── main.cpp
└── my_lib
├── CMakeLists.txt
├── include
│ └── math.h
└── src
├── add.cpp
└── sub.cpp
Step 1:新建⽂件-CMakeLists.txt
顶层 CMakeLists.txt
# 1 设置最低版本号
cmake_minimum_required(VERSION 3.18)
# 2 设置项⽬名称
project(TestMyMath
LANGUAGES CXX
)
# 3 添加⽬录层级
add_subdirectory(my_lib)
add_subdirectory(app)
Step 2:新建文件-my_lib/CMakeLists.txt
my_lib/CMakeLists.txt
# 1 收集库的源代码
file(GLOB SRC_LISTS "src/*.cpp")
# 2 添加构建⽬标
add_library(MyMath STATIC ${SRC_LISTS})
# 3 设置库的使⽤要求
target_include_directories(MyMath
PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include
)
# 4 修改默认的输出路径
set_target_properties(MyMath PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib
)
Step 3:新建文件-app/CMakeLists.txt
app/CMakeLists.txt
# 1 搜集⽂件列表
file(GLOB SRC_LISTS "*.cpp")
# 2 添加构建⽬标
add_executable(main ${SRC_LISTS})
# 3 添加依赖库列表
target_link_libraries(main PRIVATE MyMath)
# 4 设置输出路径
set_target_properties(main PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
)
# 5 打印库⽂件相对main的相对路径
add_custom_command(
TARGET main POST_BUILD
COMMAND ${CMAKE_COMMAND} -E echo
"$<PATH:RELATIVE_PATH,$<TARGET_FILE:MyMath>,${CMAKE_CURRENT_BINARY_DIR}>"
COMMENT "获取静态库的输出路径"
)
Step 4:新建文件-add.cpp
my_lib/src/add.cpp
#include "math.h"
int add(int a, int b) {
return a + b;
}
Step 5:新建文件-sub.cpp
my_lib/src/sub.cpp
#include "math.h"
int sub(int a, int b) {
return a - b;
}
Step 6:新建文件-math.h
my_lib/include/math.h
int add(int a, int b);
int sub(int a, int b);
Step 7:新建文件-main.cpp
app/main.cpp
#include <iostream>
#include "math.h"
int main()
{
std::cout << "3 + 4 = " << add(3, 4) << std::endl;
std::cout << "3 - 4 = " << sub(3, 4) << std::endl;
}
Step 8:运行cmake
shell
mkdir build && cd build
cmake ../
Step 9:编译链接
代码块
cmake --build .
代码块
[ 20%] Linking CXX static library ../lib/libMyMath.a
[ 60%] Built target MyMath
[ 80%] Linking CXX executable ../bin/main
[100%] Built target main
Step 10:运行
代码块
./app/main
代码块
3 + 4 = 7
3 - 4 = -1
3.2.2 重点命令解释
3.2.2.1 CMake的3大核心-目标-属性-API
3.2.2.1.1 目标-Target
代码块
Target
│
├─ 类型
│ ├─ EXECUTABLE
│ ├─ STATIC / SHARED / MODULE
│ ├─ OBJECT / INTERFACE
│ └─ IMPORTED / ALIAS
│
├─ 属性(≈键值表)
│ ├─ Build impl ─ SOURCES COMPILE_OPTIONS ...
│ ├─ Usage reqs ─ INTERFACE_INCLUDE_DIRECTORIES ...
│ ├─ Output/Install ─ VERSION SOVERSION OUTPUT_NAME ...
│ └─ Meta ─ TYPE ALIASED_TARGET ...
│
├─ API
│ ├─ add_library / add_executable
│ ├─ target_* (compile_options, link_libraries, include_dirs)
│ ├─ set/get_target_properties
│ └─ install(TARGETS) / export
│
└─ 流程
配置期 → 目标注册 + 属性写入
生成期 → 属性转具化到 Make/Ninja
构建期 → 编译 & 链接
安装期 → 使用属性生成 cmake_install.cmake
目标的种类
类型 | 创建命令 | 产物例子 / 说明 |
---|---|---|
EXECUTABLE | add_executable | main ,curl |
STATIC | add_library(... STATIC) | libfoo.a, foo.lib |
SHARED | add_library(... SHARED) | libfoo.so, foo.dll |
MODULE | add_library(... MODULE) | 插件:libplugin.so, 使⽤dlopen 运⾏时加载 |
OBJECT | add_library(... OBJECT) | 仅 .o/.obj,存在于内存,不⽣成库⽂件 |
INTERFACE | add_library(... INTERFACE) | 无库⽂件,携带使⽤要求 |
IMPORTED | add_library(... IMPORTED) | 使⽤cmake 内存⽬标对象引⽤磁盘上的外 部构建产物 |
ALIAS | add_library(... ALIAS) | 为同项目内的现有目标取别名 |
3.2.2.1.2 属性-Property
属性的种类
类别 | 作⽤域 | 典型读/写命令 | 常用属性示例 |
---|---|---|---|
全局属性 (Global) | 整个 CMake 运⾏⽣ 命周期 | get/set_property(GLOBAL PROPERTY …) | CMAKE_ROLE |
⽬录属性 (Directory) | 当前源码⽬录及其⼦ ⽬录 | get/set_property(DIRECTORY PROPERTY …) | INCLUDE_DIRECTORIES |
⽬标属性 (Target) | 单个构建⽬标(库、 可执⾏、接⼝库…) | get/set_property(TARGET <tgt> PROPERTY …) | LINK_LIBRARIES INCLUDE_DIRECTORIES |
源⽂件属性 (Source File) | 单个源码/资源⽂件 | get/set_source_files_properti es | COMPILE_FLAGS |
测试属性 (Test) | 由 add_test() 定义的 单个测试 | get/set_tests_properties() | WORKING_DIRECTORY |
安装⽂件属性 (Installed File) | install() ⽣成的安装 清单条⽬ | set_property(INSTALL … PROPERTY …) | RPATH |
属性的作⽤域与传播范围(main ---->curl )
关键字 | 对当前目标的构 建影响 | 是否传 播 | 对当前⽬标使⽤者 的影响 | 解释 | 例⼦(⾯包和⾯粉的例⼦) |
---|---|---|---|---|---|
PRIVATE | ✅ ⽣效 | ❌ 否 | ❌ ⽣效 | 只⾃⼰⽤ | 制作⾯包的⾯粉品牌不公开 |
PUBLIC | ✅ ⽣效 | ✅ 是 | ✅ ⽣效 | ⾃⼰-下游⽤ | 公开制作⾯包的⾯粉的品牌 |
INTERFAC E | ❌ 不⽣效 | ✅ 是 | ✅ ⽣效 | ⾃已不⽤-下 游⽤ | 说明书,说明⽤什么⾯粉制 作,不卖东西 |
3.2.2.1.3 操作目标和属性的核心API
类别 | 典型命令(可选关键词) | 主要作⽤ | 涉及的核⼼属性(部分⽰例) |
---|---|---|---|
1. 通⽤读 / 写接 ⼝ | set_target_properties() get_target_property() | 任意⽬标属性的设置、 追加、查询(最底层 API) | 任何 prop_tgt |
2. 编译阶段相关 | target_compile_definitions target_compile_options target_precompile_headers target_include_directories target_sources | 控制源⽂件编译:宏定 义、编译选项、语⾔特 性、预编译头、包含⽬ 录、源⽂件列表等 | COMPILE_DEFINITIONS、 COMPILE_OPTIONS、 COMPILE_FEATURES、 PRECOMPILE_HEADERS、 INCLUDE_DIRECTORIES、 SOURCES 等 |
3. 链接 & 输出 阶段相关 | target_link_libraries target_link_options target_link_directories | 配置⽬标被链接时的 库、选项及搜索路径 | LINK_LIBRARIES INTERFACE_LINK_LIBRARIES LINK_OPTIONS INTERFACE_LINK_OPTIONSLI NK_DIRECTORIES INTERFACE_LINK_DIRECTORIE S |
4. 安装 & 打包 阶段相关 | install(TARGETS …) install(EXPORT …) | ⽣成安装规则与包,控制⽬标在安装树中的布局及其运⾏时⾏为 | RUNTIME_OUTPUT_DIRECTO RY、 LIBRARY_OUTPUT_DIRECTOR Y、 ARCHIVE_OUTPUT_DIRECTOR Y EXPORT_NAME、 INSTALL_RPATH |
3.2.2.2 add_library
函数作用:
添加⼀个静态库或者动态库⽬标,让cmake 从指定的⽂件列表⽣成。
基本形式
代码块
add_library(<name> [STATIC | SHARED | MODULE] [EXCLUDE_FROM_ALL] [<source>...])
参数解释:
参数 | 含义 |
---|---|
<name> | 库的名称(不包含前缀和后缀,如 Foo 会⽣成 libFoo.a)。项⽬内部唯⼀ |
STATIC | 创建静态库(默认值,若不指定类型)。 |
SHARED | 创建动态库(共享库)。 |
【source】 | 构建库的源⽂件列表。 |
默认情况下,将在与调用命令的源树目录相对应的构建树目录中创建库文件。 可以使用 ARCHIVE_OUTPUT_DIRECTORY 、 LIBRARY_OUTPUT_DIRECTORY 和RUNTIME_OUTPUT_DIRECTORY ⽬标属性修改默认的输出路径。
3.2.2.3 target_include_directories
函数作用:
设置目标在开发和发布阶段的头⽂件搜索⽬录,可以传递性传播给下游的使⽤⽅。
基本形式
代码块
target_include_directories(<target>
[SYSTEM] # path 告诉编译器“这些是系统头⽂件”,GCC/Clang 会⽤ -isystem⽽⾮ -I,从⽽抑制第三⽅头引出的警告。
[BEFORE] # 把路径插到已有列表最前⾯
<INTERFACE|PUBLIC|PRIVATE> path1 [path2 ...]
[<INTERFACE|PUBLIC|PRIVATE> pathN ...] …
)
最佳实践推荐使⽤target_include_directories, ⽽不要⽤include_directories,后者或导致搜索路 径被误添加到其他⽬标。
参数解释:
参数 | 含义 |
---|---|
<target> | ⽬标名称(由 add_executable 或 add_library 定义) 可以是普通库/可执行,也可以是 INTERFACE_LIBRARY 或导⼊⽬标 (IMPORTED) |
<INTERFACE|PUBLIC|PRIVATE> | 属性的作⽤域关键字 见附录3 |
path | 头⽂件搜索路径: 如果是相对路径则相对于当前的CMAKE_CURRENT_SOURCE_DIR |
通过target_include_directories添加的 路径 最终是通过gcc 的 -I 参数传递给编译器的。
3.2.2.4 target_link_libraries
函数作用:
设置⼆进制⽬标的依赖库列表,相当于使⽤通⽤的set属性设置函数设置了LINK_LIBRARIES或者 INTERFACE_LINK_LIBRARIES这个属性,在cmake源代码⾥这2个属性是2个 vector<string> 成员。 最终以-l的形式出现在gcc参数⾥。
基本形式
代码块
target_link_libraries(<target>
<PRIVATE|PUBLIC|INTERFACE> <item>...
[<PRIVATE|PUBLIC|INTERFACE> <item>...]...)
set_target_properties(<target> PROPERTIES
LINK_LIBRARIES|INTERFACE_LINK_LIBRARIES <item>...
)
参数解释:
参数 | 含义 |
<target> | ⽬标名称(由 add_executable 或 add_library 定义) |
<INTERFACE|PUBLIC|PRIVATE> | 属性的作⽤域关键字 见附录3 |
PRIVATE 关键字:相当于使⽤set_target_properties 设置了LINK_LIBRARIES属性,设置的库列表只会写进⽬标的LINK_LIBRARIES列表⾥。
INTERFACE 关键字:相当于使⽤set_target_properties 设置了INTERFACE_LINK_LIBRARIES,只会写进⽬标的INTERFACE_LINK_LIBARIERS列表⾥。
PUBLIC 关键字设置的列表会同时写进LINK_LIBRARIES和INTERFACE_LINK_LIBRARIES⾥。
INTERFACE_LINK_LIBRARIES 列表出现的库会被传播给这个目标的使用方。
通过 target_link_libraries最终是通过gcc 的-l 选项传递给链接器的。
下⾯我们新建test_target_link_libraries 这个⼯程,通过设置属性观察属性的传递过程,并观察编译器和链接器的编译链接参数。
Step 0:⽬录结构
tree test_get_prop
├── CMakeLists.txt
├── main.cpp
└── src
├── add.cpp
└── CMakeLists.txt
Step 1:新建⽂件-CMakeLists.txt
CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(TestLinkLibrary
LANGUAGES CXX
)
add_subdirectory(src)
add_executable(main main.cpp)
target_link_libraries(main PRIVATE add)
Step 2:新建⽂件-src/CMakeLists.txt
src/CMakeLists.txt
add_library(add STATIC add.cpp)
# 头⽂件包含路径
target_include_directories(add PRIVATE "/usr/local/include/private")
target_include_directories(add PUBLIC "/usr/local/include/public")
target_include_directories(add INTERFACE "/usr/local/include/interface")
# 库⽂件搜索路径
target_link_directories(add PRIVATE "/usr/local/lib/private")
target_link_directories(add PUBLIC "/usr/local/lib/public")
target_link_directories(add INTERFACE "/usr/local/lib/interface")
# 依赖库列表
target_link_libraries(add INTERFACE "pthread")
Step 3:新建⽂件-src/add.cpp
src/add.cpp
// 空⽂件, 不真的使⽤add的逻辑,就是⽣成⼀个空的elf格式为库⽂件
Step 4:新建⽂件-main.cpp
main.cpp
int main()
{
return 0;
}
Step 5:运⾏cmake
代码块
cmake ../
代码块
-- INTERFACE_INCLUDE_DIRECTORIES:
/usr/local/include/public;/usr/local/include/interface
-- INCLUDE_DIRECTORIES: /usr/local/include/public
-- INTERFACE_LINK_DIRECTORIES: /usr/local/lib/public;/usr/local/lib/interface
-- LINK_DIRECTORIES: /usr/local/lib/public
Step 6:编译链接
代码块
cmake --build . -v
代码块
1 Change Dir: '/Users/lixiaoshen/Desktop/CMakeDemo/test_get_prop/build'
2
3 cd /Users/lixiaoshen/Desktop/CMakeDemo/test_get_prop/build/src &&
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/
usr/bin/c++ -Dadd_EXPORTS -I/usr/local/include/public -std=gnu++11 -isysroot
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/
SDKs/MacOSX15.4.sdk -mmacosx-version-min=15.3 -fPIC -MD -MT
src/CMakeFiles/add.dir/add.cpp.o -MF CMakeFiles/add.dir/add.cpp.o.d -o
CMakeFiles/add.dir/add.cpp.o -c
/Users/lixiaoshen/Desktop/CMakeDemo/test_get_prop/src/add.cpp
4 [ 50%] Linking CXX shared library libadd.dylib
5 cd /Users/lixiaoshen/Desktop/CMakeDemo/test_get_prop/build/src &&
/usr/local/bin/cmake -E cmake_link_script CMakeFiles/add.dir/link.txt --
verbose=1
6 /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/
usr/bin/c++ -isysroot
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/
SDKs/MacOSX15.4.sdk -mmacosx-version-min=15.3 -dynamiclib -Wl,-
headerpad_max_install_names -o libadd.dylib -install_name @rpath/libadd.dylib
CMakeFiles/add.dir/add.cpp.o -L/usr/local/lib/public
7 ld: warning: search path '/usr/local/lib/public' not found
8 [ 50%] Built target add
9
10 /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/
usr/bin/c++ -I/usr/local/include/public -I/usr/local/include/interface -
std=gnu++11 -isysroot
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/
SDKs/MacOSX15.4.sdk -mmacosx-version-min=15.3 -MD -MT
CMakeFiles/testSetProp.dir/main.cpp.o -MF
CMakeFiles/testSetProp.dir/main.cpp.o.d -o
CMakeFiles/testSetProp.dir/main.cpp.o -c
/Users/lixiaoshen/Desktop/CMakeDemo/test_get_prop/main.cpp
11 [100%] Linking CXX executable testSetProp
12 /usr/local/bin/cmake -E cmake_link_script CMakeFiles/testSetProp.dir/link.txt -
-verbose=1
13 /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/
usr/bin/c++ -isysroot
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/
SDKs/MacOSX15.4.sdk -mmacosx-version-min=15.3 -Wl,-search_paths_first -Wl,-
headerpad_max_install_names CMakeFiles/testSetProp.dir/main.cpp.o -o
testSetProp -L/usr/local/lib/public -L/usr/local/lib/interface -Wl,-
rpath,/usr/local/lib/public -Wl,-rpath,/usr/local/lib/interface -Wl,-
rpath,/Users/lixiaoshen/Desktop/CMakeDemo/test_get_prop/build/src
src/libadd.dylib
14 ld: warning: search path '/usr/local/lib/public' not found
15 ld: warning: search path '/usr/local/lib/interface' not found
16 [100%] Built target testSetProp
1 target_link_directories 和 target_include_directories 设置的属性保存在 target上,并传递给 gcc 编译和链接器。
2 PUBLIC的属性 自己在编译和链接(开发阶段)会使⽤,也会传递给使⽤者的开发阶段。
3 INTERFACE 属性 会传递给使⽤者的编译和链接阶段(开发阶段),库⾃⼰开发阶段不会使⽤。
3.2.2.5 set_target_properties和 get_target_properties
函数作用:
设置/查询 ⽬标(如可执⾏⽂件、库)的各种属性,控制编译、链接、安装等⾏为
基本形式
代码块
set_target_properties(<target1> <target2> ...
PROPERTIES <prop1> <value1>
<prop2> <value2> ...)
get_target_property(<variable> <target> <property>)
参数解释:
参数 | 含义 |
---|---|
<target1> | 库的名称 |
<prop1> <value1> | 属性名字和值 |
常见的的属性名字和含义
属性名字 | 含义 | gcc 选项 |
---|---|---|
INCLUDE_DIRECTORIES | 构建规范-⾃⼰编译时包含的⽬录 | -I |
INTERFACE_INCLUDE_DIRECTORIES | 使⽤要求-下游适⽤房需要包含的⽬ 录 | -I |
LINK_LIBRARIES | 构建规范-⾃⼰链接时需要链接的库 列表 | -l |
INTERFACE_LINK_LIBRARIES | 使⽤要求-⾃⼰链接时需要链接的库 列表 | -l |
LIBRARY_OUTPUT_DIRECTORY | 库的输出路径 | |
LIBRARY_OUTPUT_NAME | 库的⽂件的输出名字 | |
BUILD_RPATH | 构建⽬录中的运⾏时库⽂件搜索路 径 | -Wl,-rpath |
INSTALL_RPATH | 安装⽬录中的运⾏时库⽂件搜索路 径 | -Wl,-rpath |
其他属性详⻅官⽅⽂档 |
下⾯我们将使⽤test_set_target_properties⼯程来验证库属性的设置和更新
Step 0:⽬录结构
tree test_set_target_properties
├── CMakeLists.txt
├── main.cpp
└── src
├── add.cpp
└── CMakeLists.txt
Step 1:新建⽂件-CMakeLists.txt
CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(TestProp
LANGUAGES CXX
)
add_subdirectory(src)
add_executable(main main.cpp)
target_link_libraries(main PRIVATE add)
Step 2:新建⽂件-src/CMakeLists.txt
src/CMakeLists.txt
add_library(add SHARED add.cpp)
set_target_properties(add PROPERTIES
# 1 编译类参数
COMPILE_OPTIONS "-g"
COMPILE_OPTIONS "-O3"
COMPILE_OPTIONS "-fPIC"
INCLUDE_DIRECTORIES "/public"
INTERFACE_INCLUDE_DIRECTORIES "/interface"
# 2 链接类参数
LINK_DIRECTORIES "/public"
INTERFACE_LINK_DIRECTORIES "/interface"
LINK_LIBRARIES "curl"
INTERFACE_LINK_LIBRARIES "jsoncpp"
# 3 输出类参数
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
# 4 安装类参数
BUILD_RPATH "${CMAKE_BINARY_DIR}/lib"
INSTALL_RPATH "lib" #//usr/local/lib
OUTPUT_NAME "add"
VERSION "1.2.3"
SOVERSION "20"
)
Step 3:新建⽂件-add.cpp
src/add.cpp
//空⽂件, 不真的使⽤add的逻辑,就是⽣成⼀个空的elf格式为库⽂件
Step 4:新建⽂件-main.cpp
main.cpp
int main()
{
return 0;
}
Step 5:运⾏cmake
代码块
cmake ../
代码块
-- OUTPUT_NAME: add
-- VERSION: 1.2.3
-- SOVERSION: 1
-- ARCHIVE_OUTPUT_DIRECTORY:
/Users/lixiaoshen/Desktop/CMakeDemo/test_set_prop/build/lib
-- INCLUDE_DIRECTORIES:
/Users/lixiaoshen/Desktop/CMakeDemo/test_set_prop/include;/usr/local/include/pu
blic
-- INTERFACE_INCLUDE_DIRECTORIES:
/Users/lixiaoshen/Desktop/CMakeDemo/test_set_prop/include;/usr/local/include/pu
blic;/usr/local/include/interface
-- INTERFACE_LINK_LIBRARIES: bits
-- INTERFACE_LINK_DIRECTORIES:
/Users/lixiaoshen/Desktop/CMakeDemo/test_set_prop/build/lib;/usr/local/lib/publ
ic;/usr/local/lib/interface
-- INTERFACE_LINK_OPTIONS: -
L/Users/lixiaoshen/Desktop/CMakeDemo/test_set_prop/build/lib
-- INTERFACE_COMPILE_OPTIONS: -
I/Users/lixiaoshen/Desktop/CMakeDemo/test_set_prop/include
1 几乎所有的cmake的编译和链接属性都可以通过 set/get ⽅式设置和获取
2 target_include_directories和target_link_directories 设置的路径 最终也是存储在 target 上⾯。
3.2.2.6 add_subdirectory
函数作用:
添加⼦⽬录到构建树,cmake会⾃动进⼊到源码树⼦⽬录,执⾏位于⼦⽬录⾥CMakeLists.txt⽂
件。
基本形式
代码块
add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL] [SYSTEM])
参数解释:
参数 | 含义 |
---|---|
source_dir | 通常为当前⽂件夹下的⼦⽬录的名字。 |
【binary_dir】 | cmake会在构建树⾥创建同名的⼦⽬录,⽤于保存⼦⽬录的的cmake⽂件⾥ ⽣成的⽬标和⼆进制 |
关键行为
1. 处理顺序
- CMake 会⽴即处理 source_dir 中的 CMakeLists.txt,当前⽂件的处理会暂停,直到⼦⽬录处理完毕,在继续处理当前⽂件add_subdirectory之后的命令。
2. 路径解析
- source_dir 相对路径:相对于当前 CMakeLists.txt ⽂件所在⽬录。
- binary_dir 相对路径:相对于当前构建⽬录(不指定,则使⽤ source_dir )。
3. 变量作用域
- ⼦⽬录中定义的变量默认是局部的,不会影响⽗⽬录。
- 可通过 set(xxx PARENT_SCOPE) 将变量传递到⽗⽬录。
- 缓存变量是全局的,⼦⽬录⾥设置的缓存变量,⽗⽬录也可以获取到
4. CMake 在当前上下⽂执行完add_subdirectory命令,进⼊到⼦⽬录之后,在开始执⾏ CMakeLists.txt时会修改的内置变量:
变量名 | 进入子目录后是否变化? | 说明 |
---|---|---|
CMAKE_CURRENT_SOURCE_DIR | ✅ 变化 | 变为⼦⽬录的源代码树的⽬录 |
CMAKE_CURRENT_BINARY_DIR | ✅ 变化 | 变为⼦⽬录的构建树的⽬录 |
CMAKE_CURRENT_LIST_FILE | ✅ 变化 | 变为⼦⽬录的 CMakeLists.txt ⽂件全路 径 |
CMAKE_CURRENT_LIST_DIR | ✅ 变化 | 变为⼦⽬录的 CMakeLists.txt ⽂件⽬录 |
1. 注意同include变量的对CMAKE_CURRENT_SOURCE_DIR 的影响的区别。
2. 不管是include模式还是add_subdirectory,要得到相对于正在执⾏的cmake ⽂件,建议
使⽤CMAKE_CURRENT_LIST_FILE 作为相对路径的参考点。
下⾯我们新建test_add_sub_dir,内容如下:
Step 0 :目录结构如下:
顶层CMakeLists.txt
├── CMakeLists.txt
└── sub
└── CMakeLists.txt
Step 1 :新建-CMakeLists.txt
代码块
# 1 设置版本要求
cmake_minimum_required(VERSION 3.18)
# 2 设置项⽬名称
project(TestAddSubDir)
# 3 打印 内置路径变量
message(STATUS "from top-level CMakeLists.txt")
# 打印 当前正在执⾏的源代码⽬录--- 也就是CMakeLists.txt所在的⽬录
message(STATUS "CMAKE_CURRENT_SOURCE_DIR:" ${CMAKE_CURRENT_SOURCE_DIR})
# 打印 当前正在执⾏的cmake 脚本的 完整名称
message(STATUS "CMAKE_CURRENT_LIST_FILE:" ${CMAKE_CURRENT_LIST_FILE})
# 打印当前正在执⾏的cmake 脚本的 全⽬录
message(STATUS "CMAKE_CURRENT_LIST_DIR:" ${CMAKE_CURRENT_LIST_DIR})
# 4 包含⼦⽬录cmake脚本
add_subdirectory(sub)
Step 1 :新建-sub/CMakeLists.txt
app/CMakeLists.txt
message(STATUS "from sub/CMakeLists.txt")
# 打印 当前正在执⾏的源代码⽬录--- 也就是CMakeLists.txt所在的⽬录
message(STATUS "CMAKE_CURRENT_SOURCE_DIR:" ${CMAKE_CURRENT_SOURCE_DIR})
# 打印 当前正在执⾏的cmake 脚本的 完整名称
message(STATUS "CMAKE_CURRENT_LIST_FILE:" ${CMAKE_CURRENT_LIST_FILE})
# 打印当前正在执⾏的cmake 脚本的 全⽬录
message(STATUS "CMAKE_CURRENT_LIST_DIR:" ${CMAKE_CURRENT_LIST_DIR})
Step 2 :运行cmake
代码块
cmake ../
代码块
-- from top-level CMakeLists.txt
-- CMAKE_CURRENT_SOURCE_DIR:/home/bit/workspace/CMakeClass/test_add_subdir
--
CMAKE_CURRENT_LIST_FILE:/home/bit/workspace/CMakeClass/test_add_subdir/
CMakeLists.txt
-- CMAKE_CURRENT_LIST_DIR:/home/bit/workspace/CMakeClass/test_add_subdir
-- from sub/CMakeLists.txt
-- CMAKE_CURRENT_SOURCE_DIR:/home/bit/workspace/CMakeClass/test_add_subdir
/sub
--
CMAKE_CURRENT_LIST_FILE:/home/bit/workspace/CMakeClass/test_add_subdir/sub/
CMakeLists.txt
-- CMAKE_CURRENT_LIST_DIR:/home/bit/workspace/CMakeClass/test_add_subdir/sub
-- Configuring done (0.4s)
-- Generating done (0.0s)
-- Build files have been written to:
/home/bit/workspace/CMakeClass/test_add_subdir/build
1 可以看到,add_subdirectory 之后,src⾥的变量⾃动添加了app ⼦⽬录。
2 ⼦⽬录cmake ⽂件处理完成之后,才会回到main⾥继续执⾏,可以C语⾔ 函数调⽤流程类似。
3.2.2.7 file
函数作用:
查看目录下的所有⽂件,如果匹配规则,则添加⽂件名字到⽂件列表中,是否需要递归,需要显示指定。
基本形式
代码块
file(GLOB|GLOB_RECURSE <variable> [LIST_DIRECTORIES true|false]
[RELATIVE <path>] [CONFIGURE_DEPENDS] <globbing-expressions>...
)
参数解释:
参数 | 含义 |
---|---|
GLOB | 匹配当前⽬录下的⽂件(不递归⼦⽬录) |
GLOB_RECURSE | 递归匹配当前⽬录及其所有⼦⽬录下的⽂件。 |
<out-var> | 收集到的⽂件列表变量 |
<globbing-expr> | 通配符表达式(如 *.cpp、src/**/*.h) |
3.2.3 CMake 内部静态库的⽣成与定位流程
在上⾯的⼆进制调⽤本项⽬内部的静态库中函数例⼦,⼤家有没有这样的问题,⼆进制是如何定位到静态库的地址的,我刚开始⼀直会有这样的疑惑,但那会限于⾃⾝知识体系的单薄,没有找到合适的答案,⽹上有好多版本,但是都和cmake源代码对应不起来,官⽅⽂档也没有提及这个过程。在多年后的今天,写这个课件时,我仔细阅读了cmake的源代码,现在和⼤家⼀起分享下这个答案。
在cmake⾥当你添加⼀个⽬标⽐如静态库后,cmake会在配置阶段⾃动跟踪静态库的输出位置, 然后在⽣成阶段,会把这个输出地址通过-L参数传递给链接器,链接器就可以找到项⽬内部的静态库,整个过程的产物就是可视化的makefile。⼀切尽在makefile⾥。下⾯就详细展开这个步骤:
第⼀步:目标生成
当你写下add_library(MyMath STATIC add.cpp sub.cpp)这⼀⾏命令时,cmake内部会在全局的 Targets 容器中注册⼀个名为 MyMath 的cmTarget⽬标。
第二步:目标信息存储
每个cmTargetInternals会存储⽬标的名称,类型,源⽂件列表,输出地址等属性。当你使⽤ LIBRARY_OUTPUT_DIRECTORY等属性修改⽬标输出地址的时候,cmake也会更新最终的输出地址。
第三步:生成器阶段-定位静态库路径
CMake 配置阶段结束后,进⼊⽣成阶段(cmLocalGenerator、cmGlobalGenerator),⽣成器会遍历所有⽬标(cmTarget),根据⽬标的属性(如 ARCHIVE_OUTPUT_DIRECTORY)和平台规则,推导出静态库的实际输出路径。⽐如:build/libmylib.a。
第四步:链接命令的⽣成
生成器会为每⼀个⽬标⽣成链接规则,其中包括⽬标⽂件的输出路径,最终会⽣成⼀下 makefile 指令 g++ main.o -o myexe build/libmylib.a 这样,链接器(g++/ld)就能找到并链接libmylib.a。
上面都是理论过程,下⾯我们通过例⼦来验证下这个过程,过程中使⽤到了配置⽣成阶段的⼀个⽣成器表达式,⽤于获取⽬标的输出路径:$<TARGET_FILE:MyMath>。
⽣成器表达式(Generator Expression)是 CMake 中⼀种特殊的语法,⽤于在⽣成阶段(即 CMake ⽣成构建系统⽂件时)动态计算和展开内容。以 $<...> 的形式出现,常⽤于 target 属性、编译选项、链接参数、install 规则计算。
我们使⽤静态库的编译链接和使⽤⾥的my_math 这个项⽬,只需要在app/CMakeLists.txt 后⾯追加⼀个命令即可,具体过程如下:
Step 0:修改⽂件-app/CMakeLists.txt
my_lib/CMakeLists.txt
# 1 搜集⽂件列表
file(GLOB SRC_LISTS "*.cpp")
# 2 添加构建⽬标
add_executable(main ${SRC_LISTS})
# 3 添加依赖库列表
target_link_libraries(main PRIVATE MyMath)
# 4 设置输出路径
set_target_properties(main PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
)
# 5 打印静态库的输出路径
add_custom_command(
TARGET main POST_BUILD
COMMAND ${CMAKE_COMMAND} -E echo "$<TARGET_FILE:MyMath>"
COMMENT "获取静态库的输出路径"
)
Step 1:运行cmake
代码块
cd build
rm -fr .
cmake ../
Step 2:编译链接
代码块
make
代码块
[ 60%] Built target MyMath
[ 80%] Linking CXX executable ../bin/main
获取静态库的输出路径
/home/bit/workspace/CMakeClass/my_math/build/lib/libMyMath.a
[100%] Built target main
Step 3:查看makefile⽂件
cmake 会根据⽬标的输出路径⽣成实际的链接规则
代码块
cat app/CMakeFiles/main.dir/link.txt
代码块
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/
usr/bin/c++ -isysroot
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/
SDKs/MacOSX15.4.sdk -mmacosx-version-min=15.3 -Wl,-search_paths_first -Wl,-
headerpad_max_install_names CMakeFiles/main.dir/main.cpp.o -o ../bin/main
../lib/libMyMath.a
3.3 静态库(编译-链接-安装)
上⼀节我们⽤⼀个简单的加法和减法数学库演⽰了如何⽣成并链接项⽬内部的⼀个静态库。这节我们还是⽤同样的例⼦和⼤家⼀起演示下,如何发布我们⽣成的静态库到linux下的标准路径,以供其他软件引⽤,从⽽达到真正代码复⽤的⽬的。
在下⼀节,我们将演⽰如果使⽤cmake的find_package函数查找我们发布的静态库,并在其他项⽬⾥引⽤。
3.3.1 单步实操
Step 0:⽬录结构
tree install_static_mymath
├── CMakeLists.txt
└── my_lib
├── CMakeLists.txt
├── Config.cmake.in
├── include
│ └── math.h
└── src
├── add.cpp
└── sub.cpp
Step 1:新建文件-CMakeLists.txt
CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(InstallMyMath
LANGUAGES CXX
)
add_subdirectory(my_lib)
Step 2:新建文件-my_lib/CMakeLists.txt
my_lib/CMakeLists.txt
# 1 收集源代码
file(GLOB SRC_LISTS "src/*.cpp")
# 2 添加构建⽬标
add_library(MyMath STATIC ${SRC_LISTS})
# 3 设置库的使⽤要求,也就是下游消费者必须包含的头⽂件搜索路径
target_include_directories(MyMath INTERFACE
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>" # include
)
# 4 设置库的默认输出路径
set_target_properties(MyMath PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib
)
# 5 安装我们的静态库
include(GNUInstallDirs)
install(TARGETS MyMath
EXPORT MyMathTargets
DESTINATION ${CMAKE_INSTALL_LIBDIR} # lib
)
# 6 安装头⽂件
install(DIRECTORY include/
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/math #
/usr/local/include/math/math.h
FILES_MATCHING PATTERN "*.h"
)
# 7 安装导出⽬标集合 到 构建树
export(EXPORT MyMathTargets
FILE ${CMAKE_CURRENT_BINARY_DIR}/MyMathTargets.cmake
)
# 8 安装导出⽬标集合 到 安装树
install(EXPORT MyMathTargets
FILE MyMathTargets.cmake
NAMESPACE MyMath:: # MyMath::MyMath
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyMath #
/usr/local/lib/cmake/MyMath/MyMathTargets.cmake
)
# 9 ⽣成find_package 需要的 配置⽂件
include(CMakePackageConfigHelpers)
configure_package_config_file(
${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/MyMathConfig.cmake
INSTALL_DESTINATION "lib/cmake/MyMath"
)
# 10 安装配置⽂件到cmake 标准的安装路径
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/MyMathConfig.cmake
DESTINATION "lib/cmake/MyMath"
)
Step 3:新建文件-Config.cmake.in
my_lib/Config.cmake.in
@PACKAGE_INIT@
include(${CMAKE_CURRENT_LIST_DIR}/MyMathTargets.cmake)
Step 4:新建文件-math.h
my_lib/include/math.h
int add(int a, int b);
int sub(int a, int b);
Step 5:新建⽂件-add.cpp
my_lib/src/add.cpp
int add(int x, int y)
{
return x + y;
}
Step 6:新建⽂件-sub.cpp
my_lib/src/sub.cpp
int sub(int x, int y)
{
return x - y;
}
Step 7:运⾏cmake
shell
mkdir build && cd build
cmake ../
Step 8:编译链接
代码块
cmake --build .
代码块
[ 33%] Building CXX object my_lib/CMakeFiles/MyMath.dir/src/add.cpp.o
[ 66%] Building CXX object my_lib/CMakeFiles/MyMath.dir/src/del.cpp.o
[100%] Linking CXX static library libMyMath.a
[100%] Built target MyMath
Step 9:安装
代码块
cmake --install .
代码块
-- Install configuration: ""
-- Installing: /usr/local/lib/libMyMath.a
-- Up-to-date: /usr/local/include
-- Up-to-date: /usr/local/include/math.h
-- Installing: /usr/local/lib/cmake/MyMath/MyMathTargets.cmake
-- Installing: /usr/local/lib/cmake/MyMath/MyMathTargets-noconfig.cmake
-- Installing: /usr/local/lib/cmake/MyMath/MyMathConfig.cmake
-- Installing: /usr/local/lib/cmake/MyMath/MyMathConfigVersion.cmake
3.3.2 重点命令解释
3.3.2.1 export
函数作用:
将项⽬中的⽬标(如可执⾏⽂件、库)及其相关属性导出到⼀个⽂件中,以便在其他项⽬中使⽤。
基本形式
代码块
export(EXPORT <export_name>
FILE <file_name>
[NAMESPACE <namespace>]
[DESTINATION <destination>]
[OPTIONAL]
[CONFIGURATIONS <config1> [<config2>...]]
[INCLUDE_INSTALL_DIRS])
参数解释:
参数 | 含义 |
---|---|
<export_name> | 导出集的名称,⽤于在其他项⽬中引⽤ |
<file_name> | 导出⽂件的名称,通常为 <export_name>Config.cmake |
NAMESPACE <namespace>: (可选) | 为导出的⽬标添加命名空间,避免命名冲突 |
DESTINATION <destination>: (可选) | 指定导出⽂件的安装⽬录 |
CONFIGURATIONS <config1> : (可选) | 指定要导出的配置(如 Debug、Release)。 |
INCLUDE_INSTALL_DIRS:(可 选) | 导出的配置⽂件中包含安装路径 |
export() 命令会⽣成⼀个 CMake ⽂件,其中包含已导出⽬标的信息(如位置、依赖项、编译选项)。 其他项⽬可以通过 find_package() 加载此配置⽂件,从⽽使⽤你项⽬中的⽬标。
通常和install 配合使⽤, install 定义需要导出的导出组,export 将导出组中的所有⽬标导出到配置⽂件。
与 install(EXPORT) 命令的区别:
- export(EXPORT):⽣成临时配置⽂件,仅⽤于从构建⽬录中导出⽬标。
- install(EXPORT ...):⽣成正式配置⽂件,并安装到系统,⽤于从安装⽬录导出⽬标。
3.3.2.2 configure_package_config_file
函数作用:
根据模板⽂件⽣成⾃定义包配置⽂件,可以被其他项⽬使⽤来查找和配置你的包。
基本形式
代码块
configure_package_config_file(
<input>
<output>
[COPYONLY]
[INSTALL_DESTINATION <dir>]
[PATH_VARS <var1> [<var2>...]]
[NO_SET_AND_CHECK_MACRO]
[NO_CHECK_REQUIRED_COMPONENTS_MACRO]
[NO_MESSAGE]
)
参数解释:
参数 | 含义 |
---|---|
<input> | 模板⽂件的路径,通常是⼀个 CMake 脚本⽂件,其中包含了⼀些变量和 逻辑,⽤于⽣成最终的配置⽂件 |
<output> | ⽣成的配置⽂件的路径 |
COPYONLY:(可选) | 如果指定,将直接复制模板⽂件⽽不进⾏任何配置 |
INSTALL_DESTINATION<dir>: (可选) | 指定⽣成的配置⽂件的安装⽬录 |
CONFIGURATIONS <config1> : (可选) | 指定要导出的配置(如 Debug、Release) |
NO_SET_AND_CHECK_MACRO: (可选) | 如果指定,将不⽣成 set_and_check 宏,⽤于设置变量并验证其路径是否存在 |
NO_CHECK_REQUIRED_COMPO NENTS_MACRO:(可选) | 如果指定,将不⽣成 check_required_components 宏,⽤于检查包的所有必需组件是否都已找到并正确配置。 |
3.3.2.3 write_basic_package_version_file
函数作用:
⽣成⼀个包含版本信息和兼容性检查的包版本⽂件,使得其他项⽬能够轻松地使⽤你的包,并确保版本兼容性。
基本形式
代码块
write_basic_package_version_file(
<file>
VERSION <version>
[COMPATIBILITY <compatibility>]
[ARCH_INDEPENDENT]
)
参数解释:
参数 | 含义 |
---|---|
<file> | ⽣成的版本⽂件的路径 |
VERSION <version> | 包的版本号,格式通常为 major.minor.patch |
COMPATIBILITY <compatibility>:(可 选) | AnyNewerVersion:任何更新的版本都兼容 SameMajorVersion:只有主版本号相同的版本才兼容 ExactVersion:只有完全相同的版本才兼容 |
ARCH_INDEPENDENT:(可选) | 如果指定,版本⽂件将不包含架构相关的信息 |
3.4 静态库(查找-使用)
上⼀节我们发布了⼀个数学静态库到系统的标准路径下,这节我们将使⽤find_MyMath⼯程演⽰下如何查找和引⽤我们发布的MyMath这个静态库。
3.4.1 单步实操
Step 0:目录结构
tree test_MyMath
test_MyMath/
├── CMakeLists.txt
└── main.cpp
Step 1:新建文件-CMakeLists.txt
CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(MyMathApp)
# 查找 MyMath 库
find_package(MyMath REQUIRED CONFIG)
# 添加可执⾏⽂件
add_executable(main main.cpp)
# 添加依赖关系
target_link_libraries(main PRIVATE MyMath::MyMath)
Step 2:新建⽂件-main.cpp
src/main.cpp
#include <iostream>
#include "math/math.h"
int main()
{
std::cout << "add(3,4) = " << add(3, 4) << std::endl;
std::cout << "sub(3,4) = " << sub(3, 4) << std::endl;
}
Step 3:运行cmake
代码块
mkdir build && cd build
cmake ../
Step 4:编译链接
代码块
cmake --build .
代码块
[ 33%] Building CXX object my_lib/CMakeFiles/MyMath.dir/src/add.cpp.o
[ 66%] Building CXX object my_lib/CMakeFiles/MyMath.dir/src/del.cpp.o
[100%] Linking CXX static library libMyMath.a
[100%] Built target MyMath
Step 5:运行验证
代码块
./main
代码块
3 + 4 = 7
3 - 4 = -1
Step 6:查看main的 编译和链接过程
代码块
cmake --build . -v
代码块
gmake[2]: Leaving directory '/home/bit/workspace/CMakeClass/test_MyMath/build'
/usr/bin/gmake -f CMakeFiles/main.dir/build.make CMakeFiles/main.dir/build
gmake[2]: Entering directory '/home/bit/workspace/CMakeClass/test_MyMath/build'
[ 50%] Building CXX object CMakeFiles/main.dir/main.cpp.o
/usr/bin/c++ -MD -MT CMakeFiles/main.dir/main.cpp.o -MF
CMakeFiles/main.dir/main.cpp.o.d -o CMakeFiles/main.dir/main.cpp.o -c
/home/bit/workspace/CMakeClass/test_MyMath/main.cpp
[100%] Linking CXX executable main
3.4.2 重点命令解释
3.4.2.1 find_package
函数作用:
查找和使⽤别⼈发布的库⽂件,并在⾃⼰的项⽬中使⽤。
基本形式
代码块
find_package(<PackageName> [version] [EXACT] [QUIET] [MODULE]
[REQUIRED] [[COMPONENTS] [components...]]
)
参数解释:
参数 | 含义 |
---|---|
<PackageName> | 要查找的包名(如 Boost、OpenCV) |
<version>:可选 | 指定所需的最低版本(如 1.72.0) |
<EXACT>:(可选) | 要求精确匹配指定版本 |
<REQUIRED>:(可选) | 若找不到包,终⽌配置过程并报错 |
CONFIG/NO_MODULE | 优先使⽤ Config 模式查找 |
此命令⼯作机制和模式⽐较复杂,会在3.8节进⾏重点解释。
3.5 动态库(编译-链接-引⽤)
3.5.1 单步实操
Step 0:目录结构
tree my_math_shared
my_math_shared/
├── app
│ ├── CMakeLists.txt
│ ├── main.cpp
│ └── main.o
├── CMakeLists.txt
└── my_lib
├── CMakeLists.txt
├── include
│ └── math.h
└── src
├── add.cpp
└── sub.cpp
Step 1:新建文件-CMakeLists.txt
CMakeLists.txt
# 1 设置最低版本号
cmake_minimum_required(VERSION 3.18)
# 2 设置项目名称
project(TestMyMath
LANGUAGES CXX
)
# 3 添加目录层级
add_subdirectory(my_lib)
add_subdirectory(app)
Step 2:新建文件-my_lib/CMakeLists.txt
my_lib/CMakeLists.txt
# 1 收集库的源代码
file(GLOB SRC_LISTS "src/*.cpp")
# 2 添加构建⽬标
add_library(MyMath SHARED ${SRC_LISTS})
# 3 设置库的使⽤要求
target_include_directories(MyMath
PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include
)
# 4 修改默认的输出路径 设置库属性
set_target_properties(MyMath PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib
LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib
OUTPUT_NAME MyMath
VERSION 1.2.3
SOVERSION 20
COMPILE_OPTIONS "-fPIC"
)
Step 3:新建文件-app/CMakeLists.txt
app/CMakeLists.txt
#1搜集文件列表
file(GLOB SRC_LISTS "*.cpp")
# 2 添加构建⽬标
add_executable(main ${SRC_LISTS})
# 3 添加依赖库列表
target_link_libraries(main PRIVATE MyMath)
# 4 设置输出路径
set_target_properties(main PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
)
# 5 打印动态库的输出路径
add_custom_command(
TARGET main POST_BUILD
COMMAND ${CMAKE_COMMAND} -E echo "$<TARGET_FILE:MyMath>"
COMMENT "获取动态库的输出路径"
)
Step 4:新建文件-add.cpp
my_lib/src/add.cpp
#include "math.h"
int add(int a, int b) {
return a + b;
}
Step 5:新建文件-sub.cpp
my_lib/src/sub.cpp
#include "math.h"
int sub(int a, int b) {
return a - b;
}
Step 6:新建文件-math.h
my_lib/include/math.h
int add(int a, int b);
int sub(int a, int b);
Step 7:新建文件-app/main.cpp
app/main.cpp
#include <iostream>
#include "math.h"
int main()
{
std::cout << "3 + 4 = " << add(3, 4) << std::endl;
std::cout << "3 - 4 = " << sub(3, 4) << std::endl;
}
Step 8:运行cmake
shell
mkdir build && cd build
cmake ../
Step 9:编译&&链接
代码块
cmake --build .
代码块
[ 20%] Building CXX object my_so/CMakeFiles/MyMath.dir/src/add.cpp.o
[ 40%] Building CXX object my_so/CMakeFiles/MyMath.dir/src/del.cpp.o
[ 60%] Linking CXX shared library ../lib/MyMath.dylib
[ 60%] Built target MyMath
[ 80%] Building CXX object app/CMakeFiles/main.dir/main.cpp.o
[100%] Linking CXX executable ../bin/main
[100%] Built target main
Step 10:运行
代码块
./bin/main
代码块
3 + 4 = 7
3 - 4 = -1
3.5.2 重点命令解释
⽆新增命令
3.5.3 CMake 内部动态库的生成与定位流程
大家记得在前⾯的使⽤项⽬内部静态库最后,给⼤家演⽰了,如何定位静态库,其实定位动态库和静态库机制和原理⼀模⼀样,只是静态库是在链接时进⾏静态链接,而动态库需要把库的完整路径保存在ELF文件里,供ld动态链接器在运⾏时进⾏动态加载。
下⾯我们还是使⽤本节的例⼦,只是修改下动态库的CMakeLists.txt 添加路径打印命令。
Step 0:修改⽂件-app/CMakeLists.txt
app/CMakeLists.txt
# 1 搜集文件列表
file(GLOB SRC_LISTS "*.cpp")
# 2 添加构建目标
add_executable(main ${SRC_LISTS})
# 3 添加依赖库列表
target_link_libraries(main PRIVATE MyMath)
# 4 设置输出路径
set_target_properties(main PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
)
# 5 打印动态库的输出路径
add_custom_command(
TARGET main POST_BUILD
COMMAND ${CMAKE_COMMAND} -E echo "$<TARGET_FILE:MyMath>"
COMMENT "获取动态库的输出路径"
)
Step 1:运行cmake
代码块
cd build
rm -fr *
cmake ../
Step 2:编译链接
代码块
make
代码块
[ 20%] Linking CXX shared library ../lib/libMyMath.dylib
[ 60%] Built target MyMath
[ 80%] Linking CXX executable ../bin/main
获取动态库的输出路径
/home/bit/workspace/CMakeClass/my_math/build/lib/libMyMath.dylib
[100%] Built target main
Step 3:查看makefile
查看cmake ⽣成的⼆进制main⽤于链接动态库的链接规则
代码块
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/
usr/bin/c++ -isysroot
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/
SDKs/MacOSX15.4.sdk -mmacosx-version-min=15.3 -Wl,-search_paths_first -Wl,-
headerpad_max_install_names CMakeFiles/main.dir/main.cpp.o -o ../bin/main -
Wl,-rpath,/Users/lixiaoshen/Desktop/CMakeDemo/my_math_so/build/lib
../lib/libMyMath.dylib
可以看到生成的链接规则里,有动态库的输出路径和 -Wl,-rpath链接器参数,⽤于告诉链接器 (gcc/link)生成用于保存运行时查找路径的ELF段。
Step 4:查看ELF文件
下⾯继续⽤readlelf 和 ldd 命令确认下main⾥的rpath信息,进⼀步验证rpath保存下来了。
第⼀种:linux 使⽤readelf ⼯具 查看查看⼆进制⽂件的RUNPATH段信息,⾥⾯保存了gcc设置的
rpath 路径。
Code block
readelf -d bin/main
第⼆种:linux 下查看 ⼆进制的动态链接库依赖信息
代码块
ldd bin/main
3.6 动态库(编译-链接-安装)
上⼀节我们学习了如何制作动态库,并引链接项⽬内部的动态库,这节我们将学习如何发布动态库到系统路径下,从⽽给更多的⼈使⽤。代码结构和发布静态库⼀样结构⼀样,这⾥我们就直接 cp 整个静态库的安装⽬录结构
3.6.1 单步实操
本⼯程我们从复制 install_static_mymath 到 install_shared_mymath⽬录
Step 0:目录结构
代码块
cp -fr install_static_mymath install_shared_mymath
tree install_shared_mymath
├── CMakeLists.txt
└── my_lib
├── CMakeLists.txt
├── Config.cmake.in
├── include
│ └── math.h
└── src
├── add.cpp
└── sub.cpp
Step 1:修改⽂件-my_lib/CMakeLists.txt
my_lib/CMakeLists.txt
# 1 收集源代码
file(GLOB SRC_LISTS "src/*.cpp")
# 2 添加构建目标
add_library(MyMath SHARED ${SRC_LISTS})
# 3 设置库的使⽤要求,也就是下游消费者必须包含的头⽂件搜索路径
target_include_directories(MyMath INTERFACE
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>" # include
)
# 4 设置库的默认输出路径
set_target_properties(MyMath PROPERTIES
LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib
OUTPUT_NAME MyMath
VERSION 1.2.3
SOVERSION 20
COMPILE_OPTIONS "-fPIC"
)
# 5 安装我们的静态库
include(GNUInstallDirs)
install(TARGETS MyMath
EXPORT MyMathTargets
DESTINATION ${CMAKE_INSTALL_LIBDIR} # lib
)
# 6 安装头⽂件
install(DIRECTORY include/
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/math #
/usr/local/include/math/math.h
FILES_MATCHING PATTERN "*.h"
)
# 7 安装导出⽬标集合 到 构建树
export(EXPORT MyMathTargets
FILE ${CMAKE_CURRENT_BINARY_DIR}/MyMathTargets.cmake
)
# 8 安装导出⽬标集合 到 安装树
install(EXPORT MyMathTargets
FILE MyMathTargets.cmake
NAMESPACE MyMath:: # MyMath::MyMath
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyMath #
/usr/local/lib/cmake/MyMath/MyMathTargets.cmake
)
# 9 ⽣成find_package 需要的 配置⽂件
include(CMakePackageConfigHelpers)
configure_package_config_file(
${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/MyMathConfig.cmake
INSTALL_DESTINATION "lib/cmake/MyMath"
)
# 10 安装配置⽂件到cmake 标准的安装路径
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/MyMathConfig.cmake
DESTINATION "lib/cmake/MyMath"
)
Step 2:编译-链接
代码块
mkdir build && cd build
cmake ../
cmake --build . -v
代码块
[ 33%] Building CXX object my_lib/CMakeFiles/MyMath.dir/src/add.cpp.o
[ 66%] Building CXX object my_lib/CMakeFiles/MyMath.dir/src/del.cpp.o
[100%] Linking CXX shared library libMyMath.dylib
[100%] Built target MyMath
Step 3:安装
代码块
make install
代码块
bit@bit07:~/workspace/CMakeClass/install_shared_mymath/build$ cmake --install .
-- Install configuration: ""
-- Installing:
/home/bit/workspace/CMakeClass/install_shared_mymath/build/install/lib/libMyMat
h.so.1.2.3
-- Installing:
/home/bit/workspace/CMakeClass/install_shared_mymath/build/install/lib/libMyMat
h.so.20
-- Installing:
/home/bit/workspace/CMakeClass/install_shared_mymath/build/install/lib/libMyMat
h.so
-- Installing:
/home/bit/workspace/CMakeClass/install_shared_mymath/build/install/include/math
-- Installing:
/home/bit/workspace/CMakeClass/install_shared_mymath/build/install/include/math
/math.h
-- Installing:
/home/bit/workspace/CMakeClass/install_shared_mymath/build/install/lib/cmake/My
Math/MyMathTargets.cmake
-- Installing:
/home/bit/workspace/CMakeClass/install_shared_mymath/build/install/lib/cmake/My
Math/MyMathTargets-noconfig.cmake
-- Installing:
/home/bit/workspace/CMakeClass/install_shared_mymath/build/install/lib/cmake/My
Math/MyMathConfig.cmake
3.7 动态库(查找-使用)
上⼀节例子我们学习了如何制作发布动态库到系统⽬录下,这节我们将学习查找并使⽤我们发布的动态库。
我们继续使⽤ find_MyMath这个⽬录结构
Step 0:⽬录结构:
代码块
├── CMakeLists.txt
└── main.cpp
Step 1 :编译链接
代码块
mkdir build && cd build
cmake ../
cmake --build . -v
Step 2:查看链接
代码块
[ 50%] Building CXX object CMakeFiles/main.dir/src/main.cpp.o
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/
usr/bin/c++ -isystem /usr/local/include -isysroot
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/
SDKs/MacOSX15.4.sdk -mmacosx-version-min=15.3 -MD -MT
CMakeFiles/main.dir/src/main.cpp.o -MF CMakeFiles/main.dir/src/main.cpp.o.d -o
CMakeFiles/main.dir/src/main.cpp.o -c
/Users/lixiaoshen/Desktop/CMakeDemo/find_lib/src/main.cpp
[100%] Linking CXX executable main
/usr/local/bin/cmake -E cmake_link_script CMakeFiles/main.dir/link.txt --
verbose=1
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/
usr/bin/c++ -isysroot
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/
SDKs/MacOSX15.4.sdk -mmacosx-version-min=15.3 -Wl,-search_paths_first -Wl,-
headerpad_max_install_names CMakeFiles/main.dir/src/main.cpp.o -o main -Wl,-
rpath,/usr/local/lib /usr/local/lib/libMyMath.1.0.dylib
Step 3:查看elf⽂件
Code block
readelf -d main
3.8 查找并使用第三方库
在之前的例⼦中,我们学会了如何发布和查找自己的静态库和动态库,这节我们将深⼊了解如何查找⼀个像Curl ,Boost,JsonCpp 这样的开源的库文件,并在项⽬中使用他们。
在 CMake 中,通常使⽤ find_package() 命令来查找和使⽤外部依赖库。他有两种查找模式:
- 使⽤ find_package()的 MODULE 参数指定
- 使⽤ CMake 内置的或者⾃⼰写的 Find<PackageName>.cmake ⽂件查找。
- 适⽤于没有提供 CMake 配置⽂件的旧库。
- 使⽤ find_package()的 CONFIG 参数指定
- 查找库⾃⾝提供的 CMake 配置⽂件(如 PackageNameConfig.cmake )。
- 适用于现代库(如 Boost、Qt, Curl)。
默认优先尝试配置模式,失败后尝试模块模式。可以使用 CONFIG 或 MODULE 参数强制指定模式。
3.8.1 Module 模式介绍
CMake 执行module模式⼤概分为以下3步:
1. 查找模块文件:
- 搜索 FindMyPackageName.cmake 文件
- 顺序: CMAKE_MODULE_PATH → CMake 内置模块(/usr/local/share/cmake/Modules/)
2. 执行模块文件:
模块文件通常会:
- 查找库⽂件( find_library() )
- 查找头⽂件( find_path() )
- 检查版本( find_package_handle_standard_args() )
- 定义导⼊⽬标( add_library(... IMPORTED) )
3. 设置输出变量:
常见变量:
- MyPackage_FOUND :是否找到包
- MyPackage_INCLUDE_DIRS :头⽂件路径
- MyPackage_LIBRARIES :库⽂件路径
- MyPackage_VERSION :包版本
3.8.2 Config 模式介绍
CMake 执行Config模式⼤概分为以下3步:
1. 查找配置文件:
- 搜索 PackageNameConfig.cmake 或 <lower-case-package-name>-config.cmake
- 搜索路径: CMAKE_PREFIX_PATH → 标准系统路径(/usr/local/lib/cmake/)
2. 执行配置文件:
配置⽂件通常由包开发者提供,会:
- 定义导⼊⽬标(如 PackageName::PackageName)
- 设置依赖关系(头⽂件路径和库⽂件路径属性)
- 提供版本信息
3.8.3 包名大小写规范
包名区分大小写:例如 find_package(Boost) 和 find_package(boost) 可能指向不同的包。
官方推荐:大多数 CMake 内置模块(如 Boost、OpenCV、Threads)使⽤⼤写⾸字⺟。
包配置文件: ${PackageName}Config.cmake 或
${PackageName}ConfigVersion.cmake ,其中 PackageName 通常为⼤写⾸字⺟(如
BoostConfig.cmake )。
查找模块: Find${PackageName}.cmake (如 FindBoost.cmake )。
代码块
find_package(Boost REQUIRED COMPONENTS system filesystem)
find_package(OpenCV REQUIRED)
特殊情况:某些包可能全部大写(如 CURL)或小写(如 protobuf),但这通常由包作者决定。
3.8.4 使用Module 模式查找JsonCpp 库
我们新建⼯程 find_jsoncpp_module 来演⽰使⽤module 模式查找jsoncpp并使⽤
要查找和使⽤JsonCpp之前,我们⾸先安装JsonCpp库⽂件,安装⽅法如下:
代码块
sudo apt update
sudo apt install libjsoncpp-dev
Step 0:⽬录结构
tree find_jsoncpp
find_jsoncpp
├── CMake
│ └── FindJsonCpp.cmake
├── CMakeLists.txt
├── build
└── src
└── main.cpp
Step 1:新建⽂件-CMakeLists.txt
CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(JsonCppExample)
# 设置 C++ 标准
set(CMAKE_CXX_STANDARD 11)
# 添加⾃定义模块路径
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
# 查找 JsonCpp 库(Module 模式)
find_package(JsonCpp REQUIRED MODULE)
# 打印查找结果(⽤于调试)
message(STATUS "JsonCpp_FOUND: ${JsonCpp_FOUND}")
message(STATUS "JsonCpp_INCLUDE_DIRS: ${JsonCpp_INCLUDE_DIRS}")
message(STATUS "JsonCpp_LIBRARIES: ${JsonCpp_LIBRARIES}")
# 添加可执⾏⽂件
add_executable(myapp src/main.cpp)
# 链接 JsonCpp 库
target_link_libraries(myapp PRIVATE JsonCpp::JsonCpp)
Step 2:新建文件-FindJsonCpp.cmake
CMake/FindJsonCpp.cmake
# cmake/FindJsonCpp.cmake
# 查找头⽂件
find_path(JsonCpp_INCLUDE_DIR
NAMES json/json.h
PATHS
/usr/include
/usr/local/include
/opt/local/include
${CMAKE_PREFIX_PATH}/include
PATH_SUFFIXES
jsoncpp # 某些系统将头⽂件放在 jsoncpp/json/json.h
)
# 查找库⽂件
find_library(JsonCpp_LIBRARY
NAMES jsoncpp libjsoncpp jsoncpp_lib
PATHS
/usr/lib
/usr/local/lib
/opt/local/lib
${CMAKE_PREFIX_PATH}/lib
)
# 设置输出变量
if(JsonCpp_INCLUDE_DIR AND JsonCpp_LIBRARY)
set(JsonCpp_FOUND TRUE)
set(JsonCpp_INCLUDE_DIRS ${JsonCpp_INCLUDE_DIR})
set(JsonCpp_LIBRARIES ${JsonCpp_LIBRARY})
# 创建导⼊⽬标(可选,但推荐)
if(NOT TARGET JsonCpp::JsonCpp)
add_library(JsonCpp::JsonCpp UNKNOWN IMPORTED)
set_target_properties(JsonCpp::JsonCpp PROPERTIES
IMPORTED_LOCATION "${JsonCpp_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${JsonCpp_INCLUDE_DIRS}"
)
endif()
else()
set(JsonCpp_FOUND FALSE)
endif()
# 标记为⾼级变量(在 ccmake 或 cmake-gui 中隐藏)
mark_as_advanced(JsonCpp_INCLUDE_DIR JsonCpp_LIBRARY)
Step 3:新建文件-main.cpp
src/main.cpp
#include <iostream>
#include <json/json.h> // JsonCpp 头⽂件
int main() {
// 创建 JSON 对象
Json::Value root;
root["name"] = "John";
root["age"] = 30;
// 添加数组
Json::Value hobbies(Json::arrayValue);
hobbies.append("reading");
hobbies.append("coding");
root["hobbies"] = hobbies;
// 转换为字符串
Json::StreamWriterBuilder writer;
std::string jsonString = Json::writeString(writer, root);
std::cout << "JSON Data:" << std::endl;
std::cout << jsonString << std::endl;
return 0;
}
Step 4:运行cmake
代码块
mkdir build && cd build
cmake ../
Step 5:编译链接
代码块
cmake --build . -v
代码块
[ 50%] Building CXX object CMakeFiles/myapp.dir/src/main.cpp.o
[100%] Linking CXX executable myapp
[100%] Built target myapp
Step 6:运行
代码块
./myapp
代码块
JSON Data:
{
"age" : 30,
"hobbies" :
[
"reading",
"coding"
],
"name" : "John"
}
3.8.5 使用Config 模式查找JsonCpp 库
我们使⽤ find_jsoncpp_config ⼯程并修改查找模式来指定使⽤config模式
Step 0:项目录结构
find_jsoncpp_config
├── CMakeLists.txt
└── main.cpp
Step 1:源码安装jsoncpp-1.9.7
代码块
git clone https://gitee.com/lizhengping189/jsoncpp.git
cd jsoncpp
mkdir build && cd build
cmake ..
cmake --build .
cmake --install .
Step 2:修改文件-CMakeLists.txt
CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(JsoncppDemo
LANGUAGES CXX
)
set(CMAKE_CXX_STANDARD 11)
# 查找 jsoncpp 使⽤Config 模式
find_package(jsoncpp CONFIG REQUIRED)
# 添加构建⽬标
add_executable(main main.cpp)
# 链接jsoncpp 3选1 链接
target_link_libraries(main PRIVATE JsonCpp::JsonCpp)
#target_link_libraries(main PRIVATE jsoncpp_static)
#target_link_libraries(main PRIVATE jsoncpp_lib)
#jsoncpp 的别名到底叫什么?
#1.9.x 之后官⽅⽣成 jsoncpp_lib(静态)和 jsoncpp_shared(动态)。
# 我们可以选择链接静态库或者动态库
#早期发⾏版可能只有 JsonCpp或带命名空间 JsonCpp::JsonCpp,从⽣成的配置⽂件代码可知:优先链接静态库
#查看⽣成的 jsoncppTargets-*.cmake 可以确认实际⽬标名称。
Step 3:新建文件-src/main.cpp
代码块
#include <iostream>
#include <json/json.h> // JsonCpp 头⽂件
int main()
{
// 创建 JSON 对象
Json::Value root;
root["name"] = "Bits";
root["age"] = 18;
// 转换为字符串
Json::StreamWriterBuilder writer;
std::string jsonString = Json::writeString(writer, root);
std::cout << "JSON Data:" << std::endl;
std::cout << jsonString << std::endl;
return 0;
}
Step 4:运行cmake
代码块
rm -fr *
cmake ../
Step 5:编译链接
代码块
cmake --build . -v
代码块
gmake[2]: Leaving directory
'/home/bit/workspace/CMakeClass/find_jsoncpp_config/build'
/usr/bin/gmake -f CMakeFiles/main.dir/build.make CMakeFiles/main.dir/build
gmake[2]: Entering directory
'/home/bit/workspace/CMakeClass/find_jsoncpp_config/build'
[ 50%] Building CXX object CMakeFiles/main.dir/main.cpp.o
/usr/bin/c++ -std=gnu++11 -MD -MT CMakeFiles/main.dir/main.cpp.o -MF
CMakeFiles/main.dir/main.cpp.o.d -o CMakeFiles/main.dir/main.cpp.o -c
/home/bit/workspace/CMakeClass/find_jsoncpp_config/main.cpp
[100%] Linking CXX executable main
/usr/bin/cmake -E cmake_link_script CMakeFiles/main.dir/link.txt --verbose=1
/usr/bin/c++ CMakeFiles/main.dir/main.cpp.o -o main
/usr/local/lib/libjsoncpp.a
gmake[2]: Leaving directory
'/home/bit/workspace/CMakeClass/find_jsoncpp_config/build'
[100%] Built target main
Step 6:运行
代码块
./myapp
代码块
JSON Data:
{
"age" : 18,
"name" : "Bits"
}
3.9 调用外部命令
本节我们将展⽰如何调⽤外部的 protoc 命令(Protocol Buffers 编译器)来⽣成 C++ 代码,并将其集成到项⽬中。其他外部命令可以使⽤相同的⽅式来调⽤。
我们新建⼯程 test_cmake_protoc
Step 0:⽬录结构
tree cmake_protoc
cmake_protoc
├── CMakeLists.txt
├── main.cpp
└── proto
└── person.proto
Step 1:安装Protobuf 和 protoc
代码块
# Ubuntu/Debiansudo
apt-get install libprotobuf-dev protobuf-compiler
Step 2:新建文件-person.proto
proto/person.proto
syntax = "proto3";
package example;
message Person{
string name = 1;
int32 id = 2;
string email = 3;
}
Step 2:新建文件-CMakeLists.txt
CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(ProtocExample
LANGUAGES CXX
)
# 设置C++ 标准
set(CMAKE_CXX_STANDARD 11)
# 1 查找Protobuf 库 要求版本是 >= 3.0
find_package(Protobuf 3.0 REQUIRED)
# 2 收集proto 项⽬描述⽂件
file(GLOB PROTO_FILES ${CMAKE_CURRENT_SOURCE_DIR}/proto/*.proto)
# 3 创建protoc 的⽣成⽂件的⽬录
set(PB_OUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/pb)
file(MAKE_DIRECTORY ${PB_OUT_DIR})
# 4 初始化⽣成的源代码⽂件集合, ⽤于⽣成我们的静态库,⽤main使⽤
set(GEN_SRCS "") # *.pb.cc
set(GEN_HEADS "") # *.pb.h
# 5 为每⼀个proto⽂件⽣成对应的.cc和.h⽂件
foreach(PROTO ${PROTO_FILES})
# 5.1 获取不带扩展名的⽂件名字
get_filename_component(BASE_NAME ${PROTO} NAME_WE) #person
# 5.2 添加⽣成的c++ ⽂件到 源代码集合
list(APPEND GEN_SRCS "${PB_OUT_DIR}/${BASE_NAME}.pb.cc")
list(APPEND GEN_HEADS "${PB_OUT_DIR}/${BASE_NAME}.pb.h")
# 5.3 为每⼀个proto⽂件⽣成C++⽂件了
add_custom_command(
OUTPUT "${PB_OUT_DIR}/${BASE_NAME}.pb.cc"
"${PB_OUT_DIR}/${BASE_NAME}.pb.h"
COMMAND protoc
ARGS --cpp_out=${PB_OUT_DIR}
-I ${CMAKE_CURRENT_SOURCE_DIR}/proto
${PROTO}
DEPENDS ${PROTO}
COMMENT "从*.proto ⽣成对应的C++代码"
VERBATIM
)
endforeach()
# 6 添加触发⽣成pb⽂件的⽬标
add_custom_target(generate_protobuf DEPENDS ${GEN_SRCS} ${GEN_HEADS})
# 7 把protoc ⽣成的C++⽂件编译成我们的静态库
add_library(MyProto STATIC ${GEN_SRCS})
add_dependencies(MyProto generate_protobuf)
target_include_directories(MyProto INTERFACE
${PB_OUT_DIR}
)
target_link_libraries(MyProto PUBLIC protobuf::libprotobuf)
# 8 添加可执⾏程序
add_executable(main main.cpp)
# 9 添加依赖关系
target_link_libraries(main PRIVATE MyProto)
Step 4:新建文件-main.cpp
src/main.cpp
#include <iostream>
#include "person.pb.h"
int main()
{
// 1 创建我们的Person对象
example::Person person;
person.set_name("Bit");
person.set_id(10086);
person.set_email("Bit@example.com");
// 2 序列化person对象
std::string serialized_data;
person.SerializeToString(&serialized_data);
std::cout << "Serialized size: " << serialized_data.size() << std::endl;
// 3 反序列化 person对象
example::Person parsed_person;
parsed_person.ParseFromString(serialized_data);
std::cout << parsed_person.DebugString();
}
Step 5:运行cmake
代码块
mkdir build && cd build
cmake ..
cmake --build . -v
⽣成了.cc
[ 20%] ⽣成 person.proto 的 C++ 代码
/usr/local/bin/protoc --
cpp_out=/Users/lixiaoshen/Desktop/CMakeDemo/cmake_protoc/build/generated -I
/Users/lixiaoshen/Desktop/CMakeDemo/cmake_protoc/proto
/Users/lixiaoshen/Desktop/CMakeDemo/cmake_protoc/proto/person.proto
编译.cc⽂件
[ 60%] Building CXX object CMakeFiles/myapp.dir/generated/person.pb.cc.o
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/
usr/bin/c++ -I/Users/lixiaoshen/Desktop/CMakeDemo/cmake_protoc/build/generated
-isystem /usr/local/include -std=gnu++11 -isysroot
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/
SDKs/MacOSX15.4.sdk -mmacosx-version-min=15.3 -MD -MT
CMakeFiles/myapp.dir/generated/person.pb.cc.o -MF
CMakeFiles/myapp.dir/generated/person.pb.cc.o.d -o
CMakeFiles/myapp.dir/generated/person.pb.cc.o -c
/Users/lixiaoshen/Desktop/CMakeDemo/cmake_protoc/build/generated/person.pb.cc
链接libprotobuf.a
[ 80%] Linking CXX executable myapp
/usr/local/bin/cmake -E cmake_link_script CMakeFiles/myapp.dir/link.txt --
verbose=1
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/
usr/bin/c++ -isysroot
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/
SDKs/MacOSX15.4.sdk -mmacosx-version-min=15.3 -Wl,-search_paths_first -Wl,-
headerpad_max_install_names CMakeFiles/myapp.dir/src/main.cpp.o
CMakeFiles/myapp.dir/generated/person.pb.cc.o -o myapp
/usr/local/lib/libprotobuf.a
上图展⽰了:cmake 通过find_package 找到了Protobuf
上图展⽰了:cmake 调⽤了protoc 外部命令⽣成c++ 使⽤的.cc 和 .h ⽂件,并⽤于编译和链接⽣成⼆进制⽂件
Step 6:运行程序
代码块
./myapp
代码块
lixiaoshen@lixiaoshens-MacBook-Pro build % ./myapp
Serialized size: 31 bytes
Parsed Name: John Doe
Parsed ID: 1234
Parsed Email: john@example.com
3.10 CTest
CTest 是CMake的⼀个集成测试框架,⽤于⾃动化执⾏项⽬测试。⽀持多种测试类型(如单元测试、性能测试),并能⽣成详细的测试报告。
下⾯我们继续使⽤静态库 install_lib 这个⼯程然后添加测试⽂件,⽤ctest进⾏功能测试,测试
通过之后再安装发布。
基本命令
代码块
ctest [<options>] [--test-dir <path-to-build>]
核心函数
代码块
include(CTest)
代码块
add_test(NAME <name> COMMAND <command> [<arg>...]
参数解释:
参数 | 含义 |
---|---|
<name> | 测试的唯⼀标识符(在 CTest 中显⽰的名称) |
<command> | 要执⾏的命令(可执⾏⽂件、脚本或 CMake 命令) |
<arg> | 传递给命令的参数(可选) |
注意:
include(CTest) 应位于top-level source ⽬录中CMakeLists.txt中,因为 ctest(1) 希望在 top-level-build ⽬录中找到测试⽂件,⽽且必须位于任何add_test命令之前,不然添加的命令不会被ctest 收集到。
Step 0:目录结构
代码块
├── CMakeLists.txt
└── main.cpp
Step 1:新建文件-main.cpp
tests/test_math.cpp
#include <iostream>
#include "math/math.h"
#include <cassert>
int main() {
// 测试加法
assert(add(2, 3) == 5);
// 测法减法
assert(sub(2, 3) == -1);
std::cout << "Test OK" << std::endl;
return 0;
}
Step 2:新建文件-CMakeLists.txt
CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
# set the project name and version
project(TestMyMath
LANGUAGES CXX
)
# 启⽤测试⽀持
include(CTest)
# 添加测试可执⾏⽂件
add_executable(main main.cpp)
# 查找MyMath 库
find_package(MyMath CONFIG REQUIRED)
# 链接被测库
target_link_libraries(main PRIVATE MyMath::MyMath)
# 将测试添加到 CTest
add_test(
NAME TestMyMath
COMMAND main
)
Step 3:运行cmake
代码块
cmake ../
Step 4:编译链接
代码块
make
代码块
[ 20%] Building CXX object my_lib/CMakeFiles/MyMath.dir/src/add.cpp.o
[ 40%] Building CXX object my_lib/CMakeFiles/MyMath.dir/src/del.cpp.o
[ 60%] Linking CXX static library libMyMath.a
[ 60%] Built target MyMath
[ 80%] Building CXX object tests/CMakeFiles/test_math.dir/test_math.cpp.o
[100%] Linking CXX executable test_math
[100%] Built target test_math
Step 5:运行ctest
代码块
ctest
或者
make test
代码块
Running tests...
Test project /Users/lixiaoshen/Desktop/CMakeDemo/test_ctest/build
Start 1: MathLibraryTest
1/1 Test #1: MathLibraryTest .................. Passed 0.38 sec
100% tests passed, 0 tests failed out of 1
Total Test time (real) = 0.39 sec
测试通过之后,就可以使⽤ make install 来安装或者使用cpack 来打包发布。
3.11 CPack
本节我们将学习使⽤CPack 来打包发布我们⽣成的⼆进制,库⽂件等。CPack 是 CMake 的打包⼯具,⽤于将 CMake 构建的项⽬打包成各种分发格式(如 DEB、RPM、ZIP、MSI 等)。它能⾃动收集可执⾏⽂件、库、配置⽂件和⽂档,并⽣成符合平台规范的安装包,极⼤简化了软件分发流程。
基本命令
代码块
cpack [<options>]
3.11.1 CPack 核心功能
1. ⾃动收集⽂件:基于 CMake 的 install() 指令,⾃动收集需要打包的⽂件。
2. 多格式⽀持:⽀持⽣成跨平台的包格式(如 DEB、RPM、ZIP、MSI、DMG 等)。
3. 依赖管理:可配置依赖关系(如系统库、运⾏时环境)。
4. ⾃定义安装逻辑:⽀持预安装、后安装脚本。
5. 版本控制:⾃动包含版本号、发布信息等元数据。
3.11.2 使用CPack 打包发布bin+so
我们使⽤3.5节的 使⽤动态库的 my_math_ shared⼯程 并复制重命名为 cpack_mymath_shared
Step 0:目录结构
代码块
cp -fr my_math_so test_cpack
tree test_cpack
.
├── app
│ ├── CMakeLists.txt
│ └── main.cpp
├── build
├── CMakeLists.txt
└── my_lib
├── CMakeLists.txt
├── include
│ └── math.h
└── src
├── add.cpp
└── sub.cpp
Step 1:修改⽂件-CMakeLists.txt
CMakeLists.txt
# 1 设置最低版本号
cmake_minimum_required(VERSION 3.18)
# 2 设置项⽬名称
project(TestMyMath
LANGUAGES CXX
)
# 3 添加⽬录层级
add_subdirectory(my_lib)
add_subdirectory(app)
# 4 开启打包功能
include(CPack)
set(CPACK_PACKAGE_NAME "MyApp")
set(CPACK_PACKAGE_VERSION "1.0.0")
set(CPACK_GENERATOR "TGZ")
Step 2:修改文件-app/CMakeLists.txt
app/CMakeLists.txt
# 1 搜集⽂件列表
file(GLOB SRC_LISTS "*.cpp")
# 2 添加构建⽬标
add_executable(main ${SRC_LISTS})
# 3 添加依赖库列表
target_link_libraries(main PRIVATE MyMath)
# 4 设置输出路径
set_target_properties(main PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
INSTALL_RPATH "$ORIGIN/../lib"
)
# 5 打印库⽂件相对main的相对路径
add_custom_command(
TARGET main POST_BUILD
COMMAND ${CMAKE_COMMAND} -E echo
"$<PATH:RELATIVE_PATH,$<TARGET_FILE:MyMath>,${CMAKE_CURRENT_BINARY_DIR}>"
COMMENT "获取动态库的输出路径"
)
# 6 安装main
install(TARGETS main)
Step 3:修改文件-my_lib/CMakeLists.txt
my_lib/CMakeLists.txt
# 1 收集库的源代码
file(GLOB SRC_LISTS "src/*.cpp")
# 2 添加构建⽬标
add_library(MyMath SHARED ${SRC_LISTS})
# 3 设置库的使⽤要求
target_include_directories(MyMath PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
"$<INSTALL_INTERFACE:include>"
)
# 4 修改默认的输出路径 设置库属性
set_target_properties(MyMath PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib
LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib
OUTPUT_NAME MyMath
VERSION 1.2.3
SOVERSION 20
COMPILE_OPTIONS "-fPIC"
)
# 5 安装库⽂件
install(TARGETS MyMath)
# 6 安装头⽂件
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/
DESTINATION include
FILES_MATCHING PATTERN "*.h"
)
Step 4:运行cmake
代码块
cd build/
rm -fr *
cmake ../
Step 5:编译链接
代码块
make
代码块
[ 20%] Building CXX object my_lib/CMakeFiles/Mymath.dir/src/add.cpp.o
[ 40%] Building CXX object my_lib/CMakeFiles/Mymath.dir/src/del.cpp.o
[ 60%] Linking CXX shared library ../lib/libMymath.dylib
[ 60%] Built target Mymath
[ 80%] Building CXX object app/CMakeFiles/main.dir/main.cpp.o
[100%] Linking CXX executable ../bin/main
[100%] Built target main
Step 6:运行cpack
代码块
cpack
代码块
CPack: Create package using TGZ
CPack: Install projects
CPack: - Run preinstall target for: MyApp
CPack: - Install project: MyApp []
CPack: Create package
CPack: - package: /home/bit/workspace/CMakeDemo/cmake_cpack/build/MyApp-0.1.1-
Linux.tar.gz generated.
可以看到在当前⽬录⽣成了⼀个MyApp-0.1.1-Linux.tar.gz 压缩包
Step 7:解压
代码块
tar xvf MyApp-0.1.1-Linux.tar.gz
代码块
MyApp-0.1.1-Linux/bin/
MyApp-0.1.1-Linux/bin/main
MyApp-0.1.1-Linux/lib/
MyApp-0.1.1-Linux/lib/libMymath.so
Step 8:运⾏
代码块
cd MyApp-0.1.1-Linux/bin/
./main
代码块
3 + 4 = 7
3 - 4 = -1
Step 9:确认链接的是打包的动态库
代码块
ldd main
代码块
linux-vdso.so.1 (0x00007ffd601fb000)
libMymath.so => /home/bit/workspace/CMakeDemo/cmake_cpack/build/MyApp-0.1.1-
Linux/bin/./../lib/libMymath.so (0x0000785040a27000)
libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x0000785040600000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x0000785040200000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x0000785040938000)
/lib64/ld-linux-x86-64.so.2 (0x0000785040a33000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x000078504090a000)
Step 10:查看elf ⼆进制⽂件头,确保cmake 设置的rpath写到了elf ⽂件头⾥
代码块
readelf -d main | grep PATH
代码块
0x000000000000001d (RUNPATH) Library runpath: [$ORIGIN/../lib]
可以看到 在elf为 RUNPATH 段⾥保存了cmake 设置的rpath 路径[$ORIGIN/../lib]。
3.11.3 CPack 打包后⼆进制在运⾏时是如何找到动态库的
Linux 系统中, $ORIGIN 是⼀个特殊的动态链接器变量,类似于 macOS 的 @loader_path ,⽤ 于表⽰当前可执⾏⽂件或库所在的⽬录。它是解决动态库相对路径问题的关键机制。gcc 的链接器link 在⽣成⼆进制⽂件时,会把动态库的搜索路径转换为相对于 $ORIGIN 的相对路径,并保存在ELF⽂件的RUNPATH段⾥,这样在⼆进制运⾏时动态加载器ld就可以读取ELF的 RUNPATH段拿到动态库的加载地址,从⽽完成运⾏时加载。
CMake中 如何使⽤ $ORIGIN?
代码块
# 启⽤ RPATH
set(CMAKE_SKIP_BUILD_RPATH FALSE)
set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE)
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
# 设置 RPATH 使⽤ $ORIGIN
set(CMAKE_INSTALL_RPATH "\$ORIGIN/../lib") # 相对于可执⾏⽂件的路径
应⽤⽰例
假设应⽤结构如下:
代码块
myapp/
├── bin/
│ └── myapp # 可执⾏⽂件
└── lib/
└── libfoo.so # 依赖库
CMake 通过链接器link设置⼆进制ELF⽂件的 RPATH(新版本为RUNPATH) 为 $ORIGIN/../lib , 动态链接器ld会在运⾏时⾃动将 $ORIGIN 替换为 myapp/bin/ ,从⽽正确找到 myapp/lib/libfoo.so 。
四. 常用语法介绍
前⾯的⼯程中我们以项⽬为重点,并没有深⼊单个语法细节,先和⼤家⼀起从宏观层⾯学习了如何使⽤CMake来管理我们的应⽤场景。本节我们将对之前使⽤过的⼀些语法进⾏细化说明和演⽰。
4.1 message 函数
在CMake中可以使⽤message命令打印消息。函数的签名如下:
代码块
message([STATUS|WARNING|AUTHOR_WARNING|FATAL_ERROR|SEND_ERROR] "message to display" ...)
第⼀个参数通常不设置, 表⽰的消息类型,取决于CMake版本
- STATUS :⾮重要消息
- WARNING :CMake 警告, 会继续执⾏
- FATAL_ERROR :CMake 错误, 终⽌所有处理过程
- AUTHOR_WARNING :CMake 警告 (dev), 会继续执⾏
- SEND_ERROR :CMake 错误, 继续执⾏,但是会跳过⽣成的步骤
Step 0:目录结构
tree cmake_language
├── CMakeLists.txt
Step 1:新建⽂件-CMakeLists.txt
CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(MessageDemo)
message("begin CMakeLists.txt")
message(STATUS "begin CMakeLists.txt")
message(STATUS "1" "2" "3")
message(STATUS "123")
#message(WARNING "this is a WARNING")
#message(SEND_ERROR "this is a SEND_ERROR")
message(FATAL_ERROR "this is a FATAL_ERROR")
message(STATUS "end CMakeLists.txt")
Step 2:运⾏
代码块
mkdir build && cd build
cmake ../
代码块
begin CMakeLists.txt
-- begin CMakeLists.txt
-- 123
-- 123
CMake Error at CMakeLists.txt:15 (message):
this is a FATAL_ERROR
-- Configuring incomplete, errors occurred!
4.2 变量
变量是 CMake 语⾔中的基本存储单位。它们的值始终是字符串类型,有些命令可能会将字符串解释为其他类型的值,⽐如列表等。
set() 和 unset() 命令显式地设置或取消设置变量,但其他命令也具有修改变量的语义。
变量名称区分⼤⼩写,⼏乎可以包含任何⽂本,但建议使⽤仅由字⺟数字字符加上 _ 和 - 组成的名
称。
变量具有动态范围。 每个变量 “set” 或 “unset” 都会在当前范围内创建⼀个实例。
变量使⽤ ${var_name} 访问
4.2.1 变量的作用域
4.2.1.1 函数作用域(Function Scope)
函数作用域的变量在函数内部有效,使⽤ function() 命令定义。
特点:
- 函数内部定义的变量默认是局部变量,不会影响外部作⽤域。
- 可以通过 PARENT_SCOPE 选项将变量传递到⽗作⽤域。
Step 0:目录结构:
tree test_func_scop
.
├── CMakeLists.txt
└── build
Step 1:新建文件-CMakeLists.txt
CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(FunctionScopDemo)
set(SCOP "Directory Scop")
function(print_var)
# 1 获取⽗级⽬录变量
message("1. func: ${SCOP}")
# 2 修改变量
set(SCOP "Function Scop")
message("2 func: ${SCOP}")
endfunction(print_var)
print_var()
message("3 main: ${SCOP}")
Step 2:运行cmake
代码块
cmake ../
代码块
1. func: Directory Scop
2 func: Function Scop
3 main: Directory Scop
1 函数内可以获取到⽗作⽤域的变量
2 函数内的局部变量外⾯获取不到
3 要设置到⽗作⽤域,使⽤PARENT_SCOPE选项
4.2.1.2 目录作用域(Directory Scope)
⽬录作⽤域的变量在当前 CMakeLists.txt ⽂件及其⼦⽬录的 CMakeLists.txt ⽂件中有效,通过set设置:
set(<variable> <value>... [PARENT_SCOPE])
特点:
- ⼦⽬录可以使⽤⽗⽬录中定义的变量。
- ⼦⽬录中定义的同名变量不会影响⽗⽬录。
- ⼦⽬录如果要返回值到⽗⽬录,需要显式使⽤带 PARENT_SCOPE的 set 命令。
Step 0:⽬录结构
tree test_dir_scop
test_dir_scop
├── CMakeLists.txt
├── build
└── sub
└── CMakeLists.txt
Step 1:新建⽂件-CMakeLists.txt
CmakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(DirectScopeDemo)
set(SCOP "Parent Scop")
add_subdirectory(sub)
message("3 main: ${SCOP}")
Step 2:新建⽂件-sub/CMakeLists.txt
src/CMakeLists.txt
# 1 读取初始值
message("1 sub: ${SCOP}")
# 2 修改值
set(SCOP "Sub Scop")
# 3 查看修改之后的值
message("2 sub: ${SCOP}")
Step 3:运行cmake
代码块
mkdir build && cd build
cmake ../
代码块
1 sub: Parent Scop
2 sub: Sub Scop
3 main: Parent Scop
1 ⼦⽬录可以访问根⽬录
2 根⽬录访问不了⼦⽬录变量
Step 4:修改文件-src/CMakeLists.txt
代码块
message("src 根⽬录变量: ${ROOT_VAR}") # 输出: "根⽬录变量: 根⽬录变量"
set(ROOT_VAR "src⽬录变量" PARENT_SCOPE)
message("src 根⽬录变量: ${ROOT_VAR}") # 输出: "根⽬录变量: 根⽬录变量"
Step 5 :运行cmake
代码块
cmake ../
代码块
1 sub: Parent Scop
2 sub: Parent Scop
3 main: Sub Scop
1 ⼦⽬录设置的值是 ⽗⽬录作⽤域的,所以⽗⽬录可以获取到
2 ⼦⽬录本⾝的值还是⽗⽬录的旧值,所以没改变。
4.2.1.3 持久缓存(Persistent Cache)
持久缓存的变量在整个 CMake 运⾏过程中都有效,可通过以下命令进⾏设置:
set(<variable> <value> CACHE <type> <docstring> [FORCE])
特点:
- 变量会被存储在 CMakeCache.txt ⽂件中,在多次运⾏ CMake 时保持值不变。
- 可以通过 -D<variable>=<value> 命令⾏参数来覆盖其值。
Step 0: 目录结构
cache_scop
./
└── CMakeLists.txt
Step 1:新建文件-CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(CacheScopDemo)
set(OS "mac" CACHE STRING "os name")
message("OS: ${OS}")
#message("OS: $CACHE{OS}")
Step 1:运行cmake
代码块
cmake ../
代码块
OS: mac
Step 2:使⽤-D参数覆盖运⾏
代码块
cmake ../ -DOS=linux
代码块
OS: linux
BUILD_TYPE 变为命令⾏指定的Debug了。
Step 3:查看CMakeCache.txt中的内容
代码块
grep OS CMakeCache.txt
代码块
//os name
OS:STRING=linux
所有内置的或者⽤于⾃定义的CACHE 变量都会保存在CMakeCache.txt⾥
4.2.1.4 变量作⽤域规则总结
作用域类型 | 命令 / 结构 | 可见性规则 |
---|---|---|
函数作⽤域 | function() | 默认局部,使⽤ PARENT_SCOPE 传递 |
⽬录作⽤域 | set() | 当前⽬录及⼦⽬录可⻅ |
全局作⽤域 | set(... CACHE ...) | 全局可见,存储在缓存中 |
4.2.2 普通变量:
普通变量是最常⻅的变量类型,通过 set(<variable> <value>... [PARENT_SCOPE])命令定义,作⽤域为目录级别或函数内部,或者块内。
特点
• 作用域:
- 在⽬录中定义的变量,对当前⽬录及其⼦⽬录可⻅。
- 在函数中定义的变量,默认仅在函数内部可⻅(局部变量)。
- 如果需要向⽗作⽤域传递变量值,可以使⽤PARENT_SCOPE 选项。
• ⽣命周期:仅在当前 CMake 运⾏期间有效,不会保存到缓存⽂件。
• 覆盖规则:⼦⽬录或函数中定义的同名变量会临时覆盖上级作⽤域的变量。
4.2.3 缓存变量
缓存变量通过 set(... CACHE ...) 命令定义,存储在 CMakeCache.txt ⽂件中,具有全局作⽤域。
特点
- 作⽤域:全局可⻅,所有 CMakeLists.txt ⽂件均可访问。
- ⽣命周期:跨 CMake 运⾏会话持久化,直到⼿动修改或删除缓存⽂件。
- ⽤途:常⽤于存储需要⽤⼾⾃定义的配置选项 -D<var>=<value> (如编译选项、依赖路径)。
- 可以直接使⽤$CACHE{var_name} 访问
4.2.4 环境变量(Environment Variables)
环境变量是操作系统级别的变量,CMake 通过 $ENV{VAR} 语法访问。
特点
- 作⽤域:全局可⻅,但需要通过 $ENV{VAR} 显式引⽤。
- ⽣命周期:取决于操作系统,与 CMake 运⾏会话⽆关。
- ⽤途:⽤于获取系统配置(如 PATH 、 HOME )或传递外部配置参数。
Step 0:新建⽂件-CMakeLists.txt
CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(EnvVarDemo)
message("1 env: $ENV{OS}")
set(ENV{OS} "linux")
message("2 env: $ENV{OS}")
message("3 env: ${OS}")
Step 1:运⾏cmake
代码块
cmake ../
代码块
1 env:
2 env: linux
3 env:
没获取到,是因为没设置
Step 2:设置系统环境变量 && 重新运⾏
代码块
export OS=mac
cmake ../
代码块
1 env: mac
2 env: linux
3 env:
获取到了env 变量
4.2.5 三种变量的对比
特性 | 普通变量 | 缓存变量 | 环境变量 |
---|---|---|---|
定义语法 | set(VAR value) | set(VAR value CACHE ...) | 系统层⾯设置,CMake 读取 |
作⽤域 | ⽬录 / 函数 | 全局 | 全局 |
存储位置 | 内存(CMake 进程) | CMakeCache.txt | 操作系统环境 |
优先级 | 普通变量最⾼ | 缓存变量次之 | 需要显式引⽤ |
修改⽅式 | 重新运⾏ CMake | -D 命令⾏参数 | 修改系统环境 |
4.3 if 判断
4.3.1 if 语法格式如下:
代码块
if(<condition>)
<commands>
elseif(<condition>)
# optional block, can be repeated <commands>
else()
# optional block <commands>
endif()
4.3.2 支持的语法有:
- 字符串⽐较,⽐如:STREQUAL、STRLESS、STRGREATER等;
- 数值⽐较,⽐如:EQUAL、LESS、GREATER等;
- 布尔运算,AND、OR、NOT;
- 路径判断,⽐如:EXISTS、IS_DIRECTORY、IS_ABSOLUTE等;
- 使用小括号可以组合多个条件语句,⽐如:(cond1) AND (cond2 OR (cond3))。
4.3.3 基本表达式
• 1 对于基本的命名常量-true的情况:(参考了cmake master分⽀代码)
◦ "1", "Y", "ON", "YES", "TRUE" 返回true
• 2 对于基本的命名常量-false的情况:(参考了cmake master分⽀代码)
◦ "0", "N", "NO", "OFF", "FALSE", "IGNORE", "NOTFOUND", 以"-NOTFOUND"结尾 返回false
• 3 对于"数字" 类型:(参考了cmake master分⽀代码)
◦ 如果可以成为转换为数字,则按照C 语⾔的规则转换为布尔值(⾮零为 true,零为 false)
• 4 普通变量定义:
◦ 变量的值是基本命名常量⾥的-false 就返回false, 其余返回真。
• 5 字符串值:
◦ 基本命名常量-true 返回真,其余返回false。
Step 0:新建文件-CMakeLists.txt
CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(IfBasicExprDemo)
# 1 命名常量真
if("ON")
message("1: true")
else()
message("1: false")
endif()
# 2 命名常量假
if("NOTFOUND")
message("2: true")
else()
message("2: false")
endif()
# 3 数字真
if("1.1")
message("3: true")
else()
message("3: false")
endif()
# 4 数字假
if("0")
message("4: true")
else()
message("4: false")
endif()
# 5 不能转换为数字的字符串
if("123A")
message("5: true")
else()
message("5: false")
endif()
# 6 变量真
set(OS "linux")
if(OS)
message("6: true")
else()
message("6: false")
endif()
# 7 变量假
set(OS "OFF")
if(OS)
message("7: true")
else()
message("7: false")
endif()
# 8 字符串字⾯量-普通
if("abc")
message("8: true")
else()
message("8: false")
endif()
# 9 字符串字⾯量-常量
if("on")
message("9: true")
else()
message("9: false")
endif()
# 10 环境变量
message("OS: $ENV{OS}")
if($ENV{OS})
message("10: true")
else()
message("10: false")
endif()
# 11 变量和 7 对应 if(${})
set(OS "ON")
if(${OS})
message("11: true")
else()
message("11: false")
endif()
# 12 NOT
if(NOT "ON")
message("12: true")
else()
message("12: false")
endif()
# 13 AND
if("ON" AND "OFF")
message("13: true")
else()
message("13: false")
endif()
# 14 OR
if("ON" OR "OFF")
message("14: true")
else()
message("14: false")
endif()
# 15 COMMAND 检测
if(COMMAND project)
message("15: true")
else()
message("15: false")
endif()
# 16 target 检测
add_library(MyMath INTERFACE IMPORTED)
if(TARGET MyMath_static)
message("16: true")
else()
message("16: false")
endif()
# 17 DEFINED 普通变量是否定义 检测
set(NAME "bit")
if(DEFINED NAME)
message("17: true")
else()
message("17: false")
endif()
# 18 DEFINED 缓存变量是否定义 检测
#set(AGE "18" CACHE STRING "age")
if(DEFINED CACHE{AGE})
message("18: true")
else()
message("18: false")
endif()
# 19 DEFINED 环境变量是否定义 检测
if(DEFINED ENV{EMAIL})
message("19: true")
else()
message("19: false")
endif()
# 20 file ⽂件存在性检测
if(EXISTS ${CMAKE_CURRENT_LIST_DIR}/main.cpp)
message("20: true")
else()
message("20: false")
endif()
# 21 file ⽂件存在性检测
if(EXISTS "")
message("21: true")
else()
message("21: false")
endif()
# 22 file ⽂件存在性检测
if(EXISTS "./CMakeLists.txt")
message("22: true")
else()
message("22: false")
endif()
# # 23 实数⽐较
# if("20" LESS "30")
# message("23: true")
# else()
# message("23: false")
# endif()
# # 24 字符串⽐较
# if("abc" STRLESS "abd")
# message("24: true")
# else()
# message("24: false")
# endif()
# # 25 版本号⽐较
# set(VERSION "1.2.3")
# if(VERSION VERSION_LESS "1.3.0")
# message("25: true")
# else()
# message("25: false")
# endif()
Step 1:运⾏cmake
代码块
cmake ../
代码块
1: true
2: false
3: true
4: false
5: false
6: true
7: false
8: false
9: true
OS: mac
10: false
11: true
12: false
13: false
14: true
15: true
16: false
17: true
18: false
19: false
20: false
21: false
22: false
23: true
24: true
25: true
4.4 循环
在 CMake 中,循环结构⽤于重复执⾏代码块,处理多个⽬标或⽂件。CMake 提供了两种主要的循环方式: foreach 和 while 。以下是详细介绍:
4.4.1 foreach 循环
foreach ⽤于遍历列表中的元素,语法灵活多样。
基本语法
代码块
foreach(<variable> <item1> <item2> ...)
# 循环体
endforeach()
# 或使⽤列表变量
foreach(<variable> IN LISTS <list_variable>)
# 循环体
endforeach()
Step 0:新建⽂件-CMakeLists.txt
CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(ForeachDemo)
# 1 items
foreach(X "1" "2;3;4" "abc")
message("X1 : ${X}")
endforeach()
# 2 range stop
foreach(X RANGE 10)
message("X2 : ${X}")
endforeach()
# 3 range start-stop-step
foreach(X RANGE 10 20 3)
message("X3 : ${X}")
endforeach()
# 4 range Items
foreach(X IN ITEMS "1" "2;3;4" "abc")
message("X4 : ${X}")
endforeach()
# 5 range Lists
set(L1 "1")
set(L2 "2;3;4")
set(L3 "abc")
foreach(X IN LISTS L1 L2 L3)
message("X5 : ${X}")
endforeach()
Step 1:运行cmake
代码块
mkdir build && cd build
cmake ../
代码块
X1 : 1
X1 : 2;3;4
X1 : abc
X2 : 0
X2 : 1
X2 : 2
X2 : 3
X2 : 4
X2 : 5
X2 : 6
X2 : 7
X2 : 8
X2 : 9
X2 : 10
X3 : 10
X3 : 13
X3 : 16
X3 : 19
X4 : 1
X4 : 2;3;4
X4 : abc
X5 : 1
X5 : 2
X5 : 3
X5 : 4
X5 : abc
4.4.2 while 循环
while ⽤于在条件为真时重复执⾏代码块,需谨慎使⽤以避免⽆限循环。
基本语法
代码块
while(<condition>)
# 循环体
endwhile()
Step 0:新建文件-CMakeLists.txt
CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(whileDemo)
# 1 初始化变量
set(i 1)
# 2 当i <= 5 时 就输出i的值
while(i LESS_EQUAL 5)
message(STATUS "i = ${i}")
math(EXPR i "${i} +1") # i++
endwhile()
Step 1:运行cmake
代码块
cmake ../
代码块
-- i = 1
-- i = 2
-- i = 3
-- i = 4
-- i = 5
4.5 LIST
list 是⼀个⽤于操作列表数据结构的命令。列表在 CMake 中是⼀组由分号分隔的值。
基本语法
代码块
list(COMMAND <list> [args...])
Step 0:新建⽂件-CMakeLists.txt
CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(ListDemo)
# 创建列表
set(SRCS "main.cpp;utils.cpp;math.cpp")
# 添加元素
list(APPEND SRCS "network.cpp")
message(STATUS "add: ${SRCS}")
# 移除元素
list(REMOVE_ITEM SRCS "math.cpp")
message(STATUS "remove: ${SRCS}")
# 排序
list(SORT SRCS)
message(STATUS "sort: ${SRCS}")
# 遍历列表
foreach(file ${SRCS})
message(STATUS "file: ${file}")
endforeach()
# 求⻓度
list(LENGTH SRCS LEN)
message(STATUS "len(SRCS): ${LEN}")
Step 1:运行cmake
代码块
cmake ../
代码块
-- file: main.cpp
-- file: network.cpp
-- file: utils.cpp
-- len(SRCS): 3
4.6 函数
在 CMake 中,函数(Function)是组织代码、提⾼复⽤性的重要⼯具。通过定义函数,可以封装复杂操作,避免代码重复。以下是关于 CMake 函数的详细介绍:
4.6.1 函数定义与调用
使用 function() 命令定义函数,使用 function_name() 调⽤函数。
基本语法
代码块
function(<function_name> <arg1> <arg2> ...)
# 函数体
# 使⽤ ${ARGV0}、${ARGV1} 等访问参数
# 或使⽤ ${arg1}、${arg2} 等(需与定义时的参数名⼀致)
endfunction()
# 调⽤函数
<function_name>(<value1> <value2> ...)
4.6.2 参数传递
函数参数通过位置传递,可使⽤以下方式访问:
变量 | 描述 | 示例(假设调用 func(a b c) ) |
---|---|---|
${ARGV0} | 第⼀个参数 | ${ARGV0} → "a" |
${ARGV1} | 第⼆个参数 | ${ARGV1} → "b" |
${ARGC} | 参数总数 | ${ARGC} → 3 |
${ARGV} | 所有参数组成的列表 | ${ARGV} → "a;b;c" |
${ARGN} | 剩余参数(可变参数) | 若函数定义为 func(arg1) ,则 ${ARGN} → "b;c" |
Step 0:新建文件-CMakeLists.txt
CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(FunctionDemo)
function(print_args first second)
message("ARGC = ${ARGC}") # 实参总个数
message("ARGV = ${ARGV}") # 实参列表;分隔开的字符串
message("ARGV0 =${ARGV0}") # 第⼀个实参
message("ARGV1 =${ARGV1}") # 第⼆个实参
message("ARGV2 =${ARGV2}") # 第三个实参
message("ARGN = ${ARGN}") # 剩余的实参列表
endfunction()
print_args(A B C D E)
Step 1:运行cmake
代码块
cmake ../
代码块
ARGC = 5
ARGV = A;B;C;D;E
ARGV0 =A
ARGV1 =B
ARGV2 =C
ARGN = C;D;E
4.6.3 变量作用域
函数内部定义的变量默认是局部变量,不会影响外部作⽤域。若需将变量传递到⽗作⽤域,使⽤ PARENT_SCOPE 选项。
4.6.4 返回值
CMake 函数没有传统的返回值,但可通过以下⽅式 “返回” 结果:
方式 1:通过 PARENT_SCOPE 设置变量
代码块
set(OUTPUT_SUM ${RESULT} PARENT_SCOPE) # 将 SUM 传递到⽗作⽤域
⽅式 2:通过缓存变量(全局可见)
代码块
set(GLOBAL_SUM ${RESULT} CACHE INTERNAL "全局") # 将 SUM 传递到全局作⽤域
下⾯我们 验证下2种返回值
Step 0:新建文件-CMakeLists.txt
CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(FuncDemo)
# 实现⼀个加法函数,演⽰如何传递结果到外层作⽤域
function(sum A B)
math(EXPR RESULT "${A} + ${B}")
set(LOCAL_SUM ${RESULT}) # 局部变量
set(OUTPUT_SUM ${RESULT} PARENT_SCOPE) # ⽗级作⽤域变量
set(CACHE_SUM ${RESULT} CACHE STRING "sum") # 缓存变量
endfunction(sum A B)
sum(3 5)
message(STATUS "LOCAL_SUM = ${LOCAL_SUM}")
message(STATUS "OUTPUT_SUM = ${OUTPUT_SUM}")
message(STATUS "CACHE_SUM = $CACHE{CACHE_SUM}")
Step 1:运⾏cmake
代码块
cmake ../
代码块
-- LOCAL_SUM =
-- OUTPUT_SUM = 8
-- CACHE_SUM = 8
五、使用CMake 管理构建过程的开源项目介绍
5.1 JsonCpp
JsonCpp 是⼀个⽤于 C++ 的 JSON 解析 / ⽣成库,它提供了简单易⽤的 API 来处理 JSON 数据格 式。
5.1.1 GitHub地址:
https://github.com/open-source-parsers/jsoncpp.git
5.1.2 CMake注释地址:
https://gitee.com/bitedu/jsoncpp
5.1.3 CMake 语法介绍:
- 环境检测
- 变量定义
- 功能开启选项
- 集成测试例子
- 安装头⽂件
- 安装pkg-config 对应的pc配置⽂件
- 安装静态库和动态库
- 安装find_package的config模式的配置⽂件
5.1.3.1 基础语法示例
if 和 set
#使⽤ C++11 标准构建库,不受其他可能使⽤不同 CXX_STANDARD 或 CMAKE_CXX_STANDARD 的软件影响
set(CMAKE_CXX_STANDARD 11)
# 不使⽤编译器特定的 C++ 扩展
set(CMAKE_CXX_EXTENSIONS OFF)
# 要求必须使⽤指定的 C++ 标准
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 确保单配置⽣成器指定了 CMAKE_BUILD_TYPE 的值
if(NOT DEFINED CMAKE_BUILD_TYPE AND NOT DEFINED CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE Release CACHE STRING
"选择构建类型,可选值为: None Debug Release RelWithDebInfo MinSizeRel
Coverage.")
endif()
# 将项⽬的 cmake ⽬录添加到 CMake 模块搜索路径
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
# 如果当前源码⽬录是项⽬根⽬录,则设置输出⽬录
if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" CACHE PATH "Archive output dir.")
endif()
# 遍历⽰例程序列表,为每个⽰例创建可执⾏⽂件
foreach(example ${EXAMPLES})
# 创建可执⾏⽂件,源⽂件为对应⼦⽬录下的同名 .cpp ⽂件
add_executable(${example} ${example}/${example}.cpp)
# 为可执⾏⽂件添加公共包含⽬录,指向项⽬的 include ⽬录
target_include_directories(${example} PUBLIC ${CMAKE_SOURCE_DIR}/include)
# 将可执⾏⽂件与 jsoncpp_lib 库进⾏链接
target_link_libraries(${example} jsoncpp_lib)
endforeach()
function foreach if set
# 定义⼀个名为 join_paths 的函数,⽤于拼接路径
# 第⼀个参数 joined_path ⽤于存储拼接后的路径
# 第⼆个参数 first_path_segment 是路径的第⼀个⽚段
function(join_paths joined_path first_path_segment)
# 初始化临时路径变量,将第⼀个路径⽚段赋值给它
set(temp_path "${first_path_segment}")
# 遍历传递给函数的其余参数(除前两个参数外)
foreach(current_segment IN LISTS ARGN)
# 检查当前路径⽚段是否不为空
if(NOT ("${current_segment}" STREQUAL ""))
# 检查当前路径⽚段是否为绝对路径
if(IS_ABSOLUTE "${current_segment}")
# 若为绝对路径,则直接将临时路径更新为当前绝对路径
set(temp_path "${current_segment}")
else()
# 若为相对路径,则将当前路径⽚段拼接到临时路径后⾯,使⽤ / 作为分隔符
set(temp_path "${temp_path}/${current_segment}")
endif()
endif()
endforeach()
# 将最终拼接好的路径赋值给传⼊的 joined_path 变量,并提升到⽗作⽤域
set(${joined_path} "${temp_path}" PARENT_SCOPE)
endfunction
5.1.3.2 新老cmake版本示例
代码块
if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.12.0)
# 使⽤ CMake 3.12 及以上版本的新语法定义宏
add_compile_definitions(JSON_DLL_BUILD)
else()
# 使⽤旧语法定义宏
add_definitions(-DJSON_DLL_BUILD)
endif()
5.1.3.3 添加编译选项示例
代码块
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
# 使⽤常规 Clang 或 AppleClang 编译器
add_compile_options(-Wall -Wconversion -Wshadow)
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
# 使⽤ GCC 编译器
add_compile_options(-Wall -Wconversion -Wshadow -Wextra)
# 暂不启⽤ -Wsign-conversion
endif()
5.1.3.4 CTest 示例
代码块
# vim: et ts=4 sts=4 sw=4 tw=0
# 创建⼀个名为 jsoncpp_test 的可执⾏⽂件⽬标
# 源⽂件包括 jsontest.cpp、jsontest.h、fuzz.cpp、fuzz.h 和 main.cpp
add_executable(jsoncpp_test
jsontest.cpp
jsontest.h
fuzz.cpp
fuzz.h
main.cpp
)
## 创建⽤于仪表盘提交的测试,⽅便在 CI 结果中查看
# 创建⼀个名为 jsoncpp_test 的测试
add_test(NAME jsoncpp_test
# 测试命令为使⽤交叉编译模拟器(若有)运⾏ jsoncpp_test 可执⾏⽂件
COMMAND ${CMAKE_CROSSCOMPILING_EMULATOR} $<TARGET_FILE:jsoncpp_test>
)
# 设置 jsoncpp_test ⽬标的输出⽂件名为 jsoncpp_test
set_target_properties(jsoncpp_test PROPERTIES OUTPUT_NAME jsoncpp_test)
# 在构建后运⾏单元测试
if(JSONCPP_WITH_POST_BUILD_UNITTEST)
# 为 jsoncpp_test ⽬标添加⼀个构建后命令
add_custom_command(TARGET jsoncpp_test
POST_BUILD
# 构建后执⾏的命令为使⽤交叉编译模拟器(若有)运⾏ jsoncpp_test 可执⾏⽂件
COMMAND ${CMAKE_CROSSCOMPILING_EMULATOR} $<TARGET_FILE:jsoncpp_test>
)
endif()
5.1.3.5 编译静态库和动态库示例
代码块
# 若启⽤了构建共享库的选项
if(BUILD_SHARED_LIBS)
if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.12.0)
# 使⽤ CMake 3.12 及以上版本的新语法定义宏
add_compile_definitions(JSON_DLL_BUILD)
else()
# 使⽤旧语法定义宏
add_definitions(-DJSON_DLL_BUILD)
endif()
# 设置共享库的⽬标名称
set(SHARED_LIB ${PROJECT_NAME}_lib)
# 创建共享库⽬标
add_library(${SHARED_LIB} SHARED ${PUBLIC_HEADERS} ${JSONCPP_SOURCES})
# 设置共享库的属性
set_target_properties(${SHARED_LIB} PROPERTIES
OUTPUT_NAME jsoncpp # 输出库的名称
VERSION ${PROJECT_VERSION} # 库的版本号
SOVERSION ${PROJECT_SOVERSION} # 库的 API 版本号
POSITION_INDEPENDENT_CODE ${BUILD_SHARED_LIBS} # 是否⽣成位置⽆关代码
)
# 在 OSX 系统上设置库的运⾏时搜索路径
if(APPLE)
set_target_properties(${SHARED_LIB} PROPERTIES INSTALL_RPATH
"@loader_path/.")
endif()
# 为共享库设置所需的编译器特性
target_compile_features(${SHARED_LIB} PUBLIC ${REQUIRED_FEATURES})
# 为共享库设置包含⽬录
target_include_directories(${SHARED_LIB} PUBLIC
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>" # 安装后的包含⽬录
"$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/${JSONCPP_INCLUDE_DIR}>"
# 构建时的包含⽬录
)
# 将共享库添加到 CMake ⽬标列表中
list(APPEND CMAKE_TARGETS ${SHARED_LIB})
endif()
# 若启⽤了构建静态库的选项
if(BUILD_STATIC_LIBS)
# 设置静态库的⽬标名称
set(STATIC_LIB ${PROJECT_NAME}_static)
# 创建静态库⽬标
add_library(${STATIC_LIB} STATIC ${PUBLIC_HEADERS} ${JSONCPP_SOURCES})
# 设置静态库的属性
set_target_properties(${STATIC_LIB} PROPERTIES
OUTPUT_NAME jsoncpp${STATIC_SUFFIX} # 输出库的名称
VERSION ${PROJECT_VERSION} # 库的版本号
)
# 在 OSX 系统上设置库的运⾏时搜索路径
if(APPLE)
set_target_properties(${STATIC_LIB} PROPERTIES INSTALL_RPATH
"@loader_path/.")
endif()
# 为静态库设置所需的编译器特性
target_compile_features(${STATIC_LIB} PUBLIC ${REQUIRED_FEATURES})
# 为静态库设置包含⽬录
target_include_directories(${STATIC_LIB} PUBLIC
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>" # 安装后的包含⽬录
"$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/${JSONCPP_INCLUDE_DIR}>"
# 构建时的包含⽬录
)
# 将静态库添加到 CMake ⽬标列表中
list(APPEND CMAKE_TARGETS ${STATIC_LIB})
endif()
# 安装⽬标
install(TARGETS ${CMAKE_TARGETS} ${INSTALL_EXPORT}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} # 可执⾏⽂件安装⽬录
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} # 共享库安装⽬录
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} # 静态库安装⽬录
OBJECTS DESTINATION ${CMAKE_INSTALL_LIBDIR} # 对象⽂件安装⽬录
)
5.1.3.6 安装pkg-config配置文件
Step 0:目录结构:
代码块
find_jsoncpp_pkg_config
├── CMakeLists.txt
├── main.cpp
└── README
Step 1:新建⽂件-CMakeLists.txt
代码块
cmake_minimum_required(VERSION 3.18)
project(JsoncppDemo
LANGUAGES CXX
)
set(CMAKE_CXX_STANDARD 11)
# 1. 引⼊ FindPkgConfig.cmake
find_package(PkgConfig REQUIRED)
# 2. 让 pkg-config 搜索 jsoncpp,并⽣成 IMPORTED ⽬标
# - JSONCPP : 这是前缀,⽤来⽣成⼀系列变量 (JSONCPP_INCLUDE_DIRS、…)
# - REQUIRED : 找不到就报错并停⽌配置
# - IMPORTED_TARGET : 额外创建 PkgConfig::JSONCPP ⽬标(推荐⽤法)
pkg_check_modules(JSONCPP REQUIRED IMPORTED_TARGET jsoncpp)
# 3. 添加你的可执⾏⽂件 / 库
add_executable(main main.cpp)
# 4. 通过导⼊⽬标 PkgConfig::JSONCPP,把所有使⽤需求⼀次性传播进来
target_link_libraries(main PRIVATE PkgConfig::JSONCPP)
# 可选:查看⾃动填充的属性(调试⽤)
get_target_property(_incs PkgConfig::JSONCPP INTERFACE_INCLUDE_DIRECTORIES)
message(STATUS "jsoncpp include dirs = ${_incs}")
Step 2:新建⽂件-main.cpp
代码块
#include <iostream>
#include <json/json.h>
int main()
{
Json::Value root;
root["name"] = "Bits";
root["age"] = 18;
Json::StreamWriterBuilder writer;
std::string JsonStr = Json::writeString(writer, root);
std::cout << "root: \r\n" << JsonStr << std::endl;
return 0;
}
Step 3:运⾏
代码块
mkdir build &&cd build
cmake ../
make
5.1.3.7 安装库文件示例
代码块
set(INSTALL_EXPORT EXPORT jsoncpp)
# 安装⽬标
install(TARGETS ${CMAKE_TARGETS} ${INSTALL_EXPORT}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} # 可执⾏⽂件安装⽬录
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} # 共享库安装⽬录
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} # 静态库安装⽬录
OBJECTS DESTINATION ${CMAKE_INSTALL_LIBDIR} # 对象⽂件安装⽬录
)
# 若启⽤了⽣成并安装 CMake 包⽂件的选项
if(JSONCPP_WITH_CMAKE_PACKAGE)
include(CMakePackageConfigHelpers)
# 安装导出的⽬标
install(EXPORT jsoncpp
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/jsoncpp
FILE jsoncpp-targets.cmake)
# 配置 CMake 包配置⽂件
configure_package_config_file(jsoncppConfig.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/jsoncppConfig.cmake
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/jsoncpp)
# 编写基本的包版本⽂件
write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/jsoncppConfigVers
ion.cmake"
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion)
# 安装⽣成的 CMake 包⽂件
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/jsoncppConfigVersion.cmake
${CMAKE_CURRENT_BINARY_DIR}/jsoncppConfig.cmake
${CMAKE_CURRENT_SOURCE_DIR}/jsoncpp-namespaced-targets.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/jsoncpp)
endif()
5.1.3.8 安装头文件示例
代码块
# 使⽤ file(GLOB) 命令递归查找当前⽬录下 json ⼦⽬录中所有以 .h 结尾的⽂件
# 并将这些⽂件的路径存储在变量 INCLUDE_FILES 中
# 注意:虽然 GLOB ⽅便,但在某些情况下(如⽂件新增或删除)可能不会⾃动更新,需留意
file(GLOB INCLUDE_FILES "json/*.h")
# 使⽤ install 命令将查找到的头⽂件安装到指定⽬录
# FILES 关键字指定要安装的⽂件列表,这⾥使⽤之前存储在 INCLUDE_FILES 变量中的⽂件路径
# DESTINATION 关键字指定安装的⽬标⽬录,使⽤ CMAKE_INSTALL_INCLUDEDIR 变量获取系统标
准的包含⽬录,
# 并在其后添加 /json ⼦⽬录
install(FILES
${INCLUDE_FILES}
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/json)
5.2 Curl
Curl,即 “Client URL” 的缩写,是⼀个功能强⼤的命令⾏⼯具和库,⽤于通过 URL 语法在不同⽹络协议下进⾏数据传输,常⽤于 http/https, FTP, ⽂件传输,等场景,⽀持超过30多种协议,和 C,C++, Java,Pyhton,GO,Node 等多种语⾔。
5.2.1 Github:
https://github.com/curl/curl
5.2.2 CMake注释地址:
https://gitee.com/bitedu/curl
5.2.3 CMake 语法介绍:
- 变量定义
- 功能开启选项
- 安装头⽂件
- 安装man⼿册
- 安装pkg-config 对应的pc配置⽂件
- 导出⽬标到构建⽬录
- 安装静态库和动态库
- 安装find_package的config模式的配置⽂件
5.2.3.1 基础语法示例
代码块
# 设置 CMake 最低和最⾼兼容版本,若版本不满⾜则报错
cmake_minimum_required(VERSION 3.7...3.16 FATAL_ERROR)
# 输出当前使⽤的 CMake 版本信息
message(STATUS "Using CMake version ${CMAKE_VERSION}")
# 检查是否启⽤ GnuTLS 作为 SSL/TLS 库
if(CURL_USE_GNUTLS)
# 若启⽤ pkg-config 来检测依赖项
if(CURL_USE_PKGCONFIG)
# 查找 PkgConfig 模块,静默模式
find_package(PkgConfig QUIET)
# 使⽤ pkg-config 检查 GnuTLS 库
pkg_check_modules(GNUTLS "gnutls")
# 若找到 GnuTLS 库
if(GNUTLS_FOUND)
# 设置 GnuTLS 库列表
set(GNUTLS_LIBRARIES ${GNUTLS_LINK_LIBRARIES})
# 将 GnuTLS 编译标志列表转换为空格分隔的字符串
string(REPLACE ";" " " GNUTLS_CFLAGS "${GNUTLS_CFLAGS}")
# 若 GnuTLS 库有编译标志,将其添加到 C 编译器标志中
if(GNUTLS_CFLAGS)
string(APPEND CMAKE_C_FLAGS " ${GNUTLS_CFLAGS}")
endif()
endif()
endif()
# 若未找到 GnuTLS 库
if(NOT GNUTLS_FOUND)
# 查找 GnuTLS 库,若未找到则报错
find_package(GnuTLS REQUIRED)
endif()
# 查找 Nettle 库,若未找到则报错
find_package(Nettle REQUIRED)
# 启⽤ SSL ⽀持
set(_ssl_enabled ON)
# 启⽤ GnuTLS ⽀持
set(USE_GNUTLS ON)
# 将 GnuTLS 和 Nettle 库添加到 CURL 依赖库列表中
list(APPEND CURL_LIBS ${GNUTLS_LIBRARIES} ${NETTLE_LIBRARIES})
# 将 GnuTLS 和 Nettle 库的路径添加到 CURL 库路径列表中
list(APPEND CURL_LIBDIRS ${GNUTLS_LIBRARY_DIRS} ${NETTLE_LIBRARY_DIRS})
# 将 GnuTLS 和 Nettle 库的 pkg-config 依赖项名称添加到
LIBCURL_PC_REQUIRES_PRIVATE 列表中
list(APPEND LIBCURL_PC_REQUIRES_PRIVATE "gnutls" ${NETTLE_PC_REQUIRES})
# 添加 GnuTLS 和 Nettle 头⽂件⽬录到系统包含路径
include_directories(SYSTEM ${GNUTLS_INCLUDE_DIRS} ${NETTLE_INCLUDE_DIRS})
endif()
foreach
# 执⾏ curl 特定的测试
foreach(_curl_test IN ITEMS
HAVE_FCNTL_O_NONBLOCK
HAVE_IOCTLSOCKET
HAVE_IOCTLSOCKET_CAMEL
HAVE_IOCTLSOCKET_CAMEL_FIONBIO
HAVE_IOCTLSOCKET_FIONBIO
HAVE_IOCTL_FIONBIO
HAVE_IOCTL_SIOCGIFADDR
HAVE_SETSOCKOPT_SO_NONBLOCK
HAVE_GETHOSTBYNAME_R_3
HAVE_GETHOSTBYNAME_R_5
HAVE_GETHOSTBYNAME_R_6
HAVE_BOOL_T
STDC_HEADERS
HAVE_ATOMIC
)
# 对每个测试项执⾏⾃定义测试
curl_internal_test(${_curl_test})
endforeach()
5.2.3.2 跨平台示例
代码块
# 检查当前系统是否为 macOS(APPLE)、Cygwin 环境(CYGWIN)或 OpenBSD 系统
# 若满⾜上述条件之⼀,则系统不具备 sys/eventfd.h 头⽂件
if(APPLE OR
CYGWIN OR
CMAKE_SYSTEM_NAME STREQUAL "OpenBSD")
# 设置 HAVE_SYS_EVENTFD_H 为 0,表⽰系统没有 sys/eventfd.h 头⽂件
set(HAVE_SYS_EVENTFD_H 0)
# 若当前系统是 Linux、FreeBSD 或 NetBSD 系统
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR
CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR
CMAKE_SYSTEM_NAME STREQUAL "NetBSD")
# 设置 HAVE_SYS_EVENTFD_H 为 1,表⽰系统存在 sys/eventfd.h 头⽂件
set(HAVE_SYS_EVENTFD_H 1)
endif()
5.2.3.3 目录结构示例
代码块
if(HAVE_MANUAL_TOOLS)
# 添加 docs ⼦⽬录
add_subdirectory(docs)
endif()
# 添加 scripts ⼦⽬录,⽤于⽣成 shell 补全
add_subdirectory(scripts)
# 移除 CURL_LIBDIRS 列表中的重复项
list(REMOVE_DUPLICATES CURL_LIBDIRS)
# 添加 lib ⼦⽬录
add_subdirectory(lib)
if(BUILD_CURL_EXE)
# 若构建 CURL 可执⾏⽂件,添加 src ⼦⽬录
add_subdirectory(src)
endif()
# 定义⼀个 CMake 选项,⽤于控制是否构建 libcurl ⽰例,默认启⽤
option(BUILD_EXAMPLES "Build libcurl examples" ON)
if(BUILD_EXAMPLES)
# 若启⽤构建⽰例,添加 docs/examples ⼦⽬录
add_subdirectory(docs/examples)
endif()
if(CURL_BUILD_TESTING)
# 若构建测试,添加 tests ⼦⽬录
add_subdirectory(tests)
endif()
5.2.3.4 查找第三方模块示例
module 模式查找
# 设置 nghttp2 的 pkg-config 包名
set(NGHTTP2_PC_REQUIRES "libnghttp2")
# 若启⽤了使⽤ pkg-config 查找库,并且未⼿动指定头⽂件⽬录和库⽂件路径
if(CURL_USE_PKGCONFIG AND
NOT DEFINED NGHTTP2_INCLUDE_DIR AND
NOT DEFINED NGHTTP2_LIBRARY)
# 安静模式查找 PkgConfig 模块,避免输出过多信息
find_package(PkgConfig QUIET)
# 使⽤ pkg-config 检查 nghttp2 库,获取相关信息
pkg_check_modules(NGHTTP2 ${NGHTTP2_PC_REQUIRES})
endif()
# 若通过 pkg-config 找到了 nghttp2 库
if(NGHTTP2_FOUND)
# 将 NGHTTP2_CFLAGS 中的分号替换为空格,以符合编译标志的格式要求
string(REPLACE ";" " " NGHTTP2_CFLAGS "${NGHTTP2_CFLAGS}")
# 输出找到 nghttp2 库的状态信息,包含头⽂件⽬录和版本号
message(STATUS "Found NGHTTP2 (via pkg-config): ${NGHTTP2_INCLUDE_DIRS}
(found version \"${NGHTTP2_VERSION}\")")
else()
# ⼿动查找 nghttp2 的头⽂件⽬录,查找的头⽂件为 nghttp2/nghttp2.h
find_path(NGHTTP2_INCLUDE_DIR NAMES "nghttp2/nghttp2.h")
# ⼿动查找 nghttp2 的库⽂件,尝试查找 nghttp2 和 nghttp2_static 两个库名
find_library(NGHTTP2_LIBRARY NAMES "nghttp2" "nghttp2_static")
# 从缓存中移除 NGHTTP2_VERSION 变量,避免旧版本信息影响当前查找
unset(NGHTTP2_VERSION CACHE)
# 若找到了头⽂件⽬录,并且 nghttp2/nghttp2ver.h ⽂件存在
if(NGHTTP2_INCLUDE_DIR AND EXISTS
"${NGHTTP2_INCLUDE_DIR}/nghttp2/nghttp2ver.h")
# 定义正则表达式,⽤于匹配 NGHTTP2_VERSION 宏定义
set(_version_regex "#[\t ]*define[\t ]+NGHTTP2_VERSION[\t ]+\"([^\"]*)\"")
# 从 nghttp2/nghttp2ver.h ⽂件中读取匹配正则表达式的⾏
file(STRINGS "${NGHTTP2_INCLUDE_DIR}/nghttp2/nghttp2ver.h" _version_str
REGEX "${_version_regex}")
# 使⽤正则表达式提取版本号
string(REGEX REPLACE "${_version_regex}" "\\1" _version_str
"${_version_str}")
# 设置 NGHTTP2_VERSION 变量为提取的版本号
set(NGHTTP2_VERSION "${_version_str}")
# 移除临时变量,避免污染全局变量空间
unset(_version_regex)
unset(_version_str)
endif()
# 包含 FindPackageHandleStandardArgs 模块,⽤于统⼀处理查找结果
include(FindPackageHandleStandardArgs)
# 使⽤标准参数处理查找结果,判断是否找到 nghttp2 库
find_package_handle_standard_args(NGHTTP2
REQUIRED_VARS
NGHTTP2_INCLUDE_DIR
NGHTTP2_LIBRARY
VERSION_VAR
NGHTTP2_VERSION
)
# 若⼿动查找找到了 nghttp2 库
if(NGHTTP2_FOUND)
# 设置头⽂件⽬录列表
set(NGHTTP2_INCLUDE_DIRS ${NGHTTP2_INCLUDE_DIR})
# 设置库⽂件名称列表
set(NGHTTP2_LIBRARIES ${NGHTTP2_LIBRARY})
endif()
# 将 NGHTTP2_INCLUDE_DIR 和 NGHTTP2_LIBRARY 标记为⾼级变量,在 CMake GUI 中默认隐
藏
mark_as_advanced(NGHTTP2_INCLUDE_DIR NGHTTP2_LIBRARY)
endif()
5.2.3.5 编译静态库和动态库示例
静态库编译
代码块
# 设置库的名称
set(LIB_NAME "libcurl")
# 设置 libcurl 库的基础名称,可通过 CMake 缓存进⾏配置
set(LIBCURL_OUTPUT_NAME "libcurl" CACHE STRING "Basename of the curl library")
# 定义静态库的名称
set(LIB_STATIC "libcurl_static")
# 定义共享库的名称
set(LIB_SHARED "libcurl_shared")
# 我们希望在所有平台上都将其称为 libcurl
# 若构建静态库
if(BUILD_STATIC_LIBS)
# 将静态库添加到导出列表中 set(LIB_STATIC "libcurl_static")
list(APPEND libcurl_export ${LIB_STATIC})
# 创建静态库
add_library(${LIB_STATIC} STATIC ${LIB_SOURCE})
# 为静态库添加别名
add_library(${PROJECT_NAME}::${LIB_STATIC} ALIAS ${LIB_STATIC})
if(WIN32)
# 为 Windows 平台的静态库添加编译定义,表明使⽤静态库
set_property(TARGET ${LIB_STATIC} APPEND PROPERTY COMPILE_DEFINITIONS
"CURL_STATICLIB")
endif()
# 为静态库添加私有链接库
target_link_libraries(${LIB_STATIC} PRIVATE ${CURL_LIBS})
# 移除 "lib" 前缀,因为库已经命名为 "libcurl"
set_target_properties(${LIB_STATIC} PROPERTIES
PREFIX "" OUTPUT_NAME "${LIBCURL_OUTPUT_NAME}"
SUFFIX "${STATIC_LIB_SUFFIX}${CMAKE_STATIC_LIBRARY_SUFFIX}"
INTERFACE_COMPILE_DEFINITIONS "CURL_STATICLIB"
INTERFACE_LINK_DIRECTORIES "${CURL_LIBDIRS}")
# 若 CURL 隐藏私有符号
if(CURL_HIDES_PRIVATE_SYMBOLS)
# 为静态库添加编译标志以隐藏符号
set_property(TARGET ${LIB_STATIC} APPEND PROPERTY COMPILE_FLAGS
"${CURL_CFLAG_SYMBOLS_HIDE}")
# 为静态库添加编译定义,表明隐藏符号
set_property(TARGET ${LIB_STATIC} APPEND PROPERTY COMPILE_DEFINITIONS
"CURL_HIDDEN_SYMBOLS")
endif()
# 若 CURL ⽀持链接时优化
if(CURL_HAS_LTO)
# 为静态库启⽤链接时优化
set_target_properties(${LIB_STATIC} PROPERTIES
INTERPROCEDURAL_OPTIMIZATION TRUE)
endif()
# 为静态库添加接⼝包含⽬录
target_include_directories(${LIB_STATIC} INTERFACE
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
"$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>")
endif()
动态库的构建
代码块
# 若构建共享库
if(BUILD_SHARED_LIBS)
# 将共享库添加到导出列表中 set(LIB_SHARED "libcurl_shared")
list(APPEND libcurl_export ${LIB_SHARED})
# 创建共享库
add_library(${LIB_SHARED} SHARED ${LIB_SOURCE})
# 为共享库添加别名
add_library(${PROJECT_NAME}::${LIB_SHARED} ALIAS ${LIB_SHARED})
# 为共享库添加接⼝包含⽬录
target_include_directories(${LIB_SHARED} INTERFACE
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
"$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>")
# 若启⽤ SOVERSION
if(CURL_LIBCURL_SOVERSION)
# 设置共享库的版本号和 SOVERSION
set_target_properties(${LIB_SHARED} PROPERTIES
VERSION "${_cmakeversion}" SOVERSION "${_cmakesoname}")
endif()
endif()
5.2.3.6 安装静态库和动态库
代码块
# 为 LIB_SELECTED ⽬标添加别名
add_library(${LIB_NAME} ALIAS ${LIB_SELECTED})
add_library(${PROJECT_NAME}::${LIB_NAME} ALIAS ${LIB_SELECTED})
# 若启⽤导出⽬标
if(CURL_ENABLE_EXPORT_TARGET)
# 若构建静态库,安装静态库⽬标 set(TARGETS_EXPORT_NAME "${PROJECT_NAME}Targets")
if(BUILD_STATIC_LIBS)
install(TARGETS ${LIB_STATIC}
EXPORT ${TARGETS_EXPORT_NAME}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
endif()
# 若构建共享库,安装共享库⽬标 set(TARGETS_EXPORT_NAME "${PROJECT_NAME}Targets")
if(BUILD_SHARED_LIBS)
install(TARGETS ${LIB_SHARED}
EXPORT ${TARGETS_EXPORT_NAME}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
endif()
endif()
if(CURL_ENABLE_EXPORT_TARGET)
install(EXPORT "${TARGETS_EXPORT_NAME}"
NAMESPACE "${PROJECT_NAME}::"
DESTINATION ${_install_cmake_dir})
endif()
5.2.3.7 安装文件示例:
代码块
# ⽣成匹配此配置的 pkg-config ⽂件。
configure_file(
"${PROJECT_SOURCE_DIR}/libcurl.pc.in"
"${PROJECT_BINARY_DIR}/libcurl.pc" @ONLY)
# 安装⽣成的 libcurl.pc ⽂件
install(FILES "${PROJECT_BINARY_DIR}/libcurl.pc"
DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
# 安装头⽂件
install(DIRECTORY "${PROJECT_SOURCE_DIR}/include/curl"
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
FILES_MATCHING PATTERN "*.h")
# 包含 CMake 包配置帮助模块
include(CMakePackageConfigHelpers)
# ⽣成基本的包版本⽂件
write_basic_package_version_file(
"${_version_config}"
VERSION ${_curl_version}
COMPATIBILITY SameMajorVersion)
# 读取⽣成的版本配置⽂件内容
file(READ "${_version_config}" _generated_version_config)
# 在版本配置⽂件开头添加兼容性处理代码
file(WRITE "${_version_config}" "
if(NOT PACKAGE_FIND_VERSION_RANGE AND PACKAGE_FIND_VERSION_MAJOR STREQUAL \"7\")
# Version 8 satisfies version 7... requirements
set(PACKAGE_FIND_VERSION_MAJOR 8)
set(PACKAGE_FIND_VERSION_COUNT 1)
endif()
${_generated_version_config}")
# ⽣成项⽬配置⽂件
configure_package_config_file("CMake/curl-config.cmake.in"
"${_project_config}"
INSTALL_DESTINATION ${_install_cmake_dir}
PATH_VARS CMAKE_INSTALL_INCLUDEDIR)
if(CURL_ENABLE_EXPORT_TARGET)
# 若启⽤导出⽬标,安装导出的⽬标
install(EXPORT "${TARGETS_EXPORT_NAME}"
NAMESPACE "${PROJECT_NAME}::"
DESTINATION ${_install_cmake_dir})
endif()
# 安装版本配置⽂件和项⽬配置⽂件
install(FILES ${_version_config} ${_project_config}
DESTINATION ${_install_cmake_dir})
if(NOT TARGET curl_uninstall)
# 若不存在 curl_uninstall ⽬标,⽣成卸载脚本
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/CMake/cmake_uninstall.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/CMake/cmake_uninstall.cmake"
@ONLY)
# 添加 curl_uninstall ⾃定义⽬标
add_custom_target(curl_uninstall
COMMAND ${CMAKE_COMMAND} -P
"${CMAKE_CURRENT_BINARY_DIR}/CMake/cmake_uninstall.cmake")
endif()
5.3 CMakeQT-CustomWindow
⼀个基于 Qt 和 CMake 的⾃定义窗⼝应⽤程序⽰例,展⽰了如何创建⼀个没有系统默认边框和标 题栏的窗⼝,并通过⾃定义 UI 元素实现窗⼝的拖拽、最⼤化 / 最⼩化 / 关闭等功能。该项⽬使⽤ CMake 作为构建系统,⽀持跨平台开发(Windows、macOS、Linux)。
5.3.1 GitHub地址:
https://github.com/nianziyishi/CMakeQT-CustomWindow
5.3.2 CMake 注释地址:
https://gitee.com/bitedu/cmake-qt-custom-window
5.3.3 CMake 语法介绍:
5.3.3.1 安装Qt-5
代码块
sudo apt install build-essential qtbase5-dev qtchooser qt5-qmake qtbase5-dev
tools
5.3.3.2 基础语法示例
代码块
# CMakeList.txt: 顶层 CMake 项⽬⽂件,在此处执⾏全局配置
# 并包含⼦项⽬。
#
cmake_minimum_required (VERSION 3.8)
# ==================配置项⽬信息==================
# 项⽬名称
project(QCustomWindow LANGUAGES CXX)
# 包含当前⽬录
set(CMAKE_INCLUDE_CURRENT_DIR ON)
# 设置c++语⾔标准,这⾥使⽤c++11
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# ⾃动调⽤moc, uic, rcc
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)
# ==================配置项⽬环境==================
# 根据当前平台进⾏配置
# 设置qt路径
# 设置程序和库输出路径的变量
if(WIN32)
message( STATUS "PLATFORM = IS_WINDOWS" )
set(QTLIB_DIRECTORY C:/Qt/Qt5.15.2/5.15.2/msvc2019_64)
set(OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/out/bin/${CMAKE_BUILD_TYPE})
elseif(UNIX)
message( STATUS "PLATFORM = IS_LINUX" )
set(QTLIB_DIRECTORY $ENV{HOME}/qt/Qt5.15.2/5.15.2/gcc_64/lib/cmake)
set(OUTPUT_DIRECTORY
$ENV{HOME}/${CMAKE_PROJECT_NAME}/bin/${CMAKE_BUILD_TYPE})
elseif(APPLE)
message( STATUS "PLATFORM = IS_MACOS" )
endif()
# 设置可执⾏⽂件输出位置
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${OUTPUT_DIRECTORY}")
# 设置静态库⽂件输出位置
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${OUTPUT_DIRECTORY}")
# 设置动态库⽂件输出位置
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${OUTPUT_DIRECTORY}")
# 设置Qt5的cmake模块所在⽬录,如果不设置将使⽤系统提供的版本
set(CMAKE_PREFIX_PATH ${QTLIB_DIRECTORY})
# 包含⼦项⽬
add_subdirectory(Main)
add_subdirectory(GUI)
add_subdirectory(BaseModule)
# windows平台设置增量链接
if(MSVC)
# 关闭警告
#add_definitions(-w)
#target_compile_options(${PROJECT_NAME} PUBLIC "/ZI")
target_link_options(${PROJECT_NAME} PUBLIC "/INCREMENTAL")
endif()
# ==================打印环境信息==================
# 打印项⽬名称
message( STATUS "项⽬名称 = ${CMAKE_PROJECT_NAME}" )
# 打印项⽬路径
message( STATUS "项⽬ 录 = ${PROJECT_SOURCE_DIR}" )
# 打印项⽬输出路径
message( STATUS "输出⽬录 = ${OUTPUT_DIRECTORY}" )
# 打印编译后程序路径
#message("编译后可执⾏程序⽬录 = " ${EXECUTABLE_OUTPUT_PATH})
# 打印编译后库⽂件路径
#message("编译后库⽂件⽬录 = " ${LIBRARY_OUTPUT_PATH})
# 打印当前处理器架构
message( STATUS "PROCESSOR = ${CMAKE_SYSTEM_PROCESSOR}" )
# 打印当前系统平台
message( STATUS "SYSTEM = ${CMAKE_SYSTEM}" )
#message( STATUS "SYSTEM_NAME = ${CMAKE_SYSTEM_NAME}" )
#message( STATUS "SYSTEM_VERSION = ${CMAKE_SYSTEM_VERSION}" )
5.3.3.3 查找QT 并使用示例
代码块
# CMakeList.txt: ⼦级 GUI 项⽬⽂件
#
#
# ==================配置项⽬信息==================
# 项⽬名称
project(GUI LANGUAGES CXX)
# 设置⽣成动态库(默认静态库)
set(BUILD_SHARED_LIBS ON)
# 引⼊Qt库
find_package(Qt5 COMPONENTS Core Widgets Gui REQUIRED)
# 查找当前⽬录Demo下的所有源⽂件,将名称保存到 DIR_SRCS 变量中
aux_source_directory(. DIR_SRCS)
aux_source_directory(./Demo DIR_SRCS)
# 添加⼯程源⽂件到库
add_library(${PROJECT_NAME} ${DIR_SRCS})
# 设置并引⽤头⽂件路径
set(INCLUDE_PATH
${PROJECT_SOURCE_DIR}
${PROJECT_SOURCE_DIR}/Demo
)
target_include_directories(${PROJECT_NAME} PUBLIC ${INCLUDE_PATH})
# 定义导出符号的宏定义
if(MSVC)
target_compile_definitions(${PROJECT_NAME} PUBLIC GUI_LIB)
# 定义库类型的宏定义
if(BUILD_SHARED_LIBS)
target_compile_definitions(${PROJECT_NAME} PUBLIC SHARED_LIB)
endif()
endif()
# =================链接依赖库==================
# 将Qt的库链接⾄程序
target_link_libraries(${PROJECT_NAME} Qt5::Core Qt5::Widgets Qt5::Gui)
# 指定依赖BaseModule库⽂件
add_dependencies(${PROJECT_NAME} BaseModule)
# 设置并引⽤BaseModule的头⽂件路径
set(LIB_INCLUDE_PATH
${PROJECT_SOURCE_DIR}/../BaseModule
${PROJECT_SOURCE_DIR}/../BaseModule/BaseWidget
)
target_include_directories(${PROJECT_NAME} PUBLIC ${LIB_INCLUDE_PATH})
# 链接GUI库⽂件的⽬录
target_link_directories(${PROJECT_NAME} PUBLIC ${OUTPUT_DIRECTORY})
# 链接BaseModule库⽂件
if(WIN32)
target_link_libraries(${PROJECT_NAME} BaseModule.lib)
else()
target_link_libraries(${PROJECT_NAME} BaseModule)
endif()
# ==================打印环境信息==================
# 打印项⽬路径
message( STATUS "${PROJECT_NAME} 项⽬ 录 = ${PROJECT_SOURCE_DIR}" )
message( STATUS "${PROJECT_NAME} 输出⽬录 = ${OUTPUT_DIRECTORY}" )
六、常见问题
6.1 Visual Studio Code 里的 include "xxx.h" 下标记红线如何解决
以下修复方案是使用的VS Code 的官方插件,非第三方插件。
问题引出:
在上面的3.2节项目中,整体项目结构如下:
Code block
my_math
├── CMakeLists.txt
├── app
│ ├── CMakeLists.txt
│ ├── app.cmake
│ └── main.cpp
├── build
└── my_lib
├── CMakeLists.txt
├── include
│ └── math.h
└── src
├── add.cpp
└── sub.cpp
在 app/main.cpp⾥因为使⽤了my_lib 下的add和sub 函数,因此需要包含my_lib/include/my_math.h头⽂件,来给编译器说明 add和sub函数的签名。vs code 工程问题截图 如下:
6.1.1.1 修复方案⼀:
Step 0:⿏标放置在带波浪线的第4⾏上,vs code 会出现如下提⽰:
Step 1:点击下⽅的 Quick Fix
Step 2: 选择 Add to "includePath"
Step 3: 编辑需要添加的路径到 include path Text 框⾥,重新打开 app/main.cpp,下划线消失。
6.1.1.2 修复方案二:
如果对 VS Code 熟悉的同学,知道每⼀个⼯程都有⼀个.vscode配置,可以直接打开当前⼯程下的.vscode ⽬录下的项⽬配置⽂件configurations/includePath直接编辑。
打开.vscode/c_cpp_properties.json. 直接添加要包含的头⽂件路径到configurations/includePath⾥。并保存。
其实⽅案⼀操作操作的也是这个⽂件,熟悉之后类似场景都可以直接修改⼯程配置⽂件。
附录1:CMake源文件树构建树⾃动推导模式介绍
CMake 使⽤ -S 或 -B 指定的路径始终分别分类为 source 或 build 树。 但如果只给出了⼀种类型的 path,则当前⼯作⽬录 (cwd) ⽤于另⼀种 path。使⽤ CMakeLists.txt 标识源⽂件树根⽬录,使⽤CMakeCache.txt 标识构建树根⽬录,推导关系如下:
附录2:CMake GNUInstallDirs模块类型和安装⽬录映射关系
下表显⽰了⽬标类型及其关联变量和在未给出⽬标时应⽤的内置默认值:
Install(file) 时 必须提供 TYPE 或 DESTINATION ,但不能同时提供两者。 TYPE 参数指定要安装 的⽂件的通⽤⽂件类型。 然后,将通过从 GNUInstallDirs 中获取相应的变量来⾃动设置⽬的地,或者如果未定义该变量,则使⽤内置默认值。 有关⽀持的⽂件类型及其相应的变量和内置默认 值,请参阅下表。 如果项⽬希望显式定义安装⽬标,则可以提供 DESTINATION 参数⽽不是⽂件类型。
附录3:CMake INTERFACE, PUBLIC 和 PRIVATE 解释
在 CMake 中, INTERFACE 、 PUBLIC 和 PRIVATE 是用于控制目标(如库、可执行⽂件)
和属性传播范围的关键字。它们主要影响以下两个⽅面:
- 头⽂件搜索路径 ( target_include_directories )
- 链接依赖 ( target_link_libraries )
这些关键字决定了属性是仅对当前目标可见,还是会传递给依赖它的其他目标。
PUBLIC
同时填充构建⽬标属性和使⽤者⽬标属性。
PRIVATE
仅填充⽤于构建⽬标的属性
INTERFACE
仅填充⽤于使⽤者⽬标的属性。
三者的核心区别
关键字 | 对当前⽬标的构建影响 | 对当前⽬标使⽤者的影响 | 典型场景PRIVATE |
---|---|---|---|
PRIVATE | ⽣效 | 不传播 | 仅内部使⽤的依赖(如实现细节) |
PUBLIC | ⽣效 | 完全传播 | 公开 API 所需的依赖(如公共头⽂件) |
INTERFACE | 不⽣效 | 仅传播 | 纯接⼝库(如头⽂件) |
附录4:CMake 中 include 和 add_subdirectory 命令的区别
在 CMake 中, include() 和 add_subdirectory() 是两种不同的代码组织⽅式,它们的核⼼区别在于作⽤域和执⾏⽅式。以下是详细对⽐:
核心区别对比表
特性 | include() | add_subdirectory() |
---|---|---|
作⽤域 | 继承当前作⽤域 | 创建新的⼦⽬录作⽤域 |
⽂件类型 | 通常⽤于 .cmake ⼯具⽂件 | ⽤于包含⼦项⽬的CMakeLists.txt |
内置变量 | 不改变 CMAKE_CURRENT_SOURCE_DIR | 切换到⼦⽬录的路径 |
相对路径 | 基于调⽤者的⽬录 | 基于⼦⽬录的⽬录 |
变量传递 | 直接访问和修改当前作⽤域的变量 | 默认⽆法修改⽗作⽤域变量 |
执⾏顺序 | ⽴即执⾏,顺序执⾏ | 递归处理⼦⽬录,可能并⾏执⾏ |
典型场景 | 加载⼯具函数、配置选项 | 构建⼦模块、库或可执⾏⽂件 |
作用域与上下文
- include :直接在当前 CMakeLists.txt 的上下⽂中执⾏被包含的⽂件,被包含⽂件可以访问和 修改当前作⽤域的所有变量。
- add_subdirectory :创建新的⼦⽬录作⽤域,⼦⽬录中的变量默认是局部的,⽗⽬录⽆法直 接访问⼦⽬录的变量,需通过 PARENT_SCOPE 或缓存变量传递。
内置变量的变化
- include:保持为调⽤者的⽬录。
- add_subdirectory:变为⼦⽬录的⽬录。
⽂件类型与⽤途
- Inclue :通常⽤于包含 .cmake ⼯具⽂件,这些⽂件定义函数、宏或配置选项。
- add_subdirectory: ⽤于包含⼦项⽬的 CMakeLists.txt,通常对应实际的源代码⽬录。
变量传递方式
- Inclue :直接访问和修改当前作⽤域变量。
- add_subdirectory: ⼦⽬录需显式通过PARENT_SCOPE 传递变量到⽗⽬录:
执⾏顺序与依赖
- Inclue :按顺序⽴即执⾏被包含的⽂件,适⽤于初始化配置。
- add_subdirectory: 递归处理⼦⽬录,CMake 会⾃动处理依赖关系。
附录5:Visual Studio Code Remote SSH模式介绍
•VS Code Remote Development extensions 原⽣⽀持 Remote SSH, Remote Tunnels,
Dev Containers , Windows Subsystem for Linux (WSL) 等多种远程模式,本教程重点
介绍 Remote SSH模式,其他2种感兴趣可以⾃⾏ 点击此处去官⽅查看。
• Visual Studio Code Remote - SSH 扩展官方文档。
• 确保 Ubuntu 服务器开启了ssh远程链接功能,并使⽤ssh root@127.0.0.1 可以链接上。
• 本教程使⽤ssh 用户名密码模式登录,如果需要配置SSH公钥认证,请参考链接或者使⽤
⾖包等AI⼯具提⽰完成。
原理架构图
Visual Studio Code Remote-SSH扩展允许您在任何具有运⾏SSH服务器的远程计算机、虚拟机或容器上打开远程⽂件夹,并充分利⽤VS Code的功能集。链接到服务器后,您可以与远程⽂件系统上任何位置的⽂件和⽂件夹进⾏交互,⽽⽆需本地保存任何源代码。
扩展将在远程操作系统上安装VS Code Server。这让VS Code提供了本地质量的开发体验-包括完整的IntelliSense(代码感知)、代码导航和调试-⽆论您的代码托管在哪⾥。
流行的 C++ 包(库)管理器
包管理器 | 主要⽤途 | ⽀持平台 | 依赖解析 | CMake 支持情况 | 主要特点 |
---|---|---|---|---|---|
vcpkg | 跨平台 C++ 包 管理 | Windows, Linux, macOS | ⾃动解析 | 支持 | 微软官⽅⽀持,与 Visual Studio 深度集成 |
Conan | 跨平台 C++ 包 管理 | Windows, Linux, macOS | ⾃动解析 | 支持 | ⽀持⼆进制包,依赖管理 强大 |
Hunter | C++ 包管理 | Windows, Linux, macOS | ⾃动解析 | 支持 | 专为 CMake 设计,简单易 ⽤ |
FetchCont ent | CMake 内置 | Windows, Linux, macOS | ⼿动配置 | 支持 | CMake 3.11+ 内置,⽆需 外部⼯具 |
主流包管理器和编译器支持矩阵
包管理器 | MSVC | GCC | Clang | Intel ICC | Apple Clang | MinGW | 主要优势 |
---|---|---|---|---|---|---|---|
vcpkg | ✅ 优秀 | ✅ 完全 | ✅ 完全 | ✅ ⽀持 | ✅ ⽀持 | ✅ ⽀持 | Windows ⽣态 |
Conan | ✅ 完全 | ✅ 完全 | ✅ 完全 | ✅ 完全 | ✅ 完全 | ✅ 完全 | 跨平台最全⾯ |
Hunter | ✅ ⽀持 | ✅ 完全 | ✅ 完全 | ✅ ⽀持 | ✅ ⽀持 | ✅ ⽀持 | CMake ⽣态 |
FetchConte nt | ✅ ⽀持 | ✅ 完全 | ✅ 完全 | ✅ ⽀持 | ✅ ⽀持 | ✅ ⽀持 | CMake 内置 |
vcpkg 是微软开发的 C++ 包管理器,⽤于在 Windows Linux 和 macOS 上安装和管理 C++ 库。它提供了超过 2000 个开源库的预编译版本。
Conan 是⼀个现代化的 C++ 包管理器,⽀持跨平台依赖管理和⼆进制包分发。它提供了强⼤的依赖解析、版本管理和构建系统集成功能。
Hunter 是⼀个基于 CMake 的 C++ 包管理器,专注于为 CMake 项⽬提供简单、可靠的依赖管理。它通过 CMake 的 ExternalProject 模块⾃动下载、配置和构建依赖包。
FetchContent 是 CMake 3.11+ 内置的模块,⽤于在配置时下载和配置外部项⽬。它提供了⼀种简单、直接的⽅式来管理项⽬依赖,⽆需额外的包管理器。
主流C++商业级开发IDE
IDE | ⼚商 | Windows | Linux | macOS | 类型 | CMak e 支持 | 主要特点 |
---|---|---|---|---|---|---|---|
Visual Studio | Micros oft | ✅ | ❌ | ✅ | 免费/ 付费 | ✅ | 最强Windows C++开发,IntelliSense,调试器 |
CLion | JetBra ins | ✅ | ✅ | ✅ | 付费 | ✅ | 智能代码分析,重构,CMake⽀持,跨平台 |
Xcode | Apple | ❌ | ❌ | ✅ | 免费 | ✅ | macOS/iOS开发,Interface Builder,官⽅⼯具链 |
Visual Studio Code | Micros oft | ✅ | ✅ | ✅ | 免费 | ✅ | 轻量级,丰富插件,C/C++扩展, 调试⽀持 |
Cursor | Anysp here | ✅ | ✅ | ✅ | 付费 | ✅ | AI原⽣编程,Claude/GPT-4集成, 智能代码⽣成 |
主流商业级IDE-编译器⽀持矩阵
IDE | MSVC | GCC | Clang | Intel ICC | Apple Clang | MinGW | 主要平台 |
---|---|---|---|---|---|---|---|
Visual Studio | ✅ 原⽣ | ❌ | ✅ Clang-cl | ✅ ⼯具 集 | ❌ | ❌ | Windows |
CLion | ✅ 完全 | ✅ 完全 | ✅ 完全 | ✅ ⽀持 | ✅ 完全 | ✅ ⽀持 | 跨平台 |
Xcode | ❌ | ❌ | ✅ 完全 | ❌ | ✅ 原⽣ | ❌ | macOS |
VSCode | ✅ ⽀持 | ✅ 完全 | ✅ 完全 | ✅ ⽀持 | ✅ ⽀持 | ✅ ⽀持 | 跨平台 |
Cursor | ✅ ⽀持 | ✅ 完全 | ✅ 完全 | ✅ ⽀持 | ✅ ⽀持 | ✅ ⽀持 | 跨平台 |
IDE 解释
IDE(Integrated Development Environment)是集成开发环境,它将软件开发所需的各种⼯具集成在⼀个统⼀的界⾯中。
IDE的核心组件
1. 主要功能模块
2. 辅助功能
主流包管理器-IDE 支持矩阵
包管理器 | Visual Studio | CLion | Xcode | VSCode | Cursor |
---|---|---|---|---|---|
vcpkg | ✅ 原⽣ | ✅ ⽀持 | ✅ 命令⾏⽀持 | ✅ 扩展 | ✅ 扩展 |
Conan | ✅ 扩展 | ✅ ⽀持 | ✅ ⽀持 | ✅ 扩展 | ✅ 扩展 |
Hunter | ✅ ⽀持 | ✅ ⽀持 | ✅ ⽀持 | ✅ 扩展 | ✅ 扩展 |
FetchContent | ✅ ⽀持 | ✅ ⽀持 | ✅ ⽀持 | ✅ 扩展 | ✅ 扩展 |