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 shift 2
fi fi
;; ;;
--no-interactive) shift;;
--log-interval) shift;; --log-interval) shift;;
--log-no-times) shift;; --log-no-times) shift;;
-h) shift;; -h) shift;;

View File

@ -55,7 +55,7 @@ argument to automatically accept any license agreements.
.. code:: bash .. code:: bash
./mach bootstrap [ --no-interactive] ./mach [--no-interactive] bootstrap
- Choose option \`4. Firefox for Android\` for GeckoView development. - Choose option \`4. Firefox for Android\` for GeckoView development.
This will give you a version of Gecko configured for Android that has 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.settings = settings
self.log_manager = log_manager self.log_manager = log_manager
self.commands = commands self.commands = commands
self.is_interactive = None # Filled in after args are parsed
self.telemetry = telemetry self.telemetry = telemetry
self.command_attrs = {} self.command_attrs = {}

View File

@ -447,6 +447,12 @@ To see more help for a specific command, run:
if not hasattr(args, "mach_handler"): if not hasattr(args, "mach_handler"):
raise MachError("ArgumentParser result missing mach handler info.") 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") handler = getattr(args, "mach_handler")
report_invocation_metrics(context.telemetry, handler.name) 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 " "than relative time. Note that this is NOT execution time "
"if there are parallel operations.", "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 suppress_log_by_default = False
if "INSIDE_EMACS" in os.environ: if "INSIDE_EMACS" in os.environ:
suppress_log_by_default = True 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): 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: if application_choice:
args += ["--application-choice", application_choice] args += ["--application-choice", application_choice]
if no_interactive:
args += ["--no-interactive"]
if no_system_changes: if no_system_changes:
args += ["--no-system-changes"] args += ["--no-system-changes"]
print("Running `%s`" % " ".join(args)) print("Running `%s`" % " ".join(args))
return subprocess.call(args, cwd=srcdir) return subprocess.call(args, cwd=srcdir)

View File

@ -466,11 +466,30 @@ class BaseBootstrapper(object):
self.run_as_root(command) self.run_as_root(command)
def prompt_int(self, prompt, low, high): def prompt_int(self, prompt, low, high, default=None):
""" Prompts the user with prompt and requires an integer between low and high. """ """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: while True:
choice = input(prompt)
if choice == "" and default is not None:
return default
try: try:
choice = int(input(prompt)) choice = int(choice)
if low <= choice <= high: if low <= choice <= high:
return choice return choice
except ValueError: except ValueError:
@ -479,18 +498,20 @@ class BaseBootstrapper(object):
def prompt_yesno(self, prompt): def prompt_yesno(self, prompt):
""" Prompts the user with prompt and requires a yes/no answer.""" """ Prompts the user with prompt and requires a yes/no answer."""
valid = False if self.no_interactive:
while not valid: print(prompt)
print('Selecting "Y" because context is not interactive.')
return True
while True:
choice = input(prompt + " (Yn): ").strip().lower()[:1] choice = input(prompt + " (Yn): ").strip().lower()[:1]
if choice == "": if choice == "":
choice = "y" return True
if choice not in ("y", "n"): elif choice in ("y", "n"):
print("ERROR! Please enter y or n!")
else:
valid = True
return choice == "y" return choice == "y"
print("ERROR! Please enter y or n!")
def _ensure_package_manager_updated(self): def _ensure_package_manager_updated(self):
if self.package_manager_updated: if self.package_manager_updated:
return return

View File

@ -365,11 +365,13 @@ class Bootstrapper(object):
labels = [ labels = [
"%s. %s" % (i, name) for i, name in enumerate(APPLICATIONS.keys(), 1) "%s. %s" % (i, name) for i, name in enumerate(APPLICATIONS.keys(), 1)
] ]
prompt = APPLICATION_CHOICE % "\n".join( choices = [" {} [default]".format(labels[0])]
" {}".format(label) for label in labels choices += [" {}".format(label) for label in labels[1:]]
) prompt = APPLICATION_CHOICE % "\n".join(choices)
prompt_choice = self.instance.prompt_int( 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] name, application = list(APPLICATIONS.items())[prompt_choice - 1]
elif self.choice in APPLICATIONS.keys(): elif self.choice in APPLICATIONS.keys():
@ -428,7 +430,6 @@ class Bootstrapper(object):
# Possibly configure Mercurial, but not if the current checkout or repo # Possibly configure Mercurial, but not if the current checkout or repo
# type is Git. # type is Git.
if hg_installed and checkout_type == "hg": if hg_installed and checkout_type == "hg":
configure_hg = False
if not self.instance.no_interactive: if not self.instance.no_interactive:
configure_hg = self.instance.prompt_yesno(prompt=CONFIGURE_MERCURIAL) configure_hg = self.instance.prompt_yesno(prompt=CONFIGURE_MERCURIAL)
else: 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. in files being placed in /usr/local/bin and /usr/local/lib.
How would you like to continue? 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 2. Install a legacy Mercurial via apt
3. Do not install Mercurial 3. Do not install Mercurial
Your choice: """ Your choice: """

View File

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