package_id 决定二进制是否复用,conan 默认按 major version 和 settings 粗粒度计算,易导致误复用或重复构建;修改 package_id()、切换 default_package_id_mode 或依赖版本变更均会触发重构建。

package_id 决定二进制是否复用,不手动干预时 Conan 默认按 major version 和 settings 粗粒度计算 —— 这常导致不该复用的包被复用,或该复用的却重新构建。
为什么改 package_id 会触发重构建
Conan 每次调用 conan create 或 conan install 时,会基于当前 settings、options、requires(依赖)和 package_id() 实现,算出一个唯一 hash 值作为 package ID。只要这个 ID 变了,Conan 就认为是新二进制,即使源码完全没动。
常见诱因包括:
- 修改了
package_id()函数逻辑(比如加了self.info.settings.compiler.version = "4.8") - 切换了
general.default_package_id_mode配置(如从semver_direct_mode改为full_version_mode) - 在
requires中升级了某个依赖的 minor 版本,而当前 mode 不包含 minor —— 切到full_version_mode后 ID 就变了
怎么用 package_id() 做兼容 fallback
当你想让 gcc 4.9 的构建“降级”复用 gcc 4.8 的二进制时,不能只靠配置,必须在 recipe 里显式声明兼容包:
def package_id(self):
if self.settings.compiler == "gcc" and self.settings.compiler.version == "4.9":
for version in ("4.8", "4.7"):
compatible_pkg = self.info.clone()
compatible_pkg.settings.compiler.version = version
self.compatible_packages.append(compatible_pkg)
注意点:
-
self.info.clone()是必须的,直接改self.info会影响主 ID 计算 - 兼容包只在查找阶段生效,不会改变当前包的原始 ID
- 如果同时用了
validate()排除某些平台(如raise ConanInvalidConfiguration("Windows not supported")),记得也在package_id()里同步排除,否则 Conan 仍会尝试匹配已知不支持的组合,浪费时间
不同 default_package_id_mode 对依赖版本的敏感度
Conan 2.x 默认用 semver_direct_mode:只看直接依赖的 major 版本(如 zlib/1.2.13 → 只认 1.*),minor/patch 和间接依赖全忽略。
若需更严格控制,可全局设为 full_version_mode:
conan config set general.default_package_id_mode=full_version_mode
效果是:
-
zlib/1.2.13和zlib/1.2.14生成不同 package ID - 传递依赖(如 zlib → cmake → openssl)的版本也会参与计算
- CI 中 lockfile 更稳定,但二进制缓存命中率下降,首次构建变慢
不建议在 conanfile.py 里硬编码 mode 切换,它属于环境策略,应由 CI 配置或 profile 统一管理。
容易被忽略的 settings 影响点
package_id() 默认把所有 settings(如 os, arch, compiler, build_type)都纳入计算,但有些组合实际不影响二进制内容 —— 比如头文件库(nlohmann/json)根本不需要编译,compiler 和 build_type 其实是冗余维度。
这时可以主动精简:
def package_id(self):
self.info.clear() # 清空全部默认字段
self.info.settings.os = self.settings.os
self.info.settings.arch = self.settings.arch
关键提醒:
-
self.info.clear()后必须显式保留至少一个字段,否则 ID 变成空字符串,所有构建都撞同一个包 —— 极易引发链接冲突 - 一旦清空,
requires的版本信息也不再自动参与计算,如有必要得手动加入:self.info.requires["zlib"].full_package_mode() - 这种精简只适用于纯头文件库;含编译逻辑的包(如 fmt、spdlog)仍需保留
compiler和build_type











