From f06c26a6d02c7ef911bfa88b38c2157996fb3493 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Cab=C3=A9?= Date: Tue, 25 Feb 2025 08:26:09 +0100 Subject: [PATCH] doc: extensions: boards: add zephyr:board-supported-runners directive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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é --- doc/_extensions/zephyr/domain/__init__.py | 125 ++++++++++++++++++++ doc/_scripts/gen_boards_catalog.py | 63 ++++++++-- doc/contribute/documentation/guidelines.rst | 15 +++ doc/develop/flash_debug/host-tools.rst | 14 ++- doc/templates/board.tmpl | 2 + 5 files changed, 210 insertions(+), 9 deletions(-) diff --git a/doc/_extensions/zephyr/domain/__init__.py b/doc/_extensions/zephyr/domain/__init__.py index ab2edf4cd5b..462b5652763 100644 --- a/doc/_extensions/zephyr/domain/__init__.py +++ b/doc/_extensions/zephyr/domain/__init__.py @@ -16,6 +16,10 @@ Directives - ``zephyr:code-sample-listing::`` - Shows a listing of code samples found in a given category. - ``zephyr:board-catalog::`` - Shows a listing of boards supported by Zephyr. - ``zephyr:board::`` - Flags a document as being the documentation page for a board. +- ``zephyr:board-supported-hw::`` - Shows a table of supported hardware features for all the targets + of the board documented in the current page. +- ``zephyr:board-supported-runners::`` - Shows a table of supported runners for the board documented + in the current page. Roles ----- @@ -58,6 +62,7 @@ __version__ = "0.2.0" sys.path.insert(0, str(Path(__file__).parents[4] / "scripts/dts/python-devicetree/src")) +sys.path.insert(0, str(Path(__file__).parents[4] / "scripts/west_commands")) sys.path.insert(0, str(Path(__file__).parents[3] / "_scripts")) from gen_boards_catalog import get_catalog @@ -729,6 +734,9 @@ class BoardDirective(SphinxDirective): board_node["archs"] = board["archs"] board_node["socs"] = board["socs"] board_node["image"] = board["image"] + board_node["supported_runners"] = board["supported_runners"] + board_node["flash_runner"] = board["flash_runner"] + board_node["debug_runner"] = board["debug_runner"] return [board_node] @@ -992,6 +1000,121 @@ class BoardSupportedHardwareDirective(SphinxDirective): return result_nodes +class BoardSupportedRunnersDirective(SphinxDirective): + """A directive for showing the supported runners of a board.""" + + has_content = False + required_arguments = 0 + optional_arguments = 0 + + def run(self): + env = self.env + docname = env.docname + + matcher = NodeMatcher(BoardNode) + board_nodes = list(self.state.document.traverse(matcher)) + if not board_nodes: + logger.warning( + "board-supported-runners directive must be used in a board documentation page.", + location=(docname, self.lineno), + ) + return [] + + if not env.app.config.zephyr_generate_hw_features: + note = nodes.admonition() + note += nodes.title(text="Note") + note["classes"].append("warning") + note += nodes.paragraph( + text="The list of supported runners was not generated. Run a full documentation " + "build for the required metadata to be available." + ) + return [note] + + board_node = board_nodes[0] + runners = board_node["supported_runners"] + flash_runner = board_node["flash_runner"] + debug_runner = board_node["debug_runner"] + + result_nodes = [] + + paragraph = nodes.paragraph() + paragraph += nodes.Text("The ") + paragraph += nodes.literal(text=board_node["id"]) + paragraph += nodes.Text( + " board supports the runners and associated west commands listed below." + ) + result_nodes.append(paragraph) + + env_runners = env.domaindata["zephyr"]["runners"] + commands = ["flash", "debug"] + for runner in env_runners: + if runner in board_node["supported_runners"]: + for cmd in env_runners[runner].get("commands", []): + if cmd not in commands: + commands.append(cmd) + + # create the table + table = nodes.table(classes=["colwidths-given", "runners-table"]) + tgroup = nodes.tgroup(cols=len(commands) + 1) # +1 for the Runner column + + # Add colspec for Runner column + tgroup += nodes.colspec(colwidth=15, classes=["type"]) + # Add colspecs for command columns + for _ in commands: + tgroup += nodes.colspec(colwidth=15, classes=["type"]) + + thead = nodes.thead() + row = nodes.row() + entry = nodes.entry() + row += entry + headers = [*commands] + for header in headers: + entry = nodes.entry(classes=[header.lower()]) + entry += addnodes.literal_strong(text=header, classes=["command"]) + row += entry + thead += row + tgroup += thead + + tbody = nodes.tbody() + + # add a row for each runner + for runner in sorted(runners): + row = nodes.row() + # First column - Runner name + entry = nodes.entry() + + xref = addnodes.pending_xref( + "", + refdomain="std", + reftype="ref", + reftarget=f"runner_{runner}", + refexplicit=True, + refwarn=False, + ) + xref += nodes.Text(runner) + entry += addnodes.literal_strong("", "", xref) + row += entry + + # Add columns for each command + for command in commands: + entry = nodes.entry() + if command in env_runners[runner].get("commands", []): + entry += nodes.Text("✅") + if (command == "flash" and runner == flash_runner) or ( + command == "debug" and runner == debug_runner + ): + entry += nodes.Text(" (default)") + row += entry + tbody += row + + tgroup += tbody + table += tgroup + + result_nodes.append(table) + + return result_nodes + + class ZephyrDomain(Domain): """Zephyr domain""" @@ -1011,6 +1134,7 @@ class ZephyrDomain(Domain): "board-catalog": BoardCatalogDirective, "board": BoardDirective, "board-supported-hw": BoardSupportedHardwareDirective, + "board-supported-runners": BoardSupportedRunnersDirective, } object_types: dict[str, ObjType] = { @@ -1247,6 +1371,7 @@ def load_board_catalog_into_domain(app: Sphinx) -> None: app.env.domaindata["zephyr"]["boards"] = board_catalog["boards"] app.env.domaindata["zephyr"]["vendors"] = board_catalog["vendors"] app.env.domaindata["zephyr"]["socs"] = board_catalog["socs"] + app.env.domaindata["zephyr"]["runners"] = board_catalog["runners"] def setup(app): diff --git a/doc/_scripts/gen_boards_catalog.py b/doc/_scripts/gen_boards_catalog.py index 07c0d8ed58f..0c44f13412e 100755 --- a/doc/_scripts/gen_boards_catalog.py +++ b/doc/_scripts/gen_boards_catalog.py @@ -15,6 +15,7 @@ import list_hardware import yaml import zephyr_module from gen_devicetree_rest import VndLookup +from runners.core import ZephyrBinaryRunner ZEPHYR_BASE = Path(__file__).parents[2] ZEPHYR_BINDINGS = ZEPHYR_BASE / "dts/bindings" @@ -22,6 +23,10 @@ EDT_PICKLE_PATHS = [ "zephyr/edt.pickle", "hello_world/zephyr/edt.pickle" # for board targets using sysbuild ] +RUNNERS_YAML_PATHS = [ + "zephyr/runners.yaml", + "hello_world/zephyr/runners.yaml" # for board targets using sysbuild +] logger = logging.getLogger(__name__) @@ -108,20 +113,25 @@ def guess_doc_page(board_or_shield): return doc_file -def gather_board_devicetrees(twister_out_dir): - """Gather EDT objects for each board from twister output directory. +def gather_board_build_info(twister_out_dir): + """Gather EDT objects and runners info 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}} + A tuple of two dictionaries: + - A dictionary mapping board names to a dictionary of board targets and their EDT. + objects. + The structure is: {board_name: {board_target: edt_object}} + - A dictionary mapping board names to a dictionary of board targets and their runners + info. + The structure is: {board_name: {board_target: runners_info}} """ board_devicetrees = {} - + board_runners = {} if not twister_out_dir.exists(): - return board_devicetrees + return board_devicetrees, board_runners # Find all build_info.yml files in twister-out build_info_files = list(twister_out_dir.glob("*/**/build_info.yml")) @@ -137,6 +147,13 @@ def gather_board_devicetrees(twister_out_dir): if not edt_pickle_file: continue + runners_yaml_file = None + for runners_yaml_path in RUNNERS_YAML_PATHS: + maybe_file = build_info_file.parent / runners_yaml_path + if maybe_file.exists(): + runners_yaml_file = maybe_file + break + try: with open(build_info_file) as f: build_info = yaml.safe_load(f) @@ -155,10 +172,17 @@ def gather_board_devicetrees(twister_out_dir): edt = pickle.load(f) board_devicetrees.setdefault(board_name, {})[board_target] = edt + if runners_yaml_file: + with open(runners_yaml_file) as f: + runners_yaml = yaml.safe_load(f) + board_runners.setdefault(board_name, {})[board_target] = ( + runners_yaml + ) + except Exception as e: logger.error(f"Error processing build info file {build_info_file}: {e}") - return board_devicetrees + return board_devicetrees, board_runners def run_twister_cmake_only(outdir): @@ -174,6 +198,7 @@ def run_twister_cmake_only(outdir): "--all", "-M", *[arg for path in EDT_PICKLE_PATHS for arg in ('--keep-artifacts', path)], + *[arg for path in RUNNERS_YAML_PATHS for arg in ('--keep-artifacts', path)], "--cmake-only", "--outdir", str(outdir), ] @@ -226,12 +251,13 @@ def get_catalog(generate_hw_features=False): systems = list_hardware.find_v2_systems(args_find_boards) board_catalog = {} board_devicetrees = {} + board_runners = {} 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)) + board_devicetrees, board_runners = gather_board_build_info(Path(tmp_dir)) else: logger.info("Skipping generation of supported hardware features.") @@ -314,6 +340,15 @@ def get_catalog(generate_hw_features=False): # Store features for this specific target supported_features[board_target] = features + board_runner_info = {} + if board.name in board_runners: + # Assume all board targets have the same runners so only consider the runners + # for the first board target. + r = list(board_runners[board.name].values())[0] + board_runner_info["runners"] = r.get("runners") + board_runner_info["flash-runner"] = r.get("flash-runner") + board_runner_info["debug-runner"] = r.get("debug-runner") + # Grab all the twister files for this board and use them to figure out all the archs it # supports. archs = set() @@ -336,6 +371,10 @@ def get_catalog(generate_hw_features=False): "revision_default": board.revision_default, "supported_features": supported_features, "image": guess_image(board), + # runners + "supported_runners": board_runner_info.get("runners", []), + "flash_runner": board_runner_info.get("flash-runner", ""), + "debug_runner": board_runner_info.get("debug-runner", ""), } socs_hierarchy = {} @@ -344,8 +383,16 @@ def get_catalog(generate_hw_features=False): series = soc.series or "" socs_hierarchy.setdefault(family, {}).setdefault(series, []).append(soc.name) + available_runners = {} + for runner in ZephyrBinaryRunner.get_runners(): + available_runners[runner.name()] = { + "name": runner.name(), + "commands": runner.capabilities().commands, + } + return { "boards": board_catalog, "vendors": {**vnd_lookup.vnd2vendor, "others": "Other/Unknown"}, "socs": socs_hierarchy, + "runners": available_runners, } diff --git a/doc/contribute/documentation/guidelines.rst b/doc/contribute/documentation/guidelines.rst index 3ec1104f249..2dbb46e141f 100644 --- a/doc/contribute/documentation/guidelines.rst +++ b/doc/contribute/documentation/guidelines.rst @@ -1266,6 +1266,21 @@ Boards (``zephyr_generate_hw_features`` config option set to ``True``). If disabled, a warning message will be shown instead of the hardware features tables. +.. rst:directive:: .. zephyr:board-supported-runners:: + + This directive is used to show the supported runners for the board documented in the current + page, including which runner is the default for flashing and debugging. + + The directive must be used in a document that also contains a :rst:dir:`zephyr:board` directive, + as it relies on the board information to generate the table. + + .. note:: + + Similar to :rst:dir:`zephyr:board-supported-hw`, this directive requires hardware features + generation to be enabled (``zephyr_generate_hw_features`` config option set to ``True``) to + produce a complete table. If disabled, a warning message will be shown instead of the runners + tables. + References ********** diff --git a/doc/develop/flash_debug/host-tools.rst b/doc/develop/flash_debug/host-tools.rst index 3361908d83d..dfe04626946 100644 --- a/doc/develop/flash_debug/host-tools.rst +++ b/doc/develop/flash_debug/host-tools.rst @@ -13,6 +13,7 @@ file declares that support properly. See :ref:`west-build-flash-debug` for more information on these commands. .. _atmel_sam_ba_bootloader: +.. _runner_bossac: SAM Boot Assistant (SAM-BA) *************************** @@ -207,6 +208,7 @@ Windows PATH. A specific bossac executable can be used by passing the .. _linkserver-debug-host-tools: +.. _runner_linkserver: LinkServer Debug Host Tools **************************** @@ -282,6 +284,7 @@ LinkServer west runner ``--probe`` option to pass the probe index. will need to add a breakpoint at ``main`` or the reset handler manually. .. _jlink-debug-host-tools: +.. _runner_jlink: J-Link Debug Host Tools *********************** @@ -312,6 +315,7 @@ required. Note that the J-Link GDB server does not yet support Zephyr RTOS-awareness. .. _openocd-debug-host-tools: +.. _runner_openocd: OpenOCD Debug Host Tools ************************ @@ -339,6 +343,7 @@ Check if your SoC is listed in `OpenOCD Supported Devices`_. - Add ``C:\Program Files\OpenOCD\bin`` to 'PATH' environment variable .. _pyocd-debug-host-tools: +.. _runner_pyocd: pyOCD Debug Host Tools ********************** @@ -358,6 +363,7 @@ These debug host tools are compatible with the following debug probes: Check if your SoC is listed in `pyOCD Supported Devices`_. .. _lauterbach-trace32-debug-host-tools: +.. _runner_trace32: Lauterbach TRACE32 Debug Host Tools *********************************** @@ -409,6 +415,7 @@ To enable Zephyr RTOS awareness follow the steps described in `Lauterbach TRACE32 Zephyr OS Awareness Manual`_. .. _nxp-s32-debug-host-tools: +.. _runner_nxp_s32dbg: NXP S32 Debug Probe Host Tools ****************************** @@ -471,6 +478,8 @@ afterwards detach the debug session: west debug --tool-opt='--batch' +.. _runner_probe_rs: + probe-rs Debug Host Tools ************************* @@ -482,7 +491,7 @@ Check `probe-rs Installation`_ for more setup details. Check if your SoC is listed in `probe-rs Supported Devices`_. -.. _stm32cubeprog-flash-host-tools: +.. _runner_rfp: Renesas Flash Programmer (RFP) Host Tools ***************************************** @@ -503,6 +512,9 @@ to ``rfp-cli`` when flashing: west flash --rfp-cli ~/Downloads/RFP_CLI_Linux_V31800_x64/linux-x64/rfp-cli +.. _stm32cubeprog-flash-host-tools: +.. _runner_stm32cubeprogrammer: + STM32CubeProgrammer Flash Host Tools ************************************ diff --git a/doc/templates/board.tmpl b/doc/templates/board.tmpl index d17c936a67a..38aa0fafa1d 100644 --- a/doc/templates/board.tmpl +++ b/doc/templates/board.tmpl @@ -29,6 +29,8 @@ to connect external components] Programming and Debugging ************************* +.. zephyr:board-supported-runners:: + Flashing ======== [How to use this board with Zephyr and how to flash a Zephyr binary on this