1#!/usr/bin/env python3
2#
3# Copyright (C) 2021 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
18import argparse
19import glob
20import logging
21import os
22
23import utils
24
25LICENSE_KINDS_PREFIX = 'SPDX-license-identifier-'
26LICENSE_KEYWORDS = {
27    'Apache-2.0': ('Apache License', 'Version 2.0',),
28    'BSD': ('BSD ',),
29    'CC0-1.0': ('CC0 Public Domain Dedication license',),
30    'FTL': ('FreeType Project LICENSE',),
31    'ISC': ('Internet Systems Consortium',),
32    'ISC': ('ISC license',),
33    'MIT': (' MIT ',),
34    'MPL-2.0': ('Mozilla Public License Version 2.0',),
35    'MPL': ('Mozilla Public License',),
36    'NCSA': ('University of Illinois', 'NCSA',),
37    'OpenSSL': ('The OpenSSL Project',),
38    'Zlib': ('zlib License',),
39    'LGPL-3.0': ('LESSER GENERAL PUBLIC LICENSE', 'Version 3,',),
40    'LGPL-2.1': ('LESSER GENERAL PUBLIC LICENSE', 'Version 2.1',),
41    'LGPL-2.0': ('GNU LIBRARY GENERAL PUBLIC LICENSE', 'Version 2,',),
42    'LGPL': ('LESSER GENERAL PUBLIC LICENSE',),
43    'GPL-2.0': ('GNU GENERAL PUBLIC LICENSE', 'Version 2,',),
44    'GPL': ('GNU GENERAL PUBLIC LICENSE',),
45}
46
47LICENSE_INCLUDE = ['legacy_permissive', 'legacy_unencumbered']
48
49class LicenseCollector(object):
50    """ Collect licenses from a VNDK snapshot directory
51
52    This is to collect the license_kinds to be used in license modules.
53
54    Initialize the LicenseCollector with a vndk snapshot directory.
55    After run() is called, 'license_kinds' will include the licenses found from
56    the snapshot directory.
57    """
58    def __init__(self, install_dir):
59        self._install_dir = install_dir
60        self._paths_to_check = [os.path.join(install_dir,
61                                              utils.NOTICE_FILES_DIR_PATH),]
62        self._paths_to_check = self._paths_to_check + glob.glob(os.path.join(self._install_dir, '*/include'))
63
64        self.license_kinds = set()
65
66    def read_and_check_licenses(self, license_text, license_keywords):
67        """ Read the license keywords and check if all keywords are in the file.
68
69        The found licenses will be added to license_kinds set. This function will
70        return True if any licenses are found, False otherwise.
71        """
72        found = False
73        for lic, patterns in license_keywords.items():
74            for pattern in patterns:
75                if pattern not in license_text:
76                    break
77            else:
78                self.license_kinds.add(LICENSE_KINDS_PREFIX + lic)
79                found = True
80        return found
81
82    def check_licenses(self, filepath):
83        """ Read a license text file and find the license_kinds.
84        """
85        with open(filepath, 'rt', encoding='utf-8') as file_to_check:
86            try:
87                file_string = file_to_check.read()
88                self.read_and_check_licenses(file_string, LICENSE_KEYWORDS)
89            except UnicodeDecodeError:
90                # Read text files only.
91                return
92
93    def run(self, module=''):
94        """ search licenses in vndk snapshots
95
96        Args:
97          module: module name to find the license kind.
98                  If empty, check all license files.
99        """
100        if module == '':
101            for path in self._paths_to_check:
102                logging.info('Reading {}'.format(path))
103                for (root, _, files) in os.walk(path):
104                    for f in files:
105                        self.check_licenses(os.path.join(root, f))
106            self.license_kinds.update(LICENSE_INCLUDE)
107        else:
108            license_text_path = '{notice_dir}/{module}.txt'.format(
109                notice_dir=utils.NOTICE_FILES_DIR_NAME,
110                module=module)
111            logging.info('Reading {}'.format(license_text_path))
112            self.check_licenses(os.path.join(self._install_dir, utils.COMMON_DIR_PATH, license_text_path))
113            if not self.license_kinds:
114                # Add 'legacy_permissive' if no licenses are found for this file.
115                self.license_kinds.add('legacy_permissive')
116
117def get_args():
118    parser = argparse.ArgumentParser()
119    parser.add_argument(
120        'vndk_version',
121        type=utils.vndk_version_int,
122        help='VNDK snapshot version to check, e.g. "{}".'.format(
123            utils.MINIMUM_VNDK_VERSION))
124    parser.add_argument(
125        '-v',
126        '--verbose',
127        action='count',
128        default=0,
129        help='Increase output verbosity, e.g. "-v", "-vv".')
130    return parser.parse_args()
131
132def main():
133    """ For the local testing purpose.
134    """
135    ANDROID_BUILD_TOP = utils.get_android_build_top()
136    PREBUILTS_VNDK_DIR = utils.join_realpath(ANDROID_BUILD_TOP,
137                                             'prebuilts/vndk')
138    args = get_args()
139    vndk_version = args.vndk_version
140    install_dir = os.path.join(PREBUILTS_VNDK_DIR, 'v{}'.format(vndk_version))
141    utils.set_logging_config(args.verbose)
142
143    license_collector = LicenseCollector(install_dir)
144    license_collector.run()
145    print(sorted(license_collector.license_kinds))
146
147if __name__ == '__main__':
148    main()
149