当前位置: 首页 > news >正文

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.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 安装

shell
sudo apt install cmake

Step 2:验证安装:

安装完成后,可通过以下命令验证 CMake 是否安装成功以及查看其版本。

代码块
cmake --version
shell
cmake version 3.28.3
CMake 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

VS Code CMake 插件有以下2点好处:
  • 语法⾼亮和代码补全:对 CMakeLists.txt ⽂件提供语法⾼亮显⽰,使代码结构更加清晰易 读。同时,⽀持代码补全功能,当你输⼊ CMake 命令或变量时,插件会⾃动提⽰可能的选项,减少⼿动输⼊的错误和时间。

  • 智能分析和错误检查:能够对 CMakeLists.txt ⽂件进⾏智能分析,检查其中的语法错误和潜 在问题,并在编辑器中实时显⽰错误提⽰和警告信息,帮助你及时发现和解决问题。

安装步骤如下:

Step 0:打开 VS Code,点击左侧活动栏中的 扩展图标(或按 Ctrl+Shift+X )。

Step 1:在搜索框中输⼊ CMake ,我们选择安装以下4个插件:

  • CMake
  • CMake Tools
  • CMake Language Support
  • CMake IntelliSence

Step 2:挨个点击Install 按钮,安装完成并且成功之后如下图

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;
}
Step 2:新建文件-CMakeLists.txt

CMakeLists.txt

# 1 设置能运⾏此⼯程的cmake的最低版本

cmake_minimum_required(VERSION 3.18)

# 2 设置项⽬名称

project(helloWorld)

# 3 添加构建⽬标

add_executable(main main.cpp)

为什么需要设置最低的cmake版本?

        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

Step 4:编译&&链接

代码块

make

代码块

[ 50%] Building CXX object CMakeFiles/main.dir/main.cpp.o

[100%] Linking CXX executable main

[100%] Built target main

Step 5:运行程序

代码块

./hello

代码块

lixiaoshen@lixiaoshens-MacBook-Pro build % ./hello

hello world!

lixiaoshen@lixiaoshens-MacBook-Pro build %


二、CMake 命令行工具介绍

2.1 CMake构建流程图

本章节代码使⽤cmake-tools ⼯程代码
# 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;

}

Step 3:修改⽂件-CMakeLists.txt

代码块

# 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]

        可以使⽤install来本机安装,也可以使⽤cpack 把⼆进制或者动态库打包成压缩包的⽅式进⾏分发 和共享。如果cmake的配置⽂件CMakeLists.txt⾥包含CPack功能,则⽣成的makefile⾥也会包含 package 伪目标,可以使用make 来执行。

代码块

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 单步操作

我们创建⼀个新的 install_hello

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模块严格定义了各个平台下的默认的安装目录, 大多数场景应该严格遵守。

Step 2:新建文件-main.cpp

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)

Step 4:编译链接

代码块

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

Step 6:安装

代码块

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() 执行之后,CMake会⾃动创建以下变量,可在后续命令中使用:
变量描述
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 动态库/静态库的的版本号

为了验证以上内置变量,我们在本节的顶级CMakeLists.txt里新增加⼀些message函数打印下上面的自定义变量

代码块

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
rootroot因为执行上下文依然是A目录
CMAKE_CURRENT_LIST_
FILE
root/CMakeLists.txt
root/sub/sub.cmake
CMAKE_CURRENT_LIST_
DIR
rootroot/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

目标的种类

类型
创建命令产物例子 / 说明
EXECUTABLEadd_executablemain ,curl
STATICadd_library(... STATIC)libfoo.a, foo.lib
SHAREDadd_library(... SHARED)libfoo.so, foo.dll
MODULEadd_library(... MODULE)
插件:libplugin.so, 使⽤dlopen 运⾏时加载
OBJECTadd_library(... OBJECT)仅 .o/.obj,存在于内存,不⽣成库⽂件
INTERFACEadd_library(... INTERFACE)无库⽂件,携带使⽤要求
IMPORTED
add_library(... IMPORTED)
使⽤cmake 内存⽬标对象引⽤磁盘上的外
部构建产物
ALIASadd_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 参数传递给编译器的。

函数作用

        设置⼆进制⽬标的依赖库列表,相当于使⽤通⽤的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'

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 动态库(编译-链接-引⽤)

        在前⾯⼏节例⼦中,我们使⽤简单的例⼦演⽰了,如何制作,发布,查找和引⽤静态库,这节我们将开始学习如何制作⼀个动态库,并在项⽬内部引⽤制作的动态库。还是以静态库的数学库为例本节我们新建⼀个⼯程 my_math_shared。
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() 命令来查找和使⽤外部依赖库。他有两种查找模式:

模块模式(Module Mode)
  • 使⽤ find_package()的 MODULE 参数指定
  • 使⽤ CMake 内置的或者⾃⼰写的 Find<PackageName>.cmake ⽂件查找。
  • 适⽤于没有提供 CMake 配置⽂件的旧库。
配置模式(Config Mode)
  • 使⽤ 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 31 链接

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 支持的语法有:

  1. 字符串⽐较,⽐如:STREQUAL、STRLESS、STRGREATER等;
  2. 数值⽐较,⽐如:EQUAL、LESS、GREATER等;
  3. 布尔运算,AND、OR、NOT;
  4. 路径判断,⽐如:EXISTS、IS_DIRECTORY、IS_ABSOLUTE等;
  5. 使用小括号可以组合多个条件语句,⽐如:(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.cppjsontest.hfuzz.cppfuzz.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 跨平台示例

代码块

# 检查当前系统是否为 macOSAPPLE)、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)

# 若当前系统是 LinuxFreeBSD 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}")

# 设置Qt5cmake模块所在⽬录,如果不设置将使⽤系统提供的版本

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+ 内置,⽆需

外部⼯具

主流包管理器和编译器支持矩阵

包管理器MSVCGCCClang

Intel

ICC

Apple ClangMinGW主要优势
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-编译器⽀持矩阵

IDEMSVCGCCClangIntel ICCApple ClangMinGW主要平台

Visual

Studio

✅ 原⽣

Clang-cl

✅ ⼯具

Windows

CLion

✅ 完全✅ 完全✅ 完全✅ ⽀持✅ 完全✅ ⽀持跨平台

Xcode

✅ 完全✅ 原⽣macOS

VSCode

✅ ⽀持✅ 完全✅ 完全✅ ⽀持✅ ⽀持✅ ⽀持跨平台

Cursor

✅ ⽀持✅ 完全✅ 完全✅ ⽀持✅ ⽀持✅ ⽀持跨平台

IDE 解释

IDE(Integrated Development Environment)是集成开发环境,它将软件开发所需的各种⼯具集成在⼀个统⼀的界⾯中。

IDE的核心组件

1. 主要功能模块

2. 辅助功能

主流包管理器-IDE 支持矩阵

包管理器Visual StudioCLionXcodeVSCodeCursor
vcpkg✅ 原⽣✅ ⽀持✅ 命令⾏⽀持✅ 扩展✅ 扩展
Conan✅ 扩展✅ ⽀持✅ ⽀持✅ 扩展✅ 扩展
Hunter✅ ⽀持✅ ⽀持✅ ⽀持✅ 扩展✅ 扩展
FetchContent✅ ⽀持✅ ⽀持✅ ⽀持✅ 扩展✅ 扩展

文章转载自:

http://k4lPvMC1.xrrbj.cn
http://f0D4uzEc.xrrbj.cn
http://5Q6c0bay.xrrbj.cn
http://vgJP38Ru.xrrbj.cn
http://5NrWgMBE.xrrbj.cn
http://JmuBxek7.xrrbj.cn
http://GOy2wjhi.xrrbj.cn
http://w2h2wSgb.xrrbj.cn
http://6GNBfdpk.xrrbj.cn
http://rG5QMa1y.xrrbj.cn
http://IKrHtc9S.xrrbj.cn
http://BUmdXqdg.xrrbj.cn
http://MVkdPkSP.xrrbj.cn
http://ijA2kfxc.xrrbj.cn
http://j7YaNUaa.xrrbj.cn
http://WZlnmfQq.xrrbj.cn
http://jdpo1lqN.xrrbj.cn
http://R37tPa7E.xrrbj.cn
http://RHLJiPHu.xrrbj.cn
http://RTwyAWAI.xrrbj.cn
http://2z5A1q4C.xrrbj.cn
http://JYyOk3B5.xrrbj.cn
http://rrKyHiml.xrrbj.cn
http://JUAUvaM8.xrrbj.cn
http://L2KOvmgl.xrrbj.cn
http://zCx68rWQ.xrrbj.cn
http://8Pmc88zh.xrrbj.cn
http://Z599Iyye.xrrbj.cn
http://pW1kkIA1.xrrbj.cn
http://DarsxiUK.xrrbj.cn
http://www.dtcms.com/a/375566.html

相关文章:

  • 单北斗GNSS该如何在变形监测中发挥最大效能?
  • 大数据毕业设计-基于大数据的高考志愿填报推荐系统(高分计算机毕业设计选题·定制开发·真正大数据)
  • 分布式锁redis
  • Java学习之——“IO流“的进阶流之转换流的学习
  • git 如何直接拉去远程仓库的内容且忽略本地与远端不一致的commit
  • 每日一算:分发糖果
  • 神经算子学习
  • AI大模型入门1.1-python基础字符串代码
  • Tlias管理系统(多表查询-内连接外连接)
  • win11家庭版配置远程桌面
  • 8. LangChain4j + 提示词工程详细说明
  • ChatGPT大模型训练指南:如何借助动态代理IP提高训练效率
  • 利用git进行版本控制
  • 深入理解synchronized:从使用到原理的进阶指南
  • 第八章 矩阵按键实验
  • 【CSS 3D 实战】从零实现旋转立方体:理解 3D 空间的核心原理
  • C++互斥锁使用详解与案例分析
  • Python+DRVT 从外部调用 Revit:批量创建柱
  • Matlab机器人工具箱6.2 导入stl模型——用urdf文件描述
  • 网页设计模板 HTML源码网站模板下载
  • 南京大学计算机学院 智能软件工程导论 + Luciano Baresi 教授讲座
  • Rust/C/C++ 混合构建 - Buck2构建工具一探究竟
  • Drawnix:开源一体化白板工具,让你的创意无限流动!
  • stm32 链接脚本没有 .gcc_except_table 段也能支持 C++ 异常
  • K8S集群管理(4)
  • flutter TabBar 设置isScrollable 第一个有间距
  • 学习 Android (二十一) 学习 OpenCV (六)
  • Maven项目中修改公共依赖项目并发布到nexus供三方引用全流程示例
  • GD32VW553-IOT开发板移植适配openharmony
  • nuxt3在使用vue-echarts报错 document is not defined