gecko-dev/tools/tryselect/cli.py
Connor Sheehan c1c2126fce Bug 1835962: add support for mach try to submit to Lando r=zeid,mach-reviewers,ahochheiden
Add support for submitting stacks of commits to Lando for queueing
on Try, instead of pushing to hg.mozilla.org directly. This patch
implements the Device Code Authorization flow for Auth0, simple changeset
discovery and patch gathering, and submissing to Lando via HTTP POST.

Add a `try.txt` virtualenv site that contains packages from the common
virtualenv as well as the `auth0-python` package for verifying Auth0
JWTs. Use this new virtualenv for `mach try` and related subcommands.
Add a `--push-to-lando` flag that controls whether the push will be made
via the Lando API or using the VCS to hg.mozilla.org directly.

Create a `lando.py` module in the `tryselect` package that handles the
details around submitting to Lando. Authentication is handled by the
Device Code Authorization flow, and the returned access token is saved
to the mozbuild state directory. Auth0 details are added to the `.lando.ini`
file in the repo root, and a `LANDO_TRY_USE_DEV` environment variable can
be set to control submitting to the prod or dev Lando environments.
This module also includes patch stack gathering and discovery via `mozversioncontrol`.

mozversioncontrol's `Repository` subclass is extended with helper functions
to gather patch files from Mercurial and Git. We also add a `try_config_commit`
context manager that creates a temporary commit to hold try syntax commit
messages and/or a `try_task_config.json` file, which is removed from version
control on submission.

The `mach try` cram tests now use a separate virtualenv that must be built
before running, causing unexpected output in the tests. Run `mach try --help`
in the test setup to force the virtualenv to be built before running any
test.

`mach try chooser` would previously install packages for a small web application
from a `requirements.txt` file when run. Install these via the new `try.txt`
site environment instead. The required dependencies are also added to the
`python-test.txt` site since they will cause some tests to fail due to the
missing dependencies.

Differential Revision: https://phabricator.services.mozilla.com/D187641
2023-10-02 14:31:43 +00:00

176 lines
5.2 KiB
Python

# 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 os
import subprocess
import tempfile
from argparse import ArgumentParser
from .task_config import all_task_configs
COMMON_ARGUMENT_GROUPS = {
"push": [
[
["-m", "--message"],
{
"const": "editor",
"default": "{msg}",
"nargs": "?",
"help": "Use the specified commit message, or create it in your "
"$EDITOR if blank. Defaults to computed message.",
},
],
[
["--closed-tree"],
{
"action": "store_true",
"default": False,
"help": "Push despite a closed try tree",
},
],
[
["--push-to-lando"],
{
"action": "store_true",
"default": False,
"help": "Submit changes for Lando to push to try.",
},
],
],
"preset": [
[
["--save"],
{
"default": None,
"help": "Save selection for future use with --preset.",
},
],
[
["--preset"],
{
"default": None,
"help": "Load a saved selection.",
},
],
[
["--list-presets"],
{
"action": "store_const",
"dest": "preset_action",
"const": "list",
"default": None,
"help": "List available preset selections.",
},
],
[
["--edit-presets"],
{
"action": "store_const",
"dest": "preset_action",
"const": "edit",
"default": None,
"help": "Edit the preset file.",
},
],
],
"task": [
[
["--full"],
{
"action": "store_true",
"default": False,
"help": "Use the full set of tasks as input to fzf (instead of "
"target tasks).",
},
],
[
["-p", "--parameters"],
{
"default": None,
"help": "Use the given parameters.yml to generate tasks, "
"defaults to a default set of parameters",
},
],
],
}
NO_PUSH_ARGUMENT_GROUP = [
[
["--stage-changes"],
{
"dest": "stage_changes",
"action": "store_true",
"help": "Locally stage changes created by this command but do not "
"push to try.",
},
],
[
["--no-push"],
{
"dest": "dry_run",
"action": "store_true",
"help": "Do not push to try as a result of running this command (if "
"specified this command will only print calculated try "
"syntax and selection info and not change files).",
},
],
]
class BaseTryParser(ArgumentParser):
name = "try"
common_groups = ["push", "preset"]
arguments = []
task_configs = []
def __init__(self, *args, **kwargs):
ArgumentParser.__init__(self, *args, **kwargs)
group = self.add_argument_group("{} arguments".format(self.name))
for cli, kwargs in self.arguments:
group.add_argument(*cli, **kwargs)
for name in self.common_groups:
group = self.add_argument_group("{} arguments".format(name))
arguments = COMMON_ARGUMENT_GROUPS[name]
# Preset arguments are all mutually exclusive.
if name == "preset":
group = group.add_mutually_exclusive_group()
for cli, kwargs in arguments:
group.add_argument(*cli, **kwargs)
if name == "push":
group_no_push = group.add_mutually_exclusive_group()
arguments = NO_PUSH_ARGUMENT_GROUP
for cli, kwargs in arguments:
group_no_push.add_argument(*cli, **kwargs)
group = self.add_argument_group("task configuration arguments")
self.task_configs = {c: all_task_configs[c]() for c in self.task_configs}
for cfg in self.task_configs.values():
cfg.add_arguments(group)
def validate(self, args):
if hasattr(args, "message"):
if args.message == "editor":
if "EDITOR" not in os.environ:
self.error(
"must set the $EDITOR environment variable to use blank --message"
)
with tempfile.NamedTemporaryFile(mode="r") as fh:
subprocess.call([os.environ["EDITOR"], fh.name])
args.message = fh.read().strip()
if "{msg}" not in args.message:
args.message = "{}\n\n{}".format(args.message, "{msg}")
def parse_known_args(self, *args, **kwargs):
args, remainder = ArgumentParser.parse_known_args(self, *args, **kwargs)
self.validate(args)
return args, remainder