1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243
|
# 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/.
"""
Transforms used to create tasks based on the kind dependencies, filtering on
common attributes like the ``build-type``.
These transforms are useful when follow-up tasks are needed for some
indeterminate subset of existing tasks. For example, running a signing task
after each build task, whatever builds may exist.
"""
from copy import deepcopy
from textwrap import dedent
from voluptuous import Any, Extra, Optional, Required
from taskgraph.transforms.base import TransformSequence
from taskgraph.transforms.run import fetches_schema
from taskgraph.util.attributes import attrmatch
from taskgraph.util.dependencies import GROUP_BY_MAP, get_dependencies
from taskgraph.util.schema import Schema, validate_schema
from taskgraph.util.set_name import SET_NAME_MAP
FROM_DEPS_SCHEMA = Schema(
{
Required("from-deps"): {
Optional(
"kinds",
description=dedent(
"""
Limit dependencies to specified kinds (defaults to all kinds in
`kind-dependencies`).
The first kind in the list is the "primary" kind. The
dependency of this kind will be used to derive the label
and copy attributes (if `copy-attributes` is True).
""".lstrip()
),
): list,
Optional(
"set-name",
description=dedent(
"""
UPDATE ME AND DOCS
""".lstrip()
),
): Any(
None,
*SET_NAME_MAP,
{Any(*SET_NAME_MAP): object},
),
Optional(
"with-attributes",
description=dedent(
"""
Limit dependencies to tasks whose attributes match
using :func:`~taskgraph.util.attributes.attrmatch`.
""".lstrip()
),
): {str: Any(list, str)},
Optional(
"group-by",
description=dedent(
"""
Group cross-kind dependencies using the given group-by
function. One task will be created for each group. If not
specified, the 'single' function will be used which creates
a new task for each individual dependency.
""".lstrip()
),
): Any(
None,
*GROUP_BY_MAP,
{Any(*GROUP_BY_MAP): object},
),
Optional(
"copy-attributes",
description=dedent(
"""
If True, copy attributes from the dependency matching the
first kind in the `kinds` list (whether specified explicitly
or taken from `kind-dependencies`).
""".lstrip()
),
): bool,
Optional(
"unique-kinds",
description=dedent(
"""
If true (the default), there must be only a single unique task
for each kind in a dependency group. Setting this to false
disables that requirement.
""".lstrip()
),
): bool,
Optional(
"fetches",
description=dedent(
"""
If present, a `fetches` entry will be added for each task
dependency. Attributes of the upstream task may be used as
substitution values in the `artifact` or `dest` values of the
`fetches` entry.
""".lstrip()
),
): {str: [fetches_schema]},
},
Extra: object,
},
)
"""Schema for from_deps transforms."""
transforms = TransformSequence()
transforms.add_validate(FROM_DEPS_SCHEMA)
@transforms.add
def from_deps(config, tasks):
for task in tasks:
# Setup and error handling.
from_deps = task.pop("from-deps")
kind_deps = config.config.get("kind-dependencies", [])
kinds = from_deps.get("kinds", kind_deps)
invalid = set(kinds) - set(kind_deps)
if invalid:
invalid = "\n".join(sorted(invalid))
raise Exception(
dedent(
f"""
The `from-deps.kinds` key contains the following kinds
that are not defined in `kind-dependencies`:
{invalid}
""".lstrip()
)
)
if not kinds:
raise Exception(
dedent(
"""
The `from_deps` transforms require at least one kind defined
in `kind-dependencies`!
""".lstrip()
)
)
# Resolve desired dependencies.
with_attributes = from_deps.get("with-attributes")
deps = [
task
for task in config.kind_dependencies_tasks.values()
if task.kind in kinds
if not with_attributes or attrmatch(task.attributes, **with_attributes)
]
# Resolve groups.
group_by = from_deps.get("group-by", "single")
groups = set()
if isinstance(group_by, dict):
assert len(group_by) == 1
group_by, arg = group_by.popitem()
func = GROUP_BY_MAP[group_by]
if func.schema:
validate_schema(
func.schema, arg, f"Invalid group-by {group_by} argument"
)
groups = func(config, deps, arg)
else:
func = GROUP_BY_MAP[group_by]
groups = func(config, deps)
# Split the task, one per group.
set_name = from_deps.get("set-name", "strip-kind")
copy_attributes = from_deps.get("copy-attributes", False)
unique_kinds = from_deps.get("unique-kinds", True)
fetches = from_deps.get("fetches", [])
for group in groups:
# Verify there is only one task per kind in each group.
group_kinds = {t.kind for t in group}
if unique_kinds and len(group_kinds) < len(group):
raise Exception(
"The from_deps transforms only allow a single task per kind in a group!"
)
new_task = deepcopy(task)
new_task.setdefault("dependencies", {})
new_task["dependencies"].update(
{dep.kind if unique_kinds else dep.label: dep.label for dep in group}
)
# Set name and copy attributes from the primary kind.
for kind in kinds:
if kind in group_kinds:
primary_kind = kind
break
else:
raise Exception("Could not detect primary kind!")
new_task.setdefault("attributes", {})[
"primary-kind-dependency"
] = primary_kind
primary_dep = [dep for dep in group if dep.kind == primary_kind][0]
if set_name:
func = SET_NAME_MAP[set_name]
new_task["name"] = func(config, deps, primary_dep, primary_kind)
if copy_attributes:
attrs = new_task.setdefault("attributes", {})
new_task["attributes"] = primary_dep.attributes.copy()
new_task["attributes"].update(attrs)
if fetches:
task_fetches = new_task.setdefault("fetches", {})
for dep_task in get_dependencies(config, new_task):
# Nothing to do if this kind has no fetches listed
if dep_task.kind not in fetches:
continue
fetches_from_dep = []
for kind, kind_fetches in fetches.items():
if kind != dep_task.kind:
continue
for fetch in kind_fetches:
entry = fetch.copy()
entry["artifact"] = entry["artifact"].format(
**dep_task.attributes
)
if "dest" in entry:
entry["dest"] = entry["dest"].format(
**dep_task.attributes
)
fetches_from_dep.append(entry)
task_fetches[dep_task.label] = fetches_from_dep
yield new_task
|