|
| 1 | +#!/usr/bin/env python3 |
| 2 | + |
| 3 | +# Copyright 2023 Google LLC |
| 4 | +# |
| 5 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | +# you may not use this file except in compliance with the License. |
| 7 | +# You may obtain a copy of the License at |
| 8 | +# |
| 9 | +# https://www.apache.org/licenses/LICENSE-2.0 |
| 10 | +# |
| 11 | +# Unless required by applicable law or agreed to in writing, software |
| 12 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | +# See the License for the specific language governing permissions and |
| 15 | +# limitations under the License. |
| 16 | +'''Check that boilerplate is present in relevant files. |
| 17 | +This tools offers a simple way of ensuring that the required boilerplate header |
| 18 | +is present in files with specific extensions. Files can be excluded by using a |
| 19 | +special comment anywhere in the file. |
| 20 | +The interface is purposefully simple and only supports passing one or more |
| 21 | +folder paths as arguments, as this tool is designed to be run in CI pipelines |
| 22 | +triggered by pull requests. |
| 23 | +''' |
| 24 | + |
| 25 | +import os |
| 26 | +import re |
| 27 | +import sys |
| 28 | + |
| 29 | +_EXCLUDE_DIRS = ('.git', '.terraform') |
| 30 | +_EXCLUDE_RE = re.compile(r'# skip boilerplate check') |
| 31 | +_EXCLUDE_FILES = {} |
| 32 | +_MATCH_FILES = ('Dockerfile', '.py', '.sh', '.tf', '.yaml', '.yml', '.go', '.rego') |
| 33 | +_MATCH_STRING = (r'^\s*([#\*]|[/]{2})\sCopyright [0-9]{4} Google LLC$\s+([#\*]|[/]{2})\s+' |
| 34 | + r'([#\*]|[/]{2})\sLicensed under the Apache License, Version 2.0 ' |
| 35 | + r'\(the "License"\);\s+') |
| 36 | +_MATCH_RE = re.compile(_MATCH_STRING, re.M) |
| 37 | + |
| 38 | +def main(base_dirs): |
| 39 | + "Cycle through files in base_dirs and check for the Apache 2.0 boilerplate." |
| 40 | + errors, warnings = [], [] |
| 41 | + for dir in base_dirs: |
| 42 | + for root, dirs, files in os.walk(dir): |
| 43 | + dirs[:] = [d for d in dirs if d not in _EXCLUDE_DIRS] |
| 44 | + for fname in files: |
| 45 | + if fname in _MATCH_FILES or os.path.splitext(fname)[1] in _MATCH_FILES: |
| 46 | + fpath = os.path.abspath(os.path.join(root, fname)) |
| 47 | + content = open(fpath).read() |
| 48 | + relPath = os.path.relpath(fpath, dir) |
| 49 | + if relPath in _EXCLUDE_FILES: |
| 50 | + continue |
| 51 | + if _EXCLUDE_RE.search(content): |
| 52 | + continue |
| 53 | + try: |
| 54 | + if not _MATCH_RE.search(content): |
| 55 | + errors.append(fpath) |
| 56 | + except (IOError, OSError): |
| 57 | + warnings.append(fpath) |
| 58 | + if warnings: |
| 59 | + print('The following files cannot be accessed:') |
| 60 | + print('\n'.join(' - {}'.format(s) for s in warnings)) |
| 61 | + if errors: |
| 62 | + print('The following files are missing the license boilerplate:') |
| 63 | + print('\n'.join(' - {}'.format(s) for s in errors)) |
| 64 | + sys.exit(1) |
| 65 | + |
| 66 | + |
| 67 | +if __name__ == '__main__': |
| 68 | + if len(sys.argv) < 2: |
| 69 | + raise SystemExit('No directory to check.') |
| 70 | + main(sys.argv[1:]) |
0 commit comments