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é <benjamin@zephyrproject.org>
This commit is contained in:
parent
b6922c83a8
commit
444964e031
12 changed files with 356 additions and 25 deletions
|
@ -24,6 +24,10 @@ this page <boards-shields>`.
|
||||||
single field, selecting multiple options (such as two architectures) will show boards matching
|
single field, selecting multiple options (such as two architectures) will show boards matching
|
||||||
**either** option.
|
**either** option.
|
||||||
|
|
||||||
|
* The list of supported hardware features for each board is automatically generated using
|
||||||
|
information from the Devicetree. It may not be reflecting the full list of supported features
|
||||||
|
since some of them may not be enabled by default.
|
||||||
|
|
||||||
* Can't find your exact board? Don't worry! If a similar board with the same or a closely related
|
* Can't find your exact board? Don't worry! If a similar board with the same or a closely related
|
||||||
MCU exists, you can use it as a :ref:`starting point <create-your-board-directory>` for adding
|
MCU exists, you can use it as a :ref:`starting point <create-your-board-directory>` for adding
|
||||||
support for your own board.
|
support for your own board.
|
||||||
|
|
|
@ -16,6 +16,7 @@ set(SPHINXOPTS "-j auto -W --keep-going -T" CACHE STRING "Default Sphinx Options
|
||||||
set(SPHINXOPTS_EXTRA "" CACHE STRING "Extra Sphinx Options (added to defaults)")
|
set(SPHINXOPTS_EXTRA "" CACHE STRING "Extra Sphinx Options (added to defaults)")
|
||||||
set(LATEXMKOPTS "-halt-on-error -no-shell-escape" CACHE STRING "Default latexmk options")
|
set(LATEXMKOPTS "-halt-on-error -no-shell-escape" CACHE STRING "Default latexmk options")
|
||||||
set(DT_TURBO_MODE OFF CACHE BOOL "Enable DT turbo mode")
|
set(DT_TURBO_MODE OFF CACHE BOOL "Enable DT turbo mode")
|
||||||
|
set(HW_FEATURES_TURBO_MODE OFF CACHE BOOL "Enable HW features turbo mode")
|
||||||
set(DOC_TAG "development" CACHE STRING "Documentation tag")
|
set(DOC_TAG "development" CACHE STRING "Documentation tag")
|
||||||
set(DTS_ROOTS "${ZEPHYR_BASE}" CACHE STRING "DT bindings root folders")
|
set(DTS_ROOTS "${ZEPHYR_BASE}" CACHE STRING "DT bindings root folders")
|
||||||
|
|
||||||
|
@ -149,6 +150,16 @@ set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${GEN_DEVICETREE_
|
||||||
#-------------------------------------------------------------------------------
|
#-------------------------------------------------------------------------------
|
||||||
# html
|
# html
|
||||||
|
|
||||||
|
set(SPHINX_TAGS "${DOC_TAG}")
|
||||||
|
if(HW_FEATURES_TURBO_MODE)
|
||||||
|
list(APPEND SPHINX_TAGS "hw_features_turbo")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(SPHINX_TAGS_ARGS "")
|
||||||
|
foreach(tag ${SPHINX_TAGS})
|
||||||
|
list(APPEND SPHINX_TAGS_ARGS "-t" "${tag}")
|
||||||
|
endforeach()
|
||||||
|
|
||||||
add_doc_target(
|
add_doc_target(
|
||||||
html
|
html
|
||||||
COMMAND ${CMAKE_COMMAND} -E env ${SPHINX_ENV} OUTPUT_DIR=${DOCS_HTML_DIR}
|
COMMAND ${CMAKE_COMMAND} -E env ${SPHINX_ENV} OUTPUT_DIR=${DOCS_HTML_DIR}
|
||||||
|
@ -157,7 +168,7 @@ add_doc_target(
|
||||||
-c ${DOCS_CFG_DIR}
|
-c ${DOCS_CFG_DIR}
|
||||||
-d ${DOCS_DOCTREE_DIR}
|
-d ${DOCS_DOCTREE_DIR}
|
||||||
-w ${DOCS_BUILD_DIR}/html.log
|
-w ${DOCS_BUILD_DIR}/html.log
|
||||||
-t ${DOC_TAG}
|
${SPHINX_TAGS_ARGS}
|
||||||
${SPHINXOPTS}
|
${SPHINXOPTS}
|
||||||
${SPHINXOPTS_EXTRA}
|
${SPHINXOPTS_EXTRA}
|
||||||
${DOCS_SRC_DIR}
|
${DOCS_SRC_DIR}
|
||||||
|
@ -187,7 +198,7 @@ add_doc_target(
|
||||||
-c ${DOCS_CFG_DIR}
|
-c ${DOCS_CFG_DIR}
|
||||||
-d ${DOCS_DOCTREE_DIR}
|
-d ${DOCS_DOCTREE_DIR}
|
||||||
-w ${DOCS_BUILD_DIR}/html.log
|
-w ${DOCS_BUILD_DIR}/html.log
|
||||||
-t ${DOC_TAG}
|
${SPHINX_TAGS_ARGS}
|
||||||
${SPHINXOPTS}
|
${SPHINXOPTS}
|
||||||
${SPHINXOPTS_EXTRA}
|
${SPHINXOPTS_EXTRA}
|
||||||
${DOCS_SRC_DIR}
|
${DOCS_SRC_DIR}
|
||||||
|
@ -214,7 +225,7 @@ add_doc_target(
|
||||||
-c ${DOCS_CFG_DIR}
|
-c ${DOCS_CFG_DIR}
|
||||||
-d ${DOCS_DOCTREE_DIR}
|
-d ${DOCS_DOCTREE_DIR}
|
||||||
-w ${DOCS_BUILD_DIR}/latex.log
|
-w ${DOCS_BUILD_DIR}/latex.log
|
||||||
-t ${DOC_TAG}
|
${SPHINX_TAGS_ARGS}
|
||||||
-t convertimages
|
-t convertimages
|
||||||
${SPHINXOPTS}
|
${SPHINXOPTS}
|
||||||
${SPHINXOPTS_EXTRA}
|
${SPHINXOPTS_EXTRA}
|
||||||
|
@ -266,7 +277,7 @@ add_doc_target(
|
||||||
-c ${DOCS_CFG_DIR}
|
-c ${DOCS_CFG_DIR}
|
||||||
-d ${DOCS_DOCTREE_DIR}
|
-d ${DOCS_DOCTREE_DIR}
|
||||||
-w ${DOCS_BUILD_DIR}/linkcheck.log
|
-w ${DOCS_BUILD_DIR}/linkcheck.log
|
||||||
-t ${DOC_TAG}
|
${SPHINX_TAGS_ARGS}
|
||||||
${SPHINXOPTS}
|
${SPHINXOPTS}
|
||||||
${SPHINXOPTS_EXTRA}
|
${SPHINXOPTS_EXTRA}
|
||||||
${DOCS_SRC_DIR}
|
${DOCS_SRC_DIR}
|
||||||
|
|
|
@ -8,6 +8,7 @@ SPHINXOPTS ?= -j auto -W --keep-going -T
|
||||||
SPHINXOPTS_EXTRA ?=
|
SPHINXOPTS_EXTRA ?=
|
||||||
LATEXMKOPTS ?= -halt-on-error -no-shell-escape
|
LATEXMKOPTS ?= -halt-on-error -no-shell-escape
|
||||||
DT_TURBO_MODE ?= 0
|
DT_TURBO_MODE ?= 0
|
||||||
|
HW_FEATURES_TURBO_MODE ?= 0
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Documentation targets
|
# Documentation targets
|
||||||
|
@ -15,10 +16,10 @@ DT_TURBO_MODE ?= 0
|
||||||
.PHONY: configure clean html html-fast html-live html-live-fast latex pdf doxygen
|
.PHONY: configure clean html html-fast html-live html-live-fast latex pdf doxygen
|
||||||
|
|
||||||
html-fast:
|
html-fast:
|
||||||
${MAKE} html DT_TURBO_MODE=1
|
${MAKE} html DT_TURBO_MODE=1 HW_FEATURES_TURBO_MODE=1
|
||||||
|
|
||||||
html-live-fast:
|
html-live-fast:
|
||||||
${MAKE} html-live DT_TURBO_MODE=1
|
${MAKE} html-live DT_TURBO_MODE=1 HW_FEATURES_TURBO_MODE=1
|
||||||
|
|
||||||
html html-live latex pdf linkcheck doxygen: configure
|
html html-live latex pdf linkcheck doxygen: configure
|
||||||
cmake --build ${BUILDDIR} --target $@
|
cmake --build ${BUILDDIR} --target $@
|
||||||
|
@ -32,7 +33,8 @@ configure:
|
||||||
-DSPHINXOPTS="${SPHINXOPTS}" \
|
-DSPHINXOPTS="${SPHINXOPTS}" \
|
||||||
-DSPHINXOPTS_EXTRA="${SPHINXOPTS_EXTRA}" \
|
-DSPHINXOPTS_EXTRA="${SPHINXOPTS_EXTRA}" \
|
||||||
-DLATEXMKOPTS="${LATEXMKOPTS}" \
|
-DLATEXMKOPTS="${LATEXMKOPTS}" \
|
||||||
-DDT_TURBO_MODE=${DT_TURBO_MODE}
|
-DDT_TURBO_MODE=${DT_TURBO_MODE} \
|
||||||
|
-DHW_FEATURES_TURBO_MODE=${HW_FEATURES_TURBO_MODE}
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
cmake --build ${BUILDDIR} --target clean
|
cmake --build ${BUILDDIR} --target clean
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
Zephyr Extension
|
Zephyr Extension
|
||||||
################
|
################
|
||||||
|
|
||||||
Copyright (c) 2023 The Linux Foundation
|
Copyright (c) 2023-2025 The Linux Foundation
|
||||||
SPDX-License-Identifier: Apache-2.0
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
This extension adds a new ``zephyr`` domain for handling the documentation of various entities
|
This extension adds a new ``zephyr`` domain for handling the documentation of various entities
|
||||||
|
@ -708,6 +708,7 @@ class BoardCatalogDirective(SphinxDirective):
|
||||||
"boards": domain_data["boards"],
|
"boards": domain_data["boards"],
|
||||||
"vendors": domain_data["vendors"],
|
"vendors": domain_data["vendors"],
|
||||||
"socs": domain_data["socs"],
|
"socs": domain_data["socs"],
|
||||||
|
"hw_features_present": self.env.app.config.zephyr_generate_hw_features,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
return [nodes.raw("", rendered, format="html")]
|
return [nodes.raw("", rendered, format="html")]
|
||||||
|
@ -954,7 +955,7 @@ def install_static_assets_as_needed(
|
||||||
|
|
||||||
|
|
||||||
def load_board_catalog_into_domain(app: Sphinx) -> None:
|
def load_board_catalog_into_domain(app: Sphinx) -> None:
|
||||||
board_catalog = get_catalog()
|
board_catalog = get_catalog(generate_hw_features=app.config.zephyr_generate_hw_features)
|
||||||
app.env.domaindata["zephyr"]["boards"] = board_catalog["boards"]
|
app.env.domaindata["zephyr"]["boards"] = board_catalog["boards"]
|
||||||
app.env.domaindata["zephyr"]["vendors"] = board_catalog["vendors"]
|
app.env.domaindata["zephyr"]["vendors"] = board_catalog["vendors"]
|
||||||
app.env.domaindata["zephyr"]["socs"] = board_catalog["socs"]
|
app.env.domaindata["zephyr"]["socs"] = board_catalog["socs"]
|
||||||
|
@ -962,6 +963,7 @@ def load_board_catalog_into_domain(app: Sphinx) -> None:
|
||||||
|
|
||||||
def setup(app):
|
def setup(app):
|
||||||
app.add_config_value("zephyr_breathe_insert_related_samples", False, "env")
|
app.add_config_value("zephyr_breathe_insert_related_samples", False, "env")
|
||||||
|
app.add_config_value("zephyr_generate_hw_features", False, "env")
|
||||||
|
|
||||||
app.add_domain(ZephyrDomain)
|
app.add_domain(ZephyrDomain)
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/**
|
/**
|
||||||
* Copyright (c) 2024, The Linux Foundation.
|
* Copyright (c) 2024-2025, The Linux Foundation.
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -82,6 +82,49 @@
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tag-container {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 50px;
|
||||||
|
padding: 5px 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-container:focus-within {
|
||||||
|
border-color: var(--input-focus-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
background-color: var(--admonition-note-background-color);
|
||||||
|
color: var(--admonition-note-color);
|
||||||
|
padding: 2px 12px 4px 16px;
|
||||||
|
border-radius: 30px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag::after {
|
||||||
|
content: '\00D7'; /* multiplication sign */
|
||||||
|
margin-left: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-form input.tag-input {
|
||||||
|
flex: 1;
|
||||||
|
border: none;
|
||||||
|
padding: 5px;
|
||||||
|
outline: none;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
#catalog {
|
#catalog {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/**
|
/**
|
||||||
* Copyright (c) 2024, The Linux Foundation.
|
* Copyright (c) 2024-2025, The Linux Foundation.
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -29,6 +29,29 @@ function populateFormFromURL() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Restore supported features from URL
|
||||||
|
if (hashParams.has("features")) {
|
||||||
|
const features = hashParams.get("features").split(",");
|
||||||
|
setTimeout(() => {
|
||||||
|
features.forEach(feature => {
|
||||||
|
const tagContainer = document.getElementById('tag-container');
|
||||||
|
const tagInput = document.getElementById('tag-input');
|
||||||
|
|
||||||
|
const tagElement = document.createElement('span');
|
||||||
|
tagElement.classList.add('tag');
|
||||||
|
tagElement.textContent = feature;
|
||||||
|
tagElement.onclick = () => {
|
||||||
|
const selectedTags = [...document.querySelectorAll('.tag')].map(tag => tag.textContent);
|
||||||
|
selectedTags.splice(selectedTags.indexOf(feature), 1);
|
||||||
|
tagElement.remove();
|
||||||
|
filterBoards();
|
||||||
|
};
|
||||||
|
tagContainer.insertBefore(tagElement, tagInput);
|
||||||
|
});
|
||||||
|
filterBoards();
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
filterBoards();
|
filterBoards();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,6 +70,10 @@ function updateURL() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add supported features to URL
|
||||||
|
const selectedTags = [...document.querySelectorAll('.tag')].map(tag => tag.textContent);
|
||||||
|
selectedTags.length ? hashParams.set("features", selectedTags.join(",")) : hashParams.delete("features");
|
||||||
|
|
||||||
window.history.replaceState({}, "", `#${hashParams.toString()}`);
|
window.history.replaceState({}, "", `#${hashParams.toString()}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,6 +111,81 @@ function fillSocSocSelect(families, series = undefined, selectOnFill = false) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setupHWCapabilitiesField() {
|
||||||
|
let selectedTags = [];
|
||||||
|
|
||||||
|
const tagContainer = document.getElementById('tag-container');
|
||||||
|
const tagInput = document.getElementById('tag-input');
|
||||||
|
const datalist = document.getElementById('tag-list');
|
||||||
|
|
||||||
|
const tagCounts = Array.from(document.querySelectorAll('.board-card')).reduce((acc, board) => {
|
||||||
|
board.getAttribute('data-supported-features').split(' ').forEach(tag => {
|
||||||
|
acc[tag] = (acc[tag] || 0) + 1;
|
||||||
|
});
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const allTags = Object.keys(tagCounts).sort();
|
||||||
|
|
||||||
|
function addTag(tag) {
|
||||||
|
if (selectedTags.includes(tag) || tag === "" || !allTags.includes(tag)) return;
|
||||||
|
selectedTags.push(tag);
|
||||||
|
|
||||||
|
const tagElement = document.createElement('span');
|
||||||
|
tagElement.classList.add('tag');
|
||||||
|
tagElement.textContent = tag;
|
||||||
|
tagElement.onclick = () => removeTag(tag);
|
||||||
|
tagContainer.insertBefore(tagElement, tagInput);
|
||||||
|
|
||||||
|
tagInput.value = '';
|
||||||
|
updateDatalist();
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeTag(tag) {
|
||||||
|
selectedTags = selectedTags.filter(t => t !== tag);
|
||||||
|
document.querySelectorAll('.tag').forEach(el => {
|
||||||
|
if (el.textContent.includes(tag)) el.remove();
|
||||||
|
});
|
||||||
|
updateDatalist();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateDatalist() {
|
||||||
|
datalist.innerHTML = '';
|
||||||
|
const filteredTags = allTags.filter(tag => !selectedTags.includes(tag));
|
||||||
|
|
||||||
|
filteredTags.forEach(tag => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = tag;
|
||||||
|
datalist.appendChild(option);
|
||||||
|
});
|
||||||
|
|
||||||
|
filterBoards();
|
||||||
|
}
|
||||||
|
|
||||||
|
tagInput.addEventListener('input', () => {
|
||||||
|
if (allTags.includes(tagInput.value)) {
|
||||||
|
addTag(tagInput.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add tag when pressing the Enter key
|
||||||
|
tagInput.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Enter' && allTags.includes(tagInput.value)) {
|
||||||
|
addTag(tagInput.value);
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Delete tag when pressing the Backspace key
|
||||||
|
tagInput.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Backspace' && tagInput.value === '' && selectedTags.length > 0) {
|
||||||
|
removeTag(selectedTags[selectedTags.length - 1]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
updateDatalist();
|
||||||
|
}
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
const form = document.querySelector(".filter-form");
|
const form = document.querySelector(".filter-form");
|
||||||
|
|
||||||
|
@ -101,9 +203,10 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||||
fillSocFamilySelect();
|
fillSocFamilySelect();
|
||||||
fillSocSeriesSelect();
|
fillSocSeriesSelect();
|
||||||
fillSocSocSelect();
|
fillSocSocSelect();
|
||||||
|
|
||||||
populateFormFromURL();
|
populateFormFromURL();
|
||||||
|
|
||||||
|
setupHWCapabilitiesField();
|
||||||
|
|
||||||
socFamilySelect = document.getElementById("family");
|
socFamilySelect = document.getElementById("family");
|
||||||
socFamilySelect.addEventListener("change", () => {
|
socFamilySelect.addEventListener("change", () => {
|
||||||
const selectedFamilies = [...socFamilySelect.selectedOptions].map(({ value }) => value);
|
const selectedFamilies = [...socFamilySelect.selectedOptions].map(({ value }) => value);
|
||||||
|
@ -142,6 +245,11 @@ function resetForm() {
|
||||||
fillSocFamilySelect();
|
fillSocFamilySelect();
|
||||||
fillSocSeriesSelect();
|
fillSocSeriesSelect();
|
||||||
fillSocSocSelect();
|
fillSocSocSelect();
|
||||||
|
|
||||||
|
// Clear supported features
|
||||||
|
document.querySelectorAll('.tag').forEach(tag => tag.remove());
|
||||||
|
document.getElementById('tag-input').value = '';
|
||||||
|
|
||||||
filterBoards();
|
filterBoards();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,8 +268,10 @@ function filterBoards() {
|
||||||
const vendorSelect = document.getElementById("vendor").value;
|
const vendorSelect = document.getElementById("vendor").value;
|
||||||
const socSocSelect = document.getElementById("soc");
|
const socSocSelect = document.getElementById("soc");
|
||||||
|
|
||||||
|
const selectedTags = [...document.querySelectorAll('.tag')].map(tag => tag.textContent);
|
||||||
|
|
||||||
const resetFiltersBtn = document.getElementById("reset-filters");
|
const resetFiltersBtn = document.getElementById("reset-filters");
|
||||||
if (nameInput || archSelect || vendorSelect || socSocSelect.selectedOptions.length) {
|
if (nameInput || archSelect || vendorSelect || socSocSelect.selectedOptions.length || selectedTags.length) {
|
||||||
resetFiltersBtn.classList.remove("btn-disabled");
|
resetFiltersBtn.classList.remove("btn-disabled");
|
||||||
} else {
|
} else {
|
||||||
resetFiltersBtn.classList.add("btn-disabled");
|
resetFiltersBtn.classList.add("btn-disabled");
|
||||||
|
@ -174,6 +284,7 @@ function filterBoards() {
|
||||||
const boardArchs = board.getAttribute("data-arch").split(" ");
|
const boardArchs = board.getAttribute("data-arch").split(" ");
|
||||||
const boardVendor = board.getAttribute("data-vendor");
|
const boardVendor = board.getAttribute("data-vendor");
|
||||||
const boardSocs = board.getAttribute("data-socs").split(" ");
|
const boardSocs = board.getAttribute("data-socs").split(" ");
|
||||||
|
const boardSupportedFeatures = board.getAttribute("data-supported-features").split(" ");
|
||||||
|
|
||||||
let matches = true;
|
let matches = true;
|
||||||
|
|
||||||
|
@ -183,7 +294,8 @@ function filterBoards() {
|
||||||
!(nameInput && !boardName.includes(nameInput)) &&
|
!(nameInput && !boardName.includes(nameInput)) &&
|
||||||
!(archSelect && !boardArchs.includes(archSelect)) &&
|
!(archSelect && !boardArchs.includes(archSelect)) &&
|
||||||
!(vendorSelect && boardVendor !== vendorSelect) &&
|
!(vendorSelect && boardVendor !== vendorSelect) &&
|
||||||
(selectedSocs.length === 0 || selectedSocs.some((soc) => boardSocs.includes(soc)));
|
(selectedSocs.length === 0 || selectedSocs.some((soc) => boardSocs.includes(soc))) &&
|
||||||
|
(selectedTags.length === 0 || selectedTags.every((tag) => boardSupportedFeatures.includes(tag)));
|
||||||
|
|
||||||
board.classList.toggle("hidden", !matches);
|
board.classList.toggle("hidden", !matches);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{#
|
{#
|
||||||
Copyright (c) 2024, The Linux Foundation.
|
Copyright (c) 2024-2025, The Linux Foundation.
|
||||||
SPDX-License-Identifier: Apache-2.0
|
SPDX-License-Identifier: Apache-2.0
|
||||||
#}
|
#}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@
|
||||||
data-arch="{{ board.archs | join(" ") }}"
|
data-arch="{{ board.archs | join(" ") }}"
|
||||||
data-vendor="{{ board.vendor }}"
|
data-vendor="{{ board.vendor }}"
|
||||||
data-socs="{{ board.socs | join(" ") }}"
|
data-socs="{{ board.socs | join(" ") }}"
|
||||||
tabindex="0">
|
data-supported-features="{{ board.supported_features | join(" ") }}" tabindex="0">
|
||||||
<div class="vendor">{{ vendors[board.vendor] }}</div>
|
<div class="vendor">{{ vendors[board.vendor] }}</div>
|
||||||
{% if board.image -%}
|
{% if board.image -%}
|
||||||
<img alt="A picture of the {{ board.full_name }} board"
|
<img alt="A picture of the {{ board.full_name }} board"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{#
|
{#
|
||||||
Copyright (c) 2024, The Linux Foundation.
|
Copyright (c) 2024-2025, The Linux Foundation.
|
||||||
SPDX-License-Identifier: Apache-2.0
|
SPDX-License-Identifier: Apache-2.0
|
||||||
#}
|
#}
|
||||||
|
|
||||||
|
@ -60,6 +60,20 @@
|
||||||
<select id="soc" name="soc" size="10" multiple></select>
|
<select id="soc" name="soc" size="10" multiple></select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group" style="flex-basis: 100%">
|
||||||
|
<label for="hw-capabilities">Supported Hardware Capabilities</label>
|
||||||
|
<div class="tag-container" id="tag-container">
|
||||||
|
<input list="tag-list" class="tag-input" id="tag-input"
|
||||||
|
placeholder="{% if hw_features_present -%}
|
||||||
|
Type a tag...
|
||||||
|
{%- else -%}
|
||||||
|
List of supported hardware capabilities is not available
|
||||||
|
{%- endif %}"
|
||||||
|
{% if not hw_features_present %}disabled{% endif %}>
|
||||||
|
<datalist id="tag-list"></datalist>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div id="form-options" style="text-align: center; margin-bottom: 20px">
|
<div id="form-options" style="text-align: center; margin-bottom: 20px">
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
# Copyright (c) 2024 The Linux Foundation
|
# Copyright (c) 2024-2025 The Linux Foundation
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
import pickle
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
@ -12,6 +16,8 @@ import zephyr_module
|
||||||
from gen_devicetree_rest import VndLookup
|
from gen_devicetree_rest import VndLookup
|
||||||
|
|
||||||
ZEPHYR_BASE = Path(__file__).parents[2]
|
ZEPHYR_BASE = Path(__file__).parents[2]
|
||||||
|
ZEPHYR_BINDINGS = ZEPHYR_BASE / "dts/bindings"
|
||||||
|
EDT_PICKLE_PATH = "zephyr/edt.pickle"
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -38,6 +44,7 @@ def guess_image(board_or_shield):
|
||||||
|
|
||||||
return (img_file.relative_to(ZEPHYR_BASE)).as_posix() if img_file else None
|
return (img_file.relative_to(ZEPHYR_BASE)).as_posix() if img_file else None
|
||||||
|
|
||||||
|
|
||||||
def guess_doc_page(board_or_shield):
|
def guess_doc_page(board_or_shield):
|
||||||
patterns = [
|
patterns = [
|
||||||
"doc/index.{ext}",
|
"doc/index.{ext}",
|
||||||
|
@ -51,7 +58,92 @@ def guess_doc_page(board_or_shield):
|
||||||
return doc_file
|
return doc_file
|
||||||
|
|
||||||
|
|
||||||
def get_catalog():
|
def gather_board_devicetrees(twister_out_dir):
|
||||||
|
"""Gather EDT objects for each board from twister output directory.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
twister_out_dir: Path object pointing to twister output directory
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A dictionary mapping board names to a dictionary of board targets and their EDT objects.
|
||||||
|
The structure is: {board_name: {board_target: edt_object}}
|
||||||
|
"""
|
||||||
|
board_devicetrees = {}
|
||||||
|
|
||||||
|
if not twister_out_dir.exists():
|
||||||
|
return board_devicetrees
|
||||||
|
|
||||||
|
# Find all build_info.yml files in twister-out
|
||||||
|
build_info_files = list(twister_out_dir.glob("*/**/build_info.yml"))
|
||||||
|
|
||||||
|
for build_info_file in build_info_files:
|
||||||
|
# Look for corresponding zephyr.dts
|
||||||
|
edt_pickle_file = build_info_file.parent / EDT_PICKLE_PATH
|
||||||
|
if not edt_pickle_file.exists():
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(build_info_file) as f:
|
||||||
|
build_info = yaml.safe_load(f)
|
||||||
|
board_info = build_info.get('cmake', {}).get('board', {})
|
||||||
|
board_name = board_info.get('name')
|
||||||
|
qualifier = board_info.get('qualifiers', '')
|
||||||
|
revision = board_info.get('revision', '')
|
||||||
|
|
||||||
|
board_target = board_name
|
||||||
|
if qualifier:
|
||||||
|
board_target = f"{board_name}/{qualifier}"
|
||||||
|
if revision:
|
||||||
|
board_target = f"{board_target}@{revision}"
|
||||||
|
|
||||||
|
with open(edt_pickle_file, 'rb') as f:
|
||||||
|
edt = pickle.load(f)
|
||||||
|
board_devicetrees.setdefault(board_name, {})[board_target] = edt
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error processing build info file {build_info_file}: {e}")
|
||||||
|
|
||||||
|
return board_devicetrees
|
||||||
|
|
||||||
|
|
||||||
|
def run_twister_cmake_only(outdir):
|
||||||
|
"""Run twister in cmake-only mode to generate build info files.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
outdir: Directory where twister should output its files
|
||||||
|
"""
|
||||||
|
twister_cmd = [
|
||||||
|
sys.executable,
|
||||||
|
f"{ZEPHYR_BASE}/scripts/twister",
|
||||||
|
"-T", "samples/hello_world/",
|
||||||
|
"--all",
|
||||||
|
"-M",
|
||||||
|
"--keep-artifacts", "zephyr/edt.pickle",
|
||||||
|
"--cmake-only",
|
||||||
|
"--outdir", str(outdir),
|
||||||
|
]
|
||||||
|
|
||||||
|
minimal_env = {
|
||||||
|
'PATH': os.environ.get('PATH', ''),
|
||||||
|
'ZEPHYR_BASE': str(ZEPHYR_BASE),
|
||||||
|
'HOME': os.environ.get('HOME', ''),
|
||||||
|
'PYTHONPATH': os.environ.get('PYTHONPATH', '')
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
subprocess.run(twister_cmd, check=True, cwd=ZEPHYR_BASE, env=minimal_env)
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
logger.warning(f"Failed to run Twister, list of hw features might be incomplete.\n{e}")
|
||||||
|
|
||||||
|
|
||||||
|
def get_catalog(generate_hw_features=False):
|
||||||
|
"""Get the board catalog.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
generate_hw_features: If True, run twister to generate hardware features information.
|
||||||
|
"""
|
||||||
|
import tempfile
|
||||||
|
|
||||||
vnd_lookup = VndLookup(ZEPHYR_BASE / "dts/bindings/vendor-prefixes.txt", [])
|
vnd_lookup = VndLookup(ZEPHYR_BASE / "dts/bindings/vendor-prefixes.txt", [])
|
||||||
|
|
||||||
module_settings = {
|
module_settings = {
|
||||||
|
@ -78,6 +170,15 @@ def get_catalog():
|
||||||
boards = list_boards.find_v2_boards(args_find_boards)
|
boards = list_boards.find_v2_boards(args_find_boards)
|
||||||
systems = list_hardware.find_v2_systems(args_find_boards)
|
systems = list_hardware.find_v2_systems(args_find_boards)
|
||||||
board_catalog = {}
|
board_catalog = {}
|
||||||
|
board_devicetrees = {}
|
||||||
|
|
||||||
|
if generate_hw_features:
|
||||||
|
logger.info("Running twister in cmake-only mode to get Devicetree files for all boards")
|
||||||
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||||
|
run_twister_cmake_only(tmp_dir)
|
||||||
|
board_devicetrees = gather_board_devicetrees(Path(tmp_dir))
|
||||||
|
else:
|
||||||
|
logger.info("Skipping generation of supported hardware features.")
|
||||||
|
|
||||||
for board in boards.values():
|
for board in boards.values():
|
||||||
# We could use board.vendor but it is often incorrect. Instead, deduce vendor from
|
# We could use board.vendor but it is often incorrect. Instead, deduce vendor from
|
||||||
|
@ -91,6 +192,40 @@ def get_catalog():
|
||||||
vendor = folder.name
|
vendor = folder.name
|
||||||
break
|
break
|
||||||
|
|
||||||
|
socs = {soc.name for soc in board.socs}
|
||||||
|
full_name = board.full_name or board.name
|
||||||
|
doc_page = guess_doc_page(board)
|
||||||
|
|
||||||
|
supported_features = {}
|
||||||
|
targets = set()
|
||||||
|
|
||||||
|
# Use pre-gathered build info and DTS files
|
||||||
|
if board.name in board_devicetrees:
|
||||||
|
for board_target, edt in board_devicetrees[board.name].items():
|
||||||
|
targets.add(board_target)
|
||||||
|
|
||||||
|
okay_nodes = [
|
||||||
|
node
|
||||||
|
for node in edt.nodes
|
||||||
|
if node.status == "okay" and node.matching_compat is not None
|
||||||
|
]
|
||||||
|
|
||||||
|
target_features = {}
|
||||||
|
for node in okay_nodes:
|
||||||
|
binding_path = Path(node.binding_path)
|
||||||
|
binding_type = (
|
||||||
|
binding_path.relative_to(ZEPHYR_BINDINGS).parts[0]
|
||||||
|
if binding_path.is_relative_to(ZEPHYR_BINDINGS)
|
||||||
|
else "misc"
|
||||||
|
)
|
||||||
|
target_features.setdefault(binding_type, set()).add(node.matching_compat)
|
||||||
|
|
||||||
|
|
||||||
|
# for now we do the union of all supported features for all of board's targets but
|
||||||
|
# in the future it's likely the catalog will be organized so that the list of
|
||||||
|
# supported features is also available per target.
|
||||||
|
supported_features.update(target_features)
|
||||||
|
|
||||||
# Grab all the twister files for this board and use them to figure out all the archs it
|
# Grab all the twister files for this board and use them to figure out all the archs it
|
||||||
# supports.
|
# supports.
|
||||||
archs = set()
|
archs = set()
|
||||||
|
@ -103,10 +238,6 @@ def get_catalog():
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error parsing twister file {twister_file}: {e}")
|
logger.error(f"Error parsing twister file {twister_file}: {e}")
|
||||||
|
|
||||||
socs = {soc.name for soc in board.socs}
|
|
||||||
full_name = board.full_name or board.name
|
|
||||||
doc_page = guess_doc_page(board)
|
|
||||||
|
|
||||||
board_catalog[board.name] = {
|
board_catalog[board.name] = {
|
||||||
"name": board.name,
|
"name": board.name,
|
||||||
"full_name": full_name,
|
"full_name": full_name,
|
||||||
|
@ -114,6 +245,8 @@ def get_catalog():
|
||||||
"vendor": vendor,
|
"vendor": vendor,
|
||||||
"archs": list(archs),
|
"archs": list(archs),
|
||||||
"socs": list(socs),
|
"socs": list(socs),
|
||||||
|
"supported_features": supported_features,
|
||||||
|
"targets": list(targets),
|
||||||
"image": guess_image(board),
|
"image": guess_image(board),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
1
doc/_static/css/custom.css
vendored
1
doc/_static/css/custom.css
vendored
|
@ -1,6 +1,7 @@
|
||||||
/**
|
/**
|
||||||
* Copyright (c) 2019-2020, Juan Linietsky, Ariel Manzur and the Godot community
|
* Copyright (c) 2019-2020, Juan Linietsky, Ariel Manzur and the Godot community
|
||||||
* Copyright (c) 2021, Teslabs Engineering S.L.
|
* Copyright (c) 2021, Teslabs Engineering S.L.
|
||||||
|
* Copyright (c) 2023-2025, The Linux Foundation.
|
||||||
* SPDX-License-Identifier: CC-BY-3.0
|
* SPDX-License-Identifier: CC-BY-3.0
|
||||||
*
|
*
|
||||||
* Various tweaks to the Read the Docs theme to better conform with Zephyr's
|
* Various tweaks to the Read the Docs theme to better conform with Zephyr's
|
||||||
|
|
|
@ -325,6 +325,7 @@ external_content_keep = [
|
||||||
# -- Options for zephyr.domain --------------------------------------------
|
# -- Options for zephyr.domain --------------------------------------------
|
||||||
|
|
||||||
zephyr_breathe_insert_related_samples = True
|
zephyr_breathe_insert_related_samples = True
|
||||||
|
zephyr_generate_hw_features = not tags.has("hw_features_turbo") # pylint: disable=undefined-variable # noqa: F821
|
||||||
|
|
||||||
# -- Options for sphinx.ext.graphviz --------------------------------------
|
# -- Options for sphinx.ext.graphviz --------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -253,11 +253,19 @@ To enable this mode, set the following option when invoking cmake::
|
||||||
|
|
||||||
-DDT_TURBO_MODE=1
|
-DDT_TURBO_MODE=1
|
||||||
|
|
||||||
or invoke make with the following target::
|
Another step that typically takes a long time is the generation of the list of
|
||||||
|
supported features for each board. This can be disabled by setting the following
|
||||||
|
option when invoking cmake::
|
||||||
|
|
||||||
|
-DHW_FEATURES_TURBO_MODE=1
|
||||||
|
|
||||||
|
Invoking :command:`make` with the following target will build the documentation
|
||||||
|
without either of the aforementioned features::
|
||||||
|
|
||||||
cd ~/zephyrproject/zephyr/doc
|
cd ~/zephyrproject/zephyr/doc
|
||||||
|
|
||||||
# To generate HTML output without detailed Kconfig
|
# To generate HTML output without detailed Devicetree bindings documentation
|
||||||
|
# and supported features index
|
||||||
make html-fast
|
make html-fast
|
||||||
|
|
||||||
Viewing generated documentation locally
|
Viewing generated documentation locally
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue