You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
303 lines
12 KiB
303 lines
12 KiB
#!/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
|
|
|