16、CMake Conan:Conan基础、conanfile.txt、CMakeDeps生成器、集成到CMake

说到C++的包管理,我这些年用过不少方案。从早期的纯手工下载源码编译,到后来用Git Submodule,再到vcpkg和Conan。说实话,Conan是我个人最偏爱的一个。为什么?因为它足够灵活,而且对CMake的支持做得相当到位。

这一章,我们就来聊聊Conan的基础用法。我会从最核心的概念讲起,然后带你一步步把Conan集成到CMake项目里。

Conan是什么?

Conan是一个去中心化的C/C++包管理器。说白了,它帮你解决「我要用某个库,但不想自己编译」的问题。你只需要告诉Conan你需要什么库、什么版本、什么配置,它就会自动下载并编译好。

我刚开始用Conan时,最直观的感受是:终于不用再手动折腾OpenSSL、Boost这些大库了。以前配Boost能配一整天,现在一行命令搞定。

核心概念:conanfile.txt

Conan有两种描述项目依赖的方式:conanfile.txtconanfile.py。对于大多数CMake项目,conanfile.txt 就够用了。它简洁、直观,没有Python脚本的复杂性。

来看一个典型的 conanfile.txt

[requires]
fmt/10.1.1
spdlog/1.12.0
nlohmann_json/3.11.2

[generators]
CMakeDeps
CMakeToolchain

[options]
fmt:shared=False
spdlog:shared=False

[layout]
cmake_layout

这里我解释一下每个字段的作用:

  • [requires]:声明项目依赖的库和版本。格式是 包名/版本号
  • [generators]:指定生成器。我们主要用 CMakeDeps,它会生成CMake能直接使用的 FindXXX.cmake 文件。
  • [options]:配置包的编译选项。比如这里我把两个库都设为静态库。
  • [layout]:指定项目布局。 cmake_layout 是推荐的方式,它会按照Conan的标准目录结构组织构建文件。
我的小建议: 版本号尽量写具体。不要写 fmt/10.x 这种模糊的版本,否则哪天Conan缓存更新了,你的项目可能就莫名其妙编译不过了。我在生产项目里吃过这个亏。

CMakeDeps生成器

这是Conan和CMake集成的关键。 CMakeDeps 生成器会读取你的 conanfile.txt,然后为每个依赖生成对应的CMake配置文件。

生成的配置文件长什么样?举个例子,如果你依赖了 fmt,Conan会生成一个 fmt-config.cmake 文件。这个文件里定义了 fmt::fmt 这个目标,包含了头文件路径、库文件路径、编译选项等所有信息。

在CMake里使用这些依赖,代码非常简洁:

find_package(fmt REQUIRED)
find_package(spdlog REQUIRED)
find_package(nlohmann_json REQUIRED)

target_link_libraries(my_app
    PRIVATE
        fmt::fmt
        spdlog::spdlog
        nlohmann_json::nlohmann_json
)

你看,完全不需要手动设置 include_directorieslink_directories。Conan把这些脏活累活都干了。

关键点: CMakeDeps 生成的是 目标(target) 级别的依赖,而不是传统的变量级别。这意味着你可以享受CMake的传递依赖管理、接口属性继承等现代特性。

集成到CMake:完整流程

好了,理论说完了,我们来看一个完整的集成流程。我会用一个实际项目来演示。

第一步:项目结构

my_project/
├── conanfile.txt
├── CMakeLists.txt
├── src/
│   └── main.cpp
└── build/

第二步:编写 conanfile.txt

刚才已经给过示例了,这里不再重复。注意 [generators] 里一定要有 CMakeDeps

第三步:编写 CMakeLists.txt

cmake_minimum_required(VERSION 3.20)
project(MyApp VERSION 1.0.0 LANGUAGES CXX)

# 设置C++标准
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 告诉CMake去哪里找Conan生成的配置文件
list(PREPEND CMAKE_PREFIX_PATH "${CMAKE_BINARY_DIR}")

# 查找依赖
find_package(fmt REQUIRED)
find_package(spdlog REQUIRED)
find_package(nlohmann_json REQUIRED)

# 添加可执行文件
add_executable(my_app src/main.cpp)

# 链接依赖
target_link_libraries(my_app
    PRIVATE
        fmt::fmt
        spdlog::spdlog
        nlohmann_json::nlohmann_json
)

注意: CMAKE_PREFIX_PATH 的设置很关键。Conan生成的配置文件默认放在构建目录下,如果不把这个路径告诉CMake,find_package 就找不到它们。我曾经在这个坑里卡了半小时,后来才发现是路径没配对。

第四步:安装依赖并构建

# 进入构建目录
cd build

# 安装依赖
conan install .. --build=missing

# 配置CMake
cmake .. -DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake

# 编译
cmake --build .

这里有个细节:conan install 会生成 conan_toolchain.cmake 文件。这个文件里包含了编译器路径、编译选项、标准库路径等信息。通过 -DCMAKE_TOOLCHAIN_FILE 传入,CMake就能用Conan配置好的工具链来编译。

SVG:Conan + CMake 集成流程

Conan + CMake 集成流程 conanfile.txt 声明依赖、选项、生成器 conan install 下载依赖、生成配置文件 CMakeDeps 生成 .cmake 文件 conan_toolchain.cmake cmake .. -DCMAKE_TOOLCHAIN_FILE=... CMake读取Conan生成的配置文件 cmake --build . 编译项目,自动链接Conan管理的依赖 整个流程:声明依赖 → 安装依赖 → 配置CMake → 编译

常见问题与避坑

我在实际项目中用Conan也踩过一些坑,分享出来供你参考。

问题1:版本冲突

有时候两个库依赖了同一个库的不同版本。比如 spdlog 依赖 fmt/10.1.1,而你的项目直接依赖了 fmt/9.0.0。Conan会报版本冲突。

解决办法:conanfile.txt 里显式指定统一的版本,或者使用 conf 来覆盖依赖的版本。

问题2:找不到生成的 .cmake 文件

这个我前面提过。如果你忘了设置 CMAKE_PREFIX_PATH,CMake就找不到Conan生成的配置文件。检查一下你的构建目录下有没有 fmt-config.cmake 这类文件。

问题3:静态库 vs 动态库

[options] 里可以控制每个库是静态还是动态。我个人建议:如果是开发库(比如给其他项目用的),用动态库;如果是最终的可执行文件,用静态库可以避免部署时的DLL缺失问题。

我的经验: 在Windows上用Conan管理动态库时,记得把Conan生成的 bin 目录加到 PATH 环境变量里,否则运行时找不到DLL。这个坑我踩了两次才记住。

总结

Conan + CMake的组合,是目前C++项目依赖管理的主流方案之一。核心流程就三步:写 conanfile.txt、运行 conan install、在CMake里用 find_packagetarget_link_libraries

你想想看,以前我们要手动下载源码、配置编译选项、设置头文件路径、链接库文件……现在一行 conan install 全搞定。这就是工具的力量。

下一章我们会深入 conanfile.py 的高级用法,包括自定义构建流程、多配置管理等内容。但如果你只是日常开发,conanfile.txt 已经能覆盖90%的场景了。


公众号:蓝海资料掘金营,微信deep3321