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/.
6 Graph morphs are modifications to task-graphs that take place *after* the
9 These graph morphs are largely invisible to developers running `./mach`
10 locally, so they should be limited to changes that do not modify the meaning of
14 # Note that the translation of `{'task-reference': '..'}` and
15 # `artifact-reference` are handled in the optimization phase (since
16 # optimization involves dealing with taskIds directly). Similarly,
17 # `{'relative-datestamp': '..'}` is handled at the last possible moment during
26 from slugid
import nice
as slugid
27 from taskgraph
.graph
import Graph
28 from taskgraph
.morph
import register_morph
29 from taskgraph
.task
import Task
30 from taskgraph
.taskgraph
import TaskGraph
32 from .util
.workertypes
import get_worker_type
34 here
= os
.path
.abspath(os
.path
.dirname(__file__
))
35 logger
= logging
.getLogger(__name__
)
39 def amend_taskgraph(taskgraph
, label_to_taskid
, to_add
):
40 """Add the given tasks to the taskgraph, returning a new taskgraph"""
41 new_tasks
= taskgraph
.tasks
.copy()
42 new_edges
= set(taskgraph
.graph
.edges
)
44 new_tasks
[task
.task_id
] = task
45 assert task
.label
not in label_to_taskid
46 label_to_taskid
[task
.label
] = task
.task_id
47 for depname
, dep
in task
.dependencies
.items():
48 new_edges
.add((task
.task_id
, dep
, depname
))
50 taskgraph
= TaskGraph(new_tasks
, Graph(set(new_tasks
), new_edges
))
51 return taskgraph
, label_to_taskid
64 """Create the shell of a task that depends on `dependencies` and on the given docker
66 label
= f
"{purpose}-{target_task.label}"
68 # this is why all docker image tasks are included in the target task graph: we
69 # need to find them in label_to_taskid, even if nothing else required them
70 image_taskid
= label_to_taskid
["docker-image-" + image
]
72 provisioner_id
, worker_type
= get_worker_type(
78 deps
= copy
.copy(dependencies
)
79 deps
["docker-image"] = image_taskid
82 "provisionerId": provisioner_id
,
83 "workerType": worker_type
,
84 "dependencies": [d
for d
in deps
.values()],
85 "created": {"relative-datestamp": "0 seconds"},
86 "deadline": target_task
.task
["deadline"],
87 # no point existing past the parent task's deadline
88 "expires": target_task
.task
["deadline"],
91 "description": f
"{purpose} for {target_task.description}",
92 "owner": target_task
.task
["metadata"]["owner"],
93 "source": target_task
.task
["metadata"]["source"],
98 "path": "public/image.tar.zst",
99 "taskId": image_taskid
,
100 "type": "task-image",
102 "features": {"taskclusterProxy": True},
107 if image_taskid
not in taskgraph
.tasks
:
108 # The task above depends on the replaced docker-image not one in
109 # this current graph.
110 del deps
["docker-image"]
119 task
.task_id
= slugid()
123 # these regular expressions capture route prefixes for which we have a star
124 # scope, allowing them to be summarized. Each should correspond to a star scope
125 # in each Gecko `assume:repo:hg.mozilla.org/...` role.
126 SCOPE_SUMMARY_REGEXPS
= [
127 re
.compile(r
"(index:insert-task:docker\.images\.v1\.[^.]*\.).*"),
128 re
.compile(r
"(index:insert-task:gecko\.v2\.[^.]*\.).*"),
129 re
.compile(r
"(index:insert-task:comm\.v2\.[^.]*\.).*"),
144 task
= derive_misc_task(
155 # we need to "summarize" the scopes, otherwise a particularly
156 # namespace-heavy index task might have more scopes than can fit in a
157 # temporary credential.
159 for path
in index_paths
:
160 scope
= f
"index:insert-task:{path}"
161 for summ_re
in SCOPE_SUMMARY_REGEXPS
:
162 match
= summ_re
.match(scope
)
164 scope
= match
.group(1) + "*"
167 task
.task
["scopes"] = sorted(scopes
)
169 task
.task
["payload"]["command"] = ["insert-indexes.js"] + index_paths
170 task
.task
["payload"]["env"] = {
171 "TARGET_TASKID": parent_task
.task_id
,
172 "INDEX_RANK": index_rank
,
178 def add_index_tasks(taskgraph
, label_to_taskid
, parameters
, graph_config
):
180 The TaskCluster queue only allows 10 routes on a task, but we have tasks
181 with many more routes, for purposes of indexing. This graph morph adds
182 "index tasks" that depend on such tasks and do the index insertions
183 directly, avoiding the limits on task.routes.
185 logger
.debug("Morphing: adding index tasks")
187 # Add indexes for tasks that exceed MAX_ROUTES.
189 for label
, task
in taskgraph
.tasks
.items():
190 if len(task
.task
.get("routes", [])) <= MAX_ROUTES
:
193 r
.split(".", 1)[1] for r
in task
.task
["routes"] if r
.startswith("index.")
195 task
.task
["routes"] = [
196 r
for r
in task
.task
["routes"] if not r
.startswith("index.")
205 index_paths
=index_paths
,
206 index_rank
=task
.task
.get("extra", {}).get("index", {}).get("rank", 0),
207 purpose
="index-task",
208 dependencies
={"parent": task
.task_id
},
213 taskgraph
, label_to_taskid
= amend_taskgraph(taskgraph
, label_to_taskid
, added
)
214 logger
.info(f
"Added {len(added)} index tasks")
216 return taskgraph
, label_to_taskid
220 def add_eager_cache_index_tasks(taskgraph
, label_to_taskid
, parameters
, graph_config
):
222 Some tasks (e.g. cached tasks) we want to exist in the index before they even
223 run/complete. Our current use is to allow us to depend on an unfinished cached
224 task in future pushes. This graph morph adds "eager-index tasks" that depend on
225 the decision task and do the index insertions directly, which does not need to
226 wait on the pointed at task to complete.
228 logger
.debug("Morphing: Adding eager cached index's")
231 for label
, task
in taskgraph
.tasks
.items():
232 if "eager_indexes" not in task
.attributes
:
234 eager_indexes
= task
.attributes
["eager_indexes"]
242 index_paths
=eager_indexes
,
243 index_rank
=0, # Be sure complete tasks get priority
244 purpose
="eager-index",
250 taskgraph
, label_to_taskid
= amend_taskgraph(taskgraph
, label_to_taskid
, added
)
251 logger
.info(f
"Added {len(added)} eager index tasks")
252 return taskgraph
, label_to_taskid
256 def add_try_task_duplicates(taskgraph
, label_to_taskid
, parameters
, graph_config
):
257 try_config
= parameters
["try_task_config"]
258 rebuild
= try_config
.get("rebuild")
260 for task
in taskgraph
.tasks
.values():
261 if task
.label
in try_config
.get("tasks", []):
262 task
.attributes
["task_duplicates"] = rebuild
263 return taskgraph
, label_to_taskid