1.. _west-extensions: 2 3Extensions 4########## 5 6West is "pluggable": you can add your own commands to west without editing its 7source code. These are called **west extension commands**, or just "extensions" 8for short. Extensions show up in the ``west --help`` output in a special 9section for the project which defines them. This page provides general 10information on west extension commands, and has a tutorial for writing your 11own. 12 13Some commands you can run when using west with Zephyr, like the ones used to 14:ref:`build, flash, and debug <west-build-flash-debug>` and the 15:ref:`ones described here <west-zephyr-ext-cmds>` , are extensions. That's why 16help for them shows up like this in ``west --help``: 17 18.. code-block:: none 19 20 commands from project at "zephyr": 21 completion: display shell completion scripts 22 boards: display information about supported boards 23 build: compile a Zephyr application 24 sign: sign a Zephyr binary for bootloader chain-loading 25 flash: flash and run a binary on a board 26 debug: flash and interactively debug a Zephyr application 27 debugserver: connect to board and launch a debug server 28 attach: interactively debug a board 29 30See :file:`zephyr/scripts/west-commands.yml` and the 31:file:`zephyr/scripts/west_commands` directory for the implementation details. 32 33Disabling Extension Commands 34**************************** 35 36To disable support for extension commands, set the ``commands.allow_extensions`` 37:ref:`configuration <west-config>` option to ``false``. To set this 38globally for whenever you run west, use: 39 40.. code-block:: console 41 42 west config --global commands.allow_extensions false 43 44If you want to, you can then re-enable them in a particular :term:`west 45workspace` with: 46 47.. code-block:: console 48 49 west config --local commands.allow_extensions true 50 51Note that the files containing extension commands are not imported by west 52unless the commands are explicitly run. See below for details. 53 54Adding a West Extension 55*********************** 56 57There are three steps to adding your own extension: 58 59#. Write the code implementing the command. 60#. Add information about it to a :file:`west-commands.yml` file. 61#. Make sure the :file:`west-commands.yml` file is referenced in the 62 :term:`west manifest`. 63 64Note that west ignores extension commands whose names are the same as a 65built-in command. 66 67Step 1: Implement Your Command 68============================== 69 70Create a Python file to contain your command implementation (see the "Meta > 71Requires" information on the `west PyPI page`_ for details on the currently 72supported versions of Python). You can put it in anywhere in any project 73tracked by your :term:`west manifest`, or the manifest repository itself. 74This file must contain a subclass of the ``west.commands.WestCommand`` class; 75this class will be instantiated and used when your extension is run. 76 77Here is a basic skeleton you can use to get started. It contains a subclass of 78``WestCommand``, with implementations for all the abstract methods. For more 79details on the west APIs you can use, see :ref:`west-apis`. 80 81.. code-block:: py 82 83 '''my_west_extension.py 84 85 Basic example of a west extension.''' 86 87 from textwrap import dedent # just for nicer code indentation 88 89 from west.commands import WestCommand # your extension must subclass this 90 91 class MyCommand(WestCommand): 92 93 def __init__(self): 94 super().__init__( 95 'my-command-name', # gets stored as self.name 96 'one-line help for what my-command-name does', # self.help 97 # self.description: 98 dedent(''' 99 A multi-line description of my-command. 100 101 You can split this up into multiple paragraphs and they'll get 102 reflowed for you. You can also pass 103 formatter_class=argparse.RawDescriptionHelpFormatter when calling 104 parser_adder.add_parser() below if you want to keep your line 105 endings.''')) 106 107 def do_add_parser(self, parser_adder): 108 # This is a bit of boilerplate, which allows you full control over the 109 # type of argparse handling you want. The "parser_adder" argument is 110 # the return value of an argparse.ArgumentParser.add_subparsers() call. 111 parser = parser_adder.add_parser(self.name, 112 help=self.help, 113 description=self.description) 114 115 # Add some example options using the standard argparse module API. 116 parser.add_argument('-o', '--optional', help='an optional argument') 117 parser.add_argument('required', help='a required argument') 118 119 return parser # gets stored as self.parser 120 121 def do_run(self, args, unknown_args): 122 # This gets called when the user runs the command, e.g.: 123 # 124 # $ west my-command-name -o FOO BAR 125 # --optional is FOO 126 # required is BAR 127 self.inf('--optional is', args.optional) 128 self.inf('required is', args.required) 129 130You can ignore the second argument to ``do_run()`` (``unknown_args`` above), as 131``WestCommand`` will reject unknown arguments by default. If you want to be 132passed a list of unknown arguments instead, add ``accepts_unknown_args=True`` 133to the ``super().__init__()`` arguments. 134 135Step 2: Add or Update Your :file:`west-commands.yml` 136==================================================== 137 138You now need to add a :file:`west-commands.yml` file to your project which 139describes your extension to west. 140 141Here is an example for the above class definition, assuming it's in 142:file:`my_west_extension.py` at the project root directory: 143 144.. code-block:: yaml 145 146 west-commands: 147 - file: my_west_extension.py 148 commands: 149 - name: my-command-name 150 class: MyCommand 151 help: one-line help for what my-command-name does 152 153The top level of this YAML file is a map with a ``west-commands`` key. The 154key's value is a sequence of "command descriptors". Each command descriptor 155gives the location of a file implementing west extensions, along with the names 156of those extensions, and optionally the names of the classes which define them 157(if not given, the ``class`` value defaults to the same thing as ``name``). 158 159Some information in this file is redundant with definitions in the Python code. 160This is because west won't import :file:`my_west_extension.py` until the user 161runs ``west my-command-name``, since: 162 163- It allows users to run ``west update`` with a manifest from an untrusted 164 source, then use other west commands without your code being imported along 165 the way. Since importing a Python module is shell-equivalent, this provides 166 some peace of mind. 167 168- It's a small optimization, since your code will only be imported if it is 169 needed. 170 171So, unless your command is explicitly run, west will just load the 172:file:`west-commands.yml` file to get the basic information it needs to display 173information about your extension to the user in ``west --help`` output, etc. 174 175If you have multiple extensions, or want to split your extensions across 176multiple files, your :file:`west-commands.yml` will look something like this: 177 178.. code-block:: yaml 179 180 west-commands: 181 - file: my_west_extension.py 182 commands: 183 - name: my-command-name 184 class: MyCommand 185 help: one-line help for what my-command-name does 186 - file: another_file.py 187 commands: 188 - name: command2 189 help: another cool west extension 190 - name: a-third-command 191 class: ThirdCommand 192 help: a third command in the same file as command2 193 194Above: 195 196- :file:`my_west_extension.py` defines extension ``my-command-name`` 197 with class ``MyCommand`` 198- :file:`another_file.py` defines two extensions: 199 200 #. ``command2`` with class ``command2`` 201 #. ``a-third-command`` with class ``ThirdCommand`` 202 203See the file :file:`west-commands-schema.yml` in the `west repository`_ for a 204schema describing the contents of a :file:`west-commands.yml`. 205 206Step 3: Update Your Manifest 207============================ 208 209Finally, you need to specify the location of the :file:`west-commands.yml` you 210just edited in your west manifest. If your extension is in a project, add it 211like this: 212 213.. code-block:: yaml 214 215 manifest: 216 # [... other contents ...] 217 218 projects: 219 - name: your-project 220 west-commands: path/to/west-commands.yml 221 # [... other projects ...] 222 223Where :file:`path/to/west-commands.yml` is relative to the root of the project. 224Note that the name :file:`west-commands.yml`, while encouraged, is just a 225convention; you can name the file something else if you need to. 226 227Alternatively, if your extension is in the manifest repository, just do the 228same thing in the manifest's ``self`` section, like this: 229 230.. code-block:: yaml 231 232 manifest: 233 # [... other contents ...] 234 235 self: 236 west-commands: path/to/west-commands.yml 237 238That's it; you can now run ``west my-command-name``. Your command's name, help, 239and the project which contains its code will now also show up in the ``west 240--help`` output. If you share the updated repositories with others, they'll be 241able to use it, too. 242 243.. _west PyPI page: 244 https://pypi.org/project/west/ 245 246.. _west repository: 247 https://github.com/zephyrproject-rtos/west/ 248