feat: date-based version scheme + xlings pin single source of truth + fix #291 (2026.7.27.1) - #292
Merged
Merged
Conversation
…2026.7.27.1)
Two things that had to ship together: mcpp's version format moves to the
date form YYYY.M.D.N, and that format broke a comparison that had to be
fixed first.
Version scheme
--------------
`YYYY.M.D.N`, month/day unpadded, matching the xlings ecosystem (xlings
migrated from 0.4.70 to 2026.7.27.x the same day). The fourth segment:
`.0` is reserved for formal / stable releases; routine iterations start
at `.1`.
Ordering across the change is monotonic — 0.0.109 < 2026.7.27.1.
Fix: four-segment versions were silently truncated
-------------------------------------------------
`version_req::Version` was strictly three segments, so
parse_version("2026.7.27.1") produced {2026, 7, 27} and dropped the fourth
without an error — every release of a given day compared EQUAL.
That lands on pm/index_contract.cppm: an index declaring
min_mcpp = "2026.7.27.5" against a running 2026.7.27.1 compares equal,
`have >= need` holds, and the E0006 floor lets a too-old mcpp through —
the single thing that check exists to prevent.
Version now carries a fourth component plus `components` (how many segments
the source wrote). Two constraints:
- ordering compares only the four numbers; if `components` took part,
"1.2" would stop equalling "1.2.0".
- str() must render the fourth segment, because pm/resolver.cppm returns
it as the RESOLVED dependency version, which flows into the lock file
and the xlings wire address. A version ending in ".0" collapsed to three
segments would address an index key that does not exist.
Existing three-segment resolution is byte-identical: the fourth component
is 0 on both sides, so ^ / ~ / = are unchanged (regression cases added).
xlings pins: one source of truth, machine-checked
-------------------------------------------------
All pins move to 2026.7.27.2, and `pinned::kXlingsVersion` becomes the
single source of truth that .github/tools/check_version_pins.sh enforces
across all 16 pin sites in 7 files — plus mcpp's own version across its 4
locations.
This replaces a comment that said to keep three named files in lock-step by
hand. That list was ALREADY incomplete: it omitted both composite actions,
which is how CI's sandbox sat on 0.4.30 for weeks unnoticed. Building the
check surfaced three more pin sites nobody had recorded (release.yml's
hardcoded aarch64 xlings tarball literals, which are not interpolated from
XLINGS_VERSION) and three stale bootstraps (ci-fresh-install.yml on v0.4.38
twice and v0.4.51).
install_pinned_mcpp.sh's version regex matched three segments only, which
does not fail on a date version — it truncates both sides identically, so
the pin check would silently accept any release of that day.
Verified: v2026.7.27.2 exists on both openxlings/xlings and d2learn/xlings
with all four platform assets, same naming as 0.4.69, direct GET 200. The
aarch64 bundling step is guarded by `if curl`, so a missing asset would
degrade silently rather than fail.
… glibc (#291) Also corrects the pin invariant this branch's own guard got wrong, which is what turned every CI job red. #291 — private glibc payload on LD_LIBRARY_PATH ----------------------------------------------- LD_LIBRARY_PATH is inherited by the entire process subtree. When the run target is something that shells out — a course provider calling popen("mcpp test ...") — /bin/sh is a HOST binary: its PT_INTERP is baked in, so it loads the HOST ld.so, while this variable hands it the PAYLOAD libc.so.6. glibc's libc and ld.so are version-locked through GLIBC_PRIVATE (verified: the payload libc.so.6 carries a GLIBC_PRIVATE dependency on ld-linux-x86-64.so.2), so on any host whose glibc differs from the payload's the shell dies of SIGSEGV inside the dynamic linker, before main, with empty stdout and no diagnostic. This is NOT "compiled on a different build machine, ABI-incompatible" as originally reported — it is plain version mismatch, and it hits every user whose host glibc differs. It survived this long precisely because it does not reproduce when the two happen to match. The payload dir is the one entry on LD_LIBRARY_PATH that is not also in the executable's RUNPATH — flags.cppm excludes it deliberately to keep static and musl links clean. Its only purpose is letting a dlopen()'d library, whose own DT_NEEDED closure never consults the executable's RUNPATH, resolve the same libc. So emit it only when the build actually has such a library (depRuntimeLibraryDirs non-empty). The real host-GL passthrough case reaches mcpp through compat.glx-runtime's `[runtime] library_dirs`, which populates exactly that vector, so it is unaffected; a zero-dependency binary no longer gets it. process.cppm's strip_private_glibc already protects mcpp's own children. It cannot reach one hop further out — mcpp sets the variable for the target on purpose, and what the target spawns is beyond its control. Not emitting it when unnecessary is within its control. e2e 166 pins both directions: a zero-dependency project must NOT receive it, a project with `[runtime] library_dirs` MUST. It asserts on the emitted environment rather than on whether a shell crashes — a crash-based test passes for the wrong reason wherever host and payload glibc match, which is how this went unnoticed. e2e 65 dropped its glibc-payload assertion: its fixture has no dependencies, so it never modelled the dlopen case its own header describes. Pin guard: the bootstrap pin is SUPPOSED to lag ----------------------------------------------- The guard added earlier in this branch asserted that all four version locations are equal. That is wrong, and CI proved it — every job failed with `package 'mcpp@2026.7.27.1' not found`. .xlings.json and MCPP_PIN name a mcpp that is already published, so they must agree with each other but must NOT track the version being built; they are bumped in a separate commit after the release lands in xim-pkgindex (as ci-fresh-install.yml's own comment says). Both are back to 0.0.109. The guard now checks three invariants instead: mcpp.toml == MCPP_VERSION; the two bootstrap sites agree; and the bootstrap pin is never NEWER than the version being built — a four-key numeric sort, so the date scheme orders correctly. That last check reproduces the exact CI failure locally in under a second.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
三件事:mcpp 版本号改日期格式、这个格式踩到的一处比较断点、以及 #291。
设计与计划:
.agents/docs/2026-07-27-date-version-and-xlings-pin-design.md/-plan.md1. 版本号规范
YYYY.M.D.N,月/日不补零,与 xlings 生态对齐(xlings 同日从0.4.70迁到2026.7.27.x)。本次取
2026.7.27.1。跨方案序单调:0.0.109<2026.7.27.1。2. 修复:4 段版本号被静默截断,致 E0006 索引底线失效
version_req::Version原本严格三段,parse_version("2026.7.27.1")解析出{2026,7,27}后丢弃第 4 段且不报错 —— 同一天内所有版本比较相等。后果落在
pm/index_contract.cppm:索引写min_mcpp = "2026.7.27.5"、用户跑2026.7.27.1→ 相等 →have >= need成立 → 底线检查放行。而挡住这种情况正是它存在的唯一理由。修法:扩到 4 段 + 新增
components(源串写了几段)。两条约束:= default的<=>会把components也纳入,导致"1.2"≠"1.2.0"。str()必须能回写第 4 段 —— 它是载荷性的:pm/resolver.cppm:138用它重建已解析的依赖版本串,该串流向 lock 文件与 xlings wire 地址。.0结尾的版本塌成三段就会指向不存在的索引 key。既有三段依赖解析逐字节不变,已加
^/~回归用例。3. #291:私有 glibc payload 不再无条件进运行目标的 LD_LIBRARY_PATH
该变量被整棵进程子树继承。运行目标若会 shell out(provider 调
popen("mcpp test ...")),/bin/sh是宿主二进制 ——PT_INTERP烙死在文件里,装载它的永远是宿主 ld.so,而这个变量把 payload 的libc.so.6递给它。libc 与 ld.so 通过
GLIBC_PRIVATE版本锁定(实测 payloadlibc.so.6对ld-linux-x86-64.so.2有GLIBC_PRIVATE依赖),必须同一次构建。于是宿主 glibc ≠ payload 就崩在装载器里、main之前,stdout 全空、零诊断。这不是 issue 里说的「构建机不同致 ABI 不兼容」,而是纯版本错配 —— 受影响的是所有宿主 glibc 与 payload 不同的用户。它活这么久,正因为版本相同时不复现。
payload 目录是
LD_LIBRARY_PATH里唯一不同时在可执行文件 RUNPATH 中的一项(flags.cppm刻意排除以保持静态/musl 链接干净)。它唯一的用途是让dlopen()的库(其 DT_NEEDED 闭包不查主程序 RUNPATH)解析到同一份 libc。故改为仅当构建确有此类依赖库时注入(depRuntimeLibraryDirs非空)—— 真实的 host-GL 透传走compat.glx-runtime的[runtime] library_dirs,正落在该条件内,行为不变;零依赖二进制不再拿到。process.cppm::strip_private_glibc已保护 mcpp 自己的子进程,但够不到再外一层:变量是 mcpp 有意设给目标的。能控制的是不必要时不发。4. xlings pin 收敛到唯一真源 + 机器校验
全部升到
2026.7.27.2。src/xlings.cppm::kXlingsVersion成为唯一真源,.github/tools/check_version_pins.sh强制.github/下全部 16 个 pin 点、7 个文件一致。这替换掉了那句「keep in lock-step with release.yml / cross-build-test.yml / ci-linux-e2e.yml」的注释 —— 清单本身就不全,漏了两个 composite action,CI 沙箱因此在 0.4.30 静默数周(#289)。
落实检查时找出四类此前无人记录的问题:
XLINGS_VERSION插值)release.yml:345/347/349v0.4.38×2ci-fresh-install.yml:94/230v0.4.51ci-fresh-install.yml:291install_pinned_mcpp.sh×2守卫自身的不变量写错了,CI 抓到了
第一版守卫断言四处版本全等,导致 CI 全红:
package 'mcpp@2026.7.27.1' not found。.xlings.json/MCPP_PIN指的是bootstrap 用哪个已发布 mcpp,必须落后,发布后单独 bump(ci-fresh-install.yml:41注释原本就写着)。现改为三条不变量:mcpp.toml == MCPP_VERSION;两个 bootstrap 站点互相一致;bootstrap pin 不得新于在建版本(4 键数值排序,日期方案才排得对)。最后这条能在本地一秒内复现 CI 那次失败。验证
LDLP=[.../xim-x-glibc/2.39/lib64]v2026.7.27.2在openxlings/xlings与d2learn/xlings均存在,四平台齐全、命名同构、直连 GET 200。release.yml 那份 aarch64 xlings 由if curl保护,资产缺失只会静默降级,故必须先验顺带发现并已提 issue
#293 —
tests/e2e/_inherit_toolchain.sh把开发者真实 payload 符号链接进临时MCPP_HOME,测试的 elfpatch/specs 重写会穿过链接写回真实 payload,把mktemp -d路径烙死;临时目录一删,本机 gcc 永久报cannot execute: required file not found。本次就撞上了:~/.xlings/.../xim-x-gcc/16.1.0的 24 个可执行文件 +specs全部损坏,22_doctor_cache_publish.sh因此失败 —— 一个测试的失败源于上一次测试运行。已用 patchelf 手工修复本机并验证真实编译运行通过。记录的上游边界(不在本 PR 范围)
xlings 侧对 4 段版本有三处不足,同样作用于 xlings 自己的版本:
xlings install mcpp@2026.7.27.1xlings install mcpp(latest)latest.ref字面串semver::parsereturn nulloptsort_desc.10排在.9之下quick_install.sh镜像择优前两行是 mcpp 能装上的原因,已被 xlings 自己的
2026.7.27.x在生产验证。