CppCon 2018 学习:GIT, CMAKE, CONAN

提到的:

“THE MOST COMMON C++ TOOLSET”

  • VERSION CONTROL SYSTEM
  • BUILDING
  • PACKAGE MANAGEMENT

这些是 C++ 项目开发中最核心的工具链组成部分。下面我将逐一解释每部分的作用、常见工具,以及它们如何协同构建现代 C++ 项目。

1. VERSION CONTROL SYSTEM(版本控制系统)

用来追踪文件变化、管理协作开发、备份代码历史。

常用工具:

工具简介
Git最主流的分布式版本控制系统,用于本地和远程协作(如 GitHub、GitLab)
SVN老牌集中式版本控制系统,已不太常见

最佳实践:

  • 用 Git 管理源代码、构建脚本、配置文件
  • 使用 .gitignore 排除临时文件、构建目录
  • 每个新功能用一个分支(如 feature/foo
  • 使用 Pull Request(PR)做代码审查

2. BUILDING(构建系统)

.cpp.h 等源文件编译链接成可执行程序或库。

常用工具:

工具简介
CMake最主流跨平台构建系统,支持多编译器、自动生成构建文件(Makefile / Ninja / Visual Studio)
Make / GNU Make老牌 UNIX 构建工具,依赖 Makefile
Ninja更快的现代构建工具,常和 CMake 搭配
MSBuildWindows Visual Studio 的默认构建系统

示例:CMake 构建流程

mkdir build
cd build
cmake ..
cmake --build .

3. PACKAGE MANAGEMENT(包管理器)

用来下载、安装、管理 C++ 库和依赖项。解决“手动下载库、设置 include/lib”的麻烦。

常用工具:

工具简介
vcpkg微软出品,支持跨平台,最主流之一,支持 CMake 集成
Conan更强大,支持版本控制、构建配置、包上传等功能,适合大型项目
Hunter / CPM.cmakeCMake 模块级集成的包管理器(更轻量)

示例:用 vcpkg 安装库

./vcpkg install fmt
cmake .. -DCMAKE_TOOLCHAIN_FILE=path/to/vcpkg.cmake

结合起来:现代 C++ 项目的推荐结构

my_project/
├── CMakeLists.txt           # 构建定义
├── src/                     # 源文件
├── include/                 # 头文件
├── tests/                   # 单元测试
├── build/                   # 构建输出目录
├── .git/                    # Git 仓库
├── .gitignore
└── vcpkg.json / conanfile.txt  # 包依赖声明

总结:C++ 常用开发工具集

模块工具说明
版本控制Git管理代码版本、团队协作
构建CMake + Ninja/Make/MSBuild自动构建项目
包管理vcpkg / Conan管理第三方库依赖
编译器GCC / Clang / MSVC实际进行编译
这些工具协同使用,构成了现代 C++ 工程开发的基础工具链。

现代 C++ 最常用的开发工具集(THE MOST COMMON C++ TOOLSET)

目前业界主流推荐组合是:

类别工具说明
Version ControlGit无可争议的行业标准
BuildingCMake最广泛支持、跨平台、集成多构建系统
Package Manager(暂时无标准)生态分裂,但 Conan 是强有力的候选者

主要观点解释(你列出的那些 bullet points)

“Please do not partition our C++ development environment even more”

  • 意思是 C++ 社区的工具链已经过于碎片化(很多人用不同工具,比如 Makefile、Bazel、vcpkg、手动管理等),不希望再加剧这种分裂。
  • 建议尽量统一使用主流工具(Git + CMake + 一个主力包管理器)来提高协作效率和生态整合。

“Other tools may sometimes be better at one aspect but probably are worse at others”

  • 意思是:虽然一些构建系统、包管理器在某些方面可能比 CMake/Conan 强(比如速度、配置简洁等),但它们在其它方面(生态支持、可维护性、跨平台能力)可能较差。
  • 例如:Bazel 构建很快,语法简洁,但不易上手、缺少通用库支持。

“Until a strong game-changer appears on the market please use above tools”

  • 当前没有出现足够强大的新工具能全面替代 Git、CMake 等。
  • 所以除非出现“颠覆性”工具,建议继续使用 目前最成熟、最兼容、最通用的工具集

Conan:被视为最有希望成为 C++ 的主流包管理器

“Conan is a strong contender to become the C++ Package Manager”

这是对 C++ 生态中长期缺乏官方标准包管理器的回应。
Conan 的优势:

特性Conan 说明
支持版本控制明确依赖版本与构建配置
跨平台 / 跨编译器支持 MSVC, GCC, Clang 等多种配置
与 CMake 深度集成可以自动生成 CMake 工具链
支持私有仓库适合公司/企业内部使用
但目前还有一些阻力,比如:
  • 语法复杂度偏高
  • 社区生态还不如 npm / pip 成熟
  • 学习曲线相对陡峭

总结:你的笔记理解如下

C++ 最主流的开发工具集(当前最佳实践):
-  版本控制:Git 
-  构建系统:CMake 
-  包管理器:暂无统一标准,但 Conan 是最有力候选 
社区呼吁统一工具链,避免碎片化,除非有革命性工具出现,建议使用上述工具。

列出的是 C++ 项目中管理依赖(第三方库)的典型方式,出现在许多大型项目。

这些方法各有优缺点,理解它们可以帮助你选择适合你项目的依赖管理策略。

TYPICAL WAYS TO HANDLE DEPENDENCIES IN C++ PROJECTS

1. external/3rdparty/ 目录 + add_subdirectory()

把第三方库的源代码直接复制到你的项目中,然后用 add_subdirectory() 加进 CMake 构建图。

优点:

  • 简单、可控,离线也能构建
  • 不依赖外部工具
    缺点:
  • 手动更新很麻烦
  • 难以维护多个版本
  • 侵入式(把别人的代码塞你项目里)
    适用:
  • 小项目
  • 学习/教学用途
  • 稳定依赖,不常更新

2. Git Submodules + add_subdirectory()

用 Git submodule 管理外部库仓库,引入源码,再通过 CMake 构建。

优点:

  • 外部库保持原始结构
  • 可以独立更新、checkout 版本
    缺点:
  • Git submodule 本身复杂(init、update、版本同步)
  • 依赖项目的 CMake 配置必须支持被嵌入(有些不支持)
    适用:
  • 中等规模项目
  • 对版本有控制要求的团队开发

3. ExternalProject_Add() + add_subdirectory()

用 CMake 的 ExternalProject_Add() 指定外部源码的下载方式,然后在构建时拉取并集成。

优点:

  • 支持自动拉取、构建第三方项目
  • 不需要在源码里维护外部库
    缺点:
  • 编译/构建步骤复杂
  • 与主项目构建不共享 target,难以链接
  • 外部项目的安装方式要求统一
    适用:
  • 大型项目
  • 需要自动化集成但不想内嵌源码

4. 预先下载并安装依赖 + find_package()

要求开发者/CI 先安装所有依赖(系统或自定义路径),然后通过 find_package() 找到它们。

优点:

  • 符合 CMake 模式,清晰且传统
  • 依赖项目和主项目解耦
    缺点:
  • 需要你手动安装依赖(容易出错)
  • 跨平台兼容性差(每个平台装法不同)
    适用:
  • 系统库(如 Boost)
  • 公司/团队统一开发环境

5. 使用其他语言生态工具(如 Maven、NuGet 等)

使用 Java、.NET 等语言的包管理器(如 NuGet for MSVC,vcpkg 也类似),间接管理 C++ 依赖。

优点:

  • 如果已有现成工具链,方便复用
  • 在某些平台上更友好(如 NuGet + MSVC)
    缺点:
  • 不通用,跨平台麻烦
  • 与 C++ 本身生态割裂
    适用:
  • 混合项目(如 C++/CLI、C++ + .NET)
  • Windows-only 项目

6. 使用专门的 C++ 包管理器(如 Conan)

使用 Conan(或 vcpkg 等)来拉取、构建、管理依赖。

优点:

  • 管理灵活、支持多版本、平台和编译器
  • 可与 CMake 深度集成(Conan + CMake Toolchain)
  • 适合企业级使用,支持私有仓库
    缺点:
  • 有学习成本
  • 有时包缺失,需要自己构建
    适用:
  • 现代项目
  • 多平台构建
  • CI/CD 集成

总结对比表

#方法控制性易用性可维护性推荐场景
1手动源码 + add_subdirectory()简单教学、小项目
2Git submodule + add_subdirectory()中等一般中小型项目
3ExternalProject_Add()复杂一般自动拉库,隔离构建
4系统安装 + find_package()中等老项目、公司环境固定
5Maven / NuGet特定平台好用混合语言项目
6Conan / vcpkg现代现代企业级开发

问题是关于 使用 add_subdirectory() 管理 C++ 依赖(deps),并配合一个 Mermaid 图 来表达依赖关系:

uses
tests with
LibA
Boost_1_66
GTest_1_8_0
classDiagramclass LibA class Boost_1_66 class GTest_1_8_0LibA o-- Boost_1_66 : usesLibA ..> GTest_1_8_0 : tests with

你这个 Mermaid 类图很清晰地表达了:

  • LibA 聚合o--)了 Boost 1.66(即 LibA 使用 Boost,但不拥有生命周期)
  • LibA 通过实现/依赖(..>)了 GTest 1.8.0(即 LibA 依赖 GTest 来做测试)

关于 add_subdirectory() 用于依赖管理的理解:

1. add_subdirectory() 是 CMake 中的一个命令,用来:

  • 把一个子目录(通常包含一个 CMakeLists.txt加入当前构建树
  • 这样子目录的目标(target)、变量等都会被纳入当前项目的构建管理

2. 对于第三方依赖的处理:

  • 如果你把第三方库的源码(比如 GTest)放在你项目某个子目录下(external/gtest),并且它有自己的 CMakeLists.txt,你就可以通过 add_subdirectory(external/gtest) 来把它编译进来。
  • 对于像 GTest 这样原生支持 CMake 的库,add_subdirectory() 是一种很方便的集成方式。
  • 但是对于 Boost 这种库(尤其老版本 1.66),它并不是完整 CMake 项目,通常不适合用 add_subdirectory(),更常用的是系统安装或通过 find_package(Boost) 来使用。

3. 结合你的类图和依赖关系:

  • LibABoost聚合依赖,建议用 find_package() 或包管理工具(Conan、vcpkg)来集成 Boost。
  • LibAGTest测试依赖,如果你放了 GTest 的源码,可以用 add_subdirectory()

总结

依赖库add_subdirectory() 管理吗?推荐做法
Boost 1.66使用 find_package() 或包管理
GTest 1.8.0直接 add_subdirectory()

以下是基于您提供的图片生成的 Mermaid 类图代码,展示 LibALibB 及其依赖关系:

这张类图很好地表达了两个库 LibALibB 与它们依赖的第三方库版本之间的关系。针对你问的“add_subdirectory() FOR DEPS?”,我帮你理解和总结:

你的 Mermaid 类图解释

classDiagramclass LibA class LibB LibA *--> Boost 1.66LibA ..> GTest 1.8.0LibB *--> Boost 1.57LibB --> OpenSSL 1.0.2LibB ..> GTest 1.7.0
classDiagramclass LibA class LibB LibA *--> Boost 1.66LibA ..> GTest 1.8.0LibB *--> Boost 1.57LibB --> OpenSSL 1.0.2LibB ..> GTest 1.7.0
  • *--> 表示 组合(强拥有),即 LibALibB 都强依赖特定版本的 Boost。
  • --> 表示 关联/依赖LibB 依赖 OpenSSL。
  • ..> 表示 实现/测试依赖LibALibB 都用不同版本的 GTest 测试。

add_subdirectory() 用于依赖管理的理解

什么时候用 add_subdirectory()

  • 你拥有第三方库的源码,并且该源码是 CMake 项目(带有 CMakeLists.txt),
  • 你希望直接把第三方库编译到你的项目里,不用单独安装或包管理,
  • 这时可以用 add_subdirectory() 把依赖库源码目录加入构建。

针对你的依赖

依赖是否适合用 add_subdirectory()理由
Boost 1.66通常不适合Boost 通常是预编译库或系统库,用 find_package() 更合适
Boost 1.57同上同上
OpenSSL 1.0.2通常不适合也是系统库,或者用包管理安装更方便
GTest 1.8.0适合GTest 官方支持 CMake,源码常用 add_subdirectory()
GTest 1.7.0适合同上

推荐的依赖管理方式结合你的图

  • Boost 和 OpenSSL 用系统安装的库或者包管理器(Conan、vcpkg等),然后用 find_package() 找到它们。
  • GTest 放源码子目录,用 add_subdirectory() 编译。

举例说明

# CMakeLists.txt
# 找系统或包管理的 Boost
find_package(Boost 1.66 REQUIRED)  # for LibA
find_package(Boost 1.57 REQUIRED)  # for LibB
find_package(OpenSSL 1.0.2 REQUIRED)
# 添加 GTest 源码子目录(假设源码在 external/gtest)
add_subdirectory(external/gtest)
# 定义 LibA
add_library(LibA ...)
target_link_libraries(LibA PRIVATE Boost::boost gtest)
# 定义 LibB
add_library(LibB ...)
target_link_libraries(LibB PRIVATE Boost::boost OpenSSL::SSL OpenSSL::Crypto gtest)

总结

  • add_subdirectory() 适合用来管理支持 CMake 的第三方库源码(如 GTest)
  • 对于系统或预编译的库(Boost、OpenSSL),用 find_package() + 包管理更常见。
  • 你的图很好地表达了依赖关系和库版本,结合合适的 以下是基于您提供的图片生成的 Mermaid 类图代码,展示 App 及其依赖关系:
classDiagramclass App class LibA class GTest_Latestclass OpenSSL_1_1_0class LibBApp --> LibAApp --> GTest_LatestApp --> OpenSSL_1_1_0App --> LibBLibA o--> Boost 1.66LibA ..> GTest 1.8.0LibB o--> Boost 1.57LibB --> OpenSSL 1.0.2LibB ..> GTest 1.7.0

完整的示例 CMakeLists.txt,重点展示 LibA、LibB 和它们的依赖如何链接,以及 App 如何链接 LibA、LibB 和第三方库。
假设目录结构如下:

/project/LibACMakeLists.txt.../LibBCMakeLists.txt.../external/gtestCMakeLists.txtCMakeLists.txt   <-- 顶层文件

顶层 CMakeLists.txt 示例

cmake_minimum_required(VERSION 3.15)
project(MyApp)
# 查找系统/包管理的 Boost 和 OpenSSL
find_package(Boost 1.66 REQUIRED)    # 用于 LibA
find_package(Boost 1.57 REQUIRED)    # 用于 LibB
find_package(OpenSSL 1.1 REQUIRED)   # 用于 LibB 和 App
# 添加子目录,构建 LibA, LibB 和 GTest
add_subdirectory(LibA)
add_subdirectory(LibB)
add_subdirectory(external/gtest)
# 构建应用程序
add_executable(App src/main.cpp)
# 链接 App 依赖
target_link_libraries(App PRIVATE LibA LibB OpenSSL::SSL OpenSSL::Crypto gtest)

LibA/CMakeLists.txt 示例

add_library(LibA src/liba.cpp)
# 链接 Boost 1.66(假设 find_package 找到的 target 是 Boost::boost)
target_link_libraries(LibA PUBLIC Boost::boost)
# 添加测试依赖 GTest(假设 GTest target 是 gtest)
target_link_libraries(LibA PRIVATE gtest)

LibB/CMakeLists.txt 示例

add_library(LibB src/libb.cpp)
# 链接 Boost 1.57 和 OpenSSL
target_link_libraries(LibB PUBLIC Boost::boost OpenSSL::SSL OpenSSL::Crypto)
# 添加测试依赖 GTest
target_link_libraries(LibB PRIVATE gtest)

说明

  • target_link_libraries(LibA PUBLIC Boost::boost) 表示 LibA 对 Boost 的依赖对它的用户(比如 App)也是可见的。
  • target_link_libraries(LibA PRIVATE gtest) 表示 GTest 只在 LibA 内部用于测试,不传播给 App。
  • 顶层 App 链接了 LibA, LibB,以及 OpenSSL 和 gtest(测试用库)。
    这样写,整个依赖链和第三方库都清晰管理,避免重复链接问题。

CMake 不是构建系统,而是构建系统生成器

  • CMake 的任务是根据你的配置文件(CMakeLists.txt)生成适合你平台的构建系统文件。
  • 例如:
    • 在 Linux 下,它生成 Makefile 或 Ninja 文件
    • 在 Windows 下,它生成 Visual Studio 工程文件

跨平台 C++ 构建生成工具

  • 你写的 CMake 配置可以在多平台、多种编译器间复用
  • 只需要改动少量参数,方便跨平台开发和构建

CMake 的典型工作流程示例

Linux 下用 GCC

cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=~/.local  # 配置项目
cmake --build .                                                    # 编译
ctest -VV                                                         # 运行测试
cmake --build . --target install                                  # 安装

Windows 下用 Visual Studio

cmake .. -G "Visual Studio 15 2017 Win64" -DCMAKE_INSTALL_PREFIX=~/.local  # 生成 VS 工程
cmake --build . --config Release                                           # 编译 Release 版本
ctest -VV -C Release                                                      # 运行测试
cmake --build . --target install --config Release                         # 安装 Release 版本

总结

  • 先用 cmake 命令生成构建系统
  • cmake --build 或相应平台的构建工具去构建项目
  • ctest 运行测试
  • 再用 cmake --build --target install 安装项目

传统用法中管理编译选项(build flags)遇到的问题,并引出现代 CMake 的核心思想。帮你总结理解一下:

传统管理编译选项的问题

  • Build flags 不可扩展
    你对某个库或组件设置的公共编译选项(比如 -Wall-O2-std=c++17)必须一层层向上层传递给依赖它的所有组件和最终应用。
    这在大型项目里会很难维护。
  • 冲突难解决
    不同目标(target)可能需要不同甚至冲突的编译选项,统一管理非常麻烦。

Modern CMake 的解决方案

  • 以 Target(目标)为中心管理属性
    • 目标(target)是指 add_library()add_executable() 创建的具体模块。
    • 现代 CMake 建议把所有编译选项、包含目录、链接库等信息绑定到对应 target 的属性上。
  • 属性(Properties)管理依赖和编译设置
    • 每个 target 可以独立定义自己的编译选项、包含目录、依赖库等,且可以控制传递规则(PRIVATEPUBLICINTERFACE)。
    • 这样更灵活,也方便依赖管理和维护。

总结

  • 不要用全局变量(如 CMAKE_CXX_FLAGS)来设置编译选项。
  • 要给每个 target 设置自己的编译选项和属性。
  • 这样能让项目更模块化、可维护,解决冲突问题。

介绍 CMake 中的 目标类型(target types),也就是 add_executableadd_library 支持创建的不同目标类型。帮你梳理理解:

CMake 常见的目标类型(Target Types)

1. 可执行文件 (EXECUTABLE)

add_executable(MyApp main.cpp)
  • 生成可执行程序。
  • 最终产物是一个程序文件,比如 Linux 下的 MyApp

2. 共享库 (SHARED LIBRARIES)

add_library(MySharedLib SHARED lib.cpp)
  • 动态链接库,运行时加载。
  • Linux 下生成 .so,Windows 下生成 .dll

3. 静态库 (STATIC LIBRARIES)

add_library(MyStaticLib STATIC lib.cpp)
  • 静态链接库,编译时直接打包进可执行文件。
  • Linux 下生成 .a,Windows 下生成 .lib

4. 对象库 (OBJECT LIBRARIES)

add_library(MyObjLib OBJECT obj1.cpp obj2.cpp)
  • 不生成库文件,生成目标文件集合 (.o.obj)。
  • 这些对象文件可以被其他目标(静态库、共享库、可执行文件)重用。
  • 方便代码复用,但不会直接链接。

5. 接口库 (INTERFACE LIBRARIES)

add_library(MyInterfaceLib INTERFACE)
  • 不产生任何编译产物(没有源文件)。
  • 主要用来传递编译选项、包含目录、编译定义等给依赖它的目标。
  • 类似“虚拟目标”,用来管理接口依赖。

6. 别名库 (ALIAS LIBRARIES)

add_library(MyAliasLib ALIAS RealLib)
  • 给已有目标起一个别名。
  • 方便引用,特别是在大型项目或跨目录使用时。
  • 不生成新的目标,只是引用已有的。

7. 导入库 (IMPORTED LIBRARIES)

add_library(ExternalLib IMPORTED GLOBAL)
  • 用来声明外部预编译好的库(比如第三方库)。
  • 不由本项目构建,而是外部已有。
  • 你可以给它设置位置、包含目录、链接选项等,供项目引用。

总结

目标类型是否生成产物用途
EXECUTABLE生成可执行程序
SHARED生成动态库 (.so/.dll)
STATIC生成静态库 (.a/.lib)
OBJECT生成目标文件集合,用于复用
INTERFACE用于传递接口信息,无代码
ALIAS给目标起别名
IMPORTED声明外部已有的库

CMake 中 add_library() 没有指定库类型时的默认行为。我们来详细拆解理解:

add_library() 没有指定类型时怎么办?

如果你写:

add_library(MyLib lib.cpp)

你没有写 STATICSHAREDOBJECTINTERFACE 这些类型,这时 CMake 怎么办?

关键点:取决于变量 BUILD_SHARED_LIBS

  • CMake 有一个变量叫 BUILD_SHARED_LIBS,默认是 OFF
  • 如果你没有显式指定类型,CMake 会这样判断:
    | BUILD_SHARED_LIBS 值 | 默认类型 |
    | --------------------- | ------------- |
    | ON | SHARED(共享库) |
    | OFF(默认) | STATIC(静态库) |

示例

默认情况下(静态库):

# 没有设置 BUILD_SHARED_LIBS
add_library(MyLib lib.cpp)  # 实际等价于 add_library(MyLib STATIC lib.cpp)

如果你这样设置:

set(BUILD_SHARED_LIBS ON)   # 全局开启共享库构建
add_library(MyLib lib.cpp)  # 实际等价于 add_library(MyLib SHARED lib.cpp)

误区

这个行为 只影响没有明确指定类型的库,一旦你写了:

add_library(MyLib STATIC lib.cpp)

BUILD_SHARED_LIBS 就不会影响这个库了。

总结

  • add_library(MyLib lib.cpp) 没有指定类型时,CMake 会参考 BUILD_SHARED_LIBS 的值:
    • ON → 构建为动态库
    • OFF → 构建为静态库(默认)
  • 推荐做法
  • 对于每个库都明确指定类型,避免构建行为不一致或依赖外部变量。

CMake 现代用法讲解,核心是理解 target_link_libraries() 的作用域关键字(PRIVATE / PUBLIC / INTERFACE) 以及它们在依赖传播中的含义。

我帮你详细解释一下,确保你理解 现代 CMake 的依赖传播模型(MODULAR DESIGN)

现代 CMake 的依赖传播方式(自 CMake 2.8.12 起)

target_link_libraries(<target> PRIVATE|PUBLIC|INTERFACE ...)

这是现代 CMake 的核心命令,用于将依赖项添加到一个目标上。它不仅是“链接库”,还能控制依赖是否传递(transitive)。

三种依赖作用域的区别

作用域对我有用?()对依赖我的人有用?(dependers)用于什么场景?
PRIVATE是的只在当前目标中使用,比如实现细节库、内部工具
PUBLIC是的是的当前目标用,依赖它的人也要用,比如头文件库
INTERFACE是的自己不使用,仅是为别人传递依赖,比如纯头文件库

示例 1:普通库依赖实现细节

target_link_libraries(MyLib PRIVATE zlib)
  • MyLib 使用 zlib
  • 依赖 MyLib 的其他目标不会知道或需要 zlib

示例 2:MyLib 使用 Boost 的头文件库,且用户也必须包含

target_link_libraries(MyLib PUBLIC Boost::headers)
  • MyLib 需要 Boost
  • 依赖 MyLib 的其他目标也会自动获得 Boost 的包含路径和编译选项

示例 3:MyHeaderOnlyLib 是一个纯头文件库,自己不编译但别人需要

add_library(MyHeaderOnlyLib INTERFACE)
target_include_directories(MyHeaderOnlyLib INTERFACE include/)
  • INTERFACE 库不能编译,但可以给别人传递包含路径
  • 所以它适用于 header-only 项目

总结重点句式

  • PRIVATE:我用,你别管(不向下传播)
  • PUBLIC:我用,你也得用(向下传播)
  • INTERFACE:我不用,你用就行(传给 dependers)

最重要的一句话

Modern CMake 是关于 target 和 property 的管理,PUBLIC 和 INTERFACE 是 可传递依赖(transitive dependencies),而 PRIVATE 不是。

现代 CMake 在物理设计(physical design)层面带来的转变,以下是逐条解释,帮助你理解:

PHYSICAL DESIGN: MODERN CMAKE

“More than a linking diagram”

  • 现代 CMake 不只是“谁链接谁”的图(如早期 Makefile 那种)
  • 它捕捉的是逻辑依赖:谁真的需要谁的头文件、编译选项、定义等

“Logical dependencies included”

  • CMake 中的 target_link_libraries() 不只是“链接”
  • 它还传播头文件路径、宏定义、编译选项等(如果用 PUBLICINTERFACE

“PRIVATE and PUBLIC dependencies make a difference”

  • 如前所述:
  • PRIVATE: 自己用,但不传下去(不会污染依赖方)
  • PUBLIC: 自己用,也传下去(接口依赖)
  • 它体现了清晰的模块边界,避免“编译污染”

“Now we see real design problems…”

  • 当你采用 Modern CMake 模式后,依赖链就变成了设计表达
  • 如果你的模块之间有过度耦合、不清晰的接口,会在构建结构中直接暴露出来

NO CYCLIC PHYSICAL DEPENDENCIES!

物理设计中不允许循环依赖!

  • CMake 的依赖结构是有向无环图(DAG)
  • A → B → C → A 是非法的
  • 如果你在物理层面(模块间)形成循环,说明你的设计本身就有问题

现代 CMake 依赖结构本身能揭示架构缺陷(模块滥用、设计不清晰)

ALIAS TARGETS(别名目标)

add_library(MyLibrary lib_source.cpp)
add_library(MyCompany::MyLibrary ALIAS MyLibrary)

作用:

  • MyLibrary 是一个普通目标
  • MyCompany::MyLibrary 是一个命名空间别名
  • 让你的依赖更像现代语言的模块导入,例如 C++ 的 std::vector

好处:

好处说明
命名空间结构避免全局名字冲突,体现所属(如公司/库)
更易维护可统一更换内部实现,只要别名不变
跨项目清晰适合安装导出的库(比如用 install(EXPORT) 时)

总结:现代 CMake 带来什么?

特性意义
目标是核心(Targets)不再是全局变量控制一切
依赖是语义化的(PUBLIC/PRIVATE/INTERFACE)表达真实的逻辑接口依赖
没有循环依赖构建系统暴露架构问题
别名目标体现模块设计与命名规范
现代 CMake 变成了一个模块化构建表达语言,不仅是构建工具,它让你的构建结构直接反映了你的设计结构。

内容集中讲的是 Modern CMake 的两大核心高级特性

一、ALIAS TARGETS(别名目标)

示例:

add_library(MyLibrary lib_source.cpp)
add_library(MyCompany::MyLibrary ALIAS MyLibrary)
target_link_libraries(MyLibraryTestPUBLICMyCompany::MyLibraryGTest::Main
)

目的和优点:

解释
模块命名空间MyCompany::MyLibrary 表达清晰层级结构,像 std::vector 一样规范
统一接口find_package() 兼容:像 find_package(fmt) 给你 fmt::fmt,你也可以提供 MyCompany::MyLibrary
防止拼写错误:: 强制语法,不能用 MyCompany:: 作为目标,这种结构只能别名已有 target,错了就编译错误
适用于安装和导出目标更适合跨项目复用,如通过 install(EXPORT ...) 导出别名给下游用

二、GENERATOR EXPRESSIONS(生成器表达式)

概念:

  • 写在 $<...> 里的表达式,不是立即求值
  • 在生成构建系统时 由 CMake 评估的
  • 常用于按平台、配置、变量值等条件切换行为

BAD 示例(传统方式):

if(CMAKE_BUILD_TYPE STREQUAL DEBUG)target_sources(hello PRIVATE helper_debug.cpp)
else()target_sources(hello PRIVATE helper_release.cpp)
endif()

问题:

  • CMAKE_BUILD_TYPE 不是可靠来源(不适用于多配置生成器如 Visual Studio)
  • 无法切换构建类型后自动改变行为(必须重新配置)

GOOD 示例(生成器表达式):

add_executable(hello main.cpp$<IF:$<CONFIG:Debug>:helper_debug.cpp,helper_release.cpp>
)

优点:

  • CONFIG:Debug 检测构建配置(支持 VS/Xcode 多配置构建)
  • 表达式在构建系统生成时才判断,不影响配置时的 CMake 脚本流程
  • 可移植、更声明式、更适合现代项目

示例:使用 VERBOSE 控制宏定义

target_compile_definitions(foo PRIVATE"VERBOSITY=$<IF:$<BOOL:${VERBOSE}>,30,10>"
)
  • 如果变量 VERBOSE 为真,则定义 VERBOSITY=30,否则是 10
  • 这样可以在不写 if() 的情况下控制条件编译宏

三、BUILD_INTERFACE vs INSTALL_INTERFACE

用法示例:

target_include_directories(mylibINTERFACE$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>$<INSTALL_INTERFACE:include>
)
表达式用途
BUILD_INTERFACE当前项目被构建时用的路径(如源代码 include/)
INSTALL_INTERFACE安装后导出给别人的路径(如 CMAKE_INSTALL_PREFIX/include
意义:
  • 避免安装目标引用本地源码路径
  • 让构建时和安装后的 include 目录各自独立、正确

总结

特性说明
ALIAS TARGETS定义命名空间别名,统一目标命名,便于导出
GENERATOR EXPRESSIONS生成阶段判断条件,替代 if(),适配多构建配置
NEVER USE CMAKE_BUILD_TYPE in if()多配置构建器无法使用该变量正确判断
BUILD/INSTALL INTERFACE精准区分构建时 vs 安装后的接口信息,防止路径污染

Modern CMake 的库设计最佳实践(MODERN LIBRARY EXAMPLE) 的详细说明,重点在于:

1. GENERATOR EXPRESSIONS 用于接口路径管理

target_include_directories(Foo PUBLIC$<BUILD_INTERFACE:${Foo_BINARY_DIR}/include>$<BUILD_INTERFACE:${Foo_SOURCE_DIR}/include>$<INSTALL_INTERFACE:include>)

理解:

表达式含义
$<BUILD_INTERFACE:...>表示:构建当前项目时 应该使用的 include 路径(源码路径、构建目录等)
$<INSTALL_INTERFACE:...>表示:安装之后被别人使用时 暴露的路径(一般是 install/include
这是为了解决一个关键问题:

构建时我可以包含本地源文件夹,但安装后不应该暴露它!

2. MODERN CMake 库项目结构

这是一套推荐写法,是现代 CMake 项目模板的基础。

示例完整解释:

cmake_minimum_required(VERSION 3.8)
project(MyLibrary VERSION 0.0.1) # 避免自定义变量在这里
  • 避免在 project() 中添加自定义变量,比如不要写 LANGUAGES CXX C 冗余参数。
  • 推荐只写名称和版本,简洁明了。

依赖查找:

find_package(Foo 1.0 REQUIRED)
  • 使用 Foo::Foo 的方式链接依赖库
  • target_link_libraries() 兼容,便于封装模块化依赖

添加库:

add_library(MyLibrary lib_source.cpp lib_source.h include/MyLibrary/lib_header.h)
  • 一个真实的源文件库
  • 包含 .cpp.h,同时列出头文件方便 IDE 识别

设置 C++ 标准:

target_compile_features(MyLibrary PUBLIC cxx_std_17)
  • 设置为 C++17
  • PUBLIC 表示:依赖 MyLibrary 的项目也要使用 C++17

设置头文件路径:

target_include_directories(MyLibrary PUBLIC$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>$<INSTALL_INTERFACE:include>)
  • 构建时用项目自身路径
  • 安装后只暴露 include/ 路径
  • 避免把私有源码路径“泄露”出去

链接依赖:

target_link_libraries(MyLibrary PRIVATE Foo::Foo)
  • PRIVATE 表示:MyLibrary 自己用 Foo,但依赖 MyLibrary 的人不需要关心 Foo

创建别名目标(ALIAS):

add_library(MyCompany::MyLibrary ALIAS MyLibrary)
  • 给库设置命名空间别名
  • 为安装导出、find_package 使用提供统一接口

3. 避免在 project() 中加自定义变量(推荐写法)

project(MyLibrary VERSION 0.0.1)
  • 推荐只传名称 + 版本号
  • 不建议在 project() 中混入路径、自定义宏、toolchain 等
  • 现代 CMake 项目建议用 目标属性 控制项目行为,而非全局变量

总结:现代 CMake 库设计最佳实践

原则说明
使用 Targets 管理构建不再用全局变量
分清 PRIVATE / PUBLIC / INTERFACE明确依赖的传播性
Generator Expressions 控制构建/安装路径避免路径污染
使用 命名空间 ALIAS模块化设计,统一接口命名
安装友好,find_package 兼容便于重用和分发
避免 if(CMAKE_BUILD_TYPE)$<CONFIG:...> 替代

现代 CMake 项目的 文件组织结构(FILES ORGANIZATION),目的是让项目结构清晰、模块解耦、易于维护和安装。它强调将源码、测试和顶层配置分开处理,每一部分职责明确。

我来帮你完整地理解和总结这一结构的设计思想:

典型目录结构(现代 CMake 项目)

MyLibrary/
├── CMakeLists.txt          <-- 项目入口(顶层)
├── src/
│   ├── CMakeLists.txt      <-- 只负责库构建和安装
│   └── my_library.cpp/hpp  <-- 库源码
├── test/
│   ├── CMakeLists.txt      <-- 只负责测试目标
│   └── unit_tests.cpp
└── include/└── MyLibrary/└── lib_header.h    <-- 公共头文件

各部分职责说明

顶层 CMakeLists.txt(项目入口)

“Simple project wrapper / Entry point for development”

  • 设置项目名称和版本
  • 添加编译选项(比如 CMAKE_CXX_STANDARDBUILD_SHARED_LIBS
  • 添加子目录:add_subdirectory(src)add_subdirectory(test)
  • 不直接定义库或测试逻辑

src/CMakeLists.txt(只负责库)

“Standalone library definition and installation”

“Does not change compiler’s warnings!”

  • 添加库(add_library(...)
  • 设置接口、包含路径、版本信息、安装规则
  • 不负责控制编译警告(应由顶层或 toolchain 设置)
    保持纯净,这个 CMake 文件应是可以被其他项目 add_subdirectory()add_external_project() 使用的!

test/CMakeLists.txt(只负责测试)

“Standalone unit tests definition”

  • 查找或引入测试框架(如 GTest)
  • 添加测试目标:add_executable(MyTests ...)
  • 链接库:target_link_libraries(MyTests MyLibrary GTest::Main)
  • 使用 enable_testing() + add_test() 注册测试

示例:顶层 CMakeLists.txt

cmake_minimum_required(VERSION 3.15)
project(MyLibrary VERSION 1.0 LANGUAGES CXX)
add_subdirectory(src)
add_subdirectory(test)

示例:src/CMakeLists.txt

add_library(MyLibrary my_library.cpp)
target_include_directories(MyLibraryPUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../include>$<INSTALL_INTERFACE:include>
)
target_compile_features(MyLibrary PUBLIC cxx_std_17)
# 安装规则
install(TARGETS MyLibrary EXPORT MyLibraryTargets)
install(DIRECTORY ../include/ DESTINATION include)
# 可选:命名空间别名
add_library(MyCompany::MyLibrary ALIAS MyLibrary)

示例:test/CMakeLists.txt

enable_testing()
add_executable(MyLibraryTests unit_tests.cpp)
target_link_libraries(MyLibraryTests PRIVATE MyLibrary GTest::Main)
add_test(NAME UnitTests COMMAND MyLibraryTests)

总结

模块位置作用与原则
/src构建库本身,专注模块定义和安装,不修改全局选项
/test定义测试目标,只负责测试
顶层 CMakeLists项目入口,只做配置和引导,不定义目标

现代 CMake 鼓励模块化、清晰分工、无全局污染 —— 这也是可维护性和可复用性的关键。

Modern CMake 项目中如何组织“开发入口(wrapper)”与“测试目标”。下面是完整的理解与总结:

WRAPPER: LOCAL DEVELOPMENT ENTRY POINT

这是指顶层 CMakeLists.txt 的职责,它作为 项目开发的入口点,方便本地构建、运行测试、使用 IDE 等。

示例:

cmake_minimum_required(VERSION 3.5)
project(MyLibrary)
# 添加主库代码(不直接定义库)
add_subdirectory(src)
# 添加单元测试(可选)
enable_testing()
add_subdirectory(test)

理解要点:

项目顶层职责描述
项目入口提供给开发者(或 IDE)一个统一的构建入口
集成 src/test 子模块add_subdirectory() 将各模块构建图统一
不需要安装库也能测试本地开发时不需要 make install,直接构建测试
IDE 兼容性好CLion、Visual Studio 可识别该项目入口作为工作空间

MODERN LIBRARY USAGE: TESTS

这是你在 /test/CMakeLists.txt 中组织测试的方式。即使库被安装到系统中,测试代码也能独立存在并运行

示例:test/CMakeLists.txt

cmake_minimum_required(VERSION 3.8)
project(MyLibraryTests)
enable_testing()
find_package(GTest MODULE REQUIRED)
# 如果本地开发,MyLibrary 已存在;如果只在测试项目中构建,需要通过 find_package 找到安装版本
if(NOT TARGET MyCompany::MyLibrary)find_package(MyLibrary CONFIG REQUIRED)
endif()
add_executable(MyLibraryTests tests_source.cpp)
target_link_libraries(MyLibraryTests PRIVATEMyCompany::MyLibraryGTest::Main
)
add_test(NAME MyLibrary.UnitTests COMMAND MyLibraryTests)

理解要点:

解释
if(NOT TARGET ...)支持两种方式:本地源构建(直接 add_subdirectory(src))或使用安装包(find_package
add_test()ctest 可以自动执行测试用例
使用命名空间 MyCompany::MyLibraryfind_package() 的 alias 兼容,保持一致接口

总结一句话:

现代 CMake 项目将“库构建”、“测试逻辑”和“开发入口”清晰分离,通过顶层 wrapper 汇总,而测试模块具备独立运行能力,无需依赖安装。

构建 & 测试流程简要回顾

mkdir build && cd build
cmake ..
cmake --build .
ctest -V       # 运行测试

COMPILED, BUILT, TESTED — Done?

是的,这是现代 CMake 项目开发的基本完整流程:

  • 本地构建:
  • 测试验证:
  • 不需要安装库就能测试:
  • 可迁移到 CI/CD:
  • 可被 find_package() 使用:

现在正在学习 如何使用 CMake 正确导出(export)你的库接口,也就是说:

让别人可以通过 find_package(MyLibrary) 使用你的库

这正是现代 CMake 项目发布和复用的关键部分。

目标:EXPORT YOUR LIBRARY INTERFACE

你希望做什么?

MyLibrary 变成一个可以被别的项目使用的“包”(package):

  • 安装 .so / .a / .dll 文件
  • 安装头文件
  • 安装导出目标信息(MyLibraryTargets.cmake
  • 提供 MyLibraryConfig.cmake(+版本)
  • 支持 find_package(MyLibrary CONFIG REQUIRED) 调用

分步解释你笔记中的 CMake 脚本

基础定义

cmake_minimum_required(VERSION 3.8)
project(MyLibrary VERSION 0.0.1)
find_package(Foo 1.0 REQUIRED)
add_library(MyLibrary lib_source.cpp lib_source.h include/MyLibrary/lib_header.h)

这是定义你的库,并链接依赖 Foo(可能是别人的库)

安装目标(库)

install(TARGETS MyLibrary EXPORT MyLibraryTargetsLIBRARY DESTINATION libARCHIVE DESTINATION libRUNTIME DESTINATION binINCLUDES DESTINATION include
)

这一段的意思是安装你的库产物(.a, .so, .dll)以及包含头文件路径信息。

安装目标导出文件(targets.cmake)

install(EXPORT MyLibraryTargetsDESTINATION lib/cmake/MyLibraryFILE MyLibraryTargets.cmakeNAMESPACE MyCompany::
)

这会生成 MyLibraryTargets.cmake,它保存 MyCompany::MyLibrary 这个 target 的元信息。

依赖方可以通过 find_package(MyLibrary) 加载它。

安装头文件目录

install(DIRECTORY include/MyLibrary DESTINATION include)

把你的公共头文件安装到系统或指定安装目录。

创建 MyLibraryConfigVersion.cmake

include(CMakePackageConfigHelpers)
write_basic_package_version_file(MyLibraryConfigVersion.cmakeCOMPATIBILITY SameMajorVersion
)

自动生成 MyLibraryConfigVersion.cmake 文件,用于支持 find_package(MyLibrary VERSION ...) 时的版本检查。

安装配置文件 + 版本文件

install(FILESMyLibraryConfig.cmake${CMAKE_CURRENT_BINARY_DIR}/MyLibraryConfigVersion.cmakeDESTINATION lib/cmake/MyLibrary
)

这一段把你的 *.cmake 文件(配置+版本)放到安装路径下,让 find_package() 能找到。

MyLibraryConfig.cmake 的内容(必须手写!)

include(CMakeFindDependencyMacro)
find_dependency(Foo 1.0)
include("${CMAKE_CURRENT_LIST_DIR}/MyLibraryTargets.cmake")

解释:

  • 告诉 find_package(MyLibrary) 时:
    • 先找 Foo(你的依赖)
    • 然后加载导出的 MyLibraryTargets.cmake,即注册 MyCompany::MyLibrary

总结:完整导出流程

步骤作用
install(TARGETS ...)安装库文件和头文件
install(EXPORT ...)导出 target 接口
write_basic_package_version_file()生成版本控制文件
install(FILES ...)安装配置与版本文件
手写 MyLibraryConfig.cmake定义依赖和目标注册

安装后的 find_package 工作方式

其他项目只需这样:

find_package(MyLibrary CONFIG REQUIRED)
target_link_libraries(app PRIVATE MyCompany::MyLibrary)

即可安全链接、复用你的库(包括传递依赖、编译特性等)。

下面是一个 完整导出式 CMake 项目模板,包含:

  • 模块化的项目结构(src/include/test/
  • 安装导出支持(install() + export()
  • 可被 find_package() 使用(包含 MyLibraryConfig.cmake 与版本控制)
  • Modern CMake 实践(target-based,命名空间、INTERFACE 编译特性等)

目录结构

MyLibrary/
├── CMakeLists.txt              # 顶层入口(wrapper)
├── include/
│   └── MyLibrary/
│       └── MyLibrary.hpp       # 公共头文件
├── src/
│   ├── CMakeLists.txt
│   └── MyLibrary.cpp           # 库实现
├── test/
│   ├── CMakeLists.txt
│   └── MyLibraryTests.cpp      # 单元测试
├── MyLibraryConfig.cmake       # 手写的 config 文件

顶层 CMakeLists.txt

cmake_minimum_required(VERSION 3.15)
project(MyLibrary VERSION 1.0 LANGUAGES CXX)
# 设置 C++ 标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 添加模块
add_subdirectory(src)
add_subdirectory(test)

src/CMakeLists.txt(定义库 + 安装 + 导出)

add_library(MyLibraryMyLibrary.cpp${CMAKE_CURRENT_SOURCE_DIR}/../include/MyLibrary/MyLibrary.hpp
)
target_include_directories(MyLibraryPUBLIC$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../include>$<INSTALL_INTERFACE:include>
)
target_compile_features(MyLibrary PUBLIC cxx_std_17)
# 安装目标
install(TARGETS MyLibraryEXPORT MyLibraryTargetsARCHIVE DESTINATION libLIBRARY DESTINATION libRUNTIME DESTINATION binINCLUDES DESTINATION include
)
# 安装头文件
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../include/DESTINATION include)
# 导出 CMake targets
install(EXPORT MyLibraryTargetsFILE MyLibraryTargets.cmakeNAMESPACE MyCompany::DESTINATION lib/cmake/MyLibrary
)
# 生成版本文件
include(CMakePackageConfigHelpers)
write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/MyLibraryConfigVersion.cmake"VERSION ${PROJECT_VERSION}COMPATIBILITY SameMajorVersion
)
# 安装 config & version
install(FILES${CMAKE_CURRENT_LIST_DIR}/../MyLibraryConfig.cmake${CMAKE_CURRENT_BINARY_DIR}/MyLibraryConfigVersion.cmakeDESTINATION lib/cmake/MyLibrary
)
# 添加别名 target
add_library(MyCompany::MyLibrary ALIAS MyLibrary)

MyLibraryConfig.cmake(手写)

# Optional dependencies here:
# include(CMakeFindDependencyMacro)
# find_dependency(SomeDep REQUIRED)
include("${CMAKE_CURRENT_LIST_DIR}/MyLibraryTargets.cmake")

test/CMakeLists.txt(本地测试)

enable_testing()
find_package(GTest REQUIRED)
add_executable(MyLibraryTests MyLibraryTests.cpp)
target_link_libraries(MyLibraryTestsPRIVATEMyCompany::MyLibraryGTest::Main
)
add_test(NAME MyLibrary.UnitTests COMMAND MyLibraryTests)

include/MyLibrary/MyLibrary.hpp

#pragma once
namespace MyLibrary {int add(int a, int b);
}

src/MyLibrary.cpp

#include "MyLibrary/MyLibrary.hpp"
namespace MyLibrary {int add(int a, int b) { return a + b; }
}
xiaqiu@xz:~/test/CppCon/day205/code$ ls
CMakeLists.txt  MyLibraryConfig.cmake  include  main.cpp  src  test
xiaqiu@xz:~/test/CppCon/day205/code$ tree
.
├── CMakeLists.txt
├── MyLibraryConfig.cmake
├── include
│   └── MyLibrary
│       └── MyLibrary.hpp
├── main.cpp
├── src
│   ├── CMakeLists.txt
│   └── MyLibrary.cpp
└── test├── CMakeLists.txt└── MyLibraryTests.cpp
5 directories, 8 files
xiaqiu@xz:~/test/CppCon/day205/code$ 

构建 + 安装

mkdir build && cd build
cmake .. -DCMAKE_INSTALL_PREFIX=../install
cmake --build .
cmake --install .

下面流程

xiaqiu@xz:~/test/CppCon/day205/code$ mkdir build && cd build
xiaqiu@xz:~/test/CppCon/day205/code/build$ cmake … -DCMAKE_INSTALL_PREFIX=…/install
– The CXX compiler identification is GNU 13.3.0
– Detecting CXX compiler ABI info
– Detecting CXX compiler ABI info - done
– Check for working CXX compiler: /usr/bin/c++ - skipped
– Detecting CXX compile features
– Detecting CXX compile features - done
– Found GTest: /usr/lib/x86_64-linux-gnu/cmake/GTest/GTestConfig.cmake (found version “1.14.0”)
– Configuring done (1.0s)
– Generating done (0.0s)
– Build files have been written to: /home/xiaqiu/test/CppCon/day205/code/build
xiaqiu@xz:~/test/CppCon/day205/code/build$
xiaqiu@xz:~/test/CppCon/day205/code/build$ cmake --build .
[ 25%] Building CXX object src/CMakeFiles/MyLibrary.dir/MyLibrary.cpp.o
[ 50%] Linking CXX static library libMyLibrary.a
[ 50%] Built target MyLibrary
[ 75%] Building CXX object test/CMakeFiles/MyLibraryTests.dir/MyLibraryTests.cpp.o
[100%] Linking CXX executable MyLibraryTests
[100%] Built target MyLibraryTests
xiaqiu@xz:~/test/CppCon/day205/code/build$ cmake --install .
– Install configuration: “”
– Installing: /home/xiaqiu/test/CppCon/day205/code/install/lib/libMyLibrary.a
– Installing: /home/xiaqiu/test/CppCon/day205/code/install/include
– Installing: /home/xiaqiu/test/CppCon/day205/code/install/include/MyLibrary
– Installing: /home/xiaqiu/test/CppCon/day205/code/install/include/MyLibrary/MyLibrary.hpp
– Installing: /home/xiaqiu/test/CppCon/day205/code/install/lib/cmake/MyLibrary/MyLibraryTargets.cmake
– Installing: /home/xiaqiu/test/CppCon/day205/code/install/lib/cmake/MyLibrary/MyLibraryTargets-noconfig.cmake
– Installing: /home/xiaqiu/test/CppCon/day205/code/install/lib/cmake/MyLibrary/MyLibraryConfig.cmake
– Installing: /home/xiaqiu/test/CppCon/day205/code/install/lib/cmake/MyLibrary/MyLibraryConfigVersion.cmake
xiaqiu@xz:~/test/CppCon/day205/code/build$
xiaqiu@xz:~/test/CppCon/day205/code/build$ ls …/install/
include lib
xiaqiu@xz:~/test/CppCon/day205/code/build$ tree …/install/
…/install/
├── include
│ └── MyLibrary
│ └── MyLibrary.hpp
└── lib
├── cmake
│ └── MyLibrary
│ ├── MyLibraryConfig.cmake
│ ├── MyLibraryConfigVersion.cmake
│ ├── MyLibraryTargets-noconfig.cmake
│ └── MyLibraryTargets.cmake
└── libMyLibrary.a
6 directories, 6 files

使用这个库(示例项目)

find_package(MyLibrary CONFIG REQUIRED)
add_executable(MyApp main.cpp)
target_link_libraries(MyApp PRIVATE MyCompany::MyLibrary)

关于 CMake 和现代 C++ 项目管理的经典思路,尤其是针对依赖管理和构建流程的讨论。总结一下核心要点:

1. Package Testing Workflow

  • 先在源码目录单独构建并安装(cmake .. -DCMAKE_INSTALL_PREFIX=... + cmake --build . --target install
  • 再在测试目录单独构建,使用已安装的库,运行测试(ctest
  • 这种方式是纯 CMake 的“安装-再使用”模式

2. Pure CMake Dependencies — The Wrong Way

  • 每个库单独构建安装,生成自己的配置文件
  • 其他项目用 find_package() 查找已安装的库再链接
  • 缺点:
    • 多层依赖时,更新很麻烦,要重复手动重新构建和安装
    • 多配置(Release/Debug)、多平台支持复杂,易出错
    • 依赖版本冲突难解决
    • 不能自动下载和管理依赖

3. What We Want?

  • 一键构建:一次构建完成所有依赖,避免手动管理
  • 智能重用:只重建变更部分,其他部分用缓存或预编译库
  • 自动化依赖管理:无需手动下载安装依赖
  • 自定义版本支持:可以用自定义版本替代系统版本(比如指定特定版本的 Boost、ZLib)

现代 C++ 依赖管理的解决方案示例

方案特点适用场景
纯 CMake + find_package()简单但不适合复杂依赖小型项目
FetchContent / ExternalProject下载源码,统一构建,易集成中小型项目
包管理工具(Conan / vcpkg)专业依赖管理,版本控制,二进制缓存大型跨平台项目和团队协作
你可以考虑用:
  • FetchContent 实现统一构建依赖,减少手动步骤
  • Conan 做专业依赖版本管理,构建缓存和跨项目共享
    这两种方法比纯 find_package() + 安装更适合现代 C++ 项目。

后面Conan包管理可以参考ppt后面内容和Conan官网

https://github.com/CppCon/CppCon2018/blob/master/Presentations/git_cmake_conan_how_to_ship_and_reuse_our_cpp_projects/git_cmake_conan_how_to_ship_and_reuse_our_cpp_projects__mateusz_pusz__cppcon_2018.pdf
https://docs.conan.io/2/introduction.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/87682.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/87682.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用tensorflow的线性回归的例子(五)

我们使用Iris数据&#xff0c;Sepal length为y值而Petal width为x值。import matplotlib.pyplot as pltimport numpy as npimport tensorflow as tffrom sklearn import datasetsfrom tensorflow.python.framework import opsops.reset_default_graph()# Load the data# iris.d…

虚幻基础:动作——蒙太奇

能帮到你的话&#xff0c;就给个赞吧 &#x1f618; 文章目录 动作——蒙太奇如果动作被打断&#xff0c;则后续的动画通知不会执行 动作——蒙太奇 如果动作被打断&#xff0c;则后续的动画通知不会执行

[工具系列] 开源的 API 调试工具 Postwoman

介绍 随着 Web 应用的复杂性增加&#xff0c;API 测试已成为开发中不可或缺的一部分&#xff0c;无论是前端还是后端开发&#xff0c;确保 API 正常运行至关重要。 Postman 长期以来是开发者进行 API 测试的首选工具&#xff0c;但是很多基本功能都需要登陆才能使用&#xff…

【力扣 简单 C】746. 使用最小花费爬楼梯

目录 题目 解法一 题目 解法一 int min(int a, int b) {return a < b ? a : b; }int minCostClimbingStairs(int* cost, int costSize) {const int n costSize; // 楼顶&#xff0c;第n阶// 爬到第n阶的最小花费 // 爬到第n-1阶的最小花费从第n-1阶爬上第n阶的花费…

python+django开发带auth接口

pythondjango开发带auth接口 # coding utf-8 import base64 from django.contrib import auth as django_authfrom django.core.exceptions import ObjectDoesNotExist from django.http import JsonResponsefrom sign.models import Eventdef user_auth(request):"&quo…

RBAC权限模型如何让API访问控制既安全又灵活?

url: /posts/9f01e838545ae8d34016c759ef461423/ title: RBAC权限模型如何让API访问控制既安全又灵活? date: 2025-07-01T04:52:07+08:00 lastmod: 2025-07-01T04:52:07+08:00 author: cmdragon summary: RBAC权限模型通过用户、角色和权限的关联实现访问控制,核心组件包括用…

安达发|告别低效排产:APS高级排程如何助力电池企业智造升级?

在全球能源转型的背景下&#xff0c;动力电池、储能电池等市场需求快速增长&#xff0c;电池制造企业面临着订单波动大、工艺复杂、交期严格等挑战。传统的手工排产或基于ERP的简单计划模式已难以满足高效、精准的生产需求。APS高级排程通过智能算法优化生产计划&#xff0c;实…

数据结构20250620_数据结构考试

试卷01 天津金海通软件笔试题 选择题(4*416) 对于双向循环链表,在p指针所指的结点之后插入s指针所指结点的操作应为 p->nexts; s->prip; p->next->pris; s->nextp->nextp->nexts; p->next->pris; s->prip; s->nextp->nexts->pri …

4. 寻找正序数组的中位数

题目&#xff1a; 给定两个大小分别为 m 和 n 的正序&#xff08;从小到大&#xff09;数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。 算法的时间复杂度应该为 O(log (mn)) 。 示例&#xff1a; 输入&#xff1a;nums1 [1,3], nums2 [2] 输出&#xff1a…

DeepSeek飞机大战小游戏HTML5(附源码)

用DeepSeek帮忙生成的飞机大战小游戏网页版&#xff0c;基于HTML5。 提示词prompt 帮我做一个网页版的飞机大战游戏 html5的游戏功能说明 玩家控制&#xff1a; 使用键盘方向键或WASD移动飞机 空格键发射子弹 移动设备支持触摸控制 游戏机制&#xff1a; 敌机会从屏幕顶部随机位…

全素山药开发指南:从防痒处理到高可用食谱架构

摘要&#xff1a;本文系统性解析山药的化学特性&#xff08;黏液蛋白/皂苷致痒机制&#xff09;及全素场景下的烹饪解决方案&#xff0c;提供6种高内聚低耦合的食谱实现&#xff0c;附完整防氧化与黏液控制技术方案。一、核心问题分析&#xff1a;山药处理中的“痛点”致痒物质…

OpenLayers 入门指南:序言

本专栏旨在帮助零GIS基础的开发人员系统掌握OpenLayers这一强大的开源Web地图库&#xff0c;通过 “理论实战” 结合的方式&#xff0c;逐步实现从创建地图到构建一个基础地图应用模版。无论你是前端开发者、GIS爱好者&#xff0c;都可以通过此专栏零基础开始用OpenLayers开发一…

WebRTC轻量学习 libdatachannel

最近想了解一些在浏览器中推送音视频流&#xff0c;寻找很多版本的代码&#xff0c;C、Go、Python等语言实现的webRTC协议。 按照搭建难度和快速实现首选Python版本的WebRTC&#xff0c;这种是最适合原型开发的。 选型&#xff1a;C的开源库libdatachannel Python的开源库Ai…

Vue2中的keep-alive:组件状态缓存与性能优化实战指南

目录 一、什么是keep-alive&#xff1f; 与普通组件切换的对比 二、核心用法详解 1. 基础用法&#xff1a;动态组件缓存 2. 路由视图缓存 3. 生命周期钩子 三、进阶配置与优化 1. 精准控制缓存组件 &#xff08;1&#xff09;include/exclude属性 &#xff08;2&…

FastAPI安全加固:密钥轮换、限流策略与安全头部如何实现三重防护?

url: /posts/f96ba438de34dc197fd2598f91ae133d/ title: FastAPI安全加固:密钥轮换、限流策略与安全头部如何实现三重防护? date: 2025-07-02T22:05:04+08:00 lastmod: 2025-07-02T22:05:04+08:00 author: cmdragon summary: FastAPI框架安全加固方案包括密钥轮换自动化、请…

NeighborGeo:基于邻居的IP地理定位(五)

NeighborGeo:基于neighbors的IP地理定位 X. Wang, D. Zhao, X. Liu, Z. Zhang, T. Zhao, NeighborGeo: IP geolocation based on neighbors, Comput. Netw. 257 (2025) 110896, 5. Case analysis 为了说明NeighborGeo在优化图结构和利用邻居信息进行预测方面的优势,将目标I…

Ethernet IP与Profinet共舞:网关驱动绿色工业的智慧脉动

Ethernet IP与Profinet共舞&#xff1a;驱动绿色工业的智慧脉动 光伏建筑一体化&#xff0c;建筑碳中和&#xff0c;在全球气候变化、国家碳达峰碳中和战略大背景下&#xff0c;敬畏生活、生产与自然和谐共处&#xff0c;确立自身资源循环高效利用的倒计时和路线图。 在全球气…

衡石科技破解指标管理技术难题:语义层建模如何实现业务与技术语言对齐?

在数字化转型的深水区&#xff0c;企业指标管理体系普遍面临一个核心矛盾&#xff1a;业务部门需要敏捷的数据洞察支撑决策&#xff0c;而IT部门却受困于复杂的数据架构和冗长的需求响应周期。这种矛盾的本质&#xff0c;是传统指标管理体系中“技术语言”与“业务语言”的割裂…

正品库拍照PWA应用的实现与性能优化|得物技术

一、 背景与难点 背景 目前得物ERP主要鉴别流程&#xff0c;是通过鉴别师鉴别提需到仓库&#xff0c;仓库库工去进行商品补图拍照&#xff0c;现有正品库59%的人力投入在线下商品借取/归还业务的操作端&#xff0c;目前&#xff0c;线下借取的方式会占用商品资源&#xff0c…

如何使用python识别出文件夹中全是图片合成的的PDF,并将其移动到指定文件夹

引言 在现代数字化工作流程中&#xff0c;无论是为机器学习模型处理数据&#xff0c;还是进行数字归档&#xff0c;区分原生文本 PDF&#xff08;例如&#xff0c;由文字处理器生成的报告&#xff09;和基于图像的 PDF&#xff08;例如&#xff0c;扫描的发票、档案文件&#…