Skip to content

Commit 70d2487

Browse files
committed
docs: the bare-name window, the runtime env scope, and AUR arming
Three things a user or a maintainer cannot discover from the code: - The one-release exit ramp for namespace-omitted selectors: what still resolves, what the warning says, that the canonical identity is what reaches the lock, that `mcpp add` performs the migration, and that a stated namespace is not eligible. Written with the reason attached — every published `compat.*` package spells its dependency bare, so failing them outright would make a program release invalidate data that is already published and cannot be edited. - Why the private libc directory is binary-scoped and never environment-scoped, as a table of blast radius rather than a rule to memorize: DT_RUNPATH reaches one object, LD_LIBRARY_PATH reaches every descendant process forever, and a GLIBC_PRIVATE-coupled libc handed to the host loader kills /bin/sh before main (#401). - How to arm the AUR automatic triggers, including that the kill switch is unsetting a repository variable rather than reverting a commit. English and Chinese both, plus the changelog entry for 2026.8.10.1.
1 parent d242922 commit 70d2487

7 files changed

Lines changed: 165 additions & 10 deletions

File tree

CHANGELOG.md

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
> 本文件追踪 `mcpp-community/mcpp` 公开仓的版本演进。
44
> 格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)
55
6-
## [2026.8.9.1] — 2026-08-09
6+
## [2026.8.10.1] — 2026-08-10
77

88
包身份、开发运行时与发布链收敛为同一组可验证事实。完整设计与验证记录见
99
`.agents/docs/2026-08-09-mcpp-template-runtime-graphics-aur-focused-design.md`
@@ -34,15 +34,37 @@
3434

3535
### 修复
3636

37+
- **私有 glibc 不再泄漏进子进程环境(#401)。** `mcpp run` 曾把私有 glibc 目录放进
38+
`LD_LIBRARY_PATH`,而该变量会被程序派生的**每一个**进程继承;`/bin/sh`**宿主**
39+
loader 加载(`PT_INTERP` 写死在可执行文件里,任何环境变量都改不了),于是在重定位
40+
阶段就死于 `undefined symbol: __pointer_chk_guard, version GLIBC_PRIVATE`
41+
该目录本来只为「可执行文件 DT_NEEDED 闭包覆盖不到的 dlopen」而存在,而产物的
42+
RUNPATH 已经覆盖了它(link model 在 `--dynamic-linker` 旁就发了
43+
`-Wl,-rpath,<glibc>`,改动前后产物 RUNPATH 逐字节相同),所以这条环境项没有收益、
44+
只有代价。决策收敛在 `mcpp.platform.runtime_env_contract`:私有 libc 是
45+
**binary 作用域**,不是 environment 作用域。
3746
- RuntimeBinding 不再把 stale 的声明文本误当成实际 payload:有效 SubOS view 可规范化到
3847
唯一受管 payload 时记录真实身份;旧 view 断链时只解析声明精确指名的 payload,
3948
仍绝不枚举目录挑版本。
4049
- Linux ELF/glibc 闭包规则的单测只在 Linux 断言相应物理语义;macOS/Windows 原生 CI
4150
固定验证 typed no-op 边界,不再拿 Linux 结果误判其他平台。
4251

52+
### 迁移
53+
54+
- **省略 namespace 的依赖获得一个版本的过渡期。** 省略 namespace 依然精确表示
55+
`mcpplibs`,但当精确坐标未命中时,`compat.<name>` 与「不声明 namespace 的上游
56+
descriptor」两级会再被尝试一次;命中会打印弃用警告、给出可直接粘贴的 manifest
57+
片段,并把**规范身份**写入 lock/install/cache(歧义拼写只留在用户 manifest 里)。
58+
`mcpp add <裸名>` 直接把规范点分形式写回 `mcpp.toml`
59+
写明的身份(如 `mcpplibs.gtest`)不进入过渡期,未命中即失败;第三方 namespace 仍
60+
不可被裸名触达。该过渡期在 `2026.9` 移除。
61+
- 理由:索引里已发布的 `compat.*` 包与既有用户 manifest 全部使用裸名写法。
62+
「已发布的数据不得让程序失效」与「已发布的程序不得让既有数据失效」是同一条判据,
63+
两个方向都必须降级而不是变砖。#278 修掉的缺陷是**静默**回退,不是回退本身。
64+
4365
### 其他
4466

45-
- xlings pin 统一提升到 `2026.8.9.2`;自举 mcpp pin 提升到已发布的 `2026.8.8.4`
67+
- xlings pin 统一提升到 `2026.8.10.1`;自举 mcpp pin 提升到已发布的 `2026.8.8.4`
4668

4769
## [2026.8.8.4] — 2026-08-08
4870

docs/05-mcpp-toml.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,34 @@ The third form fails with an error that names the exact `(mcpplibs, asio)`
483483
identity that was tried and, when the short name exists elsewhere, gives a
484484
copyable explicit selector.
485485

486+
##### Migration window for bare names (`2026.8.10.1` → removed in `2026.9`)
487+
488+
Every published `compat.*` package and every manifest written before exact
489+
identity spells its dependency bare — `gtest = "1.15.2"`. Failing those
490+
outright on upgrade would break builds against data that is already published
491+
and cannot be edited retroactively, so for one release a bare name that misses
492+
`mcpplibs` still reaches `compat.<name>`, and a descriptor that declares no
493+
namespace at all still answers to its bare name.
494+
495+
It is not quiet about it:
496+
497+
```
498+
warning: dependency 'gtest' resolved to 'compat.gtest' through the deprecated
499+
bare-name search; namespace omission means `mcpplibs` only. Write the exact
500+
package:
501+
[dependencies.compat]
502+
gtest = "1.15.2"
503+
(or run `mcpp add compat.gtest@1.15.2`). This fallback is removed in 2026.9.
504+
```
505+
506+
What reaches `mcpp.lock`, the install layer and the cache is the canonical
507+
identity, so the ambiguous spelling lives in exactly one place — your manifest
508+
— until you change it. `mcpp add gtest@1.15.2` changes it for you.
509+
510+
The window does **not** apply to a selector that states a namespace
511+
(`mcpplibs.gtest` misses and stays missed), and a bare name still never reaches
512+
a third-party namespace.
513+
486514
**Why one identity?** Dependency resolution has to be reproducible. Candidate
487515
search would let two namespaces with the same short name be settled by index
488516
state, and adding an index could silently retarget an existing dependency.
@@ -511,7 +539,7 @@ path) but not required.
511539
The older fully-qualified spelling (`name = "chriskohlhoff.asio"`) is still
512540
accepted, so already-published descriptors keep working. `mcpp xpkg parse`
513541
enforces the descriptor rule — run it in your index CI. Descriptor identity
514-
requires mcpp >= 0.0.106; exact selectors require mcpp >= 2026.8.9.1; both use
542+
requires mcpp >= 0.0.106; exact selectors require mcpp >= 2026.8.10.1; both use
515543
xlings >= 0.4.69. Full normative text is in `docs/spec/package-identity.md`.
516544

517545
`mcpp new --template` deliberately reuses this identity model instead of

docs/08-toolchain-internals.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,36 @@ Post-install alignment follows the same identity rule: `glibc@2.44` resolves
383383
only `<xpkgs>/xim-x-glibc/2.44/{lib64,lib}`. A missing/stale exact payload is an
384384
error; another installed version is never a fallback.
385385

386+
### 6.2 Where a runtime search path is allowed to live (`runtime_env_contract.cppm`)
387+
388+
There are two ways to tell a loader where to look, and they differ by blast
389+
radius, not by convenience:
390+
391+
| | reaches | |
392+
| --- | --- | --- |
393+
| `DT_RUNPATH` | the one object that carries it, and its `dlopen()` | per-binary |
394+
| `LD_LIBRARY_PATH` | the process **and every process it ever spawns** | inherited, forever |
395+
396+
That second row is why the private libc payload is **binary-scoped**. A glibc's
397+
`libc.so.6` and its `ld.so` are version-locked through `GLIBC_PRIVATE`: 2.44's
398+
libc carries an undefined `__pointer_chk_guard` that only 2.44's own loader
399+
exports. An mcpp-built program is fine — `PT_INTERP` names the private loader.
400+
`/bin/sh` is not: its `PT_INTERP` names the **host** loader and no environment
401+
variable can override it, so a `popen()`/`system()` child dies during
402+
relocation, before `main`, with no output (mcpp#401; mcpp#291 is the same shape
403+
one hop closer in, killing mcpp's own nested host tools).
404+
405+
So mcpp never publishes the private libc directory through the environment. It
406+
does not need to: wherever a payload exists the link model already emits
407+
`-Wl,-rpath,<glibc>` beside `--dynamic-linker`, which covers the case the
408+
directory exists for — a `dlopen()` whose own `DT_NEEDED` closure does not
409+
consult the executable's RUNPATH.
410+
411+
This is a scope, not a condition. "Only export it when a dependency might
412+
`dlopen()`" still exports it, and the child that dies does not care why. Plain
413+
dependency runtime directories keep their environment scope: they have no
414+
loader coupling, so a host binary that stumbles onto them is at worst confused.
415+
386416
## 7. Extending the machinery
387417

388418
### 7.1 Adding a new toolchain (new compiler family or distribution)

docs/spec/package-identity.md

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
| **状态** | **评审中(Review)** —— 已实现 |
88
| **版本** | 1.2 |
99
| **最后修改** | 2026-08-09 |
10-
| **最低实现版本** | 描述符身份:mcpp **0.0.106**;精确 selector:mcpp **2026.8.9.1**(xlings >= 0.4.69) |
10+
| **最低实现版本** | 描述符身份:mcpp **0.0.106**;精确 selector:mcpp **2026.8.10.1**(xlings >= 0.4.69) |
1111
| **作者/维护** | mcpp-community |
1212
| **相关设计文档** | `.agents/docs/2026-06-20-package-resolution-architecture.md` §4<br>`.agents/docs/2026-06-26-identity-first-resolution-no-filename.md`<br>`.agents/docs/2026-07-25-issue278-descriptor-name-form-canonicalization-design.md`<br>`.agents/docs/2026-07-25-name-namespace-bidirectional-verification-report.md`<br>`.agents/docs/2026-07-25-name-namespace-canonical-implementation-spec.md` |
1313
| **相关 issue** | [mcpp#278](https://github.com/mcpp-community/mcpp/issues/278)<br>[xlings#381](https://github.com/openxlings/xlings/issues/381) —— 索引键缺命名空间维度(§3.3) |
@@ -30,7 +30,7 @@
3030
| ⚠️ **部分实现** | 已有实现,但语义或覆盖面与本规范有差异(差异已注明) |
3131
|**未实现** | 本规范要求但尚未支持;当前行为已注明 |
3232

33-
> 描述符身份规则自 mcpp 0.0.106 起实现;唯一精确 selector 自 2026.8.9.1
33+
> 描述符身份规则自 mcpp 0.0.106 起实现;唯一精确 selector 自 2026.8.10.1
3434
> 起实现。0.0.105 及更早版本要求的过渡形态(`name` 必须写成
3535
> `<namespace>.<name>`)仍被接受为**兼容写法**,见 §8。
3636
@@ -172,7 +172,7 @@ e2e `163_identity_first_resolution.sh` 锁住:身份为 `(acme, widget)` 的描
172172
173173
例如 gtest 必须写成 `compat.gtest``[dependencies.compat] gtest = ...`;裸 `gtest` 请求的是不同身份 `(mcpplibs, gtest)`
174174

175-
**已实现**(2026.8.9.1)。默认 namespace 的依赖身份门禁不再接纳 `compat` 或无 namespace descriptor。
175+
**已实现**(2026.8.10.1)。默认 namespace 的依赖身份门禁不再接纳 `compat` 或无 namespace descriptor。
176176

177177
**设计理由**:全域按短名搜索会让解析结果取决于「本机装了哪些索引」——
178178
1. 两个命名空间拥有同名包时,胜负由索引顺序决定,而用户 `[indices]` 添加的索引之间**没有全序**;
@@ -181,11 +181,38 @@ e2e `163_identity_first_resolution.sh` 锁住:身份为 `(acme, widget)` 的描
181181

182182
依赖解析的**可复现性**优先于书写便捷性。
183183

184+
#### 4.2.1 过渡期(`2026.8.10.1` 起,`2026.9` 移除)
185+
186+
索引里已发布的 `compat.*` 包与既有用户 `mcpp.toml` **全部**使用裸名写法。
187+
让它们在一次 mcpp 升级后直接失败,等于「发布一个程序,让已经发布、且无法追溯修改的
188+
数据失效」——这与「索引抬高 floor 不得让旧客户端变砖」是同一条判据的两个方向,
189+
两边都必须**降级**而不是变砖。
190+
191+
所以省略 namespace 的 selector 在一个版本内保留一条**出口坡道**
192+
193+
1. 先精确解析 `(mcpplibs, name)`
194+
2. **仅在未命中时**,再依次尝试 `(compat, name)` 与「descriptor 自己不声明 namespace」
195+
这一级;
196+
3. 命中即打印弃用警告,内容包含实际选中的完整身份与可直接粘贴的 manifest 片段;
197+
4. 写入 lock、install 与 cache 的是**规范身份**`compat.gtest`),歧义拼写只留在
198+
用户 manifest 里,直到用户改它;
199+
5. `mcpp add <裸名>` 直接把规范点分形式写回 `mcpp.toml`——碰一次就迁移一次。
200+
201+
不适用的情形(**不是**过渡期的一部分):
202+
203+
- 写明了 namespace 的 selector(`mcpplibs.gtest``[dependencies.mcpplibs]`)——
204+
那是一个身份声明,未命中就是未命中;
205+
- 第三方 namespace——裸名从来、且仍然不可触达(§4.2 的供应链理由不变)。
206+
207+
> #278 修掉的缺陷是**静默**:mcpp 带着一个用户从未写过的 namespace 继续往下走,
208+
> 并且不说。一条带完整身份的警告已经消灭了「静默」,同时保住了已发布的数据。
209+
> 同一个版本对 `ns:name → ns.name` 用的也是同一套过渡期处理。
210+
184211
### 4.3 解析失败时的诊断
185212

186213
唯一身份落空时,mcpp **必须**明确失败并列出该身份;若同短名存在于其他 namespace,**应当**只在诊断中给出可复制的显式 selector,禁止把提示结果回灌解析。
187214

188-
**已实现**(2026.8.9.1):
215+
**已实现**(2026.8.10.1):
189216

190217
```
191218
error: dependency 'asio': no package found
@@ -417,5 +444,5 @@ lua = "0.0.3"
417444

418445
---
419446

420-
> 描述符身份规则自 mcpp 0.0.106 起实现;精确 selector 自 2026.8.9.1 起实现。当前实现符合本规范,状态为「评审中」。
447+
> 描述符身份规则自 mcpp 0.0.106 起实现;精确 selector 自 2026.8.10.1 起实现。当前实现符合本规范,状态为「评审中」。
421448
> 英文版待补(`docs/spec/` 顶层按仓库惯例为英文,本文档先以中文成稿)。

docs/zh/05-mcpp-toml.md

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,30 @@ asio = "1.38.1"
437437

438438
第三种写法会明确报错,指出实际尝试的 `(mcpplibs, asio)`;若该短名存在于别处,错误信息会给出可直接复制的显式 selector。
439439

440+
##### 裸名过渡期(`2026.8.10.1` 起,`2026.9` 移除)
441+
442+
索引里已发布的 `compat.*` 包与既有 manifest **全部**写成裸名(`gtest = "1.15.2"`)。
443+
升级后直接失败,等于让一次程序发布把**已经发布、且无法追溯修改**的数据作废,
444+
所以有一个版本的过渡期:裸名在 `mcpplibs` 未命中时仍可到达 `compat.<name>`,
445+
不声明 namespace 的 descriptor 也仍可被裸名解析。
446+
447+
但它不再静默:
448+
449+
```
450+
warning: dependency 'gtest' resolved to 'compat.gtest' through the deprecated
451+
bare-name search; namespace omission means `mcpplibs` only. Write the exact
452+
package:
453+
[dependencies.compat]
454+
gtest = "1.15.2"
455+
(or run `mcpp add compat.gtest@1.15.2`). This fallback is removed in 2026.9.
456+
```
457+
458+
写进 `mcpp.lock`、install 与 cache 的是**规范身份**,歧义拼写只存在于你的
459+
`mcpp.toml` 里,直到你改它;`mcpp add gtest@1.15.2` 会替你改。
460+
461+
过渡期**不适用于**写明 namespace 的 selector(`mcpplibs.gtest` 未命中就是未命中),
462+
裸名也**仍然**到不了第三方 namespace。
463+
440464
**为什么只允许一个身份?** 因为依赖解析必须可复现。候选搜索会让同短名包受索引状态影响,新增索引还可能悄悄重定向既有依赖。
441465

442466
**给 xpkg 作者:** 索引描述符里,身份是 `(package.namespace, package.name)` 二元组。命名空间是点分路径,**`name` 是单一原子段**:
@@ -455,7 +479,7 @@ package = {
455479

456480
文件名只是提示 —— 描述符按声明的身份被发现,所以 `pkgs/c/chriskohlhoff.asio.lua``pkgs/z/anything.lua` 解析结果完全相同。推荐 `<name>.lua``<namespace>.<name>.lua`(命中 mcpp 的快路径),但不强制。
457481

458-
旧的完全限定拼写(`name = "chriskohlhoff.asio"`)仍被接受,已发布的描述符无需改动。`mcpp xpkg parse` 会校验该规则,请在索引 CI 里跑它。描述符身份需要 mcpp >= 0.0.106,精确 selector 需要 mcpp >= 2026.8.9.1,两者使用 xlings >= 0.4.69;规范全文见 `docs/spec/package-identity.md`
482+
旧的完全限定拼写(`name = "chriskohlhoff.asio"`)仍被接受,已发布的描述符无需改动。`mcpp xpkg parse` 会校验该规则,请在索引 CI 里跑它。描述符身份需要 mcpp >= 0.0.106,精确 selector 需要 mcpp >= 2026.8.10.1,两者使用 xlings >= 0.4.69;规范全文见 `docs/spec/package-identity.md`
459483

460484
`mcpp new --template` 刻意复用同一身份模型,而不是另造包文法:
461485
`[ns.]name[@version][:tname]`。其中裸名同样只表示 `mcpplibs`,version 与模板名可分别

docs/zh/08-toolchain-internals.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,30 @@ verdict 以 `.mcpp-runtime-verdicts.json` 存在 `build.ninja` 旁,键包含产
310310
`<xpkgs>/xim-x-glibc/2.44/{lib64,lib}`。精确 payload 缺失/陈旧就是错误,其他已安装版本
311311
永远不是回退项。
312312

313+
### 6.2 一条运行时搜索路径可以住在哪里(`runtime_env_contract.cppm`)
314+
315+
告诉 loader「去哪找」有两条通道,差别不在便利性,而在**波及范围**:
316+
317+
| | 波及到 | |
318+
| --- | --- | --- |
319+
| `DT_RUNPATH` | 携带它的那**一个**对象,及其 `dlopen()` | 逐二进制 |
320+
| `LD_LIBRARY_PATH` | 本进程**以及它派生的每一个进程** | 继承,且一直传下去 |
321+
322+
第二行就是私有 libc 目录必须是 **binary 作用域**的原因。glibc 的 `libc.so.6` 与它的
323+
`ld.so` 通过 `GLIBC_PRIVATE` 版本锁死:2.44 的 libc 里 `__pointer_chk_guard` 是未定义
324+
引用,只有 2.44 自己的 loader 导出它。mcpp 构建出来的程序没事——`PT_INTERP` 指向私有
325+
loader;`/bin/sh` 有事:它的 `PT_INTERP` 指向**宿主** loader,而且任何环境变量都改不了,
326+
于是 `popen()`/`system()` 的子进程在重定位阶段就死掉,连 `main` 都进不去,也没有任何
327+
输出(#401;#291 是同一形状往内一跳,杀掉的是 mcpp 自己的嵌套宿主工具)。
328+
329+
所以 mcpp 不会把私有 libc 目录发布到环境里。也不需要:只要存在 payload,link model
330+
就已经在 `--dynamic-linker` 旁发了 `-Wl,-rpath,<glibc>`,恰好覆盖这个目录存在的唯一
331+
理由——某个 `dlopen()``DT_NEEDED` 闭包看不到可执行文件的 RUNPATH。
332+
333+
这是**作用域**,不是条件判断。「只在依赖可能 dlopen 时才导出」仍然是导出,而死掉的
334+
子进程不关心原因。普通依赖运行时目录保留环境作用域:它们没有 loader 耦合,宿主二进制
335+
撞上去最多是困惑,不会死。
336+
313337
## 7. 扩充指南
314338

315339
### 7.1 新增一个工具链(新编译器家族或发行版)

scripts/aur/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ fallback):
9797

9898
```sh
9999
scripts/aur/update.sh # latest complete stable release
100-
scripts/aur/update.sh 2026.8.9.1 # accepted only if it is that exact latest tag
100+
scripts/aur/update.sh 2026.8.10.1 # accepted only if it is that exact latest tag
101101
```
102102

103103
The renderer downloads both Linux payloads and sidecars, recomputes their

0 commit comments

Comments
 (0)