generator.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  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/.
  4. from __future__ import absolute_import, print_function, unicode_literals
  5. import logging
  6. import os
  7. import yaml
  8. from .graph import Graph
  9. from .taskgraph import TaskGraph
  10. from .optimize import optimize_task_graph
  11. from .util.python_path import find_object
  12. logger = logging.getLogger(__name__)
  13. class Kind(object):
  14. def __init__(self, name, path, config):
  15. self.name = name
  16. self.path = path
  17. self.config = config
  18. def _get_impl_class(self):
  19. # load the class defined by implementation
  20. try:
  21. impl = self.config['implementation']
  22. except KeyError:
  23. raise KeyError("{!r} does not define implementation".format(self.path))
  24. return find_object(impl)
  25. def load_tasks(self, parameters, loaded_tasks):
  26. impl_class = self._get_impl_class()
  27. return impl_class.load_tasks(self.name, self.path, self.config,
  28. parameters, loaded_tasks)
  29. class TaskGraphGenerator(object):
  30. """
  31. The central controller for taskgraph. This handles all phases of graph
  32. generation. The task is generated from all of the kinds defined in
  33. subdirectories of the generator's root directory.
  34. Access to the results of this generation, as well as intermediate values at
  35. various phases of generation, is available via properties. This encourages
  36. the provision of all generation inputs at instance construction time.
  37. """
  38. # Task-graph generation is implemented as a Python generator that yields
  39. # each "phase" of generation. This allows some mach subcommands to short-
  40. # circuit generation of the entire graph by never completing the generator.
  41. def __init__(self, root_dir, parameters,
  42. target_tasks_method):
  43. """
  44. @param root_dir: root directory, with subdirectories for each kind
  45. @param parameters: parameters for this task-graph generation
  46. @type parameters: dict
  47. @param target_tasks_method: function to determine the target_task_set;
  48. see `./target_tasks.py`.
  49. @type target_tasks_method: function
  50. """
  51. self.root_dir = root_dir
  52. self.parameters = parameters
  53. self.target_tasks_method = target_tasks_method
  54. # this can be set up until the time the target task set is generated;
  55. # it defaults to parameters['target_tasks']
  56. self._target_tasks = parameters.get('target_tasks')
  57. # start the generator
  58. self._run = self._run()
  59. self._run_results = {}
  60. @property
  61. def full_task_set(self):
  62. """
  63. The full task set: all tasks defined by any kind (a graph without edges)
  64. @type: TaskGraph
  65. """
  66. return self._run_until('full_task_set')
  67. @property
  68. def full_task_graph(self):
  69. """
  70. The full task graph: the full task set, with edges representing
  71. dependencies.
  72. @type: TaskGraph
  73. """
  74. return self._run_until('full_task_graph')
  75. @property
  76. def target_task_set(self):
  77. """
  78. The set of targetted tasks (a graph without edges)
  79. @type: TaskGraph
  80. """
  81. return self._run_until('target_task_set')
  82. @property
  83. def target_task_graph(self):
  84. """
  85. The set of targetted tasks and all of their dependencies
  86. @type: TaskGraph
  87. """
  88. return self._run_until('target_task_graph')
  89. @property
  90. def optimized_task_graph(self):
  91. """
  92. The set of targetted tasks and all of their dependencies; tasks that
  93. have been optimized out are either omitted or replaced with a Task
  94. instance containing only a task_id.
  95. @type: TaskGraph
  96. """
  97. return self._run_until('optimized_task_graph')
  98. @property
  99. def label_to_taskid(self):
  100. """
  101. A dictionary mapping task label to assigned taskId. This property helps
  102. in interpreting `optimized_task_graph`.
  103. @type: dictionary
  104. """
  105. return self._run_until('label_to_taskid')
  106. def _load_kinds(self):
  107. for path in os.listdir(self.root_dir):
  108. path = os.path.join(self.root_dir, path)
  109. if not os.path.isdir(path):
  110. continue
  111. kind_name = os.path.basename(path)
  112. kind_yml = os.path.join(path, 'kind.yml')
  113. if not os.path.exists(kind_yml):
  114. continue
  115. logger.debug("loading kind `{}` from `{}`".format(kind_name, path))
  116. with open(kind_yml) as f:
  117. config = yaml.load(f)
  118. yield Kind(kind_name, path, config)
  119. def _run(self):
  120. logger.info("Loading kinds")
  121. # put the kinds into a graph and sort topologically so that kinds are loaded
  122. # in post-order
  123. kinds = {kind.name: kind for kind in self._load_kinds()}
  124. edges = set()
  125. for kind in kinds.itervalues():
  126. for dep in kind.config.get('kind-dependencies', []):
  127. edges.add((kind.name, dep, 'kind-dependency'))
  128. kind_graph = Graph(set(kinds), edges)
  129. logger.info("Generating full task set")
  130. all_tasks = {}
  131. for kind_name in kind_graph.visit_postorder():
  132. logger.debug("Loading tasks for kind {}".format(kind_name))
  133. kind = kinds[kind_name]
  134. new_tasks = kind.load_tasks(self.parameters, list(all_tasks.values()))
  135. for task in new_tasks:
  136. if task.label in all_tasks:
  137. raise Exception("duplicate tasks with label " + task.label)
  138. all_tasks[task.label] = task
  139. logger.info("Generated {} tasks for kind {}".format(len(new_tasks), kind_name))
  140. full_task_set = TaskGraph(all_tasks, Graph(set(all_tasks), set()))
  141. yield 'full_task_set', full_task_set
  142. logger.info("Generating full task graph")
  143. edges = set()
  144. for t in full_task_set:
  145. for dep, depname in t.get_dependencies(full_task_set):
  146. edges.add((t.label, dep, depname))
  147. full_task_graph = TaskGraph(all_tasks,
  148. Graph(full_task_set.graph.nodes, edges))
  149. yield 'full_task_graph', full_task_graph
  150. logger.info("Generating target task set")
  151. target_tasks = set(self.target_tasks_method(full_task_graph, self.parameters))
  152. target_task_set = TaskGraph(
  153. {l: all_tasks[l] for l in target_tasks},
  154. Graph(target_tasks, set()))
  155. yield 'target_task_set', target_task_set
  156. logger.info("Generating target task graph")
  157. target_graph = full_task_graph.graph.transitive_closure(target_tasks)
  158. target_task_graph = TaskGraph(
  159. {l: all_tasks[l] for l in target_graph.nodes},
  160. target_graph)
  161. yield 'target_task_graph', target_task_graph
  162. logger.info("Generating optimized task graph")
  163. do_not_optimize = set()
  164. if not self.parameters.get('optimize_target_tasks', True):
  165. do_not_optimize = target_task_set.graph.nodes
  166. optimized_task_graph, label_to_taskid = optimize_task_graph(target_task_graph,
  167. self.parameters,
  168. do_not_optimize)
  169. yield 'label_to_taskid', label_to_taskid
  170. yield 'optimized_task_graph', optimized_task_graph
  171. def _run_until(self, name):
  172. while name not in self._run_results:
  173. try:
  174. k, v = self._run.next()
  175. except StopIteration:
  176. raise AttributeError("No such run result {}".format(name))
  177. self._run_results[k] = v
  178. return self._run_results[name]