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.txt 和 conanfile.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_directories 或 link_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也踩过一些坑,分享出来供你参考。
问题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_package 和 target_link_libraries。
你想想看,以前我们要手动下载源码、配置编译选项、设置头文件路径、链接库文件……现在一行 conan install 全搞定。这就是工具的力量。
下一章我们会深入 conanfile.py 的高级用法,包括自定义构建流程、多配置管理等内容。但如果你只是日常开发,conanfile.txt 已经能覆盖90%的场景了。
公众号:蓝海资料掘金营,微信deep3321