这次完善了什么
原笔记已经记录了数学库、可执行文件、动态库和制品库上传,但主体是 Conan 1.x,安装与个别配置却混入了其他版本写法。这次保留原 URL、分类与封面,围绕一个问题重新整理:团队里的另一个人,怎样拿到正确且可追溯的 C++ 制品,而不是“恰好能链接”的某个库?
| 范围 | 本文约定 |
|---|---|
| 主线 | Conan 2.x;配套教学环境固定 Conan 2.15.0,不是声称它是最新版本 |
| 资料 | 2.15 系列官方文档;补充引用滚动 /2/ 页面时,以实际安装版本的帮助和实验为准 |
| 原内容 | 数学库、几种打包类型与发布流程继续保留并更新;原 Conan 1.x 笔记快照 仅供历史查询 |
| 本机实验 | 见文末验收记录;不将源代码检查当成运行成功 |
| 高通板卡 | 型号与 SDK 尚未确定;交叉 profile 是填写模板,不是已通过的高通部署配置 |
| 远程发布 | 仅说明流程;本轮不登录、不上传、不删除任何远程制品 |
旧目录书签仍保留兼容锚点。新内容由侧边目录与下面的查询表导航,不再把一份很长的手工 Markdown 目录塞在正文前。
按问题查询
| 问题 | 入口 |
|---|---|
| 制品和源码仓库、Docker 镜像有什么不同? | 分工与全流程 |
同样是 mymath/1.0,为什么不是同一个包? |
RREV、package ID、PREV |
| settings、options、profile 怎样影响结果? | 配置与兼容性 |
| conanfile.py 每个方法什么时候执行? | 配方生命周期 |
| 怎样自己打包并让另一项目调用? | 可复现实验、CMake 消费 |
| 为什么“锁版本”还不能锁住所有二进制? | lockfile 的边界 |
| 怎样发布到团队制品库? | 远程仓库与发布 |
| 工具、动态库、Header-only 怎么分别处理? | 不同制品类型 |
| 高通 / Yocto SDK 如何接进来? | 交叉编译与 SDK |
| CI、离线部署和缓存怎么做? | 交付与追溯 |
| 找不到包、链接失败、运行缺库? | 排错索引 |
| 旧 1.x 命令怎么迁移? | 迁移对照 |
| 能否把常用库预先做好,换环境直接用? | Docker 常用制品架 |
1. 制品管理究竟管理什么
1.1 用一个实际场景理解
我写了 mymath,另一个机器人项目需要它。把 libmymath.a 发给对方还不够,对方还需要知道:
- 哪些头文件与它对应,用什么名称链接。
- 它面向 Linux、Windows 还是其他系统,是什么 CPU 架构。
- 编译器、C++ 标准库 ABI、Debug/Release、静态/动态选项是什么。
- 它依赖哪些库,依赖又对应哪些版本和配置。
- 这个二进制来自哪份源码、哪份配方,是否经过测试。
- 有没有权利使用和重新分发它。
Conan 将这些问题组织成配方、依赖图、二进制包和仓库操作,但不会替你证明所有 ABI 假设都正确。配方建模漏了一项,工具也可能把本来不兼容的包视为同一种配置。官方二进制模型
1.2 各种工具的边界
| 工具/产物 | 主要负责 | 不直接替代什么 |
|---|---|---|
| Git 仓库 | 配方、源代码、配置与变更历史 | 二进制矩阵、运行环境验证 |
| CMake / Ninja | 生成与执行编译、链接、安装规则 | 远程制品权限和依赖版本发布 |
| Conan | 解析 C/C++ 依赖,构建/获取适配配置的包 | 操作系统镜像、内核与板级驱动 |
| Artifactory 等 Conan remote | 存储与分发配方、二进制、相关元数据 | 替用户设计 package ID |
| Docker 镜像 | 固化应用或构建工具的用户空间环境 | 自动解决目标板 SDK / ABI 差异 |
| Yocto / BitBake | 构建目标 BSP、系统软件包、rootfs、SDK | 自动将任意 Conan 二进制变为适配板卡的系统包 |
因此,制品库不只是一个网盘;Conan 缓存也不等于产品部署目录。
1.3 把生产端和消费端串起来
生产端: |
这里有两次“安装”:CMake 的 install 将库文件放进包暂存目录;conan install 解析并取得依赖、生成构建环境。两者都不等于把完整程序部署到开发板的 /usr。
2. 一个 Conan 制品的完整身份
2.1 五层身份,不只 name/version
mymath/1.0@myteam/stable#<RREV>:<PACKAGE_ID>#<PREV> |
| 字段 | 回答的问题 | 容易误会的地方 |
|---|---|---|
name/version |
哪个逻辑组件、哪个发布版本? | 相同版本号不保证源码与配方从未改变 |
可选 user/channel |
配方引用的额外命名空间 | stable 只是字符串,不自动表示已审核或有权限隔离 |
| RREV(recipe revision) | 哪次配方/导出源码修订? | 不天然等于 Git commit;外部下载源码还需单独固定 |
| package ID | 按所建模的 settings/options/依赖等区分哪种二进制? | 不是包文件内容的 SHA,也不读取编译器真实能力 |
| PREV(package revision) | 这份包目录内容的哪个修订? | 相同 RREV 与 package ID 重新构建,仍可能因非确定性产生不同 PREV |
默认内容哈希模式下,配方和导出源码的变化会影响 RREV;source() 从外部下载的源码应使用不可变 commit、固定归档及校验值,不要把浮动分支内容藏在不变的 recipe revision 后面。PREV 标识包内容,而不是“这个版本更高级”。官方 revisions 说明
2.2 三个变化,分别看什么
| 操作 | 通常先观察的字段 | 前提 |
|---|---|---|
改已导出的 mymath.cpp,版本号仍是 1.0 |
RREV 变化 | 使用默认内容哈希模式,文件确实在导出集合中 |
| 同一配方改成 Debug 或 shared=True | package ID 变化 | 配方保留这些配置进入身份计算 |
| 同一 RREV、同一配置重新编译 | PREV 可能变化,也可能相同 | 取决于生成内容是否相同,不应伪造“必定变化”的实验结论 |
RREV 不同的时候,即使 package ID 字符串相同,它们也位于不同配方修订下面,不能视为同一完整制品。
2.3 查询时把通配符加引号
在配套实验 shell 中,以下只查询本地缓存:
conan list "mymath/*" |
第三条适合看到完整层次。引号可避免 zsh/bash 提前展开 *。conan list 查配方/包身份;conan graph info 看某个消费者实际解析到的依赖图,两个视角不能互相替代。list 命令
3. 配置、ABI 与双 profile
3.1 settings、options、conf 的区别
| 层次 | 例子 | 应如何理解 |
|---|---|---|
| settings | os、arch、compiler、build_type |
环境/二进制兼容性维度;配方声明使用哪些 |
| options | shared、fPIC、with_tools |
配方公开的可选功能,消费者可以选择 |
| conf | CMake generator、工具链路径、作业数 | 构建行为配置;并非全部默认进入 package ID |
| profile | 上述选择的受控集合,还可带构建环境 | 文件名称本身不是 ABI 保证 |
不要为了“复用缓存”随意删除 compiler、arch 或 build_type。例如把 ARM 库的身份写成 x86_64,不会转换机器码,只会给错误的包贴上另一个标签。
同样,-s:h compiler.version=11 只是声明配置,不负责安装或切换 GCC 11。必须使实际调用的编译器、参数和 sysroot 与声明一致。
3.2 build 不是 Debug,host 也不是运行 Conan 的机器
在交叉编译语境下:
build profile:构建过程中要运行的工具 |
# 本机实验中两者相同;交叉编译时换成两份明确的 profile |
普通 requires 和构建用 tool_requires 需要区分;本机工具不能因为“目标是 ARM”就也变成无法在 build 机运行的 ARM 可执行文件。build_type=Debug 则是另外一个配置维度。双 profile 官方教程
3.3 profile detect 是起点,不是团队配置规范
conan profile detect 适合给空白教学环境生成初始配置。它不会知道团队采用哪个 Yocto SDK、哪个标准库 ABI、哪个板卡镜像。
实验脚本在独立 CONAN_HOME 中生成 profile;不覆盖你已有的默认配置。正式工程应审查并版本管理 profile、settings 扩展和关键 conf,记录真正使用的编译器版本。对于 SDK,后面的 交叉编译节 还会说明为什么路径不同未必能阻止误用缓存。
4. 配方生命周期:每个方法各管一件事
4.1 先分清描述阶段和执行阶段
| 入口 | 主要职责 | 不宜做的事 |
|---|---|---|
类属性、config_options() |
定义元信息与目标平台可用选项 | 在此下载/编译工程 |
configure() |
按配置移除不适用选项、整理逻辑 | 偷偷将不兼容配置伪装成兼容 |
requirements() |
声明库/运行依赖 | 用手工拷库掩盖依赖关系 |
build_requirements() |
声明构建工具、测试所需依赖 | 混淆 build 与 host 工具 |
validate() |
拒绝不支持的标准、编译器、组合 | 将校验推迟到难懂的链接失败 |
layout() |
定义 source/build/generators 等位置 | 在其他方法中手猜缓存路径 |
source() / exports_sources |
获取源码/导出本地源码 | 使用无法追溯的浮动下载物 |
generate() |
生成 toolchain、依赖查找、环境文件 | 认为生成成功就已完成编译 |
build() |
调用真实构建系统 | 将所有缓存操作塞到配方里 |
package() |
将应交付文件装入 package folder | 打包整棵 build tree 和临时缓存 |
package_info() |
向消费者声明头文件、库、组件等接口 | 写死本机绝对缓存路径 |
这张表是职责速查,不是对所有命令都严格相同的一条执行顺序。消费已有二进制时不会再执行它的 build();本地开发命令、缓存构建和下载消费走的路径也不同。官方创建配方教程
4.2 export、create、install、build 不等价
| 命令 | 主要作用 |
|---|---|
conan export |
导出配方及其声明的导出内容;不代表已经有二进制 |
conan create |
在缓存流程中创建包,并按配置执行 test_package |
conan install |
解析依赖、取得或按策略构建依赖、生成消费环境 |
conan build |
使用消费/开发工作区的配方执行本地构建 |
conan export-pkg |
将按约定准备的预编译产物打包入缓存;不是 ABI 检测器 |
conan upload |
发布选中的配方/二进制;不替代测试与权限审批 |
exports_sources 控制源码如何进入构建,不决定最终包包含哪些文件;最终交付由 package() 和 CMake 安装规则共同决定。打包时也应带上适用许可证,不能仅写一个 license="MIT" 就当作处理完了。
5. 用一个完整小工程跑通打包
5.1 下载内容与环境
可以直接下载 完整实验源码 ZIP,其中包含 Dockerfile、运行入口、库、消费者、test_package 和自动验证脚本。详细 实验说明 也可单独查看;站点源码中的位置为 source/downloads/code/conan2_lab/。
conan2_lab/ |
从本站源码仓库根目录,或 ZIP 解压后的 conan2-lab 根目录运行:
# 第一次构建会下载 Ubuntu 构建工具及固定版本 Conan |
这个 lab 不修改已有 MuJoCo/飞控镜像,不在 macOS 宿主安装编译器或 pip 包。Conan 版本固定;APT 和 Python 间接依赖仍需记录实际版本,因此这里只承诺可复现工作流,不声称每次重建 Docker 镜像都逐位相同。
交互 shell 的源码挂载是只读的。先复制到容器内可写工作区,再操作:
# 仅在上面打开的新实验容器内执行;每个新 shell 执行一次 |
后续手动命令默认位于这个 /work/demo。退出容器后实验缓存与工作区消失;自动测试的报告保留在宿主 build/conan-lab/,不把 Conan 缓存或编译出的 ELF 文件发布到网页。
5.2 CMake 的安装规则就是包内容边界
库的 CMakeLists.txt 将源代码编译成 mymath,声明头文件和 C++17 要求,并把 archive/shared/runtime 文件装到相应目录。关键结构是:
add_library(mymath src/mymath.cpp) |
上面是完整文件的节选。BUILD_INTERFACE 用于源码树里的构建,INSTALL_INTERFACE 描述安装后的相对布局;不能把本机源码树的绝对 include 路径写进对外接口。Windows 动态库还需正确导出符号;本轮 Linux 小实验不等于已经覆盖 Windows DLL 场景。
5.3 看懂新的 conanfile.py
完整库配方 中最重要的关系是:
from conan import ConanFile |
这仍是教学节选,不能替代完整配方;下载文件还包含 exports_sources、许可证复制、config_options/configure 和 package()。
实验采用 set_version() 提供默认值,允许后面用 --version=1.1 创建对照版本。若配方直接硬编码 version="1.0",Conan 2.15 会拒绝与它不一致的命令行版本,不能把两种写法混在同一操作流程里。
这里同时存在两套接口:
- 生产端的 CMake 安装规则:决定实际打包什么文件。
package_info():决定消费者怎样找到和链接这些文件。
只填 self.cpp_info.libs=["mymath"] 不会自动生成 libmymath.a;反过来,库文件确实存在,也不代表消费者知道它的依赖与 CMake target。
5.4 创建 Release 与 Debug 包
conan create mymath --version=1.0 \ |
观察点不是“打印了绿色成功”,而是:同一源码/配方修订下,Debug 与 Release 分别有自己的 package ID,并且 test_package 能真正链接包中的库。具体哈希取决于配方、工具链与配置,不要在教程里硬编码他人机器的 package ID。
选项的操作形式则是:
# 可选练习:Linux 实验环境中再构建共享库变体 |
shared 与 fPIC 的关系由配方管理。不能把 fPIC 当成“加速开关”,更不能认为改 package ID 就能修复缺少 PIC 的静态库。
6. 真正从另一项目消费制品
6.1 CMakeDeps 与 CMakeToolchain 各做什么
| 生成器 | 主要作用 | 典型问题 |
|---|---|---|
CMakeDeps |
将依赖接口转换成 CMake 可查找的配置与 targets | find_package 找不到包、target 名不匹配 |
CMakeToolchain |
将构建配置转成 CMake toolchain/presets 等 | 编译器、架构、标准、sysroot、构建目录不一致 |
VirtualRunEnv |
提供运行依赖需要的环境 | 链接成功后仍找不到共享库或工具 |
生成的文件位置与 layout()、--output-folder、CMake generator 相关;不要把某次生成目录写死成所有项目的规定。Conan 2 CMake 集成
6.2 消费者不应引用缓存绝对路径
消费者配方 声明 mymath/[>=1.0 <2.0],使用 cmake_layout 并生成两个 CMake 集成文件。消费者 CMake 文件 的核心很短:
find_package(mymath CONFIG REQUIRED) |
这样消费端不必知道缓存中 p/ 目录的哈希布局。不要改成 include_directories(/某人的缓存/...) 或 link_directories 全局堆路径,否则换一台机器就暴露问题。
在手动实验工作区中,先消费已经存在的 Release 包:
conan install consumer -of out/consumer-release \ |
最后的路径对应本实验的 Linux、单配置 CMake 与 cmake_layout,不是跨 generator 的通用路径。静态库基础实验不需要复制共享库;动态依赖则需看实际 run environment 与部署布局。
6.3 test_package 测的不是源码目录能否编译
完整 test_package 配方 采用 Conan 2 的显式被测引用:
def requirements(self): |
这是关键:测试依赖的是刚创建的那个包,不是通过相对路径顺手连上生产工程尚未打包的库。它可以发现漏装头文件、错误的 cpp_info.libs、消费者 target 不正确等问题。
但 test_package 仍不等于:
- 所有业务单元测试和压力测试都完成。
- 对方机器的 GPU / DSP / 驱动可用。
- ARM 程序已经在板上执行。
- 打包许可证与供应链审核已完成。
交叉编译时用 can_run(self) 判断是否执行目标程序;跳过运行仅保留编译/链接证据,不能算上板验证成功。官方 test_package 模式
7. Lockfile:锁住依赖选择,不锁住整个世界
7.1 固定版本号为什么还不够
requires="mymath/1.0" 固定逻辑版本,但同一版本还可能有新的 recipe revision。范围依赖 mymath/[>=1.0 <2.0] 更会受到可用新版本影响。
Conan 2 的 lockfile 记录解析出的依赖版本与配方修订。它不锁定 package ID / PREV,也不完整记录构建 profile、工具链、操作系统镜像和所有源码输入。不能把它当成“整套构建环境压缩包”。官方 lockfile 教程
7.2 在同一个缓存里做对照实验
先只有 mymath/1.0 时,为 consumer 生成 lockfile:
conan lock create consumer \ |
然后发布一个仅位于本地缓存的 1.1:
conan create mymath --version=1.1 \ |
分别检查未锁与锁定的依赖图:
# 明确不用 lockfile,避免默认发现 consumer/conan.lock |
预期:前者按范围选择可用的 1.1,后者仍解析到锁定的 1.0 与对应 RREV。自动脚本会检查真实 JSON 结果,不把这段预期文字当成验收日志。
7.3 同一把锁仍然可以选择不同配置的二进制
给同一 consumer lockfile 换 Debug:
conan install consumer -of out/consumer-debug \ |
如果之前已经构建了 1.0 的 Debug 包,这一步可以获得它;锁的是依赖版本/修订,而非把整个应用强制变成 Release。
再试没有构建过的 RelWithDebInfo:
# 故意制造缺失配置;非零退出是这个练习的预期结果 |
在本实验默认身份规则、没有该变体的缓存中,应看到缺失二进制错误。一般项目可能配置兼容性回退,应以真实 graph 的结果为准。
7.4 升级锁也需要审查
正式更新时保留旧 lockfile,通过明确的解析策略生成候选新锁,比较依赖图并执行测试,再一起提交。不要为了让 CI 通过就删除锁,或默认加 --lockfile-partial。
条件依赖也有影响:一份只解析过 Linux 配置的锁,不一定包含 Windows 专属依赖。团队应覆盖实际矩阵并按需要合并锁。要追溯到已经发布的具体二进制,还应记录完整制品引用/PackageList、profile 与工具链信息,不能只保存 conan.lock。
8. 团队制品库:发布什么,比上传命令更重要
8.1 本地缓存、团队库和 ConanCenter
| 位置 | 使用方式 | 边界 |
|---|---|---|
| 本地 Conan cache | 开发、构建、消费缓存 | 不是团队权威发布源,不直接编辑缓存内部结构 |
| 团队 remote | 由服务器管理账号、读写权限和包内容 | URL 与支持的 Conan 版本由管理员确认 |
| ConanCenter | 获取公共包;贡献配方向 conan-center-index 提 PR | 不是给任意客户端直接 upload 的个人空间 |
ConanCenter 已转向 center2.conan.io;旧 center.conan.io 已冻结更新。应检查当前客户端的 conan remote list,不要从旧笔记照搬旧地址。ConanCenter 官方说明
Artifactory 是一种可选后端,Nexus 也需要按实际版本、授权与仓库类型核对 Conan 2 支持,不能把旧文中的占位 URL 当成可用服务器。Nexus 3.76.0 的官方发布说明记录了 Conan 2 支持的引入;具体使用限制以所部署服务器的文档为准。Nexus 发布说明、Conan 仓库配置
8.2 配置与认证,不把 token 写进笔记
以下是未执行的远程流程模板,不在无网络的教学容器里跑。先获得团队确认的服务器 URL、账号和发布权限:
: "${CONAN_REMOTE_URL:?填写管理员提供的 Conan 2 仓库 URL}" |
交互认证时不把密码写成命令行参数。CI 可以使用受保护的 secret 注入 Conan 支持的登录环境变量,再用 conan remote auth;不要打印环境、开启包含 secret 的 shell trace,或把凭据放入 Dockerfile 的 ARG/ENV、源码和公开报告。
TLS 校验失败时先检查证书链与代理配置,不默认用 --insecure。user/channel 也不是权限控制,谁可以读、写、删除由服务端决定。Conan 2.15 remote / 认证
8.3 从已验证的完整引用生成发布清单
不要用 conan upload "*" 当成默认发布步骤,它可能把实验包和无关版本一起推上去。
先从本次验收结果取得一个具体的完整引用,不是只有 name/version:
# 例如值应包含 name/version#RREV:PACKAGE_ID#PREV |
检查 JSON 是否只含预期制品。多平台 CI 应汇总经过验收的 PackageList,不要靠 shell 通配符猜测“本轮构建了哪些东西”。
对一个完整引用运行 conan list 得到的是该制品的清单,不是它的完整依赖闭包;上传这份清单也不会自动递归发布所有依赖。必须确认依赖已存在于批准的仓库,或把本次生产且验收过的依赖一起纳入发布清单。
准备发布:
# 这一步仍可能准备本地压缩文件并访问服务器,不是完全离线检查 |
审核清单和目标后,才真正上传:
# 远程写操作:仅在获得发布授权后执行;本文未执行 |
Conan 2 的 --only-recipe 用于仅上传配方;不应继续沿用旧版的 --all 思路。上传 recipe 不保证其外部 source() 下载物都已归档,也不能保证用户不需要重新编译。--check 检查完整性,不等于漏洞扫描、许可证审查或数字签名。upload 命令与 PackageList
8.4 上传完成后,换一个干净消费者验证
应在独立缓存中,使用批准的 remote、profile 和 lockfile 安装,设置 --build=never,再编译与运行消费测试。这样才能暴露“发布只传了 recipe,二进制没传”“缺了某个平台”“开发者本机缓存掩盖了缺包”等问题。
至少记录:
- 服务端实际存在的 RREV / package ID / PREV。
- 消费端解析到的完整引用是否对应发布清单。
- 是否发生了意料之外的源码重建或从其他 remote 获取。
- 消费测试、运行库路径和真实版本输出。
本轮没有团队远端凭据,因此只验证本地生产消费流程,远端阶段仍是待执行的验收单。
9. 原来的几种打包场景,怎样用 2.x 正确理解
9.1 静态库、动态库与运行时依赖
| 类型 | 应交付的典型内容 | 特别关注 |
|---|---|---|
| 静态库 | 头文件、.a/.lib、接口元数据、许可证 |
链接依赖传递、PIC、编译器与 ABI |
| 动态库 | 头文件、共享库,Windows 通常还需 import library | DLL 导出、SONAME、RPATH、运行库与符号版本 |
| 可执行工具 | 可执行文件、必需资源与运行依赖 | 是 build 机工具还是 host 机应用 |
| 混合包 | 库、可选工具、各自依赖 | 是否需要拆包/components,选项是否完整 |
| Header-only | 头文件、必要配置与许可证 | 不一定需要多种二进制,但仍需验证使用方标准与依赖 |
链接成功只是第一步。共享库在目标机运行时还要被动态加载器找到;Linux 的 libstdc++ 符号版本、目标 glibc、Windows CRT、RPATH 都不应被“Conan install 成功”掩盖。
需要运行环境时,可在配方中使用 VirtualRunEnv,执行测试时用 self.run(..., env="conanrun")。它负责开发/测试环境,不等于最终产品安装器;部署时仍要设计目标目录、运行库和服务配置。VirtualRunEnv
9.2 可执行工具:requires 还是 tool_requires
编译时运行的代码生成器,应放进 build context;部署到机器人上的命令行程序则是 host 产物。它们有时来自同一个源码仓库,但运行机器不同。
旧文“可执行文件通常只要 os/arch,不用 compiler/build_type”的表述过于宽泛。若动态依赖特定 C++ runtime、glibc 或特定编译选项,就仍需准确建模。减少 settings 之前,先证明被合并的变体确实兼容。
Conan 2 的目录接口是 cpp_info.bindirs;需要额外运行环境时使用 runenv_info / 对应环境生成器,不用旧式 env_info。仅声明一个 bin 路径也不会自动让任意 shell 都拥有正确 PATH。
9.3 预编译库不是按文件后缀分类就够了
使用 conan export-pkg 或自定义打包 recipe 接收供应商二进制前,先整理:
| 核对项 | 为什么重要 |
|---|---|
| 来源、版本、SHA-256、许可证 | 确认拿到的文件和分发权限 |
| ELF / PE / Mach-O 架构 | 后缀为 .so 不代表就是目标 ARM 库 |
| 编译器/标准库 ABI/系统 baseline | 同 CPU 架构不保证链接与运行兼容 |
| 所需共享库、插件、资源、固件 | 单个库文件未必构成完整运行环境 |
| Debug/Release、shared/static、PIC 等 | 不能为所有配置复用一份不匹配二进制 |
| 消费者编译/链接/运行测试 | 包装成功只说明文件进入缓存 |
必须拒绝不受支持的配置,不能让一个 Linux x86_64 的预编译目录在 arch=armv8 下被无条件复制并发布。
9.4 Header-only:去掉无关二进制维度,不去掉验证
真正与平台无关的纯头文件库可使用 package_type="header-library",不声明不必要的二进制 settings,并把无意义的 cpp_info.libdirs/bindirs 设为空。
如果为了测试而声明了编译器配置,package_id() 可以按实际内容去除不影响交付文件的维度;但不能不加判断地 self.info.clear()。生成的配置头、条件依赖或嵌入平台差异时,所谓“头文件库”也未必完全相同。
需要 C++17 时,使用配方验证与 CMake 的 compile features 表达要求,不向所有消费者盲目拼接 -std=c++17。官方 Header-only 模式
9.5 混合包、components 与条件依赖
当一个包同时有 core 库、插件和命令行工具时:
options显式控制可选功能,并传到真实构建系统。requirements()根据实际选项声明依赖;不要声明后却不链接,也不要链接了却不声明。- 需要独立消费接口时,使用
cpp_info.components描述各组件及依赖,避免消费者被迫链接全部内容。 cpp_info.system_libs是明确声明已知的系统链接库,不是“自动检测系统依赖”。- 仅当需求确实如此才声明
pthread/dl/m等,不能对所有平台无条件添加一串库。
这些是扩展设计入口;本文最小实验只用单库,没有伪装成已测所有 components 和平台矩阵。
10. 高通板卡与 Yocto SDK:怎样避免错误的“能编过”
10.1 谁负责哪一层
高通 Yocto 元数据笔记 解释了 layer、machine、distro、image 和 SDK。这里补上应用制品这一层:
匹配板卡的 Yocto BSP / 镜像 |
尽量避免让 sysroot 和 Conan 同时、不受控地提供两个 OpenSSL、两个 protobuf 或两套 C++ runtime。先约定每个依赖由 BSP 还是 Conan 管理,再核对 CMake 实际选择的文件。必要的系统库封装 recipe 应明确其环境前提,不能靠 package_info 伪造已存在的软件。
10.2 host profile 填写模板
下列 ini 只是待填写模板,不能直接运行。没有确定高通型号、BSP release、SDK 与目标镜像前,不填一个猜测的 GCC 版本:
# profiles/yocto-host:占位符必须替换为 SDK 的真实值 |
常见 AArch64 在 Conan 中使用 armv8,但仍须按实际工具链核对。tools.info.package_id:confs 让受控 SDK 标识进入二进制身份,是基于官方扩展机制给出的工程建议,不是已经验证的高通 profile。创建端、消费端以及相关 host 依赖必须一致采用;修改了 SDK 却不更新标识仍然会犯错。Conan 2.15 配置项、扩展二进制模型
10.3 不要让两份工具链互相覆盖
user_toolchain 会让 Conan 生成的 CMake toolchain 包含指定 SDK toolchain;它不意味着后续所有生成块都不会改 flags。
如果 SDK 实际提供 OEToolchainConfig.cmake,就采用安装后的真实路径,并提供其需要的 SDK 环境。不要仅凭这个文件名猜安装位置。要检查:
CMAKE_C_COMPILER / CMAKE_CXX_COMPILER是否是目标 SDK 编译器。- sysroot、CPU flags、标准库 ABI、链接器选项是否保留。
find_package与 pkg-config 是否误选宿主库。- Conan 的 libcxx/flags/安装路径生成块是否与 SDK 冲突。
- 最终 verbose 编译命令与
CMakeCache.txt是否一致。
enabled_blocks 可以进一步定制,但本文不提供一份“所有 SDK 通用”的删减列表。直接设置 toolchain_file 整体替换 Conan toolchain 又是另一种方案,依赖查找与配置传递需要自己负责。Conan 2.15 CMakeToolchain
10.4 双 profile 不会自动隔离 shell 环境
Yocto 环境脚本可能将编译器、CPU flags 和 sysroot 一起放入 CC/CXX。不要把完整命令字符串当成 tools.build:compiler_executables 里的“可执行文件路径”。
更可控的做法,是将核对后的 SDK 环境放入 host profile 的 [buildenv],覆盖 SDK 所需的编译器、flags、PATH、sysroot、pkg-config 等内容。构建本机工具的 build profile 则保持原生环境。
如果选择先 source environment-setup-* 再运行 Conan,要意识到:进程继承的全局环境不会因写了 -pr:b / -pr:h 就自动消失,可能污染本机工具。清理/覆盖哪些变量应按实际 SDK 审查,不给出一个随意清空所有环境的脚本。Conan profile 环境配置
确认两份 profile 后,命令形式才是:
# 未在高通 SDK 中执行:必须先创建并审查这两份 profile |
初次移植可能确实需要构建缺失依赖,应另行批准并使用匹配 SDK 创建对应制品。--build=missing 不会修正不完整的 ABI 建模;已有“看似匹配”的错误制品仍可能被复用。
这里复用 lockfile 的前提是它覆盖目标 profile 的条件依赖;真实项目需按 锁文件矩阵说明 生成/合并并审查相应依赖锁,不能假设本机配置的锁天然完整。
10.5 编译、可执行、上板成功是三层证据
test_package 可以先完成交叉编译/链接;执行阶段采用:
from conan.tools.build import can_run |
can_run=False 只是明确不在构建机运行目标程序;True 也不会自动提供 QEMU、目标动态链接器或驱动。上板后还应核对 ELF 架构、解释器、共享库版本、驱动接口、输入输出、重启持久性与压力运行。
针对 ONNX/QNN 等推理部署,还要单独验证模型算子、执行后端与 runtime/驱动配套。Conan 成功打包不代表 NPU 加速已经生效。
11. 从开发缓存到可追溯交付
11.1 CI 需要固定的不只有 conan.lock
| 记录项 | 用途 |
|---|---|
| 源码与 recipe commit、干净工作树/补丁 | 对应真正构建的输入 |
| Conan 版本、Python 环境、CMake、编译器 | 定义工具行为,避免客户端升级导致差异 |
| build / host profiles、settings 扩展、关键 conf | 固定二进制配置及 SDK 边界 |
| lockfile | 固定已解析依赖版本/RREV |
| remote 地址与优先级策略 | 防止同名包来自意外来源 |
| RREV、package ID、PREV、发布 PackageList | 追溯具体交付制品 |
| Docker 基线、SDK 摘要、sysroot 契约 | 防止相同名字对应不同工具链 |
| 构建、test_package、消费、运行日志 | 证明验证实际发生 |
| 许可证、来源、内部审核与回滚清单 | 支持合规交付和故障恢复 |
这是本文的工程检查表,不是 Conan 通过一条命令自动保证的属性。哈希能说明内容一致,不能单独证明发布者可信。
11.2 缓存可以加速,但不是共享工作目录
Conan 2.15 的 package cache 不支持并发访问;并行 CI job 应使用各自独立的 CONAN_HOME,复用通过受控的缓存保存/恢复或远端下载实现。不能仅仅规定“不同时手改文件”,却让多个 Conan 进程并发操作同一缓存。2.15 官方说明
不要遍历缓存内部目录拷走看起来像 lib 的文件,并把这些路径写进构建脚本。
conan cache save/restore 可用于按工具支持的方式搬运缓存内容;它们不是通用安装器,也不是跨客户端版本、跨 ABI 的兼容性承诺。离线工作流还需要保证依赖图中的配方和对应二进制完整、profile 一致,并在断网环境实测。cache 命令
11.3 --build=missing 和 --build=never 分别适合什么
| 策略 | 合适的阶段 | 风险/检查 |
|---|---|---|
--build=missing |
受控开发或专门的制品生产任务 | 可能拉源码重建,带来时间、许可证、工具链差异 |
--build=never |
检查制品矩阵完整性、受控制品消费 | 缺包就失败,需要先补对应配置 |
| 强制重建指定包 | 修改配方或排查构建输入 | 避免每次重建全部依赖产生无用修订 |
“missing”不等于“重新验证所有已经命中的制品”。如果 SDK 差异没有进入兼容性模型,重用错误二进制时不会因为写了这个选项就自动重建。
11.4 最终部署不直接带走 Conan cache
应用交付应有明确的目录和清单,例如程序、共享库、许可证、配置、服务文件与版本记录。选择原生包、容器或 Yocto recipe 取决于项目的系统集成方式;不要把整个 CONAN_HOME 当作软件安装包。
对于机器人或嵌入式应用,还应记录驱动/固件需求、运行用户、设备权限、日志、watchdog、失败退出行为与回滚方式。构建成功解决的是“得到产物”,不是“设备上的行为已经可靠”。
12. 报错时按哪层排查
| 现象 | 优先核对 | 不要先做什么 |
|---|---|---|
| 找不到配方 | 引用名称、remote、认证、版本范围、lockfile | 直接删除整个缓存 |
recipe 找到却 Missing binary |
graph 的 settings/options、package ID、remote 上的变体 | 把 arch/compiler 改成别人的值骗过匹配 |
| 明明上传过还是缺包 | 是否只传 recipe;RREV 是否一致;消费 profile 是否不同 | 用无范围的通配符重新上传所有包 |
| 新版本发布了却仍是旧版 | lockfile、版本范围、缓存/remote 更新策略、实际 RREV | 把锁当作故障删掉 |
| 版本号相同却得到不同内容 | RREV / PREV、源码固定方式、非确定性构建 | 只比较 name/version |
find_package 失败 |
CMakeDeps 是否生成、toolchain 是否使用、CONFIG 名称 | 写死开发者缓存路径 |
| 找到库却 undefined reference | 导出符号、libs/components/系统依赖、ABI、静态链接依赖 | 反复调整全局 link_directories |
| 程序启动后缺共享库 | 运行环境、RPATH、SONAME、依赖库及系统 baseline | 只看编译时能否链接 |
ARM 产物报 Exec format error |
是否在 build 机运行 host 程序;ELF 架构 | 误当作文件权限问题 |
| 切换 profile 后 CMake 仍用旧编译器 | 独立构建目录、CMakeCache、toolchain、继承环境 | 在同一构建目录硬切编译器 |
GLIBCXX_* 或 glibc 版本不满足 |
目标 runtime、SDK baseline、预编译制品来源 | 从宿主随便复制一套系统库覆盖目标 |
| 共享库消费者运行失败 | DLL/共享库是否打包、符号导出、conanrun/部署布局 | 认为 cpp_info.libs 会自动解决运行环境 |
推荐依次保存:客户端版本 → profiles → graph JSON → 选中的完整引用 → CMake 配置/verbose 构建日志 → 运行时证据。含私有源地址的日志不要直接发布到公共博客。
# 在实验工作区中,只读查看配置与图;会解析本地配方 |
注意 Conan recipe 是 Python 代码。即使是“查看依赖图”的动作,也不能把不可信配方当成纯文本安全地执行;应审查来源并在受控环境中处理。
13. 原 Conan 1.x 内容的迁移对照
旧文没有丢弃:完整历史快照。它不是当前执行指南,也没有因保留而被宣称已经逐条验证正确。
| 旧文做法/问题 | 当前正文的处理 |
|---|---|
pip install conan 不固定版本,主体却写 1.x |
独立 Docker 实验固定 2.15.0,避免混装污染本机 |
from conans import ConanFile, CMake |
from conan import ConanFile 与 conan.tools.cmake |
generators="cmake"、conan_basic_setup() |
CMakeDeps / CMakeToolchain + find_package / imported targets |
self.copy(...) |
conan.tools.files.copy(self, ...) 或真实安装规则 |
conan profile show default |
明确 -pr:b / -pr:h |
conan create . user/channel |
2.x 的名称/版本/user/channel 参数;教学先省略可选命名空间 |
conan user ... |
conan remote login/auth |
conan upload ... --all |
选择具体引用/PackageList;必要时 --only-recipe |
conan search 当作所有本地查询 |
用 conan list 查询缓存制品、graph info 查询实际依赖 |
imports()、env_info、bin_paths |
按用途迁移到环境/部署机制和正确的 cpp_info.bindirs |
| 用 testing/beta/stable 名称代表已验收 | 将审核、权限和制品提升过程与命名分开记录 |
| 将 ConanCenter 当作普通上传仓库 | 通过 conan-center-index PR 与官方 CI 贡献 |
| “自动检测系统库”其实是手工添加链接库 | 改为明确依赖声明,不误导其能力 |
| 打包工具时无条件省略 compiler/runtime 维度 | 按二进制兼容性契约决定,不照搬泛化结论 |
迁移不是全局替换 import 就结束了。还需要重新审查 recipe 的生命周期、选项、package ID、工具链和消费测试。保留原项目时应单独固定其旧环境,再逐步迁移,而非在同一个全局 Python 环境混用两代接口。官方 Conan 2 迁移入口
14. 本文示例怎样验收
14.1 自动实验检查什么
脚本从独立源码副本与空白 Conan 缓存开始,断开容器网络后验证:
- 创建
mymath/1.0的 Release 与 Debug 包,执行各自的test_package。 - 比较真实 JSON:同一 RREV、不同 package ID。
- 创建 consumer 的 lockfile,再创建
mymath/1.1。 - 未锁的消费者解析并运行 1.1;锁定消费者仍解析并运行 1.0。
- 改动导出源码并再创建 1.0,检查 RREV 改变,旧锁仍保持原修订。
- 同一 lockfile 分别消费原有 Release 与 Debug 制品。
- 请求没有生成的 RelWithDebInfo,断言出现缺失二进制错误。
失败实验也必须检查具体错误,而不是把任何非零退出都叫作“符合预期”。2026-09-09 已在 Docker 内的 Linux ARM64 原生环境完成 24 项自动检查:Conan 2.15.0、GCC 11.4.0、CMake 3.22.1。包括 4 次创建与包消费测试、3 次消费者编译执行,以及预期缺失二进制的反例。
可查 真实验证摘要 与 完整命令日志。摘要记录源码、工具配置和日志哈希;可据此判断下载记录是否对应这版脚本。aarch64 原生编译不是 x86 → ARM 交叉编译,更不是高通板上实测。
此外,在另一份空白缓存中按第 5—7 节的手动命令独立复核,15 项检查通过:确认 conan build 的输出路径与运行结果、锁定前后版本选择、预期的缺失二进制,以及 Linux shared=True 的包消费测试。远程发布相关只检查 Conan 2.15.0 的 upload --help 参数,没有执行上传。见 手动流程复核摘要 与 命令日志。
两组实验的 profile 有一个有意保留的差异:自动 runner 显式指定 compiler.cppstd=17,手动流程使用检测得到的 gnu17。这也会影响 package ID,因此应比较同组实验中的制品身份,不要机械地对照两份日志的 ID。复跑入口、证据校验方式和完整 Dockerfile 都在前面的源码下载包内。
14.2 尚未覆盖的范围
这份最小实验不代表已经完成远程 Artifactory/Nexus 发布、Windows/macOS 原生库矩阵、高通 SDK 交叉编译或真机部署;也不代表产品级许可证、签名、漏洞和性能审核。
扩展到自己的项目时,先替换最小库,然后一次增加一个变量:第三方依赖 → shared → 工具包 → CI remote → SDK 交叉目标 → 板上验收。每一步保留输入、结果和失败证据。
15. 从教学实验到自己的常用制品架
前面的 mymath 用于理解机制;实际开发还需要一组经过统一配置和消费验证的常用库。现在另建 docker/conan-artifacts/:不修改原教学实验,也不直接替换现有机器人服务里的系统库。
15.1 第一批整理哪些库
| 库 | 固定版本 | 用途 |
|---|---|---|
| libxml2 | 2.13.8 | 现有辨识/标定工程使用的 XML API;本轮只预备 Conan 包,未替换原 APT 依赖 |
| Eigen | 3.4.0 | 矩阵、分解和线性方程组 |
| TinyXML2 | 10.0.0 | 轻量 XML,与 libxml2 不同 API |
| GoogleTest | 1.15.0 | C++ 单元测试 |
| fmt / spdlog | 11.1.3 / 1.15.1 | 新增通用储备:格式化和日志,两者版本按配方要求对齐 |
| nlohmann_json | 3.11.3 | 新增通用储备:JSON 配置和结果 |
| yaml-cpp | 0.8.0 | 新增通用储备:YAML 参数 |
这些是本次消费验证的版本基线,不是声称全部最新或经过漏洞审计。libxml2 的 zlib、libiconv 等传递依赖也必须随包保存;只拿上表里的几个 .a 文件不构成完整制品集合。
初始平台是 Ubuntu 22.04 / GCC 11 / libstdc++11 / C++17,分别创建 Release、Debug 静态库。实际已测架构及未覆盖项见源码包的 VALIDATION.md 和 TODO.md。不能把 Linux ARM64 原生结果当作高通 SDK 交叉编译、Windows 或原生 AMD64 的验证。
15.2 日常只走同一套入口
在站点仓库根目录执行:
./bin/site.sh artifacts-build |
构建工具镜像与首次获取源码需要联网;verify 在新建的断网容器与空白 Conan 缓存中恢复,不允许从旧缓存或远端悄悄补齐。若该架构还没有审核锁,create 会停止,只有显式 --bootstrap 才进行第一次依赖解析;解析后还要审查并固化锁,不把未来每次运行都变成一次隐式升级。
这四条是建立或更新制品架的流程。后续编译自己的工程,只恢复并消费已验证的 bundle,不必每次重新 create。 增加库、升级版本或改变平台配置时,再产生一个新批次。
实现、Dockerfile、消费示例和说明可下载:常用制品工具源码包。独立解压使用 bash docker/conan-artifacts/run.sh ...,无需先启动 Hexo。预编译库与 Docker 镜像保留在本地 build/conan-artifacts/,不会作为博客资源上传。
15.3 真正验收的是“恢复以后能用”
固定镜像 + profiles + 依赖锁 |
消费程序会真的做 Eigen 求解、日志输出、JSON/YAML 往返和两种 XML 解析,而不是只检查头文件存在。恢复前后比较完整 RREV/package ID/PREV;另用空缓存和没有准备过的 SDK 基线做反例。
manifest.json 保存实际环境和制品哈希;verification.json 记录最新验收结果。built 只表示生产完成,passed 才表示该次离线消费通过。 对外复制或开始新项目时,先查看验证结果,不能只看 last-built。
2026-09-09 实际得到 10 个配方、18 份制品,缓存归档约 40.9 MiB;同一归档已在两个全新断网 ARM64 Linux 容器中分别通过上述六组检查。每个配置的消费程序包含 5 项 GTest。工具镜像也已保存并校验,本机重复恢复通过不代表第二台机器或高通目标板已经验证。
15.4 哪些依赖仍然不能混在一起
profile 用 tools.info.package_id:confs 把自定义基线标识纳入包身份,但它不会自动识别 SDK。换 sysroot、libc、编译器契约或目标板时,必须维护新的基线和实际验证。
第三方库采用静态链接,不代表程序完全静态:glibc、libstdc++ 等系统运行库仍须匹配。迁移时同时带走制品 bundle 和构建工具镜像;镜像可用 ./bin/site.sh artifacts-image-save 保存。仅复制 Conan 缓存不等于复制了编译器和目标运行系统。
Pinocchio、Boost/OpenCV、ONNX Runtime、ROS2、OCS2 与高通 SDK 后续按实际功能和平台组合逐批加入;现有 ROS/仿真镜像保持原版本,待替换依赖时还需跑各项目原有回归。当前制品架是本地使用,不是已经上线的团队制品服务器,也没有执行远程上传。
缓存保存/恢复的边界可查 Conan 2.15 官方手册第 6.6 节。固定 2.15.0 没有新版 cache save --no-source 参数,本工具没有使用它。
资料与相关笔记
- Conan 2.15 系列文档:本文固定实验版本对应的文档系列,页面可能显示该系列维护版本。
- 二进制模型、revisions、lockfiles。
- CMakeToolchain、profiles、remote 认证。
- ConanCenter 贡献流程、upload。
- 本站:高通 Yocto 构建与部署、Docker 与可复现环境、C++ 分类。
最后记住:版本号告诉我“叫哪个版本”,RREV 告诉我“哪份配方与源码”,package ID 告诉我“按什么兼容性配置”,PREV 告诉我“哪份包内容”;真正交付还需要工具链、依赖、权限和运行验证。