1. 为什么我们需要Conan 2.x?聊聊C++依赖管理的“痛点”
如果你写过C++,尤其是稍微大一点的项目,肯定对依赖管理这件事深有体会。我说的不是那种“哦,我需要用个JSON库,去GitHub上clone下来,然后手动编译”的简单场景。我说的是那种,项目里有十几个第三方库,每个库又有自己的依赖,而且它们对编译器版本、C++标准、构建类型(Debug/Release)还有不同要求的情况。光是让所有东西在Windows、Linux和macOS上都能编译通过,可能就得花上好几天,甚至几周。这还没算上团队协作时,如何保证每个人本地环境一致的问题。
这就是传统C++依赖管理的“痛点”:手动、繁琐、易错、难以跨平台、难以版本锁定。你可能会用Git Submodule,但管理版本和构建选项很麻烦;你也可能用系统包管理器(比如apt、brew),但版本往往滞后,而且不同系统间的包名和结构天差地别。
Conan的出现,就是为了解决这些痛点。它本质上是一个去中心化的C/C++包管理器。你可以把它想象成Python的pip、JavaScript的npm,但专门为C++的复杂生态量身定制。Conan 1.x版本已经解决了“有没有”的问题,而Conan 2.x,则是在1.x的基础上,进行了一次大刀阔斧的现代化重构,目标是让依赖管理变得更简单、快速、可靠和可扩展。
我刚开始接触Conan 2.x时,感觉就像从手动挡换成了自动挡,还带上了自适应巡航。它引入的新概念,比如新的命令行接口(CLI)、改进的图模型(Graph Model)、更强大的CMake集成(CMakeDeps, CMakeToolchain)、以及革命性的“锁文件”(lockfiles),都是为了构建一个真正现代化的、可复现的构建流程。接下来,我就带你从零开始,亲手用Conan 2.x搭建一套完整的依赖管理流程,让你彻底告别“依赖地狱”。
2. 快速上手:安装与配置你的Conan 2.x环境
万事开头难,但Conan 2.x的安装简单得超乎想象。它基于Python,所以第一步是确保你有一个可用的Python 3环境。我推荐使用Python 3.8或更高版本。
2.1 跨平台安装,一条命令搞定
别再去纠结什么系统特定的安装包了,现在最推荐的方式就是使用pip。打开你的终端(Windows上用PowerShell或CMD,Linux/macOS上用Bash),直接运行:
pip install conan对,就这么简单。这条命令默认会安装最新的Conan 2.x稳定版。如果你想安装一个特定的2.x版本,比如2.0.5,可以这样:
pip install conan==2.0.5安装完成后,验证一下是否成功:
conan --version如果输出了类似Conan version 2.0.5的信息,恭喜你,第一步已经完成了。这里有个小坑我踩过:如果你的系统里既有Python2又有Python3,请确保pip命令对应的是Python3的pip。有时候可能需要用pip3 install conan。如果不确定,可以用python -m pip install conan,这个命令会明确使用当前python解释器对应的pip。
2.2 初始化配置与认识新CLI
安装好后,我们先不急着创建包。Conan 2.x的CLI命令结构和1.x有较大变化,更清晰、更一致了。我们可以先看看帮助:
conan --help你会发现命令被分成了几大类,比如conan remote(管理远程仓库)、conan profile(管理配置档案)、conan graph(查看依赖图)等等。和1.x相比,很多命令的逻辑更直观了。
接下来,我们需要配置一个远程仓库(remote)。Conan Center(conancenter)是默认的、最大的公共仓库,里面已经有成千上万个常用的C++库,比如Boost、OpenSSL、Protobuf、spdlog等。默认情况下,Conan 2.x已经添加了Conan Center远程。你可以用以下命令查看:
conan remote list你应该能看到一个名为conancenter的远程。为了后续下载包更快,我强烈建议你配置一下JFrog的免费公有仓库作为另一个源,它有时比Conan Center更快,并且包含一些额外的包。
conan remote add conancenter https://center.conan.io # 再添加JFrog的公共仓库(可选,但推荐) conan remote add jfrog-conan https://conan.jfrog.io/artifactory/api/conan/conan2.3 创建你的第一个配置文件(Profile)
Profile是Conan的核心概念之一,它定义了目标环境的设置,比如操作系统、编译器、编译架构、构建类型等。这确保了你的依赖包能根据你的环境被正确地构建或选择预编译的二进制包。
Conan 2.x自带了一些默认profile。我们可以先检测一下当前环境,让它自动生成一个profile:
conan profile detect --force这条命令会扫描你的系统,生成一个名为default的profile。让我们看看它里面有什么:
conan profile show default你会看到类似下面的输出:
[settings] os=Linux arch=x86_64 compiler=gcc compiler.version=11 compiler.libcxx=libstdc++11 build_type=Release这个profile描述了你当前的环境:Linux系统,64位架构,使用GCC 11编译器,C++标准库是libstdc++11,构建类型是Release。如果你在Windows上,可能会看到os=Windows,compiler=msvc等信息。
在实际项目中,我们通常不会直接用default,而是为不同的构建场景创建专门的profile。比如,为Debug构建创建一个:
conan profile create my_debug_profile conan profile update settings.build_type=Debug my_debug_profile你还可以在profile里定义一些自定义的选项(options)或环境变量(env vars),非常灵活。Profile文件的存在,是实现跨平台、多配置构建的基石。
3. 从消费者到创造者:使用和创建你的第一个Conan包
现在环境准备好了,我们从两个角度来体验Conan:首先作为一个消费者,使用别人已经做好的包;然后作为一个创造者,把自己的代码打包成Conan包。
3.1 消费现有包:以spdlog为例
假设我们正在开发一个需要日志功能的小工具,我们决定使用非常流行的spdlog库。在没有Conan的时候,你可能需要去下载源码,用CMake编译,然后配置头文件路径和链接库。有了Conan,这一切变得极其简单。
首先,创建一个新的项目目录,比如my_logger,并进入:
mkdir my_logger && cd my_logger然后,创建一个conanfile.txt文件。这是最简单的一种定义依赖的方式(另一种更强大的是conanfile.py)。编辑conanfile.txt,内容如下:
[requires] spdlog/1.11.0 [generators] CMakeDeps CMakeToolchain[requires]部分:声明我们需要spdlog库,版本是1.11.0。Conan会去我们配置的远程仓库(比如conancenter)查找这个包。[generators]部分:这是Conan 2.x的精华之一。CMakeDeps生成器会为每个依赖包创建对应的FindXXX.cmake或XXXConfig.cmake文件,这样你的CMake项目就能直接用find_package(spdlog)找到它。CMakeToolchain生成器则会生成一个conan_toolchain.cmake文件,它能把Conan profile里的设置(比如编译器、架构、构建类型)传递给CMake,确保构建环境一致。
接下来,在项目根目录下执行安装命令:
conan install . --output-folder=build --build=missing让我解释一下这个命令:
conan install .:告诉Conan根据当前目录下的conanfile.txt安装依赖。--output-folder=build:这是Conan 2.x的新特性!它把所有生成的文件(比如CMakeDeps和CMakeToolchain生成的文件)都输出到build子目录下,保持项目根目录的干净。这比1.x时代文件散落在各处好太多了。--build=missing:如果Conan在远程仓库里没有找到与你当前profile匹配的预编译二进制包(比如你用了一个比较新的编译器版本),它会自动从源码构建这个包。
命令执行成功后,你会看到build目录下多了很多文件,其中最重要的就是conan_toolchain.cmake和conan_deps.cmake(或一系列FindXXX.cmake)。
现在,我们可以创建一个简单的CMakeLists.txt来使用spdlog:
cmake_minimum_required(VERSION 3.15) project(MyLogger) # 关键一步:引入Conan生成的toolchain文件 include(${CMAKE_BINARY_DIR}/conan_toolchain.cmake) # 查找spdlog包,这要归功于CMakeDeps生成器 find_package(spdlog REQUIRED) add_executable(main main.cpp) # 链接spdlog,就像链接一个普通的CMake目标一样 target_link_libraries(main spdlog::spdlog)再创建一个main.cpp:
#include <spdlog/spdlog.h> int main() { spdlog::info("Hello, this is my first Conan 2.x project using spdlog!"); return 0; }最后,使用CMake构建(注意指定构建目录和使用Conan的toolchain):
cd build cmake .. -DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake -DCMAKE_BUILD_TYPE=Release cmake --build . ./main你应该能看到漂亮的日志输出。整个过程,你完全没有手动下载、编译spdlog,也没有操心它的依赖(比如fmt库)。Conan帮你全自动处理了,并且保证了二进制兼容性。这就是作为“消费者”的极致体验。
3.2 创建自己的Conan包:打包一个简单的数学库
光会用还不够,我们得学会“创造”。让我们把一个简单的C++库打包成Conan包。这个库叫mymath,就提供一个加法函数。
首先,创建库的项目结构:
mkdir mymath_lib && cd mymath_lib mkdir -p src/include/mymath创建头文件src/include/mymath/mymath.h:
#pragma once namespace mymath { int add(int a, int b); }创建源文件src/mymath.cpp:
#include "mymath/mymath.h" namespace mymath { int add(int a, int b) { return a + b; } }现在,创建核心的conanfile.py。这是Conan包的“配方”,定义了如何构建、打包你的库。Conan 2.x提供了新的、更简洁的API,我们使用它:
from conan import ConanFile from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout, CMakeDeps from conan.tools.files import copy class MymathConan(ConanFile): name = "mymath" version = "1.0.0" license = "MIT" author = "Your Name <your.email@example.com>" url = "https://github.com/yourname/mymath" description = "A simple math library" topics = ("math", "simple") # 设置:影响二进制包标识 settings = "os", "compiler", "build_type", "arch" # 选项:用户可配置的开关,比如是否构建为动态库 options = {"shared": [True, False], "fPIC": [True, False]} default_options = {"shared": False, "fPIC": True} # 导出源码文件 exports_sources = "CMakeLists.txt", "src/*" def config_options(self): # 在Windows上,fPIC选项没有意义,删除它 if self.settings.os == "Windows": del self.options.fPIC def layout(self): # 定义源码、构建、安装目录的布局,这是2.x的新推荐做法 cmake_layout(self) def generate(self): # 生成CMakeToolchain文件,将Conan设置传递给CMake tc = CMakeToolchain(self) tc.generate() # 如果本包有依赖,这里也需要生成它们的CMake文件,但本例无依赖,所以省略CMakeDeps def build(self): cmake = CMake(self) cmake.configure() cmake.build() def package(self): cmake = CMake(self) cmake.install() # 也可以手动拷贝文件,但cmake.install()通常更简洁 # copy(self, "*.h", self.source_folder, self.package_folder) def package_info(self): # 这是最重要的方法之一,告诉消费者如何链接和使用你的库 self.cpp_info.libs = ["mymath"] # 设置头文件目录 self.cpp_info.includedirs = ["include"]再创建一个简单的CMakeLists.txt来构建这个库:
cmake_minimum_required(VERSION 3.15) project(mymath VERSION 1.0.0 LANGUAGES CXX) add_library(mymath src/mymath.cpp) target_include_directories(mymath PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/include> $<INSTALL_INTERFACE:include> ) # 设置导出目标名,便于find_package set_target_properties(mymath PROPERTIES EXPORT_NAME mymath::mymath) install(TARGETS mymath EXPORT mymath-targets LIBRARY DESTINATION lib ARCHIVE DESTINATION lib RUNTIME DESTINATION bin INCLUDES DESTINATION include ) install(DIRECTORY src/include/ DESTINATION include)现在,我们可以在本地“创建”这个包。conan create命令会执行完整的流程:将配方导出到本地缓存、在临时目录构建、打包、并运行测试(如果有的话)。我们先创建一个简单的测试:
mkdir test_package创建test_package/conanfile.py:
from conan import ConanFile from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout from conan.tools.build import can_run class TestPackageConan(ConanFile): settings = "os", "compiler", "build_type", "arch" generators = "CMakeToolchain", "CMakeDeps" def requirements(self): # 这里要求我们正在创建的包 self.requires(self.tested_reference_str) def build(self): cmake = CMake(self) cmake.configure() cmake.build() def test(self): if can_run(self): cmd = os.path.join(self.cpp.build.bindir, "test_package") self.run(cmd, env="conanrun")创建test_package/test_package.cpp:
#include <iostream> #include <mymath/mymath.h> int main() { int result = mymath::add(2, 3); if (result == 5) { std::cout << "Test passed: 2 + 3 = " << result << std::endl; return 0; } else { std::cerr << "Test failed!" << std::endl; return 1; } }创建test_package/CMakeLists.txt:
cmake_minimum_required(VERSION 3.15) project(test_package LANGUAGES CXX) find_package(mymath REQUIRED) add_executable(test_package test_package.cpp) target_link_libraries(test_package mymath::mymath)一切就绪,回到项目根目录,运行创建命令:
conan create . --user=myuser --channel=mychannel这个命令会:
- 将当前目录的配方 (
conanfile.py) 导出到本地缓存,引用为mymath/1.0.0@myuser/mychannel。 - 根据你的默认profile(或指定profile)设置,在临时目录中构建这个包。
- 运行
test_package下的测试,验证包是否能被正确消费。 - 如果测试通过,将构建好的二进制包(头文件、库文件等)打包并存储到本地缓存。
看到mymath/1.0.0@myuser/mychannel创建成功的提示后,你就可以像使用spdlog一样,在任何其他项目的conanfile.txt或conanfile.py里通过requires = "mymath/1.0.0@myuser/mychannel"来使用它了。这就是创建和分享自定义包的基本流程。
4. 进阶实战:版本锁定、工作流与CI/CD集成
掌握了基本使用和创建,我们来看看Conan 2.x在真实项目工作流中的高级玩法。这些功能是保证团队协作和构建可复现性的关键。
4.1 依赖版本锁定与锁文件(Lockfiles)
这是Conan 2.x最强大的特性之一,也是解决“在我机器上能跑”问题的终极武器。想象一下,你的项目依赖A库(版本1.0),A库又依赖B库(版本>=2.0)。今天你构建时,Conan给你解析并下载了B库的2.1版本。一个月后,B库发布了2.2版本,其中有一个不兼容的改动。这时你的同事或CI服务器重新安装依赖,Conan可能会选择B库的2.2版本,导致构建失败或运行时错误。
锁文件就是为了冻结整个依赖图。它会记录下所有直接和间接依赖的精确版本、配置、选项甚至构建哈希。生成锁文件很简单,在安装依赖时加上--lockfile相关参数:
# 首次安装,生成一个基础锁文件 conan install . --output-folder=build --lockfile-out=conan.lock这会在当前目录生成一个conan.lock文件。它的内容是人类可读的JSON,记录了这次解析出的所有依赖的确切信息。之后,任何人(包括你自己)在任何机器上,只要使用这个锁文件,就能得到完全相同的依赖解析结果:
# 后续安装,使用锁文件确保一致性 conan install . --output-folder=build --lockfile=conan.lock即使远程仓库有了新版本,Conan也会严格遵循锁文件中的版本。当你确实需要升级某个依赖时,你可以有选择地更新锁文件,而不是全部推翻重来。锁文件应该被提交到版本控制系统(如Git)中,它是项目可复现构建的基石。
4.2 现代化工作流:从开发到发布
对于一个包含多个内部库的中大型项目,Conan 2.x推荐的工作流非常清晰:
开发模式(Editable Packages):当你同时开发主项目和它依赖的库时,你不想每次修改库代码都去执行一遍
conan create然后重新安装。这时可以用conan editable命令。它告诉Conan:“别去缓存里找这个包了,直接去我本地的工作目录找。” 这样你修改库源码后,主项目能立刻感知到变化,实现类似“源码级”的依赖。# 在库项目目录下 conan editable add . mymath/1.0.0@myuser/mychannel # 在主消费项目目录下正常conan install即可创建与上传:当库开发完成,需要发布版本时,使用
conan create创建正式的包,然后用conan upload上传到团队私有的Conan远程仓库(比如搭建的Artifactory)。conan create . myuser/mychannel conan upload mymath/1.0.0@myuser/mychannel -r=my_team_remote --all版本管理:Conan支持语义化版本和自定义版本。你可以通过
conan export和conan export-pkg来管理配方的版本,而conan upload则管理二进制包。
4.3 与CI/CD管道集成
将Conan集成到CI/CD(如GitHub Actions, GitLab CI, Jenkins)中,可以实现自动化的依赖安装、包构建和测试。核心思路是:
- 在CI中恢复依赖:第一步总是
conan install,使用项目中的锁文件来保证环境一致。 - 缓存Conan数据:CI Runner通常是无状态的。为了加速,一定要缓存Conan的本地缓存目录(通常是
~/.conan2或%USERPROFILE%\.conan2)。这样,下载过的包和生成的二进制包就不需要每次都重新下载/构建。 - 矩阵构建:利用Conan的profile,可以轻松地在CI中为不同的平台(Linux, Windows, macOS)、编译器(GCC, Clang, MSVC)、架构(x86, x86_64, arm)进行矩阵构建。
- 自动化创建和上传:在合并到主分支后,CI可以自动执行
conan create和conan upload,将新版本的包发布到私有仓库。
一个简化的GitHub Actions步骤可能长这样:
jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Cache Conan dependencies uses: actions/cache@v3 with: path: ~/.conan2 key: ${{ runner.os }}-conan-${{ hashFiles('conan.lock') }} restore-keys: | ${{ runner.os }}-conan- - name: Install Conan run: pip install conan - name: Install dependencies run: conan install . --output-folder=build --lockfile=conan.lock - name: Configure and Build run: | cd build cmake .. -DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake -DCMAKE_BUILD_TYPE=Release cmake --build .通过这样的流程,Conan 2.x不仅是一个包管理工具,更成为了现代C++项目基础设施自动化的核心一环。它把开发者从繁琐的环境配置和依赖冲突中解放出来,让团队能更专注于代码逻辑本身。从我自己的经验来看,在项目初期就引入并规范使用Conan 2.x,虽然有一定学习成本,但长期来看,对于提升开发效率、保证构建稳定性和促进团队协作,其回报是巨大的。尤其是锁文件功能,在大型跨团队项目中,几乎杜绝了因依赖版本漂移而导致的“幽灵bug”。