Skip to content

Commit e6dbba2

Browse files
committed
refactor: clean up linter scripts
1 parent 3b454f0 commit e6dbba2

File tree

7 files changed

+129
-165
lines changed

7 files changed

+129
-165
lines changed

.github/copilot-instructions.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ All lint scripts are in `scripts/lint/`. They MUST pass before submitting change
9999

100100
```bash
101101
# Run ALL linters (recommended)
102-
./scripts/lint/lint-all.py
102+
./scripts/lint/all.py
103103

104104
# Individual linters
105105
./scripts/lint/lint-version.py # Check version consistency across files
@@ -149,7 +149,7 @@ All lint scripts are in `scripts/lint/`. They MUST pass before submitting change
149149

150150
### Common CI Failures & Fixes
151151

152-
1. **Linting failures**: Run `./scripts/lint/lint-all.py` locally first
152+
1. **Linting failures**: Run `./scripts/lint/all.py` locally first
153153
2. **32-bit failures**: Don't use `size_t` in hashes without consideration
154154
3. **Sanitizer failures**: Check for undefined behavior, use-after-free, data races
155155
4. **Windows/MSVC**: Check for MSVC-specific warnings (see `test/meson.build`)
@@ -216,7 +216,7 @@ unordered_dense/
216216

217217
5. **Lint**: Verify all linters pass
218218
```bash
219-
./scripts/lint/lint-all.py
219+
./scripts/lint/all.py
220220
```
221221

222222
6. **Multi-config** (optional): Test across configurations
@@ -300,4 +300,4 @@ unordered_dense/
300300
- You need information not covered here
301301
- The repository structure has changed significantly
302302

303-
When in doubt, run `./scripts/lint/lint-all.py` and `meson test -C builddir/<config> -v` to verify your changes.
303+
When in doubt, run `./scripts/lint/all.py` and `meson test -C builddir/<config> -v` to verify your changes.

scripts/build.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def run(cmd):
5454
if result.returncode != 0:
5555
exit(result.returncode)
5656

57-
run('scripts/lint/lint-all.py')
57+
run('scripts/lint/all.py')
5858

5959
for cmd_dir in cmd_and_dir:
6060
workdir = cmd_dir[-1]

scripts/lint/all.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#!/usr/bin/env python3
2+
3+
from pathlib import Path
4+
from subprocess import run
5+
from time import perf_counter
6+
7+
8+
def main():
9+
start = perf_counter()
10+
linters_dir = Path(__file__).parent
11+
linters = sorted(
12+
p for p in linters_dir.iterdir() if p.is_file() and p.name.startswith("lint-")
13+
)
14+
15+
rc = 0
16+
for lint in linters:
17+
res = run([str(lint)])
18+
if res.returncode:
19+
print(f"^---- failure(s) from {lint.name}\n")
20+
rc |= res.returncode
21+
22+
print(f"{len(linters)} linters in {perf_counter() - start:0.2f}s")
23+
raise SystemExit(rc)
24+
25+
26+
if __name__ == "__main__":
27+
main()

scripts/lint/lint-all.py

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

scripts/lint/lint-clang-format.py

Lines changed: 33 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,38 @@
11
#!/usr/bin/env python3
2-
3-
from glob import glob
42
from pathlib import Path
3+
import re
4+
import shutil
55
from subprocess import run
6-
from os import path
7-
import subprocess
86
import sys
9-
from time import time
10-
import re
117

12-
root_path = path.abspath(Path(__file__).parent.parent.parent)
13-
14-
globs = [
15-
f"{root_path}/include/**/*.h",
16-
f"{root_path}/test/**/*.h",
17-
f"{root_path}/test/**/*.cpp",
18-
]
19-
exclusions = [
20-
"nanobench\\.h",
21-
"FuzzedDataProvider\\.h",
22-
'/third-party/']
23-
24-
files = []
25-
for g in globs:
26-
r = glob(g, recursive=True)
27-
files.extend(r)
28-
29-
# filter out exclusions
30-
for exclusion in exclusions:
31-
l = filter(lambda file: re.search(exclusion, file) == None, files)
32-
files = list(l)
33-
34-
if len(files) == 0:
35-
print("could not find any files!")
36-
sys.exit(1)
37-
38-
command = ['clang-format', '--dry-run', '-Werror'] + files
39-
p = subprocess.Popen(command,
40-
stdout=subprocess.PIPE,
41-
stderr=None,
42-
stdin=subprocess.PIPE,
43-
universal_newlines=True)
44-
45-
stdout, stderr = p.communicate()
46-
47-
print(f"clang-format checked {len(files)} files")
48-
49-
if p.returncode != 0:
50-
sys.exit(p.returncode)
8+
ROOT = Path(__file__).resolve().parents[2]
9+
PATTERNS = ["include/**/*.h", "test/**/*.h", "test/**/*.cpp"]
10+
EXCLUDE_RE = re.compile(r"nanobench\.h|FuzzedDataProvider\.h|/third-party/")
11+
12+
13+
def collect_files(root: Path):
14+
return [
15+
f
16+
for p in PATTERNS
17+
for f in root.glob(p)
18+
if f.is_file() and not EXCLUDE_RE.search(str(f))
19+
]
20+
21+
22+
def main():
23+
files = collect_files(ROOT)
24+
if not files:
25+
print("could not find any files!")
26+
raise SystemExit(1)
27+
28+
if not (clang_format := shutil.which("clang-format")):
29+
print("clang-format not found in PATH")
30+
raise SystemExit(2)
31+
32+
ec = run([clang_format, "--dry-run", "-Werror"] + files).returncode
33+
print(f"clang-format checked {len(files)} files")
34+
SystemExit(ec)
35+
36+
37+
if __name__ == "__main__":
38+
main()

scripts/lint/lint-clang-tidy.py

Lines changed: 25 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,37 @@
11
#!/usr/bin/env python3
2-
"""Run clang-tidy only on unordered_dense.h"""
3-
42
import argparse
5-
import subprocess
3+
import shutil
4+
from subprocess import run
65
import sys
7-
from pathlib import Path
86

97

108
def main():
11-
parser = argparse.ArgumentParser(
12-
description="Run clang-tidy on unordered_dense.h (via include_only.cpp)"
13-
)
14-
parser.add_argument(
15-
"--std",
16-
default="c++17",
17-
help="C++ standard to use (default: c++17)",
9+
p = argparse.ArgumentParser(
10+
description=(
11+
"Run clang-tidy on unordered_dense.h (via test/unit/include_only.cpp)"
12+
)
1813
)
19-
args = parser.parse_args()
20-
21-
# Define paths
22-
source_file = Path("test/unit/include_only.cpp")
23-
24-
cmd = [
25-
"clang-tidy",
26-
str(source_file),
27-
"--header-filter=.*unordered_dense\\.h",
28-
"--warnings-as-errors=*",
29-
"--",
30-
f"-std={args.std}",
31-
"-I",
32-
"include",
33-
]
14+
p.add_argument("--std", default="c++17", help="C++ standard (default: c++17)")
15+
args = p.parse_args()
3416

35-
try:
36-
subprocess.run(cmd, check=True)
37-
except subprocess.CalledProcessError as e:
38-
sys.exit(e.returncode)
39-
except FileNotFoundError:
17+
if not (clang_tidy := shutil.which("clang-tidy")):
4018
print("Error: clang-tidy not found in PATH", file=sys.stderr)
41-
sys.exit(1)
19+
raise SystemExit(1)
20+
21+
# Exit with clang-tidy's exit code.
22+
ec = run(
23+
[
24+
clang_tidy,
25+
"test/unit/include_only.cpp",
26+
"--header-filter=.*unordered_dense\\.h",
27+
"--warnings-as-errors=*",
28+
"--",
29+
f"-std={args.std}",
30+
"-I",
31+
"include",
32+
]
33+
).returncode
34+
raise SystemExit(ec)
4235

4336

4437
if __name__ == "__main__":

scripts/lint/lint-version.py

Lines changed: 39 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,45 @@
11
#!/usr/bin/env python3
2-
3-
import os
4-
import pathlib
2+
from pathlib import Path
53
import re
64

7-
8-
root = os.path.abspath(pathlib.Path(__file__).parent.parent.parent)
9-
10-
# filename, pattern, number of occurrences
11-
file_pattern_count = [
12-
(f"{root}/CMakeLists.txt", r"^\s+VERSION (\d+)\.(\d+)\.(\d+)\n", 1),
13-
(f"{root}/include/ankerl/stl.h", r"Version (\d+)\.(\d+)\.(\d+)\n", 1),
14-
(f"{root}/include/ankerl/unordered_dense.h", r"Version (\d+)\.(\d+)\.(\d+)\n", 1),
15-
(f"{root}/meson.build", r"version: '(\d+)\.(\d+)\.(\d+)'", 1),
16-
(f"{root}/test/unit/namespace.cpp", r"unordered_dense::v(\d+)_(\d+)_(\d+)", 1),
5+
# fmt: off
6+
ROOT = Path(__file__).resolve().parents[2]
7+
HEADER = ROOT / "include" / "ankerl" / "unordered_dense.h"
8+
CHECKS = [
9+
(HEADER, r"Version (\d+)\.(\d+)\.(\d+)", 1),
10+
(ROOT / "CMakeLists.txt", r"^\s+VERSION (\d+)\.(\d+)\.(\d+)", 1),
11+
(ROOT / "include" / "ankerl" / "stl.h", r"Version (\d+)\.(\d+)\.(\d+)", 1),
12+
(ROOT / "meson.build", r"version:\s*'?(\d+)\.(\d+)\.(\d+)'?", 1),
13+
(ROOT / "test" / "unit" / "namespace.cpp", r"unordered_dense::v(\d+)_(\d+)_(\d+)", 1),
1714
]
18-
19-
# let's parse the reference from svector.h
20-
major = "??"
21-
minor = "??"
22-
patch = "??"
23-
with open(f"{root}/include/ankerl/unordered_dense.h", "r") as f:
24-
for line in f:
25-
r = re.search(r"#define ANKERL_UNORDERED_DENSE_VERSION_([A-Z]+) (\d+)", line)
26-
if not r:
27-
continue
28-
29-
if "MAJOR" == r.group(1):
30-
major = r.group(2)
31-
elif "MINOR" == r.group(1):
32-
minor = r.group(2)
33-
elif "PATCH" == r.group(1):
34-
patch = r.group(2)
35-
else:
36-
"match but with something else!"
37-
exit(1)
38-
39-
is_ok = True
40-
for filename, pattern, count in file_pattern_count:
41-
num_found = 0
42-
with open(filename, "r") as f:
43-
for line in f:
44-
r = re.search(pattern, line)
45-
if r:
46-
num_found += 1
47-
if major != r.group(1) or minor != r.group(2) or patch != r.group(3):
48-
is_ok = False
49-
print(
50-
f"ERROR in {filename}: got '{line.strip()}' but version should be '{major}.{minor}.{patch}'"
51-
)
52-
if num_found != count:
53-
is_ok = False
54-
print(
55-
f"ERROR in {filename}: expected {count} occurrences but found it {num_found} times"
15+
# fmt: on
16+
17+
18+
def read_version_from_header(p: Path) -> str:
19+
m = re.findall(
20+
r"#define\s+ANKERL_UNORDERED_DENSE_VERSION_(MAJOR|MINOR|PATCH)\s+(\d+)",
21+
p.read_text(),
22+
)
23+
d = dict(m)
24+
return f"{d['MAJOR']}.{d['MINOR']}.{d['PATCH']}"
25+
26+
27+
def main():
28+
ref = read_version_from_header(HEADER)
29+
errs = []
30+
for path, pattern, count in CHECKS:
31+
matches = list(re.finditer(pattern, path.read_text(), re.M))
32+
if (n := len(matches)) != count:
33+
errs.append(f"ERROR: {path}: expected {count} matches, found {n}")
34+
errs.extend(
35+
f"ERROR: {path}: found version {found}, expected {ref}"
36+
for m in matches
37+
if (found := ".".join(m.groups())) != ref
5638
)
5739

58-
if not is_ok:
59-
exit(1)
40+
print("\n".join(errs))
41+
raise SystemExit(1 if errs else 0)
42+
43+
44+
if __name__ == "__main__":
45+
main()

0 commit comments

Comments
 (0)