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
5from __future__ import absolute_import, print_function, unicode_literals
6
7import attr
8
9
10@attr.s
11class Task(object):
12    """
13    Representation of a task in a TaskGraph.  Each Task has, at creation:
14
15    - kind: the name of the task kind
16    - label; the label for this task
17    - attributes: a dictionary of attributes for this task (used for filtering)
18    - task: the task definition (JSON-able dictionary)
19    - optimization: optimization to apply to the task (see taskgraph.optimize)
20    - dependencies: tasks this one depends on, in the form {name: label}, for example
21      {'build': 'build-linux64/opt', 'docker-image': 'docker-image-desktop-test'}
22    - soft_dependencies: tasks this one may depend on if they are available post
23      optimisation. They are set as a list of tasks label.
24    - if_dependencies: only run this task if at least one of these dependencies
25      are present.
26
27    And later, as the task-graph processing proceeds:
28
29    - task_id -- TaskCluster taskId under which this task will be created
30
31    This class is just a convenience wrapper for the data type and managing
32    display, comparison, serialization, etc. It has no functionality of its own.
33    """
34
35    kind = attr.ib()
36    label = attr.ib()
37    attributes = attr.ib()
38    task = attr.ib()
39    description = attr.ib(default="")
40    task_id = attr.ib(default=None, init=False)
41    optimization = attr.ib(default=None)
42    dependencies = attr.ib(factory=dict)
43    soft_dependencies = attr.ib(factory=list)
44    if_dependencies = attr.ib(factory=list)
45    release_artifacts = attr.ib(
46        converter=attr.converters.optional(frozenset),
47        default=None,
48    )
49
50    def __attrs_post_init__(self):
51        self.attributes["kind"] = self.kind
52
53    @property
54    def name(self):
55        if self.label.startswith(self.kind + "-"):
56            return self.label[len(self.kind) + 1 :]
57        else:
58            raise AttributeError("Task {} does not have a name.".format(self.label))
59
60    def to_json(self):
61        rv = {
62            "kind": self.kind,
63            "label": self.label,
64            "description": self.description,
65            "attributes": self.attributes,
66            "dependencies": self.dependencies,
67            "soft_dependencies": sorted(self.soft_dependencies),
68            "if_dependencies": self.if_dependencies,
69            "optimization": self.optimization,
70            "task": self.task,
71        }
72        if self.task_id:
73            rv["task_id"] = self.task_id
74        if self.release_artifacts:
75            rv["release_artifacts"] = sorted(self.release_artifacts)
76        return rv
77
78    @classmethod
79    def from_json(cls, task_dict):
80        """
81        Given a data structure as produced by taskgraph.to_json, re-construct
82        the original Task object.  This is used to "resume" the task-graph
83        generation process, for example in Action tasks.
84        """
85        rv = cls(
86            kind=task_dict["kind"],
87            label=task_dict["label"],
88            description=task_dict.get("description", ""),
89            attributes=task_dict["attributes"],
90            task=task_dict["task"],
91            optimization=task_dict["optimization"],
92            dependencies=task_dict.get("dependencies"),
93            soft_dependencies=task_dict.get("soft_dependencies"),
94            if_dependencies=task_dict.get("if_dependencies"),
95            release_artifacts=task_dict.get("release-artifacts"),
96        )
97        if "task_id" in task_dict:
98            rv.task_id = task_dict["task_id"]
99        return rv
100