2016-05-17 01:53:22 +03:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
|
2016-08-09 00:03:38 +03:00
|
|
|
|
2016-05-17 01:53:22 +03:00
|
|
|
from __future__ import absolute_import, print_function, unicode_literals
|
|
|
|
|
2016-06-07 06:09:48 +03:00
|
|
|
import json
|
2016-05-18 21:02:51 +03:00
|
|
|
import logging
|
2016-05-17 01:53:22 +03:00
|
|
|
import sys
|
2016-05-16 20:44:24 +03:00
|
|
|
import traceback
|
2016-05-17 01:53:22 +03:00
|
|
|
|
|
|
|
from mach.decorators import (
|
|
|
|
CommandArgument,
|
|
|
|
CommandProvider,
|
|
|
|
Command,
|
|
|
|
SubCommand,
|
|
|
|
)
|
|
|
|
|
|
|
|
from mozbuild.base import MachCommandBase
|
|
|
|
|
2016-06-04 22:40:35 +03:00
|
|
|
ARTIFACT_URL = 'https://queue.taskcluster.net/v1/task/{}/artifacts/{}'
|
|
|
|
|
2016-05-17 01:53:22 +03:00
|
|
|
|
|
|
|
class ShowTaskGraphSubCommand(SubCommand):
|
|
|
|
"""A SubCommand with TaskGraph-specific arguments"""
|
|
|
|
|
|
|
|
def __call__(self, func):
|
|
|
|
after = SubCommand.__call__(self, func)
|
|
|
|
args = [
|
|
|
|
CommandArgument('--root', '-r', default='taskcluster/ci',
|
|
|
|
help="root of the taskgraph definition relative to topsrcdir"),
|
2016-05-18 21:02:51 +03:00
|
|
|
CommandArgument('--quiet', '-q', action="store_true",
|
|
|
|
help="suppress all logging output"),
|
|
|
|
CommandArgument('--verbose', '-v', action="store_true",
|
|
|
|
help="include debug-level logging output"),
|
2016-06-07 06:09:48 +03:00
|
|
|
CommandArgument('--json', '-J', action="store_const",
|
|
|
|
dest="format", const="json",
|
2016-06-09 19:15:23 +03:00
|
|
|
help="Output task graph as a JSON object"),
|
2016-06-07 06:09:48 +03:00
|
|
|
CommandArgument('--labels', '-L', action="store_const",
|
|
|
|
dest="format", const="labels",
|
|
|
|
help="Output the label for each task in the task graph (default)"),
|
2016-05-17 01:53:22 +03:00
|
|
|
CommandArgument('--parameters', '-p', required=True,
|
|
|
|
help="parameters file (.yml or .json; see "
|
|
|
|
"`taskcluster/docs/parameters.rst`)`"),
|
|
|
|
CommandArgument('--no-optimize', dest="optimize", action="store_false",
|
|
|
|
default="true",
|
|
|
|
help="do not remove tasks from the graph that are found in the "
|
|
|
|
"index (a.k.a. optimize the graph)"),
|
|
|
|
]
|
|
|
|
for arg in args:
|
|
|
|
after = arg(after)
|
|
|
|
return after
|
|
|
|
|
|
|
|
|
|
|
|
@CommandProvider
|
|
|
|
class MachCommands(MachCommandBase):
|
|
|
|
|
|
|
|
@Command('taskgraph', category="ci",
|
|
|
|
description="Manipulate TaskCluster task graphs defined in-tree")
|
|
|
|
def taskgraph(self):
|
|
|
|
"""The taskgraph subcommands all relate to the generation of task graphs
|
|
|
|
for Gecko continuous integration. A task graph is a set of tasks linked
|
|
|
|
by dependencies: for example, a binary must be built before it is tested,
|
|
|
|
and that build may further depend on various toolchains, libraries, etc.
|
|
|
|
"""
|
|
|
|
|
|
|
|
@SubCommand('taskgraph', 'python-tests',
|
|
|
|
description='Run the taskgraph unit tests')
|
|
|
|
def taskgraph_python_tests(self, **options):
|
|
|
|
import unittest
|
|
|
|
import mozunit
|
|
|
|
suite = unittest.defaultTestLoader.discover('taskgraph.test')
|
|
|
|
runner = mozunit.MozTestRunner(verbosity=2)
|
|
|
|
result = runner.run(suite)
|
2016-06-13 05:37:23 +03:00
|
|
|
if not result.wasSuccessful():
|
2016-05-17 01:53:22 +03:00
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
@ShowTaskGraphSubCommand('taskgraph', 'tasks',
|
2016-06-21 04:06:55 +03:00
|
|
|
description="Show all tasks in the taskgraph")
|
2016-05-17 01:53:22 +03:00
|
|
|
def taskgraph_tasks(self, **options):
|
|
|
|
return self.show_taskgraph('full_task_set', options)
|
|
|
|
|
|
|
|
@ShowTaskGraphSubCommand('taskgraph', 'full',
|
2016-06-21 04:06:55 +03:00
|
|
|
description="Show the full taskgraph")
|
2016-05-17 01:53:22 +03:00
|
|
|
def taskgraph_full(self, **options):
|
|
|
|
return self.show_taskgraph('full_task_graph', options)
|
|
|
|
|
|
|
|
@ShowTaskGraphSubCommand('taskgraph', 'target',
|
2016-06-21 04:06:55 +03:00
|
|
|
description="Show the target task set")
|
2016-05-17 01:53:22 +03:00
|
|
|
def taskgraph_target(self, **options):
|
|
|
|
return self.show_taskgraph('target_task_set', options)
|
|
|
|
|
|
|
|
@ShowTaskGraphSubCommand('taskgraph', 'target-graph',
|
2016-06-21 04:06:55 +03:00
|
|
|
description="Show the target taskgraph")
|
2016-05-17 01:53:22 +03:00
|
|
|
def taskgraph_target_taskgraph(self, **options):
|
|
|
|
return self.show_taskgraph('target_task_graph', options)
|
|
|
|
|
|
|
|
@ShowTaskGraphSubCommand('taskgraph', 'optimized',
|
2016-06-21 04:06:55 +03:00
|
|
|
description="Show the optimized taskgraph")
|
2016-05-17 01:53:22 +03:00
|
|
|
def taskgraph_optimized(self, **options):
|
|
|
|
return self.show_taskgraph('optimized_task_graph', options)
|
|
|
|
|
|
|
|
@SubCommand('taskgraph', 'decision',
|
|
|
|
description="Run the decision task")
|
|
|
|
@CommandArgument('--root', '-r',
|
2016-06-21 04:06:55 +03:00
|
|
|
default='taskcluster/ci',
|
|
|
|
help="root of the taskgraph definition relative to topsrcdir")
|
2016-05-17 01:53:22 +03:00
|
|
|
@CommandArgument('--base-repository',
|
2016-06-21 04:06:55 +03:00
|
|
|
required=True,
|
|
|
|
help='URL for "base" repository to clone')
|
2016-05-17 01:53:22 +03:00
|
|
|
@CommandArgument('--head-repository',
|
2016-06-21 04:06:55 +03:00
|
|
|
required=True,
|
|
|
|
help='URL for "head" repository to fetch revision from')
|
2016-05-17 01:53:22 +03:00
|
|
|
@CommandArgument('--head-ref',
|
2016-06-21 04:06:55 +03:00
|
|
|
required=True,
|
|
|
|
help='Reference (this is same as rev usually for hg)')
|
2016-05-17 01:53:22 +03:00
|
|
|
@CommandArgument('--head-rev',
|
2016-06-21 04:06:55 +03:00
|
|
|
required=True,
|
|
|
|
help='Commit revision to use from head repository')
|
2016-05-17 01:53:22 +03:00
|
|
|
@CommandArgument('--message',
|
2016-06-21 04:06:55 +03:00
|
|
|
required=True,
|
|
|
|
help='Commit message to be parsed. Example: "try: -b do -p all -u all"')
|
2016-05-17 01:53:22 +03:00
|
|
|
@CommandArgument('--revision-hash',
|
2016-06-21 04:06:55 +03:00
|
|
|
required=True,
|
|
|
|
help='Treeherder revision hash (long revision id) to attach results to')
|
2016-05-17 01:53:22 +03:00
|
|
|
@CommandArgument('--project',
|
2016-06-21 04:06:55 +03:00
|
|
|
required=True,
|
|
|
|
help='Project to use for creating task graph. Example: --project=try')
|
2016-05-17 01:53:22 +03:00
|
|
|
@CommandArgument('--pushlog-id',
|
2016-06-21 04:06:55 +03:00
|
|
|
dest='pushlog_id',
|
|
|
|
required=True,
|
|
|
|
default=0)
|
2016-07-13 21:50:50 +03:00
|
|
|
@CommandArgument('--pushdate',
|
|
|
|
dest='pushdate',
|
|
|
|
required=True,
|
|
|
|
type=int,
|
|
|
|
default=0)
|
2016-05-17 01:53:22 +03:00
|
|
|
@CommandArgument('--owner',
|
2016-06-21 04:06:55 +03:00
|
|
|
required=True,
|
|
|
|
help='email address of who owns this graph')
|
2016-05-17 01:53:22 +03:00
|
|
|
@CommandArgument('--level',
|
2016-06-21 04:06:55 +03:00
|
|
|
required=True,
|
|
|
|
help='SCM level of this repository')
|
2016-09-02 20:29:07 +03:00
|
|
|
@CommandArgument('--triggered-by',
|
|
|
|
choices=['nightly', 'push'],
|
|
|
|
default='push',
|
|
|
|
help='Source of execution of the decision graph')
|
|
|
|
@CommandArgument('--target-tasks-method',
|
|
|
|
help='method for selecting the target tasks to generate')
|
2016-05-17 01:53:22 +03:00
|
|
|
def taskgraph_decision(self, **options):
|
|
|
|
"""Run the decision task: generate a task graph and submit to
|
|
|
|
TaskCluster. This is only meant to be called within decision tasks,
|
|
|
|
and requires a great many arguments. Commands like `mach taskgraph
|
|
|
|
optimized` are better suited to use on the command line, and can take
|
|
|
|
the parameters file generated by a decision task. """
|
|
|
|
|
|
|
|
import taskgraph.decision
|
2016-05-16 20:44:24 +03:00
|
|
|
try:
|
2016-05-18 21:02:51 +03:00
|
|
|
self.setup_logging()
|
|
|
|
return taskgraph.decision.taskgraph_decision(options)
|
2016-06-21 04:06:55 +03:00
|
|
|
except Exception:
|
2016-05-16 20:44:24 +03:00
|
|
|
traceback.print_exc()
|
|
|
|
sys.exit(1)
|
2016-07-11 20:13:58 +03:00
|
|
|
|
|
|
|
@SubCommand('taskgraph', 'action-task',
|
|
|
|
description="Run the action task")
|
|
|
|
@CommandArgument('--root', '-r',
|
|
|
|
default='taskcluster/ci',
|
|
|
|
help="root of the taskgraph definition relative to topsrcdir")
|
|
|
|
@CommandArgument('--decision-id',
|
|
|
|
required=True,
|
|
|
|
help="Decision Task ID of the reference decision task")
|
|
|
|
@CommandArgument('--task-labels',
|
|
|
|
required=True,
|
|
|
|
help='Comma separated list of task labels to be scheduled')
|
|
|
|
def taskgraph_action(self, **options):
|
|
|
|
"""Run the action task: Generates a task graph using the set of labels
|
|
|
|
provided in the task-labels parameter. It uses the full-task file of
|
|
|
|
the gecko decision task."""
|
|
|
|
|
|
|
|
import taskgraph.action
|
|
|
|
try:
|
|
|
|
self.setup_logging()
|
|
|
|
return taskgraph.action.taskgraph_action(options)
|
|
|
|
except Exception:
|
|
|
|
traceback.print_exc()
|
|
|
|
sys.exit(1)
|
2016-05-17 01:53:22 +03:00
|
|
|
|
2016-05-18 21:02:51 +03:00
|
|
|
def setup_logging(self, quiet=False, verbose=True):
|
|
|
|
"""
|
|
|
|
Set up Python logging for all loggers, sending results to stderr (so
|
|
|
|
that command output can be redirected easily) and adding the typical
|
|
|
|
mach timestamp.
|
|
|
|
"""
|
|
|
|
# remove the old terminal handler
|
2016-07-30 06:37:42 +03:00
|
|
|
old = self.log_manager.replace_terminal_handler(None)
|
2016-05-18 21:02:51 +03:00
|
|
|
|
|
|
|
# re-add it, with level and fh set appropriately
|
|
|
|
if not quiet:
|
|
|
|
level = logging.DEBUG if verbose else logging.INFO
|
2016-07-30 06:37:42 +03:00
|
|
|
self.log_manager.add_terminal_logging(
|
|
|
|
fh=sys.stderr, level=level,
|
|
|
|
write_interval=old.formatter.write_interval,
|
|
|
|
write_times=old.formatter.write_times)
|
2016-05-18 21:02:51 +03:00
|
|
|
|
|
|
|
# all of the taskgraph logging is unstructured logging
|
|
|
|
self.log_manager.enable_unstructured()
|
|
|
|
|
2016-05-17 01:53:22 +03:00
|
|
|
def show_taskgraph(self, graph_attr, options):
|
|
|
|
import taskgraph.parameters
|
|
|
|
import taskgraph.target_tasks
|
|
|
|
import taskgraph.generator
|
|
|
|
|
2016-05-16 20:44:24 +03:00
|
|
|
try:
|
2016-05-18 21:02:51 +03:00
|
|
|
self.setup_logging(quiet=options['quiet'], verbose=options['verbose'])
|
2016-05-16 20:44:24 +03:00
|
|
|
parameters = taskgraph.parameters.load_parameters_file(options)
|
2016-05-17 01:53:22 +03:00
|
|
|
|
2016-05-16 20:44:24 +03:00
|
|
|
target_tasks_method = parameters.get('target_tasks_method', 'all_tasks')
|
|
|
|
target_tasks_method = taskgraph.target_tasks.get_method(target_tasks_method)
|
|
|
|
tgg = taskgraph.generator.TaskGraphGenerator(
|
|
|
|
root_dir=options['root'],
|
|
|
|
parameters=parameters,
|
|
|
|
target_tasks_method=target_tasks_method)
|
2016-05-17 01:53:22 +03:00
|
|
|
|
2016-05-16 20:44:24 +03:00
|
|
|
tg = getattr(tgg, graph_attr)
|
2016-05-17 01:53:22 +03:00
|
|
|
|
2016-06-07 06:09:48 +03:00
|
|
|
show_method = getattr(self, 'show_taskgraph_' + (options['format'] or 'labels'))
|
|
|
|
show_method(tg)
|
2016-06-21 04:06:55 +03:00
|
|
|
except Exception:
|
2016-05-16 20:44:24 +03:00
|
|
|
traceback.print_exc()
|
|
|
|
sys.exit(1)
|
2016-06-07 06:09:48 +03:00
|
|
|
|
|
|
|
def show_taskgraph_labels(self, taskgraph):
|
|
|
|
for label in taskgraph.graph.visit_postorder():
|
|
|
|
print(label)
|
|
|
|
|
|
|
|
def show_taskgraph_json(self, taskgraph):
|
2016-06-09 19:15:23 +03:00
|
|
|
print(json.dumps(taskgraph.to_json(),
|
|
|
|
sort_keys=True, indent=2, separators=(',', ': ')))
|
2016-06-04 22:40:35 +03:00
|
|
|
|
|
|
|
|
|
|
|
@CommandProvider
|
2016-07-29 22:45:25 +03:00
|
|
|
class TaskClusterImagesProvider(object):
|
2016-06-04 22:40:35 +03:00
|
|
|
@Command('taskcluster-load-image', category="ci",
|
2016-06-21 04:06:55 +03:00
|
|
|
description="Load a pre-built Docker image")
|
2016-06-04 22:40:35 +03:00
|
|
|
@CommandArgument('--task-id',
|
2016-06-21 04:06:55 +03:00
|
|
|
help="Load the image at public/image.tar in this task,"
|
|
|
|
"rather than searching the index")
|
2016-06-04 22:40:35 +03:00
|
|
|
@CommandArgument('image_name', nargs='?',
|
2016-06-21 04:06:55 +03:00
|
|
|
help="Load the image of this name based on the current"
|
|
|
|
"contents of the tree (as built for mozilla-central"
|
|
|
|
"or mozilla-inbound)")
|
2016-06-04 22:40:35 +03:00
|
|
|
def load_image(self, image_name, task_id):
|
2016-06-06 21:55:10 +03:00
|
|
|
from taskgraph.docker import load_image_by_name, load_image_by_task_id
|
2016-06-04 22:40:35 +03:00
|
|
|
if not image_name and not task_id:
|
|
|
|
print("Specify either IMAGE-NAME or TASK-ID")
|
|
|
|
sys.exit(1)
|
2016-06-06 21:55:10 +03:00
|
|
|
try:
|
|
|
|
if task_id:
|
|
|
|
ok = load_image_by_task_id(task_id)
|
|
|
|
else:
|
|
|
|
ok = load_image_by_name(image_name)
|
|
|
|
if not ok:
|
2016-06-04 22:40:35 +03:00
|
|
|
sys.exit(1)
|
2016-06-21 04:06:55 +03:00
|
|
|
except Exception:
|
2016-06-06 21:55:10 +03:00
|
|
|
traceback.print_exc()
|
|
|
|
sys.exit(1)
|
2016-07-29 22:45:25 +03:00
|
|
|
|
|
|
|
@Command('taskcluster-build-image', category='ci',
|
|
|
|
description='Build a Docker image')
|
|
|
|
@CommandArgument('image_name',
|
|
|
|
help='Name of the image to build')
|
|
|
|
def build_image(self, image_name):
|
|
|
|
from taskgraph.docker import build_image
|
|
|
|
|
|
|
|
try:
|
|
|
|
build_image(image_name)
|
|
|
|
except Exception:
|
|
|
|
traceback.print_exc()
|
|
|
|
sys.exit(1)
|