Conan 进阶:仓库管理、本地开发与版本控制
原文:Conan Documentation — Release 2.29.0
第一部分:使用 Conan 仓库
3.3.1 管理远程仓库(remotes)
Conan 默认从ConanCenter获取包。你可以添加其他远程仓库。
# 查看已配置的远程仓库$ conan remote list conancenter: https://center2.conan.io[Verify SSL: True]# 添加远程仓库$ conan remoteaddmyremote https://mycompany.jfrog.io/api/conan/myrepo# 添加本地目录作为远程仓库$ conan remoteaddmylocalrepo ./repo --allowed-packages="hello/*"# 删除远程仓库$ conan remote remove myremote3.3.2 上传包
# 上传包(仅最新 recipe revision + 所有二进制)$ conan upload hello/1.0-c-r=myremote# 上传所有 revisions$ conan upload hello/1.0#* -c -r=myremote3.3.3 搜索包
# 从远程仓库搜索$ conan list"*"-r=conancenter# 搜索特定包$ conan list"zlib/*"-r=conancenter# 搜索本地缓存$ conan list hello3.3.4 从 ConanCenter 获取包
从 Conan 2.9.2 开始,默认远程仓库已更改为https://center2.conan.io。如果遇到旧的默认远程配置,更新:
$ conan remote update conancenter--url="https://center2.conan.io"贡献到 ConanCenter 请访问:https://github.com/conan-io/conan-center-index
3.3.5 本地配方索引仓库
对于不适合 ConanCenter 的包,可以创建本地配方索引仓库:
$mkdirrepo&&cdrepo $ conan new local_recipes_index-dname=hello-dversion=0.1\-durl=https://github.com/conan-io/libhello/archive/refs/tags/0.0.1.zip\-dsha256=1dfb66cfd1e2fb7640c88cc4798fe25853a51b628ed9372ffc0ca285fe5be16b目录结构:
repo/ recipes/ hello/ all/ conandata.yml conanfile.py test_package/ config.yml添加为本地远程:
$ conan remoteaddmylocalrepo ./repo --allowed-packages="hello/*"从本地索引安装:
$ conan list"*"-r=mylocalrepo $ conaninstall--requires=hello/0.1-r=mylocalrepo--build=missing注意:本地配方索引仓库不存储二进制包,每个消费者需要自行
--build=missing。
第二部分:本地开发包
3.4.1 包开发流程
每次改代码都运行conan create很慢。Conan 提供了本地开发流程,让你在本地目录中测试 recipe 的各个阶段。
第一步:conan source
测试source()方法,从远程获取源代码到本地src/目录:
$ conansource.conanfile.py(hello/1.0): Calling source()in.../src Downloading main.zip conanfile.py(hello/1.0): Unzipping3.7KB Unzipping100%第二步:conan install
安装依赖、生成构建配置:
$ conaninstall.-------- Finalizinginstall(deploy, generators)-------- conanfile.py(hello/1.0): Generator'CMakeDeps'calling'generate()'conanfile.py(hello/1.0): Generating aggregatedenvfiles会在build/目录生成 toolchain 和环境文件:
build/ Release/ generators/ conan_toolchain.cmake conanbuild.sh conanrun.sh ...第三步:conan build
构建包:
$ conan build.... -- Conan toolchain: C++ Standard11with extensions ON -- Conan toolchain: Setting BUILD_SHARED_LIBS=OFF -- Configuringdone-- Generatingdone[100%]Built target hello也可以直接用 CMake 构建(CMake >= 3.23):
$cdsrc $ cmake--presetconan-release $ cmake--build--presetconan-release[100%]Built target hello第四步:conan export-pkg
将本地构建的产物打包到 cache 中,同时运行 test_package:
$ conan export-pkg.conanfile.py(hello/1.0): Packaged1'.h'file: hello.h conanfile.py(hello/1.0): Packaged1'.a'file: libhello.a conanfile.py(hello/1.0): Package'b1d267f77ddd5d10d06d2ecf5a6bc433fbb7eeed'created... -------- Testing the package: Running test()-------- hello/1.0(test package): RUN: ./example hello/1.0: Hello World Release!3.4.2 可编辑模式下的包(Editable Mode)
Editable mode 让其他包直接引用你本地工作目录中的包,而无需先conan create。
场景:你在本地开发say/1.0库,同时hello包依赖它。你想修改say后立即在hello中看到效果。
第一步:启用 editable mode
$cdsay $ conan editableadd.--name=say--version=1.0输出:
Reference 'say/1.0' added to editable packages第二步:构建 say 包本地
$cdsay $ conaninstall.-sbuild_type=Release $ cmake--presetconan-release $ cmake--build--presetconan-release[100%]Built target say第三步:hello 消费包引用 editable say
$cd../hello $ conaninstall.-sbuild_type=Release# Linux/macOS$ cmake--presetconan-release --log-level=VERBOSE# Windows$ cmake--presetconan-default --log-level=VERBOSE... -- Conan: Target declared'say::say'-- Conan: Library say found<local_folder>/say/build/Release/libsay.a... $ cmake--build--presetconan-release[100%]Built target hello注意输出中say::say从本地的say/build/Release/libsay.a链接,而不是 Conan 缓存。
第四步:修改 say 代码立即生效
修改say/src/say.cpp后,只需重新构建say,不需要任何 Conan 操作:
$cd../say $ cmake--build--presetconan-release[100%]Built target say# hello 下次构建时自动使用新的 libsay.a$cd../hello $ cmake--build--presetconan-release第五步:退出 editable mode
$cd../say $ conan editable remove--refs=say/1.03.4.3 理解 Conan 包布局(Package Layout)
为了支持 cached 和 editable 两种模式共存,需要在layout()方法中正确配置。
完整的 layout() 示例
importosfromconanimportConanFilefromconan.tools.cmakeimportCMakeclassSayConan(ConanFile):name="say"version="1.0"exports_sources="CMakeLists.txt","src/*","include/*"deflayout(self):# 1. 定义文件夹结构self.folders.source="."self.folders.build=os.path.join("build",str(self.settings.build_type))self.folders.generators=os.path.join(self.folders.build,"generators")# 2. cpp.package — 告诉消费者在缓存中找什么self.cpp.package.libs=["say"]self.cpp.package.includedirs=["include"]self.cpp.package.libdirs=["lib"]# 3. cpp.source — 告诉消费者在 editable 模式下找头文件self.cpp.source.includedirs=["include"]# 4. cpp.build — 告诉消费者在 editable 模式下找库文件self.cpp.build.libdirs=["."]defbuild(self):cmake=CMake(self)cmake.configure()cmake.build()缓存模式查找路径
$ cmake--presetconan-release --log-level=VERBOSE... -- Conan: Library say found<CACHE>/p/say8938ceae216fc/p/lib/libsay.aEditable 模式查找路径
$ cmake--presetconan-release --log-level=VERBOSE... -- Conan: Library say found<LOCAL>/say/build/Release/libsay.a3.4.4 Workspaces(工作区)
⚠️ 警告:Workspace 是实验性功能,需要设置环境变量
CONAN_WORKSPACE_ENABLE=will_break_next才能启用。生产环境不可用。
用于管理多个 editable 模式下包之间的依赖关系。
# 创建工作区模板$ conan new workspace目录结构:
. ├── CMakeLists.txt ├── app1/ ├── liba/ ├── libb/ ├── conanws.py └── conanws.yml创建自定义工作区:
$mkdirmyproject&&cdmyproject $ conan workspace init.$ conan new cmake_lib-dname=hello-dversion=1.0-ohello $ conan new cmake_exe-dname=app-dversion=1.0-drequires=hello/1.0-oapp# 将包添加到工作区$ conan workspaceaddhello $ conan workspaceaddapp# 构建所有包$ conan workspace build $ app/build/Release/app hello/1.0: Hello World Release!app/1.0: Hello World Release!第三部分:版本管理
3.5.1 版本
最基本的版本管理——手动修改conanfile.py:
classpkgRecipe(ConanFile):name="pkg"version="1.0"$ conan create.$ conan list"pkg/*"Local Cache pkg pkg/1.0创建多个版本:
$ conan create.--version=1.0$ conan create.--version=1.1$ conan create.--version=1.2$ conan list"pkg/*"Local Cache pkg pkg/1.0 pkg/1.1 pkg/1.2自动版本设置——从文件读取
fromconan.tools.filesimportloadclasspkgRecipe(ConanFile):name="pkg"defset_version(self):self.version=load(self,"version.txt")自动版本设置——从 Git Tag 获取
fromconan.tools.scmimportGitclasspkgRecipe(ConanFile):name="pkg"defset_version(self):git=Git(self)tag=git.run("describe --tags")self.version=tag$gittag1.5$ conan create.... pkg/1.53.5.2 版本范围
classappRecipe(ConanFile):name="app"requires="pkg/[>=1.0 <2.0]"行为演示:
$ conan create pkg--version=1.0$ conaninstallapp# → 使用 pkg/1.0$ conan create pkg--version=1.1$ conaninstallapp# → 自动使用 pkg/1.1(不需改 app)$ conan create pkg--version=2.0$ conaninstallapp# → 仍然使用 pkg/1.2(2.0 不在范围内)版本范围表达式对照表:
| 表达式 | 含义 | 匹配示例 |
|---|---|---|
[>=1.0 <2.0] | 明确范围 | 1.0, 1.5, 1.9 |
[~1] | 近似 1.x | 1.3, 1.8.1 |
[~2.5] | 近似 2.5.x | 2.5.0, 2.5.3 |
[^1.2] | 兼容 1.x | 1.2.1, 1.3, 1.51 |
[^0.1.2] | 兼容 0.1.x | 0.1.2.1, 0.1.3 |
[1.2.3.*] | 字符串前缀匹配 | 1.2.3.5, 1.2.3.abc |
Conan 版本排序规则:数字按数值比较,字母按字典序,预发布版本小于正式版本。
3.5.3 Revisions(修订版本)
每次构建包时,即使版本号不改变,Conan 也会自动跟踪变更:
# 第一次创建$ conan new cmake_lib-dname=hello-dversion=1.0$ conan create.hello/1.0: Hello World Release!$ conan list"hello/1.0#*"revisions 2475ece651f666f42c155623228c75d2# 修改 src/hello.cpp 中 "Hello" → "Bye"$ conan create.hello/1.0: Bye World Release!$ conan list"hello/1.0#*"revisions 2475ece651f666f42c155623228c75d2 2b547b7f20f5541c16d0b5cbcf207502# ← 新 revision,版本号还是 1.0Recipe revision= conanfile.py + 所有导出文件的哈希
Package revision= 二进制内容的哈希
锁定特定 revision:
defrequirements(self):self.requires("hello/1.0#2475ece651f666f42c155623228c75d2")上传 revisions:
# 仅最新 revision$ conan upload hello/1.0-c-r=myremote# 所有 revisions$ conan upload hello/1.0#* -c -r=myremoteWindows 注意:Git 默认使用 CRLF 换行符,这会导致 Windows 和 Linux 计算出不同的 revision。
3.5.4 Lockfiles(锁定文件)
Lockfile 锁定依赖图的"快照",确保后续构建可重现。
创建 lockfile
$ conan lock create.生成的conan.lock:
{"version":"0.5","requires":["matrix/1.0#905c3f0babc520684c84127378fefdd0%1675278126.0552447"],"build_requires":[],"python_requires":[]}使用 lockfile
创建新版本不会影响已锁定的项目:
$ conan create matrix--version=1.1$cdengine $ conaninstall.Requirements matrix/1.0#905c3f0babc520684c84127378fefdd0 - Cache # 仍然用 1.0没有显式指定--lockfile时,Conan 自动查找当前目录的conan.lock。
部分 lockfile
$ conaninstall.-sarch=x86 --lockfile-partial演进 lockfile
# 添加新版本到 lockfile$ conan lockadd--requires=matrix/1.1# 更新并清理 lockfile$ conaninstall.-sarch=x86 --lockfile-out=conan.lock --lockfile-clean多配置 lockfile 合并
$ conan lock create.--lockfile-out=64.lock --lockfile-clean $ conan lock create.-sarch=x86 --lockfile-out=32.lock --lockfile-clean $ conan lock merge--lockfile=32.lock--lockfile=64.lock --lockfile-out=conan.lock3.5.5 依赖冲突
当依赖图中不同包要求不同版本的同一包时,会产生冲突。
创建冲突场景:
game/1.0 ├── engine/1.0 → matrix/1.0 └── intro/1.0 → matrix/1.1$ conan create matrix--version=1.0$ conan create matrix--version=1.1$ conan create engine--version=1.0$ conan create intro--version=1.0$ conaninstallgame输出:
ERROR: Version conflict: 'matrix/1.0' required by 'engine/1.0', while 'matrix/1.1' required by 'intro/1.0'解决方案:
- 使用版本范围,让上游作者统一到一个版本区间
- 使用 lockfile 固定所有依赖到一致的状态
- 升级或降级其中一个依赖以消除冲突
完整命令速查表
| 场景 | 命令 |
|---|---|
| 创建包 | conan new cmake_lib -d name=xxx -d version=1.0 |
| 构建包 | conan create . |
| 查看缓存 | conan list xxx |
| 配置不同构建 | -s build_type=Debug,-o shared=True |
| 上传包 | conan upload hello/1.0 -c -r=myremote |
| 添加远程 | conan remote add xxx <url> |
| 本地开发(source) | conan source . |
| 本地开发(install) | conan install . |
| 本地开发(build) | conan build . |
| 本地开发(打包) | conan export-pkg . |
| Editable 模式 | conan editable add . --name=xxx --version=1.0 |
| 退出 Editable | conan editable remove --refs=xxx/1.0 |
| 创建 lockfile | conan lock create . |
| 锁定 revision | self.requires("pkg/1.0#<hash>") |
| 版本范围 | self.requires("pkg/[>=1.0 <2.0]") |
| 自动版本号 | conan create . --version=1.0或set_version() |
