mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-07 20:17:37 +00:00
72383f27e2
terminal.py had an ambiguous |import logging| that was importing mach.logging from Sphinx. We fix it. There was also a poorly formed link in the mach commands documentation. We fix it. --HG-- extra : rebase_source : 12783c69027989ac031d29e4ecbc1ee2f465ffa4 extra : histedit_source : 4283c6cdecc4de8aa7636d0c4cc566daf5142b50
146 lines
5.1 KiB
ReStructuredText
146 lines
5.1 KiB
ReStructuredText
.. _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.
|