|  | #!/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)) |