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