123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146 |
- .. _mach_commands:
- =====================
- Implementing Commands
- =====================
- Mach commands are defined via Python decorators.
- All the relevant decorators are defined in the *mach.decorators* module.
- The important decorators are as follows:
- :py:func:`CommandProvider <mach.decorators.CommandProvider>`
- A class decorator that denotes that a class contains mach
- commands. The decorator takes no arguments.
- :py:func:`Command <mach.decorators.Command>`
- A method decorator that denotes that the method should be called when
- the specified command is requested. The decorator takes a command name
- as its first argument and a number of additional arguments to
- configure the behavior of the command.
- :py:func:`CommandArgument <mach.decorators.CommandArgument>`
- A method decorator that defines an argument to the command. Its
- arguments are essentially proxied to ArgumentParser.add_argument()
- :py:func:`SubCommand <mach.decorators.SubCommand>`
- A method decorator that denotes that the method should be a
- sub-command to an existing ``@Command``. The decorator takes the
- parent command name as its first argument and the sub-command name
- as its second argument.
- ``@CommandArgument`` can be used on ``@SubCommand`` instances just
- like they can on ``@Command`` instances.
- Classes with the ``@CommandProvider`` decorator **must** have an
- ``__init__`` method that accepts 1 or 2 arguments. If it accepts 2
- arguments, the 2nd argument will be a
- :py:class:`mach.base.CommandContext` instance.
- Here is a complete example:
- .. code-block:: python
- from mach.decorators import (
- CommandArgument,
- CommandProvider,
- Command,
- )
- @CommandProvider
- class MyClass(object):
- @Command('doit', help='Do ALL OF THE THINGS.')
- @CommandArgument('--force', '-f', action='store_true',
- help='Force doing it.')
- def doit(self, force=False):
- # Do stuff here.
- When the module is loaded, the decorators tell mach about all handlers.
- When mach runs, it takes the assembled metadata from these handlers and
- hooks it up to the command line driver. Under the hood, arguments passed
- to the decorators are being used to help mach parse command arguments,
- formulate arguments to the methods, etc. See the documentation in the
- :py:mod:`mach.base` module for more.
- The Python modules defining mach commands do not need to live inside the
- main mach source tree.
- Conditionally Filtering Commands
- ================================
- Sometimes it might only make sense to run a command given a certain
- context. For example, running tests only makes sense if the product
- they are testing has been built, and said build is available. To make
- sure a command is only runnable from within a correct context, you can
- define a series of conditions on the
- :py:func:`Command <mach.decorators.Command>` decorator.
- A condition is simply a function that takes an instance of the
- :py:func:`mach.decorators.CommandProvider` class as an argument, and
- returns ``True`` or ``False``. If any of the conditions defined on a
- command return ``False``, the command will not be runnable. The
- docstring of a condition function is used in error messages, to explain
- why the command cannot currently be run.
- Here is an example:
- .. code-block:: python
- from mach.decorators import (
- CommandProvider,
- Command,
- )
- def build_available(cls):
- """The build needs to be available."""
- return cls.build_path is not None
- @CommandProvider
- class MyClass(MachCommandBase):
- def __init__(self, build_path=None):
- self.build_path = build_path
- @Command('run_tests', conditions=[build_available])
- def run_tests(self):
- # Do stuff here.
- It is important to make sure that any state needed by the condition is
- available to instances of the command provider.
- By default all commands without any conditions applied will be runnable,
- but it is possible to change this behaviour by setting
- ``require_conditions`` to ``True``:
- .. code-block:: python
- m = mach.main.Mach()
- m.require_conditions = True
- Minimizing Code in Commands
- ===========================
- Mach command modules, classes, and methods work best when they are
- minimal dispatchers. The reason is import bloat. Currently, the mach
- core needs to import every Python file potentially containing mach
- commands for every command invocation. If you have dozens of commands or
- commands in modules that import a lot of Python code, these imports
- could slow mach down and waste memory.
- It is thus recommended that mach modules, classes, and methods do as
- little work as possible. Ideally the module should only import from
- the :py:mod:`mach` package. If you need external modules, you should
- import them from within the command method.
- To keep code size small, the body of a command method should be limited
- to:
- 1. Obtaining user input (parsing arguments, prompting, etc)
- 2. Calling into some other Python package
- 3. Formatting output
- Of course, these recommendations can be ignored if you want to risk
- slower performance.
- In the future, the mach driver may cache the dispatching information or
- have it intelligently loaded to facilitate lazy loading.
|