• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 xml.etree.ElementTree as ET
17import re
18import sys
19import copy
20from enum import Enum
21
22import sa_info_config_errors as sa_err
23
24
25class RearrangementPolicy(object):
26    BOOT_START_PHASE = "BootStartPhase"
27    CORE_START_PHASE = "CoreStartPhase"
28    DEFAULT_START_PHASE = "OthersStartPhase"
29
30    rearrange_category_order = (BOOT_START_PHASE, CORE_START_PHASE,
31                                DEFAULT_START_PHASE)
32
33    bootphase_priority_table = {
34        BOOT_START_PHASE: 3,
35        CORE_START_PHASE: 2,
36        DEFAULT_START_PHASE: 1
37    }
38
39    def __init__(self):
40        self.bootphase_categories = {
41            RearrangementPolicy.BOOT_START_PHASE: [],
42            RearrangementPolicy.CORE_START_PHASE: [],
43            RearrangementPolicy.DEFAULT_START_PHASE: []
44        }
45
46
47class SARearrangement(object):
48    def __init__(self):
49        self.rearranged_systemabilities = []
50        self.ordered_systemability_names = []
51        self.name_node_dict = {}
52        self.systemability_deps_dict = {}
53        self.bootphase_dict = {}
54        self.creation_dict = {}
55        self.policy = RearrangementPolicy()
56        self.sanode_start_idx = 0
57        self.systemability_nodes = []
58        self.xml_lines = []
59        self.xml_lines_range = {}
60        self.sa_nodes_count = 0
61        self.first_sanode_start_lineno = 0
62        self.last_sanode_end_lineno = 0
63        self.file_in_process = None
64        self.root = None
65
66    def __read_xml_file_into_lines(self, source_file):
67        """
68        Read xml file into lines, and map systemability node into ranges with
69        begin line and end line
70        """
71        class Status(Enum):
72            expect_sa_tag_begin = 1
73            expect_name_value = 2
74            expect_sa_tag_end = 3
75
76        def find_systemability_name(line):
77            regexp = r"^[\t\s]*<name>(.*)</name>"
78            matches = re.search(regexp, line)
79            if matches is not None:
80                name = matches.group(1).strip()
81                return name
82            return ""
83
84        with open(source_file, "r", encoding="utf-8") as xml_file:
85            self.xml_lines = xml_file.readlines()
86
87        sa_tag_begin = r"^[\t\s]*\<systemability"
88        sa_tag_end = r"^[\t\s]*\<\/systemability"
89        status = Status.expect_sa_tag_begin
90        ranges = []
91        is_first_sa_node = True
92        systemability_name = ""
93        sa_nodes_count = 0
94        for linenum in range(len(self.xml_lines)):
95            line = self.xml_lines[linenum]
96            if status == Status.expect_sa_tag_begin:
97                if re.match(sa_tag_begin, line) is not None:
98                    status = Status.expect_name_value
99                    ranges.append(linenum)
100                    if is_first_sa_node:
101                        self.first_sanode_start_lineno = linenum
102                        is_first_sa_node = False
103            elif status == Status.expect_name_value:
104                systemability_name = find_systemability_name(line)
105                if systemability_name != "":
106                    status = Status.expect_sa_tag_end
107            elif status == Status.expect_sa_tag_end:
108                if re.match(sa_tag_end, line) is not None:
109                    ranges.append(linenum)
110                    self.xml_lines_range[systemability_name] = ranges
111                    self.last_sanode_end_lineno = linenum
112                    status = Status.expect_sa_tag_begin
113                    systemability_name = ""
114                    ranges = []
115                    sa_nodes_count += 1
116            else:
117                pass  # ignore other tags
118        if self.sa_nodes_count != sa_nodes_count:
119            _format = (
120                "The <systemability> tag and it's child tags should "
121                "start at a newline, And should not have stuffs before!\n")
122            raise sa_err.BadFormatXMLError(_format, source_file)
123
124    def __parse_xml_file(self, source_file):
125        parser = ET.XMLParser()
126        tree = ET.parse(source_file, parser)
127        self.root = tree.getroot()
128        self.systemability_nodes = self.root.findall("systemability")
129
130        try:
131            first_sa_node = self.systemability_nodes[0]
132            self.sa_nodes_count = len(self.systemability_nodes)
133            children_nodes = list(self.root.iter())
134            self.sanode_start_idx = children_nodes.index(first_sa_node)
135        except IndexError:
136            # throw a BadFormatXMLError or just pass over if
137            # no <systemability> tag found in xml file
138            pass
139
140    def __rearrange_systemability_node_nonstrict(self, dest_file):
141        """
142        Rearrange systemability nodes in this way will change the original
143        format of source file
144        """
145        # remove old systemability nodes
146        for systemability_node in self.systemability_nodes:
147            self.root.remove(systemability_node)
148
149        # insert the rearranged systemability nodes
150        for idx in range(len(self.rearranged_systemabilities)):
151            name = self.rearranged_systemabilities[idx]
152            self.root.insert(self.sanode_start_idx + idx,
153                             self.name_node_dict[name])
154        tree = ET.ElementTree(self.root)
155        tree.write(dest_file, encoding="utf-8", xml_declaration=True)
156
157    def __rearrange_systemability_node_strict(self, dest_file):
158        """
159        Rearrange systemability nodes in this way preserve the original
160        format of source file
161        """
162        if self.first_sanode_start_lineno != 0:
163            rearranged_lines = self.xml_lines[:self.first_sanode_start_lineno]
164            for name in self.rearranged_systemabilities:
165                ranges = self.xml_lines_range[name]
166                rearranged_lines += self.xml_lines[ranges[0]:ranges[1] + 1]
167            rearranged_lines += self.xml_lines[self.last_sanode_end_lineno +
168                                               1:]
169        else:
170            rearranged_lines = self.xml_lines
171
172        with open(dest_file, "w", encoding="utf-8") as xml_files:
173            for line in rearranged_lines:
174                xml_files.write(line)
175
176    def __sort_systemability_by_bootphase_priority(self):
177        def inner_category_sort(systemabilities):
178            """
179            Sort dependencies with same bootphase category, preserve the
180            original order in source file
181            """
182            systemabilities_ = systemabilities[:]
183            for systemability in systemabilities_:
184                dependencies = self.systemability_deps_dict[systemability]
185                for dependency in dependencies:
186                    # should update idx_self each iteration
187                    idx_self = systemabilities.index(systemability)
188                    try:
189                        idx_dep = systemabilities.index(dependency)
190                        # if the dependency is behind, then exchange the order
191                        if idx_self < idx_dep:
192                            tmp = systemabilities[idx_dep]
193                            systemabilities[idx_dep] = systemabilities[
194                                idx_self]
195                            systemabilities[idx_self] = tmp
196                    except ValueError:
197                        pass  # ignore different category of dependencies
198
199        # put the systemability nodes into different categories
200        for systemability_name in self.ordered_systemability_names:
201            bootphase = self.bootphase_dict.get(systemability_name)
202            salist = self.policy.bootphase_categories.get(bootphase)
203            salist.append(systemability_name)
204
205        # sort the systemability nodes according to RearrangementPolicy
206        for category in RearrangementPolicy.rearrange_category_order:
207            salist = self.policy.bootphase_categories.get(category)
208            inner_category_sort(salist)
209            self.rearranged_systemabilities += salist
210
211    def __detect_invert_dependency(self, systemability, depend):
212        """
213        Detect invert dependency: systemability with high boot priority depends
214        on systemability with low ones, e.g. a systemability named 'sa1' with
215        BootStartPhase priority depends on a systemability named 'sa2' with
216        CoreStartPhase
217        """
218        _format = ("Bad dependency found: the {} with high priority "
219                   "depends on a {} with low one")
220        self_idx = self.bootphase_dict.get(systemability)
221        # The depend may be in other process
222        dep_idx = self.bootphase_dict.get(depend)
223        if dep_idx is None:
224            return
225        self_priority = RearrangementPolicy.bootphase_priority_table.get(
226            self_idx)
227        depend_priority = RearrangementPolicy.bootphase_priority_table.get(
228            dep_idx)
229        if self_priority > depend_priority:
230            raise sa_err.InvertDependencyError(
231                _format.format(systemability, depend))
232
233    def __detect_creation_dependency(self, systemability, depend):
234        """
235        Detect dependency related to configuration on <run-on-create>:
236        if a sa with <run-on-create> set to 'true' depending on a sa
237        with 'false', then a RunOnCreateDependencyError will be thrown
238        """
239        _format = ("Bad dependency found: the {} with run-on-create "
240                   "depends on a {} with run-on-demand")
241        self_creation = self.creation_dict.get(systemability)
242        dep_creation = self.creation_dict.get(depend)
243        if self_creation == "true" and dep_creation == "false":
244            raise sa_err.RunOnCreateDependencyError(
245                _format.format(systemability, depend))
246
247    @classmethod
248    def __detect_invalid_dependency(cls, dependency_checkers, ordered_sa_names,
249                                    sa_deps_dict):
250        """
251        Iterate over the dependency tree, to detect whether there
252        exists circular dependency and other kinds bad dependency
253        """
254        deps_visit_cnt = {}
255        ordered_systemability_names = ordered_sa_names
256        systemability_deps_dict = sa_deps_dict
257
258        def init_dependencies_visit_counter():
259            for name in ordered_systemability_names:
260                deps_visit_cnt[name] = 0
261
262        def do_check(systemability, dependency):
263            """
264            Check other kind dependency problem
265            """
266            for checker in dependency_checkers:
267                checker(systemability, dependency)
268
269        init_dependencies_visit_counter()
270        for systemability in ordered_systemability_names:
271            depend_path = []
272            depend_path.append(systemability)
273            while len(depend_path) != 0:
274                cur_systemability = depend_path[-1]
275                # the cur_systemability may be in a different process,
276                # thus can't find it's dependency info
277                dependencies = systemability_deps_dict.get(cur_systemability)
278                if dependencies is None:
279                    dependencies = []
280                deps_count = len(dependencies)
281                if deps_count == 0:
282                    depend_path.pop()
283                else:
284                    if deps_visit_cnt[cur_systemability] < deps_count:
285                        index = deps_visit_cnt[cur_systemability]
286                        cur_dependency = dependencies[index]
287                        # execute other kind dependency checkers right here
288                        do_check(cur_systemability, cur_dependency)
289                        try:
290                            depend_path.index(cur_dependency)
291                            depend_path.append(cur_dependency)
292                            _format = "A circular dependency found: {}"
293                            route = "->".join(map(str, depend_path))
294                            raise sa_err.CircularDependencyError(
295                                _format.format(route))
296                        except ValueError:
297                            depend_path.append(cur_dependency)
298                            deps_visit_cnt[cur_systemability] += 1
299                    else:
300                        # pop the systemability in process if it's all
301                        # dependencies have been visited
302                        depend_path.pop()
303
304    def __extract_info_from_systemability_nodes(self):
305        """
306        Extract info like dependencies and bootphase from a systemability node
307        """
308        def validate_bootphase(bootphase, nodename):
309            _format = (
310                "In systemability: {}, The bootphase '{}' is not supported "
311                "please check yourself")
312            if self.policy.bootphase_categories.get(bootphase) is None:
313                raise sa_err.NotSupportedBootphaseError(
314                    _format.format(nodename, bootphase))
315
316        def validate_creation(creation):
317            _format = ("In tag <{}> only a boolean value is expected, "
318                       "but actually is '{}'")
319            if creation not in ["true", "false"]:
320                raise sa_err.BadFormatXMLError(
321                    _format.format("run-on-create", creation),
322                    self.file_in_process)
323
324        def validate_systemability_name(nodename):
325            if not nodename.isdigit() or nodename.startswith("0"):
326                _format = ("<name>'s value should be non-zeros leading "
327                           "digits, but actually is {}")
328                raise sa_err.BadFormatXMLError(_format.format(nodename),
329                                               self.file_in_process)
330
331        def check_nodes_constraints(systemability_node, tag, ranges):
332            """
333            The number of a given node should be in a valid range
334            """
335            _format = ("The tag <{}> should be in range {},"
336                       " but actually {} is found")
337            tags_nodes = systemability_node.findall(tag)
338            node_cnt = len(tags_nodes)
339            if node_cnt < ranges[0] or node_cnt > ranges[1]:
340                raise sa_err.BadFormatXMLError(
341                    _format.format(tag, ranges, node_cnt),
342                    self.file_in_process)
343            return tags_nodes
344
345        def strip_node_value(tag, name):
346            """
347            Check empty or None tag value
348            """
349            _format = ("The tag <{}>'s value cannot be empty, "
350                       "but actually is {}")
351            if tag.text is None or tag.text.strip() == '':
352                raise sa_err.BadFormatXMLError(_format.format(name, tag.text),
353                                               self.file_in_process)
354            return tag.text.strip()
355
356        default_bootphase = RearrangementPolicy.DEFAULT_START_PHASE
357        for systemability_node in self.systemability_nodes:
358            # Required <name> one and only one is expected
359            name_node = check_nodes_constraints(systemability_node, "name",
360                                                (1, 1))[0]
361            nodename = strip_node_value(name_node, "name")
362            validate_systemability_name(nodename)
363
364            try:
365                self.ordered_systemability_names.index(nodename)
366                raise sa_err.SystemAbilityNameConflictError(nodename)
367            except ValueError:
368                self.ordered_systemability_names.append(nodename)
369            self.name_node_dict[nodename] = copy.deepcopy(systemability_node)
370            self.systemability_deps_dict[nodename] = []
371            self.bootphase_dict[nodename] = default_bootphase
372
373            # Optional <bootphase> zero or one are both accepted
374            bootphase_nodes = check_nodes_constraints(systemability_node,
375                                                      "bootphase", (0, 1))
376            if len(bootphase_nodes) == 1:
377                bootphase_value = strip_node_value(bootphase_nodes[0],
378                                                   "bootphase")
379                validate_bootphase(bootphase_value, nodename)
380                self.bootphase_dict[nodename] = bootphase_value
381
382            # Required <run-on-create> one and only one is expected
383            runoncreate_node = check_nodes_constraints(systemability_node,
384                                                       "run-on-create",
385                                                       (1, 1))[0]
386            runoncreate_value = strip_node_value(runoncreate_node,
387                                                 "run-on-create")
388            validate_creation(runoncreate_value)
389            self.creation_dict[nodename] = runoncreate_value
390
391            # Optional <depend>
392            depend_nodes = check_nodes_constraints(systemability_node,
393                                                   "depend", (0, sys.maxsize))
394            for depend_node in depend_nodes:
395                depend_value = strip_node_value(depend_node, "depend")
396                deps = self.systemability_deps_dict[nodename]
397                deps.append(depend_value)
398
399    def sort(self, source_file, dest_file):
400        self.file_in_process = source_file
401        dependency_checkers = []
402        dependency_checkers.append(self.__detect_invert_dependency)
403        dependency_checkers.append(self.__detect_creation_dependency)
404
405        self.__parse_xml_file(source_file)
406        self.__extract_info_from_systemability_nodes()
407        self.__read_xml_file_into_lines(source_file)
408        self.__detect_invalid_dependency(dependency_checkers,
409                                         self.ordered_systemability_names,
410                                         self.systemability_deps_dict)
411        self.__sort_systemability_by_bootphase_priority()
412        self.__rearrange_systemability_node_strict(dest_file)
413
414    @classmethod
415    def detect_invalid_dependency_globally(cls,
416                                           global_ordered_systemability_names,
417                                           global_systemability_deps_dict):
418        dependency_checkers = []
419        cls.__detect_invalid_dependency(dependency_checkers,
420                                        global_ordered_systemability_names,
421                                        global_systemability_deps_dict)
422
423    def get_deps_info(self):
424        """
425        Returns systemabilities and their dependencies for later detecting
426        possible globally circular dependency problem
427        """
428        return [self.ordered_systemability_names, self.systemability_deps_dict]
429