1#!/usr/bin/env python 2# -*- coding: utf-8 -*- 3# Copyright (c) 2022 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 os 17import json 18import copy 19import sa_info_config_errors as json_err 20 21 22class RearrangementPolicy(object): 23 BOOT_START_PHASE = "BootStartPhase" 24 CORE_START_PHASE = "CoreStartPhase" 25 DEFAULT_START_PHASE = "OthersStartPhase" 26 27 rearrange_category_order = (BOOT_START_PHASE, CORE_START_PHASE, 28 DEFAULT_START_PHASE) 29 30 bootphase_priority_table = { 31 BOOT_START_PHASE: 3, 32 CORE_START_PHASE: 2, 33 DEFAULT_START_PHASE: 1 34 } 35 36 def __init__(self): 37 self.bootphase_categories = { 38 RearrangementPolicy.BOOT_START_PHASE: [], 39 RearrangementPolicy.CORE_START_PHASE: [], 40 RearrangementPolicy.DEFAULT_START_PHASE: [] 41 } 42 43 44class SARearrangement(object): 45 def __init__(self): 46 self.rearranged_systemabilities = [] 47 self.ordered_systemability_names = [] 48 self.name_node_dict = {} 49 self.systemability_deps_dict = {} 50 self.bootphase_dict = {} 51 self.creation_dict = {} 52 self.policy = RearrangementPolicy() 53 self.sanode_start_idx = 0 54 self.systemability_nodes = [] 55 self.sa_nodes_count = 0 56 57 @classmethod 58 def detect_invalid_dependency_globally(clazz, 59 global_ordered_systemability_names, 60 global_systemability_deps_dict): 61 dependency_checkers = [] 62 clazz.__detect_invalid_dependency(dependency_checkers, 63 global_ordered_systemability_names, 64 global_systemability_deps_dict) 65 66 @classmethod 67 def __detect_invalid_dependency(self, dependency_checkers, ordered_sa_names: str, 68 sa_deps_dict: dict): 69 """ 70 Iterate over the dependency tree, to detect whether there 71 exists circular dependency and other kinds bad dependency 72 """ 73 deps_visit_cnt = {} 74 ordered_systemability_names = ordered_sa_names 75 systemability_deps_dict = sa_deps_dict 76 77 def init_dependencies_visit_counter(): 78 for name in ordered_systemability_names: 79 deps_visit_cnt[name] = 0 80 81 def do_check(systemability, dependency): 82 """ 83 Check other kind dependency problem 84 """ 85 for checker in dependency_checkers: 86 checker(systemability, dependency) 87 88 def check_depend(cur_systemability: str, deps_count: int, dependencies: list, depend_path: list): 89 if deps_visit_cnt.get(cur_systemability) < deps_count: 90 index = deps_visit_cnt.get(cur_systemability) 91 cur_dependency = dependencies[index] 92 # execute other kind dependency checkers right here 93 do_check(cur_systemability, cur_dependency) 94 try: 95 depend_path.index(cur_dependency) 96 depend_path.append(cur_dependency) 97 _format = "A circular dependency found: {}" 98 route = "->".join(map(str, depend_path)) 99 raise json_err.CircularDependencyError(_format.format(route)) 100 except ValueError: 101 depend_path.append(cur_dependency) 102 deps_visit_cnt[cur_systemability] += 1 103 else: 104 # pop the systemability in process if it's all 105 # dependencies have been visited 106 depend_path.pop() 107 108 init_dependencies_visit_counter() 109 for systemability in ordered_systemability_names: 110 depend_path = [] 111 depend_path.append(systemability) 112 while len(depend_path) != 0: 113 cur_systemability = depend_path[-1] 114 # the cur_systemability may be in a different process, 115 # thus can't find it's dependency info 116 dependencies = systemability_deps_dict.get(cur_systemability) 117 if dependencies is None: 118 dependencies = [] 119 deps_count = len(dependencies) 120 if deps_count == 0: 121 depend_path.pop() 122 else: 123 check_depend(cur_systemability, deps_count, dependencies, depend_path) 124 125 def sort(self, source_file: str, dest_file: str): 126 self.file_in_process = source_file 127 dependency_checkers = [] 128 dependency_checkers.append(self.__detect_invert_dependency) 129 dependency_checkers.append(self.__detect_creation_dependency) 130 131 self.__parse_json_file(source_file) 132 self.__extract_info_from_systemability_nodes() 133 self.__detect_invalid_dependency(dependency_checkers, 134 self.ordered_systemability_names, 135 self.systemability_deps_dict) 136 self.__sort_systemability_by_bootphase_priority() 137 self.__rearrange_systemability_node_strict(source_file, dest_file) 138 139 def get_deps_info(self): 140 """ 141 Returns systemabilities and their dependencies for later detecting 142 possible globally circular dependency problem 143 """ 144 return [self.ordered_systemability_names, self.systemability_deps_dict] 145 146 def __detect_invert_dependency(self, systemability: list, depend): 147 """ 148 Detect invert dependency: systemability with high boot priority depends 149 on systemability with low ones, e.g. a systemability named 'sa1' with 150 BootStartPhase priority depends on a systemability named 'sa2' with 151 CoreStartPhase 152 """ 153 _format = ("Bad dependency found: the {} with high priority " + 154 "depends on a {} with low one") 155 self_idx = self.bootphase_dict.get(systemability) 156 # The depend may be in other process 157 dep_idx = self.bootphase_dict.get(depend) 158 if dep_idx is None: 159 return 160 self_priority = RearrangementPolicy.bootphase_priority_table.get( 161 self_idx) 162 depend_priority = RearrangementPolicy.bootphase_priority_table.get( 163 dep_idx) 164 if self_priority > depend_priority: 165 raise json_err.InvertDependencyError( 166 _format.format(systemability, depend)) 167 168 def __parse_json_file(self, source_file: str): 169 with open(source_file, "r", encoding="utf-8") as file: 170 data = json.load(file) 171 self.systemability_nodes = data['systemability'] 172 try: 173 first_sa_node = self.systemability_nodes[0] 174 self.sa_nodes_count = len(self.systemability_nodes) 175 except IndexError: 176 pass 177 178 def __rearrange_systemability_node_strict(self, source_file: str, dest_file: str): 179 rearranged_name = self.rearranged_systemabilities 180 final_systemability = [] 181 for name in rearranged_name: 182 temp = self.name_node_dict.get(name) 183 final_systemability.append(temp) 184 with open(source_file, "r", encoding="utf-8") as file: 185 data = json.load(file) 186 data['systemability'] = final_systemability 187 file_node = os.open(dest_file, os.O_RDWR | os.O_CREAT, 0o640) 188 with os.fdopen(file_node, 'w') as json_files: 189 json.dump(data, json_files, indent=4, ensure_ascii=False) 190 191 def __detect_creation_dependency(self, systemability: list, depend): 192 """ 193 Detect dependency related to configuration on <run-on-create>: 194 if a sa with <run-on-create> set to 'true' depending on a sa 195 with 'false', then a RunOnCreateDependencyError will be thrown 196 """ 197 _format = ("Bad dependency found: the {} with run-on-create " + 198 "depends on a {} with run-on-demand") 199 self_creation = self.creation_dict.get(systemability) 200 dep_creation = self.creation_dict.get(depend) 201 if self_creation is True and dep_creation is False: 202 raise json_err.RunOnCreateDependencyError(_format.format(systemability, depend)) 203 204 def __extract_info_from_systemability_nodes(self): 205 """ 206 Extract info like dependencies and bootphase from a systemability node 207 """ 208 209 def validate_creation(creation: bool): 210 _format = ("In tag {} only a boolean value is expected, " + 211 "but actually is '{}'") 212 if str(creation) not in {"true", "false", "True", "False"}: 213 raise json_err.BadFormatJsonError(_format.format("run-on-create", creation), 214 self.file_in_process) 215 216 def validate_bootphase(bootphase, nodename: str): 217 _format = ("In systemability: {}, The bootphase '{}' is not supported " + 218 "please check yourself") 219 if self.policy.bootphase_categories.get(bootphase) is None: 220 raise json_err.NotSupportedBootphaseError(_format.format(nodename, bootphase)) 221 222 def validate_systemability_name(nodename: str): 223 if nodename < 1 or nodename > 16777215: 224 _format = ("name's value should be [1-16777215], but actually is {}") 225 raise json_err.BadFormatJsonError(_format.format(nodename), 226 self.file_in_process) 227 228 def check_nodes_constraints_one(systemability_node: dict, tag: str): 229 _format = ("The tag {} should exist, but it does not exist") 230 if tag not in systemability_node: 231 raise json_err.BadFormatJsonError(_format.format(tag), self.file_in_process) 232 tags_nodes = systemability_node[tag] 233 return tags_nodes 234 235 def check_nodes_constraints_two(systemability_node: dict, tag: str): 236 if tag not in systemability_node or systemability_node[tag] == '': 237 return "" 238 tags_nodes = systemability_node[tag] 239 return tags_nodes 240 241 default_bootphase = RearrangementPolicy.DEFAULT_START_PHASE 242 for systemability_node in self.systemability_nodes: 243 # Required <name> one and only one is expected 244 name_node = check_nodes_constraints_one(systemability_node, "name") 245 validate_systemability_name(name_node) 246 try: 247 self.ordered_systemability_names.index(name_node) 248 raise json_err.SystemAbilityNameConflictError(name_node) 249 except ValueError: 250 self.ordered_systemability_names.append(name_node) 251 self.name_node_dict[name_node] = systemability_node 252 self.systemability_deps_dict[name_node] = [] 253 self.bootphase_dict[name_node] = default_bootphase 254 # Optional bootphase: zero or one are both accepted 255 bootphase_nodes = check_nodes_constraints_two(systemability_node, 256 "bootphase") 257 if bootphase_nodes != '': 258 validate_bootphase(bootphase_nodes, name_node) 259 self.bootphase_dict[name_node] = bootphase_nodes 260 # Required run-on-create one and only one is expected 261 runoncreate_node = check_nodes_constraints_one(systemability_node, 262 "run-on-create") 263 validate_creation(runoncreate_node) 264 self.creation_dict[name_node] = runoncreate_node 265 # Optional depend: 266 depend_nodes = check_nodes_constraints_two(systemability_node, 267 "depend") 268 for depend_node in depend_nodes: 269 deps = self.systemability_deps_dict.get(name_node) 270 deps.append(depend_node) 271 272 def __sort_systemability_by_bootphase_priority(self): 273 def check_index(systemabilities: list, dependency, idx_self: int): 274 try: 275 idx_dep = systemabilities.index(dependency) 276 # if the dependency is behind, then exchange the order 277 if idx_self < idx_dep: 278 tmp = systemabilities[idx_dep] 279 systemabilities[idx_dep] = systemabilities[idx_self] 280 systemabilities[idx_self] = tmp 281 except ValueError: 282 pass # ignore different category of dependencies 283 284 def inner_category_sort(systemabilities: list): 285 """ 286 Sort dependencies with same bootphase category, preserve the 287 original order in source file 288 """ 289 systemabilities_ = systemabilities[:] 290 for systemability in systemabilities_: 291 dependencies = self.systemability_deps_dict.get(systemability) 292 for dependency in dependencies: 293 # should update idx_self each iteration 294 idx_self = systemabilities.index(systemability) 295 check_index(systemabilities, dependency, idx_self) 296 297 # put the systemability nodes into different categories 298 for systemability_name in self.ordered_systemability_names: 299 bootphase = self.bootphase_dict.get(systemability_name) 300 salist = self.policy.bootphase_categories.get(bootphase) 301 salist.append(systemability_name) 302 303 # sort the systemability nodes according to RearrangementPolicy 304 for category in RearrangementPolicy.rearrange_category_order: 305 salist = self.policy.bootphase_categories.get(category) 306 inner_category_sort(salist) 307 self.rearranged_systemabilities += salist 308