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
23 from mozbuild
.base
import BuildEnvironmentNotFoundException
, MozbuildObject
24 from mozversioncontrol
import Repository
26 from .tasks
import resolve_tests_by_suite
28 here
= os
.path
.abspath(os
.path
.dirname(__file__
))
29 build
= MozbuildObject
.from_environment(cwd
=here
)
33 def try_config_commit(vcs
: Repository
, commit_message
: str):
34 """Context manager that creates and removes a try config commit."""
35 # Add the `try_task_config.json` file if it exists.
36 try_task_config_path
= pathlib
.Path(build
.topsrcdir
) / "try_task_config.json"
37 if try_task_config_path
.exists():
38 vcs
.add_remove_files("try_task_config.json")
41 # Create a try config commit.
42 vcs
.create_try_commit(commit_message
)
46 # Revert the try config commit.
47 vcs
.remove_current_commit()
51 __metaclass__
= ABCMeta
56 def add_arguments(self
, parser
):
57 for cli
, kwargs
in self
.arguments
:
58 action
= parser
.add_argument(*cli
, **kwargs
)
59 self
.dests
.add(action
.dest
)
66 def try_config(self
, **kwargs
):
69 def validate(self
, **kwargs
):
73 class Artifact(TryConfig
):
77 {"action": "store_true", "help": "Force artifact builds where possible."},
82 "action": "store_true",
83 "help": "Disable artifact builds even if being used locally.",
88 def add_arguments(self
, parser
):
89 group
= parser
.add_mutually_exclusive_group()
90 return super().add_arguments(group
)
93 def is_artifact_build(cls
):
95 return build
.substs
.get("MOZ_ARTIFACT_BUILDS", False)
96 except BuildEnvironmentNotFoundException
:
99 def try_config(self
, artifact
, no_artifact
, **kwargs
):
101 return {"use-artifact-builds": True, "disable-pgo": True}
106 if self
.is_artifact_build():
107 print("Artifact builds enabled, pass --no-artifact to disable")
108 return {"use-artifact-builds": True, "disable-pgo": True}
111 class Pernosco(TryConfig
):
116 "action": "store_true",
118 "help": "Opt-in to analysis by the Pernosco debugging service.",
125 "action": "store_false",
127 "help": "Opt-out of the Pernosco debugging service (if you are on the include list).",
132 def add_arguments(self
, parser
):
133 group
= parser
.add_mutually_exclusive_group()
134 return super().add_arguments(group
)
136 def try_config(self
, pernosco
, **kwargs
):
142 # The Pernosco service currently requires a Mozilla e-mail address to
143 # log in. Prevent people with non-Mozilla addresses from using this
144 # flag so they don't end up consuming time and resources only to
145 # realize they can't actually log in and see the reports.
146 cmd
= ["ssh", "-G", "hg.mozilla.org"]
147 output
= subprocess
.check_output(
148 cmd
, universal_newlines
=True
151 l
.rsplit(" ", 1)[-1] for l
in output
if l
.startswith("user")
153 if not address
.endswith("@mozilla.com"):
157 Pernosco requires a Mozilla e-mail address to view its reports. Please
158 push to try with an @mozilla.com address to use --pernosco.
168 except (subprocess
.CalledProcessError
, IndexError):
169 print("warning: failed to detect current user for 'hg.mozilla.org'")
170 print("Pernosco requires a Mozilla e-mail address to view its reports.")
173 "Do you have an @mozilla.com address? [Y/n]: "
182 "PERNOSCO": str(int(pernosco
)),
186 def validate(self
, **kwargs
):
187 if kwargs
["try_config"].get("use-artifact-builds"):
189 "Pernosco does not support artifact builds at this time. "
190 "Please try again with '--no-artifact'."
195 class Path(TryConfig
):
202 "help": "Run tasks containing tests under the specified path(s).",
207 def try_config(self
, paths
, **kwargs
):
212 if not os
.path
.exists(p
):
213 print("error: '{}' is not a valid path.".format(p
), file=sys
.stderr
)
217 mozpath
.relpath(mozpath
.join(os
.getcwd(), p
), build
.topsrcdir
)
222 "MOZHARNESS_TEST_PATHS": six
.ensure_text(
223 json
.dumps(resolve_tests_by_suite(paths
))
229 class Environment(TryConfig
):
236 "help": "Set an environment variable, of the form FOO=BAR. "
237 "Can be passed in multiple times.",
242 def try_config(self
, env
, **kwargs
):
246 "env": dict(e
.split("=", 1) for e
in env
),
250 class RangeAction(Action
):
251 def __init__(self
, min, max, *args
, **kwargs
):
254 kwargs
["metavar"] = "[{}-{}]".format(self
.min, self
.max)
255 super().__init
__(*args
, **kwargs
)
257 def __call__(self
, parser
, namespace
, values
, option_string
=None):
258 name
= option_string
or self
.dest
259 if values
< self
.min:
260 parser
.error("{} can not be less than {}".format(name
, self
.min))
261 if values
> self
.max:
262 parser
.error("{} can not be more than {}".format(name
, self
.max))
263 setattr(namespace
, self
.dest
, values
)
266 class Rebuild(TryConfig
):
271 "action": RangeAction
,
276 "help": "Rebuild all selected tasks the specified number of times.",
281 def try_config(self
, rebuild
, **kwargs
):
285 if kwargs
.get("full") and rebuild
> 3:
287 "warning: limiting --rebuild to 3 when using --full. "
288 "Use custom push actions to add more."
297 class Routes(TryConfig
):
305 "Additional route to add to the tasks "
306 "(note: these will not be added to the decision task)"
312 def try_config(self
, routes
, **kwargs
):
319 class ChemspillPrio(TryConfig
):
322 ["--chemspill-prio"],
324 "action": "store_true",
325 "help": "Run at a higher priority than most try jobs (chemspills only).",
330 def try_config(self
, chemspill_prio
, **kwargs
):
332 return {"chemspill-prio": {}}
335 class GeckoProfile(TryConfig
):
341 "action": "store_true",
343 "help": "Create and upload a gecko profile during talos/raptor tasks.",
347 ["--gecko-profile-interval"],
349 "dest": "gecko_profile_interval",
351 "help": "How frequently to take samples (ms)",
355 ["--gecko-profile-entries"],
357 "dest": "gecko_profile_entries",
359 "help": "How many samples to take with the profiler",
363 ["--gecko-profile-features"],
365 "dest": "gecko_profile_features",
368 "help": "Set the features enabled for the profiler.",
372 ["--gecko-profile-threads"],
374 "dest": "gecko_profile_threads",
376 "help": "Comma-separated list of threads to sample.",
379 # For backwards compatibility
384 "action": "store_true",
389 # This is added for consistency with the 'syntax' selector
394 "action": "store_true",
404 gecko_profile_interval
,
405 gecko_profile_entries
,
406 gecko_profile_features
,
407 gecko_profile_threads
,
410 if profile
or not all(
411 s
is None for s
in (gecko_profile_features
, gecko_profile_threads
)
414 "gecko-profile": True,
415 "gecko-profile-interval": gecko_profile_interval
,
416 "gecko-profile-entries": gecko_profile_entries
,
417 "gecko-profile-features": gecko_profile_features
,
418 "gecko-profile-threads": gecko_profile_threads
,
420 return {key
: value
for key
, value
in cfg
.items() if value
is not None}
423 class Browsertime(TryConfig
):
428 "action": "store_true",
429 "help": "Use browsertime during Raptor tasks.",
434 def try_config(self
, browsertime
, **kwargs
):
441 class DisablePgo(TryConfig
):
446 "action": "store_true",
447 "help": "Don't run PGO builds",
452 def try_config(self
, disable_pgo
, **kwargs
):
459 class WorkerOverrides(TryConfig
):
462 ["--worker-override"],
465 "dest": "worker_overrides",
467 "Override the worker pool used for a given taskgraph worker alias. "
468 "The argument should be `<alias>=<worker-pool>`. "
469 "Can be specified multiple times."
477 "dest": "worker_suffixes",
479 "Override the worker pool used for a given taskgraph worker alias, "
480 "by appending a suffix to the work-pool. "
481 "The argument should be `<alias>=<suffix>`. "
482 "Can be specified multiple times."
488 def try_config(self
, worker_overrides
, worker_suffixes
, **kwargs
):
489 from gecko_taskgraph
.util
.workertypes
import get_worker_type
490 from taskgraph
.config
import load_graph_config
494 for override
in worker_overrides
:
495 alias
, worker_pool
= override
.split("=", 1)
496 if alias
in overrides
:
498 "Can't override worker alias {alias} more than once. "
499 "Already set to use {previous}, but also asked to use {new}.".format(
500 alias
=alias
, previous
=overrides
[alias
], new
=worker_pool
504 overrides
[alias
] = worker_pool
507 root
= build
.topsrcdir
508 root
= os
.path
.join(root
, "taskcluster", "ci")
509 graph_config
= load_graph_config(root
)
510 for worker_suffix
in worker_suffixes
:
511 alias
, suffix
= worker_suffix
.split("=", 1)
512 if alias
in overrides
:
514 "Can't override worker alias {alias} more than once. "
515 "Already set to use {previous}, but also asked "
516 "to add suffix {suffix}.".format(
517 alias
=alias
, previous
=overrides
[alias
], suffix
=suffix
521 provisioner
, worker_type
= get_worker_type(
522 graph_config
, worker_type
=alias
, parameters
={"level": "1"}
524 overrides
[alias
] = "{provisioner}/{worker_type}{suffix}".format(
525 provisioner
=provisioner
, worker_type
=worker_type
, suffix
=suffix
529 return {"worker-overrides": overrides
}
533 "artifact": Artifact
,
534 "browsertime": Browsertime
,
535 "chemspill-prio": ChemspillPrio
,
536 "disable-pgo": DisablePgo
,
538 "gecko-profile": GeckoProfile
,
540 "pernosco": Pernosco
,
543 "worker-overrides": WorkerOverrides
,