Skip to content

Commit 168ed2e

Browse files
committed
begin clang-format work
1 parent cfb34ef commit 168ed2e

File tree

4 files changed

+311
-57
lines changed

4 files changed

+311
-57
lines changed

.clang-format

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
IndentWidth: 4
2+
Language: Cpp
3+
UseTab: Never
4+
ColumnLimit: 120
5+
PointerAlignment: Left
6+
BreakBeforeBraces: Attach
7+
SpaceAfterCStyleCast: false
8+
Cpp11BracedListStyle: false
9+
IndentCaseLabels: true
10+
BinPackArguments: true
11+
BinPackParameters: true
12+
AlignAfterOpenBracket: Align
13+
AlignOperands: true
14+
BreakBeforeTernaryOperators: true
15+
BreakBeforeBinaryOperators: None
16+
AllowShortBlocksOnASingleLine: true
17+
AllowShortIfStatementsOnASingleLine: false
18+
AllowShortLoopsOnASingleLine: false
19+
AllowShortCaseLabelsOnASingleLine: false
20+
AllowShortFunctionsOnASingleLine: false
21+
AlignEscapedNewlines: Left
22+
AlignTrailingComments: true
23+
SortIncludes: false

.clang-tidy

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Checks: '-*,readability-braces-around-statements,readability-inconsistent-declaration-parameter-name'
2+
WarningsAsErrors: ''
3+
HeaderFilterRegex: '(src|include)\/.*\.h$'
4+
FormatStyle: 'file'
5+
CheckOptions:
6+
# Require argument names to match exactly (instead of allowing a name to be a prefix/suffix of another)
7+
# Note: 'true' is expected by clang-tidy 12+ but '1' is used for compatibility with older versions
8+
- key: readability-inconsistent-declaration-parameter-name.Strict
9+
value: 1

format.py

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
#!/usr/bin/env python3
2+
3+
import argparse
4+
import glob
5+
import multiprocessing
6+
import os
7+
import re
8+
import shutil
9+
import subprocess
10+
import sys
11+
import tempfile
12+
from functools import partial
13+
from typing import List
14+
15+
# clang-format, clang-tidy and clang-apply-replacements default version
16+
# This specific version is used when available, for more consistency between contributors
17+
CLANG_VER = 14
18+
19+
# Clang-Format options (see .clang-format for rules applied)
20+
FORMAT_OPTS = "-i -style=file"
21+
22+
# Clang-Tidy options (see .clang-tidy for checks enabled)
23+
TIDY_OPTS = "-p ."
24+
TIDY_FIX_OPTS = "--fix --fix-errors"
25+
26+
# Clang-Apply-Replacements options (used for multiprocessing)
27+
APPLY_OPTS = "--format --style=file"
28+
29+
# Compiler options used with Clang-Tidy
30+
# Normal warnings are disabled with -Wno-everything to focus only on tidying
31+
INCLUDES = "-Iinclude -Isrc -Ibuild -I."
32+
DEFINES = "-D_LANGUAGE_C -DNON_MATCHING -D_MIPS_SZLONG=32"
33+
COMPILER_OPTS = f"-fno-builtin -std=gnu90 -m32 -Wno-everything {INCLUDES} {DEFINES}"
34+
35+
36+
def get_clang_executable(allowed_executables: List[str]):
37+
for executable in allowed_executables:
38+
try:
39+
subprocess.check_call([executable, "--version"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
40+
return executable
41+
except FileNotFoundError or subprocess.CalledProcessError:
42+
pass
43+
return None
44+
45+
46+
def get_tidy_version(tidy_executable: str):
47+
tidy_version_run = subprocess.run([tidy_executable, "--version"], stdout=subprocess.PIPE, universal_newlines=True)
48+
match = re.search(r"LLVM version ([0-9]+)", tidy_version_run.stdout)
49+
return int(match.group(1))
50+
51+
52+
CLANG_FORMAT = get_clang_executable([f"clang-format-{CLANG_VER}", "clang-format"])
53+
if CLANG_FORMAT is None:
54+
sys.exit(f"Error: neither clang-format nor clang-format-{CLANG_VER} found")
55+
56+
CLANG_TIDY = get_clang_executable([f"clang-tidy-{CLANG_VER}", "clang-tidy"])
57+
if CLANG_TIDY is None:
58+
sys.exit(f"Error: neither clang-tidy nor clang-tidy-{CLANG_VER} found")
59+
60+
CLANG_APPLY_REPLACEMENTS = get_clang_executable([f"clang-apply-replacements-{CLANG_VER}", "clang-apply-replacements"])
61+
62+
# Try to detect the clang-tidy version and add --fix-notes for version 13+
63+
# This is used to ensure all fixes are applied properly in recent versions
64+
if get_tidy_version(CLANG_TIDY) >= 13:
65+
TIDY_FIX_OPTS += " --fix-notes"
66+
67+
68+
def list_chunks(list: List, chunk_length: int):
69+
for i in range(0, len(list), chunk_length):
70+
yield list[i : i + chunk_length]
71+
72+
73+
def run_clang_format(files: List[str]):
74+
exec_str = f"{CLANG_FORMAT} {FORMAT_OPTS} {' '.join(files)}"
75+
subprocess.run(exec_str, shell=True)
76+
77+
78+
def run_clang_tidy(files: List[str]):
79+
exec_str = f"{CLANG_TIDY} {TIDY_OPTS} {TIDY_FIX_OPTS} {' '.join(files)} -- {COMPILER_OPTS}"
80+
subprocess.run(exec_str, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
81+
82+
83+
def run_clang_tidy_with_export(tmp_dir: str, files: List[str]):
84+
(handle, tmp_file) = tempfile.mkstemp(suffix=".yaml", dir=tmp_dir)
85+
os.close(handle)
86+
87+
exec_str = f"{CLANG_TIDY} {TIDY_OPTS} --export-fixes={tmp_file} {' '.join(files)} -- {COMPILER_OPTS}"
88+
subprocess.run(exec_str, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
89+
90+
91+
def run_clang_apply_replacements(tmp_dir: str):
92+
exec_str = f"{CLANG_APPLY_REPLACEMENTS} {APPLY_OPTS} {tmp_dir}"
93+
subprocess.run(exec_str, shell=True)
94+
95+
96+
def add_final_new_line(file: str):
97+
# https://backreference.org/2010/05/23/sanitizing-files-with-no-trailing-newline/index.html
98+
# "gets the last character of the file pipes it into read, which will exit with a nonzero exit
99+
# code if it encounters EOF before newline (so, if the last character of the file isn't a newline).
100+
# If read exits nonzero, then append a newline onto the file using echo (if read exits 0,
101+
# that satisfies the ||, so the echo command isn't run)." (https://stackoverflow.com/a/34865616)
102+
exec_str = f"tail -c1 {file} | read -r _ || echo >> {file}"
103+
subprocess.run(exec_str, shell=True)
104+
105+
106+
def format_files(src_files: List[str], extra_files: List[str], nb_jobs: int):
107+
if nb_jobs != 1:
108+
print(f"Formatting files with {nb_jobs} jobs")
109+
else:
110+
print(f"Formatting files with a single job (consider using -j to make this faster)")
111+
112+
# Format files in chunks to improve performance while still utilizing jobs
113+
file_chunks = list(list_chunks(src_files, (len(src_files) // nb_jobs) + 1))
114+
115+
print("Running clang-format...")
116+
# clang-format only applies changes in the given files, so it's safe to run in parallel
117+
with multiprocessing.get_context("fork").Pool(nb_jobs) as pool:
118+
pool.map(run_clang_format, file_chunks)
119+
120+
print("Running clang-tidy...")
121+
if nb_jobs > 1:
122+
# clang-tidy may apply changes in #included files, so when running it in parallel we use --export-fixes
123+
# then we call clang-apply-replacements to apply all suggested fixes at the end
124+
tmp_dir = tempfile.mkdtemp()
125+
126+
try:
127+
with multiprocessing.get_context("fork").Pool(nb_jobs) as pool:
128+
pool.map(partial(run_clang_tidy_with_export, tmp_dir), file_chunks)
129+
130+
run_clang_apply_replacements(tmp_dir)
131+
finally:
132+
shutil.rmtree(tmp_dir)
133+
else:
134+
run_clang_tidy(src_files)
135+
136+
print("Adding missing final new lines...")
137+
# Adding final new lines is safe to do in parallel and can be applied to all types of files
138+
with multiprocessing.get_context("fork").Pool(nb_jobs) as pool:
139+
pool.map(add_final_new_line, src_files + extra_files)
140+
141+
print("Done formatting files.")
142+
143+
144+
def main():
145+
parser = argparse.ArgumentParser(description="Format files in the codebase to enforce most style rules")
146+
parser.add_argument(
147+
"--show-paths",
148+
dest="show_paths",
149+
action="store_true",
150+
help="Print the paths to the clang-* binaries used",
151+
)
152+
parser.add_argument("files", metavar="file", nargs="*")
153+
parser.add_argument(
154+
"-j",
155+
dest="jobs",
156+
type=int,
157+
nargs="?",
158+
default=1,
159+
help="number of jobs to run (default: 1 without -j, number of cpus with -j)",
160+
)
161+
args = parser.parse_args()
162+
163+
if args.show_paths:
164+
import shutil
165+
166+
print("CLANG_FORMAT ->", shutil.which(CLANG_FORMAT))
167+
print("CLANG_TIDY ->", shutil.which(CLANG_TIDY))
168+
print("CLANG_APPLY_REPLACEMENTS ->", shutil.which(CLANG_APPLY_REPLACEMENTS))
169+
170+
nb_jobs = args.jobs or multiprocessing.cpu_count()
171+
if nb_jobs > 1:
172+
if CLANG_APPLY_REPLACEMENTS is None:
173+
sys.exit(
174+
f"Error: neither clang-apply-replacements nor clang-apply-replacements-{CLANG_VER} found (required to use -j)"
175+
)
176+
177+
if args.files:
178+
files = args.files
179+
extra_files = []
180+
else:
181+
files = glob.glob("src/**/*.c", recursive=True)
182+
extra_files = glob.glob("assets/**/*.xml", recursive=True)
183+
184+
format_files(files, extra_files, nb_jobs)
185+
186+
187+
if __name__ == "__main__":
188+
main()

0 commit comments

Comments
 (0)