mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-08 10:44:56 +00:00
Bug 856392 - Categorize mach commands; r=jhammel
DONTBUILD (NPOTB)
This commit is contained in:
parent
33ac92edbf
commit
298c09657e
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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='# ')
|
||||
|
@ -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)
|
||||
|
176
python/mach/mach/dispatcher.py
Normal file
176
python/mach/mach/dispatcher.py
Normal 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()
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
||||
|
@ -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',
|
||||
|
@ -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
|
||||
|
@ -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'],
|
||||
|
@ -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)
|
||||
|
@ -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.')
|
||||
|
@ -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.')
|
||||
|
Loading…
Reference in New Issue
Block a user