bazel: add hermetic clang-tidy via aspect_rules_lint (--config=lint)#10447
bazel: add hermetic clang-tidy via aspect_rules_lint (--config=lint)#10447openroad-ci wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Code Review
This pull request integrates hermetic clang-tidy analysis into the Bazel build system using aspect_rules_lint. Key changes include the addition of a lint configuration in .bazelrc, the definition of a linting aspect in tools/lint/, and updated documentation for developers. Review feedback correctly identifies a breaking configuration error in tools/lint/linters.bzl regarding the lint_clang_tidy_aspect parameters and suggests simplifying a redundant select statement in the linter's binary definition.
|
clang-tidy review says "All clean, LGTM! 👍" |
Adds a `--config=lint` Bazel configuration that runs clang-tidy 20.1.8
(from @llvm_toolchain) hermetically against cc targets using
aspect_rules_lint. Goal: replace the The-OpenROAD-Project/clang-tidy-review
fork for local pre-PR linting and provide a foundation for replacing the
fork's GitHub workflow.
Usage:
bazel build --config=lint //src/utl/...
bazel build --config=lint -- //src/... //third-party/... \
-//src/sta/... -//third-party/abc/...
- tools/lint/BUILD.bazel: native_binary wrapping clang-tidy from
@llvm_toolchain_llvm
- tools/lint/linters.bzl: lint_clang_tidy_aspect deferring header
filtering to //:.clang-tidy
- MODULE.bazel: aspect_rules_lint 2.5.2 (dev_dep), expanded use_repo to
expose @llvm_toolchain_llvm
- BUILD.bazel: export //:.clang-tidy
- third-party/{gif-h,lodepng,stb_truetype}: tag no-lint (vendored libs)
- .bazelrc: --config=lint aspect + output group config
- docs/agents/ci.md: contributor instructions
Submodule targets (//src/sta/..., //third-party/abc/...) are excluded
via the invocation pattern, not via tag, to keep submodules untouched.
Signed-off-by: SombraSoft <sombrio@sombrasoft.dev>
7e34940 to
0e99df2
Compare
|
clang-tidy review says "All clean, LGTM! 👍" |
|
FYI A bit of context. Relying on that, though, is also very fragile. The include paths in this project are super-leaky. The problem with the ambiguity of headers can not be resolved by using the fully qualified paths from project root ( Having said that, using the aspect-based linting will be beneficial as each file gets its own 'tailored' compile_flags.txt, so the memory consumption will not be a problem. So it can be more targeted. Also, bazel caching will help, similar to run-clang-tidy-cached.cc, to only run on changes. |
It isn't huge when using cmake (~4Mb). Why would that be different with Bazel? |
Because cmake essentially has to add /usr/include and /usr/local/include |
|
Thanks for the context @hzeller — agree on all points. To confirm the two practical concerns:
|
|
Nice. And can we use that then for the |
|
@maliberty — looking for your input on the rollout plan. This PR sets up something you've explicitly asked for (PR #9122 closeout: "it would be nice to have a clang-tidy run option available from bazel"), and the medium-term goal is to deprecate the current clang-tidy CI workflow that depends on our Why deprecate the fork-based workflowThe fork-based path has produced recurring transient issues tied to runner-environment drift: the How prior concerns map onto this design
@hzeller has endorsed the per-target compile-context approach and just asked directly whether the aspect output can power the PR-comment workflow — which is exactly step 3 of the rollout. Proposed rollout
Questions for you
|
|
@hzeller — re:
Yes — that's exactly the goal. Step 3 of the rollout I just laid out in the comment to @maliberty: replace the fork-based Tool for the swap is still open — reviewdog is the obvious off-the-shelf candidate, but since the aspect output is plain clang-tidy text, any consumer works (including a small custom GHA script filtering against |
Summary
Adds a hermetic, opt-in clang-tidy path via Bazel:
bazel build --config=lint //…. Usesaspect_rules_lint2.5.2 to driveclang-tidy20.1.8 (from@llvm_toolchain) against every cc target's ownCcInfoflags. Output lands as*.AspectRulesLintClangTidy.outfiles underbazel-bin/.This PR is purely additive. No CI behavior changes, no existing local-lint flow is removed, and nothing runs unless a developer or future workflow passes
--config=lint. The intent is to (eventually) replace theThe-OpenROAD-Project/clang-tidy-reviewfork in CI withreviewdog-style PR-comment generation that consumes the aspect's output, scoped to changed lines.Why another lint path
There are three existing systems; this is a fourth with a distinct purpose. Quick map:
clang-tidy-reviewfork)compile_commands.jsonetc/run-clang-tidy.sh(bant)compile_flags.txt@llvm_toolchain20.1.8//:lint_testumbrella--config=lintaspect (this PR)CcInfo@llvm_toolchain20.1.8Why this specifically:
@llvm_toolchain20.1.8 — same toolchain 3b already uses, byte-identical binary (sha confirmed). Closes the gap @maliberty raised in Feature Request: Migrate clang-format to Bazel Lint Umbrella #9860 about "format fights" between unpinned linter versions.graphics.h-style ambiguity @maliberty flagged on PR Provide a way to create a compilation DB in the bazel project #9122 (universalcompile_flags.txtcannot distinguish threegraphics.hfiles inmpl/,fin/,exa/).compile_commands.jsonwas based on its ~hundreds-of-MB size + GB-scale RAM consumption on parallel runs. The aspect emits no compile-db; each invocation is a small per-action argv. The memory concern doesn't apply.src/sta(OpenSTA) andthird-party/abc(ABC) are upstream-managed submodules. Both are excluded by invocation pattern (-//src/sta/...,-//third-party/abc/...), not byno-linttagging — that way the submodule trees stay unmodified and lint findings against them aren't surfaced (they'd belong upstream, not here).Non-goals (deliberately out of scope)
reviewdogagainst this aspect's output.etc/run-clang-tidy.sh/etc/bazel-make-compilation-db.sh. They serveclangdIDE integration and large-scale cleanup workflows that this aspect does not.//:lint_test. That umbrella issh_test-based and reruns the full script every invocation; using it for C++ would defeat Bazel's per-action lint cache. Aspects are bazel-native for this exact reason. The umbrella is right for tools without aspect equivalents (TCL, buildifier).Relationship to existing local lint (
etc/run-clang-tidy.sh)etc/run-clang-tidy.sh(system 3b) and this aspect produce different findings by design:c9bbe43a…), so tool version is in parity.compile_flags.txt; the aspect uses per-target flags. Concrete deltas:-stdlib=libc++,-fopenmp, per-target-Dmacros,-DSPDLOG_FMT_EXTERNAL, and per-target include paths.graphics.hissue).Recommended user-facing story (added to
docs/agents/ci.md):bazel build --config=lint //affected:targetetc/run-clang-tidy.shetc/run-clang-tidy.shcached runnerThe headline guarantee: once CI runs the aspect,
bazel build --config=lintlocally produces the same findings as CI, byte for byte, by sharing the action cache.Note: divergence between local lint (3b, ct20) and current CI (3a, ct19) already exists today and is unchanged by this PR. If anything, this PR narrows the gap by introducing a path with tool-version parity to 3b.
Pre-emptive responses to likely review feedback
aspect_rules_lint2.5.2 is a maintained Aspect.dev product, not action-internals scraping. It exposes a sanctioned API.//:lint_testumbrella?" — see "Non-goals" above; aspects ≠sh_test. Aspects cache per-target;sh_testreruns the full script. Wrong tool for C++ lint at this codebase's size.@llvm_toolchain20.1.8.compile_commands.jsonmemory blow-up?" — N/A. The aspect emits no compile-db.graphics.h-style universal-flag ambiguity?" — solved. Per-targetCcInfois the input.Test plan
bazel build --config=lint //src/utl/...— clean run, produces.AspectRulesLintClangTidy.outfilesbazel build --config=lint -- //src/... //third-party/... -//src/sta/... -//third-party/abc/...— runs across all eligible cc targetsHeaderFilterRegexfrom//:.clang-tidyis honored (53k+ external warnings suppressed; only user-code findings reported)gui_qt_headlessand friends)odb/grt/gpl/rsz/dpl: no user-code diagnostic that the fork catches is silently missed by the aspect. All A-vs-D deltas were traced to deliberate toolchain choices (-stdlib=libc++, hermetic__DATE__, external fmt, bundled omp.h), not aspect bugs.Files
Parity vs
etc/run-clang-tidy.sh(bantcompile_flags.txt) — measuredCaptured
clang-tidy20.1.8 output for one representative.cpp/.ccper module under both paths and diffed the user-code findings:--config=lint)run-clang-tidy.sh)odb/src/db/dbBTerm.cppgrt/src/Pin.cppgpl/src/initialPlace.cpprsz/src/PreChecks.ccdpl/src/dbToOpendp.cppThe single bant-only finding on
dbToOpendp.cppis in vendored boost (boost/polygon/rectangle_concept.hpp:791,clang-diagnostic-bitwise-instead-of-logical), not user code. The aspect's per-target compile context filters it out correctly; bant's universal flag set surfaces it as noise. The aspect is silent where it should be silent; bant is noisier than CI will be. This is the safe direction of divergence — a developer runningetc/run-clang-tidy.shlocally might see findings CI does not, but CI will never flag something the aspect-driven local lint missed.Diagnostic deltas vs the current CI fork (system 3a: clang-tidy 19 + cmake
compile_commands.json) were also measured and are explained entirely by deliberate toolchain choices in the hermetic Bazel environment (-stdlib=libc++, hermetic__DATE__/__TIME__, externalfmtvia-DSPDLOG_FMT_EXTERNAL, bundled openmp). No silent regressions in user-code diagnostics were observed.