CMake与Conan协同工作理解

背景

最近根据 【eBPF 入门实践教程十二:使用 eBPF 程序 profile 进行性能分析】 这篇文章,写了一个性能分析的小工具。不过文章中的实现是 rust,但我还不怎么熟悉 rust,因而打算参考其代码使用 c++ 来实现相同的功能。项目地址:profiler

rust 中使用 cargo 就能非常方便的管理依赖与构建项目,c++ 中我使用 CMake 来构建项目,Conan 来管理依赖。在使用期间,我对于这二者的协同工作原理感到好奇,因此有了这篇笔记。

文中的出现的“依赖”、“包”、“库”等名词都是指代同一个概念,只是不同的说法而已~

CMake 查找依赖

在 CMake 中查找和管理项目依赖,主要是通过内置的 find_package 命令完成,其会在系统中寻找指定的库。

1
find_package(<PackageName> [version] [REQUIRED] [COMPONENTS components...])
  • PackageName: 库的名称(区分大小写,如 OpenCVQt5)。
  • REQUIRED: 如果找不到该库,CMake 会直接报错并停止配置。
  • COMPONENTS: 查找库中的特定模块(例如 Qt5 里的 Widgets)。

CMake 有两种查找库的逻辑:

  1. Module 模式 (查找 Find<PackageName>.cmake)

    CMake 预置了一系列脚本(在 /usr/share/cmake/Modules 下)。它会寻找名为 FindGSL.cmake 这样的文件。

    • 适用场景: 较老或不直接支持 CMake 的库(如 CURL, ZLIB)。
  2. Config 模式 (查找 <PackageName>Config.cmake<lower-case-pkg>-config.cmake)

    这是现代库推荐的方式。库在安装时会自带一个配置文件。

    • 适用场景: OpenCV, Qt, Protobuf 等现代库。
    • 查找路径: 库的安装路径、CMAKE_PREFIX_PATH 或环境变量。

CMAKE_PREFIX_PATH

当在 CMake 中使用 find_packagefind_libraryfind_path 等指令去寻找一个外部库时,CMake 并不知道这个库安装在哪里。CMAKE_PREFIX_PATH 就是用来告诉 CMake 去这些目录下找找看。

这里有三种设置的方法:

  1. 命令行设置

    1
    
    cmake -DCMAKE_PREFIX_PATH="/path/to"
    
  2. CMakeLists.txt

    1
    
    list(APPEND CMAKE_PREFIX_PATH "/path/to")
    
  3. 环境变量

    1
    
    export CMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH:/path/to
    

Conan Generator

在一个比较简单的项目里,通常使用 conanfile.txt 来管理依赖即可,例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
[requires]
cli11/2.6.0
spdlog/1.17.0

[generators]
CMakeDeps
CMakeToolchain

[layout]
cmake_layout

注意 [generators] 中的 CMakeDepsCMakeToolchain,他们在 Conan 与 CMake 集成使用中起了至关重要的作用。

CMakeDeps

当我们安装了 spdlog 这样的库后,需要 CMake 能看到它。CMakeDeps 就是这样一个依赖关系生成器,它会为每一个依赖库生成一个标准的 spdlog-config.cmake 文件。

将 CMakeDeps 生成的 spdlog-config.cmake 目录位置加入 CMAKE_PREFIX_PATH 后,就可以在 CMakeLists.txt 中写 find_package(spdlog REQUIRED) 来查找依赖了。

CMakeToolchain

CMakeToolchain 负责将你在 Conan Profile(配置文件)中定义的设置(如 compiler=gcc, build_type=Release)注入给 CMake。同时,其生成 conan_toolchain.cmake,并自动设置 CMAKE_PREFIX_PATH

重要的一点就是:它会自动把 CMakeDeps 生成的配置文件目录加入搜索路径。

当执行 conan install 后,这两个生成器会接力完成任务:

  1. Conan 下载库到缓存。
  2. CMakeDeps 生成 spdlog-config.cmake 等文件,描述如何链接这些库。
  3. CMakeToolchain 生成 conan_toolchain.cmake,里面有一行代码告诉 CMake:“去 CMakeDeps 生成的那个文件夹里找包”。

例如我的项目里, ~/Workspace/profiler-cpp/build/Debug/generators/conan_toolchain.cmake 中的一部分如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
########## 'find_paths' block #############
# Define paths to find packages, programs, libraries, etc.
if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/conan_cmakedeps_paths.cmake")
  message(STATUS "Conan toolchain: Including CMakeDeps generated conan_cmakedeps_paths.cmake")
  include("${CMAKE_CURRENT_LIST_DIR}/conan_cmakedeps_paths.cmake")
else()

set(CMAKE_FIND_PACKAGE_PREFER_CONFIG ON)

# Definition of CMAKE_MODULE_PATH
# the generators folder (where conan generates files, like this toolchain)
list(PREPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR})

# Definition of CMAKE_PREFIX_PATH, CMAKE_XXXXX_PATH
# The Conan local "generators" folder, where this toolchain is saved.
list(PREPEND CMAKE_PREFIX_PATH ${CMAKE_CURRENT_LIST_DIR} )
list(PREPEND CMAKE_LIBRARY_PATH "/home/kerolt/.conan2/p/b/spdlo964bc6492d317/p/lib" "/home/kerolt/.conan2/p/b/fmtc7b4bad61f66e/p/lib")
list(PREPEND CMAKE_INCLUDE_PATH "/home/kerolt/.conan2/p/cli115c29056d7e51f/p/include" "/home/kerolt/.conan2/p/b/spdlo964bc6492d317/p/include" "/home/kerolt/.conan2/p/b/fmtc7b4bad61f66e/p/include")
set(CONAN_RUNTIME_LIB_DIRS "/home/kerolt/.conan2/p/b/spdlo964bc6492d317/p/lib" "/home/kerolt/.conan2/p/b/fmtc7b4bad61f66e/p/lib" )

endif()

CMake 工具链文件

工具链文件是一个以 .cmake 结尾的脚本,通过在配置阶段指定变量 CMAKE_TOOLCHAIN_FILE 来加载:

1
cmake -DCMAKE_TOOLCHAIN_FILE=my_toolchain.cmake ..

它通常在 project() 指令执行 之前 被加载,用于初始化编译器路径、目标平台信息等关键底层变量。

现在通过 Conan 的 CMakeDepsCMakeToolchain,直接引入 Conan 自动生成的工具链文件,就能无感地使用下载的依赖了。

例如直接在命令里使用:

1
cmake -DCMAKE_TOOLCHAIN_FILE=/home/kerolt/Workspace/profiler-cpp/build/Debug/generators/conan_toolchain.cmake ...

或者通过 CMakePresets(CMake >= 3.19)来使用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
{
    "version": 8,
    "configurePresets": [
        {
            "name": "debug",
            "displayName": "Profiler Debug 构建",
            "description": "正在使用编译器: C = /usr/bin/gcc, CXX = /usr/bin/g++",
            "binaryDir": "${sourceDir}/build/Debug",
            // 就是这个 toolchainFile ~
            "toolchainFile": "${sourceDir}/build/Debug/generators/conan_toolchain.cmake",
            "cacheVariables": {
                "CMAKE_BUILD_TYPE": "Debug",
                "CMAKE_CXX_STANDARD": "23",
                "CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
            }
        }
    ]
}

总结

理解了 CMake 是如何查找依赖包后,二者的协同工作原理就明了了:Conan 通过自动生成的配置能告诉 CMake 在哪里找依赖包后,就能直接在 CMakeLists.txt 中使用 find_package 来查找对应的依赖了。

Licensed under CC BY-NC-SA 4.0
最后更新于 2026年1月23日星期五
使用 Hugo 构建
主题 StackJimmy 设计