Skip to content

Commit 9d5b197

Browse files
committed
cmake: shields: introduce shield.yml
While legacy shields are still supported, this introduces a shield.yml file similar to board.yml that allows to more explicitly declare a shield and to set some useful metadata such as vendor and full name. Signed-off-by: Benjamin Cabé <[email protected]>
1 parent 9decff5 commit 9d5b197

File tree

4 files changed

+137
-11
lines changed

4 files changed

+137
-11
lines changed

cmake/modules/shields.cmake

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
include_guard(GLOBAL)
3232

3333
include(extensions)
34+
include(yaml)
3435

3536
# Check that SHIELD has not changed.
3637
zephyr_check_cache(SHIELD WATCH)
@@ -48,21 +49,47 @@ set(SHIELD-NOTFOUND ${SHIELD_AS_LIST})
4849

4950
foreach(root ${BOARD_ROOT})
5051
set(shield_dir ${root}/boards/shields)
51-
# Match the Kconfig.shield files in the shield directories to make sure we are
52-
# finding shields, e.g. x_nucleo_iks01a1/Kconfig.shield
52+
53+
# First, look for shield.yml files
54+
file(GLOB_RECURSE shield_yml_files ${shield_dir}/*/shield.yml)
55+
56+
foreach(shield_yml ${shield_yml_files})
57+
get_filename_component(shield_path ${shield_yml} DIRECTORY)
58+
get_filename_component(shield ${shield_path} NAME)
59+
60+
set(yaml_ctx_name shield_data_${shield})
61+
yaml_load(FILE ${shield_yml} NAME ${yaml_ctx_name})
62+
63+
# Check for multiple shields format first
64+
yaml_get(shields_data NAME ${yaml_ctx_name} KEY shields)
65+
if(shields_data)
66+
yaml_length(num_shields NAME ${yaml_ctx_name} KEY shields)
67+
if(${num_shields} GREATER 0)
68+
math(EXPR shield_stop "${num_shields} - 1")
69+
foreach(i RANGE 0 ${shield_stop})
70+
yaml_get(shield_name NAME ${yaml_ctx_name} KEY shields ${i} name)
71+
list(APPEND SHIELD_LIST ${shield_name})
72+
set(SHIELD_DIR_${shield_name} ${shield_path})
73+
endforeach()
74+
endif()
75+
else()
76+
yaml_get(shield_data NAME ${yaml_ctx_name} KEY shield)
77+
if(shield_data)
78+
yaml_get(shield_name NAME ${yaml_ctx_name} KEY shield name)
79+
list(APPEND SHIELD_LIST ${shield_name})
80+
set(SHIELD_DIR_${shield_name} ${shield_path})
81+
endif()
82+
endif()
83+
endforeach()
84+
85+
# Then, look for overlay files next to Kconfig.shield files as fallback (legacy shields)
5386
file(GLOB_RECURSE shields_refs_list ${shield_dir}/*/Kconfig.shield)
5487

55-
# The above gives a list of Kconfig.shield files, like this:
56-
#
57-
# x_nucleo_iks01a1/Kconfig.shield;x_nucleo_iks01a2/Kconfig.shield
58-
#
59-
# we construct a list of shield names by extracting the directories
60-
# from each file and looking for <shield>.overlay files in there.
61-
# Each overlay corresponds to a shield. We obtain the shield name by
62-
# removing the .overlay extension.
63-
# We also create a SHIELD_DIR_${name} variable for each shield's directory.
6488
foreach(shields_refs ${shields_refs_list})
6589
get_filename_component(shield_path ${shields_refs} DIRECTORY)
90+
if(EXISTS "${shield_path}/shield.yml")
91+
continue()
92+
endif()
6693
file(GLOB shield_overlays RELATIVE ${shield_path} ${shield_path}/*.overlay)
6794
foreach(overlay ${shield_overlays})
6895
get_filename_component(shield ${overlay} NAME_WE)

doc/hardware/porting/shields.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,29 @@ under :zephyr_file:`boards/shields`:
1717
.. code-block:: none
1818
1919
boards/shields/<shield>
20+
├── shield.yml
2021
├── <shield>.overlay
2122
├── Kconfig.shield
2223
├── Kconfig.defconfig
2324
└── pre_dt_shield.cmake
2425
2526
These files provides shield configuration as follows:
2627

28+
* **shield.yml**: This file provides metadata about the shield in YAML format.
29+
It must contain the following fields:
30+
31+
* ``name``: Name of the shield used in Kconfig and build system (required)
32+
* ``full_name``: Full commercial name of the shield (required)
33+
* ``vendor``: Manufacturer/vendor of the shield (required)
34+
35+
Example:
36+
37+
.. code-block:: yaml
38+
39+
name: foo_shield
40+
full_name: Foo Shield for Arduino
41+
vendor: acme
42+
2743
* **<shield>.overlay**: This file provides a shield description in devicetree
2844
format that is merged with the board's :ref:`devicetree <dt-guide>`
2945
before compilation.

scripts/list_shields.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,24 @@
55
# SPDX-License-Identifier: Apache-2.0
66

77
import argparse
8+
import sys
89
from dataclasses import dataclass
910
from pathlib import Path
1011

12+
import pykwalify.core
13+
import yaml
14+
15+
try:
16+
from yaml import CSafeLoader as SafeLoader
17+
except ImportError:
18+
from yaml import SafeLoader
19+
20+
SHIELD_SCHEMA_PATH = str(Path(__file__).parent / 'schemas' / 'shield-schema.yml')
21+
with open(SHIELD_SCHEMA_PATH) as f:
22+
shield_schema = yaml.load(f.read(), Loader=SafeLoader)
23+
24+
SHIELD_YML = 'shield.yml'
25+
1126
#
1227
# This is shared code between the build system's 'shields' target
1328
# and the 'west shields' extension command. If you change it, make
@@ -21,10 +36,21 @@
2136
class Shield:
2237
name: str
2338
dir: Path
39+
full_name: str | None = None
40+
vendor: str | None = None
2441

2542
def shield_key(shield):
2643
return shield.name
2744

45+
def process_shield_data(shield_data, shield_dir):
46+
# Create shield from yaml data
47+
return Shield(
48+
name=shield_data['name'],
49+
dir=shield_dir,
50+
full_name=shield_data.get('full_name'),
51+
vendor=shield_data.get('vendor')
52+
)
53+
2854
def find_shields(args):
2955
ret = []
3056

@@ -41,6 +67,28 @@ def find_shields_in(root):
4167
for maybe_shield in (shields).iterdir():
4268
if not maybe_shield.is_dir():
4369
continue
70+
71+
# Check for shield.yml first
72+
shield_yml = maybe_shield / SHIELD_YML
73+
if shield_yml.is_file():
74+
with shield_yml.open('r', encoding='utf-8') as f:
75+
shield_data = yaml.load(f.read(), Loader=SafeLoader)
76+
77+
try:
78+
pykwalify.core.Core(source_data=shield_data, schema_data=shield_schema).validate()
79+
except pykwalify.errors.SchemaError as e:
80+
sys.exit(f'ERROR: Malformed shield.yml in file: {shield_yml.as_posix()}\n{e}')
81+
82+
if 'shields' in shield_data:
83+
# Multiple shields format
84+
for shield_info in shield_data['shields']:
85+
ret.append(process_shield_data(shield_info, maybe_shield))
86+
elif 'shield' in shield_data:
87+
# Single shield format
88+
ret.append(process_shield_data(shield_data['shield'], maybe_shield))
89+
continue
90+
91+
# Fallback to legacy method if no shield.yml
4492
for maybe_kconfig in maybe_shield.iterdir():
4593
if maybe_kconfig.name == 'Kconfig.shield':
4694
for maybe_overlay in maybe_shield.iterdir():

scripts/schemas/shield-schema.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
#
3+
# Copyright The Zephyr Project Contributors
4+
5+
# A pykwalify schema for basic validation of the structure of a shield metadata YAML file.
6+
#
7+
# The shield.yml file can contain either a single shield definition or a list of shields.
8+
9+
schema;shield-schema:
10+
type: map
11+
mapping:
12+
name:
13+
required: true
14+
type: str
15+
desc: Name of the shield (used in Kconfig and build system)
16+
full_name:
17+
required: true
18+
type: str
19+
desc: Full name of the shield (typically the commercial name)
20+
vendor:
21+
required: true
22+
type: str
23+
desc: Manufacturer/vendor of the shield
24+
25+
type: map
26+
range:
27+
min: 1
28+
max: 1
29+
mapping:
30+
shield:
31+
include: shield-schema
32+
shields:
33+
type: seq
34+
sequence:
35+
- include: shield-schema

0 commit comments

Comments
 (0)