Source code

Revision control

Copy as Markdown

Other Tools

# 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/.
from typing import Optional
from taskgraph.transforms.base import TransformSequence
from taskgraph.util.schema import Schema
from taskgraph.util.templates import merge
_FX_CMD = "sh browser/installer/linux/script/install-firefox.sh"
class TaskSchema(Schema, forbid_unknown_fields=False, kw_only=True, rename="kebab"):
install_method: Optional[str] = None
pre_install: Optional[str] = None
distro_symbol: Optional[str] = None
channels: Optional[list[str]] = None
locales: Optional[list[str]] = None
platform: Optional[str] = None
transforms = TransformSequence()
transforms.add_validate(TaskSchema)
def _resolve_docker_image(distro_name, distro_cfg, arch_name):
docker_image_cfg = distro_cfg["docker-image"]
if not isinstance(docker_image_cfg, dict):
raise ValueError(
f"Distro '{distro_name}' docker-image must be a dict, got: {docker_image_cfg!r}"
)
if arch_name in docker_image_cfg:
return docker_image_cfg[arch_name]
raise ValueError(
f"Distro '{distro_name}' has no docker-image entry for arch '{arch_name}'. "
f"Add it or restrict architectures."
)
def _build_worker_config(distro_cfg, docker_image):
worker_cfg = {"docker-image": docker_image}
if distro_cfg.get("max-run-time") is not None:
worker_cfg["max-run-time"] = distro_cfg["max-run-time"]
return worker_cfg
@transforms.add
def expand_distros(config, tasks):
kind_config = config.config
distros = kind_config.pop("distros", {})
architectures = kind_config.pop("architectures", {})
channels = kind_config.pop("channels", [])
locales = kind_config.pop("locales", [])
defaults = kind_config.get("task-defaults", {})
explicit_names = set(kind_config.get("tasks", {}).keys())
for task in tasks:
yield task
for distro_name, distro_cfg in distros.items():
allowed_arches = distro_cfg.get("architectures", list(architectures.keys()))
for arch_name, arch_cfg in architectures.items():
if arch_name not in allowed_arches:
continue
docker_image = _resolve_docker_image(distro_name, distro_cfg, arch_name)
worker_cfg = _build_worker_config(distro_cfg, docker_image)
for method in distro_cfg["methods"]:
name = f"{distro_name}-{arch_name}-{method}"
if name in explicit_names:
raise ValueError(
f"Generated task '{name}' conflicts with an explicit task in kind.yml"
)
task = {
"name": name,
"distro-symbol": distro_cfg["symbol"],
"install-method": method,
"worker-type": arch_cfg["worker-type"],
"worker": worker_cfg,
"platform": arch_cfg["platform"],
"channels": distro_cfg.get("channels", channels),
"locales": distro_cfg.get("locales", locales),
}
if distro_cfg.get("pre-install") is not None:
task["pre-install"] = distro_cfg["pre-install"]
if defaults:
task = merge(defaults, task)
yield task
@transforms.add
def set_treeherder_platform(config, tasks):
for task in tasks:
platform = task.pop("platform", None)
if platform:
task.setdefault("treeherder", {})["platform"] = platform
yield task
@transforms.add
def build_run_command(config, tasks):
for task in tasks:
method = task.get("install-method")
pre_install = task.pop("pre-install", None)
if method is None or task.get("run", {}).get("command"):
yield task
continue
# {{...}} produces a literal {matrix[channel]} placeholder substituted by taskgraph.transforms.matrix.
fx_args = f"--channel {{matrix[channel]}} --install-method {method} --lang {{matrix[locale]}}"
cmd = f"{_FX_CMD} {fx_args}"
parts = [pre_install, cmd] if pre_install else [cmd]
task.setdefault("run", {})["command"] = " && ".join(parts)
yield task
@transforms.add
def inject_matrix(config, tasks):
for task in tasks:
channels = task.pop("channels", None)
locales = task.pop("locales", None)
if channels is not None:
# Deliberate runtime check: the schema allows both fields to be
# absent, so this guards against a distro entry that sets channels
# without locales — caught at graph-generation time, not YAML-parse time.
if locales is None:
raise ValueError(
f"Task '{task.get('label', task.get('name', '?'))}' has channels but no locales"
)
task["matrix"] = {
"substitution-fields": ["run.command"],
"channel": channels,
"locale": locales,
}
yield task
@transforms.add
def stash_distro_attributes(config, tasks):
for task in tasks:
method = task.pop("install-method", None)
distro_symbol = task.pop("distro-symbol", None)
if distro_symbol is not None:
attrs = task.setdefault("attributes", {})
attrs["distro-symbol"] = distro_symbol
attrs["install-method"] = method
yield task