Skip to content

Commit 356c77e

Browse files
committed
D4: a compiler that cannot isolate a graph-supplied C library is degraded
The revert (bcbb3e0) removed the hard refusal for "graph-supplied C library + a compiler that cannot isolate" because it broke the "report before refusing" contract. That left GCC silent again on this combination -- which is what D4 actually ruled out. This adds the third option: a degradation, not a refusal and not silence. `mcpp::diag::degraded("target/c-abi-isolation", ...)` fires once resolution knows the C library came from the graph and the resolved compiler is not Clang-family. It names the C library (interface and package@version) and the compiler, points at a Clang-family toolchain as the one that isolates, and changes nothing about the command line -- `hostflags.cppm` was already not emitting an isolation token for GCC, and still is not. `--strict` promotes it to an error through the existing `diag::flush` policy, which is the one place that decision already lives. - `prepare.cppm`: the check sits right where `resolvedTargetSide` becomes available, beside the existing `target/compiler-runtime` degradation, and before `format_report` prints -- so the report is unaffected either way, which is what the earlier refusal got wrong. - e2e 740: names the library and the compiler in the warning, asserts the target-side report still prints (the exact thing the reverted refusal broke), and controls against a Clang-family toolchain over the same graph getting no such note. 268, 282, 303 re-verified green. - CHANGELOG's D4 bullet rewritten to describe what this branch actually does, not the removed refusal.
1 parent bcbb3e0 commit 356c77e

3 files changed

Lines changed: 151 additions & 10 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,21 @@
2020
条件改为只看这一层自己的来源,不再看 `c-abi` 是否也来自图。两个 token 出现在 `cflags`/
2121
`cxxflags`/`asmflags` 这组全局 flags 里,C、C++、汇编、依赖扫描、std 模块预编译同时受益。
2222
(`src/toolchain/hostflags.cppm`,单测 `test_hostflags.cpp`)
23-
- **GCC 家族没有等价的单一 flag,保持原样不隔离。** `-nostdinc` 加 `-isystem <gcc
24-
-print-file-name=include>`、`<…/include-fixed>` 才能拼出等价物,且 GCC 本就没有
25-
`<driver>.cfg` 可供 `bypassCfg` 打开这整块——`hostflags.cppm` 因此对 GCC 不做任何改动。
26-
最初的草案在 `prepare.cppm` 里加了一条「图供给 C 库 + GCC」的硬拒绝,想着比静默不隔离更
27-
诚实;实测(268、282、303 三个既有 e2e)显示这个组合是这几个文件的常规夹具——它们用一个
28-
只声明 `provides = ["mcpp:c-abi=..."]`、不含真实头文件的假包在**原生**目标上验证与隔离
29-
完全无关的其它事实,且都依赖 268 自己注释所陈述的设计:目标侧的解析报告先于编译打印,
30-
失败与否不影响这条报告——而这条硬拒绝恰好抢在报告打印之前退出,三个文件全部转红。撤回
31-
该拒绝;GCC 在这一组合上的行为与 #662 之前逐字节相同。
23+
- **GCC 家族没有等价的单一 flag,命令行不变,但不再静默。** `-nostdinc` 加
24+
`-isystem <gcc -print-file-name=include>`、`<…/include-fixed>` 才能拼出等价物,且
25+
GCC 本就没有 `<driver>.cfg` 可供 `bypassCfg` 打开这整块——`hostflags.cppm` 因此对 GCC
26+
不做任何改动,这一组合上产出的编译命令与 #662 之前逐字节相同。
27+
中途试过两种更强的处理,都被实测推翻:静默不隔离(#662 之前的状态,`GCC 家族没有等价的
28+
单一 flag` 的最初读法)什么也不说;`prepare.cppm` 里一条「图供给 C 库 + GCC」的硬拒绝
29+
(想着比静默更诚实)在既有 e2e 268、282、303 上转红——它们用一个只声明
30+
`provides = ["mcpp:c-abi=..."]`、不含真实头文件的假包在**原生**目标上验证与隔离完全无关
31+
的其它事实,且都依赖 268 自己注释所陈述的设计:目标侧的解析报告先于编译打印,失败与否
32+
不影响这条报告——而硬拒绝恰好抢在报告打印之前退出。第三种形状落地:`mcpp::diag::degraded`
33+
在目标侧解析出「c-abi 来自图 且 编译器不是 Clang 家族」时报一条 warning,点名 C 库
34+
(名字与 `包名@版本`)和编译器,并指向 Clang 家族能做到隔离——不拒绝构建,不改动任何命令行
35+
(`hostflags.cppm` 本就没有为这一组合追加过 token),也不再沉默。`--strict` 下
36+
`mcpp::diag::flush` 按其既有策略把它提升为错误,复用的是这一整块已经存在的策略,不是新写
37+
的一条。(`src/build/prepare.cppm`,e2e 740;268、282、303 仍然全绿)
3238
- **隔离后原本靠宿主补齐的包会确定地失败,失败信息现在可读。** 构建失败、目标侧 `c-abi`
3339
来自图、且编译器输出含 `file not found` 时,追加一条说明,点名 C 库(名字与
3440
`包名@版本`)并给出两条路:按 `cfg(c-abi = "...")` 适配,或把平台依赖以私有可见性带进
@@ -44,11 +50,13 @@
4450
构建与载荷提供 C 库的构建命令行逐字节不变(单测守卫)。这类构建的 flags 指纹变化,升级后
4551
会重建一次。原先只因为宿主头文件补上缺口才能编译的包,现在会确定地失败——这类构建此前只在
4652
装了对应宿主包的机器上成功,产物还混用了两个 C 库的声明;新增的说明使这类失败可以诊断。
53+
C 库来自图、编译器却是 GCC 家族的构建会多打印一条 warning(命令行不变,构建照常继续)。
4754
- 单测:`test_hostflags.cpp`(选项矩阵 `cAbiPrebuilt × cxxFromGraph × {clang, gcc}`,以及
4855
载荷提供两层时命令行不变的回归守卫)、`test_ninja_backend.cpp`。e2e:738(openkal
4956
`x86_64-windows-gnu` 构建,逐单元核对 clang 自己报告的头文件搜索列表,门在
5057
`mingw-host-headers` capability 上,`openkal-cross.yml` 的 `ecosystem-e2e` job 安装
51-
`mingw-w64` 使其在 CI 上成立)、739(私有 feature-dep 的双向锁定)。
58+
`mingw-w64` 使其在 CI 上成立)、739(私有 feature-dep 的双向锁定)、740(GCC 家族在
59+
图供给 C 库上得到警告而非拒绝,且既有的 268/282/303 仍然全绿)。
5260

5361
### 声明的 C 运行时由 mcpp 安装,查找只做精确匹配:#660(2026.9.17.2)
5462

‎src/build/prepare.cppm‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10769,6 +10769,39 @@ prepare_build(bool print_fingerprint,
1076910769
resolvedTargetSide = tsd::resolve(in);
1077010770
targetSideResolved = true;
1077110771

10772+
// REPORTED, NOT REFUSED (mcpp#662, D4). `mcpp.toolchain.hostflags`
10773+
// closes the compiler's own C-library search with `-nostdlibinc`
10774+
// when a package supplies the target's C library (M1) — but only on
10775+
// a Clang-family driver; GCC has no one-token equivalent
10776+
// (`hostflags.cppm`'s own note on the shape it would need). Silently
10777+
// building unisolated was ruled out once already: the resolver
10778+
// refusing the combination outright was ALSO tried and reverted —
10779+
// it fired before `format_report` below and broke three existing
10780+
// e2e fixtures (268, 282, 303) that use a synthetic C-library
10781+
// provider on this host's native, GCC-default target to assert
10782+
// something else entirely, none of them about isolation. A
10783+
// degradation is the third option: it changes no command line
10784+
// (this branch decides nothing `hostflags.cppm` does not already
10785+
// decide on its own), and it does not stop a build the previous
10786+
// release would have allowed — it names, once, the gap the previous
10787+
// release left silent.
10788+
if (tc && resolvedTargetSide.cAbi.fromGraph()
10789+
&& !mcpp::toolchain::is_clang(*tc)) {
10790+
mcpp::diag::degraded("target/c-abi-isolation", std::format(
10791+
"the target's C library ('{}', {}) comes from the "
10792+
"dependency graph, and the resolved compiler ('{}') has no "
10793+
"way to stop its own driver from also searching the host's "
10794+
"C library headers",
10795+
resolvedTargetSide.cAbi.interfaceName,
10796+
resolvedTargetSide.cAbi.impl, tc->compiler_family()),
10797+
"a host header can still satisfy an #include the graph's "
10798+
"own headers do not, silently — a Clang-family toolchain "
10799+
"closes that search entirely (docs/22 'Adaptation To The "
10800+
"Resolved Target Side')",
10801+
"add [toolchain] default = \"llvm@<version>\", or for one "
10802+
"target only [target.<triple>] toolchain = \"llvm@<version>\"");
10803+
}
10804+
1077210805
// REPORTED ONCE, NOT REFUSED. A program that never reaches an
1077310806
// availability check links and runs without the archive; refusing it
1077410807
// would trade a diagnosed hazard for a regression. The degradation
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
#!/usr/bin/env bash
2+
# requires: gcc
3+
# 740 -- when the target's C library comes from the dependency graph and the
4+
# resolved compiler cannot isolate it from the host's own (GCC: no single
5+
# flag equivalent to Clang's `-nostdlibinc`), mcpp reports a degradation and
6+
# proceeds. It neither refuses the build nor stays silent.
7+
#
8+
# THE HISTORY, BECAUSE BOTH OTHER SHAPES WERE TRIED AND MEASURED WRONG.
9+
#
10+
# Silent was the state before mcpp#662's M1: GCC never emitted the isolation
11+
# token (hostflags.cppm's `bypassCfg` is unconditionally false for GCC, no
12+
# `<driver>.cfg` beside it), and nothing said so.
13+
#
14+
# A hard refusal at `prepare_build` was tried next, reasoning "never
15+
# silently unisolated" all the way to a build error. Measured against the
16+
# existing suite it broke three files (268, 282, 303) that use a synthetic
17+
# `provides = ["mcpp:c-abi=..."]` package with the AMBIENT default toolchain
18+
# (gcc on a fresh Linux runner) to assert something about the target-side
19+
# REPORT, none of them about isolation -- and the refusal fired before that
20+
# report printed, which is exactly what 268's own comment says must never
21+
# happen: "the resolution is reported during planning ... before a single
22+
# object is compiled ... complete whether or not the link afterwards
23+
# succeeds".
24+
#
25+
# `mcpp::diag::degraded` is the third option this test locks in: the build
26+
# is not refused (268/282/303 stay green -- this file's own half two
27+
# reproduces 303's shape and checks the report line still prints), and the
28+
# gap is not silent either (half one).
29+
set -e
30+
31+
MCPP="${MCPP:-mcpp}"
32+
work="$(mktemp -d)"
33+
trap 'rm -rf "$work"' EXIT
34+
cd "$work"
35+
36+
mkdir -p libc/src
37+
printf '[package]\nname = "fake-libc"\nversion = "0.1.0"\nprovides = ["mcpp:c-abi=musl"]\n\n[build]\nsources = []\n' \
38+
> libc/mcpp.toml
39+
printf '[package]\nname = "clibdeg"\nversion = "0.1.0"\n\n[dependencies]\nfake-libc = { path = "libc" }\n' \
40+
> mcpp.toml
41+
42+
# `why toolchain`, NOT `build`: this file's subject is what mcpp REPORTS
43+
# during resolution, and a real link additionally needs a working sandbox
44+
# C-runtime setup that has nothing to do with the claim here (`build`'s own
45+
# behaviour on this exact fixture is 303's territory).
46+
out="$("$MCPP" why toolchain 2>&1)"
47+
48+
# ── Half one: the degradation names the library and the compiler ───────────
49+
echo "$out" | grep -q '^warning: ' || {
50+
echo "FAIL: no warning at all on a GCC toolchain over a graph-supplied C library:"
51+
echo "$out"; exit 1
52+
}
53+
echo "$out" | grep -qF "C library ('musl'" || {
54+
echo "FAIL: the warning does not name the C library:"; echo "$out"; exit 1
55+
}
56+
echo "$out" | grep -qF "resolved compiler ('gcc')" || {
57+
echo "FAIL: the warning does not name the compiler that cannot isolate:"
58+
echo "$out"; exit 1
59+
}
60+
echo "$out" | grep -qi 'clang' || {
61+
echo "FAIL: the warning does not point at the toolchain that does isolate:"
62+
echo "$out"; exit 1
63+
}
64+
echo " ok a GCC toolchain over a graph-supplied C library is named, not silent"
65+
66+
# ── Half two: it is a note, not a refusal -- the report still reaches print ─
67+
#
68+
# THE EXACT FAILURE MODE THE HARD REFUSAL HAD: firing before `format_report`
69+
# printed anything at all. Asserting the report line is present is what
70+
# distinguishes "advisory" from "the build's replacement".
71+
echo "$out" | grep -qE 'c-abi *musl' || {
72+
echo "FAIL: the target-side report did not print at all -- looks like the"
73+
echo " refusal this file exists to keep reverted:"
74+
echo "$out"; exit 1
75+
}
76+
echo " ok the target-side report still prints (this is advisory, not a refusal)"
77+
78+
# ── Half three: the control -- a Clang-family toolchain gets no such note ──
79+
#
80+
# THE HARNESS'S OWN VALIDITY. Without this, half one could be passing because
81+
# the message ALWAYS prints, on every toolchain, which would say nothing
82+
# about GCC specifically.
83+
llvmspec="$("$MCPP" toolchain list --format json 2>/dev/null \
84+
| jq -r '[.data.toolchains[] | select(.family=="llvm") | "llvm@"+.version]
85+
| unique | .[0] // empty' | tr -d '\r')"
86+
if [ -n "$llvmspec" ]; then
87+
printf '[package]\nname = "clibdeg"\nversion = "0.1.0"\n\n[toolchain]\ndefault = "%s"\n\n[dependencies]\nfake-libc = { path = "libc" }\n' \
88+
"$llvmspec" > mcpp.toml
89+
out2="$("$MCPP" why toolchain 2>&1)"
90+
if echo "$out2" | grep -q "has no way to stop"; then
91+
echo "FAIL: the same degradation fired on a Clang-family toolchain, which"
92+
echo " DOES isolate the graph's C library:"
93+
echo "$out2"; exit 1
94+
fi
95+
echo " ok a Clang-family toolchain over the same graph gets no such note"
96+
else
97+
echo "SKIP: no llvm installed here to run the control against"
98+
fi
99+
100+
echo "OK: 740 a compiler that cannot isolate a graph-supplied C library is degraded, not refused"

0 commit comments

Comments
 (0)