Bug 1782261 [wpt PR 35272] - Create toggles on elements when specified by the 'toggle...
[gecko.git] / tools / tryselect / mach_commands.py
blobaba215d9ad34f925f964dbf44e44bb617411adaf
1 # This Source Code Form is subject to the terms of the Mozilla Public
2 # License, v. 2.0. If a copy of the MPL was not distributed with this
3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 import argparse
7 import importlib
8 import os
9 import sys
11 from mach.decorators import (
12 Command,
13 SettingsProvider,
14 SubCommand,
16 from mach.util import get_state_dir
17 from mozbuild.base import BuildEnvironmentNotFoundException
18 from mozbuild.util import memoize
21 CONFIG_ENVIRONMENT_NOT_FOUND = """
22 No config environment detected. This means we are unable to properly
23 detect test files in the specified paths or tags. Please run:
25 $ mach configure
27 and try again.
28 """.lstrip()
31 class get_parser:
32 def __init__(self, selector):
33 self.selector = selector
35 def __call__(self):
36 mod = importlib.import_module("tryselect.selectors.{}".format(self.selector))
37 return getattr(mod, "{}Parser".format(self.selector.capitalize()))()
40 def generic_parser():
41 from tryselect.cli import BaseTryParser
43 parser = BaseTryParser()
44 parser.add_argument("argv", nargs=argparse.REMAINDER)
45 return parser
48 @SettingsProvider
49 class TryConfig:
50 @classmethod
51 def config_settings(cls):
52 from mach.registrar import Registrar
54 desc = (
55 "The default selector to use when running `mach try` without a subcommand."
57 choices = Registrar.command_handlers["try"].subcommand_handlers.keys()
59 return [
60 ("try.default", "string", desc, "auto", {"choices": choices}),
62 "try.maxhistory",
63 "int",
64 "Maximum number of pushes to save in history.",
65 10,
70 def init(command_context):
71 from tryselect import push
73 push.MAX_HISTORY = command_context._mach_context.settings["try"]["maxhistory"]
76 @memoize
77 def presets(command_context):
78 from tryselect.preset import MergedHandler
80 # Create our handler using both local and in-tree presets. The first
81 # path in this list will be treated as the 'user' file for the purposes
82 # of saving and editing. All subsequent paths are 'read-only'. We check
83 # an environment variable first for testing purposes.
84 if os.environ.get("MACH_TRY_PRESET_PATHS"):
85 preset_paths = os.environ["MACH_TRY_PRESET_PATHS"].split(os.pathsep)
86 else:
87 preset_paths = [
88 os.path.join(get_state_dir(), "try_presets.yml"),
89 os.path.join(
90 command_context.topsrcdir, "tools", "tryselect", "try_presets.yml"
94 return MergedHandler(*preset_paths)
97 def handle_presets(
98 command_context, preset_action=None, save=None, preset=None, **kwargs
100 """Handle preset related arguments.
102 This logic lives here so that the underlying selectors don't need
103 special preset handling. They can all save and load presets the same
104 way.
106 from tryselect.util.dicttools import merge
108 user_presets = presets(command_context).handlers[0]
109 if preset_action == "list":
110 presets(command_context).list()
111 sys.exit()
113 if preset_action == "edit":
114 user_presets.edit()
115 sys.exit()
117 parser = command_context._mach_context.handler.parser
118 subcommand = command_context._mach_context.handler.subcommand
119 if "preset" not in parser.common_groups:
120 return kwargs
122 default = parser.get_default
123 if save:
124 selector = (
125 subcommand or command_context._mach_context.settings["try"]["default"]
128 # Only save non-default values for simplicity.
129 kwargs = {k: v for k, v in kwargs.items() if v != default(k)}
130 user_presets.save(save, selector=selector, **kwargs)
131 print("preset saved, run with: --preset={}".format(save))
132 sys.exit()
134 if preset:
135 if preset not in presets(command_context):
136 command_context._mach_context.parser.error(
137 "preset '{}' does not exist".format(preset)
140 name = preset
141 preset = presets(command_context)[name]
142 selector = preset.pop("selector")
143 preset.pop("description", None) # description isn't used by any selectors
145 if not subcommand:
146 subcommand = selector
147 elif subcommand != selector:
148 print(
149 "error: preset '{}' exists for a different selector "
150 "(did you mean to run 'mach try {}' instead?)".format(name, selector)
152 sys.exit(1)
154 # Order of precedence is defaults -> presets -> cli. Configuration
155 # from the right overwrites configuration from the left.
156 defaults = {}
157 nondefaults = {}
158 for k, v in kwargs.items():
159 if v == default(k):
160 defaults[k] = v
161 else:
162 nondefaults[k] = v
164 kwargs = merge(defaults, preset, nondefaults)
166 return kwargs
169 def handle_try_config(command_context, **kwargs):
170 from tryselect.util.dicttools import merge
172 to_validate = []
173 kwargs.setdefault("try_config", {})
174 for cls in command_context._mach_context.handler.parser.task_configs.values():
175 try_config = cls.try_config(**kwargs)
176 if try_config is not None:
177 to_validate.append(cls)
178 kwargs["try_config"] = merge(kwargs["try_config"], try_config)
180 for name in cls.dests:
181 del kwargs[name]
183 # Validate task_configs after they have all been parsed to avoid
184 # depending on the order they were processed.
185 for cls in to_validate:
186 cls.validate(**kwargs)
187 return kwargs
190 def run(command_context, **kwargs):
191 kwargs = handle_presets(command_context, **kwargs)
193 if command_context._mach_context.handler.parser.task_configs:
194 kwargs = handle_try_config(command_context, **kwargs)
196 mod = importlib.import_module(
197 "tryselect.selectors.{}".format(
198 command_context._mach_context.handler.subcommand
201 return mod.run(**kwargs)
204 @Command(
205 "try",
206 category="ci",
207 description="Push selected tasks to the try server",
208 parser=generic_parser,
210 def try_default(command_context, argv=None, **kwargs):
211 """Push selected tests to the try server.
213 The |mach try| command is a frontend for scheduling tasks to
214 run on try server using selectors. A selector is a subcommand
215 that provides its own set of command line arguments and are
216 listed below.
218 If no subcommand is specified, the `auto` selector is run by
219 default. Run |mach try auto --help| for more information on
220 scheduling with the `auto` selector.
222 init(command_context)
223 subcommand = command_context._mach_context.handler.subcommand
224 # We do special handling of presets here so that `./mach try --preset foo`
225 # works no matter what subcommand 'foo' was saved with.
226 preset = kwargs["preset"]
227 if preset:
228 if preset not in presets(command_context):
229 command_context._mach_context.handler.parser.error(
230 "preset '{}' does not exist".format(preset)
233 subcommand = presets(command_context)[preset]["selector"]
235 sub = subcommand or command_context._mach_context.settings["try"]["default"]
236 return command_context._mach_context.commands.dispatch(
237 "try", command_context._mach_context, subcommand=sub, argv=argv, **kwargs
241 @SubCommand(
242 "try",
243 "fuzzy",
244 description="Select tasks on try using a fuzzy finder",
245 parser=get_parser("fuzzy"),
247 def try_fuzzy(command_context, **kwargs):
248 """Select which tasks to run with a fuzzy finding interface (fzf).
250 When entering the fzf interface you'll be confronted by two panes. The
251 one on the left contains every possible task you can schedule, the one
252 on the right contains the list of selected tasks. In other words, the
253 tasks that will be scheduled once you you press <enter>.
255 At first fzf will automatically select whichever task is under your
256 cursor, which simplifies the case when you are looking for a single
257 task. But normally you'll want to select many tasks. To accomplish
258 you'll generally start by typing a query in the search bar to filter
259 down the list of tasks (see Extended Search below). Then you'll either:
261 A) Move the cursor to each task you want and press <tab> to select it.
262 Notice it now shows up in the pane to the right.
266 B) Press <ctrl-a> to select every task that matches your filter.
268 You can delete your query, type a new one and select further tasks as
269 many times as you like. Once you are happy with your selection, press
270 <enter> to push the selected tasks to try.
272 All selected task labels and their dependencies will be scheduled. This
273 means you can select a test task and its build will automatically be
274 filled in.
277 Keyboard Shortcuts
278 ------------------
280 When in the fuzzy finder interface, start typing to filter down the
281 task list. Then use the following keyboard shortcuts to select tasks:
283 Ctrl-K / Up => Move cursor up
284 Ctrl-J / Down => Move cursor down
285 Tab => Select task + move cursor down
286 Shift-Tab => Select task + move cursor up
287 Ctrl-A => Select all currently filtered tasks
288 Ctrl-D => De-select all currently filtered tasks
289 Ctrl-T => Toggle select all currently filtered tasks
290 Alt-Bspace => Clear query from input bar
291 Enter => Accept selection and exit
292 Ctrl-C / Esc => Cancel selection and exit
293 ? => Toggle preview pane
295 There are many more shortcuts enabled by default, you can also define
296 your own shortcuts by setting `--bind` in the $FZF_DEFAULT_OPTS
297 environment variable. See `man fzf` for more info.
300 Extended Search
301 ---------------
303 When typing in search terms, the following modifiers can be applied:
305 'word: exact match (line must contain the literal string "word")
306 ^word: exact prefix match (line must start with literal "word")
307 word$: exact suffix match (line must end with literal "word")
308 !word: exact negation match (line must not contain literal "word")
309 'a | 'b: OR operator (joins two exact match operators together)
311 For example:
313 ^start 'exact | !ignore fuzzy end$
316 Documentation
317 -------------
319 For more detailed documentation, please see:
320 https://firefox-source-docs.mozilla.org/tools/try/selectors/fuzzy.html
322 init(command_context)
323 if kwargs.pop("interactive"):
324 kwargs["query"].append("INTERACTIVE")
326 if kwargs.pop("intersection"):
327 kwargs["intersect_query"] = kwargs["query"]
328 del kwargs["query"]
330 if kwargs.get("save") and not kwargs.get("query"):
331 # If saving preset without -q/--query, allow user to use the
332 # interface to build the query.
333 kwargs_copy = kwargs.copy()
334 kwargs_copy["push"] = False
335 kwargs_copy["save"] = None
336 kwargs["query"] = run(command_context, save_query=True, **kwargs_copy)
337 if not kwargs["query"]:
338 return
340 if kwargs.get("paths"):
341 kwargs["test_paths"] = kwargs["paths"]
343 return run(command_context, **kwargs)
346 @SubCommand(
347 "try",
348 "chooser",
349 description="Schedule tasks by selecting them from a web interface.",
350 parser=get_parser("chooser"),
352 def try_chooser(command_context, **kwargs):
353 """Push tasks selected from a web interface to try.
355 This selector will build the taskgraph and spin up a dynamically
356 created 'trychooser-like' web-page on the localhost. After a selection
357 has been made, pressing the 'Push' button will automatically push the
358 selection to try.
360 init(command_context)
361 command_context.activate_virtualenv()
362 path = os.path.join(
363 "tools", "tryselect", "selectors", "chooser", "requirements.txt"
365 command_context.virtualenv_manager.install_pip_requirements(path, quiet=True)
367 return run(command_context, **kwargs)
370 @SubCommand(
371 "try",
372 "auto",
373 description="Automatically determine which tasks to run. This runs the same "
374 "set of tasks that would be run on autoland. This "
375 "selector is EXPERIMENTAL.",
376 parser=get_parser("auto"),
378 def try_auto(command_context, **kwargs):
379 init(command_context)
380 return run(command_context, **kwargs)
383 @SubCommand(
384 "try",
385 "again",
386 description="Schedule a previously generated (non try syntax) push again.",
387 parser=get_parser("again"),
389 def try_again(command_context, **kwargs):
390 init(command_context)
391 return run(command_context, **kwargs)
394 @SubCommand(
395 "try",
396 "empty",
397 description="Push to try without scheduling any tasks.",
398 parser=get_parser("empty"),
400 def try_empty(command_context, **kwargs):
401 """Push to try, running no builds or tests
403 This selector does not prompt you to run anything, it just pushes
404 your patches to try, running no builds or tests by default. After
405 the push finishes, you can manually add desired jobs to your push
406 via Treeherder's Add New Jobs feature, located in the per-push
407 menu.
409 init(command_context)
410 return run(command_context, **kwargs)
413 @SubCommand(
414 "try",
415 "syntax",
416 description="Select tasks on try using try syntax",
417 parser=get_parser("syntax"),
419 def try_syntax(command_context, **kwargs):
420 """Push the current tree to try, with the specified syntax.
422 Build options, platforms and regression tests may be selected
423 using the usual try options (-b, -p and -u respectively). In
424 addition, tests in a given directory may be automatically
425 selected by passing that directory as a positional argument to the
426 command. For example:
428 mach try -b d -p linux64 dom testing/web-platform/tests/dom
430 would schedule a try run for linux64 debug consisting of all
431 tests under dom/ and testing/web-platform/tests/dom.
433 Test selection using positional arguments is available for
434 mochitests, reftests, xpcshell tests and web-platform-tests.
436 Tests may be also filtered by passing --tag to the command,
437 which will run only tests marked as having the specified
438 tags e.g.
440 mach try -b d -p win64 --tag media
442 would run all tests tagged 'media' on Windows 64.
444 If both positional arguments or tags and -u are supplied, the
445 suites in -u will be run in full. Where tests are selected by
446 positional argument they will be run in a single chunk.
448 If no build option is selected, both debug and opt will be
449 scheduled. If no platform is selected a default is taken from
450 the AUTOTRY_PLATFORM_HINT environment variable, if set.
452 The command requires either its own mercurial extension ("push-to-try",
453 installable from mach vcs-setup) or a git repo using git-cinnabar
454 (installable from mach vcs-setup).
457 init(command_context)
458 try:
459 if command_context.substs.get("MOZ_ARTIFACT_BUILDS"):
460 kwargs["local_artifact_build"] = True
461 except BuildEnvironmentNotFoundException:
462 # If we don't have a build locally, we can't tell whether
463 # an artifact build is desired, but we still want the
464 # command to succeed, if possible.
465 pass
467 config_status = os.path.join(command_context.topobjdir, "config.status")
468 if (kwargs["paths"] or kwargs["tags"]) and not config_status:
469 print(CONFIG_ENVIRONMENT_NOT_FOUND)
470 sys.exit(1)
472 return run(command_context, **kwargs)
475 @SubCommand(
476 "try",
477 "coverage",
478 description="Select tasks on try using coverage data",
479 parser=get_parser("coverage"),
481 def try_coverage(command_context, **kwargs):
482 """Select which tasks to use using coverage data."""
483 init(command_context)
484 return run(command_context, **kwargs)
487 @SubCommand(
488 "try",
489 "release",
490 description="Push the current tree to try, configured for a staging release.",
491 parser=get_parser("release"),
493 def try_release(command_context, **kwargs):
494 """Push the current tree to try, configured for a staging release."""
495 init(command_context)
496 return run(command_context, **kwargs)
499 @SubCommand(
500 "try",
501 "scriptworker",
502 description="Run scriptworker tasks against a recent release.",
503 parser=get_parser("scriptworker"),
505 def try_scriptworker(command_context, **kwargs):
506 """Run scriptworker tasks against a recent release.
508 Requires VPN and shipit access.
510 init(command_context)
511 return run(command_context, **kwargs)
514 @SubCommand(
515 "try",
516 "compare",
517 description="Push two try jobs, one on your current commit and another on the one you specify",
518 parser=get_parser("compare"),
520 def try_compare(command_context, **kwargs):
521 init(command_context)
522 return run(command_context, **kwargs)