tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commands.rst (5504B)


      1 .. _mach_commands:
      2 
      3 =====================
      4 Implementing Commands
      5 =====================
      6 
      7 Mach commands are defined via Python decorators.
      8 
      9 All the relevant decorators are defined in the *mach.decorators* module.
     10 The important decorators are as follows:
     11 
     12 :py:func:`Command <mach.decorators.Command>`
     13  A function decorator that denotes that the function should be called when
     14  the specified command is requested. The decorator takes a command name
     15  as its first argument and a number of additional arguments to
     16  configure the behavior of the command. The decorated function must take a
     17  ``command_context`` argument as its first.
     18  ``command_context`` is a properly configured instance of a ``MozbuildObject``
     19  subclass, meaning it can be used for accessing things like the current config
     20  and running processes.
     21 
     22 :py:func:`CommandArgument <mach.decorators.CommandArgument>`
     23  A function decorator that defines an argument to the command. Its
     24  arguments are essentially proxied to ArgumentParser.add_argument()
     25 
     26 :py:func:`SubCommand <mach.decorators.SubCommand>`
     27  A function decorator that denotes that the function should be a
     28  sub-command to an existing ``@Command``. The decorator takes the
     29  parent command name as its first argument and the sub-command name
     30  as its second argument.
     31 
     32  ``@CommandArgument`` can be used on ``@SubCommand`` instances just
     33  like they can on ``@Command`` instances.
     34 
     35 
     36 Here is a complete example:
     37 
     38 .. rstcheck: ignore-languages=python
     39 .. code-block:: python
     40 
     41   from mach.decorators import Command, CommandArgument
     42 
     43    @Command('doit', category='testing', description='Do ALL OF THE THINGS.')
     44    @CommandArgument('--force', '-f', action='store_true',
     45        help='Force doing it.')
     46    def doit(command_context, force=False):
     47        # Do stuff here.
     48        print("hello world")
     49 
     50 All paths are relative to the root source folder.
     51 
     52 Save your file somewhere e.g. ``testing/doit.py``.
     53 
     54 Add an entry for your command in ``python/mach/mach/command_util.py`` in
     55 the `MACH_COMMANDS` dictionary. Maintain the alphabetical order.
     56 
     57 e.g
     58 
     59 .. code-block:: python
     60 
     61    MACH_COMMANDS = {
     62        # ... previous entries here
     63        "doctor": MachCommandReference("python/mozbuild/mozbuild/mach_commands.py"),
     64        "doit": MachCommandReference("testing/doit.py"),
     65        "environment": MachCommandReference("python/mozbuild/mozbuild/mach_commands.py"),
     66        # ... rest of entries here
     67    }
     68 
     69 When the module is loaded, the decorators tell mach about all handlers.
     70 When mach runs, it takes the assembled metadata from these handlers and
     71 hooks it up to the command line driver. Under the hood, arguments passed
     72 to the decorators are being used to help mach parse command arguments,
     73 formulate arguments to the methods, etc. See the documentation in the
     74 :py:mod:`mach.base` module for more.
     75 
     76 The Python modules defining mach commands do not need to live inside the
     77 main mach source tree.
     78 
     79 Conditionally Filtering Commands
     80 ================================
     81 
     82 Sometimes it might only make sense to run a command given a certain
     83 context. For example, running tests only makes sense if the product
     84 they are testing has been built, and said build is available. To make
     85 sure a command is only runnable from within a correct context, you can
     86 define a series of conditions on the
     87 :py:func:`Command <mach.decorators.Command>` decorator.
     88 
     89 A condition is simply a function that takes an instance of the
     90 :py:func:`mozbuild.base.MachCommandBase` class as an argument, and
     91 returns ``True`` or ``False``. If any of the conditions defined on a
     92 command return ``False``, the command will not be runnable. The
     93 docstring of a condition function is used in error messages, to explain
     94 why the command cannot currently be run.
     95 
     96 Here is an example:
     97 
     98 .. rstcheck: ignore-languages=python
     99 .. code-block:: python
    100 
    101  from mach.decorators import (
    102      Command,
    103  )
    104 
    105  def build_available(cls):
    106      """The build needs to be available."""
    107      return cls.build_path is not None
    108 
    109   @Command('run_tests', category='testing', description='A description.' conditions=[build_available])
    110   def run_tests(command_context):
    111       # Do stuff here.
    112 
    113 By default all commands without any conditions applied will be runnable,
    114 but it is possible to change this behaviour by setting
    115 ``require_conditions`` to ``True``:
    116 
    117 .. code-block:: python
    118 
    119   m = mach.main.Mach()
    120   m.require_conditions = True
    121 
    122 Minimizing Code in Commands
    123 ===========================
    124 
    125 Mach command modules, classes, and methods work best when they are
    126 minimal dispatchers. The reason is import bloat. Currently, the mach
    127 core needs to import every Python file potentially containing mach
    128 commands for every command invocation. If you have dozens of commands or
    129 commands in modules that import a lot of Python code, these imports
    130 could slow mach down and waste memory.
    131 
    132 It is thus recommended that mach modules, classes, and methods do as
    133 little work as possible. Ideally the module should only import from
    134 the :py:mod:`mach` package. If you need external modules, you should
    135 import them from within the command method.
    136 
    137 To keep code size small, the body of a command method should be limited
    138 to:
    139 
    140 1. Obtaining user input (parsing arguments, prompting, etc)
    141 2. Calling into some other Python package
    142 3. Formatting output
    143 
    144 Of course, these recommendations can be ignored if you want to risk
    145 slower performance.
    146 
    147 In the future, the mach driver may cache the dispatching information or
    148 have it intelligently loaded to facilitate lazy loading.