Skip to content

Commit cd833ea

Browse files
committed
Add a dockerfile to make pretty
Use tools what GitHub Precommit workflow uses
1 parent 49c309c commit cd833ea

2 files changed

Lines changed: 199 additions & 0 deletions

File tree

tools/precommit/Dockerfile

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# syntax=docker/dockerfile:1.7
2+
# Usage: DOCKER_BUILDKIT=1 docker buildx build -f Dockerfile -t abacus-make-pretty ../.. --target export --progress=plain --output type=local,dest=../..
3+
4+
FROM ubuntu:24.04 AS pretty
5+
6+
ARG DEBIAN_FRONTEND=noninteractive
7+
ARG BUILD_DIR=build
8+
ARG JOBS=1
9+
ARG CMAKE_ARGS=""
10+
ARG CLANG_TIDY_EXTRA_ARGS=""
11+
ARG STRICT_CLANG_TIDY=0
12+
13+
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
14+
15+
RUN apt-get update && apt-get install -y --no-install-recommends \
16+
ca-certificates \
17+
bash \
18+
git \
19+
findutils \
20+
build-essential \
21+
cmake \
22+
ninja-build \
23+
pkg-config \
24+
python3 \
25+
clang \
26+
clang-tidy \
27+
clang-format \
28+
openmpi-bin \
29+
libopenmpi-dev \
30+
libfftw3-dev \
31+
libelpa-dev \
32+
libopenblas-dev \
33+
libscalapack-openmpi-dev \
34+
libxc-dev \
35+
libcereal-dev \
36+
libgtest-dev \
37+
libgmock-dev \
38+
libomp-dev \
39+
&& rm -rf /var/lib/apt/lists/*
40+
41+
WORKDIR /repo
42+
43+
COPY . .
44+
45+
ENV BUILD_DIR=${BUILD_DIR}
46+
ENV JOBS=${JOBS}
47+
ENV CMAKE_ARGS=${CMAKE_ARGS}
48+
ENV CLANG_TIDY_EXTRA_ARGS=${CLANG_TIDY_EXTRA_ARGS}
49+
ENV STRICT_CLANG_TIDY=${STRICT_CLANG_TIDY}
50+
51+
RUN python3 /repo/tools/precommit/make-pretty.py
52+
53+
FROM scratch AS export
54+
COPY --from=pretty /out/ /

tools/precommit/make-pretty.py

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
#!/usr/bin/env python3
2+
3+
import json
4+
import os
5+
import shutil
6+
import subprocess
7+
from pathlib import Path
8+
9+
root = Path("/repo")
10+
build_dir = Path(os.environ.get("BUILD_DIR", "build"))
11+
12+
cpp_format_exts = {
13+
".c", ".cc", ".cpp", ".cxx", ".c++",
14+
".h", ".hh", ".hpp", ".hxx",
15+
".ipp", ".tpp",
16+
".cu", ".cuh",
17+
}
18+
19+
tidy_source_exts = {
20+
".cc", ".cpp", ".cxx", ".c++",
21+
}
22+
23+
exclude_names = {
24+
".git",
25+
"build",
26+
build_dir.name,
27+
".cache",
28+
}
29+
30+
def is_excluded(path: Path) -> bool:
31+
return any(part in exclude_names or part.startswith("cmake-build-") for part in path.parts)
32+
33+
def run(cmd, check=True):
34+
print("+ " + " ".join(cmd), flush=True)
35+
return subprocess.run(cmd, check=check)
36+
37+
os.chdir(root)
38+
39+
compile_db = root / build_dir / "compile_commands.json"
40+
41+
if not compile_db.exists():
42+
cmake_args = os.environ.get("CMAKE_ARGS", "").split()
43+
run([
44+
"cmake",
45+
"-S", ".",
46+
"-B", str(build_dir),
47+
"-G", "Ninja",
48+
"-DCMAKE_EXPORT_COMPILE_COMMANDS=ON",
49+
*cmake_args,
50+
])
51+
52+
if not compile_db.exists():
53+
raise SystemExit(f"ERROR: {compile_db} was not generated")
54+
55+
with compile_db.open("r", encoding="utf-8") as f:
56+
db = json.load(f)
57+
58+
tidy_files = []
59+
seen = set()
60+
61+
for entry in db:
62+
filename = entry.get("file")
63+
if not filename:
64+
continue
65+
66+
path = Path(filename)
67+
if not path.is_absolute():
68+
path = Path(entry.get("directory", root)) / path
69+
70+
try:
71+
rel = path.resolve().relative_to(root.resolve())
72+
except ValueError:
73+
continue
74+
75+
if is_excluded(rel):
76+
continue
77+
78+
if rel.suffix in tidy_source_exts and rel.exists() and rel not in seen:
79+
tidy_files.append(rel)
80+
seen.add(rel)
81+
82+
print(f"==> clang-tidy translation units: {len(tidy_files)}", flush=True)
83+
84+
tidy_failures = []
85+
extra = os.environ.get("CLANG_TIDY_EXTRA_ARGS", "").split()
86+
strict = os.environ.get("STRICT_CLANG_TIDY", "0") == "1"
87+
88+
for rel in tidy_files:
89+
cmd = [
90+
"clang-tidy",
91+
str(rel),
92+
f"-p={build_dir}",
93+
"--fix-errors",
94+
*extra,
95+
]
96+
print("+ " + " ".join(cmd), flush=True)
97+
ret = subprocess.run(cmd).returncode
98+
if ret != 0:
99+
tidy_failures.append((str(rel), ret))
100+
print(f"WARNING: clang-tidy failed for {rel} with exit code {ret}", flush=True)
101+
102+
if tidy_failures:
103+
print("==> clang-tidy failures:", flush=True)
104+
for filename, ret in tidy_failures:
105+
print(f" {ret}: {filename}", flush=True)
106+
if strict:
107+
raise SystemExit("ERROR: clang-tidy failed and STRICT_CLANG_TIDY=1")
108+
109+
format_files = []
110+
111+
for path in root.rglob("*"):
112+
if not path.is_file():
113+
continue
114+
115+
rel = path.relative_to(root)
116+
if is_excluded(rel):
117+
continue
118+
119+
if path.suffix in cpp_format_exts:
120+
format_files.append(rel)
121+
122+
print(f"==> clang-format files: {len(format_files)}", flush=True)
123+
124+
batch = []
125+
for rel in format_files:
126+
batch.append(str(rel))
127+
if len(batch) >= 100:
128+
run(["clang-format", "-i", "-style=file", "--fallback-style=LLVM", *batch])
129+
batch.clear()
130+
131+
if batch:
132+
run(["clang-format", "-i", "-style=file", "--fallback-style=LLVM", *batch])
133+
134+
out = Path("/out")
135+
if out.exists():
136+
shutil.rmtree(out)
137+
out.mkdir(parents=True)
138+
139+
for rel in format_files:
140+
src = root / rel
141+
dst = out / rel
142+
dst.parent.mkdir(parents=True, exist_ok=True)
143+
shutil.copy2(src, dst)
144+
145+
print("==> Exported C/C++ files only to /out", flush=True)

0 commit comments

Comments
 (0)