Skip to content

[pkg] pixi/conda 上游包接入 — 背景叙事 (story) #19

Description

@edwinjhlee

缘起

x-cmd pkg 的核心模式很简单:一个被精心维护的 meta.yml + version.yml + info.yml 三件套索引 tarball,里面写了每个包从哪里下、用什么 hash 验、用什么 build flavor 编译。下载下来之后,走一套 populate.sh 把 bin/ lib/ man/ 搬进 ${SPHERE} 的对应槽位。

这套模式的天花板很明显:

  1. 包数量上不去:每个包都要人手写一份 yml,维护不过来;x-cmd/pkg 现在的索引大概覆盖几百个常用工具,但 GNU/Linux 上真正常用的二进制远不止这个数,conda-forge 一个 linux-64 subdir 就 17k+。
  2. 二进制来源单一:要么自己编译 (build/),要么从 GitHub Release 拿;碰到作者不发 release 的项目(很多科研/数据科学库就这样),就只能放弃收录。
  3. 跨语言生态孤岛:Python 用户想用 conda,R 用户想用 CRAN,都得绕一圈;pkg 内部对 npm/pip 只做了下载代理,没把它们的元数据吃进来。

pixi 的出现改变了第三点的下半场。rattler 把整个 conda 协议(repodata + channel + 求解)用 Rust 重写了一遍,而且把 SAT 求解器(resolvo)作为 crate 暴露出来,意味着 x-cmd/pkg 想用的时候,可以选择:

  • 自己再写一个依赖求解器(不现实,我们人少)
  • 调 rattler_solve 的 API(中等,需要 Rust 工具链)
  • 把 pixi 当成后端,临时生成 pixi.toml 调 pixi install(最现实)

我为什么一开始不理解 SAT

我一开始也觉得:一个包管理器而已,不就是把名字换成 URL 吗?后来看 resolvo 才意识到自己把问题想小了。

依赖解析的复杂度不在"下载",在"选谁的版本"。一个用户的 manifest 长这样:

python >=3.10,<3.11.0a0
pytorch

但 conda-forge 上 pytorch 有几十个 build:

  • pytorch-2.4.0-py3.10_cuda12.1_0.conda
  • pytorch-2.4.0-py3.10_cuda11.8_0.conda
  • pytorch-2.4.0-py3.11_cuda12.1_0.conda
  • ...

每个 build 的 depends 都不一样(cuda 版本不同、python 版本不同)。这些 depends 又都是版本范围,不是固定版本。你要找的是:一个 pytorch build,它的依赖集合 ≠ 空,且能与所有"被它拖进来"的包的版本范围求非空交集。

这就是经典的 CSP (constraint satisfaction problem),教科书上写明 NP-hard。

resolvo 干的事情,直观说就是:

  1. 把每个 (name, version, build, channel, url) 候选变成一个 Boolean 变量
  2. 把每条约束(依赖、互斥、channel 优先)变成 CNF 子句
  3. 跑 CDCL 算法,遇冲突学 clause、剪枝

它比通用 SAT 引擎聪明的地方在于:

  • 变量是"领域概念"(候选包)而不是裸 Boolean,子句可以直接用 MatchSpec 表达
  • 知道 channel 优先级的语义,直接生成排除子句,不用绕弯
  • 学习到的子句按 conda 维度压缩,内存压力比通用 SAT 低(pixi/CHANGELOG.md:3513-3516 还专门记了一笔 RAM 降低的事)

至于并行,pixi 实际并行的不是 SAT 搜索本身(那玩意儿是单线程的),而是上游并行的走(per-requirement BFS)+ 多 env 解算的 semaphore 池。我之前说"环境解析已经全并行",其实更准确的说法是"多 env 并行,单 env 内 BFS 并行,SAT 内核单线程"。

x-cmd pkg 的模型和 conda 不一样

x-cmd/pkg 的设计是"一包一文件",看到 x pkg add jq,就到 META yml 里找 jq,按 URL 下载,解压,populate。这套在 conda-包面前就撞墙了:

  1. conda 包不是 bin/ + lib/ + man/ 的标准 layout,而是任意前缀(前缀安装风格)。populate 阶段要重写 info/paths.json 里所有 prefix_placeholder。
  2. conda 包是带传递依赖的;x-cmd/pkg 没记录依赖,一旦 jq 真在 conda 上依赖 oniguruma,我们要不要拉?不拉就跑不起来;拉就要么写死依赖,要么做依赖求解。
  3. 包尺寸大一个数量级(几十 MB vs 几 MB),网络和磁盘预算要重新算。

所以接 conda,第一步是"放弃依赖求解",只做"按 URL 单包下载"。这刚好对应方案 A。我倾向于先做 A,把骨架立住,再考虑 B、C 是不是值得做。

一些还没想清楚的细节

  • 离线:x-cmd/pkg 现在是 dist tarball 离线索引;conda 的 repodata.json 没法打进索引 tarball(太大)。要么每次联网拉 current_repodata,要么预生成一份 per-channel 的 name → (version, build, sha256, size) 子集打成索引。
  • 签名:conda-forge 不签包,只给 sha256。社区信任模型靠 sha256 + 社区声誉。对 x-cmd/pkg 而言这是降级(我们连索引 tarball 都做 SHA-512 校验)。
  • 包覆盖:conda-forge 包含很多 x-cmd/pkg 没必要收录的 Python wheels / R packages;把它们全塞进索引会让 x pkg ll 太长。需要某种过滤(例如只列 xbin 已声明的)。

关联阅读

  • 设计讨论(issue 正文):[pkg→env] x-cmd/env 多 backend 编排 (env.yml 作为 conda 生态入口) #18
  • pixi 主仓库(本机镜像 /Users/l/.x-repo/github.com/x-cmd-sourcecode/pixi):
    • 入口:crates/pixi_command_dispatcher/src/solve_conda/mod.rs:318(Solver.solve(task)? 是唯一一处触发 resolvo 的地方)
    • 并行模型:crates/pixi_command_dispatcher/src/solve_binary.rs:60-73(conda_solve_semaphore)
    • 通道优先级:docs/advanced/channel_logic.md
  • x-cmd/pkg 相关 issue:
    • 0119.npm-pip-source.yml — 源镜像 env var 模式,直接套用
    • 0125.版本号问题.yml — conda 版本号格式的特殊情况
    • 0222.调整 download 的包的重命名格式.yml — 包缓存命名约定
  • rattler 系列 crate(本机未 vendor, 引用自 Cargo.lock):
    • rattler_solve — 把 MatchSpec 编码成 resolvo 子句
    • rattler_conda_types — PackageRecord / Platform / MatchSpec
    • resolvo 0.12.1 — 纯 SAT/CDCL 引擎

推进计划

  1. 先做 A 的最小 demo:选 ripgrep 做试验品(小、conda-forge 有、运行时零依赖),验证 populate 阶段能正确重写 prefix。
  2. A 跑通后,写 x pkg conda ls <name>,复用 lib/info/ll 的 advise schema。
  3. 评估 C:拉 pixi 当后端的可行性,看用户愿不愿意接受"装 pytorch 需要先装 pixi"这个 dogfood 风险。
  4. 如果 C 走通,把 x pkg conda resolve 包装成"调 pixi install → 拿 lock 文件 → 批量方案 A 下载"的脚本。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions