249 lines
9.3 KiB
ReStructuredText
249 lines
9.3 KiB
ReStructuredText
.. _west-extensions:
|
|
|
|
Extensions
|
|
##########
|
|
|
|
West is "pluggable": you can add your own commands to west without editing its
|
|
source code. These are called **west extension commands**, or just "extensions"
|
|
for short. Extensions show up in the ``west --help`` output in a special
|
|
section for the project which defines them. This page provides general
|
|
information on west extension commands, and has a tutorial for writing your
|
|
own.
|
|
|
|
Some commands you can run when using west with Zephyr, like the ones used to
|
|
:ref:`build, flash, and debug <west-build-flash-debug>` and the
|
|
:ref:`ones described here <west-zephyr-ext-cmds>` , are extensions. That's why
|
|
help for them shows up like this in ``west --help``:
|
|
|
|
.. code-block:: none
|
|
|
|
commands from project at "zephyr":
|
|
completion: display shell completion scripts
|
|
boards: display information about supported boards
|
|
build: compile a Zephyr application
|
|
sign: sign a Zephyr binary for bootloader chain-loading
|
|
flash: flash and run a binary on a board
|
|
debug: flash and interactively debug a Zephyr application
|
|
debugserver: connect to board and launch a debug server
|
|
attach: interactively debug a board
|
|
|
|
See :file:`zephyr/scripts/west-commands.yml` and the
|
|
:file:`zephyr/scripts/west_commands` directory for the implementation details.
|
|
|
|
Disabling Extension Commands
|
|
****************************
|
|
|
|
To disable support for extension commands, set the ``commands.allow_extensions``
|
|
:ref:`configuration <west-config>` option to ``false``. To set this
|
|
globally for whenever you run west, use:
|
|
|
|
.. code-block:: console
|
|
|
|
west config --global commands.allow_extensions false
|
|
|
|
If you want to, you can then re-enable them in a particular :term:`west
|
|
workspace` with:
|
|
|
|
.. code-block:: console
|
|
|
|
west config --local commands.allow_extensions true
|
|
|
|
Note that the files containing extension commands are not imported by west
|
|
unless the commands are explicitly run. See below for details.
|
|
|
|
Adding a West Extension
|
|
***********************
|
|
|
|
There are three steps to adding your own extension:
|
|
|
|
#. Write the code implementing the command.
|
|
#. Add information about it to a :file:`west-commands.yml` file.
|
|
#. Make sure the :file:`west-commands.yml` file is referenced in the
|
|
:term:`west manifest`.
|
|
|
|
Note that west ignores extension commands whose names are the same as a
|
|
built-in command.
|
|
|
|
Step 1: Implement Your Command
|
|
==============================
|
|
|
|
Create a Python file to contain your command implementation (see the "Meta >
|
|
Requires" information on the `west PyPI page`_ for details on the currently
|
|
supported versions of Python). You can put it in anywhere in any project
|
|
tracked by your :term:`west manifest`, or the manifest repository itself.
|
|
This file must contain a subclass of the ``west.commands.WestCommand`` class;
|
|
this class will be instantiated and used when your extension is run.
|
|
|
|
Here is a basic skeleton you can use to get started. It contains a subclass of
|
|
``WestCommand``, with implementations for all the abstract methods. For more
|
|
details on the west APIs you can use, see :ref:`west-apis`.
|
|
|
|
.. code-block:: py
|
|
|
|
'''my_west_extension.py
|
|
|
|
Basic example of a west extension.'''
|
|
|
|
from textwrap import dedent # just for nicer code indentation
|
|
|
|
from west.commands import WestCommand # your extension must subclass this
|
|
from west import log # use this for user output
|
|
|
|
class MyCommand(WestCommand):
|
|
|
|
def __init__(self):
|
|
super().__init__(
|
|
'my-command-name', # gets stored as self.name
|
|
'one-line help for what my-command-name does', # self.help
|
|
# self.description:
|
|
dedent('''
|
|
A multi-line description of my-command.
|
|
|
|
You can split this up into multiple paragraphs and they'll get
|
|
reflowed for you. You can also pass
|
|
formatter_class=argparse.RawDescriptionHelpFormatter when calling
|
|
parser_adder.add_parser() below if you want to keep your line
|
|
endings.'''))
|
|
|
|
def do_add_parser(self, parser_adder):
|
|
# This is a bit of boilerplate, which allows you full control over the
|
|
# type of argparse handling you want. The "parser_adder" argument is
|
|
# the return value of an argparse.ArgumentParser.add_subparsers() call.
|
|
parser = parser_adder.add_parser(self.name,
|
|
help=self.help,
|
|
description=self.description)
|
|
|
|
# Add some example options using the standard argparse module API.
|
|
parser.add_argument('-o', '--optional', help='an optional argument')
|
|
parser.add_argument('required', help='a required argument')
|
|
|
|
return parser # gets stored as self.parser
|
|
|
|
def do_run(self, args, unknown_args):
|
|
# This gets called when the user runs the command, e.g.:
|
|
#
|
|
# $ west my-command-name -o FOO BAR
|
|
# --optional is FOO
|
|
# required is BAR
|
|
log.inf('--optional is', args.optional)
|
|
log.inf('required is', args.required)
|
|
|
|
You can ignore the second argument to ``do_run()`` (``unknown_args`` above), as
|
|
``WestCommand`` will reject unknown arguments by default. If you want to be
|
|
passed a list of unknown arguments instead, add ``accepts_unknown_args=True``
|
|
to the ``super().__init__()`` arguments.
|
|
|
|
Step 2: Add or Update Your :file:`west-commands.yml`
|
|
====================================================
|
|
|
|
You now need to add a :file:`west-commands.yml` file to your project which
|
|
describes your extension to west.
|
|
|
|
Here is an example for the above class definition, assuming it's in
|
|
:file:`my_west_extension.py` at the project root directory:
|
|
|
|
.. code-block:: yaml
|
|
|
|
west-commands:
|
|
- file: my_west_extension.py
|
|
commands:
|
|
- name: my-command-name
|
|
class: MyCommand
|
|
help: one-line help for what my-command-name does
|
|
|
|
The top level of this YAML file is a map with a ``west-commands`` key. The
|
|
key's value is a sequence of "command descriptors". Each command descriptor
|
|
gives the location of a file implementing west extensions, along with the names
|
|
of those extensions, and optionally the names of the classes which define them
|
|
(if not given, the ``class`` value defaults to the same thing as ``name``).
|
|
|
|
Some information in this file is redundant with definitions in the Python code.
|
|
This is because west won't import :file:`my_west_extension.py` until the user
|
|
runs ``west my-command-name``, since:
|
|
|
|
- It allows users to run ``west update`` with a manifest from an untrusted
|
|
source, then use other west commands without your code being imported along
|
|
the way. Since importing a Python module is shell-equivalent, this provides
|
|
some peace of mind.
|
|
|
|
- It's a small optimization, since your code will only be imported if it is
|
|
needed.
|
|
|
|
So, unless your command is explicitly run, west will just load the
|
|
:file:`west-commands.yml` file to get the basic information it needs to display
|
|
information about your extension to the user in ``west --help`` output, etc.
|
|
|
|
If you have multiple extensions, or want to split your extensions across
|
|
multiple files, your :file:`west-commands.yml` will look something like this:
|
|
|
|
.. code-block:: yaml
|
|
|
|
west-commands:
|
|
- file: my_west_extension.py
|
|
commands:
|
|
- name: my-command-name
|
|
class: MyCommand
|
|
help: one-line help for what my-command-name does
|
|
- file: another_file.py
|
|
commands:
|
|
- name: command2
|
|
help: another cool west extension
|
|
- name: a-third-command
|
|
class: ThirdCommand
|
|
help: a third command in the same file as command2
|
|
|
|
Above:
|
|
|
|
- :file:`my_west_extension.py` defines extension ``my-command-name``
|
|
with class ``MyCommand``
|
|
- :file:`another_file.py` defines two extensions:
|
|
|
|
#. ``command2`` with class ``command2``
|
|
#. ``a-third-command`` with class ``ThirdCommand``
|
|
|
|
See the file :file:`west-commands-schema.yml` in the `west repository`_ for a
|
|
schema describing the contents of a :file:`west-commands.yml`.
|
|
|
|
Step 3: Update Your Manifest
|
|
============================
|
|
|
|
Finally, you need to specify the location of the :file:`west-commands.yml` you
|
|
just edited in your west manifest. If your extension is in a project, add it
|
|
like this:
|
|
|
|
.. code-block:: yaml
|
|
|
|
manifest:
|
|
# [... other contents ...]
|
|
|
|
projects:
|
|
- name: your-project
|
|
west-commands: path/to/west-commands.yml
|
|
# [... other projects ...]
|
|
|
|
Where :file:`path/to/west-commands.yml` is relative to the root of the project.
|
|
Note that the name :file:`west-commands.yml`, while encouraged, is just a
|
|
convention; you can name the file something else if you need to.
|
|
|
|
Alternatively, if your extension is in the manifest repository, just do the
|
|
same thing in the manifest's ``self`` section, like this:
|
|
|
|
.. code-block:: yaml
|
|
|
|
manifest:
|
|
# [... other contents ...]
|
|
|
|
self:
|
|
west-commands: path/to/west-commands.yml
|
|
|
|
That's it; you can now run ``west my-command-name``. Your command's name, help,
|
|
and the project which contains its code will now also show up in the ``west
|
|
--help`` output. If you share the updated repositories with others, they'll be
|
|
able to use it, too.
|
|
|
|
.. _west PyPI page:
|
|
https://pypi.org/project/west/
|
|
|
|
.. _west repository:
|
|
https://github.com/zephyrproject-rtos/west/
|