1#!/usr/bin/env python3 2# 3# Copyright (C) 2024 The Android Open Source Project 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# http://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 17"""Check if the given files in a given commit has an AOSP license.""" 18 19import argparse 20import os 21import re 22import sys 23from typing import List 24 25_path = os.path.realpath(__file__ + '/../..') 26if sys.path[0] != _path: 27 sys.path.insert(0, _path) 28del _path 29 30# We have to import our local modules after the sys.path tweak. We can't use 31# relative imports because this is an executable program, not a module. 32# pylint: disable=import-error,wrong-import-position 33import rh.git 34 35 36# AOSP uses the Apache2 License: https://source.android.com/source/licenses.html 37# Spaces and comment identifiers in different languages are allowed at the 38# beginning of each line. 39AOSP_LICENSE_HEADER = ( 40 r"""[ #/\*]*Copyright \(C\) 20\d\d The Android Open Source Project 41[ #/\*]*\n?[ #/\*]*Licensed under the Apache License, Version 2.0 """ 42 r"""\(the "License"\); 43[ #/\*]*you may not use this file except in compliance with the License\. 44[ #/\*]*You may obtain a copy of the License at 45[ #/\*]* 46[ #/\*]*http://www\.apache\.org/licenses/LICENSE-2\.0 47[ #/\*]* 48[ #/\*]*Unless required by applicable law or agreed to in writing, software 49[ #/\*]*distributed under the License is distributed on an "AS IS" BASIS, 50[ #/\*]*WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or """ 51 r"""implied\. 52[ #/\*]*See the License for the specific language governing permissions and 53[ #/\*]*limitations under the License\. 54""" 55) 56 57 58LICENSE_RE = re.compile(AOSP_LICENSE_HEADER, re.MULTILINE) 59 60 61AOSP_LICENSE_SUBSTR = 'Licensed under the Apache License' 62 63 64def check_license(contents: str) -> bool: 65 """Verifies the AOSP license/copyright header.""" 66 return LICENSE_RE.search(contents) is not None 67 68 69def get_parser() -> argparse.ArgumentParser: 70 """Returns a command line parser.""" 71 parser = argparse.ArgumentParser(description=__doc__) 72 parser.add_argument( 73 'files', 74 nargs='+', 75 help='The file paths to check.', 76 ) 77 parser.add_argument( 78 '--commit-hash', 79 '-c', 80 help='The commit hash to check.', 81 # TODO(b/370907797): Read the contents on the file system by default 82 # instead. 83 default='HEAD', 84 ) 85 return parser 86 87 88def main(argv: List[str]) -> int: 89 """The main entry.""" 90 parser = get_parser() 91 opts = parser.parse_args(argv) 92 commit_hash = opts.commit_hash 93 file_paths = opts.files 94 95 all_passed = True 96 for file_path in file_paths: 97 contents = rh.git.get_file_content(commit_hash, file_path) 98 if not check_license(contents): 99 if AOSP_LICENSE_SUBSTR in contents: 100 print(f'{file_path}: Malformed AOSP license', file=sys.stderr) 101 else: 102 print(f'{file_path}: Missing AOSP license', file=sys.stderr) 103 all_passed = False 104 return 0 if all_passed else 1 105 106 107if __name__ == '__main__': 108 sys.exit(main(sys.argv[1:])) 109