• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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