1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3# Copyright (c) 2021 Huawei Device Co., Ltd.
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#     http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import sys
17import argparse
18import os
19import shutil
20import stat
21
22sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir))
23from scripts.util.file_utils import read_json_file, write_json_file  # noqa: E402
24from scripts.util import build_utils  # noqa: E402
25
26README_FILE_NAME = 'README.OpenSource'
27LICENSE_CANDIDATES = [
28    'LICENSE',
29    'License',
30    'NOTICE',
31    'Notice',
32    'COPYRIGHT',
33    'Copyright',
34    'COPYING',
35    'Copying'
36]
37
38
39def is_top_dir(current_dir: str):
40    return os.path.exists(os.path.join(current_dir, '.gn'))
41
42
43def find_other_files(license_file_path):
44    other_files = []
45    if os.path.isfile(license_file_path):
46        license_dir = os.path.dirname(license_file_path)
47        license_file = os.path.basename(license_file_path)
48        for file in LICENSE_CANDIDATES:
49            license_path = os.path.join(license_dir, file)
50            if os.path.isfile(license_path) and file != license_file and \
51                    license_path not in other_files:
52                other_files.append(license_path)
53    elif os.path.isdir(license_file_path):
54        for file in ['COPYRIGHT', 'Copyright', 'COPYING', 'Copying']:
55            license_file = os.path.join(license_file_path, file)
56            if os.path.isfile(license_file):
57                other_files.append(license_file)
58    return other_files
59
60
61def find_file_recursively(current_dir: str, target_files: list):
62    if is_top_dir(current_dir):
63        return None
64    for file in target_files:
65        candidate = os.path.join(current_dir, file)
66        if os.path.isfile(candidate):
67            return candidate
68    return find_file_recursively(os.path.dirname(current_dir), target_files)
69
70
71def find_license(current_dir: str):
72    return find_file_recursively(current_dir, LICENSE_CANDIDATES)
73
74
75def find_opensource(current_dir: str):
76    return find_file_recursively(current_dir, [README_FILE_NAME])
77
78
79def get_license_from_readme(readme_path: str):
80    contents = read_json_file(readme_path)
81    if contents is None:
82        raise Exception("Error: failed to read {}.".format(readme_path))
83
84    if len(contents) <= 1:
85        notice_file = contents[0].get('License File').strip()
86        notice_name = contents[0].get('Name').strip()
87        notice_version = contents[0].get('Version Number').strip()
88        if notice_file is None:
89            raise Exception("Error: value of notice file is empty in {}.".format(
90                readme_path))
91        if notice_name is None:
92            raise Exception("Error: Name of notice file is empty in {}.".format(
93                readme_path))
94        if notice_version is None:
95            raise Exception("Error: Version Number of notice file is empty in {}.".format(
96                readme_path))
97
98        return os.path.join(os.path.dirname(readme_path), notice_file), notice_name, notice_version
99    else:
100        notice_files = []
101        notice_names = []
102        notice_versions = []
103        for content in contents:
104            notice_files.append(content.get('License File').strip())
105            notice_names.append(content.get('Name').strip())
106            notice_versions.append(content.get('Version Number').strip())
107
108        if notice_files is None:
109            raise Exception("Error: value of notice file is empty in {}.".format(
110                readme_path))
111        if notice_names is None:
112            raise Exception("Error: Name of notice file is empty in {}.".format(
113                readme_path))
114        if notice_versions is None:
115            raise Exception("Error: Version Number of notice file is empty in {}.".format(
116                readme_path))
117
118        return [os.path.join(os.path.dirname(readme_path), file) for file in notice_files], \
119            notice_names, notice_versions
120
121
122def add_path_to_module_notice(module_notice_info, module_notice_info_list, options):
123    if isinstance(module_notice_info.get('Software', None), list):
124        softwares = module_notice_info.get('Software')
125        versions = module_notice_info.get('Version')
126        for software, version in zip(softwares, versions):
127            module_notice_info_list.append({'Software': software, 'Version': version})
128        module_notice_info_list[-1]['Path'] = "/{}".format(options.module_source_dir[5:])
129    else:
130        if module_notice_info.get('Software', None):
131            module_notice_info['Path'] = "/{}".format(options.module_source_dir[5:])
132            module_notice_info_list.append(module_notice_info)
133    if module_notice_info_list:
134        for module_notice_info in module_notice_info_list:
135            module_path = module_notice_info.get("Path", None)
136            if module_path is not None:
137                module_notice_info["Path"] = module_path.replace("../", "")
138
139
140def do_collect_notice_files(options, depfiles: str):
141    module_notice_info_list = []
142    module_notice_info = {}
143    notice_file = options.license_file
144    other_files = []
145    if notice_file:
146        opensource_file = find_opensource(os.path.abspath(options.module_source_dir))
147        if opensource_file is not None and os.path.exists(opensource_file):
148            other_files.extend(find_other_files(opensource_file))
149            notice_file_info = get_license_from_readme(opensource_file)
150            module_notice_info['Software'] = notice_file_info[1]
151            module_notice_info['Version'] = notice_file_info[2]
152        else:
153            module_notice_info['Software'] = ""
154            module_notice_info['Version'] = ""
155    if notice_file is None:
156        readme_path = os.path.join(options.module_source_dir,
157                                   README_FILE_NAME)
158        if not os.path.exists(readme_path):
159            readme_path = find_opensource(os.path.abspath(options.module_source_dir))
160        other_files.extend(find_other_files(options.module_source_dir))
161        if readme_path is not None:
162            depfiles.append(readme_path)
163            notice_file_info = get_license_from_readme(readme_path)
164            notice_file = notice_file_info[0]
165            if isinstance(notice_file, list):
166                notice_file = ",".join(notice_file)
167            module_notice_info['Software'] = notice_file_info[1]
168            module_notice_info['Version'] = notice_file_info[2]
169
170    if notice_file is None:
171        notice_file = find_license(options.module_source_dir)
172        opensource_file = find_opensource(os.path.abspath(options.module_source_dir))
173        if opensource_file is not None and os.path.exists(opensource_file):
174            other_files.extend(find_other_files(opensource_file))
175            notice_file_info = get_license_from_readme(opensource_file)
176            module_notice_info['Software'] = notice_file_info[1]
177            module_notice_info['Version'] = notice_file_info[2]
178        else:
179            module_notice_info['Software'] = ""
180            module_notice_info['Version'] = ""
181
182    add_path_to_module_notice(module_notice_info, module_notice_info_list, options)
183
184    if notice_file:
185        if other_files:
186            notice_file = f"{notice_file},{','.join(other_files)}"
187        for output in options.output:
188            notice_info_json = '{}.json'.format(output)
189            os.makedirs(os.path.dirname(output), exist_ok=True)
190            os.makedirs(os.path.dirname(notice_info_json), exist_ok=True)
191            notice_files = [file for file in notice_file.split(",") if file]
192
193            write_file_content(notice_files, options, output, notice_info_json, module_notice_info_list, depfiles)
194
195
196def write_file_content(notice_files, options, output, notice_info_json, module_notice_info_list, depfiles):
197    for notice_file in notice_files:
198        notice_file = notice_file.strip()
199        if not os.path.exists(notice_file):
200            notice_file = os.path.join(options.module_source_dir, notice_file)
201        if os.path.exists(notice_file):
202            if not os.path.exists(output):
203                build_utils.touch(output)
204            write_notice_to_output(notice_file, output)
205            write_json_file(notice_info_json, module_notice_info_list)
206        else:
207            build_utils.touch(output)
208            build_utils.touch(notice_info_json)
209        depfiles.append(notice_file)
210
211
212def write_notice_to_output(notice_file, output):
213    with os.fdopen(os.open(notice_file, os.O_RDWR | os.O_CREAT, stat.S_IWUSR | stat.S_IRUSR),
214                   'r', encoding='utf-8', errors='ignore') as notice_data_flow:
215        license_content = notice_data_flow.read()
216    with os.fdopen(os.open(output, os.O_RDWR | os.O_CREAT, stat.S_IWUSR | stat.S_IRUSR),
217                   'r', encoding='utf-8', errors='ignore') as output_data_flow:
218        output_file_content = output_data_flow.read()
219    if license_content not in output_file_content:
220        with os.fdopen(os.open(output, os.O_RDWR | os.O_CREAT, stat.S_IWUSR | stat.S_IRUSR),
221                       'a', encoding='utf-8') as testfwk_info_file:
222            testfwk_info_file.write(f"{license_content}\n")
223            testfwk_info_file.close()
224
225
226def main(args):
227    args = build_utils.expand_file_args(args)
228
229    parser = argparse.ArgumentParser()
230    build_utils.add_depfile_option(parser)
231
232    parser.add_argument('--license-file', required=False)
233    parser.add_argument('--output', action='append', required=False)
234    parser.add_argument('--sources', action='append', required=False)
235    parser.add_argument('--sdk-install-info-file', required=False)
236    parser.add_argument('--label', required=False)
237    parser.add_argument('--sdk-notice-dir', required=False)
238    parser.add_argument('--module-source-dir',
239                        help='source directory of this module',
240                        required=True)
241
242    options = parser.parse_args()
243    depfiles = []
244
245    if options.sdk_install_info_file:
246        install_dir = ''
247        sdk_install_info = read_json_file(options.sdk_install_info_file)
248        for item in sdk_install_info:
249            if options.label == item.get('label'):
250                install_dir = item.get('install_dir')
251                break
252        if options.sources and install_dir:
253            for src in options.sources:
254                extend_output = os.path.join(options.sdk_notice_dir, install_dir,
255                                             '{}.{}'.format(os.path.basename(src), 'txt'))
256                options.output.append(extend_output)
257
258    do_collect_notice_files(options, depfiles)
259    if options.license_file:
260        depfiles.append(options.license_file)
261    build_utils.write_depfile(options.depfile, options.output[0], depfiles)
262
263
264if __name__ == '__main__':
265    sys.exit(main(sys.argv[1:]))
266
267