- 开发工具
- CLI
- 包管理器
- 任务调度
【免费下载链接】pixi
Powerful system-level package manager for Linux, macOS and Windows written in Rust – building on top of the Conda ecosystem.
本教程承接 Building a C++ Package,演示在 Pixi 中改用pixi-build-rattler-build后端,通过手写recipe.yaml构建同一个基于 CMake 与 nanobind 的 C++ 包。读完本文你将掌握:如何为 workspace 配置 rattler-build 后端、如何编写一份可运行的 rattler-build recipe、recipe 中各 section 的职责与坑点,以及如何用pixi run一步完成「构建 → 安装 → 运行验证」的完整闭环。
适用前提:
pixi-build属于 preview 功能,接口在稳定之前可能发生变化,用于生产 workspace 时请注意跟踪版本变更(见 backend 文档 中的相关说明)。同时,只要存在对应的语言级后端(如pixi-build-cmake),官方建议优先使用后端,以获得更统一、更流畅的构建体验;rattler-build方案适合没有现成后端的语言/构建系统,或需要对构建过程有更细粒度控制的场景。
为什么需要手写 recipe
pixi-build-cmake等语言级后端会替你完成大量「隐形工作」:探测构建系统、生成编译命令、处理依赖分组与安装位置。但当出现以下情况时,rattler-build方案更有价值:
- 目标语言或构建系统没有对应的 Pixi 后端;
- 你需要精确控制编译选项、构建目录、依赖注入方式等细节;
- 项目里已经存在
recipe.yaml(例如从 conda-forge 迁移),希望直接复用。
用rattler-build构建同一个包,等于把构建过程的隐藏复杂性摊开在你面前,让你更深刻地理解后端究竟做了什么。
1. 工作区结构
沿用前一篇教程 Building a C++ Package 的目录骨架:
cpp_math/ ├── CMakeLists.txt ├── pixi.toml ├── recipe.yaml ├── .gitignore └── src └── math.cpp与之前唯一的差异是:把构建后端从pixi-build-cmake换成pixi-build-rattler-build,并新增recipe.yaml。仓库中完整的可运行示例位于 docs/source_files/pixi_workspaces/pixi_build/advanced_cpp/,包含 pixi.toml、recipe.yaml、CMakeLists.txt 与 src/math.cpp,以及一份已求解的 pixi.lock。
1.1pixi.toml
[workspace] channels = ["https://prefix.dev/conda-forge"] platforms = ["osx-arm64", "osx-64", "linux-64", "win-64"] preview = ["pixi-build"] [dependencies] cpp_math = { path = "." } python = "3.12.*" [tasks] start = "python -c 'import cpp_math as b; print(b.add(1, 2))'" [package] name = "cpp_math" version = "0.1.0" [package.build] backend = { name = "pixi-build-rattler-build", version = "0.*" }逐项说明:
preview = ["pixi-build"]:开启构建预览特性。pixi-build仍是 preview 标志,需要显式 opt-in,后续接口可能会变化。[dependencies]中cpp_math = { path = "." }声明本目录是一个源码包(source dependency),python = "3.12.*"是运行时依赖,用于之后直接执行绑定模块。[tasks].start:定义验证任务——导入构建出的cpp_math模块并调用add(1, 2)。[package.build]:指定后端pixi-build-rattler-build,版本约束0.*。
对比前一篇使用pixi-build-cmake的 cpp/pixi.toml:那里还多了[package.build.config] extra-args以及[package.host-dependencies](cmake、nanobind、python),因为语言级后端需要你在清单里显式声明这些构建参数与宿主依赖;而使用 rattler-build 后端时,这些信息全部下沉到recipe.yaml中,清单只保留后端选择与运行依赖。
1.2src/math.cpp与CMakeLists.txt
源码与前一篇完全相同,math.cpp用 nanobind 暴露一个add函数:
#include <nanobind/nanobind.h> int add(int a, int b) { return a + b; } NB_MODULE(cpp_math, m) { m.def("add", &add); }CMakeLists.txt的关键点(完整内容见 CMakeLists.txt):
find_package(Python 3.8 COMPONENTS Interpreter Development.Module REQUIRED)——在 conda 环境中定位 Python 解释器与开发模块;- 通过
python -m nanobind --cmake_dir查询 nanobind 的 CMake 配置目录,保证链接到同一个Python 环境中的 nanobind; - 用
sysconfig.get_path('purelib')查询 site-packages 路径,使安装目录与 Python 版本解耦; nanobind_add_module(cpp_math src/math.cpp)生成绑定模块;install(TARGETS ... LIBRARY DESTINATION ${PYTHON_SITE_PACKAGES} ...)将产物安装进前缀的 site-packages。
注意:cmake的版本必须与CMakeLists.txt中cmake_minimum_required(VERSION 3.20...3.27)声明的范围匹配,这一约束现在要在 recipe 的依赖中落实。
2.recipe.yaml:构建的全部真相
recipe.yaml是rattler-build的构建清单,其格式参考rattler-build官方 recipe 文档。本教程使用的完整示例:
package: name: cpp_math version: 0.1.0 source: path: . use_gitignore: true # (1)! build: number: 0 script: | # (2)! cmake $CMAKE_ARGS \ -GNinja \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=$PREFIX \ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ -DBUILD_SHARED_LIBS=ON \ -B $SRC_DIR/../build \ -S . cmake --build $SRC_DIR/../build --target install requirements: build: # (3)! - ${{ compiler('cxx') }} - cmake - ninja host: # (4)! - python 3.12.* - nanobind2.1source:以当前目录为源码(注解 1)
source.path: .直接把当前目录作为源码目录。这里的.gitignore语义值得注意:
- 当源码目录是本地路径时,
rattler-build只打包被 git 跟踪(tracked)的文件; use_gitignore: true意味着同时遵循.gitignore规则过滤文件;- 如果项目文件已经全部被 git 跟踪,可以去掉
use_gitignore这一配置。
2.2build.script:构建脚本(注解 2)
脚本配置并构建一个 CMake 工程,核心参数:
| 参数 | 作用 |
|---|---|
$CMAKE_ARGS | rattler-build 注入的标准 CMake 参数(平台相关),原样透传给 cmake |
-GNinja | 指定 Ninja 生成器 |
-DCMAKE_BUILD_TYPE=Release | 构建类型为 Release |
-DCMAKE_INSTALL_PREFIX=$PREFIX | 安装前缀指向 rattler-build 提供的$PREFIX环境 |
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON | 导出 compile_commands.json,便于 IDE/工具消费 |
-DBUILD_SHARED_LIBS=ON | 生成共享库 |
-B $SRC_DIR/../build -S . | 源码目录为当前目录($SRC_DIR),构建目录放在其上一级的build |
随后执行cmake --build $SRC_DIR/../build --target install完成编译与安装。
这里出现一个与后端增量编译相关的关键细节:pixi-build-rattler-build为每个构建使用干净的根目录,以保证与默认假设「根目录是全新」的 recipe 兼容;因此如果希望复用之前构建的产物实现增量编译(增量缓存),必须把构建目录放到根目录之外。本教程把构建目录放在$SRC_DIR/../build(源码目录之外)正是这个原因——在编写自己的构建脚本时,应保持同样的习惯,例如先在脚本里cd ../build_dir再开始构建,或把../build_dir作为参数传给构建系统。
2.3requirements.build:构建期依赖(注解 3)
${{ compiler('cxx') }}:rattler-build 的 Jinja 函数,按平台注入对应的 C++ 编译器(Linux 为 gxx、macOS 为 clangxx 等),避免手工硬编码编译器;cmake:构建工具,版本需与CMakeLists.txt的cmake_minimum_required范围一致(本示例工作区使用 3.20…3.27);ninja:配合-GNinja使用的构建系统。
2.4requirements.host:宿主依赖(注解 4)
python 3.12.*与nanobind放在host分组,而不是 build 分组——因为绑定模块在运行时链接的是安装目标环境里的 Python(即宿主环境),而不是构建环境里的 Python。这是 conda 三通道(build / host / run)依赖模型的关键区分:build 依赖只服务于编译阶段,host 依赖决定最终产物链接的对象。
3. 验证运行
定义好任务后,直接执行:
$ pixi run start 3pixi run start会按需完成「构建绑定模块 → 安装进环境 → 运行start任务」整条链路,最终打印3,证明add(1, 2)正确返回。
仓库的集成测试对这一点做了自动化验证:tests/integration_python/pixi_build/test_docs_examples.py 中advanced_cpp工作区被期望输出3\n,测试通过pixi run --locked --manifest-path ... start校验每个文档工作区的真实运行结果——也就是说,本教程的示例不是纸面配置,而是被 CI 持续验证的可运行项目。
4. 深入后端实现:recipe 是如何被执行的
理解「后端做了什么」有助于排查问题与定制行为。pixi-build-rattler-build的入口在 crates/pixi_build_rattler_build/src/main.rs,它通过pixi_build_backend::cli::main启动,随后由 crates/pixi_build_rattler_build/src/protocol.rs 中的RattlerBuildBackend实现与 Pixi 主进程之间的 JSON-RPC 协议,核心能力包括:
conda_outputs:解析 recipe,探测其产出(output)与依赖分组;conda_build_v1:真正执行构建。
从源码(protocol.rs)可以看到完整流程:
- Recipe 发现:优先使用
config.recipe指定的路径;否则按recipe.yaml、recipe.yml、recipe/recipe.yaml、recipe/recipe.yml顺序查找(对应实现见 rattler_build.rs 中的RattlerBuildBackend::new); - 解析与渲染:将 recipe 解析为 stage0 结构,用
RenderConfig(含目标平台、构建平台、宿主平台)配合变体配置(variant)渲染出最终 recipe; - 依赖转换:把 recipe 的
requirements.build/host/run分组转换为 conda 输出依赖,并在变体与子包映射中解析${{ compiler(...) }}、pin_subpackage等 Jinja 表达式; - 虚拟包检测:自动检测系统虚拟包(如
__glibc、__osx等),可见于示例 pixi.lock 中每个平台段的virtual-packages列表; - 构建执行:调用 rattler-build 核心的
run_build,并设置keep_build = true(Pixi 支持增量构建)与environments_externally_managed = true(环境由 Pixi 预先准备)等关键开关; - 产物打包:按 recipe 规范生成 conda 包,并把可变的路径型源码加入输入 globs,用于后续增量触发。
4.1 后端的配置项
pixi-build-rattler-build支持在[package.build.config]中配置(解析逻辑见 crates/pixi_build_rattler_build/src/config.rs):
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
recipe | String(路径) | 按recipe.yaml、recipe.yml、recipe/recipe.yaml、recipe/recipe.yml顺序查找 | recipe 文件路径;仅允许在根级设置,不允许 target 级覆盖 |
experimental | Boolean | false | 开启 rattler-build 实验特性(如多输出 recipe 的cache:功能);仅允许根级设置 |
extra-input-globs | Array<String> | [] | 额外纳入构建输入文件的 glob 模式,叠加到默认输入 globs;target 级配置会整体替换基础配置 |
debug-dir | String | 已弃用 | 后端现在总会把 JSON-RPC 请求/响应日志与生成的中间 recipe 写到工作目录的debug子目录;该旧配置被忽略,若仍在清单中设置会触发警告 |
例如指定自定义 recipe 路径:
[package.build.config] recipe = "../template/recipe.yaml"以及为平台补充输入文件:
[package.build.config] extra-input-globs = ["patches/**/*", "scripts/*.sh", "*.md"]4.2 依赖的“单一事实来源”
使用该后端时有一条重要约束:项目清单(pixi.toml)中只允许源码依赖(workspace 包),不允许二进制依赖,原因在 backend 文档 中有明确说明:
recipe.yaml是二进制依赖的唯一事实来源——它已经精确指定了版本、构建变体以及依赖归属 build/host/run 哪个分组;- 若两处都能声明二进制依赖,会产生重复与潜在冲突(例如 recipe 说
python >=3.10,项目模型却说python >=3.9); - 源码依赖(workspace 包)不同——recipe 只能按名字引用它们,却不知道它们在工作区中的路径,这个映射关系必须由项目模型提供。
对应地,[package.run-exports]在该后端下不受支持,应改用 recipe 的requirements.run_exports声明;pin-compatible、pin-subpackage也不允许出现在清单中,需改用 recipe 里的${{ pin_compatible(...) }}、${{ pin_subpackage(...) }}Jinja 语法。这些校验与报错逻辑都在 protocol.rs 的initialize阶段实现。
工作区源码依赖则在清单中用[package.build-dependencies]、[package.host-dependencies]或[package.run-dependencies]声明:
[package.build-dependencies] a = { path = "../a" }4.3 自定义变体注入环境变量
配合[workspace.build-variants],凡不是语言类已知键(如python、numpy、r等)的变体键,都会在构建期间自动导出为环境变量,用于在不修改 recipe 的前提下向构建脚本传参。例如覆盖 macOS 编译所用的 sysroot:
[workspace.target.osx.build-variants] CONDA_BUILD_SYSROOT = ["/Library/Developer/CommandLineTools/SDKs/MacOSX15.4.sdk"]也可以在 recipe 模板里通过 Jinja 引用自定义变体:
build: script: env: MY_FLAG: ${{ my_custom_flag }}[workspace.build-variants] my_custom_flag = ["enabled"]5. 后端能力的单元验证
仓库为pixi-build-rattler-build提供了专门的单元测试(位于 protocol.rs 的 tests 模块):
test_conda_outputs:对tests/recipe下的每个recipe.yaml跑conda_outputs并做快照对比;test_variant_files_are_applied与test_variant_configuration_is_applied:验证变体文件与变体配置都能正确作用于 recipe 渲染;test_variant_configuration_overrides_variant_files:验证显式传入的变体配置优先级高于变体文件;config.rs中的test_merge_with_target_config等:验证extra_input_globs的 target 级整体替换、experimental/recipe禁止 target 级设置的合并规则。
这些测试直接印证了上文提到的配置约束与渲染行为,是理解后端边界条件的第一手资料。
6. 结论:收益与代价
通过本教程,我们用rattler-build+recipe.yaml构建了同一个 C++ 包,获得的是对构建过程的完全掌控:
- 可以随手把
-DCMAKE_BUILD_TYPE=Release改成Debug,切换构建类型; - 可以删除
-GNinja改回默认的Make生成器; - 可以用
make -j$(nproc)显式控制并行任务数。
付出的代价是失去了语言级后端提供的「重活」:依赖分组推断、构建参数预设、安装路径管理等都需要自己在 recipe 中显式声明与维护。因此,优先选择存在的后端;只有当后端缺失或需要细粒度控制时,才考虑rattler-build方案。二者的取舍,正是理解 Pixi 构建后端抽象层次的最佳切入点。
- 开发工具
- CLI
- 包管理器
- 任务调度
【免费下载链接】pixi
Powerful system-level package manager for Linux, macOS and Windows written in Rust – building on top of the Conda ecosystem.
相关推荐
使用 Pixi 将 ROS 包构建为 conda 包:pixi-build-ros 后端实战指南
使用 Pixi 将 ROS 包构建为 conda 包:pixi build ros 后端实战指南 本文讲解如何在 Pixi(基于 Conda 生态的 Rust
开发工具CLI包管理器任务调度Escrcpy 安卓屏幕镜像到电脑完整指南:3 分钟 USB 投屏 + 无线配对步骤教程
Escrcpy 安卓屏幕镜像到电脑完整指南:3 分钟 USB 投屏 + 无线配对步骤教程 Escrcpy 是一款基于 scrcpy(Android 屏幕投射与操
开发工具CLI包管理器任务调度pixi-build-mojo 后端详解:使用 Pixi 将 Mojo 项目构建为 Conda 包
pixi build mojo 后端详解:使用 Pixi 将 Mojo 项目构建为 Conda 包 pixi build mojo 是 Pixi 生态中专门为
开发工具CLI包管理器任务调度
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考