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