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.