Skip to content

Commit 9189e2e

Browse files
committed
target: the C library decides whether dynamic can be honoured
`linkage = "dynamic"` was reported as ineffective whenever the target's system came from the graph, and the warning's own reason is narrower than its predicate: "those packages are compiled into this build as objects, and there is no shared object to link against" is a property of the C library. Measured on a backend running ON a platform — kernel interface from the graph, C library from the payload: warning: `linkage = "dynamic"` has no effect … The artifact is static. $ file → dynamically linked $ readelf → NEEDED libm.so.6, libgcc_s.so.1, libc.so.6 A payload libc has a shared object, so `dynamic` was honoured and the diagnostic was false. Reads `cAbi.fromGraph()` instead. The two other `system_from_graph()` uses in prepare.cppm stay: both ask whether the graph supplies any part of the system, which is genuinely the two-layer question. e2e 291 asserts both directions — deleting the warning also stops it lying, and that would lose the diagnostic the directive needs when the C library really is the graph's. It also asserts the artifact's DT_NEEDED count, not just the absence of the text. Fourth defect of this shape in this release.
1 parent f3b9f2f commit 9189e2e

3 files changed

Lines changed: 153 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,24 @@
9393
**C 库才是决定它的那一层** —— 理由 `check_layering` 早已反向陈述:载荷的 C++
9494
运行时是对着载荷的 C 库配置的,所以当且仅当那份 C 库在用时它才可用。
9595
96+
- **⭐ 第四条同型:`linkage = "dynamic"` 的「无效」诊断在说谎。**
97+
98+
实测 2026-08-25,在 285 的形状上(kernel-abi 来自图 + C 库来自载荷):
99+
100+
```
101+
warning: `linkage = "dynamic"` has no effect … The artifact is static.
102+
$ file → dynamically linked
103+
$ readelf → NEEDED libm.so.6, libgcc_s.so.1, libc.so.6
104+
```
105+
106+
谓词用的是 `system_from_graph()`(跨 kernel-abi 与 c-abi 两层的 OR),而这条
107+
警告自己给的理由——「那些包被当作对象编进本次构建,没有共享对象可链接」——
108+
是**C 库单独一层**的性质。载荷的 libc 有共享对象,`dynamic` 就被兑现了,这里
109+
本来无话可说。改为 `cAbi.fromGraph()`。
110+
111+
prepare.cppm 里另外两处 `system_from_graph()` 保留:它们问的是「图有没有供给
112+
系统的任一部分」,那确实是两层的问题。
113+
96114
- **⭐ `build.mcpp` 的 `PATH` 前置项目声明的那个环境。**
97115
98116
```
@@ -144,6 +162,7 @@
144162
| 288 | 无 OS 无 C 库,断言报告里**没有 c-abi 那一行**,并在 qemu 里真启动 |
145163
| 289 | **一台宿主横扫四个目标** —— 这个体系本就是通用交叉构建,传统栈要六个 runner 的覆盖,这里一个循环 |
146164
| 290 | 声明把环境放到 `PATH` 前面,**而且只有声明会** —— 两个方向各一条断言 |
165+
| 291 | `dynamic` 只在 C 库来自图时被拒 —— 且断言产物的 `DT_NEEDED` 而非只断言文案 |
147166
148167
290 的两半只有一半是特性:无条件前置能通过前一半,而那正是被撤回的设计。
149168

src/build/prepare.cppm

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5981,14 +5981,26 @@ prepare_build(bool print_fingerprint,
59815981

59825982
// A request that cannot be honoured is said so rather than dropped.
59835983
//
5984-
// Measured 2026-08-23: `[build] linkage = "dynamic"` on a project whose
5985-
// system comes from the graph produced a statically linked artifact and
5984+
// Measured 2026-08-23: `linkage = "dynamic"` on a project whose system
5985+
// comes from the graph produced a statically linked artifact and
59865986
// printed nothing. The outcome is correct — the graph supplies its
59875987
// libraries as objects compiled into this build, and there is no shared
59885988
// object for a loader to resolve at run time — but a directive that has
59895989
// no effect and no diagnostic is indistinguishable from one that was
59905990
// never read.
5991-
if (resolvedTargetSide.system_from_graph()
5991+
//
5992+
// ⚠️ THE C LIBRARY IS THE LAYER THIS DEPENDS ON, NOT "THE SYSTEM".
5993+
// `system_from_graph()` spans two layers, and the arrangement that
5994+
// separates them is real: a backend running ON a platform takes its
5995+
// kernel interface from the graph while the C library stays the
5996+
// payload's. Measured 2026-08-25 on exactly that project — the
5997+
// predicate was true, this warning printed "The artifact is static",
5998+
// and the artifact had three DT_NEEDED entries including `libc.so.6`.
5999+
// The reason the message gives is a property of the C library alone:
6000+
// a payload libc has a shared object, so `dynamic` is honoured and
6001+
// there is nothing to warn about. Same shape as the three defects this
6002+
// release fixes — see `TargetSide::system_from_graph`'s own note.
6003+
if (resolvedTargetSide.cAbi.fromGraph()
59926004
&& m->buildConfig.linkage == "dynamic")
59936005
mcpp::ui::warning(
59946006
"`linkage = \"dynamic\"` has no effect when the "
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
#!/usr/bin/env bash
2+
# requires: gcc elf network
3+
# `linkage = "dynamic"` is reported as ineffective only when it actually is.
4+
#
5+
# ⚠️ THE PREDICATE USED TO SPAN TWO LAYERS AND THE REASON SPANS ONE. The
6+
# warning's own justification — "those packages are compiled into this build as
7+
# objects, and there is no shared object to link against" — is a property of
8+
# the C LIBRARY. A backend that runs ON a platform takes its kernel interface
9+
# from the graph and keeps the payload's C library, and a payload libc has a
10+
# shared object, so `dynamic` is honoured there.
11+
#
12+
# Measured 2026-08-25 before the fix, on the project this file builds:
13+
#
14+
# warning: `linkage = "dynamic"` has no effect … The artifact is static.
15+
# $ file …/dynprobe → dynamically linked
16+
# $ readelf -d … → NEEDED libm.so.6, libgcc_s.so.1, libc.so.6
17+
#
18+
# ⭐⭐ BOTH DIRECTIONS, BECAUSE ONLY ONE OF THEM IS THE FIX. Deleting the
19+
# warning outright also stops it lying, and that would lose the diagnostic the
20+
# directive needs when the C library really does come from the graph — which is
21+
# why the second half builds that arrangement and requires the warning to
22+
# appear.
23+
set -e
24+
25+
MCPP="${MCPP:-mcpp}"
26+
work="$(mktemp -d)"
27+
trap 'rm -rf "$work"' EXIT
28+
29+
# ⚠️ AN EXPLICIT `--target` IS LOAD-BEARING. `[target.<triple>]` applies to the
30+
# target that was REQUESTED; a bare `mcpp build` requests none, the row never
31+
# applies, `linkage` stays empty, and both halves of this test would pass
32+
# without exercising anything.
33+
TARGET=x86_64-linux-gnu
34+
35+
make_project() {
36+
local dir="$1" tc="$2" deps="$3"
37+
mkdir -p "$dir/src"
38+
cat > "$dir/mcpp.toml" <<TOML
39+
[package]
40+
name = "linkprobe"
41+
version = "0.1.0"
42+
43+
[toolchain]
44+
default = "$tc"
45+
46+
[target.$TARGET]
47+
linkage = "dynamic"
48+
49+
[dependencies]
50+
$deps
51+
TOML
52+
printf '#include <cstdio>\nint main() { std::printf("ok\\n"); }\n' \
53+
> "$dir/src/main.cpp"
54+
}
55+
56+
warned() { printf '%s\n' "$1" | grep -q 'has no effect'; }
57+
58+
# ── Half one: kernel interface from the graph, C library from the payload ──
59+
make_project "$work/onplatform" "gcc@16.1.0" 'openkal-linux = "0.5.4"'
60+
out="$(cd "$work/onplatform" && "$MCPP" build --target "$TARGET" 2>&1)" || {
61+
echo "SKIP: the on-platform project did not build here"
62+
printf '%s\n' "$out" | grep -iE '^.*error.*$' | head -3
63+
exit 0
64+
}
65+
66+
# The arrangement has to be the one this is about, or the assertion below is
67+
# about nothing.
68+
case "$out" in
69+
*kernel-abi*graph*) ;;
70+
*) echo "SKIP: the kernel interface did not come from the graph here"
71+
printf '%s\n' "$out" | grep -E 'abi' | sed 's/^/ /'
72+
exit 0 ;;
73+
esac
74+
75+
bin="$(find "$work/onplatform/target" -name linkprobe -type f -perm -u+x | head -1)"
76+
[ -n "$bin" ] || { echo "FAIL: no artifact was produced"; exit 1; }
77+
78+
if warned "$out"; then
79+
echo "FAIL: 'dynamic' was reported as ineffective while the payload's C library was in use"
80+
printf '%s\n' "$out" | grep 'has no effect' | sed 's/^/ /'
81+
exit 1
82+
fi
83+
echo " ok no warning when the C library is the payload's"
84+
85+
# ⭐ AND THE ARTIFACT AGREES. The warning's claim is "The artifact is static";
86+
# a test that only checked for the absence of the text would pass on a build
87+
# that silently produced a static binary anyway.
88+
needed="$(readelf -d "$bin" 2>/dev/null | grep -c NEEDED || true)"
89+
if [ "${needed:-0}" -gt 0 ]; then
90+
echo " ok 'dynamic' was honoured — $needed DT_NEEDED entries"
91+
else
92+
echo "FAIL: the artifact is static, so the warning would have been right"
93+
exit 1
94+
fi
95+
96+
# ── Half two: the C library itself comes from the graph ────────────────────
97+
# The same stack e2e 286 builds. openkal-llvm-runtime IS libc++/libc++abi/
98+
# libunwind, so the toolchain has to be the one that package exists for — and
99+
# it is the C library coming from the graph, not the C++ runtime, that this
100+
# half is about.
101+
make_project "$work/fullgraph" "llvm@22.1.8" 'openkal-musl = "0.3.5"
102+
openkal-llvm-runtime = "0.1.3"'
103+
out2="$(cd "$work/fullgraph" && "$MCPP" build --target "$TARGET" 2>&1)" || true
104+
105+
case "$out2" in
106+
*c-abi*graph*) ;;
107+
*) echo "SKIP: the C library did not come from the graph here"
108+
printf '%s\n' "$out2" | grep -E 'abi' | sed 's/^/ /'
109+
exit 0 ;;
110+
esac
111+
112+
if warned "$out2"; then
113+
echo " ok the warning still appears when the C library is the graph's"
114+
else
115+
echo "FAIL: 'dynamic' cannot be honoured here and nothing said so"
116+
exit 1
117+
fi
118+
119+
echo "OK: the C library decides whether 'dynamic' can be honoured"

0 commit comments

Comments
 (0)