Skip to content

Commit f06c26a

Browse files
committed
doc: extensions: boards: add zephyr:board-supported-runners directive
Use runners.yaml from build metadata to gather info regarding board supported runners, store the info in the board catalog, and allow to display it as a table in a board's doc page using the .. zephyr:board-supported-runner:: directive. Signed-off-by: Benjamin Cabé <benjamin@zephyrproject.org>
1 parent 76bf012 commit f06c26a

File tree

5 files changed

+210
-9
lines changed

5 files changed

+210
-9
lines changed

doc/_extensions/zephyr/domain/__init__.py

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616
- ``zephyr:code-sample-listing::`` - Shows a listing of code samples found in a given category.
1717
- ``zephyr:board-catalog::`` - Shows a listing of boards supported by Zephyr.
1818
- ``zephyr:board::`` - Flags a document as being the documentation page for a board.
19+
- ``zephyr:board-supported-hw::`` - Shows a table of supported hardware features for all the targets
20+
of the board documented in the current page.
21+
- ``zephyr:board-supported-runners::`` - Shows a table of supported runners for the board documented
22+
in the current page.
1923
2024
Roles
2125
-----
@@ -58,6 +62,7 @@
5862

5963

6064
sys.path.insert(0, str(Path(__file__).parents[4] / "scripts/dts/python-devicetree/src"))
65+
sys.path.insert(0, str(Path(__file__).parents[4] / "scripts/west_commands"))
6166
sys.path.insert(0, str(Path(__file__).parents[3] / "_scripts"))
6267

6368
from gen_boards_catalog import get_catalog
@@ -729,6 +734,9 @@ def run(self):
729734
board_node["archs"] = board["archs"]
730735
board_node["socs"] = board["socs"]
731736
board_node["image"] = board["image"]
737+
board_node["supported_runners"] = board["supported_runners"]
738+
board_node["flash_runner"] = board["flash_runner"]
739+
board_node["debug_runner"] = board["debug_runner"]
732740
return [board_node]
733741

734742

@@ -992,6 +1000,121 @@ def create_count_indicator(nodes_list, class_type, role_function=role_fn):
9921000
return result_nodes
9931001

9941002

1003+
class BoardSupportedRunnersDirective(SphinxDirective):
1004+
"""A directive for showing the supported runners of a board."""
1005+
1006+
has_content = False
1007+
required_arguments = 0
1008+
optional_arguments = 0
1009+
1010+
def run(self):
1011+
env = self.env
1012+
docname = env.docname
1013+
1014+
matcher = NodeMatcher(BoardNode)
1015+
board_nodes = list(self.state.document.traverse(matcher))
1016+
if not board_nodes:
1017+
logger.warning(
1018+
"board-supported-runners directive must be used in a board documentation page.",
1019+
location=(docname, self.lineno),
1020+
)
1021+
return []
1022+
1023+
if not env.app.config.zephyr_generate_hw_features:
1024+
note = nodes.admonition()
1025+
note += nodes.title(text="Note")
1026+
note["classes"].append("warning")
1027+
note += nodes.paragraph(
1028+
text="The list of supported runners was not generated. Run a full documentation "
1029+
"build for the required metadata to be available."
1030+
)
1031+
return [note]
1032+
1033+
board_node = board_nodes[0]
1034+
runners = board_node["supported_runners"]
1035+
flash_runner = board_node["flash_runner"]
1036+
debug_runner = board_node["debug_runner"]
1037+
1038+
result_nodes = []
1039+
1040+
paragraph = nodes.paragraph()
1041+
paragraph += nodes.Text("The ")
1042+
paragraph += nodes.literal(text=board_node["id"])
1043+
paragraph += nodes.Text(
1044+
" board supports the runners and associated west commands listed below."
1045+
)
1046+
result_nodes.append(paragraph)
1047+
1048+
env_runners = env.domaindata["zephyr"]["runners"]
1049+
commands = ["flash", "debug"]
1050+
for runner in env_runners:
1051+
if runner in board_node["supported_runners"]:
1052+
for cmd in env_runners[runner].get("commands", []):
1053+
if cmd not in commands:
1054+
commands.append(cmd)
1055+
1056+
# create the table
1057+
table = nodes.table(classes=["colwidths-given", "runners-table"])
1058+
tgroup = nodes.tgroup(cols=len(commands) + 1) # +1 for the Runner column
1059+
1060+
# Add colspec for Runner column
1061+
tgroup += nodes.colspec(colwidth=15, classes=["type"])
1062+
# Add colspecs for command columns
1063+
for _ in commands:
1064+
tgroup += nodes.colspec(colwidth=15, classes=["type"])
1065+
1066+
thead = nodes.thead()
1067+
row = nodes.row()
1068+
entry = nodes.entry()
1069+
row += entry
1070+
headers = [*commands]
1071+
for header in headers:
1072+
entry = nodes.entry(classes=[header.lower()])
1073+
entry += addnodes.literal_strong(text=header, classes=["command"])
1074+
row += entry
1075+
thead += row
1076+
tgroup += thead
1077+
1078+
tbody = nodes.tbody()
1079+
1080+
# add a row for each runner
1081+
for runner in sorted(runners):
1082+
row = nodes.row()
1083+
# First column - Runner name
1084+
entry = nodes.entry()
1085+
1086+
xref = addnodes.pending_xref(
1087+
"",
1088+
refdomain="std",
1089+
reftype="ref",
1090+
reftarget=f"runner_{runner}",
1091+
refexplicit=True,
1092+
refwarn=False,
1093+
)
1094+
xref += nodes.Text(runner)
1095+
entry += addnodes.literal_strong("", "", xref)
1096+
row += entry
1097+
1098+
# Add columns for each command
1099+
for command in commands:
1100+
entry = nodes.entry()
1101+
if command in env_runners[runner].get("commands", []):
1102+
entry += nodes.Text("✅")
1103+
if (command == "flash" and runner == flash_runner) or (
1104+
command == "debug" and runner == debug_runner
1105+
):
1106+
entry += nodes.Text(" (default)")
1107+
row += entry
1108+
tbody += row
1109+
1110+
tgroup += tbody
1111+
table += tgroup
1112+
1113+
result_nodes.append(table)
1114+
1115+
return result_nodes
1116+
1117+
9951118
class ZephyrDomain(Domain):
9961119
"""Zephyr domain"""
9971120

@@ -1011,6 +1134,7 @@ class ZephyrDomain(Domain):
10111134
"board-catalog": BoardCatalogDirective,
10121135
"board": BoardDirective,
10131136
"board-supported-hw": BoardSupportedHardwareDirective,
1137+
"board-supported-runners": BoardSupportedRunnersDirective,
10141138
}
10151139

10161140
object_types: dict[str, ObjType] = {
@@ -1247,6 +1371,7 @@ def load_board_catalog_into_domain(app: Sphinx) -> None:
12471371
app.env.domaindata["zephyr"]["boards"] = board_catalog["boards"]
12481372
app.env.domaindata["zephyr"]["vendors"] = board_catalog["vendors"]
12491373
app.env.domaindata["zephyr"]["socs"] = board_catalog["socs"]
1374+
app.env.domaindata["zephyr"]["runners"] = board_catalog["runners"]
12501375

12511376

12521377
def setup(app):

doc/_scripts/gen_boards_catalog.py

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,18 @@
1515
import yaml
1616
import zephyr_module
1717
from gen_devicetree_rest import VndLookup
18+
from runners.core import ZephyrBinaryRunner
1819

1920
ZEPHYR_BASE = Path(__file__).parents[2]
2021
ZEPHYR_BINDINGS = ZEPHYR_BASE / "dts/bindings"
2122
EDT_PICKLE_PATHS = [
2223
"zephyr/edt.pickle",
2324
"hello_world/zephyr/edt.pickle" # for board targets using sysbuild
2425
]
26+
RUNNERS_YAML_PATHS = [
27+
"zephyr/runners.yaml",
28+
"hello_world/zephyr/runners.yaml" # for board targets using sysbuild
29+
]
2530

2631
logger = logging.getLogger(__name__)
2732

@@ -108,20 +113,25 @@ def guess_doc_page(board_or_shield):
108113
return doc_file
109114

110115

111-
def gather_board_devicetrees(twister_out_dir):
112-
"""Gather EDT objects for each board from twister output directory.
116+
def gather_board_build_info(twister_out_dir):
117+
"""Gather EDT objects and runners info for each board from twister output directory.
113118
114119
Args:
115120
twister_out_dir: Path object pointing to twister output directory
116121
117122
Returns:
118-
A dictionary mapping board names to a dictionary of board targets and their EDT objects.
119-
The structure is: {board_name: {board_target: edt_object}}
123+
A tuple of two dictionaries:
124+
- A dictionary mapping board names to a dictionary of board targets and their EDT.
125+
objects.
126+
The structure is: {board_name: {board_target: edt_object}}
127+
- A dictionary mapping board names to a dictionary of board targets and their runners
128+
info.
129+
The structure is: {board_name: {board_target: runners_info}}
120130
"""
121131
board_devicetrees = {}
122-
132+
board_runners = {}
123133
if not twister_out_dir.exists():
124-
return board_devicetrees
134+
return board_devicetrees, board_runners
125135

126136
# Find all build_info.yml files in twister-out
127137
build_info_files = list(twister_out_dir.glob("*/**/build_info.yml"))
@@ -137,6 +147,13 @@ def gather_board_devicetrees(twister_out_dir):
137147
if not edt_pickle_file:
138148
continue
139149

150+
runners_yaml_file = None
151+
for runners_yaml_path in RUNNERS_YAML_PATHS:
152+
maybe_file = build_info_file.parent / runners_yaml_path
153+
if maybe_file.exists():
154+
runners_yaml_file = maybe_file
155+
break
156+
140157
try:
141158
with open(build_info_file) as f:
142159
build_info = yaml.safe_load(f)
@@ -155,10 +172,17 @@ def gather_board_devicetrees(twister_out_dir):
155172
edt = pickle.load(f)
156173
board_devicetrees.setdefault(board_name, {})[board_target] = edt
157174

175+
if runners_yaml_file:
176+
with open(runners_yaml_file) as f:
177+
runners_yaml = yaml.safe_load(f)
178+
board_runners.setdefault(board_name, {})[board_target] = (
179+
runners_yaml
180+
)
181+
158182
except Exception as e:
159183
logger.error(f"Error processing build info file {build_info_file}: {e}")
160184

161-
return board_devicetrees
185+
return board_devicetrees, board_runners
162186

163187

164188
def run_twister_cmake_only(outdir):
@@ -174,6 +198,7 @@ def run_twister_cmake_only(outdir):
174198
"--all",
175199
"-M",
176200
*[arg for path in EDT_PICKLE_PATHS for arg in ('--keep-artifacts', path)],
201+
*[arg for path in RUNNERS_YAML_PATHS for arg in ('--keep-artifacts', path)],
177202
"--cmake-only",
178203
"--outdir", str(outdir),
179204
]
@@ -226,12 +251,13 @@ def get_catalog(generate_hw_features=False):
226251
systems = list_hardware.find_v2_systems(args_find_boards)
227252
board_catalog = {}
228253
board_devicetrees = {}
254+
board_runners = {}
229255

230256
if generate_hw_features:
231257
logger.info("Running twister in cmake-only mode to get Devicetree files for all boards")
232258
with tempfile.TemporaryDirectory() as tmp_dir:
233259
run_twister_cmake_only(tmp_dir)
234-
board_devicetrees = gather_board_devicetrees(Path(tmp_dir))
260+
board_devicetrees, board_runners = gather_board_build_info(Path(tmp_dir))
235261
else:
236262
logger.info("Skipping generation of supported hardware features.")
237263

@@ -314,6 +340,15 @@ def get_catalog(generate_hw_features=False):
314340
# Store features for this specific target
315341
supported_features[board_target] = features
316342

343+
board_runner_info = {}
344+
if board.name in board_runners:
345+
# Assume all board targets have the same runners so only consider the runners
346+
# for the first board target.
347+
r = list(board_runners[board.name].values())[0]
348+
board_runner_info["runners"] = r.get("runners")
349+
board_runner_info["flash-runner"] = r.get("flash-runner")
350+
board_runner_info["debug-runner"] = r.get("debug-runner")
351+
317352
# Grab all the twister files for this board and use them to figure out all the archs it
318353
# supports.
319354
archs = set()
@@ -336,6 +371,10 @@ def get_catalog(generate_hw_features=False):
336371
"revision_default": board.revision_default,
337372
"supported_features": supported_features,
338373
"image": guess_image(board),
374+
# runners
375+
"supported_runners": board_runner_info.get("runners", []),
376+
"flash_runner": board_runner_info.get("flash-runner", ""),
377+
"debug_runner": board_runner_info.get("debug-runner", ""),
339378
}
340379

341380
socs_hierarchy = {}
@@ -344,8 +383,16 @@ def get_catalog(generate_hw_features=False):
344383
series = soc.series or "<no series>"
345384
socs_hierarchy.setdefault(family, {}).setdefault(series, []).append(soc.name)
346385

386+
available_runners = {}
387+
for runner in ZephyrBinaryRunner.get_runners():
388+
available_runners[runner.name()] = {
389+
"name": runner.name(),
390+
"commands": runner.capabilities().commands,
391+
}
392+
347393
return {
348394
"boards": board_catalog,
349395
"vendors": {**vnd_lookup.vnd2vendor, "others": "Other/Unknown"},
350396
"socs": socs_hierarchy,
397+
"runners": available_runners,
351398
}

doc/contribute/documentation/guidelines.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1266,6 +1266,21 @@ Boards
12661266
(``zephyr_generate_hw_features`` config option set to ``True``). If disabled, a warning message
12671267
will be shown instead of the hardware features tables.
12681268

1269+
.. rst:directive:: .. zephyr:board-supported-runners::
1270+
1271+
This directive is used to show the supported runners for the board documented in the current
1272+
page, including which runner is the default for flashing and debugging.
1273+
1274+
The directive must be used in a document that also contains a :rst:dir:`zephyr:board` directive,
1275+
as it relies on the board information to generate the table.
1276+
1277+
.. note::
1278+
1279+
Similar to :rst:dir:`zephyr:board-supported-hw`, this directive requires hardware features
1280+
generation to be enabled (``zephyr_generate_hw_features`` config option set to ``True``) to
1281+
produce a complete table. If disabled, a warning message will be shown instead of the runners
1282+
tables.
1283+
12691284
References
12701285
**********
12711286

0 commit comments

Comments
 (0)