123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115 |
- #!/usr/bin/env python3
- # Copyright Materialize, Inc. and contributors. All rights reserved.
- #
- # Use of this software is governed by the Business Source License
- # included in the LICENSE file at the root of this repository.
- #
- # As of the Change Date specified in that file, in accordance with
- # the Business Source License, use of this software will be governed
- # by the Apache License, Version 2.0.
- import glob
- import os
- import re
- BUF_YAML_FILE_PATH = "./src/buf.yaml"
- BUF_YAML_TEMPLATE_FILE_PATH = f"{BUF_YAML_FILE_PATH}.template"
- SOURCE_DIR = "src/"
- PROTO_FILE_GLOB = f"{SOURCE_DIR}**/*.proto"
- GENERATION_COMMENT = "File generated by generate-buf-config.py - DO NOT EDIT"
- BUF_INSTRUCTION_PREFIX = "// buf breaking:"
- # matched examples: "ignore", "ignore (database-issues#1000 must be fixed)"
- BUF_IGNORE_COMMAND = re.compile(r"ignore( \((.*)\))?")
- class ProtoFile:
- def __init__(self, path: str):
- self.path = path
- self.ignore_reason: str | None = None
- def is_ignore(self) -> bool:
- return self.ignore_reason is not None
- def collect_proto_files() -> list[ProtoFile]:
- print(f"Working dir: {os.getcwd()}")
- proto_file_paths = glob.glob(PROTO_FILE_GLOB, recursive=True)
- return [ProtoFile(path) for path in proto_file_paths]
- def load_buf_instructions(files: list[ProtoFile]) -> None:
- for file in files:
- load_buf_instructions_for_file(file)
- def load_buf_instructions_for_file(file: ProtoFile) -> None:
- with open(file.path) as lines:
- for line in lines:
- if line.startswith(BUF_INSTRUCTION_PREFIX):
- handle_buf_instruction_in_proto_file(file, line)
- def handle_buf_instruction_in_proto_file(file: ProtoFile, line: str) -> None:
- command = line.removeprefix(BUF_INSTRUCTION_PREFIX).strip()
- match = BUF_IGNORE_COMMAND.search(command)
- if match:
- has_ignore_reason = len(match.groups()) == 2 and match.group(2)
- # groups are not zero-based because group 0 contains the whole match
- file.ignore_reason = (
- match.group(2) if has_ignore_reason else "no reason specified"
- )
- else:
- raise RuntimeError(f"Unsupported buf instruction in {file.path}: {line}")
- def generate_buf_ignore_section(ignored_files: list[ProtoFile]) -> str:
- ignore_entry_lines = []
- ignored_files = sorted(ignored_files, key=lambda file: file.path)
- for ignored_file in ignored_files:
- relative_path = ignored_file.path.removeprefix(SOURCE_DIR)
- ignore_entry_lines.append(f" # reason: {ignored_file.ignore_reason}")
- ignore_entry_lines.append(f" - {relative_path}")
- if len(ignore_entry_lines) == 0:
- ignore_entry_lines.append(" # none")
- return "\n".join(ignore_entry_lines).strip()
- def write_buf_configuration(
- template_path: str, target_path: str, ignored_files: list[ProtoFile]
- ) -> None:
- with open(template_path) as input_file:
- content = input_file.read()
- content = content.replace("${generation-comment}", GENERATION_COMMENT)
- content = content.replace(
- "${ignore-entries}", generate_buf_ignore_section(ignored_files)
- )
- with open(target_path, "w") as output_file:
- output_file.write(content)
- def main() -> None:
- proto_files = collect_proto_files()
- print(f"Collected {len(proto_files)} proto files.")
- load_buf_instructions(proto_files)
- ignored_proto_files = [file for file in proto_files if file.is_ignore()]
- print(f"{len(ignored_proto_files)} proto files to be ignored from breaking check.")
- write_buf_configuration(
- BUF_YAML_TEMPLATE_FILE_PATH, BUF_YAML_FILE_PATH, ignored_proto_files
- )
- print(f"Written buf configuration to '{BUF_YAML_FILE_PATH}'.")
- if __name__ == "__main__":
- main()
|