Conan 学习:C++ 制品、依赖锁定与交叉编译部署

这次完善了什么

原笔记已经记录了数学库、可执行文件、动态库和制品库上传,但主体是 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 把生产端和消费端串起来

生产端:
源码 + conanfile.py + profile / options
→ 依赖图 → CMake 构建 → 安装到 package folder
→ test_package 消费验证 → 指定制品列表 → 团队 remote

消费端:
应用 requires + profile + lockfile + remote 策略
→ 确定 recipe revision
→ 计算/匹配 package ID → 获取 package revision
→ CMakeDeps + CMakeToolchain → 编译/链接应用
→ 运行验证 → 部署包/容器/Yocto recipe

这里有两次“安装”:CMake 的 install 将库文件放进包暂存目录;conan install 解析并取得依赖、生成构建环境。两者都不等于把完整程序部署到开发板的 /usr。

2. 一个 Conan 制品的完整身份

2.1 五层身份,不只 name/version

mymath/1.0@myteam/stable#<RREV>:<PACKAGE_ID>#<PREV>
└── 配方引用 ──────────┘ └配方修订┘ └二进制配置┘ └包内容修订┘

实验使用更短的 mymath/1.0,没有 user/channel。
上面的尖括号是示意占位符,不是真实哈希。
字段 回答的问题 容易误会的地方
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/*"
conan list "mymath/1.0#*"
conan list "mymath/1.0#*:*#*" --format=json

第三条适合看到完整层次。引号可避免 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:构建过程中要运行的工具
例:Linux x86_64 上执行的 CMake、代码生成器

host profile:最终应用/库将运行的目标
例:高通板上运行的 Linux armv8 库与程序
# 本机实验中两者相同;交叉编译时换成两份明确的 profile
conan profile show -pr:b default -pr:h default
conan graph info consumer -pr:b default -pr:h default \
-s:h build_type=Release --no-remote

普通 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/
mymath/
conanfile.py
CMakeLists.txt
LICENSE
include/mymath.h
src/mymath.cpp
test_package/
conanfile.py
CMakeLists.txt
src/test_mymath.cpp
consumer/
conanfile.py
CMakeLists.txt
main.cpp
run_lab.py
README.md

从本站源码仓库根目录,或 ZIP 解压后的 conan2-lab 根目录运行:

# 第一次构建会下载 Ubuntu 构建工具及固定版本 Conan
bash docker/conan-lab/run.sh build

# 在隔离缓存里构建和测试;不连接 Conan 远程仓库
bash docker/conan-lab/run.sh test

# 想逐条练习时,打开另一个一次性实验 shell
bash docker/conan-lab/run.sh shell

这个 lab 不修改已有 MuJoCo/飞控镜像,不在 macOS 宿主安装编译器或 pip 包。Conan 版本固定;APT 和 Python 间接依赖仍需记录实际版本,因此这里只承诺可复现工作流,不声称每次重建 Docker 镜像都逐位相同。

交互 shell 的源码挂载是只读的。先复制到容器内可写工作区,再操作:

# 仅在上面打开的新实验容器内执行;每个新 shell 执行一次
mkdir /work/demo
cp -R /lab/mymath /lab/consumer /work/demo/
cd /work/demo
conan --version
conan profile detect
conan remote disable "*"

后续手动命令默认位于这个 /work/demo。退出容器后实验缓存与工作区消失;自动测试的报告保留在宿主 build/conan-lab/,不把 Conan 缓存或编译出的 ELF 文件发布到网页。

5.2 CMake 的安装规则就是包内容边界

库的 CMakeLists.txt 将源代码编译成 mymath,声明头文件和 C++17 要求,并把 archive/shared/runtime 文件装到相应目录。关键结构是:

add_library(mymath src/mymath.cpp)
target_compile_features(mymath PUBLIC cxx_std_17)
target_include_directories(mymath PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
"$<INSTALL_INTERFACE:include>")

install(TARGETS mymath
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin)
install(DIRECTORY include/ DESTINATION include)

上面是完整文件的节选。BUILD_INTERFACE 用于源码树里的构建,INSTALL_INTERFACE 描述安装后的相对布局;不能把本机源码树的绝对 include 路径写进对外接口。Windows 动态库还需正确导出符号;本轮 Linux 小实验不等于已经覆盖 Windows DLL 场景。

5.3 看懂新的 conanfile.py

完整库配方 中最重要的关系是:

from conan import ConanFile
from conan.tools.cmake import CMake, CMakeDeps, CMakeToolchain, cmake_layout

class MyMathRecipe(ConanFile):
name = "mymath"
package_type = "library"
settings = "os", "arch", "compiler", "build_type"
options = {"shared": [True, False], "fPIC": [True, False]}
default_options = {"shared": False, "fPIC": True}

def set_version(self):
self.version = self.version or "1.0"

def layout(self):
cmake_layout(self)

def generate(self):
CMakeDeps(self).generate()
toolchain = CMakeToolchain(self)
toolchain.variables["MYMATH_VERSION"] = str(self.version)
toolchain.generate()

def build(self):
cmake = CMake(self)
cmake.configure()
cmake.build()

def package_info(self):
self.cpp_info.libs = ["mymath"]
self.cpp_info.set_property("cmake_file_name", "mymath")
self.cpp_info.set_property("cmake_target_name", "mymath::mymath")

这仍是教学节选,不能替代完整配方;下载文件还包含 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 \
-pr:b default -pr:h default -s:h build_type=Release --no-remote

conan create mymath --version=1.0 \
-pr:b default -pr:h default -s:h build_type=Debug --no-remote

conan list "mymath/1.0#*:*#*" --format=json

观察点不是“打印了绿色成功”,而是:同一源码/配方修订下,Debug 与 Release 分别有自己的 package ID,并且 test_package 能真正链接包中的库。具体哈希取决于配方、工具链与配置,不要在教程里硬编码他人机器的 package ID。

选项的操作形式则是:

# 可选练习:Linux 实验环境中再构建共享库变体
conan create mymath --version=1.0 \
-pr:b default -pr:h default -s:h build_type=Release \
-o:h "mymath/*:shared=True" --no-remote

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)
add_executable(robot-consumer main.cpp)
target_link_libraries(robot-consumer PRIVATE mymath::mymath)

这样消费端不必知道缓存中 p/ 目录的哈希布局。不要改成 include_directories(/某人的缓存/...) 或 link_directories 全局堆路径,否则换一台机器就暴露问题。

在手动实验工作区中,先消费已经存在的 Release 包:

conan install consumer -of out/consumer-release \
-pr:b default -pr:h default -s:h build_type=Release \
--build=never --no-remote

conan build consumer -of out/consumer-release \
-pr:b default -pr:h default -s:h build_type=Release

./out/consumer-release/build/Release/robot-consumer

最后的路径对应本实验的 Linux、单配置 CMake 与 cmake_layout,不是跨 generator 的通用路径。静态库基础实验不需要复制共享库;动态依赖则需看实际 run environment 与部署布局。

6.3 test_package 测的不是源码目录能否编译

完整 test_package 配方 采用 Conan 2 的显式被测引用:

def requirements(self):
self.requires(self.tested_reference_str)

这是关键:测试依赖的是刚创建的那个包,不是通过相对路径顺手连上生产工程尚未打包的库。它可以发现漏装头文件、错误的 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 \
-pr:b default -pr:h default -s:h build_type=Release \
--no-remote --lockfile-out=consumer/conan.lock

然后发布一个仅位于本地缓存的 1.1:

conan create mymath --version=1.1 \
-pr:b default -pr:h default -s:h build_type=Release --no-remote

分别检查未锁与锁定的依赖图:

# 明确不用 lockfile,避免默认发现 consumer/conan.lock
conan graph info consumer \
-pr:b default -pr:h default -s:h build_type=Release \
--no-remote --lockfile=""

# 明确指定 lockfile,CI 中不要依赖隐式发现
conan graph info consumer \
-pr:b default -pr:h default -s:h build_type=Release \
--no-remote --lockfile=consumer/conan.lock

预期:前者按范围选择可用的 1.1,后者仍解析到锁定的 1.0 与对应 RREV。自动脚本会检查真实 JSON 结果,不把这段预期文字当成验收日志。

7.3 同一把锁仍然可以选择不同配置的二进制

给同一 consumer lockfile 换 Debug:

conan install consumer -of out/consumer-debug \
-pr:b default -pr:h default -s:h build_type=Debug \
--lockfile=consumer/conan.lock --build=never --no-remote

如果之前已经构建了 1.0 的 Debug 包,这一步可以获得它;锁的是依赖版本/修订,而非把整个应用强制变成 Release。

再试没有构建过的 RelWithDebInfo:

# 故意制造缺失配置;非零退出是这个练习的预期结果
conan install consumer -of out/missing-binary \
-pr:b default -pr:h default -s:h build_type=RelWithDebInfo \
--lockfile=consumer/conan.lock --build=never --no-remote

在本实验默认身份规则、没有该变体的缓存中,应看到缺失二进制错误。一般项目可能配置兼容性回退,应以真实 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}"
: "${CONAN_LOGIN_USERNAME:?填写已授权的用户名}"

conan remote list
# 仅在 team 名称尚未配置时添加;不要强制覆盖现有 remote
conan remote add team "$CONAN_REMOTE_URL"
conan remote login team "$CONAN_LOGIN_USERNAME"

交互认证时不把密码写成命令行参数。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
: "${CONAN_PACKAGE_REF:?填写本次已验证的完整二进制引用}"

conan list "$CONAN_PACKAGE_REF" --format=json > release-list.json

检查 JSON 是否只含预期制品。多平台 CI 应汇总经过验收的 PackageList,不要靠 shell 通配符猜测“本轮构建了哪些东西”。

对一个完整引用运行 conan list 得到的是该制品的清单,不是它的完整依赖闭包;上传这份清单也不会自动递归发布所有依赖。必须确认依赖已存在于批准的仓库,或把本次生产且验收过的依赖一起纳入发布清单。

准备发布:

# 这一步仍可能准备本地压缩文件并访问服务器,不是完全离线检查
conan upload --list=release-list.json -r team \
--check --dry-run --format=json > upload-plan.json

审核清单和目标后,才真正上传:

# 远程写操作:仅在获得发布授权后执行;本文未执行
conan upload --list=release-list.json -r team --check --confirm

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 / 镜像
→ SDK:编译器 + sysroot + 头文件 / 库 + 环境设置
→ Conan host profile:描述这套目标兼容性契约
→ 应用 / 依赖制品 → 消费与目标机测试
→ Yocto recipe、应用部署包或容器

Conan 不替代 kernel / DTB / 固件,也不自动创建 rootfs。

尽量避免让 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 的真实值
[settings]
os=Linux
arch=<Conan架构名>
compiler=<SDK编译器类型>
compiler.version=<SDK编译器版本>
compiler.libcxx=<标准库及ABI>
compiler.cppstd=<SDK支持且项目采用的标准>
build_type=Release

[conf]
tools.cmake.cmaketoolchain:user_toolchain=["/absolute/path/to/SDK-toolchain.cmake"]
tools.build.cross_building:can_run=False

# 下面是团队自定义标识,不是 Conan 自动探测的板卡型号
user.yocto:sdk_id=<不可变的SDK版本或制品摘要>
tools.info.package_id:confs=["user.yocto:sdk_id"]

常见 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 环境。不要仅凭这个文件名猜安装位置。要检查:

  1. CMAKE_C_COMPILER / CMAKE_CXX_COMPILER 是否是目标 SDK 编译器。
  2. sysroot、CPU flags、标准库 ABI、链接器选项是否保留。
  3. find_package 与 pkg-config 是否误选宿主库。
  4. Conan 的 libcxx/flags/安装路径生成块是否与 SDK 冲突。
  5. 最终 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
conan install consumer \
-pr:b profiles/linux-build -pr:h profiles/yocto-host \
--lockfile=consumer/conan.lock --build=never

初次移植可能确实需要构建缺失依赖,应另行批准并使用匹配 SDK 创建对应制品。--build=missing 不会修正不完整的 ABI 建模;已有“看似匹配”的错误制品仍可能被复用。

这里复用 lockfile 的前提是它覆盖目标 profile 的条件依赖;真实项目需按 锁文件矩阵说明 生成/合并并审查相应依赖锁,不能假设本机配置的锁天然完整。

10.5 编译、可执行、上板成功是三层证据

test_package 可以先完成交叉编译/链接;执行阶段采用:

from conan.tools.build import can_run
import os

def test(self):
if can_run(self):
self.run(os.path.join(self.build_folder, "test_mymath"), env="conanrun")

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 --version
conan profile show -pr:b default -pr:h default
conan graph info consumer -pr:b default -pr:h default \
-s:h build_type=Release --no-remote --format=json
conan list "mymath/*#*:*#*" --format=json

注意 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 缓存开始,断开容器网络后验证:

  1. 创建 mymath/1.0 的 Release 与 Debug 包,执行各自的 test_package。
  2. 比较真实 JSON:同一 RREV、不同 package ID。
  3. 创建 consumer 的 lockfile,再创建 mymath/1.1。
  4. 未锁的消费者解析并运行 1.1;锁定消费者仍解析并运行 1.0。
  5. 改动导出源码并再创建 1.0,检查 RREV 改变,旧锁仍保持原修订。
  6. 同一 lockfile 分别消费原有 Release 与 Debug 制品。
  7. 请求没有生成的 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
./bin/site.sh artifacts-create
./bin/site.sh artifacts-verify
./bin/site.sh artifacts-status

构建工具镜像与首次获取源码需要联网;verify 在新建的断网容器与空白 Conan 缓存中恢复,不允许从旧缓存或远端悄悄补齐。若该架构还没有审核锁,create 会停止,只有显式 --bootstrap 才进行第一次依赖解析;解析后还要审查并固化锁,不把未来每次运行都变成一次隐式升级。

这四条是建立或更新制品架的流程。后续编译自己的工程,只恢复并消费已验证的 bundle,不必每次重新 create。 增加库、升级版本或改变平台配置时,再产生一个新批次。

实现、Dockerfile、消费示例和说明可下载:常用制品工具源码包。独立解压使用 bash docker/conan-artifacts/run.sh ...,无需先启动 Hexo。预编译库与 Docker 镜像保留在本地 build/conan-artifacts/,不会作为博客资源上传。

15.3 真正验收的是“恢复以后能用”

固定镜像 + profiles + 依赖锁
→ 源码构建 Release / Debug
→ 保存完整图、PackageList、缓存归档、哈希和许可文本
→ 换空缓存,断网恢复
→ --build=never 安装
→ 重新编译、CTest、运行安装后的消费程序

消费程序会真的做 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 参数,本工具没有使用它。

资料与相关笔记

最后记住:版本号告诉我“叫哪个版本”,RREV 告诉我“哪份配方与源码”,package ID 告诉我“按什么兼容性配置”,PREV 告诉我“哪份包内容”;真正交付还需要工具链、依赖、权限和运行验证。

知识共享许可协议