Skip to content

CI: clang-tidy static analysis + flash-size budget gate - #35

Merged
TheAngryRaven merged 1 commit into
masterfrom
ci/clang-tidy-and-size-budget
May 21, 2026
Merged

CI: clang-tidy static analysis + flash-size budget gate#35
TheAngryRaven merged 1 commit into
masterfrom
ci/clang-tidy-and-size-budget

Conversation

@TheAngryRaven

Copy link
Copy Markdown
Owner

PR #4 on the path to 8/10. CI tooling — no firmware behavior change.

1. clang-tidy static analysis

  • .clang-tidy — enables the high-signal correctness families (bugprone-*, performance-*, portability-*, misc-*, clang-analyzer-*) with WarningsAsErrors: '*'. Deliberately skips the style/opinion checks (readability-*, cppcoreguidelines-*) that fight intentional embedded idioms — C arrays, pointer math, fixed magic constants — and would drown real findings in noise.
  • HeaderFilterRegex: 'BirdsEye/.*\.h$' scopes diagnostics to our own headers. Verified locally: ~15k warnings from doctest/system headers are suppressed, leaving 0 in our code.
  • .github/workflows/clang-tidy.yml — generates the test build's compile_commands.json (so clang-tidy gets each .cpp's exact flags) and analyzes the five host-buildable units: haversine, gps_time, gps_validation, dovex_header, filename_validator.

The only real finding was a true positive about an intentional choice: dovex_header.cpp's buffer builder does a non-null-terminated memcpy into the fixed 1024-byte region (it's \n-padded, not a C string). Suppressed inline with // NOLINT(bugprone-not-null-terminated-result) + a reason comment, so the check stays live everywhere else rather than being globally disabled.

Confirmed clang-tidy exits 0 locally on clang-tidy 18.

2. Flash-size budget gate

Added to compile-sketch.yml after the compile step. Parses the arduino/compile-sketches report and fails the build if flash usage exceeds 90% of the BSP-reported maximum.

Why absolute % and not a delta: the XIAO nRF52840's app flash is shared with the SoftDevice + bootloader, so the meaningful question is "will this still fit and leave OTA room," not "did it grow." Using absolute usage also means no base-branch checkout/compare is needed.

Robustness:

  • handles both report schema variants (absolute as a bare number or as {current: N})
  • degrades to a ::warning:: and passes if the report is missing or unparseable, so a schema change upstream can't wedge CI
  • 90% threshold + FLASH_BUDGET_PERCENT env var so it's a one-line change to adjust

Validated the jq/awk logic locally against synthetic reports: a 30%-usage report passes at budget 90; a 93.7% report fails at 90 and passes at 95; an empty report warns-and-skips.

Badges

Fourth README badge (clang-tidy) joins compile-sketch, arduino-lint, unit-tests. CLAUDE.md CI line updated.

Notes / scope

  • The .ino modules aren't clang-tidy'd — they need the full Arduino toolchain and are covered by compile-sketch. Static analysis here targets exactly the Arduino-free units, which is also where the testable logic lives.
  • The 90% gate has generous headroom; this firmware is well under it. It's a runaway-growth tripwire, not a tight squeeze.
# reproduce locally
cmake -S tests -B tests/build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
clang-tidy -p tests/build BirdsEye/haversine.cpp BirdsEye/gps_time.cpp \
  BirdsEye/gps_validation.cpp BirdsEye/dovex_header.cpp BirdsEye/filename_validator.cpp

Where we are

# Branch Status
1 refactor/drop-legacy-module-headers ✅ merged (#32)
2 tests/host-doctest-harness ✅ merged (#33)
3 feat/ble-filename-sanitization ✅ merged (#34)
4 ci/clang-tidy-and-size-budget ← this PR
5 release/semver-and-uf2-artifacts next
6 docs/contributor-setup final

🤖 Generated with Claude Code


Generated by Claude Code

Two CI tooling additions toward the 8/10 bar:

clang-tidy (.clang-tidy + .github/workflows/clang-tidy.yml)
  Runs bugprone/performance/portability/misc/clang-analyzer families
  with WarningsAsErrors against the five host-buildable pure-logic
  units, using the test build's compile_commands.json for exact
  flags. HeaderFilterRegex scopes diagnostics to BirdsEye/*.h so
  doctest and system headers stay quiet (~15k third-party warnings
  suppressed, 0 in our code). The one real hit — an intentional
  non-null-terminated memcpy in dovex_header's fixed-layout buffer
  builder — is suppressed inline with a NOLINT + reason rather than
  by disabling the check everywhere.

flash-size budget gate (compile-sketch.yml)
  After compiling, parses the arduino/compile-sketches report and
  fails the build if flash usage exceeds 90% of the BSP-reported
  maximum. Uses absolute usage (no base-branch compare needed),
  handles both report schema variants (absolute as number or as
  {current}), and degrades to a warning if the report can't be
  parsed. The XIAO shares app flash with the SoftDevice + bootloader,
  so running it dry breaks OTA — this catches runaway growth before
  it bricks update capability.

Adds the clang-tidy README badge (now four) and notes both in
CLAUDE.md. No firmware behavior change.
@TheAngryRaven
TheAngryRaven merged commit 705cf09 into master May 21, 2026
4 checks passed
@TheAngryRaven
TheAngryRaven deleted the ci/clang-tidy-and-size-budget branch May 21, 2026 23:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants