Skip to content

Commit ded81c6

Browse files
committed
fix(build): 两处「由图决定」的续篇,和一条让改动看起来没生效的指纹缺陷
## 1. -ffreestanding 也归图决定 target.cppm 这个函数上方的段落自己写着这个 flag 改变了什么:「no `main` special-casing, no builtin-to-libcall rewrites it cannot back up」。两条都是 关于「有没有一个库在那里」的陈述 —— 而当图里有一个包为这个目标提供 hosted-standard-library 时,就是有。hosted 是语言自己对「不是 freestanding」 的叫法,提供这个 capability 的包断言的正是这个 flag 否认的那件事。 ⚠️ 实测 2026-08-23,而它显形的方式不是关于这个 flag 的诊断。一个 main 写成 普通 C++ `int main()` 的裸机程序链接失败于 `undefined symbol: main`,而 nm 看 它自己的对象里是 `_Z4mainv` —— 在 -ffreestanding 下 C++ 的 main 不是保留的 入口点,于是像别的函数一样被修饰。启动对象引用 main,没有人定义它。 另一条路是让每个这样的程序写 `extern "C" int main()`,那是在给一个「构建替 程序作出、而且已经不再成立」的主张打补丁。 ## 2. ⭐⭐ 依赖的 [build] 编译输入从来没进过指纹 canonical_package_build_metadata 折进去的是每个包的身份、runtime 需求和链接 意图 —— 不包括 buildConfig。只有根的编译输入经由 canonical_compile_flags 进 指纹。于是**改一个依赖的 cflags / defines / sources / per-glob flags,指纹不 变**,消费者留在同一个输出目录,快路径重放改动之前生成的 build.ninja。 ⚠️ 它显形的方式是「这条改动像是没生效」。实测:给一个 path 依赖的 [build] cflags 加一个 flag,重建后生成的 unit_cflags 里没有它;touch 源码, 还是没有;删掉 target/ 立刻就有。前两次观察正是一个读者用来得出「这个 flag 被过滤了」的依据 —— 而我确实得出了这个结论并写了下来,直到第三次测量。 prepare.cppm 里根 flag 尾部合并旁边的注释,在这条修复之前就已经写着 「canonical_package_build_metadata folds packages[].manifest.buildConfig」。 现在它真的这么做了。
1 parent d8fd41b commit ded81c6

2 files changed

Lines changed: 59 additions & 1 deletion

File tree

src/build/prepare_inputs.cppm

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,45 @@ std::string canonical_package_build_metadata(
288288
s += pkg.manifest.package.version;
289289
s += " source=";
290290
s += pkg.manifest.package.sourceProvenance;
291+
// ⭐⭐ WHAT THIS PACKAGE IS BUILT WITH, AND NOT ONLY WHAT IT ASKS THE
292+
// RUNTIME FOR.
293+
//
294+
// Only the root's compile inputs used to reach the fingerprint, through
295+
// `canonical_compile_flags` on the root manifest. A DEPENDENCY's
296+
// `[build] cflags` / `defines` / `sources` / per-glob flags reached
297+
// nothing — so editing one left the fingerprint unchanged, the consumer
298+
// kept the same output directory, and the fast path replayed a
299+
// build.ninja generated before the edit.
300+
//
301+
// ⚠️ AND THE WAY THAT SHOWS IS THAT THE EDIT APPEARS TO HAVE HAD NO
302+
// EFFECT. Measured 2026-08-23 on a path dependency: a flag added to
303+
// `[build] cflags` was absent from the generated `unit_cflags` after a
304+
// rebuild, absent after touching the sources, and present the moment
305+
// `target/` was removed. The first two observations are what a reader
306+
// uses to conclude the flag is being filtered, and one was concluded
307+
// and written down before the third measurement was taken.
308+
//
309+
// The comment beside the root-flag tail merge in prepare.cppm has said
310+
// "canonical_package_build_metadata folds packages[].manifest.
311+
// buildConfig" since before this fix. It now does.
312+
//
313+
// packages[0] is the root, whose flags `canonical_compile_flags`
314+
// already folds; serialising it twice is harmless and keeps this loop
315+
// one rule rather than one rule and an exception.
316+
s += ' ';
317+
s += canonical_compile_flags(pkg.manifest);
318+
for (auto const& src : pkg.manifest.buildConfig.sources) {
319+
s += " src:";
320+
s += src;
321+
}
322+
for (auto const& dir : pkg.manifest.buildConfig.includeDirs) {
323+
s += " inc:";
324+
s += dir.generic_string();
325+
}
326+
for (auto const& dir : pkg.manifest.buildConfig.includeDirsAfter) {
327+
s += " inca:";
328+
s += dir.generic_string();
329+
}
291330
auto const& runtime = pkg.manifest.runtimeConfig;
292331
for (auto const& requirement : runtime.requirements) {
293332
s += " runtime-need:";

src/freestanding/target.cppm

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,26 @@ inline std::vector<std::string> compile_flags(const Spec& s,
226226
// `-ffreestanding` so the ordering of this function's output stays a
227227
// function of the table rather than of the row.
228228
for (auto flag : s.extra) out.emplace_back(flag);
229-
out.emplace_back("-ffreestanding");
229+
// ⭐ AND `-ffreestanding` ITSELF IS ONE OF THE THINGS THE GRAPH DECIDES.
230+
//
231+
// The paragraph above this function names what the flag changes: "no `main`
232+
// special-casing, no builtin-to-libcall rewrites it cannot back up". Both
233+
// are statements about whether a library is there — and when a package in
234+
// the graph provides `hosted-standard-library` FOR THIS TARGET, one is.
235+
// `hosted` is the language's own word for not-freestanding, so a provider
236+
// of that capability is asserting exactly the condition this flag denies.
237+
//
238+
// ⚠️ Measured 2026-08-23, and the way it showed was not a diagnostic about
239+
// the flag. A bare-metal program whose `main` was an ordinary C++ `int
240+
// main()` failed to link with `undefined symbol: main`, while `nm` on its
241+
// own object showed `_Z4mainv` — under `-ffreestanding` a C++ `main` is not
242+
// the reserved entry point and is therefore mangled like any other
243+
// function. The startup object referred to `main` and nothing defined it.
244+
//
245+
// The alternative was to make every such program write `extern "C" int
246+
// main()`, which is a workaround for a claim the build was making on the
247+
// program's behalf and that was no longer true.
248+
if (!targetCxxRuntime) out.emplace_back("-ffreestanding");
230249
// ⚠️ No C++ standard library headers. Not a preference — the toolchain's
231250
// libc++ headers are built for the HOST: `#include <stdio.h>` resolves to
232251
// libc++'s wrapper, which opens `<__config_site>`, which is generated per

0 commit comments

Comments
 (0)