1#!/usr/bin/env python3 2# vim: set syntax=python ts=4 : 3# 4# Copyright (c) 2018-2022 Intel Corporation 5# Copyright (c) 2024 Arm Limited (or its affiliates). All rights reserved. 6# 7# SPDX-License-Identifier: Apache-2.0 8 9import logging 10import os 11import shutil 12from argparse import Namespace 13from itertools import groupby 14 15import list_boards 16import scl 17from twisterlib.constants import SUPPORTED_SIMS 18from twisterlib.environment import ZEPHYR_BASE 19 20logger = logging.getLogger('twister') 21 22 23class Simulator: 24 """Class representing a simulator""" 25 26 def __init__(self, data: dict[str, str]): 27 assert "name" in data 28 assert data["name"] in SUPPORTED_SIMS 29 self.name = data["name"] 30 self.exec = data.get("exec") 31 32 def is_runnable(self) -> bool: 33 if self.name == "simics": 34 return shutil.which(self.exec, path=os.environ.get("SIMICS_PROJECT")) is not None 35 36 return not bool(self.exec) or bool(shutil.which(self.exec)) 37 38 def __str__(self): 39 return f"Simulator(name: {self.name}, exec: {self.exec})" 40 41 def __eq__(self, other): 42 if isinstance(other, Simulator): 43 return self.name == other.name and self.exec == other.exec 44 else: 45 return False 46 47 48class Platform: 49 """Class representing metadata for a particular platform 50 51 Maps directly to BOARD when building""" 52 53 platform_schema = scl.yaml_load( 54 os.path.join(ZEPHYR_BASE, "scripts", "schemas", "twister", "platform-schema.yaml") 55 ) 56 57 def __init__(self): 58 """Constructor. 59 60 """ 61 62 self.name = "" 63 self.aliases = [] 64 self.normalized_name = "" 65 # if sysbuild to be used by default on a given platform 66 self.sysbuild = False 67 self.twister = True 68 # if no RAM size is specified by the board, take a default of 128K 69 self.ram = 128 70 71 self.timeout_multiplier = 1.0 72 self.ignore_tags = [] 73 self.only_tags = [] 74 self.default = False 75 # if no flash size is specified by the board, take a default of 512K 76 self.flash = 512 77 self.supported = set() 78 self.binaries = [] 79 80 self.arch = None 81 self.vendor = "" 82 self.tier = -1 83 self.type = "na" 84 self.simulators: list[Simulator] = [] 85 self.simulation: str = "na" 86 self.supported_toolchains = [] 87 self.env = [] 88 self.env_satisfied = True 89 self.filter_data = dict() 90 self.uart = "" 91 self.resc = "" 92 93 def load(self, board, target, aliases, data, variant_data): 94 """Load the platform data from the board data and target data 95 board: the board object as per the zephyr build system 96 target: the target name of the board as per the zephyr build system 97 aliases: list of aliases for the target 98 data: the default data from the twister.yaml file for the board 99 variant_data: the target-specific data to replace the default data 100 """ 101 self.name = target 102 self.aliases = aliases 103 104 self.normalized_name = self.name.replace("/", "_") 105 self.sysbuild = variant_data.get("sysbuild", data.get("sysbuild", self.sysbuild)) 106 self.twister = variant_data.get("twister", data.get("twister", self.twister)) 107 108 # if no RAM size is specified by the board, take a default of 128K 109 self.ram = variant_data.get("ram", data.get("ram", self.ram)) 110 # if no flash size is specified by the board, take a default of 512K 111 self.flash = variant_data.get("flash", data.get("flash", self.flash)) 112 113 testing = data.get("testing", {}) 114 self.ignore_tags = testing.get("ignore_tags", []) 115 self.only_tags = testing.get("only_tags", []) 116 self.default = testing.get("default", self.default) 117 self.binaries = testing.get("binaries", []) 118 self.timeout_multiplier = testing.get("timeout_multiplier", self.timeout_multiplier) 119 120 # testing data for variant 121 testing_var = variant_data.get("testing", data.get("testing", {})) 122 self.timeout_multiplier = testing_var.get("timeout_multiplier", self.timeout_multiplier) 123 self.ignore_tags = testing_var.get("ignore_tags", self.ignore_tags) 124 self.only_tags = testing_var.get("only_tags", self.only_tags) 125 self.default = testing_var.get("default", self.default) 126 self.binaries = testing_var.get("binaries", self.binaries) 127 renode = testing.get("renode", {}) 128 self.uart = renode.get("uart", "") 129 self.resc = renode.get("resc", "") 130 131 self.supported = set() 132 for supp_feature in variant_data.get("supported", data.get("supported", [])): 133 for item in supp_feature.split(":"): 134 self.supported.add(item) 135 136 self.arch = variant_data.get('arch', data.get('arch', self.arch)) 137 self.vendor = board.vendor 138 self.tier = variant_data.get("tier", data.get("tier", self.tier)) 139 self.type = variant_data.get('type', data.get('type', self.type)) 140 141 self.simulators = [ 142 Simulator(data) for data in variant_data.get( 143 'simulation', 144 data.get('simulation', self.simulators) 145 ) 146 ] 147 default_sim = self.simulator_by_name(None) 148 if default_sim: 149 self.simulation = default_sim.name 150 151 self.supported_toolchains = variant_data.get("toolchain", data.get("toolchain", [])) 152 if self.supported_toolchains is None: 153 self.supported_toolchains = [] 154 155 support_toolchain_variants = { 156 # we don't provide defaults for 'arc' intentionally: some targets can't be built with GNU 157 # toolchain ("zephyr", "cross-compile" options) and for some targets we haven't provided 158 # MWDT compiler / linker options in corresponding SoC file in Zephyr, so these targets 159 # can't be built with ARC MWDT toolchain ("arcmwdt" option) by Zephyr build system Instead 160 # for 'arc' we rely on 'toolchain' option in board yaml configuration. 161 "arm": ["zephyr", "gnuarmemb", "armclang", "llvm"], 162 "arm64": ["zephyr", "cross-compile"], 163 "mips": ["zephyr"], 164 "riscv": ["zephyr", "cross-compile"], 165 "posix": ["host", "llvm"], 166 "sparc": ["zephyr"], 167 "x86": ["zephyr", "llvm"], 168 # Xtensa is not listed on purpose, since there is no single toolchain 169 # that is supported on all board targets for xtensa. 170 } 171 172 if self.arch in support_toolchain_variants: 173 for toolchain in support_toolchain_variants[self.arch]: 174 if toolchain not in self.supported_toolchains: 175 self.supported_toolchains.append(toolchain) 176 177 self.env = variant_data.get("env", data.get("env", [])) 178 self.env_satisfied = True 179 for env in self.env: 180 if not os.environ.get(env, None): 181 self.env_satisfied = False 182 183 def simulator_by_name(self, sim_name: str | None) -> Simulator | None: 184 if sim_name: 185 return next(filter(lambda s: s.name == sim_name, iter(self.simulators)), None) 186 else: 187 return next(iter(self.simulators), None) 188 189 def __repr__(self): 190 return f"<{self.name} on {self.arch}>" 191 192 193def generate_platforms(board_roots, soc_roots, arch_roots): 194 """Initialize and yield all Platform instances. 195 196 Using the provided board/soc/arch roots, determine the available 197 platform names and load the test platform description files. 198 199 An exception is raised if not all platform files are valid YAML, 200 or if not all platform names are unique. 201 """ 202 alias2target = {} 203 target2board = {} 204 target2data = {} 205 dir2data = {} 206 legacy_files = [] 207 208 lb_args = Namespace(board_roots=board_roots, soc_roots=soc_roots, arch_roots=arch_roots, 209 board=None, board_dir=None) 210 211 for board in list_boards.find_v2_boards(lb_args).values(): 212 for board_dir in board.directories: 213 if board_dir in dir2data: 214 # don't load the same board data twice 215 continue 216 file = board_dir / "twister.yaml" 217 if file.is_file(): 218 data = scl.yaml_load_verify(file, Platform.platform_schema) 219 else: 220 data = None 221 dir2data[board_dir] = data 222 223 legacy_files.extend( 224 file for file in board_dir.glob("*.yaml") if file.name != "twister.yaml" 225 ) 226 227 for qual in list_boards.board_v2_qualifiers(board): 228 if board.revisions: 229 for rev in board.revisions: 230 if rev.name: 231 target = f"{board.name}@{rev.name}/{qual}" 232 alias2target[target] = target 233 if rev.name == board.revision_default: 234 alias2target[f"{board.name}/{qual}"] = target 235 if '/' not in qual and len(board.socs) == 1: 236 if rev.name == board.revision_default: 237 alias2target[f"{board.name}"] = target 238 alias2target[f"{board.name}@{rev.name}"] = target 239 else: 240 target = f"{board.name}/{qual}" 241 alias2target[target] = target 242 if '/' not in qual and len(board.socs) == 1 \ 243 and rev.name == board.revision_default: 244 alias2target[f"{board.name}"] = target 245 246 target2board[target] = board 247 else: 248 target = f"{board.name}/{qual}" 249 alias2target[target] = target 250 if '/' not in qual and len(board.socs) == 1: 251 alias2target[board.name] = target 252 target2board[target] = board 253 254 for board_dir, data in dir2data.items(): 255 if data is None: 256 continue 257 # Separate the default and variant information in the loaded board data. 258 # The default (top-level) data can be shared by multiple board targets; 259 # it will be overlaid by the variant data (if present) for each target. 260 variant_data = data.pop("variants", {}) 261 for variant in variant_data: 262 target = alias2target.get(variant) 263 if target is None: 264 continue 265 if target in target2data: 266 logger.error(f"Duplicate platform {target} in {board_dir}") 267 raise Exception(f"Duplicate platform identifier {target} found") 268 target2data[target] = variant_data[variant] 269 270 # note: this inverse mapping will only be used for loading legacy files 271 target2aliases = {} 272 273 for target, aliases in groupby(alias2target, alias2target.get): 274 aliases = list(aliases) 275 board = target2board[target] 276 277 # Default board data always comes from the primary 'board.dir'. 278 # Other 'board.directories' can only supply variant data. 279 data = dir2data[board.dir] 280 if data is not None: 281 variant_data = target2data.get(target, {}) 282 283 platform = Platform() 284 platform.load(board, target, aliases, data, variant_data) 285 yield platform 286 287 target2aliases[target] = aliases 288 289 for file in legacy_files: 290 data = scl.yaml_load_verify(file, Platform.platform_schema) 291 target = alias2target.get(data.get("identifier")) 292 if target is None: 293 continue 294 295 board = target2board[target] 296 if dir2data[board.dir] is not None: 297 # all targets are already loaded for this board 298 logger.error(f"Duplicate platform {target} in {file.parent}") 299 raise Exception(f"Duplicate platform identifier {target} found") 300 301 platform = Platform() 302 platform.load(board, target, target2aliases[target], data, variant_data={}) 303 yield platform 304