Skip to content

Commit 434a11d

Browse files
committed
fix(freestanding): 直连链接下的共享库,用话说清楚而不是丢一条 ninja 规则
直连链接路径定义 cxx_link / c_link / cxx_archive,不定义共享库规则 —— 没有加载器 的目标没有东西去加载它。缺这条检查时,失败长这样: ninja: error: build.ninja:88: unknown build rule 'cxx_shared' 对一个在清单里写下 kind = "shared" 的人,这句话指的是一条内部规则。 ⚠️ 补上那条规则比省掉它更糟:ld.lld -shared 会成功,产出一个那台机器上没有任何 东西能加载的对象 —— 静默的无意义,而不是响亮的拒绝。 e2e 137 加了第 7 条断言,同时检查两件事:拒绝时提到了原因,且**没有**提到内部 规则名。
1 parent 1a79c0a commit 434a11d

3 files changed

Lines changed: 76 additions & 2 deletions

File tree

.agents/docs/2026-08-21-freestanding-outstanding-four.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -272,10 +272,22 @@ libcxx-headers = "22.1.8" # 与产出导出表的那个 libc++ 同版本
272272

273273
**步骤 2:新建 `xim:libcxx-headers`**
274274

275-
内容是 `include/c++/v1``share/libc++`,实测 xz 压缩后 **0.9 MB**。它与
276-
宿主无关,因此**一个包服务全部五个宿主**,而「加进载荷」要给每个缺失的宿主
275+
内容是 `include/c++/v1``share/libc++`。复测于 llvm 22.1.8 的载荷:
276+
277+
| | |
278+
|---|---|
279+
| `include/c++/v1` | 16 MB |
280+
| `share/libc++` | 624 KB |
281+
| 打包后(`tar` + `xz -9`) | **943,180 字节** |
282+
283+
它与宿主无关,因此**一个包服务全部五个宿主**,而「加进载荷」要给每个缺失的宿主
277284
各加一次。
278285

286+
⚠️ **这个包尚未上传,而不上传是有意的。** 它今天可以打出来,但没有任何东西验证过
287+
它在 Windows 上确实让 `std-freestanding` 工作 —— 步骤 1 还没做。把一个未经验证的
288+
产物先镜像出去,只是让「未经验证的东西」传播得更快。这与 `mcpplibs/qemu-x86` 那边
289+
定下的顺序是同一条:**先跑通,再发布**
290+
279291
**步骤 3:实现层的选择成为可声明的。**
280292

281293
接口层不变,配置层按检测到的实现分支:

src/build/ninja_backend.cppm

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2306,6 +2306,33 @@ std::expected<BuildResult, BuildError> NinjaBackend::build(const BuildPlan& plan
23062306
"compile_commands.json was not updated: {}", cdb.error().message));
23072307
}
23082308

2309+
// ⚠️ A SHARED LIBRARY ON A TARGET WHOSE LINK IS DRIVEN BY THE LINKER, SAID
2310+
// IN WORDS RATHER THAN AS A MISSING NINJA RULE.
2311+
//
2312+
// The direct-linker path defines `cxx_link`, `c_link` and `cxx_archive` and
2313+
// no shared-library rule, because a target with no loader has nothing to
2314+
// load one. Without this check the failure is
2315+
//
2316+
// ninja: error: build.ninja:88: unknown build rule 'cxx_shared'
2317+
//
2318+
// which names an internal rule to somebody who wrote `kind = "shared"` in a
2319+
// manifest. Defining the rule would be worse: `ld.lld -shared` succeeds and
2320+
// produces an object nothing on that machine can load.
2321+
if (!flags.ldDriver.empty()) {
2322+
for (auto const& lu : plan.linkUnits) {
2323+
if (lu.kind != LinkUnit::SharedLibrary) continue;
2324+
return std::unexpected(BuildError{std::format(
2325+
"target '{}' is a shared library, and '{}' has no dynamic "
2326+
"loader to load one.\n"
2327+
" A freestanding image is linked statically because there is "
2328+
"no other option: nothing on that machine resolves a symbol at "
2329+
"run time.\n"
2330+
" Use `kind = \"lib\"` for a static library, or `kind = "
2331+
"\"bin\"` for the image itself.",
2332+
lu.targetName, plan.toolchain.targetTriple)});
2333+
}
2334+
}
2335+
23092336
// A distribution contract that could not be honored is reported, never
23102337
// silently downgraded — the whole point of the model (INV-1/INV-4 in
23112338
// .agents/docs/2026-08-02-issue336-pr142-analysis.md). Emitted here rather

tests/e2e/137_x86_64_zero_libc_target.sh

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,4 +141,39 @@ grep -q -- "-march=x86-64" verbose.log || {
141141
grep -q -- "-mno-red-zone" verbose.log || {
142142
echo "the red zone was left enabled on a target that takes interrupts"; exit 1; }
143143

144+
# ── 7. A shared library on this target is refused in words ─────────────────
145+
#
146+
# ⚠️ THE DIRECT-LINKER PATH DEFINES NO SHARED-LIBRARY RULE, AND WITHOUT A CHECK
147+
# THE FAILURE NAMES AN INTERNAL ONE:
148+
#
149+
# ninja: error: build.ninja:88: unknown build rule 'cxx_shared'
150+
#
151+
# to somebody who wrote `kind = "shared"` in a manifest. Defining the rule would
152+
# be worse than omitting it: `ld.lld -shared` succeeds and produces an object
153+
# nothing on a machine with no loader can load.
154+
cd "$TMP"
155+
mkdir -p shared/src && cd shared
156+
cat > mcpp.toml <<'EOF'
157+
[package]
158+
name = "shr"
159+
version = "0.1.0"
160+
161+
[build]
162+
target = "x86_64-none-elf"
163+
164+
[targets.shr]
165+
kind = "shared"
166+
EOF
167+
echo 'int f() { return 1; }' > src/lib.cpp
168+
if "$MCPP" build > shared.log 2>&1; then
169+
cat shared.log; echo "a shared library was built for a target with no loader"; exit 1
170+
fi
171+
grep -q "no dynamic loader" shared.log || {
172+
cat shared.log
173+
echo "the refusal does not name the situation"; exit 1; }
174+
if grep -q "unknown build rule" shared.log; then
175+
cat shared.log
176+
echo "the failure names an internal ninja rule instead of the cause"; exit 1
177+
fi
178+
144179
echo "PASS: x86_64-none-elf builds on the zero-libc tier, linked by ld.lld directly"

0 commit comments

Comments
 (0)