Bug 856392 - Categorize mach commands; r=jhammel

DONTBUILD (NPOTB)
This commit is contained in:
Gregory Szorc 2013-05-08 17:56:30 -07:00
parent 33ac92edbf
commit 298c09657e
17 changed files with 465 additions and 126 deletions

View File

@ -25,7 +25,8 @@ class JetpackRunner(MozbuildObject):
@CommandProvider
class MachCommands(MachCommandBase):
@Command('jetpack-test', help='Runs the jetpack test suite.')
@Command('jetpack-test', category='testing',
description='Runs the jetpack test suite.')
def run_jetpack_test(self, **params):
# We should probably have a utility function to ensure the tree is
# ready to run tests. Until then, we just create the state dir (in

View File

@ -51,6 +51,41 @@ MACH_MODULES = [
'tools/mach_commands.py',
]
CATEGORIES = {
'build': {
'short': 'Build Commands',
'long': 'Interact with the build system',
'priority': 80,
},
'post-build': {
'short': 'Post-build Commands',
'long': 'Common actions performed after completing a build.',
'priority': 70,
},
'testing': {
'short': 'Testing',
'long': 'Run tests.',
'priority': 60,
},
'devenv': {
'short': 'Development Environment',
'long': 'Set up and configure your development environment.',
'priority': 50,
},
'build-dev': {
'short': 'Low-level Build System Interaction',
'long': 'Interact with specific parts of the build system.',
'priority': 20,
},
'misc': {
'short': 'Potpourri',
'long': 'Potent potables and assorted snacks.',
'priority': 10,
}
}
def bootstrap(topsrcdir, mozilla_dir=None):
if mozilla_dir is None:
mozilla_dir = topsrcdir
@ -70,6 +105,11 @@ def bootstrap(topsrcdir, mozilla_dir=None):
import mach.main
mach = mach.main.Mach(topsrcdir)
for category, meta in CATEGORIES.items():
mach.define_category(category, meta['short'], meta['long'],
meta['priority'])
for path in MACH_MODULES:
mach.load_commands_from_file(os.path.join(mozilla_dir, path))
return mach

View File

@ -127,22 +127,25 @@ def ReftestCommand(func):
@CommandProvider
class MachCommands(MachCommandBase):
@Command('reftest', help='Run a reftest.')
@Command('reftest', category='testing', description='Run reftests.')
@ReftestCommand
def run_reftest(self, test_file, **kwargs):
return self._run_reftest(test_file, suite='reftest', **kwargs)
@Command('reftest-ipc', help='Run IPC reftests.')
@Command('reftest-ipc', category='testing',
description='Run IPC reftests.')
@ReftestCommand
def run_ipc(self, test_file, **kwargs):
return self._run_reftest(test_file, suite='reftest-ipc', **kwargs)
@Command('crashtest', help='Run a crashtest.')
@Command('crashtest', category='testing',
description='Run crashtests.')
@ReftestCommand
def run_crashtest(self, test_file, **kwargs):
return self._run_reftest(test_file, suite='crashtest', **kwargs)
@Command('crashtest-ipc', help='Run IPC crashtests.')
@Command('crashtest-ipc', category='testing',
description='Run IPC crashtests.')
@ReftestCommand
def run_crashtest_ipc(self, test_file, **kwargs):
return self._run_reftest(test_file, suite='crashtest-ipc', **kwargs)

View File

@ -11,6 +11,33 @@ CommandContext = namedtuple('CommandContext', ['topdir', 'cwd',
'settings', 'log_manager', 'commands'])
class MachError(Exception):
"""Base class for all errors raised by mach itself."""
class NoCommandError(MachError):
"""No command was passed into mach."""
class UnknownCommandError(MachError):
"""Raised when we attempted to execute an unknown command."""
def __init__(self, command, verb):
MachError.__init__(self)
self.command = command
self.verb = verb
class UnrecognizedArgumentError(MachError):
"""Raised when an unknown argument is passed to mach."""
def __init__(self, command, arguments):
MachError.__init__(self)
self.command = command
self.arguments = arguments
class MethodHandler(object):
"""Describes a Python method that implements a mach command.
@ -33,23 +60,32 @@ class MethodHandler(object):
# the name of the function.
'method',
# The argparse subparser for this command's arguments.
'parser',
# The name of the command.
'name',
# Arguments passed to add_parser() on the main mach subparser. This is
# a 2-tuple of positional and named arguments, respectively.
'parser_args',
# String category this command belongs to.
'category',
# Description of the purpose of this command.
'description',
# Whether to allow all arguments from the parser.
'allow_all_arguments',
# Arguments added to this command's parser. This is a 2-tuple of
# positional and named arguments, respectively.
'arguments',
)
def __init__(self, cls, method, parser_args, arguments=None,
pass_context=False):
def __init__(self, cls, method, name, category=None, description=None,
allow_all_arguments=False, arguments=None, pass_context=False):
self.cls = cls
self.method = method
self.parser_args = parser_args
self.name = name
self.category = category
self.description = description
self.allow_all_arguments = allow_all_arguments
self.arguments = arguments or []
self.pass_context = pass_context

View File

@ -15,7 +15,8 @@ class BuiltinCommands(object):
def __init__(self, context):
self.context = context
@Command('mach-debug-commands', help='Show info about available mach commands.')
@Command('mach-debug-commands', category='misc',
description='Show info about available mach commands.')
def commands(self):
import inspect

View File

@ -23,7 +23,8 @@ class Settings(object):
def __init__(self, context):
self.settings = context.settings
@Command('settings-list', help='Show available config settings.')
@Command('settings-list', category='devenv',
description='Show available config settings.')
def list_settings(self):
"""List available settings in a concise list."""
for section in sorted(self.settings):
@ -31,8 +32,8 @@ class Settings(object):
short, full = self.settings.option_help(section, option)
print('%s.%s -- %s' % (section, option, short))
@Command('settings-create',
help='Print a new settings file with usage info.')
@Command('settings-create', category='devenv',
description='Print a new settings file with usage info.')
def create(self):
"""Create an empty settings file with full documentation."""
wrapper = TextWrapper(initial_indent='# ', subsequent_indent='# ')

View File

@ -7,7 +7,11 @@ from __future__ import unicode_literals
import inspect
import types
from .base import MethodHandler
from .base import (
MachError,
MethodHandler
)
from .config import ConfigProvider
from .registrar import Registrar
@ -36,7 +40,7 @@ def CommandProvider(cls):
msg = 'Mach @CommandProvider class %s implemented incorrectly. ' + \
'__init__() must take 1 or 2 arguments. From %s'
msg = msg % (cls.__name__, inspect.getsourcefile(cls))
raise Exception(msg)
raise MachError(msg)
if len(spec.args) == 2:
pass_context = True
@ -51,13 +55,16 @@ def CommandProvider(cls):
if not isinstance(value, types.FunctionType):
continue
parser_args = getattr(value, '_mach_command', None)
if parser_args is None:
command_name, category, description, allow_all = getattr(value,
'_mach_command', (None, None, None, None))
if command_name is None:
continue
arguments = getattr(value, '_mach_command_args', None)
handler = MethodHandler(cls, attr, (parser_args[0], parser_args[1]),
handler = MethodHandler(cls, attr, command_name, category=category,
description=description, allow_all_arguments=allow_all,
arguments=arguments, pass_context=pass_context)
Registrar.register_command_handler(handler)
@ -68,21 +75,33 @@ def CommandProvider(cls):
class Command(object):
"""Decorator for functions or methods that provide a mach subcommand.
The decorator accepts arguments that would be passed to add_parser() of an
ArgumentParser instance created via add_subparsers(). Essentially, it
accepts the arguments one would pass to add_argument().
The decorator accepts arguments that define basic attributes of the
command. The following arguments are recognized:
category -- The string category to which this command belongs. Mach's
help will group commands by category.
description -- A brief description of what the command does.
allow_all_args -- Bool indicating whether to allow unknown arguments
through to the command.
For example:
@Command('foo', help='Run the foo action')
@Command('foo', category='misc', description='Run the foo action')
def foo(self):
pass
"""
def __init__(self, *args, **kwargs):
self._command_args = (args, kwargs)
def __init__(self, name, category=None, description=None,
allow_all_args=False):
self._name = name
self._category = category
self._description = description
self._allow_all_args = allow_all_args
def __call__(self, func):
func._mach_command = self._command_args
func._mach_command = (self._name, self._category, self._description,
self._allow_all_args)
return func
@ -113,6 +132,7 @@ class CommandArgument(object):
return func
def SettingsProvider(cls):
"""Class decorator to denote that this class provides Mach settings.
@ -123,7 +143,7 @@ def SettingsProvider(cls):
This decorator is only allowed on mach.config.ConfigProvider classes.
"""
if not issubclass(cls, ConfigProvider):
raise Exception('@SettingsProvider encountered on class that does ' +
raise MachError('@SettingsProvider encountered on class that does ' +
'not derived from mach.config.ConfigProvider.')
Registrar.register_settings_provider(cls)

View File

@ -0,0 +1,176 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from __future__ import unicode_literals
import argparse
import sys
from operator import itemgetter
from .base import (
NoCommandError,
UnknownCommandError,
UnrecognizedArgumentError,
)
class CommandAction(argparse.Action):
"""An argparse action that handles mach commands.
This class is essentially a reimplementation of argparse's sub-parsers
feature. We first tried to use sub-parsers. However, they were missing
features like grouping of commands (http://bugs.python.org/issue14037).
The way this works involves light magic and a partial understanding of how
argparse works.
Arguments registered with an argparse.ArgumentParser have an action
associated with them. An action is essentially a class that when called
does something with the encountered argument(s). This class is one of those
action classes.
An instance of this class is created doing something like:
parser.add_argument('command', action=CommandAction, registrar=r)
Note that a mach.registrar.Registrar instance is passed in. The Registrar
holds information on all the mach commands that have been registered.
When this argument is registered with the ArgumentParser, an instance of
this class is instantiated. One of the subtle but important things it does
is tell the argument parser that it's interested in *all* of the remaining
program arguments. So, when the ArgumentParser calls this action, we will
receive the command name plus all of its arguments.
For more, read the docs in __call__.
"""
def __init__(self, option_strings, dest, required=True, default=None,
registrar=None):
# A proper API would have **kwargs here. However, since we are a little
# hacky, we intentionally omit it as a way of detecting potentially
# breaking changes with argparse's implementation.
#
# In a similar vein, default is passed in but is not needed, so we drop
# it.
argparse.Action.__init__(self, option_strings, dest, required=required,
help=argparse.SUPPRESS, nargs=argparse.REMAINDER)
self._mach_registrar = registrar
def __call__(self, parser, namespace, values, option_string=None):
"""This is called when the ArgumentParser has reached our arguments.
Since we always register ourselves with nargs=argparse.REMAINDER,
values should be a list of remaining arguments to parse. The first
argument should be the name of the command to invoke and all remaining
arguments are arguments for that command.
The gist of the flow is that we look at the command being invoked. If
it's *help*, we handle that specially (because argparse's default help
handler isn't satisfactory). Else, we create a new, independent
ArgumentParser instance for just the invoked command (based on the
information contained in the command registrar) and feed the arguments
into that parser. We then merge the results with the main
ArgumentParser.
"""
if not values:
raise NoCommandError()
command = values[0]
args = values[1:]
if command == 'help':
if len(args):
self._handle_subcommand_help(parser, args[0])
else:
self._handle_main_help(parser)
sys.exit(0)
handler = self._mach_registrar.command_handlers.get(command)
# FUTURE consider looking for commands with similar names and
# suggest or run them.
if not handler:
raise UnknownCommandError(command, 'run')
# FUTURE
# If we wanted to conditionally enable commands based on whether
# it's possible to run them given the current state of system, here
# would be a good place to hook that up.
# We create a new parser, populate it with the command's arguments,
# then feed all remaining arguments to it, merging the results
# with ourselves. This is essentially what argparse subparsers
# do.
parser_args = {
'add_help': False,
'usage': '%(prog)s [global arguments] ' + command +
' command arguments]',
}
if handler.allow_all_arguments:
parser_args['prefix_chars'] = '+'
subparser = argparse.ArgumentParser(**parser_args)
for arg in handler.arguments:
subparser.add_argument(*arg[0], **arg[1])
# We define the command information on the main parser result so as to
# not interfere with arguments passed to the command.
setattr(namespace, 'mach_handler', handler)
setattr(namespace, 'command', command)
command_namespace, extra = subparser.parse_known_args(args)
setattr(namespace, 'command_args', command_namespace)
if extra:
raise UnrecognizedArgumentError(command, extra)
def _handle_main_help(self, parser):
# Since we don't need full sub-parser support for the main help output,
# we create groups in the ArgumentParser and populate each group with
# arguments corresponding to command names. This has the side-effect
# that argparse renders it nicely.
r = self._mach_registrar
cats = [(k, v[2]) for k, v in r.categories.items()]
sorted_cats = sorted(cats, key=itemgetter(1), reverse=True)
for category, priority in sorted_cats:
title, description, _priority = r.categories[category]
group = parser.add_argument_group(title, description)
for command in sorted(r.commands_by_category[category]):
handler = r.command_handlers[command]
description = handler.description
group.add_argument(command, help=description,
action='store_true')
parser.print_help()
def _handle_subcommand_help(self, parser, command):
handler = self._mach_registrar.command_handlers.get(command)
if not handler:
raise UnknownCommandError(command, 'query')
group = parser.add_argument_group('Command Arguments')
for arg in handler.arguments:
group.add_argument(*arg[0], **arg[1])
# This will print the description of the command below the usage.
description = handler.description
if description:
parser.description = description
parser.usage = '%(prog)s [global arguments] ' + command + \
' [command arguments]'
parser.print_help()

View File

@ -17,7 +17,13 @@ import traceback
import uuid
import sys
from .base import CommandContext
from .base import (
CommandContext,
MachError,
NoCommandError,
UnknownCommandError,
UnrecognizedArgumentError,
)
from .decorators import (
CommandArgument,
@ -26,23 +32,39 @@ from .decorators import (
)
from .config import ConfigSettings
from .dispatcher import CommandAction
from .logging import LoggingManager
from .registrar import Registrar
# Settings for argument parser that don't get proxied to sub-module. i.e. these
# are things consumed by the driver itself.
CONSUMED_ARGUMENTS = [
'settings_file',
'verbose',
'logfile',
'log_interval',
'command',
'mach_class',
'mach_method',
'mach_pass_context',
]
AVATAR = '''
_.-;:q=._
.' j=""^k;:\.
; .F ";`Y
,;.J_ ;'j
,-;"^7F : .F
,-'-_<. ;gj.
; _,._`\. : `T"5
: `?8w7 `J ,-'" -^q.
\;._ _,=' ; n58L Y.
F;"; .' k_ `^' j'
J;:: ; "y:-='
L;;== |:; jT\\
L;:;J J:L 7:;'
I;|:.L |:k J:.'
|;J:.| ;.I F.:
J;:L:: |.| |.J
J:`J.`. :.J |. L
L :k:`._ ,',j J; |
I :`=.:."_".' L J
|.: `"-=-' |.J
`: : ;:;
J: : /.;'
k;.\. _.;:Y'
`Y;."-=';:='
`"==="'
'''
MACH_ERROR = r'''
The error occurred in mach itself. This is likely a bug in mach itself or a
@ -75,6 +97,24 @@ a bug in the called code itself or in the way that mach is calling it.
You should consider filing a bug for this issue.
'''.lstrip()
NO_COMMAND_ERROR = r'''
It looks like you tried to run mach without a command.
Run |mach help| to show a list of commands.
'''.lstrip()
UNKNOWN_COMMAND_ERROR = r'''
It looks like you are trying to %s an unknown mach command: %s
Run |mach help| to show a list of commands.
'''.lstrip()
UNRECOGNIZED_ARGUMENT_ERROR = r'''
It looks like you passed an unrecognized argument into mach.
The %s command does not accept the arguments: %s
'''.lstrip()
class ArgumentParser(argparse.ArgumentParser):
"""Custom implementation argument parser to make things look pretty."""
@ -177,6 +217,11 @@ To see more help for a specific command, run:
imp.load_source(module_name, path)
def define_category(self, name, title, description, priority=50):
"""Provide a description for a named command category."""
Registrar.register_category(name, title, description, priority)
def run(self, argv, stdin=None, stdout=None, stderr=None):
"""Runs mach with arguments provided from the command line.
@ -248,18 +293,21 @@ To see more help for a specific command, run:
parser.print_usage()
return 0
args = parser.parse_args(argv)
if args.command == 'help':
if args.subcommand is None:
parser.usage = \
'%(prog)s [global arguments] command [command arguments]'
parser.print_help()
return 0
handler = Registrar.command_handlers[args.subcommand]
handler.parser.print_help()
return 0
try:
args = parser.parse_args(argv)
except NoCommandError:
print(AVATAR)
print(NO_COMMAND_ERROR)
return 1
except UnknownCommandError as e:
print(AVATAR)
print(UNKNOWN_COMMAND_ERROR % (e.verb, e.command))
return 1
except UnrecognizedArgumentError as e:
print(AVATAR)
print(UNRECOGNIZED_ARGUMENT_ERROR % (e.command,
' '.join(e.arguments)))
return 1
# Add JSON logging to a file if requested.
if args.logfile:
@ -279,27 +327,25 @@ To see more help for a specific command, run:
self.load_settings(args)
stripped = {k: getattr(args, k) for k in vars(args) if k not in
CONSUMED_ARGUMENTS}
if not hasattr(args, 'mach_handler'):
raise MachError('ArgumentParser result missing mach handler info.')
context = CommandContext(topdir=self.cwd, cwd=self.cwd,
settings=self.settings, log_manager=self.log_manager,
commands=Registrar)
if not hasattr(args, 'mach_class'):
raise Exception('ArgumentParser result missing mach_class.')
handler = getattr(args, 'mach_handler')
cls = handler.cls
cls = getattr(args, 'mach_class')
if getattr(args, 'mach_pass_context'):
if handler.pass_context:
instance = cls(context)
else:
instance = cls()
fn = getattr(instance, getattr(args, 'mach_method'))
fn = getattr(instance, handler.method)
try:
result = fn(**stripped)
result = fn(**vars(args.command_args))
if not result:
result = 0
@ -419,18 +465,12 @@ To see more help for a specific command, run:
"""Returns an argument parser for the command-line interface."""
parser = ArgumentParser(add_help=False,
usage='%(prog)s [global arguments]')
usage='%(prog)s [global arguments] command [command arguments]')
# Order is important here as it dictates the order the auto-generated
# help messages are printed.
subparser = parser.add_subparsers(dest='command', title='Commands')
parser.set_defaults(command='help')
global_group = parser.add_argument_group('Global Arguments')
global_group.add_argument('-h', '--help', action='help',
help='Show this help message and exit.')
#global_group.add_argument('--settings', dest='settings_file',
# metavar='FILENAME', help='Path to settings file.')
@ -446,14 +486,10 @@ To see more help for a specific command, run:
'than relative time. Note that this is NOT execution time '
'if there are parallel operations.')
Registrar.populate_argument_parser(subparser)
# We need to be last because CommandAction swallows all remaining
# arguments and argparse parses arguments in the order they were added.
parser.add_argument('command', action=CommandAction,
registrar=Registrar)
return parser
@Command('help', help='Show mach usage info or help for a command.')
@CommandArgument('subcommand', default=None, nargs='?',
help='Command to show help info for.')
def _help(self, subcommand=None):
# The built-in handler doesn't pass the original ArgumentParser into
# handlers (yet). This command is currently handled by _run().
assert False

View File

@ -4,7 +4,7 @@
from __future__ import unicode_literals
import collections
from .base import MachError
class MachRegistrar(object):
@ -12,28 +12,30 @@ class MachRegistrar(object):
def __init__(self):
self.command_handlers = {}
self.commands_by_category = {}
self.settings_providers = set()
self.categories = {}
def register_command_handler(self, handler):
name = handler.parser_args[0][0]
name = handler.name
if not handler.category:
raise MachError('Cannot register a mach command without a '
'category: %s' % name)
if handler.category not in self.categories:
raise MachError('Cannot register a command to an undefined '
'category: %s -> %s' % (name, handler.category))
self.command_handlers[name] = handler
self.commands_by_category[handler.category].add(name)
def register_settings_provider(self, cls):
self.settings_providers.add(cls)
def populate_argument_parser(self, parser):
for command in sorted(self.command_handlers.keys()):
handler = self.command_handlers[command]
handler.parser = parser.add_parser(*handler.parser_args[0],
**handler.parser_args[1])
for arg in handler.arguments:
handler.parser.add_argument(*arg[0], **arg[1])
handler.parser.set_defaults(mach_class=handler.cls,
mach_method=handler.method,
mach_pass_context=handler.pass_context)
def register_category(self, name, title, description, priority=50):
self.categories[name] = (title, description, priority)
self.commands_by_category[name] = set()
Registrar = MachRegistrar()

View File

@ -15,8 +15,8 @@ from mach.decorators import (
class Bootstrap(object):
"""Bootstrap system and mach for optimal development experience."""
@Command('bootstrap',
help='Install required system packages for building.')
@Command('bootstrap', category='devenv',
description='Install required system packages for building.')
def bootstrap(self):
from mozboot.bootstrap import Bootstrapper

View File

@ -43,8 +43,8 @@ def print_extra(extra):
@CommandProvider
class MozbuildFileCommands(object):
@Command('mozbuild-reference',
help='View reference documentation on mozbuild files.')
@Command('mozbuild-reference', category='build-dev',
description='View reference documentation on mozbuild files.')
@CommandArgument('symbol', default=None, nargs='*',
help='Symbol to view help on. If not specified, all will be shown.')
@CommandArgument('--name-only', '-n', default=False, action='store_true',

View File

@ -46,7 +46,7 @@ Preferences.
class Build(MachCommandBase):
"""Interface to build the tree."""
@Command('build', help='Build the tree.')
@Command('build', category='build', description='Build the tree.')
@CommandArgument('--jobs', '-j', default='0', metavar='jobs', type=int,
help='Number of concurrent jobs to run. Default is the number of CPUs.')
@CommandArgument('what', default=None, nargs='*', help=BUILD_WHAT_HELP)
@ -224,7 +224,8 @@ class Build(MachCommandBase):
print(FINDER_SLOW_MESSAGE % finder_percent)
@Command('configure', help='Configure the tree (run configure and config.status')
@Command('configure', category='build',
description='Configure the tree (run configure and config.status')
def configure(self):
def on_line(line):
self.log(logging.INFO, 'build_output', {'line': line}, '{line}')
@ -240,7 +241,8 @@ class Build(MachCommandBase):
return status
@Command('clobber', help='Clobber the tree (delete the object directory).')
@Command('clobber', category='build',
description='Clobber the tree (delete the object directory).')
def clobber(self):
try:
self.remove_objdir()
@ -276,8 +278,8 @@ class Warnings(MachCommandBase):
return database
@Command('warnings-summary',
help='Show a summary of compiler warnings.')
@Command('warnings-summary', category='post-build',
description='Show a summary of compiler warnings.')
@CommandArgument('report', default=None, nargs='?',
help='Warnings report to display. If not defined, show the most '
'recent report.')
@ -295,7 +297,8 @@ class Warnings(MachCommandBase):
print('%d\tTotal' % total)
@Command('warnings-list', help='Show a list of compiler warnings.')
@Command('warnings-list', category='post-build',
description='Show a list of compiler warnings.')
@CommandArgument('report', default=None, nargs='?',
help='Warnings report to display. If not defined, show the most '
'recent report.')
@ -319,7 +322,8 @@ class Warnings(MachCommandBase):
@CommandProvider
class GTestCommands(MachCommandBase):
@Command('gtest', help='Run GTest unit tests.')
@Command('gtest', category='testing',
description='Run GTest unit tests.')
@CommandArgument('gtest_filter', default=b"*", nargs='?', metavar='gtest_filter',
help="test_filter is a ':'-separated list of wildcard patterns (called the positive patterns),"
"optionally followed by a '-' and another ':'-separated pattern list (called the negative patterns).")
@ -381,7 +385,8 @@ class GTestCommands(MachCommandBase):
@CommandProvider
class ClangCommands(MachCommandBase):
@Command('clang-complete', help='Generate a .clang_complete file.')
@Command('clang-complete', category='devenv',
description='Generate a .clang_complete file.')
def clang_complete(self):
import shlex
@ -435,7 +440,8 @@ class ClangCommands(MachCommandBase):
class Package(MachCommandBase):
"""Package the built product for distribution."""
@Command('package', help='Package the built product for distribution as an APK, DMG, etc.')
@Command('package', category='post-build',
description='Package the built product for distribution as an APK, DMG, etc.')
def package(self):
return self._run_make(directory=".", target='package', ensure_exit_code=False)
@ -443,7 +449,8 @@ class Package(MachCommandBase):
class Install(MachCommandBase):
"""Install a package."""
@Command('install', help='Install the package on the machine, or on a device.')
@Command('install', category='post-build',
description='Install the package on the machine, or on a device.')
def install(self):
return self._run_make(directory=".", target='install', ensure_exit_code=False)
@ -451,8 +458,9 @@ class Install(MachCommandBase):
class RunProgram(MachCommandBase):
"""Launch the compiled binary"""
@Command('run', help='Run the compiled program.', prefix_chars='+')
@CommandArgument('params', default=None, nargs='*',
@Command('run', category='post-build', allow_all_args=True,
description='Run the compiled program.')
@CommandArgument('params', default=None, nargs='...',
help='Command-line arguments to pass to the program.')
def run(self, params):
try:
@ -471,8 +479,9 @@ class RunProgram(MachCommandBase):
class DebugProgram(MachCommandBase):
"""Debug the compiled binary"""
@Command('debug', help='Debug the compiled program.', prefix_chars='+')
@CommandArgument('params', default=None, nargs='*',
@Command('debug', category='post-build', allow_all_args=True,
description='Debug the compiled program.')
@CommandArgument('params', default=None, nargs='...',
help='Command-line arguments to pass to the program.')
def debug(self, params):
import which
@ -498,13 +507,15 @@ class DebugProgram(MachCommandBase):
class Buildsymbols(MachCommandBase):
"""Produce a package of debug symbols suitable for use with Breakpad."""
@Command('buildsymbols', help='Produce a package of Breakpad-format symbols.')
@Command('buildsymbols', category='post-build',
description='Produce a package of Breakpad-format symbols.')
def buildsymbols(self):
return self._run_make(directory=".", target='buildsymbols', ensure_exit_code=False)
@CommandProvider
class Makefiles(MachCommandBase):
@Command('empty-makefiles', help='Find empty Makefile.in in the tree.')
@Command('empty-makefiles', category='build-dev',
description='Find empty Makefile.in in the tree.')
def empty(self):
import pymake.parser
import pymake.parserdata

View File

@ -16,7 +16,8 @@ from mach.decorators import (
@CommandProvider
class MachCommands(MachCommandBase):
@Command('marionette-test', help='Run a Marionette test.')
@Command('marionette-test', category='testing',
description='Run a Marionette test.')
@CommandArgument('--homedir', dest='b2g_path',
help='For B2G testing, the path to the B2G repo.')
@CommandArgument('--emulator', choices=['x86', 'arm'],

View File

@ -240,27 +240,32 @@ def MochitestCommand(func):
@CommandProvider
class MachCommands(MachCommandBase):
@Command('mochitest-plain', help='Run a plain mochitest.')
@Command('mochitest-plain', category='testing',
description='Run a plain mochitest.')
@MochitestCommand
def run_mochitest_plain(self, test_file, **kwargs):
return self.run_mochitest(test_file, 'plain', **kwargs)
@Command('mochitest-chrome', help='Run a chrome mochitest.')
@Command('mochitest-chrome', category='testing',
description='Run a chrome mochitest.')
@MochitestCommand
def run_mochitest_chrome(self, test_file, **kwargs):
return self.run_mochitest(test_file, 'chrome', **kwargs)
@Command('mochitest-browser', help='Run a mochitest with browser chrome.')
@Command('mochitest-browser', category='testing',
description='Run a mochitest with browser chrome.')
@MochitestCommand
def run_mochitest_browser(self, test_file, **kwargs):
return self.run_mochitest(test_file, 'browser', **kwargs)
@Command('mochitest-metro', help='Run a mochitest with metro browser chrome.')
@Command('mochitest-metro', category='testing',
description='Run a mochitest with metro browser chrome.')
@MochitestCommand
def run_mochitest_metro(self, test_file, **kwargs):
return self.run_mochitest(test_file, 'metro', **kwargs)
@Command('mochitest-a11y', help='Run an a11y mochitest.')
@Command('mochitest-a11y', category='testing',
description='Run an a11y mochitest.')
@MochitestCommand
def run_mochitest_a11y(self, test_file, **kwargs):
return self.run_mochitest(test_file, 'a11y', **kwargs)

View File

@ -150,7 +150,8 @@ class XPCShellRunner(MozbuildObject):
@CommandProvider
class MachCommands(MachCommandBase):
@Command('xpcshell-test', help='Run an xpcshell test.')
@Command('xpcshell-test', category='testing',
description='Run XPCOM Shell tests.')
@CommandArgument('test_file', default='all', nargs='?', metavar='TEST',
help='Test to run. Can be specified as a single JS file, a directory, '
'or omitted. If omitted, the entire test suite is executed.')

View File

@ -13,7 +13,8 @@ from mach.decorators import (
@CommandProvider
class SearchProvider(object):
@Command('mxr', help='Search for something in MXR.')
@Command('mxr', category='misc',
description='Search for something in MXR.')
@CommandArgument('term', nargs='+', help='Term(s) to search for.')
def mxr(self, term):
import webbrowser
@ -21,7 +22,8 @@ class SearchProvider(object):
uri = 'https://mxr.mozilla.org/mozilla-central/search?string=%s' % term
webbrowser.open_new_tab(uri)
@Command('dxr', help='Search for something in DXR.')
@Command('dxr', category='misc',
description='Search for something in DXR.')
@CommandArgument('term', nargs='+', help='Term(s) to search for.')
def dxr(self, term):
import webbrowser
@ -29,7 +31,8 @@ class SearchProvider(object):
uri = 'http://dxr.mozilla.org/search?tree=mozilla-central&q=%s' % term
webbrowser.open_new_tab(uri)
@Command('mdn', help='Search for something on MDN.')
@Command('mdn', category='misc',
description='Search for something on MDN.')
@CommandArgument('term', nargs='+', help='Term(s) to search for.')
def mdn(self, term):
import webbrowser
@ -37,7 +40,8 @@ class SearchProvider(object):
uri = 'https://developer.mozilla.org/search?q=%s' % term
webbrowser.open_new_tab(uri)
@Command('google', help='Search for something on Google.')
@Command('google', category='misc',
description='Search for something on Google.')
@CommandArgument('term', nargs='+', help='Term(s) to search for.')
def google(self, term):
import webbrowser
@ -45,7 +49,8 @@ class SearchProvider(object):
uri = 'https://www.google.com/search?q=%s' % term
webbrowser.open_new_tab(uri)
@Command('search', help='Search for something on the Internets. '
@Command('search', category='misc',
description='Search for something on the Internets. '
'This will open 3 new browser tabs and search for the term on Google, '
'MDN, and MXR.')
@CommandArgument('term', nargs='+', help='Term(s) to search for.')