Bug 1695272: Move --no-interactive to global mach args r=firefox-build-system-reviewers,glandium

Updates `./mach bootstrap` to use `--no-interactive` from global args.
Ensures all bootstrap prompts have a default option.

Differential Revision: https://phabricator.services.mozilla.com/D106814
This commit is contained in:
Mitchell Hentges 2021-03-12 16:07:11 +00:00
parent 8133abcb41
commit b7e0dfe1d1
9 changed files with 68 additions and 31 deletions

1
mach
View File

@ -62,6 +62,7 @@ get_command() {
shift 2
fi
;;
--no-interactive) shift;;
--log-interval) shift;;
--log-no-times) shift;;
-h) shift;;

View File

@ -55,7 +55,7 @@ argument to automatically accept any license agreements.
.. code:: bash
./mach bootstrap [ --no-interactive]
./mach [--no-interactive] bootstrap
- Choose option \`4. Firefox for Android\` for GeckoView development.
This will give you a version of Gecko configured for Android that has

View File

@ -23,6 +23,7 @@ class CommandContext(object):
self.settings = settings
self.log_manager = log_manager
self.commands = commands
self.is_interactive = None # Filled in after args are parsed
self.telemetry = telemetry
self.command_attrs = {}

View File

@ -447,6 +447,12 @@ To see more help for a specific command, run:
if not hasattr(args, "mach_handler"):
raise MachError("ArgumentParser result missing mach handler info.")
context.is_interactive = (
args.is_interactive
and sys.__stdout__.isatty()
and sys.__stderr__.isatty()
and not os.environ.get("MOZ_AUTOMATION", None)
)
handler = getattr(args, "mach_handler")
report_invocation_metrics(context.telemetry, handler.name)
@ -643,6 +649,14 @@ To see more help for a specific command, run:
"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

View File

@ -285,13 +285,20 @@ def clone(vcs, no_interactive):
def bootstrap(srcdir, application_choice, no_interactive, no_system_changes):
args = [sys.executable, os.path.join(srcdir, "mach"), "bootstrap"]
args = [sys.executable, os.path.join(srcdir, "mach")]
if no_interactive:
# --no-interactive is a global argument, not a command argument,
# so it needs to be specified before "bootstrap" is appended.
args += ["--no-interactive"]
args += ["bootstrap"]
if application_choice:
args += ["--application-choice", application_choice]
if no_interactive:
args += ["--no-interactive"]
if no_system_changes:
args += ["--no-system-changes"]
print("Running `%s`" % " ".join(args))
return subprocess.call(args, cwd=srcdir)

View File

@ -466,11 +466,30 @@ class BaseBootstrapper(object):
self.run_as_root(command)
def prompt_int(self, prompt, low, high):
""" Prompts the user with prompt and requires an integer between low and high. """
def prompt_int(self, prompt, low, high, default=None):
"""Prompts the user with prompt and requires an integer between low and high.
If the user doesn't select an option and a default isn't provided, then
the lowest option is used. This is because some option must be implicitly
selected if mach is invoked with "--no-interactive"
"""
if default is not None:
assert isinstance(default, int)
assert low <= default <= high
else:
default = low
if self.no_interactive:
print(prompt)
print('Selecting "{}" because context is not interactive.'.format(default))
return default
while True:
choice = input(prompt)
if choice == "" and default is not None:
return default
try:
choice = int(input(prompt))
choice = int(choice)
if low <= choice <= high:
return choice
except ValueError:
@ -479,18 +498,20 @@ class BaseBootstrapper(object):
def prompt_yesno(self, prompt):
""" Prompts the user with prompt and requires a yes/no answer."""
valid = False
while not valid:
if self.no_interactive:
print(prompt)
print('Selecting "Y" because context is not interactive.')
return True
while True:
choice = input(prompt + " (Yn): ").strip().lower()[:1]
if choice == "":
choice = "y"
if choice not in ("y", "n"):
print("ERROR! Please enter y or n!")
else:
valid = True
return True
elif choice in ("y", "n"):
return choice == "y"
print("ERROR! Please enter y or n!")
def _ensure_package_manager_updated(self):
if self.package_manager_updated:
return

View File

@ -365,11 +365,13 @@ class Bootstrapper(object):
labels = [
"%s. %s" % (i, name) for i, name in enumerate(APPLICATIONS.keys(), 1)
]
prompt = APPLICATION_CHOICE % "\n".join(
" {}".format(label) for label in labels
)
choices = [" {} [default]".format(labels[0])]
choices += [" {}".format(label) for label in labels[1:]]
prompt = APPLICATION_CHOICE % "\n".join(choices)
prompt_choice = self.instance.prompt_int(
prompt=prompt, low=1, high=len(APPLICATIONS)
prompt=prompt,
low=1,
high=len(APPLICATIONS),
)
name, application = list(APPLICATIONS.items())[prompt_choice - 1]
elif self.choice in APPLICATIONS.keys():
@ -428,7 +430,6 @@ class Bootstrapper(object):
# Possibly configure Mercurial, but not if the current checkout or repo
# type is Git.
if hg_installed and checkout_type == "hg":
configure_hg = False
if not self.instance.no_interactive:
configure_hg = self.instance.prompt_yesno(prompt=CONFIGURE_MERCURIAL)
else:

View File

@ -20,7 +20,7 @@ Mercurial via the "pip" Python packaging utility. This will likely result
in files being placed in /usr/local/bin and /usr/local/lib.
How would you like to continue?
1. Install a modern Mercurial via pip (recommended)
1. Install a modern Mercurial via pip [default]
2. Install a legacy Mercurial via apt
3. Do not install Mercurial
Your choice: """

View File

@ -32,26 +32,18 @@ class Bootstrap(MachCommandBase):
help="Pass in an application choice instead of using the default "
"interactive prompt.",
)
@CommandArgument(
"--no-interactive",
dest="no_interactive",
action="store_true",
help="Answer yes to any (Y/n) interactive prompts.",
)
@CommandArgument(
"--no-system-changes",
dest="no_system_changes",
action="store_true",
help="Only execute actions that leave the system " "configuration alone.",
)
def bootstrap(
self, application_choice=None, no_interactive=False, no_system_changes=False
):
def bootstrap(self, application_choice=None, no_system_changes=False):
from mozboot.bootstrap import Bootstrapper
bootstrapper = Bootstrapper(
choice=application_choice,
no_interactive=no_interactive,
no_interactive=not self._mach_context.is_interactive,
no_system_changes=no_system_changes,
mach_context=self._mach_context,
)