develop/west/extensions.html · develop/west/extensions.rst 공식 원문 ↗

Zephyr 3.7.0 · Develop · West · Extensions

West extension command

West core를 수정하지 않고 Python command를 추가하고, descriptor와 manifest를 통해 안전하게 발견·실행하게 합니다.

Official pathdevelop/west/extensions.html
Source filedevelop/west/extensions.rst
Source versionZephyr 3.7.0
TranslationFull · reviewed

본문 기준: Zephyr 3.7.0 LTS3 · 이후 유지보수 패치는 별도입니다.

Part 1

요약·해설

Extension command의 발견 방식과 비활성화 설정, WestCommand subclass 구현, west-commands.yml descriptor, 지연 import의 보안 경계, manifest project·self 등록을 설명합니다.

Implement

WestCommand · argparse · do_run

Describe

west-commands.yml

Register

manifest project · self

Part 2

접을 수 있는 영어 원문 전체

영어 원문 전체 펼치기
원문 SHA-256 A5C2C1A4A6CA6162A942B6E7D157A1154ED9CDB697BD78EE0B44091B76D02152
.. _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/

Part 3

한국어 전문 번역

Extension

West는 plug-in 방식으로 확장할 수 있습니다. West source code를 수정하지 않고도 자신의 명령을 추가할 수 있으며, 이를 west extension command 또는 줄여서 extension이라고 합니다.

Extension은 west --help 출력에서 해당 명령을 정의한 project 전용 section에 나타납니다. 이 페이지는 west extension command의 일반 원리와 직접 작성하는 방법을 설명합니다.

Zephyr에서 west로 실행하는 build·flash·debug 명령과 Zephyr extension command도 extension입니다. 따라서 west --help에는 다음과 같이 표시됩니다.

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

구현 세부 사항은 zephyr/scripts/west-commands.yml과 zephyr/scripts/west_commands 디렉터리에서 확인할 수 있습니다.

Extension command 비활성화

Extension command 지원을 끄려면 commands.allow_extensions 설정을 false로 지정합니다. West를 실행하는 모든 workspace에서 전역으로 끄려면:

west config --global commands.allow_extensions false

필요하다면 특정 west workspace에서만 다시 켤 수 있습니다.

west config --local commands.allow_extensions true

West extension 추가

자신의 extension을 추가하는 작업은 세 단계입니다.

  1. Command를 구현하는 code를 작성합니다.
  2. west-commands.yml file에 command 정보를 추가합니다.
  3. west-commands.yml file이 west manifest에서 참조되게 합니다.

내장 명령과 이름이 같은 extension command는 west가 무시합니다.

1단계: command 구현

Command 구현을 담을 Python file을 만듭니다. 현재 지원하는 Python version은 west PyPI의 Meta > Requires에서 확인합니다.

File은 west manifest가 추적하는 어느 project에나 둘 수 있고 manifest repository 자체에 두어도 됩니다. 이 file에는 west.commands.WestCommand의 subclass가 반드시 있어야 하며, extension이 실행될 때 west가 이 class를 instantiate하여 사용합니다.

다음 skeleton은 WestCommand subclass와 모든 abstract method의 기본 구현을 포함합니다. 사용할 수 있는 API는 West API를 참고합니다.

'''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)

do_run()의 두 번째 argument인 unknown_args는 기본적으로 무시해도 됩니다. WestCommand가 알 수 없는 argument를 기본 거부하기 때문입니다.

알 수 없는 argument의 목록도 command가 직접 받으려면 super().__init__() argument에 accepts_unknown_args=True를 추가합니다.

2단계: west-commands.yml 추가 또는 갱신

이제 project에 extension을 설명하는 west-commands.yml file을 추가합니다.

앞의 class가 project root의 my_west_extension.py에 있다고 가정하면 다음과 같이 작성합니다.

west-commands:
  - file: my_west_extension.py
    commands:
      - name: my-command-name
        class: MyCommand
        help: one-line help for what my-command-name does

YAML 최상위는 west-commands key를 가진 map입니다. 이 key의 값은 command descriptor의 sequence입니다.

각 descriptor는 west extension을 구현한 file 위치와 extension 이름을 지정합니다. Command를 정의한 class 이름은 선택 사항이며 class를 생략하면 기본값은 name과 같습니다.

이 file의 일부 정보는 Python code의 정의와 중복됩니다. West가 사용자가 west my-command-name을 실행하기 전에는 my_west_extension.py를 import하지 않기 때문입니다.

지연 import에는 다음 두 이유가 있습니다.

  • 신뢰할 수 없는 source의 manifest로 west update를 실행한 뒤에도, 다른 west 명령을 쓰는 동안 그 source의 code가 저절로 import되지 않습니다. Python module import는 shell command 실행과 같은 수준의 권한을 가질 수 있으므로 안전성을 높입니다.
  • Extension code가 실제로 필요할 때만 import하므로 작은 성능 최적화가 됩니다.

따라서 command를 명시적으로 실행하지 않은 동안 west는 west-commands.yml만 읽고 west --help 등에 표시할 이름·도움말 같은 기본 정보만 얻습니다.

Extension이 여러 개이거나 여러 file로 나누고 싶다면 west-commands.yml을 다음처럼 작성합니다.

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

위 예의 정의 관계는 다음과 같습니다.

  • my_west_extension.py는 MyCommand class로 my-command-name extension을 정의합니다.
  • another_file.py는 두 extension을 정의합니다.
    1. command2는 class 이름도 기본값인 command2입니다.
    2. a-third-command는 ThirdCommand class를 사용합니다.

west-commands.yml 내용의 schema는 west repository의 west-commands-schema.yml에서 확인합니다.

3단계: manifest 갱신

마지막으로 편집한 west-commands.yml의 위치를 west manifest에 지정합니다.

Extension이 일반 project에 있다면 그 project entry에 다음처럼 추가합니다.

manifest:
   # [... other contents ...]

   projects:
     - name: your-project
       west-commands: path/to/west-commands.yml
     # [... other projects ...]

path/to/west-commands.yml은 project root를 기준으로 한 상대 path입니다. west-commands.yml이라는 이름은 권장 관례일 뿐이며 필요하면 다른 이름을 사용할 수 있습니다.

Extension이 manifest repository에 있다면 manifest의 self section에 같은 방식으로 지정합니다.

manifest:
  # [... other contents ...]

  self:
    west-commands: path/to/west-commands.yml

이제 west my-command-name을 실행할 수 있습니다. Command 이름, 도움말, code가 들어 있는 project도 west --help output에 표시됩니다. 갱신한 repository를 다른 사람과 공유하면 그들도 같은 extension을 사용할 수 있습니다.

Source

출처

영어 원문에는 문단, 문서 지시문, 표, 코드, 코드 심벌, 경로를 그대로 보존했습니다.