diff --git a/cmake/modules/shields.cmake b/cmake/modules/shields.cmake index 04aaec57ac7..ced26be5a58 100644 --- a/cmake/modules/shields.cmake +++ b/cmake/modules/shields.cmake @@ -31,6 +31,7 @@ include_guard(GLOBAL) include(extensions) +include(yaml) # Check that SHIELD has not changed. zephyr_check_cache(SHIELD WATCH) @@ -48,21 +49,47 @@ set(SHIELD-NOTFOUND ${SHIELD_AS_LIST}) foreach(root ${BOARD_ROOT}) set(shield_dir ${root}/boards/shields) - # Match the Kconfig.shield files in the shield directories to make sure we are - # finding shields, e.g. x_nucleo_iks01a1/Kconfig.shield + + # First, look for shield.yml files + file(GLOB_RECURSE shield_yml_files ${shield_dir}/*/shield.yml) + + foreach(shield_yml ${shield_yml_files}) + get_filename_component(shield_path ${shield_yml} DIRECTORY) + get_filename_component(shield ${shield_path} NAME) + + set(yaml_ctx_name shield_data_${shield}) + yaml_load(FILE ${shield_yml} NAME ${yaml_ctx_name}) + + # Check for multiple shields format first + yaml_get(shields_data NAME ${yaml_ctx_name} KEY shields) + if(shields_data) + yaml_length(num_shields NAME ${yaml_ctx_name} KEY shields) + if(${num_shields} GREATER 0) + math(EXPR shield_stop "${num_shields} - 1") + foreach(i RANGE 0 ${shield_stop}) + yaml_get(shield_name NAME ${yaml_ctx_name} KEY shields ${i} name) + list(APPEND SHIELD_LIST ${shield_name}) + set(SHIELD_DIR_${shield_name} ${shield_path}) + endforeach() + endif() + else() + yaml_get(shield_data NAME ${yaml_ctx_name} KEY shield) + if(shield_data) + yaml_get(shield_name NAME ${yaml_ctx_name} KEY shield name) + list(APPEND SHIELD_LIST ${shield_name}) + set(SHIELD_DIR_${shield_name} ${shield_path}) + endif() + endif() + endforeach() + + # Then, look for overlay files next to Kconfig.shield files as fallback (legacy shields) file(GLOB_RECURSE shields_refs_list ${shield_dir}/*/Kconfig.shield) - # The above gives a list of Kconfig.shield files, like this: - # - # x_nucleo_iks01a1/Kconfig.shield;x_nucleo_iks01a2/Kconfig.shield - # - # we construct a list of shield names by extracting the directories - # from each file and looking for .overlay files in there. - # Each overlay corresponds to a shield. We obtain the shield name by - # removing the .overlay extension. - # We also create a SHIELD_DIR_${name} variable for each shield's directory. foreach(shields_refs ${shields_refs_list}) get_filename_component(shield_path ${shields_refs} DIRECTORY) + if(EXISTS "${shield_path}/shield.yml") + continue() + endif() file(GLOB shield_overlays RELATIVE ${shield_path} ${shield_path}/*.overlay) foreach(overlay ${shield_overlays}) get_filename_component(shield ${overlay} NAME_WE) diff --git a/doc/hardware/porting/shields.rst b/doc/hardware/porting/shields.rst index d93a4354063..e42036b52c1 100644 --- a/doc/hardware/porting/shields.rst +++ b/doc/hardware/porting/shields.rst @@ -17,6 +17,7 @@ under :zephyr_file:`boards/shields`: .. code-block:: none boards/shields/ + ├── shield.yml ├── .overlay ├── Kconfig.shield ├── Kconfig.defconfig @@ -24,6 +25,21 @@ under :zephyr_file:`boards/shields`: These files provides shield configuration as follows: +* **shield.yml**: This file provides metadata about the shield in YAML format. + It must contain the following fields: + + * ``name``: Name of the shield used in Kconfig and build system (required) + * ``full_name``: Full commercial name of the shield (required) + * ``vendor``: Manufacturer/vendor of the shield (required) + + Example: + + .. code-block:: yaml + + name: foo_shield + full_name: Foo Shield for Arduino + vendor: acme + * **.overlay**: This file provides a shield description in devicetree format that is merged with the board's :ref:`devicetree ` before compilation. diff --git a/scripts/list_shields.py b/scripts/list_shields.py index dfbf2580eb4..514e44eba97 100755 --- a/scripts/list_shields.py +++ b/scripts/list_shields.py @@ -5,9 +5,24 @@ # SPDX-License-Identifier: Apache-2.0 import argparse +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 @@ -21,10 +36,21 @@ from pathlib import Path class Shield: name: str dir: Path + full_name: str | None = None + vendor: 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') + ) + def find_shields(args): ret = [] @@ -41,6 +67,28 @@ def find_shields_in(root): 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(): diff --git a/scripts/schemas/shield-schema.yml b/scripts/schemas/shield-schema.yml new file mode 100644 index 00000000000..b3dcba896b5 --- /dev/null +++ b/scripts/schemas/shield-schema.yml @@ -0,0 +1,35 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright The Zephyr Project Contributors + +# A pykwalify schema for basic validation of the structure of a shield metadata YAML file. +# +# The shield.yml file can contain either a single shield definition or a list of shields. + +schema;shield-schema: + type: map + mapping: + name: + required: true + type: str + desc: Name of the shield (used in Kconfig and build system) + full_name: + required: true + type: str + desc: Full name of the shield (typically the commercial name) + vendor: + required: true + type: str + desc: Manufacturer/vendor of the shield + +type: map +range: + min: 1 + max: 1 +mapping: + shield: + include: shield-schema + shields: + type: seq + sequence: + - include: shield-schema