# 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/.
# This module provides functionality for the command-line build tool # (mach). It is packaged as a module because everything is a library.
import argparse import logging import os import sys import traceback from pathlib import Path from typing import List
from .base import (
CommandContext,
FailedCommandError,
MachError,
NoCommandError,
UnknownCommandError,
UnrecognizedArgumentError,
) from .config import ConfigSettings from .dispatcher import CommandAction from .logging import LoggingManager from .registrar import Registrar from .sentry import NoopErrorReporter, register_sentry from .telemetry import create_telemetry_from_environment, report_invocation_metrics from .util import UserError, setenv
SUGGEST_MACH_BUSTED_TEMPLATE = r"""
You can invoke ``./mach busted`` to check if this issue is already on file. If it
isn't, please use ``./mach busted file %s`` to report it. If ``./mach busted`` is
misbehaving, you can also inspect the dependencies of bug 1543241. """.lstrip()
MACH_ERROR_TEMPLATE = (
r"""
The error occurred in mach itself. This is likely a bug in mach itself or a
fundamental problem with a loaded module.
""".lstrip()
+ SUGGEST_MACH_BUSTED_TEMPLATE
)
ERROR_FOOTER = r""" If filing a bug, please include the full output of mach, including this error
message.
The details of the failure are as follows: """.lstrip()
USER_ERROR = r"""
This is a user error and does not appear to be a bug in mach. """.lstrip()
COMMAND_ERROR_TEMPLATE = (
r"""
The error occurred in the implementation of the invoked mach command.
This should never occur andis likely a bug in the implementation of that
command. """.lstrip()
+ SUGGEST_MACH_BUSTED_TEMPLATE
)
MODULE_ERROR_TEMPLATE = (
r"""
The error occurred in code that was called by the mach command. This is either
a bug in the called code itself orin the way that mach is calling it. """.lstrip()
+ SUGGEST_MACH_BUSTED_TEMPLATE
)
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
%s
Run ``mach help`` to show a list of commands. """.lstrip()
SUGGESTED_COMMANDS_MESSAGE = r"""
Did you want to %s any of these commands instead: %s? """
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."""
def error(self, message): """Custom error reporter to give more helpful text on bad commands.""" ifnot message.startswith("argument command: invalid choice"):
argparse.ArgumentParser.error(self, message) assertFalse
print("Invalid command specified. The list of commands is below.\n")
self.print_help()
sys.exit(1)
def format_help(self):
text = argparse.ArgumentParser.format_help(self)
# Strip out the silly command list that would preceed the pretty list. # # Commands: # {foo,bar} # foo Do foo. # bar Do bar.
search = "Commands:\n {"
start = text.find(search)
if start != -1:
end = text.find("}\n", start) assert end != -1
real_start = start + len("Commands:\n")
real_end = end + len("}\n")
def __getattribute__(self, key): try: return getattr(object.__getattribute__(self, "_context"), key) except AttributeError as e: try:
ret = object.__getattribute__(self, "_handler")(key) except (AttributeError, TypeError): # TypeError is in case the handler comes from old code not # taking a key argument. raise e
setattr(self, key, ret) return ret
This type is responsible for holding global mach state and dispatching
a command from arguments.
The following attributes may be assigned to the instance to influence
behavior:
populate_context_handler -- If defined, it must be a callable. The
callable signature is the following:
populate_context_handler(key=None)
It acts as a fallback getter for the mach.base.CommandContext
instance.
This allows to augment the context instance with arbitrary data for use in command handlers.
require_conditions -- IfTrue, commands that do not have any condition
functions applied will be skipped. Defaults to False.
settings_paths -- A list of files or directories in which to search for settings files to load.
@require_conditions.setter def require_conditions(self, value):
Registrar.require_conditions = value
def run(self, argv, stdin=None, stdout=None, stderr=None): """Runs mach with arguments provided from the command line.
Returns the integer exit code that should be used. 0 means success. All
other values indicate failure. """
sentry = NoopErrorReporter()
# If no encoding is defined, we default to UTF-8 because without this # Python 2.7 will assume the default encoding of ASCII. This will blow # up with UnicodeEncodeError as soon as it encounters a non-ASCII # character in a unicode instance. We simply install a wrapper around # the streams and restore once we have finished.
stdin = sys.stdin if stdin isNoneelse stdin
stdout = sys.stdout if stdout isNoneelse stdout
stderr = sys.stderr if stderr isNoneelse stderr
# Allow invoked processes (which may not have a handle on the # original stdout file descriptor) to know if the original stdout # is a TTY. This provides a mechanism to allow said processes to # enable emitting code codes, for example. if os.isatty(orig_stdout.fileno()):
setenv("MACH_STDOUT_ISATTY", "1")
return self._run(argv) except KeyboardInterrupt:
print("mach interrupted by signal or user action. Stopping.") return 1
except Exception: # _run swallows exceptions in invoked handlers and converts them to # a proper exit code. So, the only scenario where we should get an # exception here is if _run itself raises. If _run raises, that's a # bug in mach (or a loaded command module being silly) and thus # should be reported differently.
self._print_error_header(argv, sys.stdout)
print(MACH_ERROR_TEMPLATE % "general")
ifnot len(argv): # We don't register the usage until here because if it is globally # registered, argparse always prints it. This is not desired when # running with --help.
parser.usage = Mach.USAGE
parser.print_usage() return 0
# Always enable terminal logging. The log manager figures out if we are # actually in a TTY or are a pipe and does the right thing.
self.log_manager.add_terminal_logging(
level=log_level, write_interval=args.log_interval, write_times=write_times
)
if args.settings_file: # Argument parsing has already happened, so settings that apply # to command line handling (e.g alias, defaults) will be ignored.
self.load_settings_by_file([Path(args.settings_file)])
try: return Registrar._run_command_handler(
handler,
context,
self.command_site_manager,
debug_command=args.debug_command,
profile_command=args.profile_command,
**vars(args.command_args),
) except KeyboardInterrupt as ki: raise ki except FailedCommandError as e:
print(e) return e.exit_code except UserError: # We explicitly don't report UserErrors to Sentry.
exc_type, exc_value, exc_tb = sys.exc_info() # The first two frames are us and are never used.
stack = traceback.extract_tb(exc_tb)[2:]
self._print_error_header(argv, sys.stdout)
print(USER_ERROR)
self._print_exception(sys.stdout, exc_type, exc_value, stack) return 1 except Exception:
exc_type, exc_value, exc_tb = sys.exc_info()
sentry_event_id = sentry.report_exception(exc_value)
# The first two frames are us and are never used.
stack = traceback.extract_tb(exc_tb)[2:]
# If we have nothing on the stack, the exception was raised as part # of calling the @Command method itself. This likely means a # mismatch between @CommandArgument and arguments to the method. # e.g. there exists a @CommandArgument without the corresponding # argument on the method. We handle that here until the module # loader grows the ability to validate better. ifnot len(stack):
print(COMMAND_ERROR_TEMPLATE % handler.name)
self._print_exception(
sys.stdout,
exc_type,
exc_value,
traceback.extract_tb(exc_tb),
sentry_event_id=sentry_event_id,
) return 1
# Split the frames into those from the module containing the # command and everything else.
command_frames = []
other_frames = []
initial_file = stack[0][0]
for frame in stack: if frame[0] == initial_file:
command_frames.append(frame) else:
other_frames.append(frame)
# If the exception was in the module providing the command, it's # likely the bug is in the mach command module, not something else. # If there are other frames, the bug is likely not the mach # command's fault.
self._print_error_header(argv, sys.stdout)
if len(other_frames):
print(MODULE_ERROR_TEMPLATE % handler.name) else:
print(COMMAND_ERROR_TEMPLATE % handler.name)
# WARNING!!! If you add a global argument here, also add it to the # global argument handling in the top-level `mach` script. # Order is important here as it dictates the order the auto-generated # help messages are printed.
global_group = parser.add_argument_group("Global Arguments")
global_group.add_argument( "-v", "--verbose",
dest="verbose",
action="store_true",
default=False,
help="Print verbose output.",
)
global_group.add_argument( "-l", "--log-file",
dest="logfile",
metavar="FILENAME",
type=argparse.FileType("a"),
help="Filename to write log data to.",
)
global_group.add_argument( "--log-interval",
dest="log_interval",
action="store_true",
default=False,
help="Prefix log line with interval from last message rather " "than relative time. Note that this is NOT execution time " "if there are parallel operations.",
)
global_group.add_argument( "--no-interactive",
dest="is_interactive",
action="store_false",
help="Automatically selects the default option on any " "interactive prompts. If the output is not a terminal, " "then --no-interactive is assumed.",
)
suppress_log_by_default = False if"INSIDE_EMACS"in os.environ:
suppress_log_by_default = True
global_group.add_argument( "--log-no-times",
dest="log_no_times",
action="store_true",
default=suppress_log_by_default,
help="Do not prefix log lines with times. By default, " "mach will prefix each output line with the time since " "command start.",
)
global_group.add_argument( "-h", "--help",
dest="help",
action="store_true",
default=False,
help="Show this help message.",
)
global_group.add_argument( "--debug-command",
action="store_true",
help="Start a Python debugger when command is dispatched.",
)
global_group.add_argument( "--profile-command",
action="store_true",
help="Capture a Python profile of the mach process as command is dispatched.",
)
global_group.add_argument( "--settings",
dest="settings_file",
metavar="FILENAME",
default=None,
help="Path to settings file.",
)
if context: # 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, context=context
) else:
parser.add_argument("command", topsrcdir=topsrcdir, action=action)
return parser
¤ Dauer der Verarbeitung: 0.4 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.