8000 gh-112301: Compiler warning management tooling by nohlson · Pull Request #121730 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

gh-112301: Compiler warning management tooling #121730

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Jul 27, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8000
Next Next commit
Create simple warning check tool and add to ubuntu build and test job
  • Loading branch information
nohlson committed Jul 23, 2024
commit 5148727e060c947a3fc0ebb942e4ce2dc2dad2e7
4 changes: 3 additions & 1 deletion .github/workflows/reusable-ubuntu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,12 @@ jobs:
${{ fromJSON(inputs.free-threading) && '--disable-gil' || '' }}
- name: Build CPython out-of-tree
working-directory: ${{ env.CPYTHON_BUILDDIR }}
run: make -j4
run: make -j4 &> compiler_output.txt
- name: Display build info
working-directory: ${{ env.CPYTHON_BUILDDIR }}
run: make pythoninfo
- name: Check compiler warnings
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
- name: Remount sources writable for tests
# some tests write to srcdir, lack of pyc files slows down testing
run: sudo mount $CPYTHON_RO_SRCDIR -oremount,rw
Expand Down
3 changes: 3 additions & 0 deletions Tools/build/.warnignore_ubuntu
8000
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Files listed will be ignored by the compiler warning checker
# for the Ubuntu/build and test job.
# Keep lines sorted lexicographically to help avoid merge conflicts.
181 changes: 181 additions & 0 deletions Tools/build/check_warnings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
#!/usr/bin/env python3
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this one?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant to comment this too: no, I don't think we do because the file does not have the executable bit and we're running it via python Tools/build/check_warnings.py

import sys
from pathlib import Path
import argparse
import json


def extract_json_objects(compiler_output: str) -> list[dict]:

return json_objects


def extract_warnings_from_compiler_output(compiler_output: str) -> list[dict]:
Copy link
Member
@hugovk hugovk Jul 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks almost good to merge, just some style nits :)

Technically we're outside the stdlib so strictly speaking PEP 8 doesn't apply, but let's follow it anyway. For example, please could you add extra newlines before these defs and wrap things to 79 chars?

The easiest way is to run Black (which we don't run on the stdlib but again we're outside the stdlib :)

black --line-length 79 Tools/build/check_warnings.py

Then some comments and long strings will need manual wrapping.

Suggested change
def extract_warnings_from_compiler_output(compiler_output: str) -> list[dict]:
def extract_warnings_from_compiler_output(compiler_output: str) -> list[dict]:

Also, please don't force push in this repo, it makes it easier to review new commits, and we squash merge everything at the end anyway. I think force-pushing is sometimes to blame for pinging lots of unrelated CODEOWNERS too.

In order to keep the commit history intact, please avoid squashing or amending history and then force-pushing to the PR. Reviewers often want to look at individual commits.

https://devguide.python.org/getting-started/pull-request-lifecycle/#quick-guide

Thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to adhere to PEP8.

After a rebase there were several commits that came from branches from different forks. Not really sure how that happened but I had to revert to my last commit and force push to get rid of them.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! I just pushed a commit to also wrap comments and strings to 79 chars as well.

"""
Extracts warnings from the compiler output when using -fdiagnostics-format=json

Compiler output as a whole is not a valid json document, but includes many json
objects and may include other output that is not json.
"""
# Extract JSON objects from the raw compiler output
compiler_output_json_objects = []
stack = []
start_index = None
for index, char in enumerate(compiler_output):
if char == '[':
if len(stack) == 0:
start_index = index # Start of a new JSON array
stack.append(char)
elif char == ']':
if len(stack) > 0:
stack.pop()
if len(stack) == 0 and start_index is not None:
try:
json_data = json.loads(compiler_output[start_index:index+1])
compiler_output_json_objects.extend(json_data)
start_index = None
except json.JSONDecodeError:
continue # Skip malformed JSON

compiler_warnings = [entry for entry in compiler_output_json_objects if entry.get('kind') == 'warning']

return compiler_warnings



def get_unexpected_warnings(
warnings: list[dict],
files_with_expected_warnings: set[str],
) -> int:
"""
Fails if there are unexpected warnings
"""
unexpected_warnings = []
for warning in warnings:
locations = warning['locations']
for location in locations:
for key in ['caret', 'start', 'end']:
if key in location:
filename = location[key]['file']
if filename not in files_with_expected_warnings:
unexpected_warnings.append(warning)

if unexpected_warnings:
print("Unexpected warnings:")
for warning in unexpected_warnings:
print(warning)
return 1


return 0


def get_unexpected_improvements(
warnings: list[dict],
files_with_expected_warnings: set[str],
) -> int:
"""
Fails if files that were expected to have warnings have no warnings
"""

# Create set of files with warnings
files_with_ewarnings = set()
for warning in warnings:
locations = warning['locations']
for location in locations:
for key in ['caret', 'start', 'end']:
if key in location:
filename = location[key]['file']
files_with_ewarnings.add(filename)



unexpected_improvements = []
for filename in files_with_expected_warnings:
if filename not in files_with_ewarnings:
unexpected_improvements.append(filename)

if unexpected_improvements:
print("Unexpected improvements:")
for filename in unexpected_improvements:
print(filename)
return 1


return 0






def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument(
"--compiler-output-file-path",
type=str,
required=True,
help="Path to the file"
)
parser.add_argument(
"--warning-ignore-file-path",
type=str,
required=True,
help="Path to the warning ignore file"
)


args = parser.parse_args(argv)

exit_code = 0

# Check that the compiler output file is a valid path
if not Path(args.compiler_output_file_path).is_file():
print(f"Compiler output file does not exist: {args.compiler_output_file_path}")
return 1

# Check that the warning ignore file is a valid path
if not Path(args.warning_ignore_file_path).is_file():
print(f"Warning ignore file does not exist: {args.warning_ignore_file_path}")
return 1

with Path(args.compiler_output_file_path).open(encoding="UTF-8") as f:
compiler_output_file_contents = f.read()

with Path(args.warning_ignore_file_path).open(encoding="UTF-8") as clean_files:
files_with_expected_warnings = {
filename.strip()
for filename in clean_files
if filename.strip() and not filename.startswith("#")
}


if len(compiler_output_file_contents) > 0:
print("Successfully got compiler output")
else:
exit_code = 1

if len(files_with_expected_warnings) > 0:
print("we have some exceptions")


warnings = extract_warnings_from_compiler_output(compiler_output_file_contents)

exit_code = get_unexpected_warnings(warnings, files_with_expected_warnings)

exit_code = get_unexpected_improvements(warnings, files_with_expected_warnings)




return exit_code








if __name__ == "__main__":
sys.exit(main())
0