zephyr/scripts/list_shields.py
Benjamin Cabé 804915841a shields: doc: allow to indicate supported hw features
Shield authors can now indicate an optional list of hardware features
that the shield supports, in the form of the same kind of "binding type"
already used for boards.

Signed-off-by: Benjamin Cabé <benjamin@zephyrproject.org>
2025-05-28 15:33:50 +02:00

137 lines
4.2 KiB
Python
Executable File

#!/usr/bin/env python3
# Copyright (c) 2024 Vestas Wind Systems A/S
# Copyright (c) 2020 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
import argparse
import json
import sys
from dataclasses import dataclass
from pathlib import Path
import pykwalify.core
import yaml
try:
from yaml import CSafeLoader as SafeLoader
except ImportError:
from yaml import SafeLoader
SHIELD_SCHEMA_PATH = str(Path(__file__).parent / 'schemas' / 'shield-schema.yml')
with open(SHIELD_SCHEMA_PATH) as f:
shield_schema = yaml.load(f.read(), Loader=SafeLoader)
SHIELD_YML = 'shield.yml'
#
# This is shared code between the build system's 'shields' target
# and the 'west shields' extension command. If you change it, make
# sure to test both ways it can be used.
#
# (It's done this way to keep west optional, making it possible to run
# 'ninja shields' in a build directory without west installed.)
#
@dataclass(frozen=True)
class Shield:
name: str
dir: Path
full_name: str | None = None
vendor: str | None = None
supported_features: list[str] | None = None
def shield_key(shield):
return shield.name
def process_shield_data(shield_data, shield_dir):
# Create shield from yaml data
return Shield(
name=shield_data['name'],
dir=shield_dir,
full_name=shield_data.get('full_name'),
vendor=shield_data.get('vendor'),
supported_features=shield_data.get('supported_features', []),
)
def find_shields(args):
ret = []
for root in args.board_roots:
for shields in find_shields_in(root):
ret.append(shields)
return sorted(ret, key=shield_key)
def find_shields_in(root):
shields = root / 'boards' / 'shields'
ret = []
if not shields.exists():
return ret
for maybe_shield in (shields).iterdir():
if not maybe_shield.is_dir():
continue
# Check for shield.yml first
shield_yml = maybe_shield / SHIELD_YML
if shield_yml.is_file():
with shield_yml.open('r', encoding='utf-8') as f:
shield_data = yaml.load(f.read(), Loader=SafeLoader)
try:
pykwalify.core.Core(source_data=shield_data, schema_data=shield_schema).validate()
except pykwalify.errors.SchemaError as e:
sys.exit(f'ERROR: Malformed shield.yml in file: {shield_yml.as_posix()}\n{e}')
if 'shields' in shield_data:
# Multiple shields format
for shield_info in shield_data['shields']:
ret.append(process_shield_data(shield_info, maybe_shield))
elif 'shield' in shield_data:
# Single shield format
ret.append(process_shield_data(shield_data['shield'], maybe_shield))
continue
# Fallback to legacy method if no shield.yml
for maybe_kconfig in maybe_shield.iterdir():
if maybe_kconfig.name == 'Kconfig.shield':
for maybe_overlay in maybe_shield.iterdir():
file_name = maybe_overlay.name
if file_name.endswith('.overlay'):
shield_name = file_name[:-len('.overlay')]
ret.append(Shield(shield_name, maybe_shield))
return sorted(ret, key=shield_key)
def parse_args():
parser = argparse.ArgumentParser(allow_abbrev=False)
add_args(parser)
add_args_formatting(parser)
return parser.parse_args()
def add_args(parser):
# Remember to update west-completion.bash if you add or remove
# flags
parser.add_argument("--board-root", dest='board_roots', default=[],
type=Path, action='append',
help='add a board root, may be given more than once')
def add_args_formatting(parser):
parser.add_argument("--json", action='store_true',
help='''output list of shields in JSON format''')
def dump_shields(shields):
if args.json:
print(
json.dumps([{'dir': shield.dir.as_posix(), 'name': shield.name} for shield in shields])
)
else:
for shield in shields:
print(f' {shield.name}')
if __name__ == '__main__':
args = parse_args()
dump_shields(find_shields(args))