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