Skip to content

docs(design): sysroot / bin / lib / files multi-version model - #409

Merged
Sunrisepeak merged 5 commits into
release/0.4.70from
docs/sysroot-files-model
Jul 26, 2026
Merged

docs(design): sysroot / bin / lib / files multi-version model#409
Sunrisepeak merged 5 commits into
release/0.4.70from
docs/sysroot-files-model

Conversation

@Sunrisepeak

Copy link
Copy Markdown
Member

设计文档,不含代码,不影响 0.4.70 行为。承接 #408 的讨论。

.agents/docs/2026-07-27-sysroot-files-model-design.md

它解决的问题

0.4.70 让 xlings use 整组切换 —— 但只对程序成立

资产 Catalog Selection 切换
program ✅ 由构造正确
lib VData::libdir 无任何写入者install_libs() 零调用点 → use 是 no-op
header ❌ 模型里没有它 → 索引长出 7 种写入方式,两种互相矛盾的冲突策略

可观察后果(openssl 装两个版本):装完就是头 3.1.5 / 库 3.2.0,且两个方向的 use 都修不回来,还都报成功。

架构

  • 两类资产program(xlings 在 exec 时解析)/ files(别人按路径查找,库是它的一种)
  • 物化收敛成一条路径 —— install 不物化,只有 use 物化。active 门禁这个概念随之消失
  • config() 收窄成纯声明 —— 查环境的能力移走,"纯函数"从要求变成结构上做不到别的
  • 切换 = 全删全装 + 逐条 rename 原子替换(不做差集)

但结论不是"把这些都做了"

§9.4 分四阶段,只有前两段是今天有依据的

阶段 内容 触发
0 doctor 停止把 31 个锚点报成 broken payload;libdir 写入或删死代码;E2E 证明 provider-scoped 卸载可替代手写 xvm.remove 无条件
1 spec 3 门禁 + type="files" + install 不物化 + 展开在物化时现算 修掉全部已知用户可见问题
2 per-payload manifest 仅当 recipe 漂移导致卸载不精确成为真问题
3 use() hook + use_deps 仅当 gcc/llvm 的 cfg 陈旧成为真痛点 —— 那是 2 个 recipe,不是一类
4 generation 树 仅当深度合并被判定必需(唯一强制它的需求

后三段各自解决一个尚未被证明存在的问题。

也写了反方案(§9.3)

没有任何官方 recipe 声明过 header op,4 个铺 sysroot 的走的都是 Lua 路径。受影响的只有"同一个包装 ≥2 版本且提供头/库"——官方索引里 4 个包。31 个 doctor 假警报可以单独修。

如果多版本头文件不是真实需求,"只修具体 bug"就是正确答案。 这个判断是评审者的,它决定整份文档值不值。

迁移

  • 118 个 recipe,90 个一行不改
  • spec = "3" 逐包选择加入 —— spec = "2"(arch fail-closed 门禁)已是成功先例,两代并存运转正常
  • 28 个需迁移的按难度分 A–G 七类,gcc/llvm 最后
  • 硬前置:计划里的 P4.2(真实 GCC 15/16 E2E)从未做过,gcc/llvm 迁移前必须先补

事实来源

每条论断都锚定到代码位置或真实安装的实测数据(附录列全)。实测包括:~/.xlings.json 107.6 KB / 372 条目、payload 顶层扇出分布(glibc 130、大多数 1–2)、31 个假警报的分类统计。

顺带记录一个既有热路径问题:shim.cppm:347 每次 dispatch 解析整个版本库,无缓存。尝试量化时 list --all 卡在索引刷新,所以只有代码事实,没有数字 —— 建议单独立项。

Review 重点

§10 列了 7 条,第 1 条最关键:多版本头文件/库是不是真实需求? 若否,§9.3 就是答案。

0.4.70 made `xlings use` switch a whole toolchain release. It did that for
programs. Headers and libraries are not actually managed by the same model:
`VData::libdir` has no writer at all, so `use` is a no-op for libraries, and
headers are not in the model, so the index grew seven ways of writing the
sysroot with two contradictory conflict policies.

The design collapses assets to two kinds -- `program` (xlings resolves at
exec time) and `files` (someone else resolves by path) -- collapses three
materialization code paths to one, and narrows `config()` to pure
declaration. Every claim in it is anchored to a code location or a
measurement from a real installation; the appendix lists both.

What it concludes is not "build this". Section 9.4 splits the work into four
stages and argues only the first two are justified today:

  stage 0  doctor stops reporting 31 phantom anchors as broken payloads;
           libdir written or its dead code removed; an E2E proving
           provider-scoped removal replaces hand-written `xvm.remove`
  stage 1  spec 3 gate, `type = "files"`, install stops materializing,
           expansion computed at materialize time
  stage 2  a per-payload manifest -- only once recipe drift is shown to
           cause imprecise removal in practice
  stage 3  a `use()` hook -- only once stale gcc/llvm configs are shown to
           hurt; that is two recipes, not a class
  stage 4  generation trees -- only if deep merge turns out to be required

Stages 0 and 1 fix every user-visible problem we found. The other three each
address a problem that has not yet been shown to exist, and the doc says so
rather than scheduling them.

It also records the honest counter-position (section 9.3): no official recipe
declares a header op, and only four packages populate the sysroot, so if
multi-version headers are not a real need, fixing the specific bugs is the
right answer instead. That judgement is the reviewer's, and it decides
whether the rest of the document is worth anything.

Does not affect 0.4.70 -- none of the problems it describes were introduced
there, and nothing in it ships in this release.
The document said 31. That was a script's count of program-typed entries with
no same-named executable in the payload root -- not what `self doctor`
reports. doctor resolves through `resolve_executable`, which searches `bin/`
as well, so most of those 31 resolve fine and were never reported.

Measured on the same 372-entry installation: doctor reports 9 broken
payloads, 2 of which are release anchors (`binutils@2.42`, `cairo@1.18.0`).
The fix is worth making and is already made; the number was wrong.
Records the ordering constraint that came out of implementation: xlings
ships first, package migration follows. The index cannot adopt the new
declaration until libxpkg publishes it, mcpp-index carries it, and a
released xlings understands it -- migrating first would leave recipes that
the shipped xlings cannot read.

Also states what the layering buys: a stall in migration does not hold back
the fixes users are already waiting for.
Release identity becomes compound: `(0.4.70) 2026.07.27.0`. The semantic
version stays the machine-readable one -- `self update` compares it, and
both indexes key entries on it, so replacing it with a date would leave
already-released clients unable to decide whether an upgrade exists. The date
is the human-facing label.

Also expands T3 into the seven steps the publish chain actually takes,
including the one that is easy to miss: mcpp-index entries carry both a
GLOBAL and a CN URL, so a GitHub-only release breaks resolution for CN users.
The document first kept the semantic version as the machine-readable one and
made the date a display label, on the assumption that an already-released
client could not compare a date against semver.

Checking rather than assuming: `self update` expands to `install
xlings@latest` plus `use xlings latest`, and the latter sorts through
`version_key_greater` (db.cppm:247), which splits on '.' and compares each
component numerically. [2026,7,27,0] against [0,4,68] decides on the first
component, so a date version is greater than every 0.x by construction, and
dates order correctly among themselves.

So the concern was wrong and `x.y.z` is retired outright. Months and days
carry no leading zero -- the comparator parses "07" and "7" identically,
and dropping the zero keeps string equality and numeric equality from
disagreeing about which key names a version.
@Sunrisepeak
Sunrisepeak merged commit 2643c45 into release/0.4.70 Jul 26, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant