doc: extensions: Navigate to DTS entries from supported hardware list

Update the gen_board_catalog.py logic to also capture lineno for
each devicetree node. Use that info in the supported hardware list to
create clickable elements that directly take the user to the
corresponding line in the devicetree file on Github.

Signed-off-by: Benjamin Cabé <benjamin@zephyrproject.org>
This commit is contained in:
Benjamin Cabé 2025-02-27 12:21:39 +01:00 committed by Benjamin Cabé
commit fdb8b30da7
3 changed files with 55 additions and 28 deletions

View file

@ -36,7 +36,7 @@ from typing import Any
from anytree import ChildResolverError, Node, PreOrderIter, Resolver, search
from docutils import nodes
from docutils.parsers.rst import directives
from docutils.parsers.rst import directives, roles
from docutils.statemachine import StringList
from sphinx import addnodes
from sphinx.application import Sphinx
@ -811,7 +811,8 @@ class BoardSupportedHardwareDirective(SphinxDirective):
<span class="count disabled-count">2</span>
</dt>
<dd>
Number of instances that are enabled / disabled.
Number of instances that are enabled / disabled. <br/>
Click on the label to see the first instance of this feature in the board/SoC DTS files.
</dd>
<dt>
<code class="docutils literal notranslate"><span class="pre">vnd,foo</span></code>
@ -870,7 +871,7 @@ class BoardSupportedHardwareDirective(SphinxDirective):
for i, (key, value) in enumerate(items):
row = nodes.row()
if value.get("disabled_count", 0) > 0 and value.get("okay_count", 0) == 0:
if value.get("disabled_nodes", []) and not value.get("okay_nodes", []):
row["classes"].append("disabled")
# TYPE column
@ -911,22 +912,39 @@ class BoardSupportedHardwareDirective(SphinxDirective):
desc_para += nodes.Text(value["description"])
# Add count indicators for okay and not-okay instances
okay_count = value.get("okay_count", 0)
disabled_count = value.get("disabled_count", 0)
okay_nodes = value.get("okay_nodes", [])
disabled_nodes = value.get("disabled_nodes", [])
if okay_count > 0:
okay_count_indicator = nodes.inline(
classes=["count", "okay-count"],
text=str(okay_count),
)
desc_para += okay_count_indicator
role_fn, _ = roles.role(
"zephyr_file", self.state_machine.language, self.lineno, self.state.reporter
)
if disabled_count > 0:
disabled_count_indicator = nodes.inline(
classes=["count", "disabled-count"],
text=str(disabled_count),
def create_count_indicator(nodes_list, class_type, role_function=role_fn):
if not nodes_list:
return None
count = len(nodes_list)
if role_function is None:
return nodes.inline(
classes=["count", f"{class_type}-count"], text=str(count)
)
# Create a reference to the first node in the list
first_node = nodes_list[0]
file_ref = f"{count} <{first_node['filename']}#L{first_node['lineno']}>"
role_nodes, _ = role_function(
"zephyr_file", file_ref, file_ref, self.lineno, self.state.inliner
)
desc_para += disabled_count_indicator
count_node = role_nodes[0]
count_node["classes"] = ["count", f"{class_type}-count"]
return count_node
desc_para += create_count_indicator(okay_nodes, "okay")
desc_para += create_count_indicator(disabled_nodes, "disabled")
desc_entry += desc_para
row += desc_entry

View file

@ -178,6 +178,7 @@
font-size: 0.7em;
font-weight: 600;
margin-left: 4px;
padding-right: 6px !important;
&::before {
content: "×";

View file

@ -254,7 +254,7 @@ def get_catalog(generate_hw_features=False):
# Use pre-gathered build info and DTS files
if board.name in board_devicetrees:
for board_target, edt in board_devicetrees[board.name].items():
target_features = {}
features = {}
for node in edt.nodes:
if node.binding_path is None:
continue
@ -271,6 +271,7 @@ def get_catalog(generate_hw_features=False):
description = DeviceTreeUtils.get_cached_description(node)
filename = node.filename
lineno = node.lineno
locations = set()
if Path(filename).is_relative_to(ZEPHYR_BASE):
filename = Path(filename).relative_to(ZEPHYR_BASE)
@ -279,23 +280,30 @@ def get_catalog(generate_hw_features=False):
else:
locations.add("soc")
existing_feature = target_features.get(binding_type, {}).get(
existing_feature = features.get(binding_type, {}).get(
node.matching_compat
)
node_info = {"filename": str(filename), "lineno": lineno}
node_list_key = "okay_nodes" if node.status == "okay" else "disabled_nodes"
if existing_feature:
locations.update(existing_feature["locations"])
key = "okay_count" if node.status == "okay" else "disabled_count"
existing_feature[key] = existing_feature.get(key, 0) + 1
else:
key = "okay_count" if node.status == "okay" else "disabled_count"
target_features.setdefault(binding_type, {})[node.matching_compat] = {
"description": description,
"locations": locations,
key: 1
}
existing_feature.setdefault(node_list_key, []).append(node_info)
continue
feature_data = {
"description": description,
"locations": locations,
"okay_nodes": [],
"disabled_nodes": [],
}
feature_data[node_list_key].append(node_info)
features.setdefault(binding_type, {})[node.matching_compat] = feature_data
# Store features for this specific target
supported_features[board_target] = target_features
supported_features[board_target] = features
# Grab all the twister files for this board and use them to figure out all the archs it
# supports.