| # Copyright 2025 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. |
| |
| import argparse |
| import pathlib |
| import io |
| import sys |
| from typing import List |
| |
| |
| def get_bazel_files(root_dir: pathlib.Path) -> List[pathlib.Path]: |
| """Finds files matching "BUILD.bazel" or "*.bzl" under the given directory. |
| |
| Args: |
| root_dir: The root directory to search in. |
| |
| Returns: |
| A list of pathlib.Path objects representing the matching files. |
| """ |
| return [ |
| path |
| for path in root_dir.rglob("*") |
| if path.match("BUILD.bazel") or path.match("*.bzl") |
| ] |
| |
| def get_py_files(root_dir: pathlib.Path) -> List[pathlib.Path]: |
| """Finds files matching "*.py" under the given directory. |
| |
| Args: |
| root_dir: The root directory to search in. |
| |
| Returns: |
| A list of pathlib.Path objects representing the matching files. |
| """ |
| return [ |
| path |
| for path in root_dir.rglob("*") |
| if path.match("*.py") |
| ] |
| |
| def get_cc_files(root_dir: pathlib.Path) -> List[pathlib.Path]: |
| """Finds C/C++ and linker files under the given directory. |
| |
| Args: |
| root_dir: The root directory to search in. |
| |
| Returns: |
| A list of pathlib.Path objects representing the matching files. |
| """ |
| patterns = ( |
| "Kconfig*", |
| "*.c", |
| "*.cc", |
| "*.dts", |
| "*.dtsi", |
| "*.h", |
| "*.ld", |
| "*.S", |
| "*.yml", |
| ) |
| return [ |
| path |
| for path in root_dir.rglob("*") |
| if any(path.match(p) for p in patterns) |
| ] |
| |
| def print_file_info( |
| file_path: pathlib.Path, |
| root_dir: pathlib.Path, |
| writer: io.TextIOWrapper, |
| ): |
| """Prints the relative path, line count, and prefixed content of a file. |
| |
| Args: |
| file_path: The path to the file. |
| root_dir: The root directory to calculate the relative path from. |
| writer: Where to write the output |
| """ |
| relative_path = file_path.relative_to(root_dir) |
| with open(file_path, "r", encoding="utf-8") as file: |
| content = file.readlines() |
| line_count = len(content) |
| writer.write("\n") |
| writer.write("--- /dev/null\n") |
| writer.write(f"+++ b/{relative_path}\n") |
| writer.write(f"@@ -0,0 +1,{line_count} @@\n") |
| for line in content: |
| writer.write(f"+{line.rstrip()}\n") |
| |
| def print_all(root_dir: pathlib.Path, writer: io.TextIOWrapper): |
| """Iterate through all BUILD.bazel and *.bzl files and write them to writer |
| |
| Args: |
| root_dir: The root directory to calculate the relative path from. |
| writer: Where to write the output |
| """ |
| file_paths = get_bazel_files(root_dir=root_dir) |
| file_paths += get_py_files(root_dir=root_dir) |
| file_paths += get_cc_files(root_dir=root_dir) |
| for file_path in file_paths: |
| print_file_info(file_path=file_path, root_dir=root_dir, writer=writer) |
| |
| def append_custom_patches( |
| root_dir: pathlib.Path, |
| custom_patches_dir_name: str, |
| writer: io.TextIOWrapper, |
| ): |
| custom_patches_dir = root_dir / custom_patches_dir_name |
| if custom_patches_dir.exists(): |
| for path in custom_patches_dir.glob("*.patch"): |
| with open(path, "r", encoding="utf-8") as f: |
| writer.write(f.read()) |
| |
| def main() -> None: |
| """Finds all BUILD.bazel files and *.bzl files and generates a diff""" |
| parser = argparse.ArgumentParser() |
| parser.add_argument( |
| "--root-dir", |
| type=pathlib.Path, |
| nargs="+", |
| ) |
| parser.add_argument( |
| "-o", |
| dest="output", |
| type=pathlib.Path, |
| ) |
| parser.add_argument( |
| "--custom-patches-dir", |
| type=str, |
| default="custom_patches", |
| help="Directory name for custom patches, relative to root-dir", |
| ) |
| args = parser.parse_args() |
| if args.output: |
| args.output.parent.mkdir(parents=True, exist_ok=True) |
| with open(args.output, mode="w", encoding="utf-8") as o: |
| for root_dir in args.root_dir: |
| print_all(root_dir=root_dir, writer=o) |
| append_custom_patches( |
| root_dir=root_dir, |
| custom_patches_dir_name=args.custom_patches_dir, |
| writer=o, |
| ) |
| else: |
| writer = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8") |
| for root_dir in args.root_dir: |
| print_all(root_dir=root_dir, writer=writer) |
| append_custom_patches( |
| root_dir=root_dir, |
| custom_patches_dir_name=args.custom_patches_dir, |
| writer=writer, |
| ) |
| writer.flush() |
| |
| if __name__ == "__main__": |
| main() |