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.
10 from __future__
import absolute_import
, print_function
, unicode_literals
17 from abc
import ABCMeta
, abstractmethod
, abstractproperty
18 from argparse
import Action
, SUPPRESS
19 from textwrap
import dedent
21 import mozpack
.path
as mozpath
22 from mozbuild
.base
import BuildEnvironmentNotFoundException
, MozbuildObject
23 from taskgraph
.util
.python_path
import find_object
25 from .tasks
import resolve_tests_by_suite
27 here
= os
.path
.abspath(os
.path
.dirname(__file__
))
28 build
= MozbuildObject
.from_environment(cwd
=here
)
31 class TryConfig(object):
32 __metaclass__
= ABCMeta
37 def add_arguments(self
, parser
):
38 for cli
, kwargs
in self
.arguments
:
39 action
= parser
.add_argument(*cli
, **kwargs
)
40 self
.dests
.add(action
.dest
)
47 def try_config(self
, **kwargs
):
50 def validate(self
, **kwargs
):
54 class Artifact(TryConfig
):
58 {'action': 'store_true',
59 'help': 'Force artifact builds where possible.'
62 {'action': 'store_true',
63 'help': 'Disable artifact builds even if being used locally.',
67 def add_arguments(self
, parser
):
68 group
= parser
.add_mutually_exclusive_group()
69 return super(Artifact
, self
).add_arguments(group
)
72 def is_artifact_build(cls
):
74 return build
.substs
.get("MOZ_ARTIFACT_BUILDS", False)
75 except BuildEnvironmentNotFoundException
:
78 def try_config(self
, artifact
, no_artifact
, **kwargs
):
81 'use-artifact-builds': True
87 if self
.is_artifact_build():
88 print("Artifact builds enabled, pass --no-artifact to disable")
90 'use-artifact-builds': True
94 class Pernosco(TryConfig
):
97 {'action': 'store_true',
99 'help': 'Opt-in to analysis by the Pernosco debugging service.',
103 'action': 'store_false',
105 'help': 'Opt-out of the Pernosco debugging service (if you are on the whitelist).',
109 def add_arguments(self
, parser
):
110 group
= parser
.add_mutually_exclusive_group()
111 return super(Pernosco
, self
).add_arguments(group
)
113 def try_config(self
, pernosco
, **kwargs
):
119 # The Pernosco service currently requires a Mozilla e-mail address to
120 # log in. Prevent people with non-Mozilla addresses from using this
121 # flag so they don't end up consuming time and resources only to
122 # realize they can't actually log in and see the reports.
123 cmd
= ['ssh', '-G', 'hg.mozilla.org']
124 output
= subprocess
.check_output(cmd
, universal_newlines
=True).splitlines()
125 address
= [l
.rsplit(' ', 1)[-1] for l
in output
if l
.startswith('user')][0]
126 if not address
.endswith('@mozilla.com'):
128 Pernosco requires a Mozilla e-mail address to view its reports. Please
129 push to try with an @mozilla.com address to use --pernosco.
132 """.format(address
)))
135 except (subprocess
.CalledProcessError
, IndexError):
136 print("warning: failed to detect current user for 'hg.mozilla.org'")
137 print("Pernosco requires a Mozilla e-mail address to view its reports.")
139 answer
= input("Do you have an @mozilla.com address? [Y/n]: ").lower()
147 'PERNOSCO': str(int(pernosco
)),
151 def validate(self
, **kwargs
):
152 if kwargs
['try_config'].get('use-artifact-builds'):
153 print("Pernosco does not support artifact builds at this time. "
154 "Please try again with '--no-artifact'.")
158 class Path(TryConfig
):
164 'help': 'Run tasks containing tests under the specified path(s).',
168 def try_config(self
, paths
, **kwargs
):
173 if not os
.path
.exists(p
):
174 print("error: '{}' is not a valid path.".format(p
), file=sys
.stderr
)
177 paths
= [mozpath
.relpath(mozpath
.join(os
.getcwd(), p
), build
.topsrcdir
) for p
in paths
]
180 'MOZHARNESS_TEST_PATHS': six
.ensure_text(
181 json
.dumps(resolve_tests_by_suite(paths
))),
186 class Environment(TryConfig
):
192 'help': 'Set an environment variable, of the form FOO=BAR. '
193 'Can be passed in multiple times.',
197 def try_config(self
, env
, **kwargs
):
201 'env': dict(e
.split('=', 1) for e
in env
),
205 class RangeAction(Action
):
206 def __init__(self
, min, max, *args
, **kwargs
):
209 kwargs
['metavar'] = '[{}-{}]'.format(self
.min, self
.max)
210 super(RangeAction
, self
).__init
__(*args
, **kwargs
)
212 def __call__(self
, parser
, namespace
, values
, option_string
=None):
213 name
= option_string
or self
.dest
214 if values
< self
.min:
215 parser
.error('{} can not be less than {}'.format(name
, self
.min))
216 if values
> self
.max:
217 parser
.error('{} can not be more than {}'.format(name
, self
.max))
218 setattr(namespace
, self
.dest
, values
)
221 class Rebuild(TryConfig
):
225 {'action': RangeAction
,
230 'help': 'Rebuild all selected tasks the specified number of times.',
234 def try_config(self
, rebuild
, **kwargs
):
238 if kwargs
.get('full') and rebuild
> 3:
239 print('warning: limiting --rebuild to 3 when using --full. '
240 'Use custom push actions to add more.')
248 class Routes(TryConfig
):
256 "Additional route to add to the tasks "
257 "(note: these will not be added to the decision task)"
263 def try_config(self
, routes
, **kwargs
):
270 class ChemspillPrio(TryConfig
):
273 [['--chemspill-prio'],
274 {'action': 'store_true',
275 'help': 'Run at a higher priority than most try jobs (chemspills only).',
279 def try_config(self
, chemspill_prio
, **kwargs
):
286 class GeckoProfile(TryConfig
):
288 [['--gecko-profile'],
290 'action': 'store_true',
292 'help': 'Create and upload a gecko profile during talos/raptor tasks.',
294 # For backwards compatibility
295 [['--talos-profile'],
297 'action': 'store_true',
301 # This is added for consistency with the 'syntax' selector
304 'action': 'store_true',
310 def try_config(self
, profile
, **kwargs
):
313 'gecko-profile': True,
317 class OptimizeStrategies(TryConfig
):
322 'help': 'Override the default optimization strategy. Valid values '
323 'are the experimental strategies defined at the bottom of '
324 '`taskcluster/taskgraph/optimize/__init__.py`.'
328 def try_config(self
, strategy
, **kwargs
):
330 if ':' not in strategy
:
331 strategy
= "taskgraph.optimize:tryselect.{}".format(strategy
)
334 obj
= find_object(strategy
)
335 except (ImportError, AttributeError):
336 print("error: invalid module path '{}'".format(strategy
))
339 if not isinstance(obj
, dict):
340 print("error: object at '{}' must be a dict".format(strategy
))
344 'optimize-strategies': strategy
,
348 class Browsertime(TryConfig
):
351 {'action': 'store_true',
352 'help': 'Use browsertime during Raptor tasks.',
356 def try_config(self
, browsertime
, **kwargs
):
363 class DisablePgo(TryConfig
):
367 {'action': 'store_true',
368 'help': 'Don\'t run PGO builds',
372 def try_config(self
, disable_pgo
, **kwargs
):
379 class WorkerOverrides(TryConfig
):
383 ["--worker-override"],
386 "dest": "worker_overrides",
388 "Override the worker pool used for a given taskgraph worker alias. "
389 "The argument should be `<alias>=<worker-pool>`. "
390 "Can be specified multiple times."
398 "dest": "worker_suffixes",
400 "Override the worker pool used for a given taskgraph worker alias, "
401 "by appending a suffix to the work-pool. "
402 "The argument should be `<alias>=<suffix>`. "
403 "Can be specified multiple times."
409 def try_config(self
, worker_overrides
, worker_suffixes
, **kwargs
):
410 from taskgraph
.config
import load_graph_config
411 from taskgraph
.util
.workertypes
import get_worker_type
415 for override
in worker_overrides
:
416 alias
, worker_pool
= override
.split("=", 1)
417 if alias
in overrides
:
419 "Can't override worker alias {alias} more than once. "
420 "Already set to use {previous}, but also asked to use {new}.".format(
421 alias
=alias
, previous
=overrides
[alias
], new
=worker_pool
425 overrides
[alias
] = worker_pool
428 root
= build
.topsrcdir
429 root
= os
.path
.join(root
, "taskcluster", "ci")
430 graph_config
= load_graph_config(root
)
431 for worker_suffix
in worker_suffixes
:
432 alias
, suffix
= worker_suffix
.split("=", 1)
433 if alias
in overrides
:
435 "Can't override worker alias {alias} more than once. "
436 "Already set to use {previous}, but also asked "
437 "to add suffix {suffix}.".format(
438 alias
=alias
, previous
=overrides
[alias
], suffix
=suffix
442 provisioner
, worker_type
= get_worker_type(
443 graph_config
, alias
, level
="1", release_level
="staging",
445 overrides
[alias
] = "{provisioner}/{worker_type}{suffix}".format(
446 provisioner
=provisioner
, worker_type
=worker_type
, suffix
=suffix
450 return {"worker-overrides": overrides
}
454 'artifact': Artifact
,
455 'browsertime': Browsertime
,
456 'chemspill-prio': ChemspillPrio
,
457 'disable-pgo': DisablePgo
,
459 'gecko-profile': GeckoProfile
,
461 'pernosco': Pernosco
,
464 'strategy': OptimizeStrategies
,
465 'worker-overrides': WorkerOverrides
,