Skip to content

Commit 28c696b

Browse files
committed
doc: extensions: boards: Add hardware features filter to board catalog
Add a new hardware features filter to the board catalog that allows users to filter boards based on their supported hardware capabilities. The features are extracted from the devicetree files and organized by binding type. Key changes: - Extract hardware feature descriptions from devicetree bindings - Add HW_FEATURES_TURBO_MODE option to skip feature generation for faster builds (similar to DT_TURBO_MODE) - Add tag-based UI for filtering boards by hardware features The feature can be disabled for faster documentation builds using -DHW_FEATURES_TURBO_MODE=1 or by using 'make html-fast'. Signed-off-by: Benjamin Cabé <[email protected]>
1 parent 831f2f4 commit 28c696b

File tree

12 files changed

+356
-25
lines changed

12 files changed

+356
-25
lines changed

boards/index.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ this page <boards-shields>`.
2424
single field, selecting multiple options (such as two architectures) will show boards matching
2525
**either** option.
2626

27+
* The list of supported hardware features for each board is automatically generated using
28+
information from the Devicetree. It may not be reflecting the full list of supported features
29+
since some of them may not be enabled by default.
30+
2731
* Can't find your exact board? Don't worry! If a similar board with the same or a closely related
2832
MCU exists, you can use it as a :ref:`starting point <create-your-board-directory>` for adding
2933
support for your own board.

doc/CMakeLists.txt

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ set(SPHINXOPTS "-j auto -W --keep-going -T" CACHE STRING "Default Sphinx Options
1616
set(SPHINXOPTS_EXTRA "" CACHE STRING "Extra Sphinx Options (added to defaults)")
1717
set(LATEXMKOPTS "-halt-on-error -no-shell-escape" CACHE STRING "Default latexmk options")
1818
set(DT_TURBO_MODE OFF CACHE BOOL "Enable DT turbo mode")
19+
set(HW_FEATURES_TURBO_MODE OFF CACHE BOOL "Enable HW features turbo mode")
1920
set(DOC_TAG "development" CACHE STRING "Documentation tag")
2021
set(DTS_ROOTS "${ZEPHYR_BASE}" CACHE STRING "DT bindings root folders")
2122

@@ -149,6 +150,16 @@ set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${GEN_DEVICETREE_
149150
#-------------------------------------------------------------------------------
150151
# html
151152

153+
set(SPHINX_TAGS "${DOC_TAG}")
154+
if(HW_FEATURES_TURBO_MODE)
155+
list(APPEND SPHINX_TAGS "hw_features_turbo")
156+
endif()
157+
158+
set(SPHINX_TAGS_ARGS "")
159+
foreach(tag ${SPHINX_TAGS})
160+
list(APPEND SPHINX_TAGS_ARGS "-t" "${tag}")
161+
endforeach()
162+
152163
add_doc_target(
153164
html
154165
COMMAND ${CMAKE_COMMAND} -E env ${SPHINX_ENV} OUTPUT_DIR=${DOCS_HTML_DIR}
@@ -157,7 +168,7 @@ add_doc_target(
157168
-c ${DOCS_CFG_DIR}
158169
-d ${DOCS_DOCTREE_DIR}
159170
-w ${DOCS_BUILD_DIR}/html.log
160-
-t ${DOC_TAG}
171+
${SPHINX_TAGS_ARGS}
161172
${SPHINXOPTS}
162173
${SPHINXOPTS_EXTRA}
163174
${DOCS_SRC_DIR}
@@ -187,7 +198,7 @@ add_doc_target(
187198
-c ${DOCS_CFG_DIR}
188199
-d ${DOCS_DOCTREE_DIR}
189200
-w ${DOCS_BUILD_DIR}/html.log
190-
-t ${DOC_TAG}
201+
${SPHINX_TAGS_ARGS}
191202
${SPHINXOPTS}
192203
${SPHINXOPTS_EXTRA}
193204
${DOCS_SRC_DIR}
@@ -214,7 +225,7 @@ add_doc_target(
214225
-c ${DOCS_CFG_DIR}
215226
-d ${DOCS_DOCTREE_DIR}
216227
-w ${DOCS_BUILD_DIR}/latex.log
217-
-t ${DOC_TAG}
228+
${SPHINX_TAGS_ARGS}
218229
-t convertimages
219230
${SPHINXOPTS}
220231
${SPHINXOPTS_EXTRA}
@@ -266,7 +277,7 @@ add_doc_target(
266277
-c ${DOCS_CFG_DIR}
267278
-d ${DOCS_DOCTREE_DIR}
268279
-w ${DOCS_BUILD_DIR}/linkcheck.log
269-
-t ${DOC_TAG}
280+
${SPHINX_TAGS_ARGS}
270281
${SPHINXOPTS}
271282
${SPHINXOPTS_EXTRA}
272283
${DOCS_SRC_DIR}

doc/Makefile

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,18 @@ SPHINXOPTS ?= -j auto -W --keep-going -T
88
SPHINXOPTS_EXTRA ?=
99
LATEXMKOPTS ?= -halt-on-error -no-shell-escape
1010
DT_TURBO_MODE ?= 0
11+
HW_FEATURES_TURBO_MODE ?= 0
1112

1213
# ------------------------------------------------------------------------------
1314
# Documentation targets
1415

1516
.PHONY: configure clean html html-fast html-live html-live-fast latex pdf doxygen
1617

1718
html-fast:
18-
${MAKE} html DT_TURBO_MODE=1
19+
${MAKE} html DT_TURBO_MODE=1 HW_FEATURES_TURBO_MODE=1
1920

2021
html-live-fast:
21-
${MAKE} html-live DT_TURBO_MODE=1
22+
${MAKE} html-live DT_TURBO_MODE=1 HW_FEATURES_TURBO_MODE=1
2223

2324
html html-live latex pdf linkcheck doxygen: configure
2425
cmake --build ${BUILDDIR} --target $@
@@ -32,7 +33,8 @@ configure:
3233
-DSPHINXOPTS="${SPHINXOPTS}" \
3334
-DSPHINXOPTS_EXTRA="${SPHINXOPTS_EXTRA}" \
3435
-DLATEXMKOPTS="${LATEXMKOPTS}" \
35-
-DDT_TURBO_MODE=${DT_TURBO_MODE}
36+
-DDT_TURBO_MODE=${DT_TURBO_MODE} \
37+
-DHW_FEATURES_TURBO_MODE=${HW_FEATURES_TURBO_MODE}
3638

3739
clean:
3840
cmake --build ${BUILDDIR} --target clean

doc/_extensions/zephyr/domain/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
Zephyr Extension
33
################
44
5-
Copyright (c) 2023 The Linux Foundation
5+
Copyright (c) 2023-2025 The Linux Foundation
66
SPDX-License-Identifier: Apache-2.0
77
88
This extension adds a new ``zephyr`` domain for handling the documentation of various entities
@@ -708,6 +708,7 @@ def run(self):
708708
"boards": domain_data["boards"],
709709
"vendors": domain_data["vendors"],
710710
"socs": domain_data["socs"],
711+
"hw_features_present": self.env.app.config.zephyr_generate_hw_features,
711712
},
712713
)
713714
return [nodes.raw("", rendered, format="html")]
@@ -954,14 +955,15 @@ def install_static_assets_as_needed(
954955

955956

956957
def load_board_catalog_into_domain(app: Sphinx) -> None:
957-
board_catalog = get_catalog()
958+
board_catalog = get_catalog(generate_hw_features=app.config.zephyr_generate_hw_features)
958959
app.env.domaindata["zephyr"]["boards"] = board_catalog["boards"]
959960
app.env.domaindata["zephyr"]["vendors"] = board_catalog["vendors"]
960961
app.env.domaindata["zephyr"]["socs"] = board_catalog["socs"]
961962

962963

963964
def setup(app):
964965
app.add_config_value("zephyr_breathe_insert_related_samples", False, "env")
966+
app.add_config_value("zephyr_generate_hw_features", False, "env")
965967

966968
app.add_domain(ZephyrDomain)
967969

doc/_extensions/zephyr/domain/static/css/board-catalog.css

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright (c) 2024, The Linux Foundation.
2+
* Copyright (c) 2024-2025, The Linux Foundation.
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

@@ -82,6 +82,49 @@
8282
white-space: nowrap;
8383
}
8484

85+
.tag-container {
86+
display: flex;
87+
flex-wrap: wrap;
88+
border: 1px solid #ccc;
89+
border-radius: 50px;
90+
padding: 5px 18px;
91+
}
92+
93+
.tag-container:focus-within {
94+
border-color: var(--input-focus-border-color);
95+
}
96+
97+
.tag {
98+
background-color: var(--admonition-note-background-color);
99+
color: var(--admonition-note-color);
100+
padding: 2px 12px 4px 16px;
101+
border-radius: 30px;
102+
display: inline-flex;
103+
align-items: center;
104+
cursor: pointer;
105+
font-size: 14px;
106+
margin-right: 8px;
107+
}
108+
109+
.tag:hover {
110+
background-color: #0056b3;
111+
}
112+
113+
.tag::after {
114+
content: '\00D7'; /* multiplication sign */
115+
margin-left: 8px;
116+
font-size: 12px;
117+
cursor: pointer;
118+
}
119+
120+
.filter-form input.tag-input {
121+
flex: 1;
122+
border: none;
123+
padding: 5px;
124+
outline: none;
125+
background-color: transparent;
126+
}
127+
85128
#catalog {
86129
display: flex;
87130
flex-wrap: wrap;

doc/_extensions/zephyr/domain/static/js/board-catalog.js

Lines changed: 116 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright (c) 2024, The Linux Foundation.
2+
* Copyright (c) 2024-2025, The Linux Foundation.
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

@@ -29,6 +29,29 @@ function populateFormFromURL() {
2929
}
3030
});
3131

32+
// Restore supported features from URL
33+
if (hashParams.has("features")) {
34+
const features = hashParams.get("features").split(",");
35+
setTimeout(() => {
36+
features.forEach(feature => {
37+
const tagContainer = document.getElementById('tag-container');
38+
const tagInput = document.getElementById('tag-input');
39+
40+
const tagElement = document.createElement('span');
41+
tagElement.classList.add('tag');
42+
tagElement.textContent = feature;
43+
tagElement.onclick = () => {
44+
const selectedTags = [...document.querySelectorAll('.tag')].map(tag => tag.textContent);
45+
selectedTags.splice(selectedTags.indexOf(feature), 1);
46+
tagElement.remove();
47+
filterBoards();
48+
};
49+
tagContainer.insertBefore(tagElement, tagInput);
50+
});
51+
filterBoards();
52+
}, 0);
53+
}
54+
3255
filterBoards();
3356
}
3457

@@ -47,6 +70,10 @@ function updateURL() {
4770
}
4871
});
4972

73+
// Add supported features to URL
74+
const selectedTags = [...document.querySelectorAll('.tag')].map(tag => tag.textContent);
75+
selectedTags.length ? hashParams.set("features", selectedTags.join(",")) : hashParams.delete("features");
76+
5077
window.history.replaceState({}, "", `#${hashParams.toString()}`);
5178
}
5279

@@ -84,6 +111,81 @@ function fillSocSocSelect(families, series = undefined, selectOnFill = false) {
84111
});
85112
}
86113

114+
function setupHWCapabilitiesField() {
115+
let selectedTags = [];
116+
117+
const tagContainer = document.getElementById('tag-container');
118+
const tagInput = document.getElementById('tag-input');
119+
const datalist = document.getElementById('tag-list');
120+
121+
const tagCounts = Array.from(document.querySelectorAll('.board-card')).reduce((acc, board) => {
122+
board.getAttribute('data-supported-features').split(' ').forEach(tag => {
123+
acc[tag] = (acc[tag] || 0) + 1;
124+
});
125+
return acc;
126+
}, {});
127+
128+
const allTags = Object.keys(tagCounts).sort();
129+
130+
function addTag(tag) {
131+
if (selectedTags.includes(tag) || tag === "" || !allTags.includes(tag)) return;
132+
selectedTags.push(tag);
133+
134+
const tagElement = document.createElement('span');
135+
tagElement.classList.add('tag');
136+
tagElement.textContent = tag;
137+
tagElement.onclick = () => removeTag(tag);
138+
tagContainer.insertBefore(tagElement, tagInput);
139+
140+
tagInput.value = '';
141+
updateDatalist();
142+
}
143+
144+
function removeTag(tag) {
145+
selectedTags = selectedTags.filter(t => t !== tag);
146+
document.querySelectorAll('.tag').forEach(el => {
147+
if (el.textContent.includes(tag)) el.remove();
148+
});
149+
updateDatalist();
150+
}
151+
152+
function updateDatalist() {
153+
datalist.innerHTML = '';
154+
const filteredTags = allTags.filter(tag => !selectedTags.includes(tag));
155+
156+
filteredTags.forEach(tag => {
157+
const option = document.createElement('option');
158+
option.value = tag;
159+
datalist.appendChild(option);
160+
});
161+
162+
filterBoards();
163+
}
164+
165+
tagInput.addEventListener('input', () => {
166+
if (allTags.includes(tagInput.value)) {
167+
addTag(tagInput.value);
168+
}
169+
});
170+
171+
// Add tag when pressing the Enter key
172+
tagInput.addEventListener('keydown', (e) => {
173+
if (e.key === 'Enter' && allTags.includes(tagInput.value)) {
174+
addTag(tagInput.value);
175+
e.preventDefault();
176+
}
177+
});
178+
179+
// Delete tag when pressing the Backspace key
180+
tagInput.addEventListener('keydown', (e) => {
181+
if (e.key === 'Backspace' && tagInput.value === '' && selectedTags.length > 0) {
182+
removeTag(selectedTags[selectedTags.length - 1]);
183+
}
184+
});
185+
186+
updateDatalist();
187+
}
188+
87189
document.addEventListener("DOMContentLoaded", function () {
88190
const form = document.querySelector(".filter-form");
89191

@@ -101,9 +203,10 @@ document.addEventListener("DOMContentLoaded", function () {
101203
fillSocFamilySelect();
102204
fillSocSeriesSelect();
103205
fillSocSocSelect();
104-
105206
populateFormFromURL();
106207

208+
setupHWCapabilitiesField();
209+
107210
socFamilySelect = document.getElementById("family");
108211
socFamilySelect.addEventListener("change", () => {
109212
const selectedFamilies = [...socFamilySelect.selectedOptions].map(({ value }) => value);
@@ -142,6 +245,11 @@ function resetForm() {
142245
fillSocFamilySelect();
143246
fillSocSeriesSelect();
144247
fillSocSocSelect();
248+
249+
// Clear supported features
250+
document.querySelectorAll('.tag').forEach(tag => tag.remove());
251+
document.getElementById('tag-input').value = '';
252+
145253
filterBoards();
146254
}
147255

@@ -160,8 +268,10 @@ function filterBoards() {
160268
const vendorSelect = document.getElementById("vendor").value;
161269
const socSocSelect = document.getElementById("soc");
162270

271+
const selectedTags = [...document.querySelectorAll('.tag')].map(tag => tag.textContent);
272+
163273
const resetFiltersBtn = document.getElementById("reset-filters");
164-
if (nameInput || archSelect || vendorSelect || socSocSelect.selectedOptions.length) {
274+
if (nameInput || archSelect || vendorSelect || socSocSelect.selectedOptions.length || selectedTags.length) {
165275
resetFiltersBtn.classList.remove("btn-disabled");
166276
} else {
167277
resetFiltersBtn.classList.add("btn-disabled");
@@ -174,6 +284,7 @@ function filterBoards() {
174284
const boardArchs = board.getAttribute("data-arch").split(" ");
175285
const boardVendor = board.getAttribute("data-vendor");
176286
const boardSocs = board.getAttribute("data-socs").split(" ");
287+
const boardSupportedFeatures = board.getAttribute("data-supported-features").split(" ");
177288

178289
let matches = true;
179290

@@ -183,7 +294,8 @@ function filterBoards() {
183294
!(nameInput && !boardName.includes(nameInput)) &&
184295
!(archSelect && !boardArchs.includes(archSelect)) &&
185296
!(vendorSelect && boardVendor !== vendorSelect) &&
186-
(selectedSocs.length === 0 || selectedSocs.some((soc) => boardSocs.includes(soc)));
297+
(selectedSocs.length === 0 || selectedSocs.some((soc) => boardSocs.includes(soc))) &&
298+
(selectedTags.length === 0 || selectedTags.every((tag) => boardSupportedFeatures.includes(tag)));
187299

188300
board.classList.toggle("hidden", !matches);
189301
});

doc/_extensions/zephyr/domain/templates/board-card.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{#
2-
Copyright (c) 2024, The Linux Foundation.
2+
Copyright (c) 2024-2025, The Linux Foundation.
33
SPDX-License-Identifier: Apache-2.0
44
#}
55

@@ -15,7 +15,7 @@
1515
data-arch="{{ board.archs | join(" ") }}"
1616
data-vendor="{{ board.vendor }}"
1717
data-socs="{{ board.socs | join(" ") }}"
18-
tabindex="0">
18+
data-supported-features="{{ board.supported_features | join(" ") }}" tabindex="0">
1919
<div class="vendor">{{ vendors[board.vendor] }}</div>
2020
{% if board.image -%}
2121
<img alt="A picture of the {{ board.full_name }} board"

0 commit comments

Comments
 (0)