blob: 9fb0f44bd297fbad8998bfe81e2d085510c9d08b [file] [log] [blame]
#!/usr/bin/env python3
# Copyright 2024 The Pigweed Authors
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
"""Applies clang-tidy suggested fixes.
This uses the clang-apply-replacements tool to apply code changes described in
a YAML file generated by clang-tidy.
"""
import argparse
import logging
import re
from pathlib import Path
from pw_cli.tool_runner import BasicSubprocessRunner
_LOG: logging.Logger = logging.getLogger(__name__)
_REMOVE_CHANGE_FILES_FLAG = "--remove-change-desc-files"
_IGNORE_INSERT_CONFLICT_FLAG = "--ignore-insert-conflict"
_CLANG_TIDY_SUGGESTED_FIX_FILE_REGEX = "*.o.yaml"
_PIGWEED_INCLUDE_FIX_REGEX = re.compile(r'ReplacementText: "#include <(pw_.*)>')
_PIGWEED_INCLUDE_REPLACEMENT_REGEX = r'ReplacementText: "#include \"\1\"'
def apply_replacements(
root: Path, remove_change_desc_files: bool, raise_insert_conflict: bool
) -> int:
"""Runs the clang-apply-replacements tool to apply clang-tidy fixes.
Args:
root: The directory under which clang-apply-replacements will search
for YAML fix files generated by clang-tidy.
remove_change_desc_files: Whether or not to remove the change
description files regardless of successful.
raise_insert_conflict: Whether or not to ignore insert conflicts.
Returns:
The return code of the clang-apply-replacements tool.
"""
# Search under root for YAML files generated by clang-tidy that contain
# suggested fixes
for filepath in root.rglob(_CLANG_TIDY_SUGGESTED_FIX_FILE_REGEX):
clang_tidy_fixes = filepath.read_text()
# Change suggested pigweed includes from <pw_.*> to "pw_.*"
if clang_tidy_fixes:
new_clang_tidy_fixes = _PIGWEED_INCLUDE_FIX_REGEX.sub(
_PIGWEED_INCLUDE_REPLACEMENT_REGEX, clang_tidy_fixes
)
filepath.write_text(new_clang_tidy_fixes)
# Add flags for the clang-apply-replacements tool
flags = []
if remove_change_desc_files:
flags.append(_REMOVE_CHANGE_FILES_FLAG)
if raise_insert_conflict:
flags.append(_IGNORE_INSERT_CONFLICT_FLAG)
# Use the clang-apply-replacements tool via a subprocess
_LOG.info('Applying clang-tidy fixes')
run_tool = BasicSubprocessRunner()
process_result = run_tool(
'clang-apply-replacements',
[str(root)] + flags,
check=True,
)
return process_result.returncode
def arguments() -> argparse.ArgumentParser:
"""Creates an argument parser for clang-apply-replacements tool."""
parser = argparse.ArgumentParser(description=__doc__)
def existing_path(arg: str) -> Path:
path = Path(arg)
if not path.is_dir():
raise argparse.ArgumentTypeError(
f'{arg} is not a path to a directory'
)
return path
parser.add_argument(
'root',
type=existing_path,
help=(
'Root directory that clang-apply-replacements will recursively '
'search under for the YAML fix files generated by clang-tidy.'
),
)
parser.add_argument(
'--remove-change-desc-files',
action=argparse.BooleanOptionalAction,
default=True,
help=(
'Remove the change description files regardless of successful '
'merging/replacing.'
),
)
parser.add_argument(
'--raise-insert-conflict',
action=argparse.BooleanOptionalAction,
default=True,
help='Do not ignore insert conflicts.',
)
return parser
def main() -> int:
"""Check and fix formatting for source files."""
return apply_replacements(**vars(arguments().parse_args()))