8000 Create simple warning check tool and add to ubuntu build and test job · python/cpython@5148727 · GitHub
[go: up one dir, main page]

Skip to content

Commit 5148727

Browse files
committed
Create simple warning check tool and add to ubuntu build and test job
1 parent a9bb3c7 commit 5148727

File tree

3 files changed

+187
-1
lines changed

3 files changed

+187
-1
lines changed

.github/workflows/reusable-ubuntu.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,12 @@ jobs:
7474
${{ fromJSON(inputs.free-threading) && '--disable-gil' || '' }}
7575
- name: Build CPython out-of-tree
7676
working-directory: ${{ env.CPYTHON_BUILDDIR }}
77-
run: make -j4
77+
run: make -j4 &> compiler_output.txt
7878
- name: Display build info
7979
working-directory: ${{ env.CPYTHON_BUILDDIR }}
8080
run: make pythoninfo
81+
- name: Check compiler warnings
82+
run: python Tools/build/check_warnings.py --compiler-output-file-path=${{ env.CPYTHON_BUILDDIR }}/compiler_output.txt --warning-ignore-file-path ${GITHUB_WORKSPACE}/Tools/build/.warningignore_ubuntu
8183
- name: Remount sources writable for tests
8284
# some tests write to srcdir, lack of pyc files slows down testing
8385
run: sudo mount $CPYTHON_RO_SRCDIR -oremount,rw

Tools/build/.warnignore_ubuntu

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Files listed will be ignored by the compiler warning checker
2+
# for the Ubuntu/build and test job.
3+
# Keep lines sorted lexicographically to help avoid merge conflicts.

Tools/build/check_warnings.py

Lines changed: 181 additions & 0 deletions
< 9E7A tr class="diff-line-row">
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
#!/usr/bin/env python3
2+
import sys
3+
from pathlib import Path
4+
import argparse
5+
import json
6+
7+
8+
def extract_json_objects(compiler_output: str) -> list[dict]:
9+
10+
return json_objects
11+
12+
13+
def extract_warnings_from_compiler_output(compiler_output: str) -> list[dict]:
14+
"""
15+
Extracts warnings from the compiler output when using -fdiagnostics-format=json
16+
17+
Compiler output as a whole is not a valid json document, but includes many json
18+
objects and may include other output that is not json.
19+
"""
20+
# Extract JSON objects from the raw compiler output
21+
compiler_output_json_objects = []
22+
stack = []
23+
start_index = None
24+
for index, char in enumerate(compiler_output):
25+
if char == '[':
26+
if len(stack) == 0:
27+
start_index = index # Start of a new JSON array
28+
stack.append(char)
29+
elif char == ']':
30+
if len(stack) > 0:
31+
stack.pop()
32+
if len(stack) == 0 and start_index is not None:
33+
try:
34+
json_data = json.loads(compiler_output[start_index:index+1])
35+
compiler_output_json_objects.extend(json_data)
36+
start_index = None
37+
except json.JSONDecodeError:
38+
continue # Skip malformed JSON
39+
40+
compiler_warnings = [entry for entry in compiler_output_json_objects if entry.get('kind') == 'warning']
41+
42+
return compiler_warnings
43+
44+
45+
46+
def get_unexpected_warnings(
47+
warnings: list[dict],
48+
files_with_expected_warnings: set[str],
49+
) -> int:
50+
"""
51+
Fails if there are unexpected warnings
52+
"""
53+
unexpected_warnings = []
54+
for warning in warnings:
55+
locations = warning['locations']
56+
for location in locations:
57+
for key in ['caret', 'start', 'end']:
58+
if key in location:
59+
filename = location[key]['file']
60+
if filename not in files_with_expected_warnings:
61+
unexpected_warnings.append(warning)
62+
63+
if unexpected_warnings:
64+
print("Unexpected warnings:")
65+
for warning in unexpected_warnings:
66+
print(warning)
67+
return 1
68+
69+
70+
return 0
71+
72+
73+
def get_unexpected_improvements(
74+
warnings: list[dict],
75+
files_with_expected_warnings: set[str],
76+
) -> int:
77+
"""
78+
Fails if files that were expected to have warnings have no warnings
79+
"""
80+
81+
# Create set of files with warnings
82+
files_with_ewarnings = set()
83+
for warning in warnings:
84+
locations = warning['locations']
85+
for location in locations:
86+
for key in ['caret', 'start', 'end']:
87+
if key in location:
88+
filename = location[key]['file']
89+
files_with_ewarnings.add(filename)
90+
91+
92+
93+
unexpected_improvements = []
94+
for filename in files_with_expected_warnings:
95+
if filename not in files_with_ewarnings:
96+
unexpected_improvements.append(filename)
97+
98+
if unexpected_improvements:
99+
print("Unexpected improvements:")
100+
for filename in unexpected_improvements:
101+
print(filename)
102+
return 1
103+
104+
105+
return 0
106+
107+
108+
109+
110+
111+
112+
def main(argv: list[str] | None = None) -> int:
113+
parser = argparse.ArgumentParser()
114+
parser.add_argument(
115+
"--compiler-output-file-path",
116+
type=str,
117+
required=True,
118+
help="Path to the file"
119+
)
120+
parser.add_argument(
121+
"--warning-ignore-file-path",
122+
type=str,
123+
required=True,
124+
help="Path to the warning ignore file"
125+
)
126+
127+
128+
args = parser.parse_args(argv)
129+
130+
exit_code = 0
131+
132+
# Check that the compiler output file is a valid path
133+
if not Path(args.compiler_output_file_path).is_file():
134+
print(f"Compiler output file does not exist: {args.compiler_output_file_path}")
135+
return 1
136+
137+
# Check that the warning ignore file is a valid path
138+
if not Path(args.warning_ignore_file_path).is_file():
139+
print(f"Warning ignore file does not exist: {args.warning_ignore_file_path}")
140+
return 1
141+
142+
with Path(args.compiler_output_file_path).open(encoding="UTF-8") as f:
143+
compiler_output_file_contents = f.read()
144+
145+
with Path(args.warning_ignore_file_path).open(encoding="UTF-8") as clean_files:
146+
files_with_expected_warnings = {
147+
filename.strip()
148+
for filename in clean_files
149+
if filename.strip() and not filename.startswith("#")
150+
}
151+
152+
153+
if len(compiler_output_file_contents) > 0:
154+
print("Successfully got compiler output")
155+
else:
156+
exit_code = 1
157+
158+
if len(files_with_expected_warnings) > 0:
159+
print("we have some exceptions")
160+
161+
162+
warnings = extract_warnings_from_compiler_output(compiler_output_file_contents)
163+
164+
exit_code = get_unexpected_warnings(warnings, files_with_expected_warnings)
165+
166+
exit_code = get_unexpected_improvements(warnings, files_with_expected_warnings)
167+
168+
169+
170+
171+
return exit_code
172+
173+
174+
175+
176+
177+
178+
179+
180+
if __name__ == "__main__":
181+
sys.exit(main())

0 commit comments

Comments
 (0)
0