diff --git a/tools/gater_lane.py b/tools/gater_lane.py index 1648dbac4..88cb42648 100644 --- a/tools/gater_lane.py +++ b/tools/gater_lane.py @@ -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 + # `\t: :: `; 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) diff --git a/tools/parallel_gate.py b/tools/parallel_gate.py index 68762314b..05b6e2912 100644 --- a/tools/parallel_gate.py +++ b/tools/parallel_gate.py @@ -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 `.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 `\t: `. The CLASS alone is not enough to decide whether the + # worktree was blind — `CC1-FAIL(no-diagnostic)` is the blind signature, `CC1-FAIL: :: + # ` 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])}