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 os
18import shutil
19import tarfile
20import argparse
21from util import build_utils
22
23sys.path.append(
24    os.path.abspath(os.path.dirname(os.path.abspath(
25        os.path.dirname(__file__)))))
26from scripts.util.file_utils import read_json_file  # noqa: E402
27
28RELEASE_FILENAME = 'README.OpenSource'
29
30
31def _copy_opensource_file(opensource_config_file: str, top_dir: str, package_dir: str) -> bool:
32    if not os.path.exists(opensource_config_file):
33        print("Warning, the opensource config file is not exists.")
34        return False
35
36    src_dir = os.path.dirname(opensource_config_file)
37    dst_dir = os.path.join(package_dir, os.path.relpath(src_dir, top_dir))
38
39    # copy opensource folder to out dir
40    if os.path.exists(dst_dir):
41        shutil.rmtree(dst_dir)
42    shutil.copytree(src_dir,
43                    dst_dir,
44                    symlinks=True,
45                    ignore=shutil.ignore_patterns('*.pyc', 'tmp*', '.git*'))
46
47    # delete the README.OpenSource file
48    release_file = os.path.join(dst_dir, RELEASE_FILENAME)
49    os.remove(release_file)
50    return True
51
52
53def _parse_opensource_file(opensource_config_file: str, license_set: set) -> bool:
54    if not os.path.exists(opensource_config_file):
55        print("Warning, the opensource config file is not exists.")
56        return False
57
58    opensource_config = read_json_file(opensource_config_file)
59    if opensource_config is None:
60        raise Exception("read opensource config file [{}] failed.".format(
61            opensource_config_file))
62
63    result = False
64    for info in opensource_config:
65        _license = info.get('License')
66        # any license in collect list is collected
67        if any(lic in _license for lic in license_set):
68            result = True
69            break
70
71    return result
72
73
74def _scan_and_package_code_release(scan_dir: str, top_dir: str, package_dir: str, license_set: set):
75    file_dir_names = os.listdir(scan_dir)
76    for file_dir_name in file_dir_names:
77        file_dir_path = os.path.join(scan_dir, file_dir_name)
78        if os.path.isdir(file_dir_path) and not os.path.islink(file_dir_path):
79            _scan_and_package_code_release(file_dir_path, top_dir, package_dir, license_set)
80        elif file_dir_path == os.path.join(scan_dir, RELEASE_FILENAME):
81            if _parse_opensource_file(file_dir_path, license_set):
82                _copy_opensource_file(file_dir_path, top_dir, package_dir)
83
84
85def _collect_opensource(options, package_dir: str):
86    # get the source top directory to be scan
87    top_dir = options.root_dir
88
89    # processing scan dir and license, split by colon
90    scan_dir_list = options.scan_dirs.split(":")
91    if not scan_dir_list:
92        raise Exception("empty scan dir, please check the value of osp_scan_dirs.")
93    scan_licenses = options.scan_licenses.split(":")
94    if not scan_licenses:
95        raise Exception("empty scan licenses, please check the value of osp_scan_licenses.")
96
97    # scan the target dir and copy release code to out/opensource dir
98    # remove duplicate scan dir
99    dir_set = set([os.path.join(top_dir, _dir) for _dir in scan_dir_list])
100    # remove duplicate licenses
101    license_set = set(scan_licenses)
102    for scan_dir in dir_set:
103        if not os.path.isdir(scan_dir):
104            raise Exception(f"{scan_dir} not exist, this is invalid.")
105        _scan_and_package_code_release(scan_dir, top_dir, package_dir, license_set)
106
107
108def _tar_opensource_package_file(options, package_dir: str) -> int:
109    result = -1
110    if os.path.exists(package_dir):
111        try:
112            with tarfile.open(options.output, "w:gz") as tar:
113                tar.add(package_dir, arcname=".")
114                result = 0
115        except IOError as err:
116            raise err
117    return result
118
119
120def main(args) -> int:
121    """generate open source packages to release."""
122    parser = argparse.ArgumentParser()
123    build_utils.add_depfile_option(parser)
124    parser.add_argument('--output', required=True, help='output')
125    parser.add_argument('--root-dir', required=True, help='source root directory')
126    parser.add_argument('--scan-dirs', required=True, help='extended scan directory')
127    parser.add_argument('--scan-licenses', required=True, help='extended scan licenses')
128
129    # add optional extended parameters
130    parser.add_argument('--only-collect-file', action='store_true', help='need post process, only collect file')
131
132    options = parser.parse_args(args)
133
134    # need post process, only collection is required
135    if options.only_collect_file:
136        package_dir = os.path.dirname(options.output)
137        if os.path.exists(package_dir):
138            shutil.rmtree(package_dir)
139        os.makedirs(package_dir, exist_ok=True)
140        _collect_opensource(options, package_dir)
141        build_utils.touch(options.output)
142        return 0
143
144    with build_utils.temp_dir() as package_dir:
145        _collect_opensource(options, package_dir)
146        # package the opensource to Code_Opensource.tar.gz
147        if _tar_opensource_package_file(options, package_dir) == 0:
148            print('Generate the opensource package successfully.')
149        else:
150            print('Generate the opensource package failed.')
151
152    return 0
153
154
155if __name__ == '__main__':
156    sys.exit(main(sys.argv[1:]))
157