Skip to content

Commit 5d11241

Browse files
committed
build: add clang-tidy integration
- Add meson infrastructure for clang-tidy in nix-meson-build-support/common/clang-tidy/ with compile_commands.json cleaning and parallel runner - Create custom clang-tidy plugin scaffolding in src/clang-tidy-plugin/ - Add enableClangTidyLayer in packaging/components.nix that runs clang-tidy on all C++ components via postBuild hook - Add clangTidy Hydra job in packaging/hydra.nix - Add clang-tidy CI job in .github/workflows/ci.yml to enforce on PRs - Document usage in doc/manual/source/development/static-analysis.md Uses debug build mode and skips tests for faster CI. Warnings are treated as errors to catch issues early.
1 parent 423e732 commit 5d11241

File tree

19 files changed

+702
-3
lines changed

19 files changed

+702
-3
lines changed

.clang-tidy

Lines changed: 0 additions & 3 deletions
This file was deleted.

.clang-tidy

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
nix-meson-build-support/common/clang-tidy/.clang-tidy

.github/workflows/ci.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,23 @@ jobs:
271271
docker tag nix:$NIX_VERSION $IMAGE_ID:master
272272
docker push $IMAGE_ID:master
273273
274+
clang-tidy:
275+
needs: basic-checks
276+
runs-on: ubuntu-24.04
277+
name: clang-tidy
278+
steps:
279+
- uses: actions/checkout@v6
280+
with:
281+
fetch-depth: 0
282+
- uses: ./.github/actions/install-nix-action
283+
with:
284+
github_token: ${{ secrets.GITHUB_TOKEN }}
285+
dogfood: ${{ github.event_name == 'workflow_dispatch' && inputs.dogfood || github.event_name != 'workflow_dispatch' }}
286+
extra_nix_config: "sandbox = true"
287+
- run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
288+
- name: Run clang-tidy
289+
run: nix build .#hydraJobs.clangTidy.x86_64-linux -L
290+
274291
flake_regressions:
275292
needs: tests
276293
runs-on: ubuntu-24.04

doc/manual/source/SUMMARY.md.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@
147147
- [CLI guideline](development/cli-guideline.md)
148148
- [JSON guideline](development/json-guideline.md)
149149
- [C++ style guide](development/cxx.md)
150+
- [Static Analysis](development/static-analysis.md)
150151
- [Experimental Features](development/experimental-features.md)
151152
- [Contributing](development/contributing.md)
152153
- [Releases](release-notes/index.md)
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Static Analysis
2+
3+
Nix uses [clang-tidy](https://clang.llvm.org/extra/clang-tidy/) for static analysis of C++ code.
4+
This helps catch bugs, enforce coding standards, and maintain code quality.
5+
6+
## Running clang-tidy locally
7+
8+
To run clang-tidy on the entire codebase in the development shell:
9+
10+
```console
11+
$ nix develop .#native-clangStdenv
12+
$ configurePhase
13+
$ meson compile -C build clang-tidy
14+
```
15+
16+
This will analyze all C++ source files and report any warnings.
17+
18+
To automatically apply fixes for certain warnings:
19+
20+
```console
21+
$ meson compile -C build clang-tidy-fix
22+
```
23+
24+
> **Warning**: Review the changes before committing, as automatic fixes may not always be correct.
25+
26+
## CI integration
27+
28+
clang-tidy runs automatically on every pull request via GitHub Actions.
29+
The CI job builds `.#hydraJobs.clangTidy.x86_64-linux` which:
30+
31+
1. Builds all components with debug mode (for faster compilation)
32+
2. Runs clang-tidy on each component
33+
3. Fails if any warnings are found (warnings are treated as errors)
34+
35+
## Configuration
36+
37+
The clang-tidy configuration is in `.clang-tidy` at the repository root (symlinked from `nix-meson-build-support/common/clang-tidy/.clang-tidy`).
38+
39+
### Suppressing warnings
40+
41+
If a warning is a false positive, you can suppress it in several ways:
42+
43+
1. **Inline suppression** (preferred for specific cases):
44+
```cpp
45+
// NOLINTBEGIN(bugprone-some-check)
46+
... code ...
47+
// NOLINTEND(bugprone-some-check)
48+
```
49+
Or for a single line:
50+
```cpp
51+
int x = something(); // NOLINT(bugprone-some-check)
52+
```
53+
54+
2. **Configuration file** (for project-wide suppression):
55+
Add the check to the disabled list in `.clang-tidy`:
56+
```yaml
57+
Checks:
58+
- -bugprone-some-check # Reason for disabling
59+
```
60+
61+
3. **Check options** (for configuring check behavior):
62+
```yaml
63+
CheckOptions:
64+
bugprone-reserved-identifier.AllowedIdentifiers: '__some_identifier'
65+
```
66+
67+
### Adding new checks
68+
69+
To enable additional checks:
70+
71+
1. Edit `nix-meson-build-support/common/clang-tidy/.clang-tidy`
72+
2. Add the check to the `Checks` list
73+
3. Run clang-tidy locally to see the impact
74+
4. Fix any new warnings or disable specific sub-checks if needed
75+
76+
## Custom clang-tidy plugin
77+
78+
The Nix project includes infrastructure for custom clang-tidy checks in `src/clang-tidy-plugin/`.
79+
These checks can enforce Nix-specific coding patterns that aren't covered by standard clang-tidy checks.
80+
81+
To add a new custom check:
82+
83+
1. Add the check implementation in `src/clang-tidy-plugin/`
84+
2. Register it in `NixClangTidyChecks.cc`
85+
3. Enable it in `.clang-tidy` with the `nix-` prefix

flake.nix

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,10 @@
424424
"nix-perl-bindings" = {
425425
supportsCross = false;
426426
};
427+
428+
"nix-clang-tidy-plugin" = {
429+
supportsCross = false;
430+
};
427431
}
428432
(
429433
pkgName:

meson.build

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,6 @@ endif
6666
if get_option('kaitai-struct-checks')
6767
subproject('kaitai-struct-checks')
6868
endif
69+
70+
# Static Analysis
71+
subdir('nix-meson-build-support/common/clang-tidy')
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
UseColor: true
2+
Checks:
3+
- -*
4+
- bugprone-*
5+
# Too many warnings
6+
- -bugprone-assignment-in-if-condition
7+
# Too many warnings
8+
- -bugprone-narrowing-conversions
9+
# Kind of nonsense
10+
- -bugprone-easily-swappable-parameters
11+
# Too many warnings for now
12+
- -bugprone-implicit-widening-of-multiplication-result
13+
# Exception handling patterns in Nix
14+
- -bugprone-empty-catch
15+
# Many warnings
16+
- -bugprone-unchecked-optional-access
17+
# Many warnings, questionable lint
18+
- -bugprone-branch-clone
19+
# Extremely noisy before clang 19: https://github.com/llvm/llvm-project/issues/93959
20+
- -bugprone-multi-level-implicit-pointer-conversion
21+
# We don't compile out our asserts
22+
- -bugprone-assert-side-effect
23+
# TODO: figure out if this warning is useful
24+
- -bugprone-exception-escape
25+
# We use pointers to aggregates intentionally; void * would look weird
26+
- -bugprone-sizeof-expression
27+
#
28+
# Checks disabled to pass on current codebase (can be progressively enabled):
29+
#
30+
# 11 warnings - optional value conversions in various places
31+
- -bugprone-optional-value-conversion
32+
# 4 warnings - switches without default cases
33+
- -bugprone-switch-missing-default-case
34+
# 4 warnings - string_view::data() usage patterns
35+
- -bugprone-suspicious-stringview-data-usage
36+
# 4 warnings - .cc files included in other files (intentional pattern)
37+
- -bugprone-suspicious-include
38+
# 2 warnings - unused return values (AllowCastToVoid helps but some remain)
39+
- -bugprone-unused-return-value
40+
# 2 warnings - unused local RAII-style variables
41+
- -bugprone-unused-local-non-trivial-variable
42+
# 2 warnings - returning const& from parameter
43+
- -bugprone-return-const-ref-from-parameter
44+
# 1 warning - unsafe C functions (e.g., getenv)
45+
- -bugprone-unsafe-functions
46+
# 1 warning - signed char misuse
47+
- -bugprone-signed-char-misuse
48+
# 1 warning - calling parent virtual instead of override
49+
- -bugprone-parent-virtual-call
50+
# 1 warning - null termination issues
51+
- -bugprone-not-null-terminated-result
52+
# 1 warning - macro parentheses
53+
- -bugprone-macro-parentheses
54+
# 1 warning - increment/decrement in conditions
55+
- -bugprone-inc-dec-in-conditions
56+
#
57+
# Non-bugprone checks (also disabled to pass on current codebase):
58+
#
59+
# 4 warnings - exceptions not derived from std::exception
60+
# All thrown exceptions must derive from std::exception
61+
# - hicpp-exception-baseclass
62+
# 88 warnings - C-style casts should be explicit about intent
63+
# - cppcoreguidelines-pro-type-cstyle-cast
64+
# Capturing async lambdas are dangerous (no warnings, kept enabled)
65+
- cppcoreguidelines-avoid-capturing-lambda-coroutines
66+
# Custom nix checks (when added)
67+
- nix-*
68+
69+
CheckOptions:
70+
# __asan_default_options: ASAN runtime configuration function (see nix-meson-build-support/common/asan-options/)
71+
# __wrap___assert_fail: Linker-wrapped assert handler for better stack traces (see nix-meson-build-support/common/assert-fail/)
72+
# _SingleDerivedPathRaw, _DerivedPathRaw: Internal type aliases in derived-path.hh (leading underscore pattern)
73+
# _SingleBuiltPathRaw, _BuiltPathRaw: Internal type aliases in built-path.hh (leading underscore pattern)
74+
bugprone-reserved-identifier.AllowedIdentifiers: '__asan_default_options;__wrap___assert_fail;_SingleDerivedPathRaw;_DerivedPathRaw;_SingleBuiltPathRaw;_BuiltPathRaw'
75+
# Allow explicitly discarding return values with (void) cast
76+
bugprone-unused-return-value.AllowCastToVoid: true
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Builds all generated files before running clang-tidy.
4+
5+
clang-tidy needs all source files and headers to exist before it can
6+
analyze the code. This script queries Ninja for all custom command
7+
targets that generate files needed for analysis and builds them.
8+
9+
Generated files include:
10+
- .gen.hh: Embedded file headers (SQL schemas, Nix expressions, etc.)
11+
- .gen.inc: Generated include files
12+
- lexer-tab.cc, parser-tab.cc: Flex/Bison generated parsers
13+
- Perl XS bindings, Kaitai parsers, and other generated sources
14+
15+
See: https://github.com/mesonbuild/meson/issues/12817
16+
"""
17+
18+
import subprocess
19+
20+
21+
def get_targets_of_rule(build_root: str, rule_name: str) -> list[str]:
22+
return (
23+
subprocess.check_output(
24+
["ninja", "-C", build_root, "-t", "targets", "rule", rule_name]
25+
)
26+
.decode()
27+
.strip()
28+
.splitlines()
29+
)
30+
31+
32+
def ninja_build(build_root: str, targets: list[str]):
33+
if targets:
34+
subprocess.check_call(["ninja", "-C", build_root, "--", *targets])
35+
36+
37+
def main():
38+
import argparse
39+
40+
ap = argparse.ArgumentParser(description="Build required targets for clang-tidy")
41+
ap.add_argument("build_root", help="Ninja build root", type=str)
42+
43+
args = ap.parse_args()
44+
45+
custom_commands = get_targets_of_rule(args.build_root, "CUSTOM_COMMAND")
46+
47+
targets = (
48+
# Generated headers from embedded files
49+
[t for t in custom_commands if t.endswith(".gen.hh")]
50+
# Generated include files
51+
+ [t for t in custom_commands if t.endswith(".gen.inc")]
52+
# Flex/Bison generated parsers
53+
+ [t for t in custom_commands if t.endswith("-tab.cc")]
54+
# Kaitai Struct generated parsers
55+
+ [t for t in custom_commands if t.endswith(".cpp") and "kaitai" in t]
56+
# Perl XS generated bindings
57+
+ [t for t in custom_commands if t.endswith(".cc") and "perl" in t.lower()]
58+
)
59+
ninja_build(args.build_root, targets)
60+
61+
62+
if __name__ == "__main__":
63+
main()

0 commit comments

Comments
 (0)