4"""Gate: bench users take the lock, and programmers inspect their image first.
6Why this is a gate and not a convention
7---------------------------------------
8There is exactly ONE EK-RA8D2, and the owner, another maintainer, ~20
9concurrent agents over ssh and a nightly CI job all reach it. Before #497
10nothing serialised that at all -- no flock, no lockfile, no PID file anywhere
11in the tree. ``scripts/hil/bench.sh`` fixed the mechanism; this fixes the
12enforcement, because a lock nobody is forced to take is decoration.
14The set of bench-touching entry points is DERIVED, never listed. Anything that
15invokes ``JLinkExe``, ``JLinkGDBServer``, ``rfp-cli``, ``openocd``,
16``esptool``, ``uhubctl`` or ``tapo_control.py``, opens a ``/dev/ttyACM*``
17console, or shells into the bench host over ``ssh $PI_HOST``, is a bench
18operation by construction. A hand-maintained list would go stale the first time
19somebody added a script -- exactly the way the deleted HIL suite runner held a
20small static table against a much larger derived catalogue.
24- the file sources ``lib/bench_lock.sh`` AND calls ``ra8_bench_require`` (or
25 the ``_recovery`` form), or
26- the file drives the hardware through ``bench.sh run --``, which is the same
27 hold in wrapper form, or
28- the file is on :data:`CARVE_OUTS` **with a stated reason**. A carve-out
29 without a reason is a rejected carve-out; the reason is what a reviewer
32Matching is invocation-shaped, not substring-shaped: a tool name has to appear
33in COMMAND position (start of a line, or after a pipe, ``&&``, ``;``, ``sudo``,
34``exec`` and friends). Every file in this tree that mentions ``JLinkExe`` in
35prose -- and most of them do, because the comments are good -- would otherwise
36be flagged, and a gate that cries wolf gets a blanket carve-out list within a
39Every script that actually programs an image has a second obligation:
40``ra8_preflash_guard`` must inspect the full image before ``loadfile``, GDB
41``load``, OpenOCD ``program`` or Ozone ``File.Open`` can write it. That call-site
42set is derived from the programming commands rather than hand-maintained.
46This repo's dominant tooling defect is a checker that quietly stopped matching
47and reported a clean tree forever; it has happened at least four times.
48``--selftest`` therefore asserts three things, not one:
501. a synthetic script that shells out to JLinkExe with no guard must FAIL;
512. the same script with the guard call must PASS, as must one that only
52 mentions the tool in a comment or an echo;
533. a DISCOVERY FLOOR -- the live scan must still find at least
54 :data:`DISCOVERY_FLOOR` bench-touching files, and must still find every
55 file in :data:`MUST_DISCOVER`. A regex that stopped matching turns those
56 red instead of turning the tree green.
60 check_bench_lock.py # gate
61 check_bench_lock.py --selftest # prove the detector, both ways, plus floor
62 check_bench_lock.py --list # what it considers bench-touching, and why
64Exit 0 clean, 1 on a violation or a failing selftest, 2 when the tree cannot
68from __future__
import annotations
75from pathlib
import Path
77REPO_ROOT = Path(__file__).resolve().parents[2]
86SCANNED_SUFFIXES = (
".sh",
".py",
".mk",
".just",
".yml",
".yaml")
87SCANNED_BASENAMES = (
"justfile",
"Justfile")
92 "apps/shared_libs/third_party/",
94 "coprocessor/esp32c6/esp-hosted-mcu/",
108 "ra8-hil-privileged",
116 r"(?:sudo(?:\s+-n)?|nohup|setsid|exec|time|timeout\s+\S+|env"
117 r"|python3?\s+-m|bash|sh)"
122_CMD_START =
r"(?:^|[|;&`]|\$\(|&&|\|\||\bthen\b|\bdo\b|\bif\b|\belif\b|\bcmd:|--\s)"
125def _command_word_re(tool: str) -> re.Pattern[str]:
126 """A regex matching *tool* in command position, allowing a path prefix."""
127 esc = re.escape(tool)
128 return re.compile(rf
"{_CMD_START}\s*!?\s*(?:{_PREFIXES}\s+)*(?:[\w./$'\"{{}}-]*/)?{esc}\b")
131TOOL_RES = tuple((t, _command_word_re(t))
for t
in BENCH_TOOLS)
135PRESENCE_RE = re.compile(
136 r"\b(?:command\s+-v|which|type\s+-?\w?)\s+\S*(?:"
137 +
"|".join(re.escape(t)
for t
in BENCH_TOOLS)
143TTY_OPEN_RE = re.compile(
144 r"(?:\b(?:stty|cat|dd|fuser|tee|screen|minicom|picocom)\b[^\n]*|[<>]\s*)"
150SSH_PI_RE = re.compile(
r"\bssh\b[^\n]*\$\{?PI_HOST\b|\bssh\b[^\n]*\"\$PI\"")
153GUARD_RE = re.compile(
154 r"\bra8_bench_require(?:_recovery)?\b"
155 r"|\bbench\.sh\s+(?:run|acquire)\b"
156 r"|\bbench_host\.sh\s+hold\b"
161PROGRAM_RES: tuple[tuple[str, re.Pattern[str]], ...] = (
162 (
"J-Link loadfile", re.compile(
r"^\s*loadfile\b", re.MULTILINE)),
163 (
"GDB load", re.compile(
r"(?:^|\s)-ex\s+[\"']load[\"']")),
164 (
"OpenOCD program", re.compile(
r"(?:^|\s)-c\s+[\"']program\b")),
165 (
"Ozone File.Open", re.compile(
r"^\s*File\.Open\s*\(", re.MULTILINE)),
166 (
"Renesas programmer write", re.compile(
r"\brfp-cli\b[^\n]*(?:-write|-program)\b")),
168PREFLASH_GUARD_RE = re.compile(
r"\bra8_preflash_guard\b")
174CARVE_OUTS: dict[str, str] = {
176 "scripts/hil/bench.sh":
"IS the lock CLI -- it cannot take a lock to take a lock",
177 "scripts/hil/lib/bench_client.sh": (
178 "IS the lock's client transport; its ssh to the bench host is how a "
179 "hold is taken in the first place"
184 "scripts/hil/find_jlink.sh": (
185 "bootstrap -- enumerates attached probes to populate .env, before the "
186 "rig is configured at all; it never connects to the target"
189 "scripts/hil/dlm_reset_local.sh": (
190 "the board has been physically moved off the rig onto this "
191 "workstation, so the bench host is not in the path; requiring a lock "
192 "held on that host would make recovery impossible exactly when the "
193 "rig is the thing that is broken"
195 "scripts/checks/check_no_antirecovery.py": (
196 "a checker whose PATTERNS name the tools; it invokes nothing"
198 "scripts/checks/check_hil_privilege_boundary.py": (
199 "an AST/static policy proof whose exact expected argv and negative fixtures "
200 "name uhubctl; it executes no hardware command"
202 "scripts/checks/check_python_lock_policy.py": (
203 "the dependency consumer catalogue names tapo_control.py as data; it invokes nothing"
205 "scripts/checks/hil_convergence_safety_roles.py": (
206 "the role checker compares the exact J-Link health-check command as data; "
209 "scripts/checks/lint_coverage_rules.py": (
210 "the lint ownership registry names privileged helper artefacts as data; it invokes nothing"
212 "scripts/checks/check_shell_just_invocations.py": (
213 "the sensitive-boundary registry records the OBSOLETE direct-ssh supervisor line as a "
214 "must-be-absent literal, so the only bench command in the file is the one it forbids; "
215 "it invokes nothing but git ls-files"
217 "scripts/checks/check_hil_rig_contract.py": (
218 "its hermetic selftest harness shadows ssh and scp with shell functions that assert "
219 "argument shape and print a marker, so the fixture proves PI_HOST survives sourcing "
220 "without a bench in the path; it must run in CI where no bench exists, so it cannot "
223 "infra/ansible/roles/dev_box/files/ra8-hil-privileged.py": (
224 "the fixed root delegate is reached only by guarded HIL callers; guarding the "
225 "delegate again would deadlock the caller's already-held bench lease"
227 "infra/ansible/roles/hil_bench/tasks/transaction.yml": (
228 "the fleet dispatcher holds the authenticated whole-play bench lease, and the "
229 "convergence safety gate rejects direct playbook/task-selector bypasses"
231 "scripts/hil/lib/privileged_helper.sh": (
232 "the library performs only read-only helper identity probes itself; mutating "
233 "helper calls remain in derived, guarded HIL entrypoints"
241 "scripts/hil/bench_contention.sh": (
242 "drives the contention EXPERIMENT; holding the lock would prevent the "
243 "contention it measures. It never touches hardware -- the actors it "
244 "launches each take the lock through the ordinary guard"
251 "scripts/hil/bench_unguarded_probe.sh": (
252 "IS the negative control -- it proves the bench witness can see two "
253 "machines on the board at once, which is what makes a clean guarded "
254 "run mean anything. Read-only, gated behind "
255 "RA8_BENCH_NEGATIVE_CONTROL=1, and refuses while the bench is held"
265 "scripts/hil/flash.sh",
266 "scripts/hil/run_direct.sh",
267 "scripts/hil/recover.sh",
268 "scripts/hil/erase.sh",
269 "scripts/hil/dlm_reset.sh",
270 "scripts/hil/flash_retry.sh",
271 "scripts/hil/probe.sh",
272 "scripts/hil/jlink_memprobe.sh",
273 "scripts/hil/ppps.sh",
274 "scripts/dev/flash.sh",
275 "scripts/dev/debug.sh",
276 "scripts/dev/openocd_flash.sh",
277 "coprocessor/esp32c6/flash.sh",
280PREFLASH_DISCOVERY_FLOOR = 10
281PREFLASH_MUST_DISCOVER = (
282 "scripts/dev/flash.sh",
283 "scripts/dev/debug.sh",
284 "scripts/dev/openocd_flash.sh",
285 "scripts/dev/openocd_debug.sh",
286 "scripts/dev/ozone.sh",
287 "scripts/hil/run_direct.sh",
288 "scripts/hil/eth_tcp.sh",
289 "scripts/hil/rtt_scrape.sh",
290 "scripts/hil/exit_low_power.sh",
294def _tracked_files() -> list[str]:
295 """Every authored path, including new non-ignored worktree files."""
296 git = shutil.which(
"git")
298 sys.stderr.write(
"check_bench_lock.py: git is not installed\n")
299 raise SystemExit(EXIT_CONFIG)
300 out = subprocess.run(
306 "--exclude-standard",
313 if out.returncode != 0:
314 sys.stderr.write(
"check_bench_lock.py: `git ls-files` failed\n")
315 raise SystemExit(EXIT_CONFIG)
316 return [line
for line
in out.stdout.splitlines()
if line]
319def _in_scope(rel: str) -> bool:
320 if any(frag
in rel
for frag
in EXCLUDE_FRAGMENTS):
322 name = rel.rsplit(
"/", 1)[-1]
323 return name
in SCANNED_BASENAMES
or rel.endswith(SCANNED_SUFFIXES)
326def _strip_comments(text: str) -> list[tuple[int, str]]:
327 """Return (1-based line number, code) for lines that are not pure comments.
329 Only whole-line comments are dropped. A trailing comment is left alone --
330 stripping it properly needs a shell parser, and the command-position match
331 below does not fire on prose anyway.
333 kept: list[tuple[int, str]] = []
334 for n, line
in enumerate(text.splitlines(), start=1):
335 stripped = line.lstrip()
336 if stripped.startswith(
"#"):
338 kept.append((n, line))
342def bench_touches(text: str) -> list[tuple[int, str]]:
343 """Every (line number, reason) at which *text* drives the bench."""
344 hits: list[tuple[int, str]] = []
345 for n, line
in _strip_comments(text):
346 if PRESENCE_RE.search(line):
348 for tool, rx
in TOOL_RES:
350 hits.append((n, f
"invokes {tool}"))
353 if TTY_OPEN_RE.search(line):
354 hits.append((n,
"opens a /dev/ttyACM* console"))
355 elif SSH_PI_RE.search(line):
356 hits.append((n,
"runs a command on the bench host over ssh"))
360def is_guarded(text: str) -> bool:
361 """True when *text* takes the bench lock in any of its accepted forms."""
362 return any(GUARD_RE.search(line)
for _, line
in _strip_comments(text))
365def programming_touches(text: str) -> list[tuple[int, str]]:
366 """Return programming actions found outside pure-comment lines."""
367 hits: list[tuple[int, str]] = []
368 for line_no, line
in _strip_comments(text):
369 for label, pattern
in PROGRAM_RES:
370 if pattern.search(line):
371 hits.append((line_no, label))
376def has_preflash_guard(text: str) -> bool:
377 """True when a caller invokes the anti-recovery image guard."""
378 return any(PREFLASH_GUARD_RE.search(line)
for _, line
in _strip_comments(text))
381def scan(files: list[str]) -> tuple[dict[str, list[tuple[int, str]]], list[str]]:
382 """Return {rel: hits} for every bench-touching file, and the unguarded ones."""
383 touching: dict[str, list[tuple[int, str]]] = {}
384 unguarded: list[str] = []
386 if not _in_scope(rel):
388 path = REPO_ROOT / rel
390 text = path.read_text(encoding=
"utf-8", errors=
"replace")
393 hits = bench_touches(text)
397 if rel
in CARVE_OUTS:
399 if not is_guarded(text):
400 unguarded.append(rel)
401 return touching, unguarded
404def scan_preflash(files: list[str]) -> tuple[dict[str, list[tuple[int, str]]], list[str]]:
405 """Return every physical programmer and those missing the image guard."""
406 programming: dict[str, list[tuple[int, str]]] = {}
407 unsafe: list[str] = []
409 if rel ==
"scripts/checks/check_bench_lock.py":
411 if not rel.endswith((
".sh",
".just",
".yml",
".yaml")):
413 if not _in_scope(rel):
416 text = (REPO_ROOT / rel).read_text(encoding=
"utf-8", errors=
"replace")
419 hits = programming_touches(text)
422 programming[rel] = hits
423 if not has_preflash_guard(text):
425 return programming, unsafe
432_FIRE_UNGUARDED =
"""#!/usr/bin/env bash
435JLinkExe -nogui 1 -CommanderScript /tmp/x.jlink
438_QUIET_GUARDED =
"""#!/usr/bin/env bash
440source "$_hil_dir/lib/bench_lock.sh"
441ra8_bench_require "flash $1" || exit $?
442JLinkExe -nogui 1 -CommanderScript /tmp/x.jlink
445_QUIET_RECOVERY =
"""#!/usr/bin/env bash
446source "$_hil_dir/lib/bench_lock.sh"
447ra8_bench_require_recovery "erase" || exit $?
448rfp-cli -d ra -erase-chip
451_QUIET_WRAPPED =
"""#!/usr/bin/env bash
452bash scripts/hil/bench.sh run --intent "probe" -- JLinkExe -nogui 1
455_QUIET_PROSE =
"""#!/usr/bin/env bash
456# JLinkExe is what scripts/dev/flash.sh runs; rfp-cli does the DLM reset.
457echo " flash runs JLinkExe via scripts/dev/flash.sh"
458bash "$ROOT/scripts/dev/flash.sh" "$HEX"
461_QUIET_TTY_PRESENCE =
"""#!/usr/bin/env bash
462for _d in /dev/ttyACM*; do
463 [ -e "$_d" ] && return 0
467_FIRE_TTY_OPEN =
"""#!/usr/bin/env bash
468stty -F /dev/ttyACM0 115200 raw -echo
469cat /dev/ttyACM0 > /tmp/log
472_FIRE_SSH =
"""#!/usr/bin/env bash
473ssh "$PI_HOST" "rfp-cli -d ra -erase-chip"
476_FIRE_SUDO_PREFIX =
"""#!/usr/bin/env bash
477sudo -n uhubctl -l 2-1.3 -p 2 -a off
480_FIRE_PRIVILEGED_HELPER =
"""#!/usr/bin/env bash
481sudo -n -- /usr/local/libexec/ra8-hil-privileged usb-root-cycle
484_QUIET_PRIVILEGED_HELPER =
"""#!/usr/bin/env bash
485source "$ROOT/scripts/hil/lib/bench_lock.sh"
486ra8_bench_require_recovery "cycle" || exit $?
487sudo -n -- /usr/local/libexec/ra8-hil-privileged usb-root-cycle
490_FIRE_PROGRAM_UNGUARDED =
"""#!/usr/bin/env bash
491cat >/tmp/flash.jlink <<EOF
494JLinkExe -commanderscript /tmp/flash.jlink
497_QUIET_PROGRAM_GUARDED =
"""#!/usr/bin/env bash
498source "$ROOT/scripts/hil/lib/preflash_guard.sh"
499ra8_preflash_guard "$HEX" || exit $?
500openocd -f board.cfg -c "program $HEX verify reset exit"
503_QUIET_READ_ONLY_DEBUG =
"""#!/usr/bin/env bash
504JLinkGDBServer -device R7KA8D2KF_CPU0 -port 2331
507SELFTEST_CASES: tuple[tuple[str, str, bool, bool, str], ...] = (
509 (
"fire_unguarded.sh", _FIRE_UNGUARDED,
True,
False,
"a bare JLinkExe call is caught"),
510 (
"quiet_guarded.sh", _QUIET_GUARDED,
True,
True,
"ra8_bench_require satisfies it"),
516 "the recovery guard satisfies it",
518 (
"quiet_wrapped.sh", _QUIET_WRAPPED,
True,
True,
"`bench.sh run --` satisfies it"),
519 (
"quiet_prose.sh", _QUIET_PROSE,
False,
False,
"prose and echo text do not fire"),
521 "quiet_tty_presence.sh",
525 "a /dev/ttyACM* presence test is not an open",
527 (
"fire_tty_open.sh", _FIRE_TTY_OPEN,
True,
False,
"opening a console is caught"),
528 (
"fire_ssh.sh", _FIRE_SSH,
True,
False,
"ssh onto the bench host is caught"),
530 "fire_sudo_prefix.sh",
534 "a sudo-prefixed uhubctl is caught",
537 "fire_privileged_helper.sh",
538 _FIRE_PRIVILEGED_HELPER,
541 "an unguarded privileged helper mutation is caught",
544 "quiet_privileged_helper.sh",
545 _QUIET_PRIVILEGED_HELPER,
548 "the recovery guard covers a privileged helper mutation",
553def _selftest_detector() -> list[str]:
554 failures: list[str] = []
555 with tempfile.TemporaryDirectory():
556 for name, body, want_hit, want_guard, label
in SELFTEST_CASES:
557 hits = bench_touches(body)
558 if bool(hits) != want_hit:
559 verb =
"did not fire" if want_hit
else "fired"
560 failures.append(f
" detector {verb} (unexpected): {name} -- {label}")
564 if is_guarded(body) != want_guard:
565 verb =
"not recognised as guarded" if want_guard
else "wrongly guarded"
566 failures.append(f
" guard {verb}: {name} -- {label}")
570def _selftest_floor(touching: dict[str, list[tuple[int, str]]]) -> list[str]:
571 failures: list[str] = []
572 if len(touching) < DISCOVERY_FLOOR:
574 f
" DISCOVERY FLOOR: found {len(touching)} bench-touching file(s), "
575 f
"floor is {DISCOVERY_FLOOR}. The detector has stopped matching; "
576 f
"a clean tree is NOT the explanation."
578 missing = [rel
for rel
in MUST_DISCOVER
if rel
not in touching]
581 " MUST_DISCOVER: these drive the bench and were not detected -- "
582 "the detector is broken, or they were renamed:"
584 failures.extend(f
" {rel}" for rel
in missing)
588def _selftest_preflash_detector() -> list[str]:
589 failures: list[str] = []
592 "unguarded programmer is caught",
593 _FIRE_PROGRAM_UNGUARDED,
598 "guarded programmer passes",
599 _QUIET_PROGRAM_GUARDED,
604 "read-only debugger needs no image guard",
605 _QUIET_READ_ONLY_DEBUG,
610 for label, body, want_programmer, want_guard
in cases:
611 if bool(programming_touches(body)) != want_programmer:
612 failures.append(f
" preflash detector mismatch: {label}")
613 if has_preflash_guard(body) != want_guard:
614 failures.append(f
" preflash guard mismatch: {label}")
618def _selftest_preflash_floor(
619 programming: dict[str, list[tuple[int, str]]],
621 failures: list[str] = []
622 if len(programming) < PREFLASH_DISCOVERY_FLOOR:
624 f
" PREFLASH DISCOVERY FLOOR: found {len(programming)} programmer(s), "
625 f
"floor is {PREFLASH_DISCOVERY_FLOOR}"
627 missing = [rel
for rel
in PREFLASH_MUST_DISCOVER
if rel
not in programming]
629 failures.append(
" PREFLASH MUST_DISCOVER missed physical programmer(s):")
630 failures.extend(f
" {rel}" for rel
in missing)
634def run_selftest() -> int:
635 """Prove the detector fires, stays quiet, and still discovers the tree."""
636 touching, _ = scan(_tracked_files())
637 programming, _ = scan_preflash(_tracked_files())
640 + _selftest_floor(touching)
641 + _selftest_preflash_detector()
642 + _selftest_preflash_floor(programming)
645 sys.stderr.write(
"check_bench_lock.py: --selftest FAILED:\n")
646 sys.stderr.write(
"\n".join(failures) +
"\n")
649 f
"check_bench_lock.py: --selftest OK "
650 f
"({len(SELFTEST_CASES)} detector cases both directions; live scan finds "
651 f
"{len(touching)} bench-touching file(s), floor {DISCOVERY_FLOOR}, "
652 f
"{len(MUST_DISCOVER)} named files all present; {len(programming)} physical "
653 f
"programmer(s), floor {PREFLASH_DISCOVERY_FLOOR})."
659 touching: dict[str, list[tuple[int, str]]],
660 unguarded: list[str],
661 programming: dict[str, list[tuple[int, str]]],
662 unsafe_programmers: list[str],
664 """Print the derived bench and programming call-site inventory."""
665 for rel
in sorted(touching):
666 mark =
"CARVE-OUT" if rel
in CARVE_OUTS
else "guarded "
669 print(f
"{mark} {rel}")
670 for line_no, why
in touching[rel][:3]:
671 print(f
" line {line_no}: {why}")
672 if rel
in CARVE_OUTS:
673 print(f
" reason: {CARVE_OUTS[rel]}")
674 print(
"\nphysical programmers:")
675 for rel
in sorted(programming):
676 mark =
"guarded" if rel
not in unsafe_programmers
else "UNGUARDED"
677 print(f
"{mark:9} {rel}")
678 for line_no, why
in programming[rel][:3]:
679 print(f
" line {line_no}: {why}")
682def _report_bench_failures(
683 touching: dict[str, list[tuple[int, str]]], unguarded: list[str]
685 """Explain unguarded physical-bench entry points."""
688 sys.stderr.write(
"check_bench_lock.py: file(s) drive the bench without taking the lock:\n")
689 for rel
in sorted(unguarded):
690 sys.stderr.write(f
"\n {rel}\n")
691 for line_no, why
in touching[rel][:4]:
692 sys.stderr.write(f
" line {line_no}: {why}\n")
694 "\nAdd the guard immediately before the first hardware operation:\n"
695 " # shellcheck source=scripts/hil/lib/bench_lock.sh\n"
696 ' source "$_hil_dir/lib/bench_lock.sh"\n'
697 ' ra8_bench_require "<what you are doing>" || exit $?\n'
698 "\nRecovery paths (erase, DLM reset, power cycle) use\n"
699 "ra8_bench_require_recovery instead -- recovery is MORE\n"
700 "destructive than a normal flash, not less, so it is not exempt.\n"
701 "\nIf it genuinely cannot take the lock, add it to CARVE_OUTS in\n"
702 "this file WITH A REASON. A reason a reviewer would reject is not\n"
707def _report_programmer_failures(
708 programming: dict[str, list[tuple[int, str]]], unsafe_programmers: list[str]
710 """Explain programming entry points missing the image guard."""
711 if not unsafe_programmers:
714 "\ncheck_bench_lock.py: physical programmer(s) do not invoke "
715 "ra8_preflash_guard on the full image:\n"
717 for rel
in sorted(unsafe_programmers):
718 sys.stderr.write(f
"\n {rel}\n")
719 for line_no, why
in programming[rel][:4]:
720 sys.stderr.write(f
" line {line_no}: {why}\n")
722 "\nSource scripts/hil/lib/preflash_guard.sh and call "
723 "ra8_preflash_guard on the original image before stripping or programming it.\n"
727def main(argv: list[str]) -> int:
728 """Gate entry point: --selftest, --list, or the scan itself."""
729 if "--selftest" in argv:
730 return run_selftest()
732 tracked = _tracked_files()
733 touching, unguarded = scan(tracked)
734 programming, unsafe_programmers = scan_preflash(tracked)
736 _print_listing(touching, unguarded, programming, unsafe_programmers)
740 stale = [rel
for rel
in CARVE_OUTS
if rel
not in touching
and (REPO_ROOT / rel).exists()]
741 if unguarded
or stale
or unsafe_programmers:
742 _report_bench_failures(touching, unguarded)
744 sys.stderr.write(
"\ncheck_bench_lock.py: stale carve-out(s):\n")
745 for rel
in sorted(stale):
746 sys.stderr.write(f
" {rel}\n")
747 _report_programmer_failures(programming, unsafe_programmers)
751 f
"check_bench_lock.py: {len(touching)} bench-touching file(s), all guarded "
752 f
"or carved out with a reason ({len(CARVE_OUTS)} carve-out(s)); "
753 f
"{len(programming)} physical programmer(s) all image-guarded."
758if __name__ ==
"__main__":
759 raise SystemExit(
main(sys.argv[1:]))
void main(void)
The application entry point Reset_Handler hands control to.