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