| #!/usr/bin/env python |
| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you 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. |
| |
| """Check that all Python source files contain a valid Apache License header.""" |
| |
| import os |
| import sys |
| |
| # Required phrases that must appear in every license header. |
| # Keep phrases short enough to not be split by narrow line-wrapping. |
| REQUIRED_PHRASES = [ |
| 'Licensed to the Apache Software Foundation (ASF) under one', |
| 'you may not use this file except in compliance', |
| 'http://www.apache.org/licenses/LICENSE-2.0', |
| 'WITHOUT WARRANTIES OR CONDITIONS OF ANY', |
| ] |
| |
| SKIP_DIRS = {'.venv', '.venv-py310', '.venv-py311', '.venv-py36', |
| '.venv-py36-compat', '.venv-py39', '.venv-py313', |
| '__pycache__', 'sample', '.tox', 'node_modules'} |
| |
| |
| def check_file(filepath): |
| """Return a list of issues found in the file's license header.""" |
| issues = [] |
| try: |
| with open(filepath, 'r', encoding='utf-8', errors='replace') as f: |
| content = f.read() |
| except Exception as e: |
| return [str(e)] |
| |
| if not content.strip(): |
| return [] # skip empty files |
| |
| head = content[:2000] |
| |
| for phrase in REQUIRED_PHRASES: |
| if phrase not in head: |
| issues.append('missing: "%s"' % phrase) |
| |
| lines = content.split('\n') |
| for i, line in enumerate(lines[:3]): |
| stripped = line.lstrip('#').lstrip().lstrip('"').lstrip("'").strip() |
| if i > 0 and stripped == lines[0].lstrip('#').lstrip().lstrip('"').lstrip("'").strip() \ |
| and 'Licensed to the Apache' in stripped: |
| issues.append('line %d: duplicated license first line' % (i + 1)) |
| |
| return issues |
| |
| |
| def main(): |
| root = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'pypaimon') |
| root = os.path.normpath(root) |
| |
| failed = [] |
| checked = 0 |
| |
| for dirpath, dirnames, filenames in os.walk(root): |
| dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS] |
| for fname in filenames: |
| if not fname.endswith('.py'): |
| continue |
| filepath = os.path.join(dirpath, fname) |
| checked += 1 |
| issues = check_file(filepath) |
| if issues: |
| rel = os.path.relpath(filepath, os.path.join(root, '..')) |
| failed.append((rel, issues)) |
| |
| if failed: |
| print('License header issues found in %d file(s):' % len(failed)) |
| for path, issues in sorted(failed): |
| for issue in issues: |
| print(' %s: %s' % (path, issue)) |
| print('\nChecked %d files, %d failed.' % (checked, len(failed))) |
| return 1 |
| else: |
| print('All %d files have valid license headers.' % checked) |
| return 0 |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |