Skip to content

Commit eb02575

Browse files
The C library layer declares the C environment it presents, the engine realises and checks it (#668)
* The C library layer declares the C environment it presents, the engine realises and checks it Until now the compiler payload's target triple implied the environment a C program compiles against. The moment a package supplies the C library instead (mcpp:c-abi=<impl>), that stops being true: openkal-musl on x86_64-windows-gnu generates PE/Win64 code while presenting a POSIX environment, and every #ifdef _WIN32 above it is asking the wrong layer. - [c-abi] manifest block: presents (posix/windows/none), data-model (arch-default/lp64/llp64/ilp32), wchar (16/32), builtins (iso/platform). Only a package that also provides mcpp:c-abi=<impl> may declare it. presents/data-model/wchar carry no default; an unknown key or value is always a parse error naming the key. Absent block changes nothing. (modules/manifest/src/{targetside_model,toml,types}.cppm) - Realisation (src/toolchain/cenv.cppm): a pure, table-driven mapping from request to compiler tokens with no package names. The flagship case -- presents=posix on Windows/x86_64 -- realises as a Cygwin-flavoured compile-only identity switch (--target=x86_64-pc-cygwin, -U__CYGWIN__ -U__CYGWIN32__), leaving the link line on the graph's resolved triple, because the two triples measure identical machine code. Reaches C, C++, the dependency scan and the std module precompile. A request this engine cannot realise is refused naming the target, request and what is missing. [package] c-environment = "platform" opts a package out of the realisation entirely. - Verification, not trust (src/toolchain/cenv_probe.cppm): one -E -dM predefined-macro dump, no codegen and no execution, checked against the declaration and cached per configuration. Caught a real mapping bug during development (data-model = "llp64" alongside presents = "posix" on Windows silently read as already-satisfied); the mapping now refuses that combination directly instead of relying on the probe to catch it late. - __openkal__ defined for every target-side unit when the resolved kernel-abi layer's interface is openkal, read from the layer's value. - Closure visibility: provides = ["platform-sdk"] is a package's own statement; the Target report gains a platform-deps line, and [build] platform-dependencies = "refuse" fails the build when one is present. (src/build/prepare.cppm) - The realised environment and __openkal__ fold into the build fingerprint, so LP64 and LLP64 builds of one source/manifest never share a directory. The equivalent store-key gap for install-hook artifacts is designed but not built here -- documented in docs/22 as a known gap. Two pre-existing defects found while verifying this are filed separately, not fixed here: #666 (a Clang-built mcpp binary SIGSEGVs in its own ELF runtime inspector; GCC-built does not) and #667 (a GCC self-host ICE importing mcpp.targetside from a new consumer under a parallel build; -j1 avoids it). Docs: docs/22 ([c-abi], verification, fingerprint), docs/21 (a declared environment moves the compiled triple, not the linked one), docs/24 (the three macro families, the __openkal__ rule, platform units), docs/06 (platform-sdk), and their zh mirrors. Tests: test_manifest.cpp, test_cenv.cpp (14 cases covering the whole mapping table), e2e 741. Design: mcpplibs/openkal .agents/docs/2026-09-18-openkal-c-environment-and-personalities-design.md * Fix docs lint: zh heading parity and an interrogative table header - docs/zh/21-the-target-triple.md was missing the zh mirror of the new "a declared environment can move the compiled triple" note; heading count parity with the English document now holds (25/25). - docs/zh/24-openkal-cross.md's macro-family table header "由谁定义" reads as a question (contains 谁); renamed to the noun phrase "定义者". - Three cross-file anchors in the zh docs pointed at the ENGLISH heading slug of their target section instead of the zh one (#adaptation-to-the-resolved-target-side, #closure-visibility) — fixed to the zh anchors check_docs_structure.sh actually validates against. Caught by CI on #668 ("build + unit tests" job's doc-lint step); verified locally afterwards with check_docs_style.sh, check_docs_structure.sh, check_version_pins.sh, check_target_tiers.py and check_modules_wiring.sh. * Keep __CYGWIN__/__CYGWIN32__ defined under the posix realisation Design revision from the openkal-musl spike. The Cygwin-flavoured Windows realisation no longer undefines __CYGWIN__/__CYGWIN32__ -- the -U tokens are dropped, everything else (the --target= substitution, LP64, no _WIN32, -fno-short-wchar for wchar=32) is unchanged. The reason, stated once and correctly (an earlier private draft of this change mis-attributed the cause to libunwind's own branch selection; that was wrong on inspection of the vendored source and is not repeated here): third-party portable code that needs to know the OBJECT FORMAT -- distinct from the C environment and from the platform API -- has no other name for "PE format with a POSIX-presenting C environment" than __CYGWIN__, and such code cannot be patched the way this ecosystem's own packages can. This is a trade-off for the 30-member measurement to settle: a library reaching for __CYGWIN__ may also reach for a real Cygwin interface that does not exist here, and if defining it produces more new failures than it fixes, the answer flips. Updated: the realisation table and its verification expectations (src/toolchain/cenv.cppm), the unit test and e2e assertions (now checking __CYGWIN__ stays DEFINED, the opposite of the first version), and the English + zh docs/CHANGELOG prose. * The realised [c-abi] environment reaches assembly units too A .S unit's command line is assembled independently of a .c/.cpp unit's (mcpp.build.flags::CompileFlags::as, not ::cc/::cxx), and only inherits the -D/-U/-I subset of a package's C flags on purpose -- a -std= or -O token meant for the C compiler is meaningless to GAS. The realised environment broadcast landed only in privateBuild.cflags/cxxflags, so it never reached that narrower channel: within one package a .c unit saw _WIN32 undefined while a .S unit still saw it defined, because --target=/-fno-short-wchar never reached the assembler's command line at all. Found by the openkal-musl spike (okm_setjmp.S, and upstream libunwind's assembly.h, both select register-save sets on that macro) -- a jmp_buf written by one save set and sized by the other header is a silent mismatch, the exact failure class this feature exists to remove. Adds UsageRequirements::asmflags, a broadcast-only channel parallel to cflags/cxxflags (no [build] asmflags manifest key backs it -- per-glob `flags = [{ asmflags }]` remains the author-facing one), and routes the realised environment tokens into it for GAS units only: NASM has no --target= concept and accepts none of these flags, so it must never receive them, and a unit test pins that split directly. Every token was checked against clang's assembler-with-cpp front end first; none is rejected, so nothing is filtered a second time. e2e 741 gains a .S unit asserting it carries the same tokens main.cpp does. * A kernel-abi provider infers the platform boundary, and the build cache key now covers the realised environment A package providing mcpp:kernel-abi=<impl> is by definition the boundary where the platform's own interfaces are called, so it can never want the graph's presented [c-abi] environment instead of the triple's own. Rather than require every kernel-abi implementation to write c-environment = "platform" (and every already-released one to bump a version to add it), that value is now inferred from provides alone, in both manifest parsers (mcpp.toml and the xpkg index descriptor). The explicit key still wins when present. Measured without it: openkal-windows, compiled under the POSIX substitution like everything else in its graph, got a 32-bit wchar_t from -fno-short-wchar while the Win32 calls it makes hand back genuine 16-bit UTF-16, misreading its own results. Separately: the global build cache's key (~/.mcpp/build-cache/v1, mcpp.build.cache_key) read only a package's OWN declared cflags/cxxflags, never the engine's broadcast channel the realised environment (and __openkal__, and targetSideUsage) actually travels through. Two builds realising different environments for the identical package produced the identical key. Measured: upgrading mcpp in place, with the cache directory left alone, served objects compiled under the old realised environment into an image built under the new one. fill_package_config now folds in privateBuild.cflags/cxxflags/asmflags (the post-broadcast values) alongside the declared ones, exactly as it already did for include directories. * Bump the build-cache epoch: a corrected key cannot tell a poisoned entry from a trustworthy one The previous commit made fill_package_config read the realised environment, but a corrected derivation does not make an entry written under the old, wrong one safe to keep. An entry is poisoned exactly when its recorded key and its actual compiled inputs already disagreed at write time, and the package most likely to still produce an unchanged key after the fix is the one this same PR newly exempts from the realisation: a kernel-abi provider inferred into c-environment = "platform" now has an empty privateBuild.cflags, so its new key is computed from nothing -- matching its old key, which was also computed from nothing, while the object on disk was compiled WITH the substitution. No cheaper mechanism distinguishes a pre-fix entry from a post-fix one, so kCacheEpoch moves (2 -> 3): every existing ~/.mcpp/build-cache/v1 entry is orphaned unconditionally, one cold rebuild for everyone, rather than trust a key equality that is wrong for exactly the entries that matter most. Also adds CacheKey.EveryPrivateBuildBroadcastFieldReachesTheKey, a durable guard for the class of defect rather than only this one instance of it: PackageRoot::privateBuild (UsageRequirements) is the engine's own broadcast channel, and every one of its members -- today includeDirs, includeDirsAfter, cflags, cxxflags, asmflags, ldflags, modules -- has to move the cache key, because the key's only job is to describe what reaches the compiler. This predates the c-abi wave (targetSideUsage's own broadcast, and __openkal__, had the identical exposure before [c-abi] existed); the two fields nothing broadcasts into yet (ldflags, modules) are asserted uncovered on purpose, with an explicit instruction to flip that assertion in the same change that adds their broadcast. * Verification-probe test coverage, and the install-hook store's failure mode written out plainly The declaration probe (mcpp.toolchain.cenv_probe, design 2026-09-18 §3.2) landed in an earlier commit and is wired into prepare.cppm, but had zero direct test coverage: e2e 741 only exercises its success path (the build would not otherwise complete) and its one refusal leg comes from cenv::realise refusing an unrealisable request before the probe is ever reached. Adds tests/unit/test_cenv_probe.cpp, calling cenv_probe::verify directly against a real host compiler with deliberately wrong expectations -- a declared long/wchar_t width the compiler disagrees with, a macro declared defined that isn't and vice versa -- confirming each produces the declared-vs-measured Mismatch entry prepare.cppm renders into its refusal text, plus the cache's hit/miss behavior. Each test gets its own temp cache directory: the probe's cache is keyed on (compiler, argv) alone, not on what expectations are being checked, so two tests sharing a default cache root and identical argv would have the second read the first's cached dump rather than genuinely probe. Separately, traced the install-hook store's code path (prepare.cppm, install_hook_env) to answer directly: yes, an install hook can and does compile target-side code into the shared store, and no, there is no way today for it to know the realised [c-abi] environment, because the hook runs before the toolchain resolves at all -- prepare.cppm resolves tc only after the dependency graph installs, since resolving the target side can itself depend on which package the graph supplies a layer from. This is not a gap this PR could close with a local fix; docs/22 (+ zh mirror) now states the failure mode plainly (objects sized for one wchar_t linked against headers sized for another, with nothing checking it) rather than as a line in a gap table, names what closing it for real would take, and says explicitly that this is the same shape as the already-shipped c++-abi store-key gap, not a new one this PR introduces. * Self-review before merge: a fingerprint bug found, the fourth fact and the inference precedence made findable, macOS CI pointed at #669 Found while verifying the P1 criterion (a package declaring nothing gets a byte-identical command line): the c-abi fingerprint's cenv-platform loop was unconditional, scanning every package for c-environment == "platform" regardless of whether any [c-abi] realisation was active in this build at all. Since c-environment = "platform" is now INFERRED for every mcpp:kernel-abi=<impl> provider, this folded "cenv-platform:<name>" into the fingerprint -- moving the output directory -- of every project using one, even when nothing about the realised environment was ever active. Confirmed live: the identical fakekernel-in-graph, no-[c-abi] scenario, same project directory, same manifest, produced two different output-dir hashes before and after gating the loop behind the same "cEnvTokens or cEnvBuiltinsTokens non-empty" condition the rest of the block already uses correctly. Fixed; a package using no c-abi feature at all now keeps the directory it already had, whether or not its graph happens to include a kernel-abi provider. docs/24 gains the fourth fact this cross-repo doc's own table was missing: object format (__CYGWIN__) is a property of the target triple, not a fourth C-environment macro, and rides along with -- not folded into -- the three-family table; and corrects its now-stale "platform units" paragraph, which still described openkal-windows declaring c-environment explicitly, to state the inferred/explicit split and precedence (both en and zh). CHANGELOG's cache-epoch entry now leads with the user-facing consequence (first build after upgrading is cold) rather than burying it inside the technical explanation. ci-macos.yml's SDK-selection comment is corrected per review: it claimed mcpp's own SDK resolution is independent of and overrides the xlings LLVM package's clang++.cfg sysroot, which does not survive checking (both currently resolve to the same path; which wins on the command line is not established). Replaced with the defensible statement plus a pointer to #669, which both known-red xcode-27 jobs now name. --------- Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com>
1 parent 2b42f74 commit eb02575

30 files changed

Lines changed: 3136 additions & 19 deletions

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ jobs:
2424
e2e:
2525
name: e2e suite (macOS ARM64, self-host, ${{ matrix.image }})
2626
# The same two images as ci-macos.yml; `xcode-27` is macOS 27 (see there).
27+
# KNOWN RED on xcode-27, along with ci-macos.yml's own job: the image's
28+
# Command Line Tools SDK ships an `arm64e.x1` .tbd stub ld64.lld 22.1.8
29+
# cannot parse (fixed upstream, llvm-project#222721, not yet in a
30+
# release). See mcpp-community/mcpp#669.
2731
strategy:
2832
fail-fast: false
2933
matrix:

.github/workflows/ci-macos.yml

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -81,15 +81,15 @@ jobs:
8181
- name: The floating macros mcpp states for an Apple target
8282
run: echo 'APPLE_FLOAT_MACROS=-DINFINITY=HUGE_VALF -DNAN=__builtin_nanf("0x7fc00000")' >> "$GITHUB_ENV"
8383

84-
# THE SDK mcpp SELECTS, NOT THE ONE THE PACKAGE'S clang++.cfg NAMES. The
85-
# xlings LLVM package writes `--sysroot=/Library/Developer/CommandLineTools/
86-
# SDKs/MacOSX.sdk` into its configuration; mcpp passes --no-default-config
87-
# and uses `xcrun --show-sdk-path` (modules/platform/src/macos/macos.cppm).
88-
# On xcode-27-arm64 20260912 the Command Line Tools SDK lists `arm64e.x1`
89-
# in its .tbd stubs, which ld64.lld 22.1.8 does not parse (support landed
90-
# upstream in llvm-project#222721, after 22.1.8), while the SDK xcrun
91-
# selects links. These raw compiles state the SDK the way mcpp does, so the
92-
# job measures what mcpp builds rather than the package's default.
84+
# THE SDK mcpp'S OWN RESOLUTION NAMES: `xcrun --show-sdk-path`
85+
# (modules/platform/src/macos/macos.cppm). These raw compiles state the
86+
# SDK the same way, on purpose.
87+
#
88+
# KNOWN RED on the xcode-27 image, this job and "e2e suite (macOS ARM64,
89+
# self-host, xcode-27)" both: the Command Line Tools SDK there ships an
90+
# `arm64e.x1` .tbd stub ld64.lld 22.1.8 cannot parse — fixed upstream
91+
# (llvm-project#222721) after 22.1.8 was tagged, not yet in any release.
92+
# See mcpp-community/mcpp#669 for the evidence and status.
9393
- name: The SDK mcpp selects for an Apple target
9494
run: echo "APPLE_SYSROOT=-isysroot $(xcrun --show-sdk-path)" >> "$GITHUB_ENV"
9595

CHANGELOG.md

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

66
## [Unreleased]
77

8+
### C 库层可以声明它呈现的 C 环境,引擎实现并校验:设计 2026-09-18(2026.9.18.1)
9+
10+
三元组的 OS 段过去总是同时回答两件事:目标机器长什么样,以及源码面对的 C 环境是什么。
11+
一旦某个包接管了 C 库(`mcpp:c-abi=<impl>`),这两个问题就可能有不同答案——openkal-musl
12+
在 `x86_64-windows-gnu` 上生成 PE/Win64 代码,却是一个呈现 POSIX 环境的 musl 移植版,而
13+
`_WIN32` 在这种图里同时冒充了「机器是 Windows」与「C 运行时是 Windows CRT」两件事,后者
14+
是假的。这一版本让 C 库层把它实际呈现的环境说出来,由引擎实现并校验,不再由三元组或某个
15+
库自己猜。
16+
17+
- **`[c-abi]` 块**:只有提供 `mcpp:c-abi=<impl>` 层的包可以声明,其余情况在清单解析阶段
18+
即被拒绝。`presents`(`posix`/`windows`/`none`)、`data-model`
19+
(`arch-default`/`lp64`/`llp64`/`ilp32`)、`wchar`(`16`/`32`)三个键没有默认值,
20+
`builtins`(`iso`/`platform`)默认 `platform`;拼错的键或取值都是解析错误而不是静默
21+
忽略。不声明该块的包,产出的命令行与这项能力之前逐字节相同。
22+
(`modules/manifest/src/{targetside_model,toml,types}.cppm`,单测 `test_manifest.cpp`)
23+
- **实现(realisation)**:新模块 `mcpp.toolchain.cenv` 保存「请求 → 三元组与开关」的映射
24+
——通用知识,不含包名。Windows 上 `presents = "posix", data-model = "arch-default"`
25+
采用 Cygwin 式语义,仅在编译行把 `--target=` 换成 `x86_64-pc-cygwin`;链接行保持图解析出
26+
的三元组不变,因为两个三元组生成的机器码实测完全一致(PE、Win64 调用约定、SEH)。
27+
无法满足的请求明确拒绝,点名目标、请求与缺什么。`[package] c-environment = "platform"`
28+
让一个包的自身单元退出这项实现,继续按三元组自身的默认环境编译。
29+
(`src/toolchain/cenv.cppm`,单测 `test_cenv.cpp`)
30+
- **`c-environment = "platform"` 对 `mcpp:kernel-abi=<impl>` 提供者是推导出来的,不需要
31+
自己声明,经 PR 进行中 openkal-musl 尖峰实验的修订。** 这样的包定义上就是说平台自身 ABI
32+
的边界,绝不会是想要图里呈现的 `[c-abi]` 环境的那个包——openkal-windows 在 POSIX 替换下
33+
编译,`-fno-short-wchar` 给了它 32 位 `wchar_t`,而它调的 Win32 接口回传真正的 16 位
34+
UTF-16,`wchar_t*` 循环于是把两个 UTF-16 码元读成一个码点,这是实测出来的失败,不是假设。
35+
推导让 openkal-windows 0.8.0、openkal-macos 0.10.0、openkal-linux 0.13.0 等每一个已发布
36+
实现都不需要新发版本、不需要跨仓库协调版本号就能把边界做对,这一类失败因此**无法被表达**。
37+
包自身清单里显式写的 `c-environment` 仍然优先于推导——该键仍是设计 §5.3 另一类情形(不是
38+
kernel-abi 边界、但自身确有平台绑定单元的普通包)唯一的表达手段。`mcpp.manifest.xpkg` 与
39+
`mcpp.toml` 两条清单解析路径都实现了同一条推导。(`modules/manifest/src/{toml,xpkg}.cppm`,
40+
单测 `test_manifest.cpp` 的 `CEnvironmentIsInferredForAKernelAbiProvider`(两条解析路径各一
41+
个),e2e `tests/e2e/741_...sh`)
42+
- **升级到这个版本后,第一次构建会是一次冷构建——这是故意的,原因如下。** 全局构建缓存
43+
(`~/.mcpp/build-cache/v1`)的键原先没有覆盖解析出的 [c-abi] 环境(经 openkal-musl 尖峰
44+
实验发现):`mcpp.build.cache_key::fill_package_config` 只读包自己清单里声明的
45+
`cflags`/`cxxflags`,而解析出的环境是引擎的广播,只写进 `PackageRoot::privateBuild`,
46+
从不写回前者——两次解析出不同环境的构建因此拿到同一把键。原地升级 `mcpp` 而不清理缓存
47+
目录,会把按**旧**环境编译的目标文件喂给按**新**环境构建的镜像,一个镜像混两种 C 环境且
48+
没有任何诊断,这正是本设计要防止的那个不变量本身。键的推导已经修好(见下),但已经写下的
49+
条目没法用它来判断自己还能不能信——所以 `kCacheEpoch` 从 2 提到了 3,`~/.mcpp/
50+
build-cache/v1` 下已有的条目整体作废,不再逐条判断。这不是求稳的富余动作:键改对了以后,
51+
恰恰是那些*不再*触发替换的包最危险——比如同一个 PR 里被推导进 `c-environment =
52+
"platform"` 的 kernel-abi 包,它广播前后 `privateBuild.cflags` 都是空的,新键和旧键因此
53+
照样相同,而旧键当初对应的目标文件,正是带着替换令牌编译出来的那一份。
54+
`fill_package_config` 现在把 `privateBuild.cflags`/`cxxflags`/新增的 `asmflags`(广播后的
55+
值)与包自身声明的标志一起折进键里,和它原本处理 include 目录的方式一致。
56+
(`src/build/cache_key.cppm`,单测 `test_cache_key.cpp` 的
57+
`TwoDifferentRealisedCEnvironmentsDoNotShareASlot` 与
58+
`EveryPrivateBuildBroadcastFieldReachesTheKey`——后者是给这一类缺陷立的长期防线:
59+
`privateBuild`(`UsageRequirements`)每加一个新的广播字段,都要在这个测试和
60+
`fill_package_config` 里同时补上一行,否则历史会重演,`-D__openkal__` 和
61+
`targetSideUsage` 自己的广播在这次修订之前就已经有过同样的缺口)
62+
- **`__CYGWIN__`/`__CYGWIN32__` 保持定义,经 openkal-musl 尖峰实验修订。** 最初的实现
63+
取消定义它们(理由是图里没有真正的 Cygwin 用户态)。第三方可移植代码里需要知道**目标文件
64+
格式**——不是 C 环境,也不是平台 API——的那部分,没有别的名字能指代「PE 格式 + 呈现
65+
POSIX 的 C 环境」这一组合,只有 `__CYGWIN__`;这样的代码不像本生态自己的包那样可以打
66+
补丁。这是一项留给 30 个成员那轮实测去判定的权衡,不是已经定论的事实:一个库伸手去够
67+
`__CYGWIN__`,也可能伸手去够一个这里并不存在的真正 Cygwin 接口——如果定义它带来的新
68+
失败比修好的还多,结论就会翻过来。(`src/toolchain/cenv.cppm`)
69+
- **声明被校验,不被信任**:新模块 `mcpp.toolchain.cenv_probe` 用最终参数编译一次纯预处理
70+
探针(`-E -dM`,不执行、不需要目标可在本机运行),核对 `__SIZEOF_LONG__`、
71+
`__SIZEOF_WCHAR_T__` 与环境身份宏是否与声明相符,不符即失败并同时打印声明值与实测值;
72+
结果按配置缓存。(`src/toolchain/cenv_probe.cppm`,协调者复核后补的单测
73+
`tests/unit/test_cenv_probe.cpp`——针对真实编译器直接调用
74+
`cenv_probe::verify`,不经过 `cenv::realise`,专门核实测出的不符会被正确识别并渲染成
75+
声明值/实测值两列;此前这一模块只在 e2e 里被间接跑过成功路径,不符路径完全没有测试覆盖)
76+
- **`c-abi` 环境放进共享 store 的那道口子,讲清楚失败模式是什么(而不是缺口表里一行字)。**
77+
对照真正写入 store 的代码路径核实过:安装钩子确实能编译目标侧代码,而它运行在工具链解析
78+
**之前**——`install_hook_env` 拿到的工具链字段一律为空,这是顺序上的硬约束,不是漏传。
79+
钩子因此没有办法知道这次构建解析出的环境,而 store 也不记录装的是按哪种环境编译的,所以
80+
一次环境不一致的构建会拿到宽度错配的目标文件,**没有任何东西核对它**。这与
81+
`mcpp:c++-abi=<impl>` 那条 `requires` 检查已经接受下来的同一种限制同形,不是这个 PR
82+
新引入的;真正补上需要两阶段安装,或者把同样的 `requires` 检查方式推广到
83+
`c-abi`/`c-environment`——都不在这个 PR 范围内。(`docs/22` 及其 zh 镜像)
84+
- **`__openkal__`**:`kernel-abi` 解析为 `openkal` 时,引擎为目标侧全部单元定义它——取自层
85+
的取值,不取自包名。只能用于决定是否调用 `kal_*`,不得用于选择头文件或推断平台
86+
(`docs/24`)。
87+
- **闭包可见性**:`provides = ["platform-sdk"]` 是包对自己的陈述;构建报告新增一行列出
88+
图中所有这样的包(没有则为空),`[build] platform-dependencies = "refuse"`
89+
让它们的出现直接失败构建。(`src/build/prepare.cppm`,`docs/06`)
90+
- **指纹**:解析出的环境与 `__openkal__` 参与构建指纹,LP64 与 LLP64 两次构建绝不共享输出
91+
目录。安装钩子的存储键尚未补上同一个缺口,已在 `docs/22` 记录为已知差距。
92+
- **实现同样到达汇编单元,经 openkal-musl 尖峰实验发现并修订。** 最初的广播只写入每个包的
93+
`privateBuild.cflags`/`cxxflags`,`.S` 单元走独立组装的 `f.as`,只从 `packageCflags` 里
94+
继承 `-D`/`-U`/`-I` 子集(`unit_asm_flags`,本就如此,为了不让 `-std=`/`-O` 这类对汇编
95+
无意义的标志混进去)——`--target=`/`-fno-short-wchar` 因此从未到达汇编器,同一个包里
96+
`.c` 单元看到 `_WIN32` 未定义而 `.S` 单元仍看到它已定义(openkal-musl 自己的
97+
`okm_setjmp.S`、上游 libunwind 的 `assembly.h` 都按这个宏选目标文件格式分支与寄存器保存
98+
集,后果是用 SysV 保存集写、按 Win64 头部量的 `jmp_buf` 悄悄错位)。新增
99+
`UsageRequirements::asmflags`——`privateBuild` 内与 `cflags`/`cxxflags` 平行、但只供引擎
100+
自己广播用的第三条通道(没有对应的 `[build] asmflags = [...]` 清单键)——把同一份令牌
101+
原样送进 `packageAsmflags`,绕开 D/U/I 过滤。实测 clang 对 `-x assembler-with-cpp` 接受
102+
这些令牌全集,故未作裁剪。(`src/modgraph/scanner.cppm`、`src/build/prepare.cppm`,e2e
103+
`tests/e2e/741_...sh` 新增 `.S` 单元与其上的断言)
104+
- 文档:`docs/22`(`[c-abi]`、校验、指纹)、`docs/21`(声明的环境如何移动编译三元组而不
105+
移动链接三元组)、`docs/24`(三组宏、`__openkal__` 的规则、平台单元)、`docs/06`
106+
(`platform-sdk` 标记)及对应 zh 镜像。
107+
8108
### 目标侧由依赖图供给时,编译侧关掉对应的隐式搜索:#662(2026.9.17.3)
9109

10110
链接侧早在 #511 就已经按 `plan.targetSide.cAbi.prebuilt()` 撤掉 `-nostdlib`,编译侧一直

docs/06-features-and-capabilities.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,32 @@ exists to name — it works for the declaring package and then hands the SDK's
523523
headers, unasked, to a consumer that may be built for a target where that SDK
524524
does not belong at all.
525525

526+
#### Making it visible to the closure, not only private (mcpp 2026.9.18+)
527+
528+
`visibility = "private"` answers "does this dependency leak to a consumer's
529+
`-I` list" and says nothing about "is this dependency in the graph at all" —
530+
a question [22 — The Target Side](22-target-side.md#closure-visibility)
531+
answers for the WHOLE build. The SDK package itself states the fact a report
532+
or a refusal needs:
533+
534+
```toml
535+
[package]
536+
name = "some.windows-headers"
537+
version = "1.0.0"
538+
provides = ["platform-sdk"]
539+
```
540+
541+
`platform-sdk` is an ordinary, UNPREFIXED capability — like `blas` above, not
542+
like `mcpp:c-abi=<impl>` — because it names no layer this engine resolves,
543+
only a fact a package states about itself. A build's `Target` report lists
544+
every package in the graph that declares it (empty when none), and `[build]
545+
platform-dependencies = "refuse"` fails the build outright when one is
546+
present — the machine-checkable form of "this build is a closure entirely on
547+
its kernel-abi implementation and nothing else." Declaring `provides =
548+
["platform-sdk"]` and `visibility = "private"` together is the complete
549+
statement: private keeps the headers off a consumer's search path, and
550+
`platform-sdk` keeps the fact off nobody's report.
551+
526552
## Current limitations
527553

528554
**A default feature is turned off in the manifest, not on the command line.**

docs/21-the-target-triple.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,21 @@ is in use. A payload for it does not exist on any host; its system can only come
468468
from a dependency graph, which is what `toolchain list` reports as
469469
`via dependency graph`.
470470

471+
### A declared environment can move the compiled triple, never the linked one (mcpp 2026.9.18+)
472+
473+
A `c-abi` package's `[c-abi]` block ([22 — The C Environment](22-target-side.md#the-c-environment-a-c-abi-package-presents-mcpp-2026918))
474+
can change what `--target=` a COMPILE receives — `x86_64-windows-gnu` compiles
475+
as `x86_64-pc-cygwin` when the graph's C library declares `presents =
476+
"posix"` — without changing the triple this chapter is otherwise about. The
477+
resolved triple (`mcpp toolchain list`'s spelling, the output directory name,
478+
the `Target` report's head line, the LINK line) stays exactly what the graph
479+
and the toolchain resolved; only the compiler's own `--target=` token, on
480+
compile commands, is substituted, because that is the one place the
481+
declaration's environment-identity macros and data model come from. A reader
482+
who greps a `compile_commands.json` for `--target=` and finds a spelling this
483+
chapter never lists is looking at exactly this — see docs/22 for what
484+
triggered it and why.
485+
471486
## The Build Host Is A Third Axis
472487

473488
The two axes above — which compiler, and where the C library comes from — are

0 commit comments

Comments
 (0)