conan通过五元组(name/version@user/channel + settings + options)唯一标识依赖,支持多版本共存;conan.lock记录完整依赖图实现确定性构建;override机制可强制统一传递依赖版本。

Conan 用包名+版本号+设置组合唯一标识依赖
Conan 不靠“路径隔离”或“环境变量切换”,而是把每个库版本当作独立包来管理。关键在于:同一个库名(比如 zlib)的不同版本(1.2.11 和 1.2.13),在 Conan 里是完全不同的包,各自有独立的二进制缓存路径和依赖图节点。
它通过五元组唯一确定一个包实例:name/version@user/channel + settings(如 os=Linux, compiler=gcc, compiler.version=11)+ options(如 shared=True)。哪怕只改一个编译选项,Conan 就认为是新包,不会复用旧二进制。
- 默认 channel 是
_,所以zlib/1.2.11@_/_和zlib/1.2.13@_/_天然不冲突 - 如果你手动上传到私有仓库,可以加 user/channel 区分用途,比如
zlib/1.2.11@myteam/stable - 调用时写明版本约束:
requires = "zlib/1.2.11", "openssl/3.0.12",Conan 解析时按语义化版本规则匹配,不会跨主版本混用
多版本共存时,链接阶段怎么避免符号冲突
Conan 本身不解决 C++ 运行时符号冲突——那是链接器和加载器的事。但它能帮你避开绝大多数雷区:它默认生成静态库(shared=False),且每个包的头文件、库文件、CMake config 都严格按版本路径隔离。
当你同时依赖 libA(用 zlib/1.2.11)和 libB(用 zlib/1.2.13),Conan 的 conan install 会分别拉取两个 zlib 版本,并在生成的 conanbuildinfo.cmake 或 xxx-config.cmake 中为它们分配不同 target 名(如 zlib::zlib-1-2-11 和 zlib::zlib-1-2-13)。你的 CMakeLists.txt 必须显式链接对应 target,而不是笼统地 find_package(zlib)。
- 不要在项目中直接
#include <zlib.h></zlib.h>后又手动target_link_libraries(myapp PRIVATE zlib)—— 这会绕过 Conan 的版本绑定,导致链接到系统 zlib 或某个“胜出者” - 务必用 Conan 提供的 generator(如
CMakeDeps+CMakeToolchain),让 CMake 知道每个依赖的精确头文件路径和库路径 - 若必须动态链接,确保两个 zlib 版本都以
.so.1.2.11和.so.1.2.13命名,并在LD_LIBRARY_PATH中只放你当前构建所用的那个路径
为什么 conan lock 能锁定多版本共存行为
conan lock 生成的 conan.lock 文件不是“版本快照”,而是整个依赖图的**确定性快照**:它记录了每个包的完整引用、所有 settings/options、以及每个包依赖的子包具体是哪一个 hash 实例。这意味着,即使你本地 cache 里删掉了 zlib/1.2.11,只要 lock 文件存在,conan install --lockfile 就能从远程仓库精准拉取那个 exact 二进制(或触发重编译)。
- 没有 lock 文件时,
zlib/1.X这种模糊版本约束可能今天解析成 1.2.11,明天变成 1.2.13(取决于远程仓库最新版) - 有了 lock,即使你新增一个依赖引入了
zlib/1.2.13,Conan 也会检查是否与已有zlib/1.2.11冲突;如果冲突(比如 ABI 不兼容),它会报错,而不是静默覆盖 - 企业级 CI/CD 中,
conan.lock必须随代码提交,否则不同机器上构建结果不可重现
容易被忽略的坑:transitive 依赖的隐式升级
最常踩的坑不是你主动写了两个 zlib 版本,而是 A 库声明依赖 zlib/1.2.11,B 库声明依赖 zlib/1.2.13,而你自己的 conanfile.py 没写 zlib ——这时 Conan 默认启用“版本范围合并”,试图找一个满足两者的版本(比如 zlib/1.2.12),结果两边都得不到想要的 ABI。
解决方式只有两种:要么强制指定你认可的版本(requires = "zlib/1.2.13"),要么禁用自动合并,在 conanfile.py 里加:python_requires = "base-conan/1.0@myteam/stable" 并在 base 中设 default_options = {"zlib:shared": True};但更稳妥的是用 override=True 显式接管:
requires = (
"libA/1.0",
"libB/2.1",
("zlib/1.2.11", "override"),
)
这个 override 表示:不管 libA 或 libB 声明要什么 zlib 版本,最终全部强制用 1.2.11。反过来也一样。不写 override,就等于把 ABI 兼容性交给 Conan 猜——它猜对了是运气,猜错了就是 segmentation fault。











