# 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/.
import argparse import errno import itertools import json import logging import operator import os import os.path import platform import re import shutil import subprocess import sys import tempfile import time from os import path from pathlib import Path
import mozpack.path as mozpath from mach.decorators import (
Command,
CommandArgument,
CommandArgumentGroup,
SubCommand,
) from mozfile import load_source
from mozbuild.base import (
BinaryNotFoundException,
BuildEnvironmentNotFoundException,
MozbuildObject,
) from mozbuild.base import MachCommandConditions as conditions from mozbuild.util import MOZBUILD_METRICS_PATH
Your machine experienced a lot of swap activity during the build. This is
possibly a sign that your machine doesn't have enough physical memory or not enough available memory to perform the build. It's also possible some
other system activity during the build is to blame.
If you feel this message isnot appropriate for your machine configuration,
please file a Firefox Build System :: General bug at https://bugzilla.mozilla.org/enter_bug.cgi?product=Firefox%20Build%20System&component=General and tell us about your machine and build configuration so we can adjust the
warning heuristic.
=================== """
class StoreDebugParamsAndWarnAction(argparse.Action): def __call__(self, parser, namespace, values, option_string=None):
sys.stderr.write( "The --debugparams argument is deprecated. Please "
+ "use --debugger-args instead.\n\n"
)
setattr(namespace, self.dest, values)
@Command( "watch",
category="post-build",
description="Watch and re-build (parts of) the tree.",
conditions=[conditions.is_firefox],
virtualenv_name="watch",
)
@CommandArgument( "-v", "--verbose",
action="store_true",
help="Verbose output for what commands the watcher is running.",
) def watch(command_context, verbose=False): """Watch and re-build (parts of) the source tree.""" ifnot conditions.is_artifact_build(command_context):
print( "WARNING: mach watch only rebuilds the `mach build faster` parts of the tree!"
)
try: return daemon.watch() except KeyboardInterrupt: # Suppress ugly stack trace when user hits Ctrl-C.
sys.exit(3)
CARGO_CONFIG_NOT_FOUND_ERROR_MSG = """\
The sub-command {subcommand} isnot currently configured to be used with ./mach cargo.
To do so, add the corresponding file in <mozilla-root-dir>/build/cargo, following other examples in this directory"""
def _cargo_config_yaml_schema(): from voluptuous import All, Boolean, Required, Schema
def starts_with_cargo(s): if s.startswith("cargo-"): return s else: raise ValueError
return Schema(
{ # The name of the command (not checked for now, but maybe # later)
Required("command"): All(str, starts_with_cargo), # Whether `make` should stop immediately in case # of error returned by the command. Default: False "continue_on_error": Boolean, # Whether this command requires pre_export and export build # targets to have run. Defaults to bool(cargo_build_flags). "requires_export": Boolean, # Build flags to use. If this variable is not # defined here, the build flags are generated automatically and are # the same as for `cargo build`. See available substitutions at the # end. "cargo_build_flags": [str], # Extra build flags to use. These flags are added # after the cargo_build_flags both when they are provided or # automatically generated. See available substitutions at the end. "cargo_extra_flags": [str], # Available substitutions for `cargo_*_flags`: # * {arch}: architecture target # * {crate}: current crate name # * {directory}: Directory of the current crate within the source tree # * {features}: Rust features (for `--features`) # * {manifest}: full path of `Cargo.toml` file # * {target}: `--lib` for library, `--bin CRATE` for executables # * {topsrcdir}: Top directory of sources
}
)
@Command( "cargo",
category="build",
description="Run `cargo ` on a given crate. Defaults to gkrust.",
metrics_path=MOZBUILD_METRICS_PATH,
)
@CommandArgument( "cargo_command",
default=None,
help="Target to cargo, must be one of the commands in build/cargo/",
)
@CommandArgument( "--all-crates",
action="store_true",
help="Check all of the crates in the tree.",
)
@CommandArgument( "-p", "--package", default=None, help="The specific crate name to check."
)
@CommandArgument( "--jobs", "-j",
default="0",
nargs="?",
metavar="jobs",
type=int,
help="Run the tests in parallel using multiple processes.",
)
@CommandArgument("-v", "--verbose", action="store_true", help="Verbose output.")
@CommandArgument( "--message-format-json",
action="store_true",
help="Emit error messages as JSON.",
)
@CommandArgument( "--continue-on-error",
action="store_true",
help="Do not return an error exit code if the subcommands errors out.",
)
@CommandArgument( "subcommand_args",
nargs=argparse.REMAINDER,
help="These arguments are passed as-is to the cargo subcommand.",
) def cargo(
command_context,
cargo_command,
all_crates=None,
package=None,
jobs=0,
verbose=False,
message_format_json=False,
continue_on_error=False,
subcommand_args=[],
): import yaml
from mozbuild.controller.building import BuildDriver
ret = 0 if requires_export: # This directory is created during export. If it's not there, # export hasn't run already.
deps = Path(command_context.topobjdir) / ".deps" ifnot deps.exists():
build = command_context._spawn(BuildDriver)
ret = build.build(
command_context.metrics,
what=["pre-export", "export"],
jobs=jobs,
verbose=verbose,
mach_context=command_context._mach_context,
) else: try:
command_context.config_environment except BuildEnvironmentNotFoundException:
build = command_context._spawn(BuildDriver)
ret = build.configure(
command_context.metrics,
buildstatus_messages=False,
) if ret != 0: return ret
if command_context.substs.get("commtopsrcdir"): # Thunderbird uses its own gkrust, located in its own workspace.
gkrust_path = "comm/rust/gkrust"
gtest_path = "comm/rust/gtest" else:
gkrust_path = "toolkit/library/rust"
gtest_path = "toolkit/library/gtest/rust"
if subcommand_args:
subcommand_args = " ".join(subcommand_args)
for crate in crates:
crate_info = crates_and_roots.get(crate, None) ifnot crate_info:
print( "Cannot locate crate %s. Please check your spelling or " "add the crate information to the list." % crate
) return 1
append_env = {} if cargo_extra_flags:
append_env["CARGO_EXTRA_FLAGS"] = cargo_extra_flags.format(**subst) if message_format_json:
append_env["USE_CARGO_JSON_MESSAGE_FORMAT"] = "1" if continue_on_error:
append_env["CARGO_CONTINUE_ON_ERROR"] = "1" if cargo_build_flags:
append_env["CARGO_NO_AUTO_ARG"] = "1" else:
append_env["ADD_RUST_LTOABLE"] = ( "force-cargo-library-{s:s} force-cargo-program-{s:s}".format(
s=cargo_command
)
)
ret = command_context._run_make(
srcdir=False,
directory=directory,
ensure_exit_code=0,
silent=not verbose,
print_directory=False,
target=targets,
num_jobs=jobs,
append_env=append_env,
) if ret != 0: return ret
# Logging of commands enables logging from `bootstrap_toolchain` that we # don't want to expose. Disable them temporarily.
logger = logging.getLogger("gecko_taskgraph.generator")
level = logger.getEffectiveLevel()
logger.setLevel(logging.ERROR)
env = env.copy()
cargo_vet = bootstrap_toolchain("cargo-vet") if cargo_vet:
env["PATH"] = os.pathsep.join([cargo_vet, env["PATH"]])
logger.setLevel(level) try:
cargo = command_context.substs["CARGO"] except (BuildEnvironmentNotFoundException, KeyError): # Default if this tree isn't configured. from mozfile import which
cargo = which("cargo", path=env["PATH"]) ifnot cargo: raise OSError(
errno.ENOENT,
( "Could not find 'cargo' on your $PATH. " "Hint: have you run `mach build` or `mach configure`?"
),
)
try: # When run for Thunderbird, configure must run first if override_config_toml_in := command_context.substs.get( "MOZ_OVERRIDE_CARGO_CONFIG"
):
config_toml_in = Path(override_config_toml_in).absolute()
cargo_vet_dir = config_toml_in.parent.parent except BuildEnvironmentNotFoundException: pass
locked = "--locked"in arguments if locked: # The use of --locked requires .cargo/config.toml to exist, but other things, # like cargo update, don't want it there, so remove it once we're done.
shutil.copyfile(config_toml_in, config_toml) try:
res = subprocess.run(
[cargo, "vet"] + arguments,
cwd=cargo_vet_dir,
stdout=stdout,
env=env,
) finally: if locked:
config_toml.unlink()
# When the function is invoked without stdout set (the default when running # as a mach subcommand), exit with the returncode from cargo vet. # When the function is invoked with stdout (direct function call), return # the full result from subprocess.run. return res if stdout else res.returncode
@Command( "doctor",
category="devenv",
description="Diagnose and fix common development environment issues.",
)
@CommandArgument( "--fix",
default=False,
action="store_true",
help="Attempt to fix found problems.",
)
@CommandArgument( "--verbose",
default=False,
action="store_true",
help="Print verbose information found by checks.",
) def doctor(command_context, fix=False, verbose=False): """Diagnose common build environment problems""" from mozbuild.doctor import run_doctor
@Command( "clobber",
category="build",
description="Clobber the tree (delete the object directory).",
no_auto_log=True,
)
@CommandArgument( "what",
default=["objdir", "python"],
nargs="*",
help="Target to clobber, must be one of {{{}}} (default " "objdir and python).".format(", ".join(CLOBBER_CHOICES)),
)
@CommandArgument("--full", action="store_true", help="Perform a full clobber") def clobber(command_context, what, full=False): """Clean up the source and object directories.
Performing builds and running various commands generate various files.
Sometimes it is necessary to clean up these files in order to make
things work again. This command can be used to perform that cleanup.
The `objdir` target removes most files in the current object directory
(where build output is stored). Some files (like Visual Studio project
files) are not removed by default. If you would like to remove the
object directory in its entirety, run with `--full`.
The `python` target will clean up Python's generated files (virtualenvs, ".pyc", "__pycache__", etc).
The `gradle` target will remove the "gradle" subdirectory of the object
directory.
By default, the command clobbers the `objdir` and `python` targets. """
what = set(what)
invalid = what - CLOBBER_CHOICES if invalid:
print( "Unknown clobber target(s): {}. Choose from {{{}}}".format( ", ".join(invalid), ", ".join(CLOBBER_CHOICES)
)
) return 1
ret = 0 if"objdir"in what: from mozbuild.controller.clobber import Clobberer
try:
Clobberer(
command_context.topsrcdir, command_context.topobjdir, substs
).remove_objdir(full) except OSError as e: if sys.platform.startswith("win"): if isinstance(e, WindowsError) and e.winerror in (5, 32):
command_context.log(
logging.ERROR, "file_access_error",
{"error": e}, "Could not clobber because a file was in use. If the " "application is running, try closing it. {error}",
) return 1 raise
# We'll keep this around to delete the legacy "_virtualenv" dir folders # so that people don't get confused if they see it and try to manipulate # it but it has no effect.
shutil.rmtree(
mozpath.join(command_context.topobjdir, "_virtualenvs"),
ignore_errors=True,
) from mach.util import get_virtualenv_base_dir
for specific_venv in virtualenv_dir.iterdir(): if specific_venv.name == "mach": # We can't delete the "mach" virtualenv with clobber # since it's the one doing the clobbering. It always # has to be removed manually. pass else:
shutil.rmtree(specific_venv, ignore_errors=True)
@Command( "show-log", category="post-build", description="Display mach logs", no_auto_log=True
)
@CommandArgument( "log_file",
nargs="?",
type=argparse.FileType("rb"),
help="Filename to read log data from. Defaults to the log of the last " "mach command.",
) def show_log(command_context, log_file=None): """Show mach logs If we're in a terminal context, the log is piped to 'less' for more convenient viewing.
(https://man7.org/linux/man-pages/man1/less.1.html) """ ifnot log_file:
path = command_context._get_state_filename("last_log.json")
log_file = open(path, "rb")
if os.isatty(sys.stdout.fileno()):
env = dict(os.environ) if"LESS"notin env: # Sensible default flags if none have been set in the user environment.
env["LESS"] = "FRX"
less = subprocess.Popen(
["less"], stdin=subprocess.PIPE, env=env, encoding="UTF-8"
)
try: # Create a new logger handler with the stream being the stdin of our 'less' # process so that we can pipe the logger output into 'less'
less_handler = logging.StreamHandler(stream=less.stdin)
less_handler.setFormatter(
command_context.log_manager.terminal_handler.formatter
)
less_handler.setLevel(command_context.log_manager.terminal_handler.level)
# replace the existing terminal handler with the new one for 'less' while # still keeping the original one to set back later
original_handler = command_context.log_manager.replace_terminal_handler(
less_handler
)
# Save this value so we can set it back to the original value later
original_logging_raise_exceptions = logging.raiseExceptions
# We need to explicitly disable raising exceptions inside logging so # that we can catch them here ourselves to ignore the ones we want
logging.raiseExceptions = False
# Parses the log file line by line and streams # (to less.stdin) the relevant records we want
handle_log_file(command_context, log_file)
# At this point we've piped the entire log file to # 'less', so we can close the input stream
less.stdin.close()
# Wait for the user to manually terminate `less`
less.wait() except OSError as os_error: # (POSIX) errno.EPIPE: BrokenPipeError: [Errno 32] Broken pipe # (Windows) errno.EINVAL: OSError: [Errno 22] Invalid argument if os_error.errno == errno.EPIPE or os_error.errno == errno.EINVAL: # If the user manually terminates 'less' before the entire log file # is piped (without scrolling close enough to the bottom) we will get # one of these errors (depends on the OS) because the logger will still # attempt to stream to the now invalid less.stdin. To prevent a bunch # of errors being shown after a user terminates 'less', we just catch # the first of those exceptions here, and stop parsing the log file. pass else: raise except Exception: raise finally: # Ensure these values are changed back to the originals, regardless of outcome
command_context.log_manager.replace_terminal_handler(original_handler)
logging.raiseExceptions = original_logging_raise_exceptions else: # Not in a terminal context, so just handle the log file with the # default stream without piping it to a pager (less)
handle_log_file(command_context, log_file)
def handle_log_file(command_context, log_file):
start_time = 0 for line in log_file:
created, action, params = json.loads(line) ifnot start_time:
start_time = created
command_context.log_manager.terminal_handler.formatter.start_time = created if"line"in params:
record = logging.makeLogRecord(
{ "created": created, "name": command_context._logger.name, "levelno": logging.INFO, "msg": "{line}", "params": params, "action": action,
}
)
command_context._logger.handle(record)
def get_warnings_database(command_context): from mozbuild.compilation.warnings import WarningsDatabase
path = database_path(command_context)
database = WarningsDatabase()
if os.path.exists(path):
database.load_from_file(path)
return database
@Command( "warnings-summary",
category="post-build",
description="Show a summary of compiler warnings.",
)
@CommandArgument( "-C", "--directory",
default=None,
help="Change to a subdirectory of the build directory first.",
)
@CommandArgument( "report",
default=None,
nargs="?",
help="Warnings report to display. If not defined, show the most recent report.",
) def summary(command_context, directory=None, report=None):
database = get_warnings_database(command_context)
total = 0 for k, v in sorted_counts:
print("%d\t%s" % (v, k))
total += v
print("%d\tTotal" % total)
@Command( "warnings-list",
category="post-build",
description="Show a list of compiler warnings.",
)
@CommandArgument( "-C", "--directory",
default=None,
help="Change to a subdirectory of the build directory first.",
)
@CommandArgument( "--flags", default=None, nargs="+", help="Which warnings flags to match."
)
@CommandArgument( "report",
default=None,
nargs="?",
help="Warnings report to display. If not defined, show the most recent report.",
) def list_warnings(command_context, directory=None, flags=None, report=None):
database = get_warnings_database(command_context)
@Command("gtest", category="testing", description="Run GTest unit tests (C++ tests).")
@CommandArgument( "gtest_filter",
default="*",
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)." "Test names are of the format SUITE.NAME. Use --list-tests to see all.",
)
@CommandArgument("--list-tests", action="store_true", help="list all available tests")
@CommandArgument( "--jobs", "-j",
default="1",
nargs="?",
metavar="jobs",
type=int,
help="Run the tests in parallel using multiple processes.",
)
@CommandArgument( "--tbpl-parser", "-t",
action="store_true",
help="Output test results in a format that can be parsed by TBPL.",
)
@CommandArgument( "--shuffle", "-s",
action="store_true",
help="Randomize the execution order of tests.",
)
@CommandArgument( "--enable-webrender",
action="store_true",
default=False,
dest="enable_webrender",
help="Enable the WebRender compositor in Gecko.",
)
@CommandArgument( "--enable-inc-origin-init",
action="store_true",
default=False,
dest="enable_inc_origin_init",
help="Enable the incremental origin initialization in Gecko.",
)
@CommandArgumentGroup("filter sets")
@CommandArgument( "--filter-set",
default=None,
dest="filter_set",
type=str,
group="filter sets",
help="Use a predefined gtest filter (this overrides the gtest_filter).",
)
@CommandArgument( "--list-filter-sets",
action="store_true",
dest="list_filter_sets",
group="filter sets",
help="List available predefined gtest filters.",
)
@CommandArgumentGroup("Android")
@CommandArgument( "--package",
default="org.mozilla.geckoview.test_runner",
group="Android",
help="Package name of test app.",
)
@CommandArgument( "--adbpath", dest="adb_path", group="Android", help="Path to adb binary."
)
@CommandArgument( "--deviceSerial",
dest="device_serial",
group="Android",
help="adb serial number of remote device. " "Required when more than one device is connected to the host. " "Use 'adb devices' to see connected devices.",
)
@CommandArgument( "--remoteTestRoot",
dest="remote_test_root",
group="Android",
help="Remote directory to use as test root (eg. /data/local/tmp/test_root).",
)
@CommandArgument( "--libxul", dest="libxul_path", group="Android", help="Path to gtest libxul.so."
)
@CommandArgument( "--no-install",
action="store_true",
default=False,
group="Android",
help="Skip the installation of the APK.",
)
@CommandArgumentGroup("debugging")
@CommandArgument( "--debug",
action="store_true",
group="debugging",
help="Enable the debugger. Not specifying a --debugger option will result in " "the default debugger being used.",
)
@CommandArgument( "--debugger",
default=None,
type=str,
group="debugging",
help="Name of debugger to use.",
)
@CommandArgument( "--debugger-args",
default=None,
metavar="params",
type=str,
group="debugging",
help="Command-line arguments to pass to the debugger itself; " "split as the Bourne shell would.",
) def gtest(
command_context,
shuffle,
jobs,
gtest_filter,
list_tests,
tbpl_parser,
enable_webrender,
enable_inc_origin_init,
filter_set,
list_filter_sets,
package,
adb_path,
device_serial,
remote_test_root,
libxul_path,
no_install,
debug,
debugger,
debugger_args,
): # We lazy build gtest because it's slow to link try:
command_context.config_environment except Exception:
print("Please run |./mach build| before |./mach gtest|.") return 1
res = command_context._mach_context.commands.dispatch( "build", command_context._mach_context, what=["recurse_gtest"]
) if res:
print("Could not build xul-gtest") return res
if command_context.substs.get("MOZ_WIDGET_TOOLKIT") == "cocoa":
command_context._run_make(
directory="browser/app", target="repackage", ensure_exit_code=True
)
if conditions.is_android(command_context): if jobs != 1:
print("--jobs is not supported on Android and will be ignored") if enable_inc_origin_init:
print( "--enable-inc-origin-init is not supported on Android and will" "be ignored"
) if filter_set:
print("--filter-set is not supported on Android and will be ignored") if list_filter_sets:
print("--list-filter-sets is not supported on Android and will be ignored") if debug or debugger or debugger_args:
print("--debug options are not supported on Android and will be ignored") from mozrunner.devices.android_device import InstallIntent
if (
package or adb_path or device_serial or remote_test_root or libxul_path or no_install
):
print("One or more Android-only options will be ignored")
# Note: we must normalize the path here so that gtest on Windows sees # a MOZ_GMP_PATH which has only Windows dir seperators, because # nsIFile cannot open the paths with non-Windows dir seperators.
xre_path = os.path.join(os.path.normpath(command_context.topobjdir), "dist", "bin")
gtest_env["MOZ_XRE_DIR"] = xre_path
gtest_env["MOZ_GMP_PATH"] = os.pathsep.join(
os.path.join(xre_path, p, "1.0") for p in ("gmp-fake", "gmp-fakeopenh264")
)
gtest_env["MOZ_RUN_GTEST"] = "True"
if shuffle:
gtest_env["GTEST_SHUFFLE"] = "True"
if tbpl_parser:
gtest_env["MOZ_TBPL_PARSER"] = "True"
@Command( "package",
category="post-build",
description="Package the built product for distribution as an APK, DMG, etc.",
)
@CommandArgument( "-v", "--verbose",
action="store_true",
help="Verbose output for what commands the packaging process is running.",
) def package(command_context, verbose=False): """Package the built product for distribution."""
ret = command_context._run_make(
directory=".", target="package", silent=not verbose, ensure_exit_code=False
) if ret == 0:
command_context.notify("Packaging complete")
_print_package_name(command_context) return ret
@Command( "install",
category="post-build",
conditions=[conditions.has_build],
parser=setup_install_parser,
description="Install the package on the machine (or device in the case of Android).",
) def install(command_context, **kwargs): """Install a package.""" if conditions.is_android(command_context): from mozrunner.devices.android_device import (
InstallIntent,
verify_android_device,
)
ret = (
verify_android_device(
command_context,
install=InstallIntent.YES,
**kwargs,
)
== 0
) else:
ret = command_context._run_make(
directory=".", target="install", ensure_exit_code=False
)
if ret == 0:
command_context.notify("Install complete") return ret
def _get_android_run_parser():
parser = argparse.ArgumentParser()
group = parser.add_argument_group("The compiled program")
group.add_argument("url", nargs="?", default=None, help="URL to open")
group.add_argument( "--app",
default="org.mozilla.geckoview_example",
help="Android package to run (default: org.mozilla.geckoview_example)",
)
group.add_argument( "--intent",
default="android.intent.action.VIEW",
help="Android intent action to launch with " "(default: android.intent.action.VIEW)",
)
group.add_argument( "--setenv",
dest="env",
action="append",
default=[],
help="Set target environment variable, like FOO=BAR",
)
group.add_argument( "--profile", "-P",
default=None,
help="Path to Gecko profile, like /path/to/host/profile " "or /path/to/target/profile",
)
group.add_argument( "--aab",
action="store_true",
default=False,
help="Install app as Android App Bundle (AAB).",
)
group.add_argument( "--no-install",
action="store_true",
default=False,
help="Do not try to install application on device before running " "(default: False)",
)
group.add_argument( "--no-wait",
action="store_true",
default=False,
help="Do not wait for application to start before returning " "(default: False)",
)
group.add_argument( "--enable-fission",
action="store_true",
help="Run the program with Fission (site isolation) enabled.",
)
group.add_argument( "--fail-if-running",
action="store_true",
default=False,
help="Fail if application is already running (default: False)",
)
group.add_argument( "--restart",
action="store_true",
default=False,
help="Stop the application if it is already running (default: False)",
)
group = parser.add_argument_group("Debugging")
group.add_argument("--debug", action="store_true", help="Enable the lldb debugger.")
group.add_argument( "--debugger",
default=None,
type=str,
help="Name of lldb compatible debugger to use.",
)
group.add_argument( "--debugger-args",
default=None,
metavar="params",
type=str,
help="Command-line arguments to pass to the debugger itself; " "split as the Bourne shell would.",
)
group.add_argument( "--no-attach",
action="store_true",
default=False,
help="Start the debugging servers on the device but do not " "attach any debuggers.",
)
group.add_argument( "--use-existing-process",
action="store_true",
default=False,
help="Select an existing process to debug.",
) return parser
def _get_jsshell_run_parser():
parser = argparse.ArgumentParser()
group = parser.add_argument_group("the compiled program")
group.add_argument( "params",
nargs="...",
default=[],
help="Command-line arguments to be passed through to the program. Not " "specifying a --profile or -P option will result in a temporary profile " "being used.",
)
group = parser.add_argument_group("debugging")
group.add_argument( "--debug",
action="store_true",
help="Enable the debugger. Not specifying a --debugger option will result " "in the default debugger being used.",
)
group.add_argument( "--debugger", default=None, type=str, help="Name of debugger to use."
)
group.add_argument( "--debugger-args",
default=None,
metavar="params",
type=str,
help="Command-line arguments to pass to the debugger itself; " "split as the Bourne shell would.",
)
group.add_argument( "--debugparams",
action=StoreDebugParamsAndWarnAction,
default=None,
type=str,
dest="debugger_args",
help=argparse.SUPPRESS,
)
return parser
def _get_desktop_run_parser():
parser = argparse.ArgumentParser()
group = parser.add_argument_group("the compiled program")
group.add_argument( "params",
nargs="...",
default=[],
help="Command-line arguments to be passed through to the program. Not " "specifying a --profile or -P option will result in a temporary profile " "being used.",
)
group.add_argument("--packaged", action="store_true", help="Run a packaged build.")
group.add_argument( "--app", help="Path to executable to run (default: output of ./mach build)"
)
group.add_argument( "--background", "-b",
action="store_true",
help="Do not pass the --foreground argument by default on Mac.",
)
group.add_argument( "--noprofile", "-n",
action="store_true",
help="Do not pass the --profile argument by default.",
)
group.add_argument( "--disable-e10s",
action="store_true",
help="Run the program with electrolysis disabled.",
)
group.add_argument( "--enable-crash-reporter",
action="store_true",
help="Run the program with the crash reporter enabled.",
)
group.add_argument( "--disable-fission",
action="store_true",
help="Run the program with Fission (site isolation) disabled.",
)
group.add_argument( "--setpref",
action="append",
default=[],
help="Set the specified pref before starting the program. Can be set " "multiple times. Prefs can also be set in ~/.mozbuild/machrc in the " "[runprefs] section - see `./mach settings` for more information.",
)
group.add_argument( "--temp-profile",
action="store_true",
help="Run the program using a new temporary profile created inside " "the objdir.",
)
group.add_argument( "--macos-open",
action="store_true",
help="On macOS, run the program using the open(1) command. Per open(1), " "the browser is launched \"just asif you had double-clicked the file's " 'icon". The browser can not be launched under a debugger with this ' "option.",
)
group = parser.add_argument_group("debugging")
group.add_argument( "--debug",
action="store_true",
help="Enable the debugger. Not specifying a --debugger option will result " "in the default debugger being used.",
)
group.add_argument( "--debugger", default=None, type=str, help="Name of debugger to use."
)
group.add_argument( "--debugger-args",
default=None,
metavar="params",
type=str,
help="Command-line arguments to pass to the debugger itself; " "split as the Bourne shell would.",
)
group.add_argument( "--debugparams",
action=StoreDebugParamsAndWarnAction,
default=None,
type=str,
dest="debugger_args",
help=argparse.SUPPRESS,
)
group = parser.add_argument_group("DMD")
group.add_argument( "--dmd",
action="store_true",
help="Enable DMD. The following arguments have no effect without this.",
)
group.add_argument( "--mode",
choices=["live", "dark-matter", "cumulative", "scan"],
help="Profiling mode. The default is 'dark-matter'.",
)
group.add_argument( "--stacks",
choices=["partial", "full"],
help="Allocation stack trace coverage. The default is 'partial'.",
)
group.add_argument( "--show-dump-stats", action="store_true", help="Show stats when doing dumps."
)
return parser
def setup_run_parser():
build = MozbuildObject.from_environment(cwd=here) if conditions.is_android(build): return _get_android_run_parser() if conditions.is_jsshell(build): return _get_jsshell_run_parser() return _get_desktop_run_parser()
@Command( "run",
category="post-build",
conditions=[conditions.has_build_or_shell],
parser=setup_run_parser,
description="Run the compiled program, possibly under a debugger or DMD.",
) def run(command_context, **kwargs): """Run the compiled program.""" if conditions.is_android(command_context): return _run_android(command_context, **kwargs) if conditions.is_jsshell(command_context): return _run_jsshell(command_context, **kwargs) return _run_desktop(command_context, **kwargs)
ifnot metadata.activity_name: raise RuntimeError("Application not recognized: {}".format(app))
# If we want to debug an existing process, we implicitly do not want # to kill it and pave over its installation with a new one. if debug and use_existing_process:
no_install = True
# `verify_android_device` respects `DEVICE_SERIAL` if it is set and sets it otherwise.
verify_android_device(
command_context,
app=metadata.package_name,
aab=aab,
debugger=debug,
install=InstallIntent.NO if no_install else InstallIntent.YES,
)
device_serial = os.environ.get("DEVICE_SERIAL") ifnot device_serial:
print("No ADB devices connected.") return 1
if debug: # This will terminate any existing processes, so we skip it when we # want to attach to an existing one. ifnot use_existing_process:
command_context.log(
logging.INFO, "run",
{"app": metadata.package_name}, "Setting {app} as the device debug app",
)
device.shell("am set-debug-app -w --persistent %s" % metadata.package_name) else: # Make sure that the app doesn't block waiting for jdb
device.shell("am clear-debug-app")
ifnot debug ornot use_existing_process:
args = [] if profile: if os.path.isdir(profile):
host_profile = profile # Always /data/local/tmp, rather than `device.test_root`, because # GeckoView only takes its configuration file from /data/local/tmp, # and we want to follow suit.
target_profile = "/data/local/tmp/{}-profile".format(
metadata.package_name
)
device.rm(target_profile, recursive=True, force=True)
device.push(host_profile, target_profile)
command_context.log(
logging.INFO, "run",
{ "host_profile": host_profile, "target_profile": target_profile,
}, 'Pushed profile from host "{host_profile}" to ' 'target "{target_profile}"',
) else:
target_profile = profile
command_context.log(
logging.INFO, "run",
{"target_profile": target_profile}, 'Using profile from target "{target_profile}"',
)
args = ["--profile", shlex_quote(target_profile)]
# FIXME: When android switches to using Fission by default, # MOZ_FORCE_DISABLE_FISSION will need to be configured correctly. if enable_fission:
env.append("MOZ_FORCE_ENABLE_FISSION=1")
extras = {} for i, e in enumerate(env):
extras["env{}".format(i)] = e if args:
extras["args"] = " ".join(args)
if env or args:
restart = True
if restart:
fail_if_running = False
command_context.log(
logging.INFO, "run",
{"app": metadata.package_name}, "Stopping {app} to ensure clean restart.",
)
device.stop_application(metadata.package_name)
# We'd prefer to log the actual `am start ...` command, but it's not trivial # to wire the device's logger to mach's logger.
command_context.log(
logging.INFO, "run",
{ "app": metadata.package_name, "activity_name": metadata.activity_name,
}, "Starting {app}/{activity_name}.",
)
# Give lldb-server a chance to start
command_context.log(
logging.INFO, "run",
{"msg": "Pausing to ensure lldb-server has started..."}, "{msg}",
)
time.sleep(1)
if use_existing_process:
def _is_geckoview_process(proc_name, pkg_name): ifnot proc_name.startswith(pkg_name): # Definitely not our package returnFalse if len(proc_name) == len(pkg_name): # Parent process from our package returnTrue if proc_name[len(pkg_name)] == ":": # Child process from our package returnTrue # Process name is a prefix of our package name returnFalse
# If we're going to attach to an existing process, we need to know # who we're attaching to. Obtain a list of all processes associated # with our desired app.
proc_list = [
proc[:-1] for proc in device.get_process_list() if _is_geckoview_process(proc[1], metadata.package_name)
]
ifnot proc_list:
command_context.log(
logging.ERROR, "run",
{"app": metadata.package_name}, "No existing {app} processes found",
) return 1 elif len(proc_list) == 1:
pid = proc_list[0][0] else: # Prompt the user to determine which process we should use
entries = [ "%2d: %6d %s" % (n, p[0], p[1]) for n, p in enumerate(proc_list, start=1)
]
prompt = "\n".join(["\nPlease select a process:\n"] + entries) + "\n\n"
valid_range = range(1, len(proc_list) + 1)
whileTrue:
response = int(input(prompt).strip()) if response in valid_range: break
command_context.log(
logging.ERROR, "run", {"msg": "Invalid response"}, "{msg}"
)
pid = proc_list[response - 1][0] else: # We're not using an existing process, so there should only be our # parent process at this time.
pids = device.pidof(app_name=metadata.package_name) if len(pids) != 1:
command_context.log(
logging.ERROR, "run",
{"msg": "Not sure which pid to attach to!"}, "{msg}",
) return 1
pid = pids[0]
command_context.log(
logging.INFO, "run", {"pid": str(pid)}, "Debuggee pid set to {pid}..."
)
if no_attach:
command_context.log(
logging.INFO, "run",
{"pid": str(pid), "url": lldb_connect_url}, "To debug native code, connect lldb to {url} and attach to pid {pid}",
)
command_context.log(
logging.INFO, "run",
{"port": str(local_jdb_port)}, "To debug Java code, connect jdb using tcp to localhost:{port}",
) return 0
# Beyond this point we want to be able to automatically clean up after ourselves, # so we enter the following try block. try:
command_context.log(
logging.INFO, "run", {"msg": "Starting debugger..."}, "{msg}"
)
ifnot use_existing_process: # The app is waiting for jdb to attach and will not continue running # until we do so. def _jdb_ping(local_jdb_port):
jdb_process = subprocess.Popen(
["jdb", "-attach", "localhost:%d" % local_jdb_port],
stdin=subprocess.PIPE,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
encoding="utf-8",
) # Wait a bit to provide enough time for jdb and lldb to connect # to the debuggee
time.sleep(5) # NOTE: jdb cannot detach while the debuggee is frozen in lldb, # so its process might not necessarily exit immediately once the # quit command has been issued.
jdb_process.communicate(input="quit\n")
# We run this in the background while lldb attaches in the foreground from threading import Thread
if use_existing_process:
continue_flag = "" else: # Tell lldb to continue after attaching; instead we'll break at # the initial SEGVHandler, similarly to how things work when we # attach using Android Studio. Doing this gives Android a chance # to dismiss the "Waiting for Debugger" dialog.
continue_flag = "-c "
try: # Write out our lldb startup commands to a temp file. We'll pass its # name to lldb on its command line. with tempfile.NamedTemporaryFile(
mode="wt", encoding="utf-8", newline="\n", delete=False
) as tmp:
tmp_lldb_start_script = tmp.name
tmp.write(
LLDBINIT.format(
obj_xul=obj_xul,
obj_mozglue=obj_mozglue,
obj_nss=obj_nss,
connect_url=lldb_connect_url,
continue_flag=continue_flag,
pid=pid,
)
)
if debug or debugger or debugger_args: if"INSIDE_EMACS"in os.environ:
command_context.log_manager.terminal_handler.setLevel(logging.WARNING)
import mozdebug
ifnot debugger: # No debugger name was provided. Look for the default ones on # current OS.
debugger = mozdebug.get_default_debugger_name(
mozdebug.DebuggerSearch.KeepLooking
)
if debugger:
debuggerInfo = mozdebug.get_debugger_info(debugger, debugger_args)
ifnot debugger ornot debuggerInfo:
print("Could not find a suitable debugger in your PATH.") return 1
try: if packaged:
binpath = command_context.get_binary_path(where="staged-package") else:
binpath = app or command_context.get_binary_path("app") except BinaryNotFoundException as e: if packaged:
ret = command_context._run_make(
directory=".",
target="stage-package",
silent=True,
ensure_exit_code=False,
)
if ret == 0: try:
binpath = command_context.get_binary_path(where="staged-package") except BinaryNotFoundException:
command_context.log(
logging.ERROR, "run", {"error": str(e)}, "ERROR: {error}"
)
ret = 1
if ret != 0:
command_context.log(
logging.INFO, "run",
{ "help": "It looks like your build failed to package properly? " "You can run |./mach package| to package it manually."
--> --------------------
--> maximum size reached
--> --------------------
Messung V0.5
¤ Dauer der Verarbeitung: 0.48 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 und die Messung sind noch experimentell.