Skip to content

Commit 7956b5e

Browse files
authored
Merge branch 'main' into update-docs
2 parents e89583a + edc4ae4 commit 7956b5e

4 files changed

Lines changed: 308 additions & 17 deletions

File tree

.pre-commit-config.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
ci:
2+
autofix_prs: true
3+
14
repos:
25
- repo: https://github.com/pre-commit/pre-commit-hooks
36
rev: v6.0.0
@@ -10,7 +13,7 @@ repos:
1013
- id: check-toml
1114
- id: requirements-txt-fixer
1215
- repo: https://github.com/astral-sh/ruff-pre-commit
13-
rev: v0.14.2
16+
rev: v0.15.8
1417
hooks:
1518
# Run the linter.
1619
- id: ruff-check

README.md

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ A pre-commit hook that automatically formats and lints your C/C++ code using `cl
1313
- [Quick Start](#quick-start)
1414
- [Custom Configuration Files](#custom-configuration-files)
1515
- [Custom Clang Tool Version](#custom-clang-tool-version)
16+
- [Compilation Database (CMake/Meson Projects)](#compilation-database-cmakemeson-projects)
1617
- [Output](#output)
1718
- [clang-format Output](#clang-format-output)
1819
- [clang-tidy Output](#clang-tidy-output)
@@ -75,6 +76,52 @@ repos:
7576
> [!TIP]
7677
> For production use, always pin the tool version explicitly with `--version` (e.g. `--version=21`) so upgrades to `cpp-linter-hooks` never silently change your linter version.
7778

79+
### Compilation Database (CMake/Meson Projects)
80+
81+
For CMake or Meson projects, clang-tidy works best with a `compile_commands.json`
82+
file that records the exact compiler flags used for each file. Without it, clang-tidy
83+
may report false positives from missing include paths or wrong compiler flags.
84+
85+
The hook auto-detects `compile_commands.json` in common build directories (`build/`,
86+
`out/`, `cmake-build-debug/`, `_build/`) and passes `-p <dir>` to clang-tidy
87+
automatically — no configuration needed for most projects:
88+
89+
```yaml
90+
repos:
91+
- repo: https://github.com/cpp-linter/cpp-linter-hooks
92+
rev: v1.2.0
93+
hooks:
94+
- id: clang-tidy
95+
args: [--checks=.clang-tidy]
96+
# Auto-detects ./build/compile_commands.json if present
97+
```
98+
99+
To specify the build directory explicitly:
100+
101+
```yaml
102+
- id: clang-tidy
103+
args: [--compile-commands=build, --checks=.clang-tidy]
104+
```
105+
106+
To disable auto-detection (e.g. in a monorepo where auto-detect might pick the wrong database):
107+
108+
```yaml
109+
- id: clang-tidy
110+
args: [--no-compile-commands, --checks=.clang-tidy]
111+
```
112+
113+
To see which `compile_commands.json` the hook is using, add `-v`:
114+
115+
```yaml
116+
- id: clang-tidy
117+
args: [--compile-commands=build, -v, --checks=.clang-tidy]
118+
```
119+
120+
> [!NOTE]
121+
> Generate `compile_commands.json` with CMake using `cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -Bbuild .`
122+
> or add `set(CMAKE_EXPORT_COMPILE_COMMANDS ON)` to your `CMakeLists.txt`.
123+
> `--compile-commands` takes the **directory** containing `compile_commands.json`, not the file path itself.
124+
78125
## Output
79126

80127
### clang-format Output
@@ -175,15 +222,19 @@ This approach ensures that only modified files are checked, further speeding up
175222
### Verbose Output
176223

177224
> [!NOTE]
178-
> Use `-v` or `--verbose` in `args` of `clang-format` to show the list of processed files e.g.:
225+
> Use `-v` or `--verbose` in `args` to enable verbose output.
226+
> For `clang-format`, it shows the list of processed files.
227+
> For `clang-tidy`, it prints which `compile_commands.json` is being used (when auto-detected or explicitly set).
179228

180229
```yaml
181230
repos:
182231
- repo: https://github.com/cpp-linter/cpp-linter-hooks
183232
rev: v1.2.0
184233
hooks:
185234
- id: clang-format
186-
args: [--style=file, --version=21, --verbose] # Add -v or --verbose for detailed output
235+
args: [--style=file, --version=21, --verbose] # Shows processed files
236+
- id: clang-tidy
237+
args: [--checks=.clang-tidy, --verbose] # Shows which compile_commands.json is used
187238
```
188239

189240
## FAQ
@@ -199,6 +250,7 @@ repos:
199250
| Supports passing format style string | ✅ via `--style` | ❌ |
200251
| Verbose output | ✅ via `--verbose` | ❌ |
201252
| Dry-run mode | ✅ via `--dry-run` | ❌ |
253+
| Compilation database support | ✅ auto-detect or `--compile-commands` | ❌ |
202254

203255

204256
<!-- > [!TIP]

cpp_linter_hooks/clang_tidy.py

Lines changed: 76 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,96 @@
11
import subprocess
2+
import sys
23
from argparse import ArgumentParser
3-
from typing import Tuple
4+
from pathlib import Path
5+
from typing import Optional, Tuple
46

57
from cpp_linter_hooks.util import resolve_install, DEFAULT_CLANG_TIDY_VERSION
68

9+
COMPILE_DB_SEARCH_DIRS = ["build", "out", "cmake-build-debug", "_build"]
710

811
parser = ArgumentParser()
912
parser.add_argument("--version", default=DEFAULT_CLANG_TIDY_VERSION)
13+
parser.add_argument("--compile-commands", default=None, dest="compile_commands")
14+
parser.add_argument(
15+
"--no-compile-commands", action="store_true", dest="no_compile_commands"
16+
)
17+
parser.add_argument("-v", "--verbose", action="store_true")
1018

1119

12-
def run_clang_tidy(args=None) -> Tuple[int, str]:
13-
hook_args, other_args = parser.parse_known_args(args)
14-
if hook_args.version:
15-
resolve_install("clang-tidy", hook_args.version)
16-
command = ["clang-tidy"] + other_args
20+
def _find_compile_commands() -> Optional[str]:
21+
for d in COMPILE_DB_SEARCH_DIRS:
22+
if (Path(d) / "compile_commands.json").exists():
23+
return d
24+
return None
25+
26+
27+
def _resolve_compile_db(
28+
hook_args, other_args
29+
) -> Tuple[Optional[str], Optional[Tuple[int, str]]]:
30+
"""Resolve the compile_commands.json directory to pass as -p to clang-tidy.
31+
32+
Returns (db_path, None) on success or (None, (retval, message)) on error.
33+
"""
34+
if hook_args.no_compile_commands:
35+
return None, None
36+
37+
# Covers both "-p ./build" (two tokens) and "-p=./build" (one token)
38+
has_p = any(a == "-p" or a.startswith("-p=") for a in other_args)
39+
40+
if hook_args.compile_commands:
41+
if has_p:
42+
print(
43+
"Warning: --compile-commands ignored; -p already in args",
44+
file=sys.stderr,
45+
)
46+
return None, None
47+
p = Path(hook_args.compile_commands)
48+
if not p.is_dir() or not (p / "compile_commands.json").exists():
49+
return None, (
50+
1,
51+
f"--compile-commands: no compile_commands.json"
52+
f" in '{hook_args.compile_commands}'",
53+
)
54+
return hook_args.compile_commands, None
55+
56+
if not has_p:
57+
return _find_compile_commands(), None
58+
59+
return None, None
1760

18-
retval = 0
19-
output = ""
61+
62+
def _exec_clang_tidy(command) -> Tuple[int, str]:
63+
"""Run clang-tidy and return (retval, output)."""
2064
try:
2165
sp = subprocess.run(
2266
command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8"
2367
)
24-
retval = sp.returncode
2568
output = (sp.stdout or "") + (sp.stderr or "")
26-
if "warning:" in output or "error:" in output:
27-
retval = 1
69+
retval = (
70+
1 if sp.returncode != 0 or "warning:" in output or "error:" in output else 0
71+
)
2872
return retval, output
29-
except FileNotFoundError as stderr:
30-
retval = 1
31-
return retval, str(stderr)
73+
except FileNotFoundError as e:
74+
return 1, str(e)
75+
76+
77+
def run_clang_tidy(args=None) -> Tuple[int, str]:
78+
hook_args, other_args = parser.parse_known_args(args)
79+
if hook_args.version:
80+
resolve_install("clang-tidy", hook_args.version)
81+
82+
compile_db_path, error = _resolve_compile_db(hook_args, other_args)
83+
if error is not None:
84+
return error
85+
86+
if compile_db_path:
87+
if hook_args.verbose:
88+
print(
89+
f"Using compile_commands.json from: {compile_db_path}", file=sys.stderr
90+
)
91+
other_args = ["-p", compile_db_path] + other_args
92+
93+
return _exec_clang_tidy(["clang-tidy"] + other_args)
3294

3395

3496
def main() -> int:

0 commit comments

Comments
 (0)