Skip to content

Commit f4529b2

Browse files
One C++ runtime per process, the graph and pack facts, bounded offline-aware planning: #646 to #649 (2026.9.16.1) (#650)
* docs(design): the #646-#649 triage record, its probes, and the implementation plan * The refresh decision walks the resolver's bare-name rung; the saved stdout is not inherited (#648 L1, L2) * Refreshes go through one policy; an offline plan that needs a download has its own code (#648 L5, L6) The fetcher's pre-install and retry refreshes and a project's first custom-index sync now take the refresh policy's decision, so [index] auto_refresh = false governs them. The policy half of mcpp.pm.index_refresh moves to mcpp.pm.refresh_policy, which the fetcher can import. Every offline refusal records offline-download-required, and emit build-database reports it as MCPP_OFFLINE_DOWNLOAD_REQUIRED. * Symbol provision: GNU_UNIQUE is vague linkage, and a definition both images take from one object is not a conflict (#646 F3) * One process, one C++ runtime; one static package, one image; the llvm row's MSVC runtime record (#646 F1, F3a; #649 E10) * Features, tools and git sources: forwards over every table, one naming derivation, packages of programs, git members (#647 E4, #649 E6-E8) - The forward validator reads every dependency table of its manifest, on every row, including [build-dependencies] (#647 E4.1). - A [feature-deps] restatement naming another source than the declaration in effect is refused, naming both (#647 E4.2). - One derivation names a provider for dep_dir, dep_linkage and dep_bin, so a namespace + name package's tool is published under its qualified name (#647 E4.3). - A package whose declared targets are all programs contributes nothing to a consumer's graph; package cycles are refused where the graph is resolved (package-cycle); a tool requested by its own sub-build is refused at the first repetition (#649 E6). - A git dependency selects the repository member that declares its key; a member's in-clone path edge names the same git source; a second declaration of one dependency merges its requests into the edge; the git banner names its reference (#649 E7). - --features <dependency>/<feature> is a forward of the root and never a macro; mcpp why accepts --features (#649 E8). * Tests for forwards, feature-deps restatements, tool packages, git members and dependency feature tokens (e2e 710-714, 187) * docs: forwards and dependency feature tokens, feature-deps restatements, packages of programs, git members, one naming derivation, package-cycle * xlings children are owned and bounded; the envelope reports observed network access; e2e 730-735 (#648 L3, L4) * docs: refresh bounds and auto_refresh scope, observed network effects, the offline code (#648) * e2e 700-705 and 307, and docs 04/20/50: one C++ runtime per process, one image per static package, the llvm row's CRT record, and the cross-image identity measurements (#646, #649 E10) * Choose the link line by host and target object format (#647 E3) An Android row built on a macOS host received the Apple SDK line, which names no --target, so -fuse-ld=lld selected ld64.lld for an ELF object. link_shape takes the host and the target's object format; a target outside the host's own family takes the generic line that carries the target. Unit LinkShape.* states the choice for every host; e2e 721 links the row on macOS. * Keep [package.metadata] verbatim and report unknown [package] keys (#647 E1, #649 X4) The metadata table is kept as JSON text for the graph document and is not interpreted. [package] reports a key the parser does not read the way [build] does: a warning for the root manifest, an error under --strict. * The resolved graph for the root build program, pack strips what the graph built, and a machine-readable pack report (#647 E1, #649 E5, E9) - mcpp::graph_file(): the root package's program reads every package in dependency order with its manifest directory, features, targets and [package.metadata]; the document's content joins the re-run key. The same entry builder produces resolution.json's graph section. - mcpp pack strips the program on every row that strips (the Android row did not reach the strip step), every shared library the graph built and the staged copy of the toolchain's runtime; the Packing line states what the row does; mcpp::pack_strip() and mcpp::pack_debug_symbols_dir() carry the decision to a member that stages libraries of its own. - mcpp pack --message-format json prints one mcpp.pack envelope; pack takes --release and --dev; build, run, test, emit and pack resolve the profile with one rule (--profile wins over the shorthands). e2e 720, 722, 723; unit BuildProfile.*. * The default mcpplibs artifact is a GLOBAL/CN region object, and existing homes gain the CN half (#648 L7) mcpplibs/mcpp-index#432 made the GitCode copy of the index artifact the same bytes as the GitHub copy. With mirror = CN, mcpp index update now reaches only raw.gitcode.com, gitcode.com and file-cdn.gitcode.com (traced). e2e 151 covers the fresh seed, the flat-to-region upgrade and its idempotence, and a user base that keeps its own value. * Name the PE host link shape by its format WindowsLld spelled the vocabulary the runtime-contract source scan reserves for provider selection (RuntimeContract.SourceOwnsNoProviderSpecificSelectionOrProbeBranch). * CI: e2e 700 on the hermetic llvm job, measurement readings in the macOS and Windows job summaries, 721 as its own macOS step; migration unit tests for the region artifact * One derivation of the target object format for the contract table and the link shape; version 2026.9.16.1, CHANGELOG, records updated with the implementation's readings * Mach-O: the measured runtime identity split is reported; the stream-init shim belongs to C++ units; e2e 732 bounds itself portably The macos-15 run of e2e 704 reads runtime_error=not-matched, errc=unequal under the payload default and caught/equal under host-coupled, so #646 F2 holds. The default is unchanged, and such a build is told once. The same run showed the #336 shim prepended to a C-only shared library, whose link carries no libc++, and that macOS runners have no `timeout`. * docs(record): the local verification readings of the integrated branch --------- Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com>
1 parent 2fc7b5b commit f4529b2

95 files changed

Lines changed: 9503 additions & 526 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.agents/docs/2026-09-16-646-649-four-issues-by-home.md‎

Lines changed: 1347 additions & 0 deletions
Large diffs are not rendered by default.

‎.agents/docs/2026-09-16-646-649-implementation-plan.md‎

Lines changed: 498 additions & 0 deletions
Large diffs are not rendered by default.

‎.agents/docs/2026-09-16-646-649-probes.sh‎

Lines changed: 778 additions & 0 deletions
Large diffs are not rendered by default.

‎.agents/docs/2026-09-16-646-649-verify.sh‎

Lines changed: 322 additions & 0 deletions
Large diffs are not rendered by default.

‎.agents/docs/README.md‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ superseded_by: 2026-09-07-....md # when status is superseded
1818
---
1919
```
2020

21-
289 records.
21+
291 records.
2222

2323
## By subject
2424

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

6363
### triage
6464

65+
- [#646 to #649 implemented: the plan, its review from eight angles, and the ledger](2026-09-16-646-649-implementation-plan.md) — active
66+
- [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
6567
- [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
6668
- [#641 and #642 implemented: the plan, its review from eight angles, and the ledger](2026-09-15-641-642-implementation-plan.md) — landed
6769
- [#634 implemented across five repositories: the plan, its review, and the ledger that tracks it](2026-09-14-634-implementation-plan.md) — landed
@@ -74,6 +76,8 @@ Records that declare one. Everything else is listed by date below.
7476

7577
### 2026-09
7678

79+
- [#646 to #649 implemented: the plan, its review from eight angles, and the ledger](2026-09-16-646-649-implementation-plan.md) — active
80+
- [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
7781
- [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
7882
- [#641 and #642 implemented: the plan, its review from eight angles, and the ledger](2026-09-15-641-642-implementation-plan.md) — landed
7983
- [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

‎.github/workflows/ci-linux-e2e.yml‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,7 @@ jobs:
422422
# skips. They run here, where llvm is installed, and each is held to the
423423
# line it prints only when it ran to the end, and to the line of the step
424424
# that runs a program (#641).
425-
- name: "graph C++ runtime e2e (663, 690, 696)"
425+
- name: "C++ runtime e2e that needs llvm (663, 690, 696, 700)"
426426
run: |
427427
set -o pipefail
428428
export PATH="$HOME/.xlings/subos/current/bin:$PATH"
@@ -443,3 +443,9 @@ jobs:
443443
"PASS: 690 a shared library over a graph C++ runtime is refused or carries a stated private copy"
444444
run_and_assert tests/e2e/696_a_cxx_layer_provider_keeps_its_own_standard.sh \
445445
"PASS: a C++-layer provider compiles its implementation units at its own standard"
446+
# #646 F3a: the default llvm shape of a program over a C++ shared library
447+
# aborted with std::bad_cast before its programs took the library's contract.
448+
run_and_assert tests/e2e/700_a_program_over_a_cxx_shared_library_has_one_cxx_runtime.sh \
449+
"ok: a program over a C++ shared library runs on one C++ runtime" \
450+
"ok: a stated self-contained program over a coupled C++ shared library is refused" \
451+
"PASS: 700 a program over a C++ shared library has one C++ runtime"

‎.github/workflows/ci-macos-e2e.yml‎

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
e2e:
2525
name: e2e suite (macOS ARM64, self-host)
2626
runs-on: macos-15
27-
timeout-minutes: 30
27+
timeout-minutes: 60
2828
# NOTE: no MCPP_VERBOSE — the e2e suite asserts mcpp's default quiet
2929
# output (tests 48/53).
3030
steps:
@@ -64,4 +64,35 @@ jobs:
6464
"$MCPP" self config
6565
# macOS default toolchain is LLVM
6666
"$MCPP" toolchain default "llvm@${MCPP_LLVM_VER}"
67-
bash tests/e2e/run_all.sh
67+
echo "MCPP=$MCPP" >> "$GITHUB_ENV"
68+
set -o pipefail
69+
bash tests/e2e/run_all.sh 2>&1 | tee "$RUNNER_TEMP/e2e-suite.log"
70+
71+
# Measurement legs print READING lines and pass whatever they read; the
72+
# readings are what a decision is taken from (#646 F2: whether a C++
73+
# exception thrown in a dylib is caught by its class under the payload's
74+
# default runtime), so they are collected where a reader finds them.
75+
- name: Measurement readings
76+
if: always()
77+
shell: bash
78+
run: |
79+
{
80+
echo "### Measurement readings (macOS)"
81+
echo '```'
82+
grep -h '^READING' "$RUNNER_TEMP/e2e-suite.log" 2>/dev/null || echo "(none)"
83+
echo '```'
84+
} >> "$GITHUB_STEP_SUMMARY"
85+
86+
# #647 E3: an Android row links on a macOS host. Its own step, because the
87+
# NDK is a large download the suite's per-test bound does not allow for,
88+
# and the shard does not carry it (the script declares android-ndk).
89+
- name: "Android row on a macOS host (721)"
90+
timeout-minutes: 30
91+
shell: bash
92+
run: |
93+
set -o pipefail
94+
export MCPP_VENDORED_XLINGS="$XLINGS_BIN"
95+
export MCPP_E2E_TOOLCHAIN_MIRROR=GLOBAL
96+
"$MCPP" toolchain install android-ndk@30.0.16248370
97+
bash tests/e2e/721_*.sh 2>&1 | tee "$RUNNER_TEMP/721.log"
98+
grep -q '^PASS: 721' "$RUNNER_TEMP/721.log"

‎.github/workflows/ci-windows-e2e.yml‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,18 @@ jobs:
107107
export MCPP_E2E_TOOLCHAIN_MIRROR=GLOBAL
108108
"$MCPP_SELF" self config --mirror GLOBAL
109109
"$MCPP_SELF" toolchain default llvm@20.1.7
110-
bash tests/e2e/run_all.sh
110+
set -o pipefail
111+
bash tests/e2e/run_all.sh 2>&1 | tee "$RUNNER_TEMP/e2e-suite.log"
112+
113+
# Measurement legs print READING lines (#646 F2 across PE images, #649 E10
114+
# the llvm row's recorded CRT); collected where a reader finds them.
115+
- name: Measurement readings
116+
if: always()
117+
shell: bash
118+
run: |
119+
{
120+
echo "### Measurement readings (Windows, shard ${{ matrix.shard }})"
121+
echo '```'
122+
grep -h '^READING' "$RUNNER_TEMP/e2e-suite.log" 2>/dev/null || echo "(none)"
123+
echo '```'
124+
} >> "$GITHUB_STEP_SUMMARY"

‎CHANGELOG.md‎

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,99 @@
55

66
## [Unreleased]
77

8+
### 一个进程一个 C++ 运行时、图与打包的事实、离线与有期限的规划:#646 至 #649(2026.9.16.1)
9+
10+
一个 UI 框架与一个编辑器报告的 23 项,外加实测发现的 12 项。按归属分诊后,引擎承担其中的通用
11+
缺陷与通用能力;插件、索引与 xlings 清单各自的一项在各自仓库。设计、测量与计划:
12+
`.agents/docs/2026-09-16-646-649-*.md`。
13+
14+
**图像与运行时(#646、#649 E10)**
15+
16+
- **程序与共享库共用一个 C++ 运行时(F3a)。** ELF 上程序默认 `self-contained`、共享库默认
17+
`toolchain-coupled`,一个加载本次构建所出 C++ 共享库的程序于是在一个进程里带两份运行时:
18+
llvm 默认构建运行即以 `std::bad_cast` 中止(退出 134),gcc 带 900 个被抢占的 libstdc++
19+
符号。现在这样的程序与测试取共享库的契约;显式写明自含运行时的程序被编译前拒绝,reason 为
20+
`program-cxx-runtime-split`。(单测 `Distribution.*`,e2e 700)
21+
- **符号重复检查不再报告按构造共享的定义(F3b)。** `STB_GNU_UNIQUE` 按弱定义处理;两个映像
22+
取自同一个构建对象(如 `std.o`)的定义,以及工具链自身运行时提供的 std 模块初始化器,不算
23+
冲突;真实的重复定义仍然报告。(单测 `SymbolProvision.*`,e2e 701)
24+
- **静态包放进到达它的那个共享库(F1)。** 只被一个依赖共享库到达的静态包链进该库,不再链进
25+
程序(此前库里留着未定义符号,只在 ELF 上靠程序的副本侥幸运行);被多个映像到达时,在
26+
Mach-O、PE 与 Android `app` 行上编译前拒绝(`static-package-in-two-images`),其他 ELF 行
27+
照旧构建并给出 `build/static-placement` 警告,`--strict` 下失败,出路是
28+
`linkage = "shared"`。(单测 `StaticPlacement.*`,e2e 702、307)
29+
- **Mach-O 上跨映像的 C++ 身份(F2,macos-15 实测)。** 默认每个映像内嵌一份隐藏的
30+
`libc++.a`:dylib 里抛出的 `std::runtime_error` 在程序中不按该类捕获,两个
31+
`std::error_code` 的 category 比较不相等;全角色 `cxx_runtime = "host-coupled"` 时
32+
两者成立。默认值本次不改(它是今天每个 macOS 构建的形态,改动另行记录),但这样的构建
33+
会被告知一次:`build/cxx-runtime-identity`。(e2e 704 的读数)
34+
- **llvm 行在 MSVC ABI 上如实记录静态 CRT(E10 第一步)。** 解析记录此前写 `host-coupled`
35+
而产物静态链接 `libcmt`;显式要求本行不能交付的动态运行时会得到说明。默认值是否改为 `/MD`
36+
另行测量与记录。(e2e 703;Mach-O 与 PE 上跨映像的异常身份由 e2e 704、705 测量)
37+
38+
**图、构建程序与打包(#647 E1–E3、#649 E5、E9)**
39+
40+
- **根构建程序读取解析后的依赖图(E1)。** `mcpp::graph_file()` / `MCPP_GRAPH_FILE` 指向按
41+
依赖在前排序的 JSON 文档,每个包含 `manifest_dir`、`features`、`targets`、链接形态与原样
42+
保留的 `[package.metadata.<tool>]`;文档摘要进入构建程序的重跑键,编辑依赖的 metadata 会
43+
重跑根构建程序,编辑其源码不会。`[package]` 的未知键像 `[build]` 一样报告。(e2e 720)
44+
- **macOS 主机上 Android 行可以链接(E3)。** 链接行按主机与目标对象格式选择
45+
(`link_shape`),Apple SDK 分支只用于 Mach-O 目标;Linux 上的链接行逐字节不变。
46+
(单测 `LinkShape.*`,e2e 721 在 macOS CI 上运行)
47+
- **打包剥离构建出来的一切(E5)。** Android 行的程序此前根本没有经过剥离步骤;现在每条剥离的
48+
行上剥离程序、本图构建的共享库与暂存的工具链运行时副本(`--strip-unneeded`,保留导出),
49+
状态行只在真正剥离时写 "stripped";`--no-strip` 与 `--debug-symbols` 作用于每个文件,构建
50+
程序通过 `MCPP_PACK_STRIP` / `MCPP_PACK_DEBUG_SYMBOLS_DIR` 读到同一决定。(e2e 722)
51+
- **`mcpp pack --message-format json`(E9)。** 输出一个 `mcpp.pack` 信封,逐项列出产物的
52+
绝对路径、类型、格式与目标行;人类可读的行改走 stderr。`pack` 接受 `--release` / `--dev`;
53+
`build`、`run`、`test`、`pack` 共用一个 profile 判定,`--profile` 优先于简写(此前 `run`
54+
相反)。(单测 `BuildProfile.*`,e2e 723)
55+
56+
**feature、工具与 git 依赖(#647 E4、#649 E6–E8)**
57+
58+
- **转发校验覆盖每一张依赖表与每一行(E4.1)。** 经 `[build-dependencies]` 的转发、只在另一行
59+
声明的依赖不再被报告为未声明;无处声明的键仍然报告。(e2e 710)
60+
- **`[feature-deps]` 的重述(E4.2)。** 文档改为写明重述来源;来源与生效声明不同的重述被拒绝,
61+
消息给出两个来源。(e2e 711)
62+
- **`dep_bin` 发布限定名(E4.3)。** 一个函数给出提供者对消费者的所有名字,`dep_dir`、
63+
`dep_linkage` 与 `dep_bin` 共用;`namespace = "ns"` 加 `name = "x"` 的包也有
64+
`MCPP_DEP_NS_X_BIN_*`。(e2e 187、711)
65+
- **只提供程序的包不进入消费者的图(E6)。** 这样的包不被扫描、不链接进消费者,其程序只由工具
66+
子构建产出;feature 工具于是可以依赖声明它的包。包之间的环在解析时拒绝(`package-cycle`),
67+
各种缓存模式一致;工具请求自身时在第一次重复即拒绝。(e2e 712)
68+
- **git 依赖可以选择仓库里的成员包(E7)。** 键的身份不是根包时,在根的 `[workspace] members`
69+
中按身份查找;同一消费者对同一依赖的第二次声明合并其 `tools`、`features`、`host-module` 与
70+
`reexport`;git 依赖的编译行写提交而不是空版本。(e2e 713)
71+
- **`--features dep/feature`(E8)。** 作为根的转发应用;不指向任何依赖时警告,`--strict` 下
72+
失败,且不再变成宏;普通名字保持文档所述的纯宏用法。`mcpp why deps` 接受 `--features`。
73+
(e2e 714)
74+
75+
**编辑器在后台规划(#648)**
76+
77+
- **刷新判定与解析器走同一条阶梯(新发现)。** 省略命名空间的 `ftxui = "6.1.9"` 由解析器经
78+
已弃用的裸名回退找到 `compat.ftxui`,刷新判定却只查精确坐标并判为缺失;防抖只有 120 秒,
79+
这样的工程每次联网规划都执行一次 `xlings update`。现在判定也走该回退。(单测
80+
`PmIndexRefresh.BareNameResolvedThroughTheLegacyRungIsNotAMiss`,e2e 730)
81+
- **规划期间的子进程不继承调用方的管道(A2)。** 保存的标准输出是 close-on-exec(Windows 上不可
82+
继承);此前构建程序以描述符 3 持有调用方读取的管道。(e2e 731)
83+
- **xlings 子进程有期限并随 mcpp 结束(A3)。** 刷新受 `[index] refresh_timeout`(秒,默认
84+
120)约束,超时视为刷新失败并继续用本地索引;经接口的安装在 300 秒无任何输出(含心跳)时终止;
85+
子进程在自己的进程组(Windows 上为作业对象)中运行,结束 mcpp 即一并结束。(e2e 732)
86+
- **信封的 `effects` 按观测报告 `network`(A4)。** 本次运行启动过刷新、安装或 git 远程操作时
87+
列出,离线运行从不列出。(e2e 733)
88+
- **`auto_refresh = false` 约束所有隐式刷新(A5)。** 安装前的刷新、重试前的刷新与自定义索引的
89+
首次同步改走同一个刷新策略(`mcpp.pm.refresh_policy`)。(e2e 734)
90+
- **离线缺下载有自己的诊断码(A1)。** `MCPP_OFFLINE_DOWNLOAD_REQUIRED` 与 refusal
91+
`offline-download-required`,消息指出第一个需要下载的工具链、包、git 修订或索引。
92+
(e2e 733、735)
93+
- **默认索引制品按镜像分区(A6)。** mcpplibs 索引的 `artifact` 默认是
94+
`{ GLOBAL = github, CN = gitcode }`,已有 home 的 `.xlings.json` 就地升级;`mirror = CN` 时
95+
`mcpp index update` 只访问 GitCode。前提是 mcpplibs/mcpp-index#432 让两端制品逐字节一致。
96+
(e2e 151)
97+
98+
**CI**:hermetic llvm job 运行 700 并断言结束行;macOS 与 Windows e2e 把测量行写入 job
99+
summary;macOS 上单独一步运行 721。
100+
8101
### 链接形态、标准档位与路径长度:#641 与 #642(2026.9.15.2)
9102

10103
一个 UI 框架迁到 macOS 12 下限与 Android 独立共享库时报告的七项,全部在引擎内处理。

‎docs/04-mcpp-toml.md‎

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,20 @@ When a **dependency declares a level above the graph's**, mcpp says so before co
8989
rather than letting it fail somewhere inside that dependency's sources. See
9090
[workspace §4.2](07-workspace.md).
9191

92+
`[package.metadata.<tool>]` (mcpp 2026.9.16.1+) is a table the engine keeps and
93+
does not interpret. It is the package's statement about itself for a tool that
94+
reads it, such as a framework collecting what each library contributes, and it
95+
reaches the root package's build program through `mcpp::graph_file()`
96+
([30 — build.mcpp](30-build-mcpp.md)). A path in it is resolved by that reader
97+
against the package's manifest directory. Any other key in `[package]` that
98+
mcpp does not read is reported, as in `[build]`: a warning, and an error under
99+
`--strict` (2026.9.16.1+).
100+
101+
```toml
102+
[package.metadata.demo]
103+
resources = "res"
104+
```
105+
92106
#### Dialect flags and the `import std` BMI
93107

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

518+
#### A static package under a shared library *(mcpp 2026.9.16.1+)*
519+
520+
A shared library is linked with the static packages it reaches. Each shared
521+
image of a build has a **static closure**: the static packages reachable from
522+
its package without crossing another shared package. A static package in
523+
exactly one closure, which the root project does not reach itself, is linked
524+
into that image and not into the program.
525+
526+
Before 2026.9.16.1 such a package went into the program, and the library bound
527+
to the program's copy at run time. That worked on ELF and only for that program:
528+
the library refused `-Wl,-z,defs`, a host that did not link the package could not
529+
load it (`undefined symbol`), Mach-O and PE resolve every reference at link time,
530+
and Android loads an application's shared library before anything that could
531+
supply the package.
532+
533+
A static package that **several** images reach (two shared libraries, or a shared
534+
library and the program) has no single image to live in:
535+
536+
- on Mach-O, on PE and on the Android application row the build is refused
537+
before compiling, with reason `static-package-in-two-images`
538+
([50](50-machine-output.md));
539+
- on other ELF rows the package stays in the program as before, and the build
540+
reports it (`build/static-placement`), which `--strict` turns into an error.
541+
542+
The message names the package, the images that reach it, and the remedy: give
543+
the package the shared form, so that every image loads one copy.
544+
545+
```toml
546+
[dependencies]
547+
x = { path = "../x", linkage = "shared" } # on the root's edge
548+
549+
# or as the package's own default, in its manifest
550+
[targets.x]
551+
linkage = "shared"
552+
```
553+
554+
A package that provides a target layer (`provides = ["mcpp:..."]`, a C library or
555+
a C++ runtime) is outside this rule: where its objects go is the runtime
556+
contract's decision ([20](20-toolchains.md)).
557+
504558
#### `soname` on a library target
505559

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

0 commit comments

Comments
 (0)