mach_commands.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. # -*- coding: utf-8 -*-
  2. # This Source Code Form is subject to the terms of the Mozilla Public
  3. # License, v. 2.0. If a copy of the MPL was not distributed with this
  4. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
  5. from __future__ import absolute_import, print_function, unicode_literals
  6. import json
  7. import logging
  8. import sys
  9. import traceback
  10. from mach.decorators import (
  11. CommandArgument,
  12. CommandProvider,
  13. Command,
  14. SubCommand,
  15. )
  16. from mozbuild.base import MachCommandBase
  17. ARTIFACT_URL = 'https://queue.taskcluster.net/v1/task/{}/artifacts/{}'
  18. class ShowTaskGraphSubCommand(SubCommand):
  19. """A SubCommand with TaskGraph-specific arguments"""
  20. def __call__(self, func):
  21. after = SubCommand.__call__(self, func)
  22. args = [
  23. CommandArgument('--root', '-r', default='taskcluster/ci',
  24. help="root of the taskgraph definition relative to topsrcdir"),
  25. CommandArgument('--quiet', '-q', action="store_true",
  26. help="suppress all logging output"),
  27. CommandArgument('--verbose', '-v', action="store_true",
  28. help="include debug-level logging output"),
  29. CommandArgument('--json', '-J', action="store_const",
  30. dest="format", const="json",
  31. help="Output task graph as a JSON object"),
  32. CommandArgument('--labels', '-L', action="store_const",
  33. dest="format", const="labels",
  34. help="Output the label for each task in the task graph (default)"),
  35. CommandArgument('--parameters', '-p', required=True,
  36. help="parameters file (.yml or .json; see "
  37. "`taskcluster/docs/parameters.rst`)`"),
  38. CommandArgument('--no-optimize', dest="optimize", action="store_false",
  39. default="true",
  40. help="do not remove tasks from the graph that are found in the "
  41. "index (a.k.a. optimize the graph)"),
  42. ]
  43. for arg in args:
  44. after = arg(after)
  45. return after
  46. @CommandProvider
  47. class MachCommands(MachCommandBase):
  48. @Command('taskgraph', category="ci",
  49. description="Manipulate TaskCluster task graphs defined in-tree")
  50. def taskgraph(self):
  51. """The taskgraph subcommands all relate to the generation of task graphs
  52. for Gecko continuous integration. A task graph is a set of tasks linked
  53. by dependencies: for example, a binary must be built before it is tested,
  54. and that build may further depend on various toolchains, libraries, etc.
  55. """
  56. @SubCommand('taskgraph', 'python-tests',
  57. description='Run the taskgraph unit tests')
  58. def taskgraph_python_tests(self, **options):
  59. import unittest
  60. import mozunit
  61. suite = unittest.defaultTestLoader.discover('taskgraph.test')
  62. runner = mozunit.MozTestRunner(verbosity=2)
  63. result = runner.run(suite)
  64. if not result.wasSuccessful():
  65. sys.exit(1)
  66. @ShowTaskGraphSubCommand('taskgraph', 'tasks',
  67. description="Show all tasks in the taskgraph")
  68. def taskgraph_tasks(self, **options):
  69. return self.show_taskgraph('full_task_set', options)
  70. @ShowTaskGraphSubCommand('taskgraph', 'full',
  71. description="Show the full taskgraph")
  72. def taskgraph_full(self, **options):
  73. return self.show_taskgraph('full_task_graph', options)
  74. @ShowTaskGraphSubCommand('taskgraph', 'target',
  75. description="Show the target task set")
  76. def taskgraph_target(self, **options):
  77. return self.show_taskgraph('target_task_set', options)
  78. @ShowTaskGraphSubCommand('taskgraph', 'target-graph',
  79. description="Show the target taskgraph")
  80. def taskgraph_target_taskgraph(self, **options):
  81. return self.show_taskgraph('target_task_graph', options)
  82. @ShowTaskGraphSubCommand('taskgraph', 'optimized',
  83. description="Show the optimized taskgraph")
  84. def taskgraph_optimized(self, **options):
  85. return self.show_taskgraph('optimized_task_graph', options)
  86. @SubCommand('taskgraph', 'decision',
  87. description="Run the decision task")
  88. @CommandArgument('--root', '-r',
  89. default='taskcluster/ci',
  90. help="root of the taskgraph definition relative to topsrcdir")
  91. @CommandArgument('--base-repository',
  92. required=True,
  93. help='URL for "base" repository to clone')
  94. @CommandArgument('--head-repository',
  95. required=True,
  96. help='URL for "head" repository to fetch revision from')
  97. @CommandArgument('--head-ref',
  98. required=True,
  99. help='Reference (this is same as rev usually for hg)')
  100. @CommandArgument('--head-rev',
  101. required=True,
  102. help='Commit revision to use from head repository')
  103. @CommandArgument('--message',
  104. required=True,
  105. help='Commit message to be parsed. Example: "try: -b do -p all -u all"')
  106. @CommandArgument('--revision-hash',
  107. required=True,
  108. help='Treeherder revision hash (long revision id) to attach results to')
  109. @CommandArgument('--project',
  110. required=True,
  111. help='Project to use for creating task graph. Example: --project=try')
  112. @CommandArgument('--pushlog-id',
  113. dest='pushlog_id',
  114. required=True,
  115. default=0)
  116. @CommandArgument('--pushdate',
  117. dest='pushdate',
  118. required=True,
  119. type=int,
  120. default=0)
  121. @CommandArgument('--owner',
  122. required=True,
  123. help='email address of who owns this graph')
  124. @CommandArgument('--level',
  125. required=True,
  126. help='SCM level of this repository')
  127. @CommandArgument('--triggered-by',
  128. choices=['nightly', 'push'],
  129. default='push',
  130. help='Source of execution of the decision graph')
  131. @CommandArgument('--target-tasks-method',
  132. help='method for selecting the target tasks to generate')
  133. def taskgraph_decision(self, **options):
  134. """Run the decision task: generate a task graph and submit to
  135. TaskCluster. This is only meant to be called within decision tasks,
  136. and requires a great many arguments. Commands like `mach taskgraph
  137. optimized` are better suited to use on the command line, and can take
  138. the parameters file generated by a decision task. """
  139. import taskgraph.decision
  140. try:
  141. self.setup_logging()
  142. return taskgraph.decision.taskgraph_decision(options)
  143. except Exception:
  144. traceback.print_exc()
  145. sys.exit(1)
  146. @SubCommand('taskgraph', 'action-task',
  147. description="Run the action task")
  148. @CommandArgument('--root', '-r',
  149. default='taskcluster/ci',
  150. help="root of the taskgraph definition relative to topsrcdir")
  151. @CommandArgument('--decision-id',
  152. required=True,
  153. help="Decision Task ID of the reference decision task")
  154. @CommandArgument('--task-labels',
  155. required=True,
  156. help='Comma separated list of task labels to be scheduled')
  157. def taskgraph_action(self, **options):
  158. """Run the action task: Generates a task graph using the set of labels
  159. provided in the task-labels parameter. It uses the full-task file of
  160. the gecko decision task."""
  161. import taskgraph.action
  162. try:
  163. self.setup_logging()
  164. return taskgraph.action.taskgraph_action(options)
  165. except Exception:
  166. traceback.print_exc()
  167. sys.exit(1)
  168. def setup_logging(self, quiet=False, verbose=True):
  169. """
  170. Set up Python logging for all loggers, sending results to stderr (so
  171. that command output can be redirected easily) and adding the typical
  172. mach timestamp.
  173. """
  174. # remove the old terminal handler
  175. old = self.log_manager.replace_terminal_handler(None)
  176. # re-add it, with level and fh set appropriately
  177. if not quiet:
  178. level = logging.DEBUG if verbose else logging.INFO
  179. self.log_manager.add_terminal_logging(
  180. fh=sys.stderr, level=level,
  181. write_interval=old.formatter.write_interval,
  182. write_times=old.formatter.write_times)
  183. # all of the taskgraph logging is unstructured logging
  184. self.log_manager.enable_unstructured()
  185. def show_taskgraph(self, graph_attr, options):
  186. import taskgraph.parameters
  187. import taskgraph.target_tasks
  188. import taskgraph.generator
  189. try:
  190. self.setup_logging(quiet=options['quiet'], verbose=options['verbose'])
  191. parameters = taskgraph.parameters.load_parameters_file(options)
  192. parameters.check()
  193. target_tasks_method = parameters.get('target_tasks_method', 'all_tasks')
  194. target_tasks_method = taskgraph.target_tasks.get_method(target_tasks_method)
  195. tgg = taskgraph.generator.TaskGraphGenerator(
  196. root_dir=options['root'],
  197. parameters=parameters,
  198. target_tasks_method=target_tasks_method)
  199. tg = getattr(tgg, graph_attr)
  200. show_method = getattr(self, 'show_taskgraph_' + (options['format'] or 'labels'))
  201. show_method(tg)
  202. except Exception:
  203. traceback.print_exc()
  204. sys.exit(1)
  205. def show_taskgraph_labels(self, taskgraph):
  206. for label in taskgraph.graph.visit_postorder():
  207. print(label)
  208. def show_taskgraph_json(self, taskgraph):
  209. print(json.dumps(taskgraph.to_json(),
  210. sort_keys=True, indent=2, separators=(',', ': ')))
  211. @CommandProvider
  212. class TaskClusterImagesProvider(object):
  213. @Command('taskcluster-load-image', category="ci",
  214. description="Load a pre-built Docker image")
  215. @CommandArgument('--task-id',
  216. help="Load the image at public/image.tar in this task,"
  217. "rather than searching the index")
  218. @CommandArgument('image_name', nargs='?',
  219. help="Load the image of this name based on the current"
  220. "contents of the tree (as built for mozilla-central"
  221. "or mozilla-inbound)")
  222. def load_image(self, image_name, task_id):
  223. from taskgraph.docker import load_image_by_name, load_image_by_task_id
  224. if not image_name and not task_id:
  225. print("Specify either IMAGE-NAME or TASK-ID")
  226. sys.exit(1)
  227. try:
  228. if task_id:
  229. ok = load_image_by_task_id(task_id)
  230. else:
  231. ok = load_image_by_name(image_name)
  232. if not ok:
  233. sys.exit(1)
  234. except Exception:
  235. traceback.print_exc()
  236. sys.exit(1)
  237. @Command('taskcluster-build-image', category='ci',
  238. description='Build a Docker image')
  239. @CommandArgument('image_name',
  240. help='Name of the image to build')
  241. @CommandArgument('--context-only',
  242. help="File name the context tarball should be written to."
  243. "with this option it will only build the context.tar.",
  244. metavar='context.tar')
  245. def build_image(self, image_name, context_only):
  246. from taskgraph.docker import build_image, build_context
  247. try:
  248. if context_only is None:
  249. build_image(image_name)
  250. else:
  251. build_context(image_name, context_only)
  252. except Exception:
  253. traceback.print_exc()
  254. sys.exit(1)