Skip to content

scripts: west_commands: Support out-of-tree runners #83190

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions doc/develop/modules.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1050,6 +1050,25 @@ requirement files in the ``scripts`` directory of the module.
- scripts/requirements-build.txt
- scripts/requirements-doc.txt


.. _modules-runners:

External Runners
================

If a module has out of tree boards that require custom :ref:`runners <west-runner>`,
then it can add a list to its ``zephyr/module.yml`` file, for example:


.. code-block:: yaml

runners:
- file: scripts/my-runner.py


Each file entry is imported when executing ``west flash`` or ``west debug`` and
subclasses of the ``ZephyrBinaryRunner`` are registered for use.

Module Inclusion
================

Expand Down
18 changes: 13 additions & 5 deletions doc/develop/west/build-flash-debug.rst
Original file line number Diff line number Diff line change
Expand Up @@ -779,18 +779,26 @@ To view all available options Renode supports, use::

west simulate --runner=renode --renode-help

Out of tree runners
*******************

:ref:`Zephyr modules <modules>` can have external runners discovered by adding python
files in their :ref:`module.yml <modules-runners>`. Create an external runner class by
inheriting from ``ZephyrBinaryRunner`` and implement all abstract methods.

.. note::

Support for custom out-of-tree runners makes the ``runners.core`` module part of
the public API and backwards incompatible changes need to undergo the
:ref:`deprecation process <breaking_api_changes>`.

Hacking
*******

This section documents the ``runners.core`` module used by the
flash and debug commands. This is the core abstraction used to implement
support for these features.

.. warning::

These APIs are provided for reference, but they are more "shared code" used
to implement multiple extension commands than a stable API.

Developers can add support for new ways to flash and debug Zephyr programs by
implementing additional runners. To get this support into upstream Zephyr, the
runner should be added into a new or existing ``runners`` module, and imported
Expand Down
19 changes: 18 additions & 1 deletion scripts/west_commands/run_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
'''Common code used by commands which execute runners.
'''

import importlib.util
import re
import argparse
import logging
Expand All @@ -28,7 +29,8 @@
from runners.core import BuildConfiguration
import yaml

from zephyr_ext_common import ZEPHYR_SCRIPTS
import zephyr_module
from zephyr_ext_common import ZEPHYR_BASE, ZEPHYR_SCRIPTS

# Runners depend on edtlib. Make sure the copy in the tree is
# available to them before trying to import any.
Expand Down Expand Up @@ -107,6 +109,13 @@ class SocBoardFilesProcessing:
priority: int = IGNORED_RUN_ONCE_PRIORITY
yaml: object = None

def import_from_path(module_name, file_path):
spec = importlib.util.spec_from_file_location(module_name, file_path)
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)
return module

def command_verb(command):
return "flash" if command.name == "flash" else "debug"

Expand Down Expand Up @@ -197,6 +206,14 @@ def do_run_common(command, user_args, user_runner_args, domain_file=None):
dump_context(command, user_args, user_runner_args)
return

# Import external module runners
for module in zephyr_module.parse_modules(ZEPHYR_BASE, command.manifest):
runners_ext = module.meta.get("runners", [])
for runner in runners_ext:
import_from_path(
module.meta.get("name", "runners_ext"), Path(module.project) / runner["file"]
)

build_dir = get_build_dir(user_args)
if not user_args.skip_rebuild:
rebuild(command, build_dir, user_args)
Expand Down
9 changes: 9 additions & 0 deletions scripts/zephyr_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,15 @@
type: seq
sequence:
- type: str
runners:
required: false
type: seq
sequence:
- type: map
mapping:
file:
required: true
type: str
'''

MODULE_YML_PATH = PurePath('zephyr/module.yml')
Expand Down
Loading