Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
9188fef
docs(design): the #646-#649 triage record, its probes, and the implem…
speak-agent Sep 15, 2026
ed52f78
The refresh decision walks the resolver's bare-name rung; the saved s…
speak-agent Sep 15, 2026
5c8b851
Refreshes go through one policy; an offline plan that needs a downloa…
speak-agent Sep 15, 2026
d69ca44
Symbol provision: GNU_UNIQUE is vague linkage, and a definition both …
speak-agent Sep 15, 2026
199d478
One process, one C++ runtime; one static package, one image; the llvm…
speak-agent Sep 15, 2026
9f336b0
Features, tools and git sources: forwards over every table, one namin…
speak-agent Sep 15, 2026
d1bcca7
Tests for forwards, feature-deps restatements, tool packages, git mem…
speak-agent Sep 15, 2026
95fad45
docs: forwards and dependency feature tokens, feature-deps restatemen…
speak-agent Sep 15, 2026
f78b8d1
xlings children are owned and bounded; the envelope reports observed …
speak-agent Sep 15, 2026
0f2c7aa
docs: refresh bounds and auto_refresh scope, observed network effects…
speak-agent Sep 15, 2026
3866c91
e2e 700-705 and 307, and docs 04/20/50: one C++ runtime per process, …
speak-agent Sep 15, 2026
18c925d
Merge W2: features, tools and git sources (G1-G6)
speak-agent Sep 15, 2026
3c3fe5a
Choose the link line by host and target object format (#647 E3)
speak-agent Sep 15, 2026
bf86a6d
Keep [package.metadata] verbatim and report unknown [package] keys (#…
speak-agent Sep 15, 2026
cd4e0c2
The resolved graph for the root build program, pack strips what the g…
speak-agent Sep 15, 2026
f7adbcf
The default mcpplibs artifact is a GLOBAL/CN region object, and exist…
speak-agent Sep 15, 2026
5223452
Merge W1: images and runtimes (R1-R5)
speak-agent Sep 15, 2026
66b29cd
Name the PE host link shape by its format
speak-agent Sep 15, 2026
759ec68
CI: e2e 700 on the hermetic llvm job, measurement readings in the mac…
speak-agent Sep 15, 2026
9362d9a
Merge W3: the graph for build programs, the link line by format, pack…
speak-agent Sep 15, 2026
8181523
One derivation of the target object format for the contract table and…
speak-agent Sep 15, 2026
25e8e1c
Mach-O: the measured runtime identity split is reported; the stream-i…
speak-agent Sep 15, 2026
bae5062
docs(record): the local verification readings of the integrated branch
speak-agent Sep 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,347 changes: 1,347 additions & 0 deletions .agents/docs/2026-09-16-646-649-four-issues-by-home.md

Large diffs are not rendered by default.

498 changes: 498 additions & 0 deletions .agents/docs/2026-09-16-646-649-implementation-plan.md

Large diffs are not rendered by default.

778 changes: 778 additions & 0 deletions .agents/docs/2026-09-16-646-649-probes.sh

Large diffs are not rendered by default.

322 changes: 322 additions & 0 deletions .agents/docs/2026-09-16-646-649-verify.sh

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion .agents/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ superseded_by: 2026-09-07-....md # when status is superseded
---
```

289 records.
291 records.

## By subject

Expand Down Expand Up @@ -62,6 +62,8 @@ Records that declare one. Everything else is listed by date below.

### triage

- [#646 to #649 implemented: the plan, its review from eight angles, and the ledger](2026-09-16-646-649-implementation-plan.md) — active
- [Four issues from a framework and an editor: #646, #647, #648 and #649, read against the engine and routed by home](2026-09-16-646-649-four-issues-by-home.md) — active
- [Link forms, standard levels and a path limit: the asks of #641 and #642, read against the code](2026-09-15-641-642-link-forms-standards-and-paths.md) — landed
- [#641 and #642 implemented: the plan, its review from eight angles, and the ledger](2026-09-15-641-642-implementation-plan.md) — landed
- [#634 implemented across five repositories: the plan, its review, and the ledger that tracks it](2026-09-14-634-implementation-plan.md) — landed
Expand All @@ -74,6 +76,8 @@ Records that declare one. Everything else is listed by date below.

### 2026-09

- [#646 to #649 implemented: the plan, its review from eight angles, and the ledger](2026-09-16-646-649-implementation-plan.md) — active
- [Four issues from a framework and an editor: #646, #647, #648 and #649, read against the engine and routed by home](2026-09-16-646-649-four-issues-by-home.md) — active
- [Link forms, standard levels and a path limit: the asks of #641 and #642, read against the code](2026-09-15-641-642-link-forms-standards-and-paths.md) — landed
- [#641 and #642 implemented: the plan, its review from eight angles, and the ledger](2026-09-15-641-642-implementation-plan.md) — landed
- [The build database of #636, and two defects on the way to the latest xlings](2026-09-14-636-build-database-and-the-latest-xlings.md) — active
Expand Down
8 changes: 7 additions & 1 deletion .github/workflows/ci-linux-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ jobs:
# skips. They run here, where llvm is installed, and each is held to the
# line it prints only when it ran to the end, and to the line of the step
# that runs a program (#641).
- name: "graph C++ runtime e2e (663, 690, 696)"
- name: "C++ runtime e2e that needs llvm (663, 690, 696, 700)"
run: |
set -o pipefail
export PATH="$HOME/.xlings/subos/current/bin:$PATH"
Expand All @@ -443,3 +443,9 @@ jobs:
"PASS: 690 a shared library over a graph C++ runtime is refused or carries a stated private copy"
run_and_assert tests/e2e/696_a_cxx_layer_provider_keeps_its_own_standard.sh \
"PASS: a C++-layer provider compiles its implementation units at its own standard"
# #646 F3a: the default llvm shape of a program over a C++ shared library
# aborted with std::bad_cast before its programs took the library's contract.
run_and_assert tests/e2e/700_a_program_over_a_cxx_shared_library_has_one_cxx_runtime.sh \
"ok: a program over a C++ shared library runs on one C++ runtime" \
"ok: a stated self-contained program over a coupled C++ shared library is refused" \
"PASS: 700 a program over a C++ shared library has one C++ runtime"
35 changes: 33 additions & 2 deletions .github/workflows/ci-macos-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
e2e:
name: e2e suite (macOS ARM64, self-host)
runs-on: macos-15
timeout-minutes: 30
timeout-minutes: 60
# NOTE: no MCPP_VERBOSE — the e2e suite asserts mcpp's default quiet
# output (tests 48/53).
steps:
Expand Down Expand Up @@ -64,4 +64,35 @@ jobs:
"$MCPP" self config
# macOS default toolchain is LLVM
"$MCPP" toolchain default "llvm@${MCPP_LLVM_VER}"
bash tests/e2e/run_all.sh
echo "MCPP=$MCPP" >> "$GITHUB_ENV"
set -o pipefail
bash tests/e2e/run_all.sh 2>&1 | tee "$RUNNER_TEMP/e2e-suite.log"

# Measurement legs print READING lines and pass whatever they read; the
# readings are what a decision is taken from (#646 F2: whether a C++
# exception thrown in a dylib is caught by its class under the payload's
# default runtime), so they are collected where a reader finds them.
- name: Measurement readings
if: always()
shell: bash
run: |
{
echo "### Measurement readings (macOS)"
echo '```'
grep -h '^READING' "$RUNNER_TEMP/e2e-suite.log" 2>/dev/null || echo "(none)"
echo '```'
} >> "$GITHUB_STEP_SUMMARY"

# #647 E3: an Android row links on a macOS host. Its own step, because the
# NDK is a large download the suite's per-test bound does not allow for,
# and the shard does not carry it (the script declares android-ndk).
- name: "Android row on a macOS host (721)"
timeout-minutes: 30
shell: bash
run: |
set -o pipefail
export MCPP_VENDORED_XLINGS="$XLINGS_BIN"
export MCPP_E2E_TOOLCHAIN_MIRROR=GLOBAL
"$MCPP" toolchain install android-ndk@30.0.16248370
bash tests/e2e/721_*.sh 2>&1 | tee "$RUNNER_TEMP/721.log"
grep -q '^PASS: 721' "$RUNNER_TEMP/721.log"
16 changes: 15 additions & 1 deletion .github/workflows/ci-windows-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,18 @@ jobs:
export MCPP_E2E_TOOLCHAIN_MIRROR=GLOBAL
"$MCPP_SELF" self config --mirror GLOBAL
"$MCPP_SELF" toolchain default llvm@20.1.7
bash tests/e2e/run_all.sh
set -o pipefail
bash tests/e2e/run_all.sh 2>&1 | tee "$RUNNER_TEMP/e2e-suite.log"

# Measurement legs print READING lines (#646 F2 across PE images, #649 E10
# the llvm row's recorded CRT); collected where a reader finds them.
- name: Measurement readings
if: always()
shell: bash
run: |
{
echo "### Measurement readings (Windows, shard ${{ matrix.shard }})"
echo '```'
grep -h '^READING' "$RUNNER_TEMP/e2e-suite.log" 2>/dev/null || echo "(none)"
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
93 changes: 93 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,99 @@

## [Unreleased]

### 一个进程一个 C++ 运行时、图与打包的事实、离线与有期限的规划:#646 至 #649(2026.9.16.1)

一个 UI 框架与一个编辑器报告的 23 项,外加实测发现的 12 项。按归属分诊后,引擎承担其中的通用
缺陷与通用能力;插件、索引与 xlings 清单各自的一项在各自仓库。设计、测量与计划:
`.agents/docs/2026-09-16-646-649-*.md`。

**图像与运行时(#646、#649 E10)**

- **程序与共享库共用一个 C++ 运行时(F3a)。** ELF 上程序默认 `self-contained`、共享库默认
`toolchain-coupled`,一个加载本次构建所出 C++ 共享库的程序于是在一个进程里带两份运行时:
llvm 默认构建运行即以 `std::bad_cast` 中止(退出 134),gcc 带 900 个被抢占的 libstdc++
符号。现在这样的程序与测试取共享库的契约;显式写明自含运行时的程序被编译前拒绝,reason 为
`program-cxx-runtime-split`。(单测 `Distribution.*`,e2e 700)
- **符号重复检查不再报告按构造共享的定义(F3b)。** `STB_GNU_UNIQUE` 按弱定义处理;两个映像
取自同一个构建对象(如 `std.o`)的定义,以及工具链自身运行时提供的 std 模块初始化器,不算
冲突;真实的重复定义仍然报告。(单测 `SymbolProvision.*`,e2e 701)
- **静态包放进到达它的那个共享库(F1)。** 只被一个依赖共享库到达的静态包链进该库,不再链进
程序(此前库里留着未定义符号,只在 ELF 上靠程序的副本侥幸运行);被多个映像到达时,在
Mach-O、PE 与 Android `app` 行上编译前拒绝(`static-package-in-two-images`),其他 ELF 行
照旧构建并给出 `build/static-placement` 警告,`--strict` 下失败,出路是
`linkage = "shared"`。(单测 `StaticPlacement.*`,e2e 702、307)
- **Mach-O 上跨映像的 C++ 身份(F2,macos-15 实测)。** 默认每个映像内嵌一份隐藏的
`libc++.a`:dylib 里抛出的 `std::runtime_error` 在程序中不按该类捕获,两个
`std::error_code` 的 category 比较不相等;全角色 `cxx_runtime = "host-coupled"` 时
两者成立。默认值本次不改(它是今天每个 macOS 构建的形态,改动另行记录),但这样的构建
会被告知一次:`build/cxx-runtime-identity`。(e2e 704 的读数)
- **llvm 行在 MSVC ABI 上如实记录静态 CRT(E10 第一步)。** 解析记录此前写 `host-coupled`
而产物静态链接 `libcmt`;显式要求本行不能交付的动态运行时会得到说明。默认值是否改为 `/MD`
另行测量与记录。(e2e 703;Mach-O 与 PE 上跨映像的异常身份由 e2e 704、705 测量)

**图、构建程序与打包(#647 E1–E3、#649 E5、E9)**

- **根构建程序读取解析后的依赖图(E1)。** `mcpp::graph_file()` / `MCPP_GRAPH_FILE` 指向按
依赖在前排序的 JSON 文档,每个包含 `manifest_dir`、`features`、`targets`、链接形态与原样
保留的 `[package.metadata.<tool>]`;文档摘要进入构建程序的重跑键,编辑依赖的 metadata 会
重跑根构建程序,编辑其源码不会。`[package]` 的未知键像 `[build]` 一样报告。(e2e 720)
- **macOS 主机上 Android 行可以链接(E3)。** 链接行按主机与目标对象格式选择
(`link_shape`),Apple SDK 分支只用于 Mach-O 目标;Linux 上的链接行逐字节不变。
(单测 `LinkShape.*`,e2e 721 在 macOS CI 上运行)
- **打包剥离构建出来的一切(E5)。** Android 行的程序此前根本没有经过剥离步骤;现在每条剥离的
行上剥离程序、本图构建的共享库与暂存的工具链运行时副本(`--strip-unneeded`,保留导出),
状态行只在真正剥离时写 "stripped";`--no-strip` 与 `--debug-symbols` 作用于每个文件,构建
程序通过 `MCPP_PACK_STRIP` / `MCPP_PACK_DEBUG_SYMBOLS_DIR` 读到同一决定。(e2e 722)
- **`mcpp pack --message-format json`(E9)。** 输出一个 `mcpp.pack` 信封,逐项列出产物的
绝对路径、类型、格式与目标行;人类可读的行改走 stderr。`pack` 接受 `--release` / `--dev`;
`build`、`run`、`test`、`pack` 共用一个 profile 判定,`--profile` 优先于简写(此前 `run`
相反)。(单测 `BuildProfile.*`,e2e 723)

**feature、工具与 git 依赖(#647 E4、#649 E6–E8)**

- **转发校验覆盖每一张依赖表与每一行(E4.1)。** 经 `[build-dependencies]` 的转发、只在另一行
声明的依赖不再被报告为未声明;无处声明的键仍然报告。(e2e 710)
- **`[feature-deps]` 的重述(E4.2)。** 文档改为写明重述来源;来源与生效声明不同的重述被拒绝,
消息给出两个来源。(e2e 711)
- **`dep_bin` 发布限定名(E4.3)。** 一个函数给出提供者对消费者的所有名字,`dep_dir`、
`dep_linkage` 与 `dep_bin` 共用;`namespace = "ns"` 加 `name = "x"` 的包也有
`MCPP_DEP_NS_X_BIN_*`。(e2e 187、711)
- **只提供程序的包不进入消费者的图(E6)。** 这样的包不被扫描、不链接进消费者,其程序只由工具
子构建产出;feature 工具于是可以依赖声明它的包。包之间的环在解析时拒绝(`package-cycle`),
各种缓存模式一致;工具请求自身时在第一次重复即拒绝。(e2e 712)
- **git 依赖可以选择仓库里的成员包(E7)。** 键的身份不是根包时,在根的 `[workspace] members`
中按身份查找;同一消费者对同一依赖的第二次声明合并其 `tools`、`features`、`host-module` 与
`reexport`;git 依赖的编译行写提交而不是空版本。(e2e 713)
- **`--features dep/feature`(E8)。** 作为根的转发应用;不指向任何依赖时警告,`--strict` 下
失败,且不再变成宏;普通名字保持文档所述的纯宏用法。`mcpp why deps` 接受 `--features`。
(e2e 714)

**编辑器在后台规划(#648)**

- **刷新判定与解析器走同一条阶梯(新发现)。** 省略命名空间的 `ftxui = "6.1.9"` 由解析器经
已弃用的裸名回退找到 `compat.ftxui`,刷新判定却只查精确坐标并判为缺失;防抖只有 120 秒,
这样的工程每次联网规划都执行一次 `xlings update`。现在判定也走该回退。(单测
`PmIndexRefresh.BareNameResolvedThroughTheLegacyRungIsNotAMiss`,e2e 730)
- **规划期间的子进程不继承调用方的管道(A2)。** 保存的标准输出是 close-on-exec(Windows 上不可
继承);此前构建程序以描述符 3 持有调用方读取的管道。(e2e 731)
- **xlings 子进程有期限并随 mcpp 结束(A3)。** 刷新受 `[index] refresh_timeout`(秒,默认
120)约束,超时视为刷新失败并继续用本地索引;经接口的安装在 300 秒无任何输出(含心跳)时终止;
子进程在自己的进程组(Windows 上为作业对象)中运行,结束 mcpp 即一并结束。(e2e 732)
- **信封的 `effects` 按观测报告 `network`(A4)。** 本次运行启动过刷新、安装或 git 远程操作时
列出,离线运行从不列出。(e2e 733)
- **`auto_refresh = false` 约束所有隐式刷新(A5)。** 安装前的刷新、重试前的刷新与自定义索引的
首次同步改走同一个刷新策略(`mcpp.pm.refresh_policy`)。(e2e 734)
- **离线缺下载有自己的诊断码(A1)。** `MCPP_OFFLINE_DOWNLOAD_REQUIRED` 与 refusal
`offline-download-required`,消息指出第一个需要下载的工具链、包、git 修订或索引。
(e2e 733、735)
- **默认索引制品按镜像分区(A6)。** mcpplibs 索引的 `artifact` 默认是
`{ GLOBAL = github, CN = gitcode }`,已有 home 的 `.xlings.json` 就地升级;`mirror = CN` 时
`mcpp index update` 只访问 GitCode。前提是 mcpplibs/mcpp-index#432 让两端制品逐字节一致。
(e2e 151)

**CI**:hermetic llvm job 运行 700 并断言结束行;macOS 与 Windows e2e 把测量行写入 job
summary;macOS 上单独一步运行 721。

### 链接形态、标准档位与路径长度:#641 与 #642(2026.9.15.2)

一个 UI 框架迁到 macOS 12 下限与 Android 独立共享库时报告的七项,全部在引擎内处理。
Expand Down
63 changes: 62 additions & 1 deletion docs/04-mcpp-toml.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,20 @@ When a **dependency declares a level above the graph's**, mcpp says so before co
rather than letting it fail somewhere inside that dependency's sources. See
[workspace §4.2](07-workspace.md).

`[package.metadata.<tool>]` (mcpp 2026.9.16.1+) is a table the engine keeps and
does not interpret. It is the package's statement about itself for a tool that
reads it, such as a framework collecting what each library contributes, and it
reaches the root package's build program through `mcpp::graph_file()`
([30 — build.mcpp](30-build-mcpp.md)). A path in it is resolved by that reader
against the package's manifest directory. Any other key in `[package]` that
mcpp does not read is reported, as in `[build]`: a warning, and an error under
`--strict` (2026.9.16.1+).

```toml
[package.metadata.demo]
resources = "res"
```

#### Dialect flags and the `import std` BMI

Some flags change what the standard library's headers declare, so the precompiled `import std`
Expand Down Expand Up @@ -501,6 +515,46 @@ A per-dependency `linkage` is honoured **only in the root project's**
final program is laid out; one that genuinely must be a single shared copy says
so on its own target instead.

#### A static package under a shared library *(mcpp 2026.9.16.1+)*

A shared library is linked with the static packages it reaches. Each shared
image of a build has a **static closure**: the static packages reachable from
its package without crossing another shared package. A static package in
exactly one closure, which the root project does not reach itself, is linked
into that image and not into the program.

Before 2026.9.16.1 such a package went into the program, and the library bound
to the program's copy at run time. That worked on ELF and only for that program:
the library refused `-Wl,-z,defs`, a host that did not link the package could not
load it (`undefined symbol`), Mach-O and PE resolve every reference at link time,
and Android loads an application's shared library before anything that could
supply the package.

A static package that **several** images reach (two shared libraries, or a shared
library and the program) has no single image to live in:

- on Mach-O, on PE and on the Android application row the build is refused
before compiling, with reason `static-package-in-two-images`
([50](50-machine-output.md));
- on other ELF rows the package stays in the program as before, and the build
reports it (`build/static-placement`), which `--strict` turns into an error.

The message names the package, the images that reach it, and the remedy: give
the package the shared form, so that every image loads one copy.

```toml
[dependencies]
x = { path = "../x", linkage = "shared" } # on the root's edge

# or as the package's own default, in its manifest
[targets.x]
linkage = "shared"
```

A package that provides a target layer (`provides = ["mcpp:..."]`, a C library or
a C++ runtime) is outside this rule: where its objects go is the runtime
contract's decision ([20](20-toolchains.md)).

#### `soname` on a library target

A `soname` (§2.2) may be declared on `kind = "lib"` as well as
Expand All @@ -525,7 +579,14 @@ diagnostic exists for this.
The check is a measurement, not a declaration: it reads the produced image's
dynamic symbol table, removes the entries that are copy relocations, and
reports only those a library in the artifact's own closure **also** defines.
An arrangement with one copy in the process is silent. The verdict is recorded
An arrangement with one copy in the process is silent. Three kinds of shared
definition are counted and not reported *(2026.9.16.1+ for the last two)*:
vague linkage, which the loader unifies by design (`STB_WEAK`, and
`STB_GNU_UNIQUE`, which GCC uses for the static data of inline entities); a
definition the build links into both images from **one object**, such as the
`std` module's initialiser in every C++ image that imports `std`; and that same
initialiser against the toolchain's own C++ runtime, which exports it from
GCC 16 on. A name of the same shape defined anywhere else is still a finding. The verdict is recorded
in `target/<triple>/<fp>/resolution.json` under `runtime.symbol_provision`,
with the count and its denominator, so CI can read it without `readelf`.

Expand Down
Loading
Loading