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 Templates provide a way of modifying the task definition of selected tasks.
7 They are added to 'try_task_config.json' and processed by the transforms.
16 from abc
import ABCMeta
, abstractmethod
, abstractproperty
17 from argparse
import SUPPRESS
, Action
18 from contextlib
import contextmanager
19 from textwrap
import dedent
21 import mozpack
.path
as mozpath
24 from mozbuild
.base
import BuildEnvironmentNotFoundException
, MozbuildObject
25 from mozversioncontrol
import Repository
26 from taskgraph
.util
import taskcluster
28 from .tasks
import resolve_tests_by_suite
29 from .util
.ssh
import get_ssh_user
31 here
= pathlib
.Path(__file__
).parent
32 build
= MozbuildObject
.from_environment(cwd
=str(here
))
36 def try_config_commit(vcs
: Repository
, commit_message
: str):
37 """Context manager that creates and removes a try config commit."""
38 # Add the `try_task_config.json` file if it exists.
39 try_task_config_path
= pathlib
.Path(build
.topsrcdir
) / "try_task_config.json"
40 if try_task_config_path
.exists():
41 vcs
.add_remove_files("try_task_config.json")
44 # Create a try config commit.
45 vcs
.create_try_commit(commit_message
)
49 # Revert the try config commit.
50 vcs
.remove_current_commit()
53 class ParameterConfig
:
54 __metaclass__
= ABCMeta
59 def add_arguments(self
, parser
):
60 for cli
, kwargs
in self
.arguments
:
61 action
= parser
.add_argument(*cli
, **kwargs
)
62 self
.dests
.add(action
.dest
)
69 def get_parameters(self
, **kwargs
) -> dict:
72 def validate(self
, **kwargs
):
76 class TryConfig(ParameterConfig
):
78 def try_config(self
, **kwargs
) -> dict:
81 def get_parameters(self
, **kwargs
):
82 result
= self
.try_config(**kwargs
)
85 return {"try_task_config": result
}
88 class Artifact(TryConfig
):
92 {"action": "store_true", "help": "Force artifact builds where possible."},
97 "action": "store_true",
98 "help": "Disable artifact builds even if being used locally.",
103 def add_arguments(self
, parser
):
104 group
= parser
.add_mutually_exclusive_group()
105 return super().add_arguments(group
)
108 def is_artifact_build(cls
):
110 return build
.substs
.get("MOZ_ARTIFACT_BUILDS", False)
111 except BuildEnvironmentNotFoundException
:
114 def try_config(self
, artifact
, no_artifact
, **kwargs
):
116 return {"use-artifact-builds": True, "disable-pgo": True}
121 if self
.is_artifact_build():
122 print("Artifact builds enabled, pass --no-artifact to disable")
123 return {"use-artifact-builds": True, "disable-pgo": True}
126 class Pernosco(TryConfig
):
131 "action": "store_true",
133 "help": "Opt-in to analysis by the Pernosco debugging service.",
140 "action": "store_false",
142 "help": "Opt-out of the Pernosco debugging service (if you are on the include list).",
147 def add_arguments(self
, parser
):
148 group
= parser
.add_mutually_exclusive_group()
149 return super().add_arguments(group
)
151 def try_config(self
, pernosco
, **kwargs
):
157 # The Pernosco service currently requires a Mozilla e-mail address to
158 # log in. Prevent people with non-Mozilla addresses from using this
159 # flag so they don't end up consuming time and resources only to
160 # realize they can't actually log in and see the reports.
161 address
= get_ssh_user()
162 if not address
.endswith("@mozilla.com"):
166 Pernosco requires a Mozilla e-mail address to view its reports. Please
167 push to try with an @mozilla.com address to use --pernosco.
177 except (subprocess
.CalledProcessError
, IndexError):
178 print("warning: failed to detect current user for 'hg.mozilla.org'")
179 print("Pernosco requires a Mozilla e-mail address to view its reports.")
182 "Do you have an @mozilla.com address? [Y/n]: "
191 "PERNOSCO": str(int(pernosco
)),
195 def validate(self
, **kwargs
):
196 try_config
= kwargs
["try_config_params"].get("try_task_config") or {}
197 if try_config
.get("use-artifact-builds"):
199 "Pernosco does not support artifact builds at this time. "
200 "Please try again with '--no-artifact'."
205 class Path(TryConfig
):
212 "help": "Run tasks containing tests under the specified path(s).",
217 def try_config(self
, paths
, **kwargs
):
222 if not os
.path
.exists(p
):
223 print("error: '{}' is not a valid path.".format(p
), file=sys
.stderr
)
227 mozpath
.relpath(mozpath
.join(os
.getcwd(), p
), build
.topsrcdir
)
232 "MOZHARNESS_TEST_PATHS": six
.ensure_text(
233 json
.dumps(resolve_tests_by_suite(paths
))
239 class Environment(TryConfig
):
246 "help": "Set an environment variable, of the form FOO=BAR. "
247 "Can be passed in multiple times.",
252 def try_config(self
, env
, **kwargs
):
256 "env": dict(e
.split("=", 1) for e
in env
),
260 class ExistingTasks(ParameterConfig
):
261 TREEHERDER_PUSH_ENDPOINT
= (
262 "https://treeherder.mozilla.org/api/project/try/push/?count=1&author={user}"
264 TREEHERDER_PUSH_URL
= (
265 "https://treeherder.mozilla.org/jobs?repo={branch}&revision={revision}"
270 ["-E", "--use-existing-tasks"],
272 "const": "last_try_push",
276 Use existing tasks from a previous push. Without args this
277 uses your most recent try push. You may also specify
278 `rev=<revision>` where <revision> is the head revision of the
279 try push or `task-id=<task id>` where <task id> is the Decision
280 task id of the push. This last method even works for non-try
287 def find_decision_task(self
, use_existing_tasks
):
289 if use_existing_tasks
== "last_try_push":
290 # Use existing tasks from user's previous try push.
291 user
= get_ssh_user()
292 url
= self
.TREEHERDER_PUSH_ENDPOINT
.format(user
=user
)
293 res
= requests
.get(url
, headers
={"User-Agent": "gecko-mach-try/1.0"})
294 res
.raise_for_status()
296 if data
["meta"]["count"] == 0:
297 raise Exception(f
"Could not find a try push for '{user}'!")
298 revision
= data
["results"][0]["revision"]
300 elif use_existing_tasks
.startswith("rev="):
301 revision
= use_existing_tasks
[len("rev=") :]
304 raise Exception("Unable to parse '{use_existing_tasks}'!")
306 url
= self
.TREEHERDER_PUSH_URL
.format(branch
=branch
, revision
=revision
)
307 print(f
"Using existing tasks from: {url}")
308 index_path
= f
"gecko.v2.{branch}.revision.{revision}.taskgraph.decision"
309 return taskcluster
.find_task_id(index_path
)
311 def get_parameters(self
, use_existing_tasks
, **kwargs
):
312 if not use_existing_tasks
:
315 if use_existing_tasks
.startswith("task-id="):
316 tid
= use_existing_tasks
[len("task-id=") :]
318 tid
= self
.find_decision_task(use_existing_tasks
)
320 label_to_task_id
= taskcluster
.get_artifact(tid
, "public/label-to-taskid.json")
321 return {"existing_tasks": label_to_task_id
}
324 class RangeAction(Action
):
325 def __init__(self
, min, max, *args
, **kwargs
):
328 kwargs
["metavar"] = "[{}-{}]".format(self
.min, self
.max)
329 super().__init
__(*args
, **kwargs
)
331 def __call__(self
, parser
, namespace
, values
, option_string
=None):
332 name
= option_string
or self
.dest
333 if values
< self
.min:
334 parser
.error("{} can not be less than {}".format(name
, self
.min))
335 if values
> self
.max:
336 parser
.error("{} can not be more than {}".format(name
, self
.max))
337 setattr(namespace
, self
.dest
, values
)
340 class Rebuild(TryConfig
):
345 "action": RangeAction
,
350 "help": "Rebuild all selected tasks the specified number of times.",
355 def try_config(self
, rebuild
, **kwargs
):
360 not kwargs
.get("new_test_config", False)
361 and kwargs
.get("full")
365 "warning: limiting --rebuild to 3 when using --full. "
366 "Use custom push actions to add more."
375 class Routes(TryConfig
):
383 "Additional route to add to the tasks "
384 "(note: these will not be added to the decision task)"
390 def try_config(self
, routes
, **kwargs
):
397 class ChemspillPrio(TryConfig
):
400 ["--chemspill-prio"],
402 "action": "store_true",
403 "help": "Run at a higher priority than most try jobs (chemspills only).",
408 def try_config(self
, chemspill_prio
, **kwargs
):
410 return {"chemspill-prio": True}
413 class GeckoProfile(TryConfig
):
419 "action": "store_true",
421 "help": "Create and upload a gecko profile during talos/raptor tasks.",
425 ["--gecko-profile-interval"],
427 "dest": "gecko_profile_interval",
429 "help": "How frequently to take samples (ms)",
433 ["--gecko-profile-entries"],
435 "dest": "gecko_profile_entries",
437 "help": "How many samples to take with the profiler",
441 ["--gecko-profile-features"],
443 "dest": "gecko_profile_features",
446 "help": "Set the features enabled for the profiler.",
450 ["--gecko-profile-threads"],
452 "dest": "gecko_profile_threads",
454 "help": "Comma-separated list of threads to sample.",
457 # For backwards compatibility
462 "action": "store_true",
467 # This is added for consistency with the 'syntax' selector
472 "action": "store_true",
482 gecko_profile_interval
,
483 gecko_profile_entries
,
484 gecko_profile_features
,
485 gecko_profile_threads
,
488 if profile
or not all(
489 s
is None for s
in (gecko_profile_features
, gecko_profile_threads
)
492 "gecko-profile": True,
493 "gecko-profile-interval": gecko_profile_interval
,
494 "gecko-profile-entries": gecko_profile_entries
,
495 "gecko-profile-features": gecko_profile_features
,
496 "gecko-profile-threads": gecko_profile_threads
,
498 return {key
: value
for key
, value
in cfg
.items() if value
is not None}
501 class Browsertime(TryConfig
):
506 "action": "store_true",
507 "help": "Use browsertime during Raptor tasks.",
512 def try_config(self
, browsertime
, **kwargs
):
519 class DisablePgo(TryConfig
):
524 "action": "store_true",
525 "help": "Don't run PGO builds",
530 def try_config(self
, disable_pgo
, **kwargs
):
537 class NewConfig(TryConfig
):
540 ["--new-test-config"],
542 "action": "store_true",
543 "help": "When a test fails (mochitest only) restart the browser and start from the next test",
548 def try_config(self
, new_test_config
, **kwargs
):
551 "new-test-config": True,
555 class WorkerOverrides(TryConfig
):
558 ["--worker-override"],
561 "dest": "worker_overrides",
563 "Override the worker pool used for a given taskgraph worker alias. "
564 "The argument should be `<alias>=<worker-pool>`. "
565 "Can be specified multiple times."
573 "dest": "worker_suffixes",
575 "Override the worker pool used for a given taskgraph worker alias, "
576 "by appending a suffix to the work-pool. "
577 "The argument should be `<alias>=<suffix>`. "
578 "Can be specified multiple times."
586 "dest": "worker_types",
588 "help": "Select tasks that only run on the specified worker.",
593 def try_config(self
, worker_overrides
, worker_suffixes
, worker_types
, **kwargs
):
594 from gecko_taskgraph
.util
.workertypes
import get_worker_type
595 from taskgraph
.config
import load_graph_config
599 for override
in worker_overrides
:
600 alias
, worker_pool
= override
.split("=", 1)
601 if alias
in overrides
:
603 "Can't override worker alias {alias} more than once. "
604 "Already set to use {previous}, but also asked to use {new}.".format(
605 alias
=alias
, previous
=overrides
[alias
], new
=worker_pool
609 overrides
[alias
] = worker_pool
612 root
= build
.topsrcdir
613 root
= os
.path
.join(root
, "taskcluster", "ci")
614 graph_config
= load_graph_config(root
)
615 for worker_suffix
in worker_suffixes
:
616 alias
, suffix
= worker_suffix
.split("=", 1)
617 if alias
in overrides
:
619 "Can't override worker alias {alias} more than once. "
620 "Already set to use {previous}, but also asked "
621 "to add suffix {suffix}.".format(
622 alias
=alias
, previous
=overrides
[alias
], suffix
=suffix
626 provisioner
, worker_type
= get_worker_type(
627 graph_config
, worker_type
=alias
, parameters
={"level": "1"}
629 overrides
[alias
] = "{provisioner}/{worker_type}{suffix}".format(
630 provisioner
=provisioner
, worker_type
=worker_type
, suffix
=suffix
635 retVal
["worker-types"] = list(overrides
.keys()) + worker_types
638 retVal
["worker-overrides"] = overrides
643 "artifact": Artifact
,
644 "browsertime": Browsertime
,
645 "chemspill-prio": ChemspillPrio
,
646 "disable-pgo": DisablePgo
,
648 "existing-tasks": ExistingTasks
,
649 "gecko-profile": GeckoProfile
,
650 "new-test-config": NewConfig
,
652 "pernosco": Pernosco
,
655 "worker-overrides": WorkerOverrides
,