4"""Assert the host tools compile first-party sources at the project warning bar.
8``apps/host/mdl`` compiled ``apps/shared_libs/jof/*``, ``libs/ra8_hal/ra8_jpeg_sw*``,
9``apps/shared_libs/compress/src/ra8_compress.c`` and three ``libs/ra8_core`` files under a
10blanket ``-w``, with a comment claiming "the repo lints these via its own
11build". It does not: ``-w`` turns off every diagnostic, so the one build in
12the tree that compiles those files for a 64-bit host could not report a
13host-only finding, and neither tool carried ``-Werror`` at all (#309).
14Removing the ``-w`` is a one-line edit that nothing stops a later change from
15re-adding, and it would come back silently -- the build would still pass.
17So the removal is checked, not just done. The check reads the compile
18database CMake emits, which records the exact argv per translation unit, and
19holds every first-party source in it to NASA Power of 10 Rule 10:
21 * blanket ``-Werror`` is present, and
22 * blanket ``-w`` is absent.
24Vendored SOUP (``libs/third_party/``) and generated data (``libs/ra8_fonts/``) are
25exempt by construction -- they are not hand-authored here and CLAUDE.md scopes
26the style bar to first-party code.
30The traps this deliberately avoids, each of which a substring test gets wrong:
32 * ``-Werror=return-type`` promotes ONE diagnostic and is not blanket
33 ``-Werror``; ``"-Werror" in command`` would wrongly accept it.
34 * ``-Wno-error`` later on the line cancels an earlier ``-Werror``.
35 * ``-Wall`` / ``-Wwrite-strings`` are not ``-w``; a prefix test would
38Matching is therefore on exact argv arguments, in order, never on substrings.
40Second compiler arm (#356)
41--------------------------
42gcc-14 catches warning families clang-18 misses -- its ``-Wformat-truncation``
43found a silent PATH_MAX path-join truncation in ``apps/host/mdl`` that clang
44did not flag. The tools-build gate therefore compiles the host tools under BOTH
45pinned compilers and passes both sets of databases here. ``--require-compilers
46clang,gcc`` makes a silently-dropped arm a hard failure rather than a vacuous
47pass (the #348/#355 class): each named family must drive at least one database.
51``--require-all-cmake-tools`` discovers the host CMake projects itself so the
52gate cannot quietly stop covering one. When the products tier landed, that
53discovery predated it: the ``apps/*/*/CMakeLists.txt`` glob swept in
54``apps/board/stand_alone/ereader`` -- a cross-compiled TrustZone image no host build
55produces -- and the gate failed demanding a tools-build database that must
56never exist. Firmware products are now excluded by
57``lint_targets.firmware_app_dirs()``, the tree's one definition of "linked into
58an image rather than started by a C runtime", and representation is decided by
59the sources the databases record rather than by build-tree names, because two
60products may legitimately share a name across categories.
64 check_tool_warning_flags.py COMPILE_COMMANDS_JSON [...]
65 check_tool_warning_flags.py --require-compilers clang,gcc COMPILE_COMMANDS_JSON [...]
66 check_tool_warning_flags.py --require-all-cmake-tools COMPILE_COMMANDS_JSON [...]
67 check_tool_warning_flags.py --list-missing-cmake-tools COMPILE_COMMANDS_JSON [...]
68 check_tool_warning_flags.py --selftest
71from __future__
import annotations
80from collections.abc
import Iterable
81from pathlib
import Path
83sys.path.insert(0, str(Path(__file__).resolve().parent))
85from lint_targets
import firmware_app_dirs, is_build_output
89EXEMPT_FRAGMENTS = (
"libs/third_party/",
"apps/shared_libs/third_party/",
"libs/ra8_fonts/")
99MIN_CMAKE_TOOL_PROJECTS = 12
101CMAKE_COMMAND_RE = re.compile(
r"^[A-Za-z_][A-Za-z0-9_]*[ \t]*\(")
102CMAKE_BRACKET_COMMENT_RE = re.compile(
r"#\[(=*)\[.*?\]\1\]", re.DOTALL)
105def is_exempt(source: str) -> bool:
106 """True when `source` is vendored SOUP or generated data."""
107 normalised = source.replace(
"\\",
"/")
108 return any(fragment
in normalised
for fragment
in EXEMPT_FRAGMENTS)
111def tokens_of(entry: dict) -> list[str]:
112 """Return the argv of one compile-database entry.
114 CMake emits either `arguments` (a list) or `command` (a single string)
115 depending on generator and version; both spellings are accepted.
117 if isinstance(entry.get(
"arguments"), list):
118 return [str(arg)
for arg
in entry[
"arguments"]]
119 return shlex.split(str(entry.get(
"command",
"")))
122def compiler_of(argv: list[str]) -> str:
123 """Classify the compiler family driving one compile-database entry.
125 Returns ``"clang"`` or ``"gcc"`` for the two pinned host-tool arms, or
126 ``"other"`` for anything else. The first recognised driver token wins, so a
127 launcher prefix (``ccache gcc-14 ...``) still classifies as gcc.
130 base = token.replace(
"\\",
"/").rsplit(
"/", 1)[-1]
133 if base
in (
"gcc",
"g++")
or base.startswith((
"gcc-",
"g++-")):
138def missing_required_compilers(seen: set[str], required: list[str]) -> list[str]:
139 """Return the required compiler families that no database exercised.
141 This is the #356 second-arm guard: the tools-build gate compiles the host
142 tools under clang-18 AND gcc-14 and passes both sets of databases here. If
143 the gcc arm is ever silently dropped, its family never appears in `seen`,
144 and a silently-dropped arm otherwise reads as a pass -- the #348/#355
145 failure class. ``--selftest`` asserts this fires when an arm is absent.
147 return sorted(set(required) - set(seen))
150def missing_tool_projects(projects: Iterable[str], sources: Iterable[str]) -> list[str]:
151 """Return host CMake project directories whose code the gate never compiled.
153 Representation is decided by the SOURCES the compile databases record, not
154 by the name of the build tree a database sits in. Two things follow, and
157 * two products may legitimately share a name in different categories
158 (``apps/shared_libs/mdl`` is the portable core, ``apps/host/mdl`` the
159 CLI form). A basename key cannot tell them apart, so it
160 silently reports the second covered because the first was built -- the
161 same collapse that made scan_build.sh configure one into the other's
163 * a project a sibling COMPOSES with ``add_subdirectory`` has no build
164 tree of its own, yet its translation units really are compiled and
165 really are held to the warning bar here. That is exactly the shared
166 mdl core, and a build-tree-name test would demand a standalone
167 build that has no reason to exist.
169 Paths are collapsed before matching. A form reaches its shared core through
170 a repo-root variable spelled ``${CMAKE_CURRENT_SOURCE_DIR}/../../..``, so a
171 database entry can name a source as ``.../apps/host/mdl/../../
172 ../apps/shared_libs/mdl/src/x.c`` -- which contains BOTH project paths as
173 substrings and would mark a project represented by a file that is not its
177 projects: Repo-relative host CMake project directories.
178 sources: Every ``file`` entry across the databases handed to the gate.
181 The project directories no database compiled a single file from,
184 normalised = {posixpath.normpath(str(source).replace(
"\\",
"/"))
for source
in sources}
187 for project
in projects
189 f
"/{project}/" in source
or source.startswith(f
"{project}/")
for source
in normalised
194def cmake_listfile_has_commands(path: Path) -> bool:
195 """Return whether ``path`` contains an active CMake command.
197 A source-only library can keep a ``CMakeLists.txt`` as a documented
198 composition boundary without defining a standalone configuration. CMake
199 accepts that comment-only file, but it emits no compile database, so
200 treating its directory as a host project makes the tools gate demand a
201 build that cannot represent anything.
203 Command-looking text inside line or bracket comments is deliberately
204 ignored. The first active command is enough: later project representation
205 is still proved from compile-database sources by ``missing_tool_projects``.
207 text = CMAKE_BRACKET_COMMENT_RE.sub(
"", path.read_text())
208 for raw_line
in text.splitlines():
209 line = raw_line.lstrip()
210 if line.startswith(
"#"):
212 if CMAKE_COMMAND_RE.match(line):
217def cmake_tool_projects(repo_root: Path, firmware: Iterable[str] |
None =
None) -> tuple[str, ...]:
218 """Discover every HOST CMake project under ``tools/`` and ``apps/``.
220 The two roots nest differently and the discovery has to say so. A tool is a
221 direct child of ``tools/``; products sit under ``apps/host/``,
222 ``apps/shared_libs/``, or a deeper board form such as
223 ``apps/board/stand_alone/``. A fixed-depth glob silently misses one of
224 those layouts, so apps are discovered recursively and firmware products
225 are then removed using the shared classifier below.
227 Matching the glob is not enough to be a host tool, though, and that is the
228 half this originally got wrong. A comment-only listfile documents a
229 source-only library's composition but defines no standalone build, and
230 therefore cannot emit the compile database this gate consumes. ``apps/``
231 also carries BOTH kinds of active build:
232 ``apps/board/stand_alone/ereader`` is a cross-compiled TrustZone image with a
233 linker script and a reset path, built by the firmware gates and by nothing
234 here, and demanding a tools-build database for it failed the gate on a
235 project that must never have one. The discriminator is not re-derived --
236 ``lint_targets.firmware_app_dirs()`` is the one definition of "linked into
237 an image rather than started by a C runtime", shared with the tier gates.
240 repo_root: Tree to discover in.
241 firmware: Repo-relative firmware app directories to exclude. Defaults
242 to the live tree's; the parameter exists so the selftest can drive
243 the rule with a fixture.
246 The repo-relative host project directories, sorted.
248 excluded = set(firmware_app_dirs()
if firmware
is None else firmware)
250 *(repo_root /
"tools").glob(
"*/CMakeLists.txt"),
251 *(repo_root /
"apps").glob(
"**/CMakeLists.txt"),
254 path.parent.relative_to(repo_root).as_posix()
255 for path
in candidates
256 if not is_build_output(path.relative_to(repo_root).as_posix())
257 and cmake_listfile_has_commands(path)
259 return tuple(sorted(found - excluded))
262def flag_problem(argv: list[str]) -> str |
None:
263 """Return a problem description for `argv`, or None when it is compliant.
265 Blanket `-Werror` must be in force at the end of the line and blanket `-w`
266 must never appear. Both are exact-token tests: `-Werror=xxx` is a targeted
267 promotion rather than the blanket flag, and `-Wno-error` cancels a blanket
268 `-Werror` that precedes it.
274 elif arg
in (
"-Wno-error",
"-Wno-error=all"):
277 return "compiled with -w (all diagnostics disabled)"
279 return "compiled without blanket -Werror"
283def check_database(path: Path) -> tuple[list[str], set[str], set[str]]:
284 """Return (violation lines, compiler families, compiled sources) for one database.
286 The compiler set is the #356 second-arm evidence: every translation unit in
287 one database is compiled by the same driver, so the union across the clang
288 and gcc databases the gate passes must contain both families.
290 The source set is the project-scope evidence: it is what
291 ``missing_tool_projects`` reads to decide whether a host CMake project's
292 code reached the gate at all.
295 entries = json.loads(path.read_text())
296 except (OSError, json.JSONDecodeError)
as exc:
297 sys.stderr.write(f
"check_tool_warning_flags.py: FATAL -- cannot read {path}: {exc}\n")
298 raise SystemExit(2)
from exc
302 f
"check_tool_warning_flags.py: FATAL -- {path} lists no translation\n"
303 " units. An empty compile database would let this gate report a\n"
304 " pass for a build that never happened.\n"
308 violations: list[str] = []
309 compilers: set[str] = set()
310 sources: set[str] = set()
311 for entry
in entries:
312 argv = tokens_of(entry)
313 compilers.add(compiler_of(argv))
314 source = str(entry.get(
"file",
""))
318 if is_exempt(source):
320 problem = flag_problem(argv)
321 if problem
is not None:
322 violations.append(f
"{source}: {problem}")
323 return violations, compilers, sources
331SELFTEST_CASES: list[tuple[str, str, list[str], bool]] = [
338 "first-party with -w on top of an otherwise-compliant line",
339 "apps/shared_libs/jof/src/jof.c",
340 [
"cc",
"-Wall",
"-Wextra",
"-Werror",
"-w",
"-fno-strict-aliasing",
"-c"],
344 "first-party without -Werror",
345 "libs/ra8_core/src/ra8_log.c",
346 [
"cc",
"-Wall",
"-Wextra",
"-c"],
350 "first-party with only a targeted -Werror=",
351 "apps/host/mdl/src/main.c",
352 [
"cc",
"-Wall",
"-Wextra",
"-Werror=return-type",
"-c"],
356 "first-party whose -Werror is cancelled later",
357 "apps/shared_libs/mdl/src/mdl_export.c",
358 [
"cc",
"-Wall",
"-Werror",
"-Wno-error",
"-c"],
362 "compliant first-party source",
363 "apps/shared_libs/jof/src/jof_produce.c",
364 [
"cc",
"-Wall",
"-Wextra",
"-Werror",
"-c"],
369 "apps/shared_libs/compress/src/ra8_compress.c",
370 [
"cc",
"-Wall",
"-Wwrite-strings",
"-Werror",
"-c"],
374 "vendored SOUP may keep -w",
375 "apps/shared_libs/third_party/miniz/miniz.c",
376 [
"cc",
"-w",
"-fno-strict-aliasing",
"-c"],
380 "generated font data is exempt",
381 "libs/ra8_fonts/ra8_font_dejavu.c",
391COMPILER_SELFTEST_CASES: list[tuple[str, list[str], str]] = [
392 (
"clang-18 absolute path", [
"/usr/bin/clang-18",
"-c",
"f.c"],
"clang"),
393 (
"clang++-18 driver", [
"clang++-18",
"-c",
"f.cc"],
"clang"),
394 (
"gcc-14 absolute path", [
"/usr/local/bin/gcc-14",
"-c",
"f.c"],
"gcc"),
395 (
"g++-14 driver", [
"g++-14",
"-c",
"f.cc"],
"gcc"),
396 (
"bare gcc", [
"gcc",
"-c",
"f.c"],
"gcc"),
397 (
"ccache-wrapped gcc-14", [
"ccache",
"gcc-14",
"-c",
"f.c"],
"gcc"),
398 (
"unknown driver", [
"tcc",
"-c",
"f.c"],
"other"),
405COVERAGE_SELFTEST_CASES: list[tuple[str, set[str], list[str], list[str]]] = [
406 (
"only clang seen -> gcc missing", {
"clang"}, [
"clang",
"gcc"], [
"gcc"]),
407 (
"only gcc seen -> clang missing", {
"gcc"}, [
"clang",
"gcc"], [
"clang"]),
408 (
"both seen -> nothing missing", {
"clang",
"gcc"}, [
"clang",
"gcc"], []),
415PROJECT_SELFTEST_CASES: list[tuple[str, list[str], list[str], list[str]]] = [
417 "a host project nothing compiled is reported",
418 [
"tools/ra8_emulator",
"apps/host/mdl"],
419 [
"/w/apps/host/mdl/src/main.c"],
420 [
"tools/ra8_emulator"],
423 "a shared core composed into a sibling's build tree stays quiet",
424 [
"apps/shared_libs/mdl",
"apps/host/mdl"],
426 "/w/apps/host/mdl/src/main.c",
427 "/w/apps/shared_libs/mdl/src/mdl_cache.c",
432 "a same-named product in another category is NOT covered by its twin",
433 [
"apps/shared_libs/mdl",
"apps/host/mdl"],
434 [
"/w/apps/host/mdl/src/main.c"],
435 [
"apps/shared_libs/mdl"],
438 "every project compiled stays quiet",
439 [
"tools/ra8_emulator",
"tools/mkbookimg"],
441 "/w/tools/ra8_emulator/src/emu.c",
442 "/w/tools/mkbookimg/src/mkbookimg.c",
447 "a source reached through .. counts only for the project it resolves to",
448 [
"apps/shared_libs/mdl",
"apps/host/mdl"],
449 [
"/w/apps/host/mdl/../../../apps/shared_libs/mdl/src/mdl_cache.c"],
455def _discovery_fixture(root: Path) ->
None:
456 """Write the smallest tree that exercises both discovery depths.
458 A host tool at ``tools/<tool>/``, a host product one level deeper under a
459 category, and a firmware product at the same depth carrying the linker
460 script + vector table that mark an image.
464 "apps/shared_libs/core",
465 "apps/board/stand_alone/blinky",
467 (root / rel).mkdir(parents=
True)
468 (root / rel /
"CMakeLists.txt").write_text(
"project(x C)\n")
469 source_only = root /
"apps/shared_libs/source_only"
470 source_only.mkdir(parents=
True)
471 (source_only /
"CMakeLists.txt").write_text(
472 "# Source-only composition note.\n"
473 "#[[ A bracket comment may mention a command without defining one.\n"
474 "project(not_a_real_project C)\n"
477 (root /
"apps/board/stand_alone/blinky/linker_script.ld").write_text(
"MEMORY {}\n")
478 firmware_src = root /
"apps/board/stand_alone/blinky/src"
480 (firmware_src /
"vector_table.c").write_text(
"int v;\n")
483def _discovery_selftest() -> list[str]:
484 """Drive discovery over a fixture tree; return failure lines.
486 Both directions, and through the REAL discriminator: the firmware set is
487 computed by ``lint_targets.firmware_app_dirs()`` over the fixture's paths
488 rather than hand-written here, so a change that stopped classifying
489 firmware apps fails this test instead of sailing through it.
491 failures: list[str] = []
492 with tempfile.TemporaryDirectory()
as name:
494 _discovery_fixture(root)
495 paths = [str(path.relative_to(root))
for path
in root.rglob(
"*")
if path.is_file()]
496 firmware = firmware_app_dirs(paths)
497 if firmware != (
"apps/board/stand_alone/blinky",):
498 failures.append(f
" discovery: firmware apps {firmware}, want the blinky image")
499 got = cmake_tool_projects(root, firmware=firmware)
500 want = (
"apps/shared_libs/core",
"tools/widget")
502 failures.append(f
" discovery: host projects {got}, want {want}")
506def selftest() -> int:
507 """Prove the detector fires and stays quiet where it must."""
508 failures: list[str] = []
509 for name, source, argv, should_fire
in SELFTEST_CASES:
510 fired = (
not is_exempt(source))
and (flag_problem(argv)
is not None)
511 if fired != should_fire:
513 f
" {name}: expected {'a violation' if should_fire else 'no violation'}, "
514 f
"got {'a violation' if fired else 'none'}"
517 for name, argv, want
in COMPILER_SELFTEST_CASES:
518 got = compiler_of(argv)
520 failures.append(f
" compiler_of {name}: expected {want!r}, got {got!r}")
522 for name, seen, required, want_missing
in COVERAGE_SELFTEST_CASES:
523 got_missing = missing_required_compilers(seen, required)
524 if got_missing != want_missing:
526 f
" coverage {name}: expected missing {want_missing}, got {got_missing}"
529 for name, projects, sources, want_missing
in PROJECT_SELFTEST_CASES:
530 got_missing = missing_tool_projects(projects, sources)
531 if got_missing != want_missing:
533 f
" project scope {name}: expected missing {want_missing}, got {got_missing}"
536 failures += _discovery_selftest()
539 sys.stderr.write(
"check_tool_warning_flags.py --selftest: FAILED\n")
540 sys.stderr.write(
"\n".join(failures) +
"\n")
543 fires = sum(1
for case
in SELFTEST_CASES
if case[3])
544 quiet = len(SELFTEST_CASES) - fires
546 f
"check_tool_warning_flags.py --selftest: PASS "
547 f
"({fires} must-fire, {quiet} must-stay-quiet flag cases; "
548 f
"{len(COMPILER_SELFTEST_CASES)} classifier, "
549 f
"{len(COVERAGE_SELFTEST_CASES)} second-arm coverage, "
550 f
"{len(PROJECT_SELFTEST_CASES)} project-scope cases; "
551 "discovery excludes firmware apps and comment-only source libraries "
552 "while keeping host products at category depth)"
557def _scan_databases(paths: list[str]) -> tuple[list[str], set[str], set[str]]:
558 """Scan every database path.
560 Returns (violations, compiler families seen, sources compiled).
562 Exits (SystemExit 2) when a path is missing, matching check_database's
563 fatal handling of an empty or unreadable database -- a gate must never
564 report a pass for a database it could not inspect.
566 violations: list[str] = []
567 seen: set[str] = set()
568 sources: set[str] = set()
571 if not path.is_file():
573 f
"check_tool_warning_flags.py: FATAL -- {path} does not exist.\n"
574 " Configure the tool with -DCMAKE_EXPORT_COMPILE_COMMANDS=ON first.\n"
577 db_violations, db_compilers, db_sources = check_database(path)
578 violations += db_violations
580 sources |= db_sources
581 return violations, seen, sources
584def _report_violations(violations: list[str]) ->
None:
585 """Print every first-party translation unit found below the warning bar."""
587 f
"check_tool_warning_flags.py: {len(violations)} first-party "
588 "translation unit(s) below the project warning bar:\n\n"
590 for line
in sorted(violations):
591 sys.stderr.write(f
" {line}\n")
593 "\nFirst-party sources are held to -Wall -Wextra -Werror everywhere\n"
594 "else in this tree (NASA Power of 10 Rule 10). Only libs/third_party/\n"
595 "SOUP may be compiled -w. Fix the warning; do not re-suppress it.\n"
599def _discover_projects() -> tuple[str, ...]:
600 """Host CMake projects in the tree, enforcing the non-vacuity floor.
602 The floor is checked here rather than at the call site so a collapsed glob
603 can never be mistaken for "nothing is missing": `cmake_tool_projects`
604 returning an empty tuple makes `missing_tool_projects` return an empty
605 list, which is indistinguishable from a clean pass.
608 SystemExit: 2 when discovery falls below MIN_CMAKE_TOOL_PROJECTS,
609 matching _scan_databases' fatal handling of an input it could not
612 projects = cmake_tool_projects(Path.cwd())
613 if len(projects) >= MIN_CMAKE_TOOL_PROJECTS:
616 f
"check_tool_warning_flags.py: FATAL -- discovered {len(projects)} host "
617 f
"CMake project(s); the floor is {MIN_CMAKE_TOOL_PROJECTS}.\n"
618 f
" found: {', '.join(projects) or '(none)'}\n"
619 " Discovery collapsed. An empty scope reports no missing project and\n"
620 " reads as a pass, which is the failure this floor exists to catch.\n"
625def _report_missing_projects(missing: list[str]) ->
None:
626 """Print the host CMake projects whose code the gate never compiled."""
628 "check_tool_warning_flags.py: FATAL -- host CMake project(s) whose "
629 f
"sources tools-build never compiled: {', '.join(missing)}\n"
630 " Every host CMake project must reach the gate, either through its\n"
631 " own build tree or by being composed into a sibling's with\n"
632 " add_subdirectory. A project nothing builds is a project nothing\n"
633 " holds to -Wall -Wextra -Werror.\n"
637def _report_missing_arm(missing: list[str], seen: set[str]) ->
None:
638 """Print the silently-dropped compiler-arm failure (#356)."""
640 "check_tool_warning_flags.py: FATAL -- required compiler arm(s) "
641 f
"never exercised: {', '.join(missing)}\n"
642 f
" databases were compiled by: {', '.join(sorted(seen)) or '(none)'}\n"
643 " The tools-build gate compiles the host tools under clang-18 AND\n"
644 " gcc-14 (#356) so the warnings each catches but the other misses\n"
645 " are both held. A silently-dropped arm would read as a pass.\n"
651 databases: list[str],
653 violations: list[str],
655 missing_projects: list[str],
657 """Render one discovery, warning, and compiler-arm verdict."""
659 _report_missing_projects(missing_projects)
662 _report_violations(violations)
664 missing = missing_required_compilers(seen, required)
666 _report_missing_arm(missing, seen)
669 arms = f
", arms: {', '.join(sorted(seen))}" if required
else ""
671 f
"check_tool_warning_flags.py: OK ({len(databases)} compile "
672 f
"database(s), every first-party TU at -Wall -Wextra -Werror{arms})"
679 parser = argparse.ArgumentParser(description=__doc__)
680 parser.add_argument(
"databases", nargs=
"*", help=
"compile_commands.json paths")
684 help=
"prove the detector fires and stays quiet, then exit",
687 "--require-compilers",
689 metavar=
"FAMILY[,FAMILY...]",
691 "comma-separated compiler families (e.g. clang,gcc) that must each "
692 "drive at least one database; the #356 second-arm guard. A single "
693 "comma-joined value keeps it order-independent of the database list."
697 "--require-all-cmake-tools",
699 help=
"fail when any host CMake project's sources are absent from the databases",
702 "--list-missing-cmake-tools",
704 help=
"print uncovered host CMake project directories, one per line",
706 args = parser.parse_args()
710 if not args.databases:
712 "check_tool_warning_flags.py: FATAL -- no compile database given.\n"
713 " This gate must never run with nothing to inspect: that reports a\n"
714 " pass for work never done.\n"
718 projects: tuple[str, ...] = ()
719 if args.require_all_cmake_tools
or args.list_missing_cmake_tools:
720 projects = _discover_projects()
722 required = [fam
for fam
in args.require_compilers.split(
",")
if fam]
723 violations, seen, sources = _scan_databases(args.databases)
724 missing_projects = missing_tool_projects(projects, sources)
725 if args.list_missing_cmake_tools:
726 for project
in missing_projects:
729 return _report_verdict(
730 databases=args.databases,
732 violations=violations,
734 missing_projects=missing_projects,
738if __name__ ==
"__main__":
void main(void)
The application entry point Reset_Handler hands control to.