Skip to content

Commit 1fa5e57

Browse files
committed
CI - restyle script summary and annotations in PRs
similar to .ino builder, prepare a short 'diff' summary of all the requried changes https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#adding-a-job-summary provide inline annotations, so it is apparent clang-format job is the cause of the PR actions check failure https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-a-notice-message
1 parent 07feace commit 1fa5e57

File tree

2 files changed

+232
-48
lines changed

2 files changed

+232
-48
lines changed

tests/restyle.py

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
#!/usr/bin/env python
2+
3+
import argparse
4+
import os
5+
import sys
6+
import pathlib
7+
import subprocess
8+
import collections
9+
import contextlib
10+
11+
GIT_ROOT = pathlib.Path(
12+
subprocess.check_output(
13+
["git", "rev-parse", "--show-toplevel"], universal_newlines=True
14+
).strip()
15+
)
16+
17+
18+
def clang_format(clang_format, config, files):
19+
cmd = [clang_format, "--verbose", f"--style=file:{config.as_posix()}", "-i"]
20+
cmd.extend(files)
21+
22+
proc = subprocess.run(cmd)
23+
proc.check_returncode()
24+
25+
26+
def find_files(patterns):
27+
return [
28+
file
29+
for pattern in patterns
30+
for file in [found for found in GIT_ROOT.rglob(pattern)]
31+
]
32+
33+
34+
# just like original script, only handle a subset of core sources
35+
def find_core_files():
36+
return [
37+
file
38+
for file in find_files(
39+
(
40+
"cores/esp8266/Lwip*",
41+
"libraries/ESP8266mDNS/**/*",
42+
"libraries/Wire/**/*",
43+
"libraries/lwIP*/**/*",
44+
"cores/esp8266/debug*",
45+
"cores/esp8266/core_esp8266_si2c*",
46+
"cores/esp8266/StreamString*",
47+
"cores/esp8266/StreamSend*",
48+
"libraries/Netdump/**/*",
49+
"tests/**/*",
50+
)
51+
)
52+
if file.is_file()
53+
and file.suffix in (".c", ".cpp", ".h", ".hpp")
54+
and not GIT_ROOT / "tests/host/bin" in file.parents
55+
and not "ArduinoCatch" in file.name
56+
and not "catch.hpp" in file.name
57+
]
58+
59+
60+
# include *every* .ino file, excluding submodules
61+
def find_arduino_files():
62+
all_libraries = [path for path in GIT_ROOT.glob("libraries/*") if path.is_dir()]
63+
submodules = [
64+
path.parent for path in GIT_ROOT.glob("libraries/*/.git") if path.is_file()
65+
]
66+
67+
libraries = [library for library in all_libraries if not library in submodules]
68+
69+
files = []
70+
for library in libraries:
71+
for ino in library.rglob("**/*.ino"):
72+
files.append(ino)
73+
74+
return files
75+
76+
77+
FILES_PRESETS = {
78+
"core": find_core_files,
79+
"arduino": find_arduino_files,
80+
}
81+
82+
83+
Changed = collections.namedtuple("changed", "file hunk lines")
84+
85+
86+
# naive git-diff parser for clang-format aftercare
87+
# ref. https://github.com/cpp-linter/cpp-linter/blob/main/cpp_linter/git/__init__.py ::parse_diff
88+
# ref. https://github.com/libgit2/pygit2/blob/master/src/diff.c ::parse_diff
89+
# TODO: pygit2?
90+
def changed_files():
91+
cmd = ["git", "--no-pager", "diff"]
92+
93+
proc = subprocess.run(cmd, capture_output=True, universal_newlines=True)
94+
proc.check_returncode()
95+
96+
out = []
97+
98+
deleted = False
99+
file = ""
100+
lines = []
101+
102+
append_hunk = False
103+
hunk = []
104+
105+
for line in proc.stdout.split("\n"):
106+
# '--- a/path/to/changed/file' most likely
107+
if line.startswith("---"):
108+
if file and hunk and lines:
109+
out.append(Changed(file, "\n".join(hunk), ", ".join(lines)))
110+
111+
deleted = False
112+
file = ""
113+
lines = []
114+
115+
append_hunk = False
116+
hunk = [line]
117+
118+
# '+++ b/path/to/changed/file' most likely
119+
# '+++ /dev/null' aka removed file
120+
elif line.startswith("+++"):
121+
hunk.append(line)
122+
123+
_, file = line.split(" ")
124+
deleted = "/dev/null" in file
125+
if not deleted:
126+
file = file[2:]
127+
128+
# @@ from-file-line-numbers to-file-line-numbers @@
129+
elif not deleted and line.startswith("@@"):
130+
hunk.append(line)
131+
132+
_, _, numbers, _ = line.split(" ", 3)
133+
if "," in numbers:
134+
numbers, _ = numbers.split(",") # drop count
135+
136+
numbers = numbers.replace("+", "")
137+
numbers = numbers.replace("-", "")
138+
139+
lines.append(numbers)
140+
append_hunk = True
141+
142+
# capture diff for the summary
143+
elif append_hunk and line.startswith(("+", "-", " ")):
144+
hunk.append(line)
145+
146+
return out
147+
148+
149+
def notice_changed(changed: Changed):
150+
print(
151+
f"::notice file={changed.file},title=Run tests/restyle.sh and re-commit {changed.file}::File {changed.file} failed clang-format style check. (lines {changed.lines})"
152+
)
153+
154+
155+
SUMMARY_PATH = pathlib.Path(os.environ.get("GITHUB_STEP_SUMMARY", os.devnull))
156+
SUMMARY_OUTPUT = SUMMARY_PATH.open("a")
157+
158+
159+
def summary_diff(changed: Changed):
160+
with contextlib.redirect_stdout(SUMMARY_OUTPUT):
161+
print("```diff")
162+
print(changed.hunk)
163+
print("```")
164+
165+
166+
def stdout_diff():
167+
subprocess.run(["git", "--no-pager", "diff"])
168+
169+
170+
def assert_unchanged():
171+
subprocess.run(
172+
["git", "diff", "--exit-code"], check=True, stdout=subprocess.DEVNULL
173+
)
174+
175+
176+
def run_format(args):
177+
print(args)
178+
targets = []
179+
180+
for include in args.include:
181+
targets.append(
182+
(GIT_ROOT / f"tests/clang-format-{include}.yaml", FILES_PRESETS[include]())
183+
)
184+
185+
if not targets:
186+
targets.append((args.config, args.files))
187+
188+
for target in targets:
189+
clang_format(args.clang_format, *target)
190+
191+
192+
def run_assert(args):
193+
for changed in changed_files():
194+
if args.with_notice:
195+
notice_changed(changed)
196+
if args.with_summary:
197+
summary_diff(changed)
198+
199+
if not args.with_summary:
200+
stdout_diff()
201+
assert_unchanged()
202+
203+
204+
if __name__ == "__main__":
205+
parser = argparse.ArgumentParser()
206+
207+
cmd = parser.add_subparsers(required=True)
208+
format_ = cmd.add_parser("format")
209+
format_.set_defaults(func=run_format)
210+
format_.add_argument("--clang-format", default="clang-format")
211+
212+
fmt = format_.add_subparsers(required=True)
213+
214+
preset = fmt.add_parser("preset")
215+
preset.add_argument(
216+
"--include", action="append", required=True, choices=tuple(FILES_PRESETS.keys())
217+
)
218+
219+
files = fmt.add_parser("files")
220+
files.add_argument("--config", type=pathlib.Path, required=True)
221+
files.add_argument("files", type=pathlib.Path, nargs="+")
222+
223+
assert_ = cmd.add_parser("assert")
224+
assert_.set_defaults(func=run_assert)
225+
assert_.add_argument("--with-summary", action="store_true")
226+
assert_.add_argument("--with-notice", action="store_true")
227+
228+
args = parser.parse_args()
229+
args.func(args)

tests/restyle.sh

Lines changed: 3 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!/bin/sh
2-
# requires clang-format, git, python3 with pyyaml
2+
# requires python3, git, and runnable clang-format (specified below)
33

44
set -e -x
55

@@ -11,51 +11,6 @@ test -d ${root}/libraries
1111
# default to v15, latest stable version from ubuntu-latest Github Actions image
1212
CLANG_FORMAT=${CLANG_FORMAT:-clang-format-15}
1313

14-
#########################################
15-
# 'all' variable should be "cores/esp8266 libraries"
16-
17-
all=${1:-"
18-
cores/esp8266/Lwip*
19-
libraries/ESP8266mDNS
20-
libraries/Wire
21-
libraries/lwIP*
22-
cores/esp8266/debug*
23-
cores/esp8266/core_esp8266_si2c.cpp
24-
cores/esp8266/StreamString.*
25-
cores/esp8266/StreamSend.*
26-
libraries/Netdump
27-
tests
28-
"}
29-
30-
#########################################
31-
# restyling core & libraries
32-
3314
cd $root
34-
35-
style=${root}/tests/clang-format-core.yaml
36-
for target in $all; do
37-
if [ -d "$target" ]; then
38-
find $target \
39-
'(' -name "*.cpp" -o -name "*.c" -o -name "*.h" ')' \
40-
-exec $CLANG_FORMAT --verbose --style="file:$style" -i {} \;
41-
else
42-
$CLANG_FORMAT --verbose --style="file:$style" -i $target
43-
fi
44-
done
45-
46-
#########################################
47-
# restyling arduino examples
48-
49-
# TODO should not be matched, these are formatted externally
50-
# exclude=$(git submodule --quiet foreach git rev-parse --show-toplevel | grep libraries)
51-
52-
if [ -z $1 ] ; then
53-
style=${root}/tests/clang-format-arduino.yaml
54-
find libraries \
55-
-path libraries/ESP8266SdFat -prune -o \
56-
-path libraries/Ethernet -prune -o \
57-
-path libraries/SoftwareSerial -prune -o \
58-
-name '*.ino' -exec $CLANG_FORMAT --verbose --style="file:$style" -i {} \;
59-
fi
60-
61-
#########################################
15+
python $root/tests/restyle.py format --clang-format=$CLANG_FORMAT preset --include core --include arduino
16+
python $root/tests/restyle.py assert

0 commit comments

Comments
 (0)