| #!/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())) |