4"""Emit ONE ``compile_commands.json`` covering every cross-compiled first-party TU.
8``clang_tidy.sh`` parses against the HOST compile database produced by the unit
9test build. That database describes ``libs/``, ``src/``, ``tests/`` and
10``tools/`` and nothing else: no ``-mcpu=cortex-m85``, no per-app include
11directories, no vendored RTOS paths. Pointing clang-tidy at the firmware anyway
12was measured, not guessed -- 135 findings across 96 files, every one a
13``clang-diagnostic-error`` and not one an actionable style finding (#369).
15The fix is not a wider glob, it is a database that actually describes how those
16translation units compile. CMake already knows: it emits
17``compile_commands.json`` from any configure. This script performs the
18configures and merges their output into a single database keyed by real,
21WHAT IT COVERS AND HOW IT FINDS IT
22----------------------------------
231. The unified RA8D2 cross-configure with every ``RA8_USE_*`` middleware option
24 forced ON, so apps that ``USES threadx`` / ``usbx`` / ``mbedtls`` are
25 configured instead of skipped. This alone accounts for the overwhelming
26 majority of firmware TUs.
272. Whatever the unified configure still leaves out, discovered by DIFFING the
28 result against ``git ls-files`` -- never from a hardcoded app list. For each
29 app directory still missing, the shared ``scripts/dev/ra8_apps.py`` registry
30 supplies the source directory and toolchain used by ``just apps::build``.
31 The app's own CMake ``ra8_add_app(USES ...)`` declaration supplies its
32 middleware switches in a standalone configure. That is how the RA8P1 tier
33 and ``ra8_cache_store_demo`` are picked up without duplicating their build
353. A last-resort derivation for TUs that NO configure compiles, described
38Step 2 is the load-bearing one. A hardcoded residual list is the exact defect
39that #296 / #332 / #358 / #359 / #360 were each an instance of: a scan list
40that silently stops matching the tree. Deriving the residual means a new app,
41at any depth, in any tier, needing any middleware, is picked up the day it
42lands -- or fails this script loudly if it cannot be configured at all.
44TUs THAT NOTHING BUILDS
45-----------------------
46One first-party TU is compiled by no configure at all, for a reason that is a
47fact about the tree rather than a defect in the enumeration above:
49 * ``examples/.../dfu_copy_to_run/src/payload.c`` -- a freestanding image linked at
50 a fixed SRAM base by its own
51 ``examples/ek_ra8d2/hw_validated/hil/dfu_copy_to_run/scripts/build_payload.sh``,
54Being unbuilt is not a licence to go unlinted: it is first-party firmware and
55CLAUDE.md holds it to the same bar. So a command is DERIVED from the nearest
56already-covered sibling TU -- same directory subtree, therefore the same
57middleware, the same board layer and the same include set -- and then VERIFIED
58by actually running the cross-compiler over the file with it.
60The verification is what separates this from guessing. A derived command that
61does not compile the file is a hard error here, so the fallback can never
62quietly hand clang-tidy a wrong command and let a bogus parse read as a clean
63lint. Nothing is exempted and no path is allowlisted; a file either gets a
64command that provably works or this script fails.
66THE COVERAGE ASSERTION IS THE POINT
67-----------------------------------
68Any first-party cross-compiled ``.c`` absent from the merged database fails the
69run. This script must not quietly emit a smaller database over which clang-tidy
70would report a clean result. ``--check`` remains as a compatibility spelling
71for CI, but completeness is mandatory for every caller.
75 build_cross_compile_db.py --selftest # assert the merge + gap logic fires
76 build_cross_compile_db.py -o DIR # write a complete compile_commands.json
77 build_cross_compile_db.py -o DIR --check # compatibility spelling; same strict verdict
80from __future__
import annotations
91from dataclasses
import dataclass, field
92from functools
import cache
93from pathlib
import Path
95REPO_ROOT = Path(__file__).resolve().parents[2]
96sys.path.insert(0, str(REPO_ROOT /
"scripts" /
"dev"))
97from git_environment
import (
98 isolated_git_environment,
99 trusted_git_executable,
101from ra8_apps
import get_apps
105def firmware_apps() -> tuple[dict, ...]:
106 """Return the Just app registry once for this generator invocation."""
107 return tuple(get_apps())
118UNIFIED_MIDDLEWARE_OFF = (
"RA8_USE_LEVELX_STANDALONE",)
122FIRMWARE_ROOTS = (
"examples/",
"port/")
133HOST_PORT_ROOTS = (
"port/posix/",)
139HOST_TEST_PATH_PART =
"/tests/"
144FIRMWARE_EXEMPT: tuple[str, ...] = ()
155EXPECTED_FIXTURE_ENTRIES = 2
167def run(argv: list[str], cwd: Path |
None =
None) -> subprocess.CompletedProcess[str]:
168 """Run `argv`, capturing output. Never raises; callers inspect returncode.
170 A missing executable is reported as a failed run rather than an exception:
171 donor probing walks compile commands from several toolchains (Arm and
172 RISC-V), and only some of them are installed on any given machine. An
173 absent compiler must disqualify that donor, not abort the whole build.
176 return subprocess.run(
178 cwd=str(cwd)
if cwd
else None,
183 except (FileNotFoundError, NotADirectoryError, PermissionError)
as exc:
184 return subprocess.CompletedProcess(argv, returncode=127, stdout=
"", stderr=str(exc))
187def live_files(paths: list[str], root: Path) -> list[str]:
188 """Keep only paths whose regular file exists below ``root``."""
189 return [rel
for rel
in paths
if (root / rel).is_file()]
192def working_tree_files(root: Path = REPO_ROOT) -> list[str]:
193 """Every cached or untracked, non-ignored live file, repo-relative."""
195 [trusted_git_executable(),
"ls-files",
"--cached",
"--others",
"--exclude-standard"],
198 if out.returncode != 0:
199 sys.stderr.write(f
"git ls-files failed: {out.stderr.strip()}\n")
201 return live_files(out.stdout.splitlines(), root)
204def is_firmware_source(rel: str) -> bool:
205 """Report whether `rel` is a first-party cross-compiled C source."""
208 and rel.startswith(FIRMWARE_ROOTS)
209 and not rel.startswith(FIRMWARE_EXEMPT)
210 and not rel.startswith(HOST_PORT_ROOTS)
211 and HOST_TEST_PATH_PART
not in rel
215def firmware_sources() -> set[str]:
216 """Repo-relative first-party ``.c`` under the cross-compiled roots."""
217 return {rel
for rel
in working_tree_files()
if is_firmware_source(rel)}
225 """Accumulates compile-command entries keyed by absolute source path."""
227 entries: dict[str, dict] = field(default_factory=dict)
229 def absorb(self, build_dir: Path) -> int:
230 """Merge ``build_dir/compile_commands.json``. Returns entries added."""
231 path = build_dir /
"compile_commands.json"
232 if not path.is_file():
235 for entry
in json.loads(path.read_text(encoding=
"utf-8")):
236 key = str(Path(entry[
"directory"], entry[
"file"]).resolve())
237 if key
in self.entries:
239 self.entries[key] = entry
243 def add(self, directory: Path, source: Path, argv: list[str]) ->
None:
244 """Add one hand-assembled compile-database entry (used by the derived-command pass)."""
245 key = os.path.realpath(str(source))
246 self.entries.setdefault(
249 "directory": str(directory),
255 def covered(self) -> set[str]:
256 """Repo-relative paths of every covered source inside the repo."""
257 root = str(REPO_ROOT)
258 return {os.path.relpath(key, root)
for key
in self.entries
if key.startswith(root + os.sep)}
260 def write(self, out_dir: Path) -> Path:
261 """Serialise the accumulated entries to compile_commands.json.
263 Writes the values of the entry map, not a list built alongside it, so
264 a translation unit compiled twice (two configurations of one source)
265 contributes exactly one entry -- clangd and clang-tidy both take the
266 first match and a duplicate would make which flags apply arbitrary.
268 Returns the path written.
270 out_dir.mkdir(parents=
True, exist_ok=
True)
271 path = out_dir /
"compile_commands.json"
273 json.dumps(list(self.entries.values()), indent=1) +
"\n",
282def known_middleware_options() -> list[str]:
283 """Every ``RA8_USE_*`` option cmake/ declares, read from cmake/ itself.
285 Read rather than listed so a new cmake/<middleware>.cmake is picked up
286 automatically; a hardcoded list here would be the same silently-stale scan
287 list this whole script exists to stop reintroducing.
289 names: set[str] = set()
290 pattern = re.compile(
r"option\(\s*(RA8_USE_[A-Z0-9_]+)")
291 for cmake_file
in sorted((REPO_ROOT /
"cmake").glob(
"*.cmake")):
292 names.update(pattern.findall(cmake_file.read_text(encoding=
"utf-8")))
293 for match
in pattern.findall((REPO_ROOT /
"CMakeLists.txt").read_text(encoding=
"utf-8")):
295 return sorted(names - set(UNIFIED_MIDDLEWARE_OFF))
298def configure_unified(build_dir: Path, verbose: bool) ->
None:
299 """Configure the whole-tree RA8D2 cross build with all middleware ON."""
306 f
"-DCMAKE_TOOLCHAIN_FILE={REPO_ROOT / 'cmake' / 'toolchain-ra8d2.cmake'}",
307 "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON",
310 argv += [f
"-D{name}=ON" for name
in known_middleware_options()]
312 if result.returncode != 0
or not (build_dir /
"compile_commands.json").is_file():
313 sys.stderr.write(result.stdout[-4000:])
314 sys.stderr.write(result.stderr[-4000:])
315 sys.stderr.write(
"ERROR: the unified RA8D2 cross-configure failed (see above).\n")
318 print(f
"[db] unified RA8D2 configure -> {build_dir}")
324def app_dir_for(rel: str) -> Path |
None:
325 """Nearest ancestor directory of `rel` that is a discovered firmware app.
327 Discovery is shared with ``just apps::build`` through ``ra8_apps.py`` so
328 this pass cannot grow a second, subtly different definition of an app.
330 source = (REPO_ROOT / rel).resolve()
332 Path(app[
"dir"]).resolve()
333 for app
in firmware_apps()
334 if Path(app[
"dir"]).resolve()
in source.parents
336 return max(candidates, key=
lambda path: len(path.parts), default=
None)
339def configure_argv_for_app(app_dir: Path) -> list[str] |
None:
340 """Return the standalone configure consumed by ``just apps::build``."""
342 (entry
for entry
in firmware_apps()
if Path(entry[
"dir"]).resolve() == app_dir.resolve()),
347 toolchain = REPO_ROOT / app[
"toolchain"]
351 str(Path(app[
"dir"]).resolve()),
353 str(app_dir /
"build"),
354 f
"-DCMAKE_TOOLCHAIN_FILE={toolchain}",
355 "-DCMAKE_BUILD_TYPE=RelWithDebInfo",
356 "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON",
360def rewrite_build_dir(argv: list[str], build_dir: Path) -> list[str]:
361 """Point a configure argv at `build_dir` and make it export its database."""
371 if arg.startswith(
"-B")
and len(arg) > len(
"-B"):
373 if arg.startswith(
"-DCMAKE_EXPORT_COMPILE_COMMANDS"):
376 out += [
"-B", str(build_dir),
"-DCMAKE_EXPORT_COMPILE_COMMANDS=ON"]
380def configure_residual_apps(db: Database, scratch: Path, verbose: bool) -> list[str]:
381 """Configure each app still missing from `db`. Returns paths still uncovered."""
382 missing = sorted(firmware_sources() - db.covered())
383 seen: set[Path] = set()
385 app_dir = app_dir_for(rel)
386 if app_dir
is None or app_dir
in seen:
389 argv = configure_argv_for_app(app_dir)
392 build_dir = scratch / f
"app-{app_dir.name}"
393 result = run(rewrite_build_dir(argv, build_dir))
394 if result.returncode != 0:
396 print(f
"[db] per-app configure FAILED for {app_dir.relative_to(REPO_ROOT)}")
398 added = db.absorb(build_dir)
400 print(f
"[db] per-app {app_dir.relative_to(REPO_ROOT)} -> +{added}")
401 return sorted(firmware_sources() - db.covered())
407def entry_argv(entry: dict) -> list[str]:
408 """The compile command of a database entry, as an argv list."""
409 argv = entry.get(
"arguments")
412 return shlex.split(entry.get(
"command",
""))
415def own_include_args(source: Path) -> list[str]:
416 """``-I`` flags for the include directories `source`'s own library declares.
418 Every library in this tree is laid out ``<lib>/inc`` + ``<lib>/src`` -- the
419 convention check_header_file_placement.py enforces -- and a port library
420 publishes ``<lib>/inc`` through target_include_directories(). A donor from
421 another library supplies the middleware and HAL context but cannot supply
422 that, so walk the TU's own ancestors and offer theirs.
424 This is reading the tree's structure, not inventing flags: a directory is
425 only added when it exists, and the result still has to compile the file.
428 current = source.parent
429 while current == REPO_ROOT
or REPO_ROOT
in current.parents:
430 candidate = current /
"inc"
431 if candidate.is_dir():
432 args.append(f
"-I{candidate}")
433 if current == REPO_ROOT:
435 current = current.parent
439def retarget(argv: list[str], source: Path, obj: Path) -> list[str]:
440 """Rewrite a sibling's compile command to compile `source` into `obj`."""
447 if arg
in (
"-c",
"-o"):
448 skip_next = arg ==
"-o"
450 if arg.endswith((
".c",
".o",
".obj")):
453 return out + own_include_args(source) + [
"-c", str(source),
"-o", str(obj)]
456def shared_prefix_len(a: str, b: str) -> int:
457 """How many leading path components `a` and `b` have in common."""
458 left, right = a.split(
"/"), b.split(
"/")
460 for one, other
in zip(left, right, strict=
False):
467def candidate_donors(db: Database, rel: str) -> list[dict]:
468 """Covered entries ordered by how close they sit to `rel` in the tree.
470 Closeness is shared path depth, so the same app or the same port library
471 comes first, then the same tier, then the rest. Order is a PREFERENCE, not
472 an answer -- the caller proves a donor correct by compiling with it.
474 root = str(REPO_ROOT)
475 seen_dirs: set[Path] = set()
476 scored: list[tuple[int, str, dict]] = []
477 for key, entry
in db.entries.items():
478 if not key.startswith(root + os.sep)
or not key.endswith(
".c"):
480 parent = Path(key).parent
481 if parent
in seen_dirs:
483 seen_dirs.add(parent)
484 candidate_rel = os.path.relpath(key, root)
485 scored.append((-shared_prefix_len(rel, candidate_rel), candidate_rel, entry))
487 return [entry
for _, _, entry
in scored[:MAX_DONOR_PROBES]]
490def derive_unbuilt(db: Database, scratch: Path, verbose: bool) -> list[str]:
491 """Give every still-uncovered TU a sibling-derived, compile-verified command.
493 A donor is accepted only once the cross-compiler has compiled the file with
494 its flags. Candidates are tried nearest-first; the first that compiles wins.
495 Guessing is therefore not possible -- either a provably working command is
496 found or the TU is reported uncovered and the run fails.
498 still: list[str] = []
499 for rel
in sorted(firmware_sources() - db.covered()):
500 source = REPO_ROOT / rel
501 obj = scratch / (rel.replace(
"/",
"_") +
".o")
503 last_error =
"no covered TU was available to derive a command from"
504 for donor
in candidate_donors(db, rel):
505 argv = retarget(entry_argv(donor), source, obj)
506 probe = run(argv, cwd=Path(donor[
"directory"]))
507 if probe.returncode != 0:
508 last_error = probe.stderr.strip()[-1200:]
510 db.add(Path(donor[
"directory"]), source, argv)
513 donor_rel = os.path.relpath(donor[
"file"], REPO_ROOT)
514 print(f
"[db] derived {rel} from {donor_rel} (compiles clean)")
518 f
"ERROR: no compile command could be derived for {rel}.\n"
519 f
" last attempt failed with:\n {last_error}",
529def report_uncovered(uncovered: list[str], total: int) -> int:
530 """Print the coverage verdict. Returns the process exit code."""
531 covered = total - len(uncovered)
532 print(f
"cross-compile database: {covered}/{total} first-party firmware TUs covered")
534 print(
"OK: every cross-compiled first-party .c has a compile command.")
536 print(file=sys.stderr)
537 print(f
"ERROR: {len(uncovered)} firmware TU(s) have no compile command:", file=sys.stderr)
538 for rel
in uncovered[:MAX_SHOWN]:
539 print(f
" {rel}", file=sys.stderr)
540 if len(uncovered) > MAX_SHOWN:
541 print(f
" ... and {len(uncovered) - MAX_SHOWN} more", file=sys.stderr)
542 print(file=sys.stderr)
544 "Each needs a configure that reaches it. Firmware apps are discovered\n"
545 "through scripts/dev/ra8_apps.py, and each app's CMakeLists.txt must\n"
546 "declare its complete ra8_add_app() dependency surface.",
555def _selftest_database() -> list[str]:
556 """Assertions about Database merging and coverage accounting."""
557 failures: list[str] = []
560 with tempfile.TemporaryDirectory()
as tmp:
561 one = Path(tmp) /
"one"
563 (one /
"compile_commands.json").write_text(
566 {
"directory": str(REPO_ROOT),
"file":
"a.c",
"arguments": [
"cc",
"a.c"]},
567 {
"directory": str(REPO_ROOT),
"file":
"./a.c",
"arguments": [
"cc",
"a.c"]},
568 {
"directory": str(REPO_ROOT),
"file":
"b.c",
"arguments": [
"cc",
"b.c"]},
574 added = db.absorb(one)
575 if added != EXPECTED_FIXTURE_ENTRIES:
576 failures.append(f
"absorb() merged {added} entries, expected 2 after de-duplication")
577 if db.covered() != {
"a.c",
"b.c"}:
578 failures.append(f
"covered() returned {sorted(db.covered())}, expected ['a.c', 'b.c']")
583def _selftest_reporting() -> list[str]:
584 """Assertions about the uncovered-TU verdict."""
585 failures: list[str] = []
590 print(
"selftest: probing the gap report with a planted uncovered TU ...")
591 if report_uncovered([
"examples/broken/main.c"], 10) == 0:
592 failures.append(
"report_uncovered() returned success while a TU was uncovered")
593 if report_uncovered([], 10) != 0:
594 failures.append(
"report_uncovered() returned failure with nothing uncovered")
599def _selftest_command_building() -> list[str]:
600 """Assertions about option discovery and compile-command construction."""
601 failures: list[str] = []
606 options = known_middleware_options()
607 if "RA8_USE_THREADX" not in options
or "RA8_USE_USBX" not in options:
608 failures.append(f
"known_middleware_options() missed a vendored switch: {options}")
609 if "RA8_USE_LEVELX_STANDALONE" in options:
610 failures.append(
"known_middleware_options() included the mutually-exclusive LevelX mode")
614 apps = firmware_apps()
616 failures.append(
"ra8_apps.py discovered no firmware apps")
618 app_dir = Path(app[
"dir"]).resolve()
619 argv = configure_argv_for_app(app_dir)
620 expected_toolchain = f
"-DCMAKE_TOOLCHAIN_FILE={REPO_ROOT / app['toolchain']}"
621 if argv
is None or str(app_dir)
not in argv
or expected_toolchain
not in argv:
622 failures.append(f
"configure argv drifted from ra8_apps.py for {app_dir}")
626 rewritten = rewrite_build_dir([
"cmake",
"-S",
".",
"-B",
"/orig",
"-DA=1"], Path(
"/new"))
627 if "/orig" in rewritten
or "-B" not in rewritten
or "/new" not in rewritten:
628 failures.append(f
"rewrite_build_dir() left the original -B: {rewritten}")
629 if "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON" not in rewritten:
630 failures.append(
"rewrite_build_dir() did not force the compile-command export")
635 donor_argv = [
"arm-none-eabi-gcc",
"-Iinc",
"-c",
"donor.c",
"-o",
"donor.o"]
636 retargeted = retarget(donor_argv, Path(
"/w/mine.c"), Path(
"/w/mine.o"))
637 if "donor.c" in retargeted
or "donor.o" in retargeted:
638 failures.append(f
"retarget() kept the donor's source or object: {retargeted}")
639 if retargeted[-4:] != [
"-c",
"/w/mine.c",
"-o",
"/w/mine.o"]:
640 failures.append(f
"retarget() did not compile the requested file: {retargeted}")
641 if "-Iinc" not in retargeted:
642 failures.append(
"retarget() dropped the donor's include flags")
646 if entry_argv({
"arguments": [
"cc",
"x.c"]}) != [
"cc",
"x.c"]:
647 failures.append(
"entry_argv() mishandled the 'arguments' form")
648 if entry_argv({
"command":
"cc x.c"}) != [
"cc",
"x.c"]:
649 failures.append(
"entry_argv() mishandled the 'command' form")
654def _selftest_source_classification() -> list[str]:
655 """Assert app-local host tests and absent index paths stay out of the database."""
656 failures: list[str] = []
657 if is_firmware_source(
"examples/demo/tests/src/test_demo.c"):
658 failures.append(
"is_firmware_source() claimed an app-local host test")
659 if not is_firmware_source(
"examples/demo/src/main.c"):
660 failures.append(
"is_firmware_source() dropped an example implementation")
661 with tempfile.TemporaryDirectory()
as tmp:
663 (root /
"present.c").write_text(
"void present(void) {}\n", encoding=
"ascii")
664 if live_files([
"present.c",
"missing.c"], root) != [
"present.c"]:
665 failures.append(
"live_files() did not keep the real file and drop the absent path")
666 run([trusted_git_executable(),
"init",
"--quiet"], cwd=root)
667 (root /
".gitignore").write_text(
"._*\n", encoding=
"ascii")
668 (root /
"ordinary.cc").write_text(
"void ordinary() {}\n", encoding=
"ascii")
669 (root /
"._artifact.c").write_bytes(b
"\x00AppleDouble")
670 enumerated = working_tree_files(root)
671 if "present.c" not in enumerated
or "ordinary.cc" not in enumerated:
672 failures.append(
"working_tree_files() dropped an ordinary untracked C/C++ file")
673 if "._artifact.c" in enumerated:
674 failures.append(
"working_tree_files() included an ignored AppleDouble artifact")
678def _selftest_body() -> int:
679 """Assert the merge and the gap detection both work, and that they fail."""
682 + _selftest_reporting()
683 + _selftest_command_building()
684 + _selftest_source_classification()
688 sources = firmware_sources()
689 if len(sources) < TU_FLOOR:
691 f
"firmware_sources() found {len(sources)} TUs, "
692 f
"below the floor of {TU_FLOOR} -- enumeration is broken"
700 if any(rel.startswith(HOST_PORT_ROOTS)
for rel
in sources):
701 failures.append(
"firmware_sources() still claims a hosted port root")
702 if not any(rel.startswith(
"port/")
for rel
in sources):
703 failures.append(
"firmware_sources() claims no port/ TU at all -- carve-out too wide")
706 print(
"SELFTEST FAILED:", file=sys.stderr)
707 for problem
in failures:
708 print(f
" - {problem}", file=sys.stderr)
710 print(
"selftest: cross-compile database merge + gap detection OK")
714def selftest() -> int:
715 """Run compile-database fixtures without inheriting the caller's repo."""
716 with isolated_git_environment():
717 return _selftest_body()
724 """Build a complete cross-compilation database.
726 Completeness is mandatory for every caller. ``--check`` remains accepted
727 for the CI call site, but cannot weaken or strengthen the verdict.
729 parser = argparse.ArgumentParser(description=__doc__)
730 parser.add_argument(
"-o",
"--out", help=
"directory to write compile_commands.json into")
734 help=
"compatibility flag; completeness is always enforced",
736 parser.add_argument(
"--selftest", action=
"store_true", help=
"assert this script still fires")
737 parser.add_argument(
"-v",
"--verbose", action=
"store_true")
738 args = parser.parse_args()
743 parser.error(
"--out is required unless --selftest is given")
745 if shutil.which(
"cmake")
is None:
747 "ERROR: cmake not found; the cross-compile database cannot be built.", file=sys.stderr
751 total = len(firmware_sources())
754 f
"ERROR: only {total} firmware TUs enumerated (floor {TU_FLOOR}). "
755 "The enumeration is broken; refusing to emit a database that would "
756 "let clang-tidy report a clean run over almost nothing.",
769 out_dir = Path(args.out).resolve()
770 scratch = out_dir /
".compile_commands_scratch"
771 scratch.mkdir(parents=
True, exist_ok=
True)
774 unified = scratch /
"unified"
775 configure_unified(unified, args.verbose)
778 print(f
"[db] unified -> {len(db.entries)} entries")
779 configure_residual_apps(db, scratch, args.verbose)
780 uncovered = derive_unbuilt(db, scratch, args.verbose)
781 code = report_uncovered(uncovered, total)
784 out_path = db.write(out_dir)
785 print(f
"wrote {out_path} ({len(db.entries)} entries)")
789if __name__ ==
"__main__":
void main(void)
The application entry point Reset_Handler hands control to.