Skip to content

Commit 5cc4e34

Browse files
committed
fix(bench): point the linker at the C runtime, and surface the error a tail buries
Two things the last matrix taught, one fixed and one made diagnosable. 1. cmake/linux/gcc still died in its own compiler probe with `cannot find crt1.o`. My previous attempt — only pass `--sysroot` when crt1.o is under it — was correct and changed nothing, and the CI log says why: neither `--sysroot=` nor the STATUS line the other branch would have printed appears, so `registry/subos/default` does not exist on the runner at all. The payload gcc then fell back to a built-in prefix that is not there either. The stable anchor is the PACKAGE: `subos/default/lib/crt1.o` is a symlink into `xpkgs/xim-x-glibc/<ver>/lib/`, and xpkgs is where the compiler itself was found, so it exists by construction — `xim-x-glibc` is present in the failing job's own log. `--sysroot` is gone entirely; `-B` and `-L` ADD to the search instead of replacing it, which is the property this needed from the start. HONEST LIMIT: I could not reproduce the runner's failure locally. A fake MCPP_HOME with no subos reproduces the missing directory, but this machine's payload gcc finds crt1.o by itself, so the pre-fix code configures there too. What is verified is that the arm still configures with and without a subos, and that the directory the fix names exists on the runner. Whether that is sufficient, CI will say. 2. `xmake/clang` failed with `seed build exited 255` and the captured tail was twenty lines of `generating.module.deps`. A tail is the wrong shape for a tool that prints a line per translation unit: the cause is printed once, hundreds of lines earlier. That cell cost a full matrix cycle and taught nothing. Failures now report the lines that LOOK like a cause, pulled from anywhere in the log, with the tail after them as context. A keyword sieve rather than per-engine parsing — approximately right for four different diagnostic formats beats exactly right until one changes its wording. Verified against a fake engine that prints an error and then 300 progress lines: the error is surfaced, where before only progress was.
1 parent f1add74 commit 5cc4e34

3 files changed

Lines changed: 97 additions & 25 deletions

File tree

bench/projects/common/cmake/hermetic_payload.cmake

Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -259,40 +259,55 @@ function(bench_hermetic_payload_preproject)
259259
if(_binutils)
260260
string(APPEND _add " -B${_binutils}/bin")
261261
endif()
262-
# ⚠️ THE TEST IS "DOES IT HOLD A C RUNTIME", NOT "DOES THE DIRECTORY EXIST".
262+
# ── The C runtime: FIND crt1.o, then ADD its directory. No --sysroot. ──────
263263
#
264-
# `--sysroot` does not ADD a search path, it REPLACES gcc's default one. Point
265-
# it at a directory that exists but has no libc and every link dies at the
266-
# first object:
264+
# ⚠️ `--sysroot` does not ADD a search path, it REPLACES gcc's default one, so
265+
# pointing it anywhere that lacks a libc removes the C runtime entirely:
267266
#
268267
# ld: cannot find crt1.o: No such file or directory
269268
# ld: cannot find crti.o: No such file or directory
270269
# ld: cannot find -lm: No such file or directory
271270
#
272-
# which is a message about the C runtime and says nothing about the flag that
273-
# caused it. `IS_DIRECTORY` passed on the runners because mcpp creates
274-
# `registry/subos/default` whether or not anything has been installed into it,
275-
# so all five cmake cells of the linux/gcc bench failed at cmake's own compiler
276-
# probe — before a single line of the project was configured.
271+
# — a message about the C runtime that names neither the flag nor the cause.
272+
# All five cmake cells of the linux/gcc bench died there, inside cmake's own
273+
# compiler probe, before a line of the project was configured.
277274
#
278-
# This repository has been bitten by the same flag before (an `install()`
279-
# source package lost its libc headers exactly this way). Existence was the
280-
# wrong predicate then too.
281-
set(_crt "")
282-
foreach(_d lib lib64 usr/lib usr/lib64 usr/lib/x86_64-linux-gnu)
283-
if(EXISTS "${_sysroot}/${_d}/crt1.o")
284-
set(_crt "${_sysroot}/${_d}/crt1.o")
285-
break()
275+
# TWO WRONG ANSWERS PRECEDED THIS ONE, and both looked right:
276+
# * `IS_DIRECTORY "${_sysroot}"` — true on the runners, because that path is
277+
# created whether or not anything was installed into it.
278+
# * then "only pass it when crt1.o is under the sysroot" — correct as far as
279+
# it went, and it changed nothing: on the runner that directory does not
280+
# exist at all, so neither branch ran, no --sysroot was passed, and the
281+
# payload gcc fell back to a built-in prefix that is not there either.
282+
# Diagnosed from the CI log by the ABSENCE of both `--sysroot=` on the
283+
# command line and the STATUS line the second branch would have printed.
284+
#
285+
# The stable anchor is the PACKAGE. `subos/default/lib/crt1.o` is a symlink
286+
# into `xpkgs/xim-x-glibc/<ver>/lib/`, and xpkgs is where the compiler itself
287+
# was found, so it exists by construction. `-B` (startup files) and `-L`
288+
# (`-lm`) ADD to the search rather than replacing it, which is the property
289+
# this needed all along.
290+
set(_crtdir "")
291+
bench_newest_package("${_xpkgs}" "xim-x-glibc" _glibc)
292+
foreach(_root "${_sysroot}" "${_glibc}")
293+
if(_crtdir OR NOT _root)
294+
continue()
286295
endif()
296+
foreach(_d lib lib64 usr/lib usr/lib64 usr/lib/x86_64-linux-gnu)
297+
if(EXISTS "${_root}/${_d}/crt1.o")
298+
set(_crtdir "${_root}/${_d}")
299+
break()
300+
endif()
301+
endforeach()
287302
endforeach()
288-
if(_crt)
289-
string(APPEND _add " --sysroot=${_sysroot}")
290-
elseif(IS_DIRECTORY "${_sysroot}")
291-
# Say so. A payload build that silently falls back to the host's libc is a
292-
# different measurement from the one this file claims to set up, and the
293-
# only way to notice is if it announces itself.
294-
message(STATUS "bench: ${_sysroot} has no crt1.o — NOT passing --sysroot; "
295-
"this arm links against the host C runtime")
303+
if(_crtdir)
304+
string(APPEND _add " -B${_crtdir} -L${_crtdir}")
305+
else()
306+
# Say so. Falling back to the host's C runtime is a different measurement
307+
# from the one this file claims to set up, and the only way anyone notices
308+
# is if it announces itself.
309+
message(STATUS "bench: no crt1.o under ${_sysroot} or ${_glibc} — this arm "
310+
"links against the host C runtime")
296311
endif()
297312
if(_add STREQUAL "")
298313
return()

bench/src/platform.cppm

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,40 @@ inline bool log_mentions(const std::filesystem::path& p,
107107
return false;
108108
}
109109

110+
// The lines anywhere in `p` that look like a cause, not a progress report.
111+
//
112+
// A tail cannot answer "why did this fail" for a tool that prints a line per
113+
// translation unit: the error scrolled past hundreds of lines ago and the last
114+
// 20 are all `[ 2%]: generating.module.deps ...`. That is exactly how an
115+
// `xmake exited 255` cell reached CI with nothing to diagnose it by.
116+
//
117+
// Deliberately a keyword sieve rather than per-engine parsing: every engine
118+
// here is a different program with a different diagnostic format, and one that
119+
// is merely APPROXIMATELY right on all of them beats four that are exactly
120+
// right until a tool changes its wording. False positives cost a line of noise;
121+
// a false negative costs a matrix cycle.
122+
inline std::string log_grep(const std::filesystem::path& p,
123+
std::initializer_list<std::string_view> markers,
124+
std::size_t max = 12) {
125+
std::ifstream in(p, std::ios::binary);
126+
if (!in) return {};
127+
std::string out, line;
128+
std::size_t kept = 0;
129+
while (kept < max && std::getline(in, line)) {
130+
if (!line.empty() && line.back() == '\r') line.pop_back();
131+
bool hit = false;
132+
for (const auto m : markers)
133+
if (line.find(m) != std::string::npos) { hit = true; break; }
134+
if (!hit) continue;
135+
// Long lines here are usually a whole compiler command line; the cause
136+
// is at the front of them.
137+
if (line.size() > 400) { line.resize(400); line += ""; }
138+
out += " "; out += line; out += '\n';
139+
++kept;
140+
}
141+
return out;
142+
}
143+
110144
inline std::string tail_of(const std::filesystem::path& p, std::size_t lines = 20) {
111145
std::ifstream in(p, std::ios::binary);
112146
if (!in) return {};

bench/src/runner.cppm

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,29 @@ public:
293293
// that crash is still undiagnosed.
294294
const auto crashed = platform::log_mentions(
295295
job.log_path, {"PLEASE submit a bug report", "Stack dump"});
296+
297+
// ⚠️ A TAIL IS THE WRONG SHAPE WHEN THE TOOL IS CHATTY. Every build
298+
// engine here prints a progress line per translation unit, so 20
299+
// lines of tail is 20 lines of `generating.module.deps ...` and the
300+
// error that actually stopped it — printed once, hundreds of lines
301+
// earlier — is gone. That is not hypothetical: `xmake/clang` failed
302+
// with `seed build exited 255` and the captured tail contained
303+
// nothing but progress, so the cell could not be diagnosed from CI
304+
// at all and cost a full matrix cycle to learn nothing.
305+
//
306+
// So the lines that LOOK like an error are pulled out first, from
307+
// anywhere in the file, and the tail follows as context. Cheap, and
308+
// it is the difference between "exited 255" and a cause.
309+
if (const auto why = platform::log_grep(
310+
job.log_path,
311+
{"error:", "error :", "ERROR:", " error ", "fatal",
312+
"not found", "No such file", "cannot find", "undefined",
313+
"failed to", "Assertion", "abort"},
314+
/*max=*/12);
315+
!why.empty())
316+
report(std::format("--- error lines from {} ---\n{}",
317+
job.log_path.filename().string(), why));
318+
296319
if (const auto tail = platform::tail_of(job.log_path, crashed ? 80 : 20);
297320
!tail.empty())
298321
report(std::format("--- last lines of {} ---\n{}",

0 commit comments

Comments
 (0)