| .. _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/ |