mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-26 13:33:34 -04:00
fix(gate): carry the per-function verdicts out of the worktree, and gate the in-tree retry on them
The first version of this parsed 'failed by class:' from the worker's stdout and was INERT: the worker is gate_stage, which never prints that line (harvest_verify does, one level down). classes came back empty for all 17 binaries of a batch and the retry gate that consumed it fired ZERO times — a field that is always empty makes its consumer a silent no-op (R54). Verified the claim only after re-reading the log; correcting it here. Now parallel_gate copies harvest_verify's <stem>.classified.txt out of the worktree before teardown (it lives in the worktree's own .run/, which is not symlinked and dies with it) and derives the class summary from those rows. That also PRESERVES the verdict layer, which until now survived only as a side effect of gater_lane re-running the whole binary in-tree afterwards (R47). gater_lane judges the retry on the rows: a class with no per-function diagnostic is the blind-worktree signature; anything cc1 named is a real compile error and the serial rebuild would only reproduce it. Verified live on ov_SC07_000: 'NOT retrying in-tree' fired, and the verdict row landed at .run/gate_lane/ov_SC07_000.pgate.classified.txt.
This commit is contained in:
+7
-2
@@ -399,9 +399,14 @@ def main():
|
||||
# worktree was not blind: the draft genuinely does not compile in its TU, and the in-tree
|
||||
# retry is a serial full-binary build that reproduces the same error. S69 ran 22 of those
|
||||
# back-to-back and banked 0 — ~20 minutes of a lane whose whole design goal is parallelism.
|
||||
# Judge on the VERDICT ROWS the worker actually wrote, not on a summary string. A row reads
|
||||
# `<fn>\t<CLASS>: <file>:<line>: <message>`; the blind-worktree signature is a class with NO
|
||||
# per-function diagnostic (`CC1-FAIL(no-diagnostic)`), which is precisely what a missing
|
||||
# generated header or an unstageable link input produces. Anything cc1 named is real.
|
||||
rows = r.get("verdicts") or []
|
||||
real = [row for row in rows if "no-diagnostic" not in row and ":" in row.split("\t", 1)[-1]]
|
||||
cls = (r.get("classes") or "")
|
||||
real = [c for c in cls.split() if c and "no-diagnostic" not in c]
|
||||
if real:
|
||||
if rows and real:
|
||||
print("[gater] %s: worktree FAILED %d/%d with real cc1 diagnostics (%s) — NOT retrying "
|
||||
"in-tree; the worktree was not blind, the drafts do not compile in their TU"
|
||||
% (b, tail["failed"], tail.get("drafts", 0), cls), flush=True)
|
||||
|
||||
+35
-5
@@ -318,13 +318,43 @@ def gate_one(idx, pin, job):
|
||||
# printed EARLIER by harvest_verify, never survived. gater_lane's in-tree retry therefore
|
||||
# could not tell "the worktree was blind" from "cc1 emitted a real diagnostic naming the
|
||||
# function", and retried all 22 binaries serially for nothing (measured S69: ~20 min).
|
||||
cls = ""
|
||||
for ln in (r.stdout or "").splitlines():
|
||||
if "failed by class:" in ln:
|
||||
cls = ln.split("failed by class:", 1)[1].strip()
|
||||
# THE VERDICT LAYER LIVES IN THE WORKTREE'S OWN .run/ AND DIES WITH IT (P31 S69, R47).
|
||||
# `harvest_verify` writes `<stem>.classified.txt` — ONE ROW PER FUNCTION with the exact cc1
|
||||
# diagnostic. `.run/` is not symlinked into a worktree, so those rows were lost and survived
|
||||
# only because gater_lane re-ran the whole binary IN-TREE afterwards, purely as a side effect.
|
||||
# Copy them out, and derive the class summary FROM THEM.
|
||||
#
|
||||
# THE FIRST ATTEMPT AT THIS PARSED `failed by class:` OUT OF THE WORKER'S STDOUT AND WAS
|
||||
# INERT: the worker is `gate_stage`, which does not print that line — harvest_verify does,
|
||||
# one level down. `classes` came back empty for all 17 binaries of the batch and the retry
|
||||
# gate that consumed it never fired once. A field that is always empty makes its consumer a
|
||||
# no-op, silently (R54); reading the artifact the tool actually writes cannot drift that way.
|
||||
verdicts, cls = [], ""
|
||||
wrun = os.path.join(wt, ".run")
|
||||
if os.path.isdir(wrun):
|
||||
for name in sorted(os.listdir(wrun)):
|
||||
if name.startswith("harvest_failed") and name.endswith(".classified.txt"):
|
||||
try:
|
||||
rows = [ln.rstrip("\n") for ln in open(os.path.join(wrun, name)) if ln.strip()]
|
||||
except OSError:
|
||||
continue
|
||||
verdicts.extend(rows)
|
||||
try:
|
||||
shutil.copy(os.path.join(wrun, name),
|
||||
os.path.join(REPO, ".run/gate_lane/%s.pgate.classified.txt" % binary))
|
||||
except OSError:
|
||||
pass
|
||||
# A row is `<fn>\t<CLASS>: <diagnostic>`. The CLASS alone is not enough to decide whether the
|
||||
# worktree was blind — `CC1-FAIL(no-diagnostic)` is the blind signature, `CC1-FAIL: <file>:<line>:
|
||||
# <message>` is a real compile error — so keep the whole row and let the consumer judge.
|
||||
classes = []
|
||||
for row in verdicts:
|
||||
part = row.split("\t", 1)[-1]
|
||||
classes.append(part.split(":", 1)[0].strip())
|
||||
cls = " ".join(sorted(set(classes)))
|
||||
return {"binary": binary, "banked": banked, "files": files, "ovl": ovl,
|
||||
"secs": round(time.time() - t0, 1),
|
||||
"missing_generated": missing, "classes": cls,
|
||||
"missing_generated": missing, "classes": cls, "verdicts": verdicts,
|
||||
"rc": r.returncode, "tail": (r.stdout or r.stderr)[-200:] if not banked else ""}
|
||||
except Exception as e:
|
||||
return {"binary": binary, "banked": [], "error": "%s: %s" % (type(e).__name__, str(e)[:160])}
|
||||
|
||||
Reference in New Issue
Block a user