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 textwrap
import dedent
20 import mozpack
.path
as mozpath
23 from mozbuild
.base
import BuildEnvironmentNotFoundException
, MozbuildObject
24 from taskgraph
.util
import taskcluster
26 from .tasks
import resolve_tests_by_suite
27 from .util
.ssh
import get_ssh_user
29 here
= pathlib
.Path(__file__
).parent
30 build
= MozbuildObject
.from_environment(cwd
=str(here
))
33 class ParameterConfig
:
34 __metaclass__
= ABCMeta
39 def add_arguments(self
, parser
):
40 for cli
, kwargs
in self
.arguments
:
41 action
= parser
.add_argument(*cli
, **kwargs
)
42 self
.dests
.add(action
.dest
)
49 def get_parameters(self
, **kwargs
) -> dict:
52 def validate(self
, **kwargs
):
56 class TryConfig(ParameterConfig
):
58 def try_config(self
, **kwargs
) -> dict:
61 def get_parameters(self
, **kwargs
):
62 result
= self
.try_config(**kwargs
)
65 return {"try_task_config": result
}
68 class Artifact(TryConfig
):
72 {"action": "store_true", "help": "Force artifact builds where possible."},
77 "action": "store_true",
78 "help": "Disable artifact builds even if being used locally.",
83 def add_arguments(self
, parser
):
84 group
= parser
.add_mutually_exclusive_group()
85 return super().add_arguments(group
)
88 def is_artifact_build(cls
):
90 return build
.substs
.get("MOZ_ARTIFACT_BUILDS", False)
91 except BuildEnvironmentNotFoundException
:
94 def try_config(self
, artifact
, no_artifact
, **kwargs
):
96 return {"use-artifact-builds": True, "disable-pgo": True}
101 if self
.is_artifact_build():
102 print("Artifact builds enabled, pass --no-artifact to disable")
103 return {"use-artifact-builds": True, "disable-pgo": True}
106 class Pernosco(TryConfig
):
111 "action": "store_true",
113 "help": "Opt-in to analysis by the Pernosco debugging service.",
120 "action": "store_false",
122 "help": "Opt-out of the Pernosco debugging service (if you are on the include list).",
127 def add_arguments(self
, parser
):
128 group
= parser
.add_mutually_exclusive_group()
129 return super().add_arguments(group
)
131 def try_config(self
, pernosco
, **kwargs
):
132 pernosco
= pernosco
or os
.environ
.get("MOZ_USE_PERNOSCO")
138 # The Pernosco service currently requires a Mozilla e-mail address to
139 # log in. Prevent people with non-Mozilla addresses from using this
140 # flag so they don't end up consuming time and resources only to
141 # realize they can't actually log in and see the reports.
142 address
= get_ssh_user()
143 if not address
.endswith("@mozilla.com"):
147 Pernosco requires a Mozilla e-mail address to view its reports. Please
148 push to try with an @mozilla.com address to use --pernosco.
158 except (subprocess
.CalledProcessError
, IndexError):
159 print("warning: failed to detect current user for 'hg.mozilla.org'")
160 print("Pernosco requires a Mozilla e-mail address to view its reports.")
163 "Do you have an @mozilla.com address? [Y/n]: "
172 # TODO Bug 1907076: Remove the env below once Pernosco consumers
173 # are using the `pernosco-v1` task routes.
175 "PERNOSCO": str(int(pernosco
)),
179 def validate(self
, **kwargs
):
180 try_config
= kwargs
["try_config_params"].get("try_task_config") or {}
181 if try_config
.get("use-artifact-builds"):
183 "Pernosco does not support artifact builds at this time. "
184 "Please try again with '--no-artifact'."
189 class Path(TryConfig
):
196 "help": "Run tasks containing tests under the specified path(s).",
201 def try_config(self
, paths
, **kwargs
):
206 if not os
.path
.exists(p
):
207 print("error: '{}' is not a valid path.".format(p
), file=sys
.stderr
)
211 mozpath
.relpath(mozpath
.join(os
.getcwd(), p
), build
.topsrcdir
)
216 "MOZHARNESS_TEST_PATHS": six
.ensure_text(
217 json
.dumps(resolve_tests_by_suite(paths
))
223 class Tag(TryConfig
):
230 "help": "Run tests matching the specified tag.",
235 def try_config(self
, tag
, **kwargs
):
241 "MOZHARNESS_TEST_TAG": json
.dumps(tag
),
246 class Environment(TryConfig
):
253 "help": "Set an environment variable, of the form FOO=BAR. "
254 "Can be passed in multiple times.",
259 def try_config(self
, env
, **kwargs
):
263 "env": dict(e
.split("=", 1) for e
in env
),
267 class ExistingTasks(ParameterConfig
):
268 TREEHERDER_PUSH_ENDPOINT
= (
269 "https://treeherder.mozilla.org/api/project/try/push/?count=1&author={user}"
271 TREEHERDER_PUSH_URL
= (
272 "https://treeherder.mozilla.org/jobs?repo={branch}&revision={revision}"
277 ["-E", "--use-existing-tasks"],
279 "const": "last_try_push",
283 Use existing tasks from a previous push. Without args this
284 uses your most recent try push. You may also specify
285 `rev=<revision>` where <revision> is the head revision of the
286 try push or `task-id=<task id>` where <task id> is the Decision
287 task id of the push. This last method even works for non-try
294 def find_decision_task(self
, use_existing_tasks
):
296 if use_existing_tasks
== "last_try_push":
297 # Use existing tasks from user's previous try push.
298 user
= get_ssh_user()
299 url
= self
.TREEHERDER_PUSH_ENDPOINT
.format(user
=user
)
300 res
= requests
.get(url
, headers
={"User-Agent": "gecko-mach-try/1.0"})
301 res
.raise_for_status()
303 if data
["meta"]["count"] == 0:
304 raise Exception(f
"Could not find a try push for '{user}'!")
305 revision
= data
["results"][0]["revision"]
307 elif use_existing_tasks
.startswith("rev="):
308 revision
= use_existing_tasks
[len("rev=") :]
311 raise Exception("Unable to parse '{use_existing_tasks}'!")
313 url
= self
.TREEHERDER_PUSH_URL
.format(branch
=branch
, revision
=revision
)
314 print(f
"Using existing tasks from: {url}")
315 index_path
= f
"gecko.v2.{branch}.revision.{revision}.taskgraph.decision"
316 return taskcluster
.find_task_id(index_path
)
318 def get_parameters(self
, use_existing_tasks
, **kwargs
):
319 if not use_existing_tasks
:
322 if use_existing_tasks
.startswith("task-id="):
323 tid
= use_existing_tasks
[len("task-id=") :]
325 tid
= self
.find_decision_task(use_existing_tasks
)
327 label_to_task_id
= taskcluster
.get_artifact(tid
, "public/label-to-taskid.json")
328 return {"existing_tasks": label_to_task_id
}
331 class RangeAction(Action
):
332 def __init__(self
, min, max, *args
, **kwargs
):
335 kwargs
["metavar"] = "[{}-{}]".format(self
.min, self
.max)
336 super().__init
__(*args
, **kwargs
)
338 def __call__(self
, parser
, namespace
, values
, option_string
=None):
339 name
= option_string
or self
.dest
340 if values
< self
.min:
341 parser
.error("{} can not be less than {}".format(name
, self
.min))
342 if values
> self
.max:
343 parser
.error("{} can not be more than {}".format(name
, self
.max))
344 setattr(namespace
, self
.dest
, values
)
347 class Rebuild(TryConfig
):
352 "action": RangeAction
,
357 "help": "Rebuild all selected tasks the specified number of times.",
362 def try_config(self
, rebuild
, **kwargs
):
367 not kwargs
.get("new_test_config", False)
368 and kwargs
.get("full")
372 "warning: limiting --rebuild to 3 when using --full. "
373 "Use custom push actions to add more."
382 class Routes(TryConfig
):
390 "Additional route to add to the tasks "
391 "(note: these will not be added to the decision task)"
397 def try_config(self
, routes
, **kwargs
):
404 class ChemspillPrio(TryConfig
):
407 ["--chemspill-prio"],
409 "action": "store_true",
410 "help": "Run at a higher priority than most try jobs (chemspills only).",
415 def try_config(self
, chemspill_prio
, **kwargs
):
417 return {"chemspill-prio": True}
420 class GeckoProfile(TryConfig
):
426 "action": "store_true",
428 "help": "Create and upload a gecko profile during talos/raptor tasks.",
432 ["--gecko-profile-interval"],
434 "dest": "gecko_profile_interval",
436 "help": "How frequently to take samples (ms)",
440 ["--gecko-profile-entries"],
442 "dest": "gecko_profile_entries",
444 "help": "How many samples to take with the profiler",
448 ["--gecko-profile-features"],
450 "dest": "gecko_profile_features",
453 "help": "Set the features enabled for the profiler.",
457 ["--gecko-profile-threads"],
459 "dest": "gecko_profile_threads",
461 "help": "Comma-separated list of threads to sample.",
464 # For backwards compatibility
469 "action": "store_true",
474 # This is added for consistency with the 'syntax' selector
479 "action": "store_true",
489 gecko_profile_interval
,
490 gecko_profile_entries
,
491 gecko_profile_features
,
492 gecko_profile_threads
,
495 if profile
or not all(
496 s
is None for s
in (gecko_profile_features
, gecko_profile_threads
)
499 "gecko-profile": True,
500 "gecko-profile-interval": gecko_profile_interval
,
501 "gecko-profile-entries": gecko_profile_entries
,
502 "gecko-profile-features": gecko_profile_features
,
503 "gecko-profile-threads": gecko_profile_threads
,
505 return {key
: value
for key
, value
in cfg
.items() if value
is not None}
508 class Browsertime(TryConfig
):
513 "action": "store_true",
514 "help": "Use browsertime during Raptor tasks.",
519 def try_config(self
, browsertime
, **kwargs
):
526 class DisablePgo(TryConfig
):
531 "action": "store_true",
532 "help": "Don't run PGO builds",
537 def try_config(self
, disable_pgo
, **kwargs
):
544 class NewConfig(TryConfig
):
547 ["--new-test-config"],
549 "action": "store_true",
550 "help": "When a test fails (mochitest only) restart the browser and start from the next test",
555 def try_config(self
, new_test_config
, **kwargs
):
558 "new-test-config": True,
562 class WorkerOverrides(TryConfig
):
565 ["--worker-override"],
568 "dest": "worker_overrides",
570 "Override the worker pool used for a given taskgraph worker alias. "
571 "The argument should be `<alias>=<worker-pool>`. "
572 "Can be specified multiple times."
580 "dest": "worker_suffixes",
582 "Override the worker pool used for a given taskgraph worker alias, "
583 "by appending a suffix to the work-pool. "
584 "The argument should be `<alias>=<suffix>`. "
585 "Can be specified multiple times."
593 "dest": "worker_types",
595 "help": "Select tasks that only run on the specified worker.",
600 def try_config(self
, worker_overrides
, worker_suffixes
, worker_types
, **kwargs
):
601 from gecko_taskgraph
.util
.workertypes
import get_worker_type
602 from taskgraph
.config
import load_graph_config
606 for override
in worker_overrides
:
607 alias
, worker_pool
= override
.split("=", 1)
608 if alias
in overrides
:
610 "Can't override worker alias {alias} more than once. "
611 "Already set to use {previous}, but also asked to use {new}.".format(
612 alias
=alias
, previous
=overrides
[alias
], new
=worker_pool
616 overrides
[alias
] = worker_pool
619 root
= build
.topsrcdir
620 root
= os
.path
.join(root
, "taskcluster")
621 graph_config
= load_graph_config(root
)
622 for worker_suffix
in worker_suffixes
:
623 alias
, suffix
= worker_suffix
.split("=", 1)
624 if alias
in overrides
:
626 "Can't override worker alias {alias} more than once. "
627 "Already set to use {previous}, but also asked "
628 "to add suffix {suffix}.".format(
629 alias
=alias
, previous
=overrides
[alias
], suffix
=suffix
633 provisioner
, worker_type
= get_worker_type(
634 graph_config
, worker_type
=alias
, parameters
={"level": "1"}
636 overrides
[alias
] = "{provisioner}/{worker_type}{suffix}".format(
637 provisioner
=provisioner
, worker_type
=worker_type
, suffix
=suffix
642 retVal
["worker-types"] = list(overrides
.keys()) + worker_types
645 retVal
["worker-overrides"] = overrides
650 "artifact": Artifact
,
651 "browsertime": Browsertime
,
652 "chemspill-prio": ChemspillPrio
,
653 "disable-pgo": DisablePgo
,
655 "existing-tasks": ExistingTasks
,
656 "gecko-profile": GeckoProfile
,
657 "new-test-config": NewConfig
,
660 "pernosco": Pernosco
,
663 "worker-overrides": WorkerOverrides
,