| #!/usr/bin/env python3 |
| # vim: set syntax=python ts=4 : |
| # |
| # Copyright (c) 2018-2022 Intel Corporation |
| # Copyright (c) 2024 Arm Limited (or its affiliates). All rights reserved. |
| # |
| # SPDX-License-Identifier: Apache-2.0 |
| |
| import logging |
| import os |
| import shutil |
| from argparse import Namespace |
| from itertools import groupby |
| |
| import list_boards |
| import scl |
| from twisterlib.constants import SUPPORTED_SIMS |
| from twisterlib.environment import ZEPHYR_BASE |
| |
| logger = logging.getLogger('twister') |
| |
| |
| class Simulator: |
| """Class representing a simulator""" |
| |
| def __init__(self, data: dict[str, str]): |
| assert "name" in data |
| assert data["name"] in SUPPORTED_SIMS |
| self.name = data["name"] |
| self.exec = data.get("exec") |
| |
| def is_runnable(self) -> bool: |
| if self.name == "simics": |
| return shutil.which(self.exec, path=os.environ.get("SIMICS_PROJECT")) is not None |
| |
| return not bool(self.exec) or bool(shutil.which(self.exec)) |
| |
| def __str__(self): |
| return f"Simulator(name: {self.name}, exec: {self.exec})" |
| |
| def __eq__(self, other): |
| if isinstance(other, Simulator): |
| return self.name == other.name and self.exec == other.exec |
| else: |
| return False |
| |
| |
| class Platform: |
| """Class representing metadata for a particular platform |
| |
| Maps directly to BOARD when building""" |
| |
| platform_schema = scl.yaml_load( |
| os.path.join(ZEPHYR_BASE, "scripts", "schemas", "twister", "platform-schema.yaml") |
| ) |
| |
| def __init__(self): |
| """Constructor. |
| |
| """ |
| |
| self.name = "" |
| self.aliases = [] |
| self.normalized_name = "" |
| # if sysbuild to be used by default on a given platform |
| self.sysbuild = False |
| self.twister = True |
| # if no RAM size is specified by the board, take a default of 128K |
| self.ram = 128 |
| |
| self.timeout_multiplier = 1.0 |
| self.ignore_tags = [] |
| self.only_tags = [] |
| self.default = False |
| # if no flash size is specified by the board, take a default of 512K |
| self.flash = 512 |
| self.supported = set() |
| self.binaries = [] |
| |
| self.arch = None |
| self.vendor = "" |
| self.tier = -1 |
| self.type = "na" |
| self.simulators: list[Simulator] = [] |
| self.simulation: str = "na" |
| self.supported_toolchains = [] |
| self.env = [] |
| self.env_satisfied = True |
| self.filter_data = dict() |
| self.uart = "" |
| self.resc = "" |
| |
| def load(self, board, target, aliases, data, variant_data): |
| """Load the platform data from the board data and target data |
| board: the board object as per the zephyr build system |
| target: the target name of the board as per the zephyr build system |
| aliases: list of aliases for the target |
| data: the default data from the twister.yaml file for the board |
| variant_data: the target-specific data to replace the default data |
| """ |
| self.name = target |
| self.aliases = aliases |
| |
| self.normalized_name = self.name.replace("/", "_") |
| self.sysbuild = variant_data.get("sysbuild", data.get("sysbuild", self.sysbuild)) |
| self.twister = variant_data.get("twister", data.get("twister", self.twister)) |
| |
| # if no RAM size is specified by the board, take a default of 128K |
| self.ram = variant_data.get("ram", data.get("ram", self.ram)) |
| # if no flash size is specified by the board, take a default of 512K |
| self.flash = variant_data.get("flash", data.get("flash", self.flash)) |
| |
| testing = data.get("testing", {}) |
| self.ignore_tags = testing.get("ignore_tags", []) |
| self.only_tags = testing.get("only_tags", []) |
| self.default = testing.get("default", self.default) |
| self.binaries = testing.get("binaries", []) |
| self.timeout_multiplier = testing.get("timeout_multiplier", self.timeout_multiplier) |
| |
| # testing data for variant |
| testing_var = variant_data.get("testing", data.get("testing", {})) |
| self.timeout_multiplier = testing_var.get("timeout_multiplier", self.timeout_multiplier) |
| self.ignore_tags = testing_var.get("ignore_tags", self.ignore_tags) |
| self.only_tags = testing_var.get("only_tags", self.only_tags) |
| self.default = testing_var.get("default", self.default) |
| self.binaries = testing_var.get("binaries", self.binaries) |
| renode = testing.get("renode", {}) |
| self.uart = renode.get("uart", "") |
| self.resc = renode.get("resc", "") |
| |
| self.supported = set() |
| for supp_feature in variant_data.get("supported", data.get("supported", [])): |
| for item in supp_feature.split(":"): |
| self.supported.add(item) |
| |
| self.arch = variant_data.get('arch', data.get('arch', self.arch)) |
| self.vendor = board.vendor |
| self.tier = variant_data.get("tier", data.get("tier", self.tier)) |
| self.type = variant_data.get('type', data.get('type', self.type)) |
| |
| self.simulators = [ |
| Simulator(data) for data in variant_data.get( |
| 'simulation', |
| data.get('simulation', self.simulators) |
| ) |
| ] |
| default_sim = self.simulator_by_name(None) |
| if default_sim: |
| self.simulation = default_sim.name |
| |
| self.supported_toolchains = variant_data.get("toolchain", data.get("toolchain", [])) |
| if self.supported_toolchains is None: |
| self.supported_toolchains = [] |
| |
| support_toolchain_variants = { |
| # we don't provide defaults for 'arc' intentionally: some targets can't be built with GNU |
| # toolchain ("zephyr", "cross-compile" options) and for some targets we haven't provided |
| # MWDT compiler / linker options in corresponding SoC file in Zephyr, so these targets |
| # can't be built with ARC MWDT toolchain ("arcmwdt" option) by Zephyr build system Instead |
| # for 'arc' we rely on 'toolchain' option in board yaml configuration. |
| "arm": ["zephyr", "gnuarmemb", "armclang", "llvm"], |
| "arm64": ["zephyr", "cross-compile"], |
| "mips": ["zephyr"], |
| "riscv": ["zephyr", "cross-compile"], |
| "posix": ["host", "llvm"], |
| "sparc": ["zephyr"], |
| "x86": ["zephyr", "llvm"], |
| # Xtensa is not listed on purpose, since there is no single toolchain |
| # that is supported on all board targets for xtensa. |
| } |
| |
| if self.arch in support_toolchain_variants: |
| for toolchain in support_toolchain_variants[self.arch]: |
| if toolchain not in self.supported_toolchains: |
| self.supported_toolchains.append(toolchain) |
| |
| self.env = variant_data.get("env", data.get("env", [])) |
| self.env_satisfied = True |
| for env in self.env: |
| if not os.environ.get(env, None): |
| self.env_satisfied = False |
| |
| def simulator_by_name(self, sim_name: str | None) -> Simulator | None: |
| if sim_name: |
| return next(filter(lambda s: s.name == sim_name, iter(self.simulators)), None) |
| else: |
| return next(iter(self.simulators), None) |
| |
| def __repr__(self): |
| return f"<{self.name} on {self.arch}>" |
| |
| |
| def generate_platforms(board_roots, soc_roots, arch_roots): |
| """Initialize and yield all Platform instances. |
| |
| Using the provided board/soc/arch roots, determine the available |
| platform names and load the test platform description files. |
| |
| An exception is raised if not all platform files are valid YAML, |
| or if not all platform names are unique. |
| """ |
| alias2target = {} |
| target2board = {} |
| target2data = {} |
| dir2data = {} |
| legacy_files = [] |
| |
| lb_args = Namespace(board_roots=board_roots, soc_roots=soc_roots, arch_roots=arch_roots, |
| board=None, board_dir=None) |
| |
| for board in list_boards.find_v2_boards(lb_args).values(): |
| for board_dir in board.directories: |
| if board_dir in dir2data: |
| # don't load the same board data twice |
| continue |
| file = board_dir / "twister.yaml" |
| if file.is_file(): |
| data = scl.yaml_load_verify(file, Platform.platform_schema) |
| else: |
| data = None |
| dir2data[board_dir] = data |
| |
| legacy_files.extend( |
| file for file in board_dir.glob("*.yaml") if file.name != "twister.yaml" |
| ) |
| |
| for qual in list_boards.board_v2_qualifiers(board): |
| if board.revisions: |
| for rev in board.revisions: |
| if rev.name: |
| target = f"{board.name}@{rev.name}/{qual}" |
| alias2target[target] = target |
| if rev.name == board.revision_default: |
| alias2target[f"{board.name}/{qual}"] = target |
| if '/' not in qual and len(board.socs) == 1: |
| if rev.name == board.revision_default: |
| alias2target[f"{board.name}"] = target |
| alias2target[f"{board.name}@{rev.name}"] = target |
| else: |
| target = f"{board.name}/{qual}" |
| alias2target[target] = target |
| if '/' not in qual and len(board.socs) == 1 \ |
| and rev.name == board.revision_default: |
| alias2target[f"{board.name}"] = target |
| |
| target2board[target] = board |
| else: |
| target = f"{board.name}/{qual}" |
| alias2target[target] = target |
| if '/' not in qual and len(board.socs) == 1: |
| alias2target[board.name] = target |
| target2board[target] = board |
| |
| for board_dir, data in dir2data.items(): |
| if data is None: |
| continue |
| # Separate the default and variant information in the loaded board data. |
| # The default (top-level) data can be shared by multiple board targets; |
| # it will be overlaid by the variant data (if present) for each target. |
| variant_data = data.pop("variants", {}) |
| for variant in variant_data: |
| target = alias2target.get(variant) |
| if target is None: |
| continue |
| if target in target2data: |
| logger.error(f"Duplicate platform {target} in {board_dir}") |
| raise Exception(f"Duplicate platform identifier {target} found") |
| target2data[target] = variant_data[variant] |
| |
| # note: this inverse mapping will only be used for loading legacy files |
| target2aliases = {} |
| |
| for target, aliases in groupby(alias2target, alias2target.get): |
| aliases = list(aliases) |
| board = target2board[target] |
| |
| # Default board data always comes from the primary 'board.dir'. |
| # Other 'board.directories' can only supply variant data. |
| data = dir2data[board.dir] |
| if data is not None: |
| variant_data = target2data.get(target, {}) |
| |
| platform = Platform() |
| platform.load(board, target, aliases, data, variant_data) |
| yield platform |
| |
| target2aliases[target] = aliases |
| |
| for file in legacy_files: |
| data = scl.yaml_load_verify(file, Platform.platform_schema) |
| target = alias2target.get(data.get("identifier")) |
| if target is None: |
| continue |
| |
| board = target2board[target] |
| if dir2data[board.dir] is not None: |
| # all targets are already loaded for this board |
| logger.error(f"Duplicate platform {target} in {file.parent}") |
| raise Exception(f"Duplicate platform identifier {target} found") |
| |
| platform = Platform() |
| platform.load(board, target, target2aliases[target], data, variant_data={}) |
| yield platform |