| #!/usr/bin/env python3 |
| # Copyright (C) 2025 The Android Open Source Project |
| # |
| # 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 |
| # |
| # http://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. |
| """Generates .github/CODEOWNERS from .github/CODEOWNERS.template and OWNERS.github files.""" |
| |
| import os |
| import sys |
| |
| ROOT_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) |
| |
| HEADER = """# ============================================================================ |
| # THIS FILE IS AUTO-GENERATED - DO NOT EDIT DIRECTLY |
| # |
| # This file is automatically generated by tools/gen_github_codeowners |
| # Changes to this file will be overwritten. |
| # |
| # To modify this file, edit OWNERS.github files throughout the repository. |
| # ============================================================================ |
| |
| """ |
| |
| |
| def main(): |
| output = HEADER |
| |
| # Add content from root OWNERS.github |
| root_owners_path = os.path.join(ROOT_DIR, 'OWNERS.github') |
| with open(root_owners_path, 'r') as f: |
| output += f.read().rstrip() + '\n' |
| |
| # Add separator |
| output += '\n# ============================================================================\n' |
| output += '# AUTO-GENERATED ENTRIES FROM OWNERS.github FILES\n' |
| output += '# ============================================================================\n' |
| |
| # Find and process all OWNERS.github files |
| for root, dirs, files in os.walk(ROOT_DIR): |
| dirs[:] = [d for d in dirs if not d.startswith('.') and d != 'node_modules'] |
| |
| if 'OWNERS.github' not in files: |
| continue |
| |
| rel_dir = os.path.relpath(root, ROOT_DIR) |
| |
| # Skip root OWNERS.github as it's already processed |
| if rel_dir == '.': |
| continue |
| base_path = '/' + rel_dir.replace(os.sep, '/') |
| if not base_path.endswith('/'): |
| base_path += '/' |
| |
| with open(os.path.join(root, 'OWNERS.github'), 'r') as f: |
| for line in f: |
| line = line.strip() |
| if not line or line.startswith('#'): |
| continue |
| |
| parts = line.split() |
| if len(parts) < 2: |
| continue |
| |
| pattern = parts[0] |
| owners = ' '.join(parts[1:]) |
| |
| # Security check: reject patterns with parent directory references |
| if '../' in pattern or pattern.startswith('..'): |
| owners_file = os.path.join(root, 'OWNERS.github') |
| print(f'ERROR: Invalid pattern in {owners_file}: {pattern}') |
| print('Patterns cannot contain "../" for security reasons.') |
| sys.exit(1) |
| |
| # Convert relative pattern to absolute path |
| if pattern == '/' or pattern == './' or pattern == '.': |
| path = base_path |
| elif pattern.startswith('/'): |
| path = base_path + pattern[1:] |
| else: |
| path = base_path + pattern |
| |
| output += f'{path} {owners}\n' |
| |
| # Write output |
| codeowners_path = os.path.join(ROOT_DIR, '.github', 'CODEOWNERS') |
| with open(codeowners_path, 'w') as f: |
| f.write(output) |
| |
| print(f'Generated {codeowners_path}') |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |