1 # -*- coding: utf-8 -*-
3 # This Source Code Form is subject to the terms of the Mozilla Public
4 # License, v. 2.0. If a copy of the MPL was not distributed with this
5 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
15 from functools
import partial
17 import gecko_taskgraph
.main
18 from gecko_taskgraph
.main
import commands
as taskgraph_commands
19 from mach
.decorators
import Command
, CommandArgument
, SettingsProvider
, SubCommand
21 logger
= logging
.getLogger("taskcluster")
25 class TaskgraphConfig(object):
27 def config_settings(cls
):
32 "The command to run with `./mach taskgraph --diff`",
33 "diff --report-identical-files "
34 "--label={attr}@{base} --label={attr}@{cur} -U20",
41 """Convert string to boolean.
43 Wraps "distutils.util.strtobool", deferring the import of the package
44 in case it's not installed. Otherwise, we have a "chicken and egg problem" where
45 |mach bootstrap| would install the required package to enable "distutils.util", but
46 it can't because mach fails to interpret this file.
48 from distutils
.util
import strtobool
50 return bool(strtobool(value
))
53 def get_taskgraph_command_parser(name
):
54 """Given a command name, obtain its argument parser.
57 name (str): Name of the command.
60 ArgumentParser: An ArgumentParser instance.
62 command
= taskgraph_commands
[name
]
63 parser
= argparse
.ArgumentParser()
64 for arg
in command
.func
.args
:
65 parser
.add_argument(*arg
[0], **arg
[1])
67 parser
.set_defaults(func
=command
.func
, **command
.defaults
)
71 def get_taskgraph_decision_parser():
72 parser
= get_taskgraph_command_parser("decision")
76 ["--optimize-target-tasks"],
78 "type": lambda flag
: strtobool(flag
),
81 "help": "If specified, this indicates whether the target "
82 "tasks are eligible for optimization. Otherwise, the default "
83 "for the project is used.",
87 ["--include-push-tasks"],
89 "action": "store_true",
90 "help": "Whether tasks from the on-push graph should be re-used "
91 "in this graph. This allows cron graphs to avoid rebuilding "
92 "jobs that were built on-push.",
98 "dest": "rebuild_kinds",
100 "default": argparse
.SUPPRESS
,
101 "help": "Kinds that should not be re-used from the on-push graph.",
105 for arg
in extra_args
:
106 parser
.add_argument(*arg
[0], **arg
[1])
114 description
="Manipulate TaskCluster task graphs defined in-tree",
116 def taskgraph_command(command_context
):
117 """The taskgraph subcommands all relate to the generation of task graphs
118 for Gecko continuous integration. A task graph is a set of tasks linked
119 by dependencies: for example, a binary must be built before it is tested,
120 and that build may further depend on various toolchains, libraries, etc.
127 description
="Show all tasks in the taskgraph",
128 parser
=partial(get_taskgraph_command_parser
, "tasks"),
130 def taskgraph_tasks(command_context
, **options
):
131 return run_show_taskgraph(command_context
, **options
)
137 description
="Show the full taskgraph",
138 parser
=partial(get_taskgraph_command_parser
, "full"),
140 def taskgraph_full(command_context
, **options
):
141 return run_show_taskgraph(command_context
, **options
)
147 description
="Show the target task set",
148 parser
=partial(get_taskgraph_command_parser
, "target"),
150 def taskgraph_target(command_context
, **options
):
151 return run_show_taskgraph(command_context
, **options
)
157 description
="Show the target taskgraph",
158 parser
=partial(get_taskgraph_command_parser
, "target-graph"),
160 def taskgraph_target_graph(command_context
, **options
):
161 return run_show_taskgraph(command_context
, **options
)
167 description
="Show the optimized taskgraph",
168 parser
=partial(get_taskgraph_command_parser
, "optimized"),
170 def taskgraph_optimized(command_context
, **options
):
171 return run_show_taskgraph(command_context
, **options
)
177 description
="Show the morphed taskgraph",
178 parser
=partial(get_taskgraph_command_parser
, "morphed"),
180 def taskgraph_morphed(command_context
, **options
):
181 return run_show_taskgraph(command_context
, **options
)
184 def run_show_taskgraph(command_context
, **options
):
185 # There are cases where we don't want to set up mach logging (e.g logs
186 # are being redirected to disk). By monkeypatching the 'setup_logging'
187 # function we can let 'taskgraph.main' decide whether or not to log to
189 gecko_taskgraph
.main
.setup_logging
= partial(
192 quiet
=options
["quiet"],
193 verbose
=options
["verbose"],
195 show_taskgraph
= options
.pop("func")
196 return show_taskgraph(options
)
199 @SubCommand("taskgraph", "actions", description
="Write actions.json to stdout")
201 "--root", "-r", help="root of the taskgraph definition relative to topsrcdir"
204 "--quiet", "-q", action
="store_true", help="suppress all logging output"
210 help="include debug-level logging output",
215 default
="project=mozilla-central",
216 help="parameters file (.yml or .json; see `taskcluster/docs/parameters.rst`)`",
218 def taskgraph_actions(command_context
, **options
):
219 return show_actions(command_context
, options
)
225 description
="Run the decision task",
226 parser
=get_taskgraph_decision_parser
,
228 def taskgraph_decision(command_context
, **options
):
229 """Run the decision task: generate a task graph and submit to
230 TaskCluster. This is only meant to be called within decision tasks,
231 and requires a great many arguments. Commands like `mach taskgraph
232 optimized` are better suited to use on the command line, and can take
233 the parameters file generated by a decision task."""
235 setup_logging(command_context
)
236 start
= time
.monotonic()
237 ret
= taskgraph_commands
["decision"].func(options
)
238 end
= time
.monotonic()
239 if os
.environ
.get("MOZ_AUTOMATION") == "1":
241 "framework": {"name": "build_metrics"},
245 "value": end
- start
,
246 "lowerIsBetter": True,
253 "PERFHERDER_DATA: {}".format(json
.dumps(perfherder_data
)),
258 traceback
.print_exc()
265 description
="Provide a pointer to the new `.cron.yml` handler.",
267 def taskgraph_cron(command_context
, **options
):
269 'Handling of ".cron.yml" files has move to '
270 "https://hg.mozilla.org/ci/ci-admin/file/default/build-decision."
278 description
="Run action callback used by action tasks",
279 parser
=partial(get_taskgraph_command_parser
, "action-callback"),
281 def action_callback(command_context
, **options
):
282 setup_logging(command_context
)
283 taskgraph_commands
["action-callback"].func(options
)
288 "test-action-callback",
289 description
="Run an action callback in a testing mode",
290 parser
=partial(get_taskgraph_command_parser
, "test-action-callback"),
292 def test_action_callback(command_context
, **options
):
293 setup_logging(command_context
)
295 if not options
["parameters"]:
296 options
["parameters"] = "project=mozilla-central"
298 taskgraph_commands
["test-action-callback"].func(options
)
301 def setup_logging(command_context
, quiet
=False, verbose
=True):
303 Set up Python logging for all loggers, sending results to stderr (so
304 that command output can be redirected easily) and adding the typical
307 # remove the old terminal handler
308 old
= command_context
.log_manager
.replace_terminal_handler(None)
310 # re-add it, with level and fh set appropriately
312 level
= logging
.DEBUG
if verbose
else logging
.INFO
313 command_context
.log_manager
.add_terminal_logging(
316 write_interval
=old
.formatter
.write_interval
,
317 write_times
=old
.formatter
.write_times
,
320 # all of the taskgraph logging is unstructured logging
321 command_context
.log_manager
.enable_unstructured()
324 def show_actions(command_context
, options
):
325 import gecko_taskgraph
326 import gecko_taskgraph
.actions
327 from taskgraph
.generator
import TaskGraphGenerator
328 from taskgraph
.parameters
import parameters_loader
332 command_context
, quiet
=options
["quiet"], verbose
=options
["verbose"]
334 parameters
= parameters_loader(options
["parameters"])
336 tgg
= TaskGraphGenerator(
337 root_dir
=options
.get("root"),
338 parameters
=parameters
,
341 actions
= gecko_taskgraph
.actions
.render_actions_json(
344 decision_task_id
="DECISION-TASK",
346 print(json
.dumps(actions
, sort_keys
=True, indent
=2, separators
=(",", ": ")))
348 traceback
.print_exc()
353 "taskcluster-load-image",
355 description
="Load a pre-built Docker image. Note that you need to "
356 "have docker installed and running for this to work.",
357 parser
=partial(get_taskgraph_command_parser
, "load-image"),
359 def load_image(command_context
, **kwargs
):
360 taskgraph_commands
["load-image"].func(kwargs
)
364 "taskcluster-build-image",
366 description
="Build a Docker image",
367 parser
=partial(get_taskgraph_command_parser
, "build-image"),
369 def build_image(command_context
, **kwargs
):
371 taskgraph_commands
["build-image"].func(kwargs
)
373 traceback
.print_exc()
378 "taskcluster-image-digest",
380 description
="Print the digest of the image of this name based on the "
381 "current contents of the tree.",
382 parser
=partial(get_taskgraph_command_parser
, "build-image"),
384 def image_digest(command_context
, **kwargs
):
385 taskgraph_commands
["image-digest"].func(kwargs
)
391 description
="Query balrog for release history used by enable partials generation",
396 help="The gecko project branch used in balrog, such as "
397 "mozilla-central, release, maple",
400 "--product", default
="Firefox", help="The product identifier, such as 'Firefox'"
402 def generate_partials_builds(command_context
, product
, branch
):
403 from gecko_taskgraph
.util
.partials
import populate_release_history
408 release_history
= {"release_history": populate_release_history(product
, branch
)}
411 release_history
, allow_unicode
=True, default_flow_style
=False
415 traceback
.print_exc()