1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3"""
4Copyright (c) 2020-2021 Huawei Device Co., Ltd.
5Licensed under the Apache License, Version 2.0 (the "License");
6you may not use this file except in compliance with the License.
7You may obtain a copy of the License at
8
9  http://www.apache.org/licenses/LICENSE-2.0
10
11Unless required by applicable law or agreed to in writing, software
12distributed under the License is distributed on an "AS IS" BASIS,
13WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14See the License for the specific language governing permissions and
15limitations under the License.
16"""
17
18
19import os
20import sys
21import argparse
22import re
23import subprocess
24import xml.dom.minidom
25from xml.parsers.expat import ExpatError
26from string import Template
27import utils
28import json
29import shutil
30import glob
31import stat
32
33
34XTS_RESOURCE_ROOT = 'ivbxfj`qspqsjfubsz0sftpvsdf'
35
36
37def _get_xts_rootpath(project_path):
38    if project_path is not None:
39        return project_path[0:project_path.find('/xts/') + len('/xts/')]
40    raise ValueError('Illegal xts project path ' + project_path)
41
42
43def _get_subsystem_name(project_path):
44    if '/hits/' in project_path:
45        index0 = project_path.find('/hits/') + len('/hits/')
46    elif '/acts/' in project_path:
47        index0 = project_path.find('/acts/') + len('/acts/')
48    else:
49        raise ValueError('Illegal xts project path ' + project_path)
50    index1 = project_path.find('/', index0)
51    return project_path[index0:index1]
52
53
54def _get_resource_rootpath(project_path):
55    xts_root = _get_xts_rootpath(project_path)
56    resource_reldir = ''.join(chr(ord(ch) - 1) for ch in XTS_RESOURCE_ROOT)
57    return os.path.join(xts_root, resource_reldir)
58
59
60class XDeviceBuilder:
61    """
62    To build XTS as an egg package
63    @arguments(project_dir, output_dirs)
64    """
65
66    def __init__(self, arguments):
67        parser = argparse.ArgumentParser()
68        parser.add_argument('--source_dir', help='', required=True)
69        parser.add_argument('--configs_dir', help='', required=False)
70        parser.add_argument('--resources_dir', help='', required=False)
71        parser.add_argument('--suite_out_dir', help='', required=True)
72        self.args = parser.parse_args(arguments)
73
74    def build_xdevice(self):
75        """
76        build xdevice package
77        :return:
78        """
79        ohos_dir = os.path.join(self.args.source_dir, 'plugins', 'ohos')
80        devicetest_dir = os.path.join(self.args.source_dir, 'plugins', 'devicetest')
81        gen_dir0 = os.path.join(self.args.source_dir, 'dist')
82        gen_dir1 = os.path.join(ohos_dir, 'dist')
83        gen_dir2 = os.path.join(devicetest_dir, 'dist')
84        shutil.rmtree(gen_dir0, ignore_errors=True)
85        shutil.rmtree(gen_dir1, ignore_errors=True)
86        shutil.rmtree(gen_dir2, ignore_errors=True)
87        command0 = ["python", "setup.py", "sdist"]
88        command1 = ["python", "setup.py", "sdist"]
89        try:
90            subprocess.check_call(command0, cwd=self.args.source_dir)
91            subprocess.check_call(command1, cwd=ohos_dir)
92            subprocess.check_call(command0, cwd=devicetest_dir)
93        except subprocess.CalledProcessError as exc:
94            print('returncode: {} cmd: {} output: {}'.format(
95                  exc.returncode, exc.cmd, exc.output))
96
97        run_scripts = ",".join(
98            [os.path.join(self.args.source_dir, "run.bat"),
99             os.path.join(self.args.source_dir, "run.sh")])
100
101        dist_tools_dir = os.path.join(self.args.suite_out_dir, 'tools')
102        utils.copy_file(output=dist_tools_dir, source_dirs=gen_dir0,
103                        to_dir=True)
104        utils.copy_file(output=dist_tools_dir, source_dirs=gen_dir1,
105                        to_dir=True)
106        utils.copy_file(output=dist_tools_dir, source_dirs=gen_dir2,
107                        to_dir=True)
108        utils.copy_file(output=self.args.suite_out_dir, sources=run_scripts,
109                        to_dir=True)
110        xtssuite_out_dir = self.args.suite_out_dir
111        if (xtssuite_out_dir.find('/acts/') != -1):
112            acts_validator_dir = os.path.join(self.args.suite_out_dir, '../acts-validator')
113            acts_validator_tools_dir = os.path.join(self.args.suite_out_dir, '../acts-validator/tools')
114            utils.copy_file(output=acts_validator_tools_dir, source_dirs=gen_dir0,
115                            to_dir=True)
116            utils.copy_file(output=acts_validator_tools_dir, source_dirs=gen_dir1,
117                            to_dir=True)
118            utils.copy_file(output=acts_validator_dir, sources=run_scripts,
119                            to_dir=True)
120
121
122        if self.args.configs_dir:
123            dist_configs_dir = os.path.join(self.args.suite_out_dir, 'config')
124            utils.copy_file(output=dist_configs_dir,
125                            source_dirs=self.args.configs_dir, to_dir=True)
126            if (xtssuite_out_dir.find('/acts/') != -1):
127                acts_validator_config_dir = os.path.join(self.args.suite_out_dir, '../acts-validator/config')
128                utils.copy_file(output=acts_validator_config_dir,
129                                source_dirs=self.args.configs_dir, to_dir=True)
130        if self.args.resources_dir:
131            dist_resources_dir = os.path.join(self.args.suite_out_dir,
132                                              'resource')
133            utils.copy_file(output=dist_resources_dir,
134                            source_dirs=self.args.resources_dir, to_dir=True)
135
136
137class SuiteArchiveBuilder:
138    def __init__(self, arguments):
139        self.module_info_dir = None
140        self.arguments = arguments
141
142    def archive_suite(self):
143        parser = argparse.ArgumentParser()
144        parser.add_argument('--suite_path', help='', required=True)
145        parser.add_argument('--prebuilts_resource', help='', required=True)
146        parser.add_argument('--suite_archive_dir', help='', required=True)
147        parser.add_argument('--make_archive', help='', required=True)
148        args = parser.parse_args(self.arguments)
149
150        if not args.make_archive.lower() == 'true':
151            print('make archive disabled')
152            return 0
153
154        suite_path = args.suite_path
155        if not os.path.isdir(suite_path):
156            raise Exception("[%s] does not exist" % suite_path)
157
158        copyfiles = args.prebuilts_resource.split(",")
159        for file_path in copyfiles:
160            subprocess.call(["cp", "-rf", file_path, suite_path])
161
162        archive_name = os.path.basename(suite_path)
163        archive_root_path = args.suite_archive_dir
164        if not os.path.isdir(archive_root_path):
165            os.mkdir(archive_root_path)
166        # remove the extra output of target "java_prebuilt"
167        # such as ztest-tradefed-common.interface.jar
168        subprocess.call(["find", suite_path, "-name", "*.interface.jar",
169                        "-exec", "rm", "{}", "+"])
170        shutil.make_archive(os.path.join(archive_root_path, archive_name),
171                            "zip", suite_path)
172        return 0
173
174
175class SuiteModuleWithTestbundleBuilder:
176
177    def __init__(self, arguments):
178        self.arguments = arguments
179        self.args = None
180
181    def build_module_with_testbundle(self):
182        parser = argparse.ArgumentParser()
183        parser.add_argument('--build_gen_dir', help='', required=True)
184        parser.add_argument('--build_target_name', help='', required=True)
185        parser.add_argument('--subsystem_name', help='',
186                            required=False)
187        parser.add_argument('--part_name', help='',
188                            required=False)
189        parser.add_argument('--buildgen_testfile', help='', required=True)
190        parser.add_argument('--project_path', help='', required=True)
191        parser.add_argument('--test_xml', help='', required=False)
192        parser.add_argument('--project_type', help='', required=True)
193        parser.add_argument('--suite_out_dir', help='', required=True)
194        parser.add_argument('--archive_testfile', help='', required=True)
195        parser.add_argument('--apilibrary_deps', help='', required=False)
196        parser.add_argument('--test_files', help='', required=False)
197
198        self.args = parser.parse_args(self.arguments)
199
200        self._create_testsuite(self.args)
201        return 0
202
203    def _create_testsuite(self, args):
204        _test_xml = args.test_xml
205        _testcases_dir = os.path.dirname(args.archive_testfile)
206        _testsuite_name = os.path.basename(args.archive_testfile)\
207            .replace('.hap', '').replace('module_', '')
208        _testcase_xml = os.path.join(_testcases_dir, _testsuite_name + ".xml")
209        _config_file = os.path.join(_testcases_dir,
210                                    _testsuite_name + ".config")
211        _json_file = os.path.join(_testcases_dir, _testsuite_name + ".json")
212
213        if args.project_type == "js_test_hap":
214            self._generate_json_by_template(_test_xml.replace(".xml", ".json"),
215                                            _testsuite_name, _json_file)
216            dest_file = os.path.join(
217                _testcases_dir, "{}.hap".format(_testsuite_name))
218            self._copy_file(args.buildgen_testfile, dest_file)
219            os.chmod(dest_file, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
220            return
221        if args.project_type == "pythontest":
222            self._generate_json_by_template(_test_xml.replace(".xml", ".json"),
223                                            _testsuite_name, os.path.join(
224                    args.archive_testfile, _testsuite_name + ".json"))
225            utils.copy_file(output=self.args.archive_testfile,
226                            source_dirs=self.args.test_files)
227            return
228        self._check_file_exist(args.buildgen_testfile)
229        self._copy_file(args.buildgen_testfile, args.archive_testfile)
230        if args.project_type == "app":
231            return
232        self._record_testmodule_info(args.build_target_name,
233                                     _testsuite_name,
234                                     _testcases_dir)
235        self._record_testpart_info(args.build_target_name,
236                                     _testsuite_name,
237                                     _testcases_dir,
238                                     args.subsystem_name,args.part_name)
239        if _test_xml and os.path.exists(_test_xml):
240            self._copy_file(_test_xml, _config_file)
241        elif _test_xml.replace(".xml", ".json") and \
242                os.path.exists(_test_xml.replace(".xml", ".json")):
243            self._generate_json_by_template(_test_xml.replace(".xml", ".json"),
244                                            _testsuite_name, _json_file)
245        else:
246            self._generate_xml_by_template(_test_xml, _testsuite_name,
247                                           _config_file)
248        _resource_srcroot = _get_resource_rootpath(args.project_path)
249        self._archive_test_file_to_testcase(_testcases_dir)
250
251    @staticmethod
252    def _record_testmodule_info(build_target_name, module_name, testcases_dir):
253        if not build_target_name or not module_name:
254            raise ValueError(
255                    'Ethire build_target_name or module_name is invalid')
256
257        module_info_list_file = os.path.join(testcases_dir, 'module_info.list')
258        lines = []
259        if os.path.isfile(module_info_list_file):
260            with open(module_info_list_file, 'r') as file_read:
261                for line in file_read:
262                    lines.append(line.strip())
263
264        new_lines = ['%s %s' % (build_target_name, module_name)]
265        for line in lines:
266            arr = line.strip().split(' ')
267            if len(arr) == 0 or arr[0] == build_target_name:
268                continue
269            new_lines.append(line)
270
271        # add module info
272        new_lines.sort()
273        with open(module_info_list_file, 'w') as file_write:
274            file_write.write('\n'.join(new_lines) + '\n')
275
276    @staticmethod
277    def _record_testpart_info(build_target_name, module_name, testcases_dir, subsystem_name, part_name):
278        if not build_target_name or not module_name:
279            raise ValueError(
280                    'Ethire build_target_name or module_name is invalid')
281
282        module_info_file = os.path.join(testcases_dir, module_name+'.moduleInfo')
283
284        if os.path.exists(module_info_file):
285            return
286        module_info_data = {'subsystem': subsystem_name, 'part': part_name,
287                            'module': module_name}
288        with open(module_info_file, 'w') as out_file:
289            json.dump(module_info_data, out_file)
290
291    @staticmethod
292    def _generate_json_by_template(source_file, module_name, dest_file):
293        source_content = utils.read_file(source_file)
294        values = {"module": module_name}
295        xml_content = Template(source_content).substitute(values)
296        utils.write_file(dest_file, xml_content, False)
297
298    @staticmethod
299    def _generate_xml_by_template(test_xml, module_name,
300                                  config_file):
301        # find the template file
302        index = test_xml.rfind(".xml")
303        tmpl_file = test_xml[:index] + ".tmpl"
304        if not os.path.exists(tmpl_file):
305            raise Exception("Can't find the Test.xml or "
306                            "Test.tmpl in the path %s " %
307                            os.path.dirname(test_xml))
308        tmpl_content = utils.read_file(tmpl_file)
309        values = {"module": module_name}
310        xml_content = Template(tmpl_content).substitute(values)
311        utils.write_file(config_file, xml_content, False)
312
313    def _copy_file(self, source, target):
314        if not os.path.exists(os.path.dirname(target)):
315            os.makedirs(os.path.dirname(target))
316        if not os.path.exists(target) or (
317                os.path.exists(target) and
318                (os.stat(target).st_mtime != os.stat(source).st_mtime)):
319            print('Trying to copy "%s" to "%s"' % (source, target))
320            subprocess.call(["rm", "-rf", target])
321            subprocess.call(["cp", "-rf", source, target])
322
323    def _copy_file_to_target(self, source_file, dest_file):
324        if not os.path.exists(source_file):
325            print("[ERROR] source_file is not exist. %s" % source_file,
326                  file=sys.stderr)
327            return
328        else:
329            self._copy_file(source_file, dest_file)
330
331    def _archive_test_resource(self, xml_file, root_dir, resource_dir):
332        _pattern = re.compile("[-=]>")
333        if os.path.exists(xml_file):
334            try:
335                dom = xml.dom.minidom.parse(xml_file)
336                childroot = dom.getElementsByTagName("target_preparer")
337                for child in childroot:
338                    for child_atrr in child.getElementsByTagName("option"):
339                        name = child_atrr.getAttribute("name")
340                        attr_value = child_atrr.getAttribute("value")
341                        value = _pattern.split(attr_value)[0].strip()
342                        if name != "" or value != "":
343                            if name == "push" and \
344                                    value.startswith("resource/"):
345                                self._copy_file_to_target(
346                                    os.path.join(root_dir, "../", value),
347                                    os.path.join(resource_dir, value))
348                        else:
349                            raise Exception("Test.xml node name and "
350                                            "value not is null")
351            except IOError as error:
352                print("IO Error: %s" % error, file=sys.stderr)
353            except ExpatError as error:
354                print("ExpatError Error: %s" % error, file=sys.stderr)
355            finally:
356                pass
357        else:
358            with open(xml_file.replace(".config", ".json"),
359                      "r", encoding="UTF-8") as json_file:
360                json_str = json.load(json_file)
361                kits = json_str["kits"]
362                for kit in kits:
363                    types = kit["type"]
364                    if types == "PushKit":
365                        push_resources = kit["push"]
366                        for resource in push_resources:
367                            value = _pattern.split(resource)[0].strip()
368                            if value.startswith("resource/"):
369                                self._copy_file_to_target(
370                                    os.path.join(root_dir, "../", value),
371                                    os.path.join(resource_dir, value))
372
373    def _archive_test_file_to_testcase(self, cases_dir):
374        if self.args.test_files is None:
375            return
376        arr_test_files = self.args.test_files.split(',')
377        for file in arr_test_files:
378            if file == "":
379                continue
380            self._copy_file_to_target(file,
381                                      os.path.join(cases_dir,
382                                                   os.path.basename(file)))
383
384    def _check_file_exist(self, file_path):
385        if not os.path.exists(file_path):
386            raise Exception("File [%s] does not exist!" % file_path)
387
388
389ACTION_MAP = {"build_module": "SuiteModuleBuilder",
390              "build_xdevice": "XDeviceBuilder",
391              "archive_suite": "SuiteArchiveBuilder",
392              "build_module_with_testbundle":
393                  "SuiteModuleWithTestbundleBuilder"}
394
395
396def _find_action(action, arguments):
397    class_name = ACTION_MAP[action]
398    if not class_name:
399        raise ValueError('Unsupported operation: %s' % action)
400
401    this_module = sys.modules[__name__]
402    class_def = getattr(this_module, class_name, None)
403    if not class_def:
404        raise ValueError(
405            'Unsupported operation(No Implementation Class): %s' % action)
406    class_obj = class_def(arguments)
407    func = getattr(class_obj, action, None)
408    if not func:
409        raise ValueError(
410            'Unsupported operation(No Implementation Method): %s' % action)
411    return func
412
413
414def main(arguments):
415    action = arguments[0]
416    args = arguments[1:]
417    func = _find_action(action, args)
418    func()
419    return 0
420
421
422if __name__ == '__main__':
423    sys.exit(main(sys.argv[1:]))
424