tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

repackage.py (28201B)


      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 """
      5 Transform the repackage task into an actual task description.
      6 """
      7 
      8 from taskgraph.transforms.base import TransformSequence
      9 from taskgraph.util.copy import deepcopy
     10 from taskgraph.util.dependencies import get_primary_dependency
     11 from taskgraph.util.schema import Schema, optionally_keyed_by, resolve_keyed_by
     12 from taskgraph.util.taskcluster import get_artifact_prefix
     13 from voluptuous import Any, Extra, Optional, Required
     14 
     15 from gecko_taskgraph.transforms.job import job_description_schema
     16 from gecko_taskgraph.util.attributes import copy_attributes_from_dependent_job
     17 from gecko_taskgraph.util.platforms import architecture, archive_format
     18 from gecko_taskgraph.util.workertypes import worker_type_implementation
     19 
     20 packaging_description_schema = Schema({
     21    # unique label to describe this repackaging task
     22    Optional("label"): str,
     23    Optional("worker-type"): str,
     24    Optional("worker"): object,
     25    Optional("attributes"): job_description_schema["attributes"],
     26    Optional("dependencies"): job_description_schema["dependencies"],
     27    # treeherder is allowed here to override any defaults we use for repackaging.  See
     28    # taskcluster/gecko_taskgraph/transforms/task.py for the schema details, and the
     29    # below transforms for defaults of various values.
     30    Optional("treeherder"): job_description_schema["treeherder"],
     31    # If a l10n task, the corresponding locale
     32    Optional("locale"): str,
     33    # Routes specific to this task, if defined
     34    Optional("routes"): [str],
     35    # passed through directly to the job description
     36    Optional("extra"): job_description_schema["extra"],
     37    # passed through to job description
     38    Optional("fetches"): job_description_schema["fetches"],
     39    Optional("run-on-projects"): job_description_schema["run-on-projects"],
     40    Optional("run-on-repo-type"): job_description_schema["run-on-repo-type"],
     41    # Shipping product and phase
     42    Optional("shipping-product"): job_description_schema["shipping-product"],
     43    Optional("shipping-phase"): job_description_schema["shipping-phase"],
     44    Required("package-formats"): optionally_keyed_by(
     45        "build-platform", "release-type", "build-type", [str]
     46    ),
     47    Optional("msix"): {
     48        Optional("channel"): optionally_keyed_by(
     49            "package-format",
     50            "level",
     51            "build-platform",
     52            "release-type",
     53            "shipping-product",
     54            str,
     55        ),
     56        Optional("identity-name"): optionally_keyed_by(
     57            "package-format",
     58            "level",
     59            "build-platform",
     60            "release-type",
     61            "shipping-product",
     62            str,
     63        ),
     64        Optional("publisher"): optionally_keyed_by(
     65            "package-format",
     66            "level",
     67            "build-platform",
     68            "release-type",
     69            "shipping-product",
     70            str,
     71        ),
     72        Optional("publisher-display-name"): optionally_keyed_by(
     73            "package-format",
     74            "level",
     75            "build-platform",
     76            "release-type",
     77            "shipping-product",
     78            str,
     79        ),
     80        Optional("vendor"): str,
     81    },
     82    Optional("flatpak"): {
     83        Required("name"): optionally_keyed_by(
     84            "level",
     85            "build-platform",
     86            "release-type",
     87            "shipping-product",
     88            str,
     89        ),
     90        Required("branch"): optionally_keyed_by(
     91            "level",
     92            "build-platform",
     93            "release-type",
     94            "shipping-product",
     95            str,
     96        ),
     97    },
     98    # All l10n jobs use mozharness
     99    Required("mozharness"): {
    100        Extra: object,
    101        # Config files passed to the mozharness script
    102        Required("config"): optionally_keyed_by("build-platform", [str]),
    103        # Additional paths to look for mozharness configs in. These should be
    104        # relative to the base of the source checkout
    105        Optional("config-paths"): [str],
    106        # if true, perform a checkout of a comm-central based branch inside the
    107        # gecko checkout
    108        Optional("comm-checkout"): bool,
    109        Optional("run-as-root"): bool,
    110        Optional("use-caches"): Any(bool, [str]),
    111    },
    112    Optional("task-from"): job_description_schema["task-from"],
    113 })
    114 
    115 # The configuration passed to the mozharness repackage script. This defines the
    116 # arguments passed to `mach repackage`
    117 # - `args` is interpolated by mozharness (`{package-name}`, `{installer-tag}`,
    118 #   `{stub-installer-tag}`, `{sfx-stub}`, `{wsx-stub}`, `{fetch-dir}`), with values
    119 #    from mozharness.
    120 # - `inputs` are passed as long-options, with the filename prefixed by
    121 #   `MOZ_FETCH_DIR`. The filename is interpolated by taskgraph
    122 #   (`{archive_format}`).
    123 # - `output` is passed to `--output`, with the filename prefixed by the output
    124 #   directory.
    125 PACKAGE_FORMATS = {
    126    "mar": {
    127        "args": [
    128            "mar",
    129            "--arch",
    130            "{architecture}",
    131            "--mar-channel-id",
    132            "{mar-channel-id}",
    133        ],
    134        "inputs": {
    135            "input": "target{archive_format}",
    136            "mar": "mar-tools/mar",
    137        },
    138        "output": "target.complete.mar",
    139    },
    140    "msi": {
    141        "args": [
    142            "msi",
    143            "--wsx",
    144            "{wsx-stub}",
    145            "--version",
    146            "{version_display}",
    147            "--locale",
    148            "{_locale}",
    149            "--arch",
    150            "{architecture}",
    151            "--candle",
    152            "{fetch-dir}/candle.exe",
    153            "--light",
    154            "{fetch-dir}/light.exe",
    155        ],
    156        "inputs": {
    157            "setupexe": "target.installer.exe",
    158        },
    159        "output": "target.installer.msi",
    160    },
    161    "msix": {
    162        "args": [
    163            "msix",
    164            "--channel",
    165            "{msix-channel}",
    166            "--publisher",
    167            "{msix-publisher}",
    168            "--publisher-display-name",
    169            "{msix-publisher-display-name}",
    170            "--identity-name",
    171            "{msix-identity-name}",
    172            "--vendor",
    173            "{msix-vendor}",
    174            "--arch",
    175            "{architecture}",
    176            # For langpacks.  Ignored if directory does not exist.
    177            "--distribution-dir",
    178            "{fetch-dir}/distribution",
    179            "--verbose",
    180            "--makeappx",
    181            "{fetch-dir}/msix-packaging/makemsix",
    182        ],
    183        "inputs": {
    184            "input": "target{archive_format}",
    185        },
    186        "output": "target.installer.msix",
    187    },
    188    "msix-store": {
    189        "args": [
    190            "msix",
    191            "--channel",
    192            "{msix-channel}",
    193            "--publisher",
    194            "{msix-publisher}",
    195            "--publisher-display-name",
    196            "{msix-publisher-display-name}",
    197            "--identity-name",
    198            "{msix-identity-name}",
    199            "--vendor",
    200            "{msix-vendor}",
    201            "--arch",
    202            "{architecture}",
    203            # For langpacks.  Ignored if directory does not exist.
    204            "--distribution-dir",
    205            "{fetch-dir}/distribution",
    206            "--verbose",
    207            "--makeappx",
    208            "{fetch-dir}/msix-packaging/makemsix",
    209        ],
    210        "inputs": {
    211            "input": "target{archive_format}",
    212        },
    213        "output": "target.store.msix",
    214    },
    215    "dmg": {
    216        "args": [
    217            "dmg",
    218            "--compression",
    219            "lzma",
    220        ],
    221        "inputs": {
    222            "input": "target{archive_format}",
    223        },
    224        "output": "target.dmg",
    225    },
    226    "dmg-attrib": {
    227        "args": [
    228            "dmg",
    229            "--compression",
    230            "lzma",
    231            "--attribution_sentinel",
    232            "__MOZCUSTOM__",
    233        ],
    234        "inputs": {
    235            "input": "target{archive_format}",
    236        },
    237        "output": "target.dmg",
    238    },
    239    "pkg": {
    240        "args": ["pkg"],
    241        "inputs": {
    242            "input": "target{archive_format}",
    243        },
    244        "output": "target.pkg",
    245    },
    246    "installer": {
    247        "args": [
    248            "installer",
    249            "--package-name",
    250            "{package-name}",
    251            "--tag",
    252            "{installer-tag}",
    253            "--sfx-stub",
    254            "{sfx-stub}",
    255        ],
    256        "inputs": {
    257            "package": "target{archive_format}",
    258            "setupexe": "setup.exe",
    259        },
    260        "output": "target.installer.exe",
    261    },
    262    "installer-stub": {
    263        "args": [
    264            "installer",
    265            "--tag",
    266            "{stub-installer-tag}",
    267            "--sfx-stub",
    268            "{sfx-stub}",
    269        ],
    270        "inputs": {
    271            "setupexe": "setup-stub.exe",
    272        },
    273        "output": "target.stub-installer.exe",
    274    },
    275    "deb": {
    276        "args": [
    277            "deb",
    278            "--arch",
    279            "{architecture}",
    280            "--templates",
    281            "{deb-templates}",
    282            "--version",
    283            "{version_display}",
    284            "--build-number",
    285            "{build_number}",
    286            "--product",
    287            "{shipping_product}",
    288            "--release-type",
    289            "{release_type}",
    290        ],
    291        "inputs": {
    292            "input": "target{archive_format}",
    293        },
    294        "output": "target.deb",
    295    },
    296    "deb-l10n": {
    297        "args": [
    298            "deb-l10n",
    299            "--version",
    300            "{version_display}",
    301            "--build-number",
    302            "{build_number}",
    303            "--templates",
    304            "{deb-l10n-templates}",
    305            "--product",
    306            "{shipping_product}",
    307            "--extensions-dir",
    308            "{extensions-dir}",
    309        ],
    310        "inputs": {
    311            "input-xpi-file": "target.langpack.xpi",
    312            "input-tar-file": "target{archive_format}",
    313        },
    314        "output": "target.langpack.deb",
    315    },
    316    "rpm": {
    317        "args": [
    318            "rpm",
    319            "--arch",
    320            "{architecture}",
    321            "--templates",
    322            "{rpm-templates}",
    323            "--version",
    324            "{version_display}",
    325            "--build-number",
    326            "{build_number}",
    327            "--product",
    328            "{shipping_product}",
    329            "--release-type",
    330            "{release_type}",
    331            "--input-xpi-dir",
    332            "{fetch-dir}/extensions",
    333        ],
    334        "inputs": {
    335            "input": "target{archive_format}",
    336        },
    337        "output": "target.rpm",
    338    },
    339    "flatpak": {
    340        "args": [
    341            "flatpak",
    342            "--name",
    343            "{flatpak-name}",
    344            "--arch",
    345            "{architecture}",
    346            "--version",
    347            "{version_display}",
    348            "--product",
    349            "{package-name}",
    350            "--release-type",
    351            "{release_type}",
    352            "--flatpak-branch",
    353            "{flatpak-branch}",
    354            "--template-dir",
    355            "{flatpak-templates}",
    356            "--langpack-pattern",
    357            "{fetch-dir}/extensions/*/target.langpack.xpi",
    358        ],
    359        "inputs": {
    360            "input": "target{archive_format}",
    361        },
    362        "output": "target.flatpak.tar.xz",
    363    },
    364 }
    365 MOZHARNESS_EXPANSIONS = [
    366    "package-name",
    367    "installer-tag",
    368    "fetch-dir",
    369    "stub-installer-tag",
    370    "deb-templates",
    371    "rpm-templates",
    372    "deb-l10n-templates",
    373    "sfx-stub",
    374    "wsx-stub",
    375    "flatpak-templates",
    376    "extensions-dir",
    377 ]
    378 
    379 transforms = TransformSequence()
    380 
    381 
    382 @transforms.add
    383 def remove_name(config, jobs):
    384    for job in jobs:
    385        if "name" in job:
    386            del job["name"]
    387        yield job
    388 
    389 
    390 transforms.add_validate(packaging_description_schema)
    391 
    392 
    393 @transforms.add
    394 def copy_in_useful_magic(config, jobs):
    395    """Copy attributes from upstream task to be used for keyed configuration."""
    396    for job in jobs:
    397        dep = get_primary_dependency(config, job)
    398        assert dep
    399 
    400        job["build-platform"] = dep.attributes.get("build_platform")
    401        job["shipping-product"] = dep.attributes.get("shipping_product")
    402        job["build-type"] = dep.attributes.get("build_type")
    403        yield job
    404 
    405 
    406 @transforms.add
    407 def handle_keyed_by(config, jobs):
    408    """Resolve fields that can be keyed by platform, etc, but not `msix.*` fields
    409    that can be keyed by `package-format`.  Such fields are handled specially below.
    410    """
    411    fields = [
    412        "mozharness.config",
    413        "package-formats",
    414        "worker.max-run-time",
    415        "flatpak.name",
    416        "flatpak.branch",
    417    ]
    418    for job in jobs:
    419        job = deepcopy(job)  # don't overwrite dict values here
    420        for field in fields:
    421            resolve_keyed_by(
    422                item=job,
    423                field=field,
    424                item_name="?",
    425                **{
    426                    "release-type": config.params["release_type"],
    427                    "level": config.params["level"],
    428                },
    429            )
    430        yield job
    431 
    432 
    433 @transforms.add
    434 def make_repackage_description(config, jobs):
    435    for job in jobs:
    436        dep_job = get_primary_dependency(config, job)
    437        assert dep_job
    438 
    439        label = job.get("label", dep_job.label.replace("signing-", "repackage-"))
    440        job["label"] = label
    441 
    442        yield job
    443 
    444 
    445 @transforms.add
    446 def make_job_description(config, jobs):
    447    for job in jobs:
    448        dep_job = get_primary_dependency(config, job)
    449        assert dep_job
    450 
    451        dependencies = {dep_job.kind: dep_job.label}
    452 
    453        attributes = copy_attributes_from_dependent_job(dep_job)
    454        attributes["repackage_type"] = "repackage"
    455 
    456        locale = attributes.get("locale", job.get("locale"))
    457        if locale:
    458            attributes["locale"] = locale
    459 
    460        description = (
    461            "Repackaging for locale '{locale}' for build '"
    462            "{build_platform}/{build_type}'".format(
    463                locale=attributes.get("locale", "en-US"),
    464                build_platform=attributes.get("build_platform"),
    465                build_type=attributes.get("build_type"),
    466            )
    467        )
    468 
    469        treeherder = job.get("treeherder", {})
    470        treeherder.setdefault("symbol", "Rpk")
    471        dep_th_platform = dep_job.task.get("extra", {}).get("treeherder-platform")
    472        treeherder.setdefault("platform", dep_th_platform)
    473        treeherder.setdefault("tier", 1)
    474        treeherder.setdefault("kind", "build")
    475 
    476        # Search dependencies before adding langpack dependencies.
    477        signing_task = None
    478        repackage_signing_task = None
    479        for dependency in dependencies.keys():
    480            if "repackage-signing" in dependency:
    481                repackage_signing_task = dependency
    482            elif "signing" in dependency or "notarization" in dependency:
    483                signing_task = dependency
    484            elif "shippable-l10n" in dependency:
    485                # Thunderbird does not sign langpacks, so we find them in the langpack build task
    486                signing_task = dependency
    487 
    488        if config.kind == "repackage-msi":
    489            treeherder["symbol"] = "MSI({})".format(locale or "N")
    490 
    491        elif config.kind == "repackage-msix":
    492            assert not locale
    493 
    494            # Like "MSIXs(Bs)".
    495            treeherder["symbol"] = "MSIX({})".format(
    496                dep_job.task.get("extra", {}).get("treeherder", {}).get("symbol", "B")
    497            )
    498 
    499        elif config.kind == "repackage-shippable-l10n-msix":
    500            assert not locale
    501 
    502            if attributes.get("l10n_chunk") or attributes.get("chunk_locales"):
    503                # We don't want to produce MSIXes for single-locale repack builds.
    504                continue
    505 
    506            description = (
    507                "Repackaging with multiple locales for build '"
    508                "{build_platform}/{build_type}'".format(
    509                    build_platform=attributes.get("build_platform"),
    510                    build_type=attributes.get("build_type"),
    511                )
    512            )
    513 
    514            # Like "MSIXs(Bs-multi)".
    515            treeherder["symbol"] = "MSIX({}-multi)".format(
    516                dep_job.task.get("extra", {}).get("treeherder", {}).get("symbol", "B")
    517            )
    518 
    519            fetches = job.setdefault("fetches", {})
    520 
    521            # The keys are unique, like `shippable-l10n-signing-linux64-shippable-1/opt`, so we
    522            # can't ask for the tasks directly, we must filter for them.
    523            for t in config.kind_dependencies_tasks.values():
    524                if t.kind != "shippable-l10n-signing":
    525                    continue
    526                if t.attributes["build_platform"] != "linux64-shippable":
    527                    continue
    528                if t.attributes["build_type"] != "opt":
    529                    continue
    530 
    531                dependencies.update({t.label: t.label})
    532 
    533                fetches.update({
    534                    t.label: [
    535                        {
    536                            "artifact": f"{loc}/target.langpack.xpi",
    537                            "extract": False,
    538                            # Otherwise we can't disambiguate locales!
    539                            "dest": f"distribution/extensions/{loc}",
    540                        }
    541                        for loc in t.attributes["chunk_locales"]
    542                    ]
    543                })
    544 
    545        elif config.kind in ("repackage-deb", "repackage-rpm"):
    546            attributes["repackage_type"] = config.kind
    547            description = (
    548                "Repackaging the '{build_platform}/{build_type}' "
    549                "{version} build into a '.{package}' package"
    550            ).format(
    551                build_platform=attributes.get("build_platform"),
    552                build_type=attributes.get("build_type"),
    553                version=config.params["version"],
    554                package=config.kind.split("-")[1],
    555            )
    556 
    557        if config.kind in ("repackage-flatpak", "repackage-rpm"):
    558            assert not locale
    559 
    560            if attributes.get("l10n_chunk") or attributes.get("chunk_locales"):
    561                # We don't want to produce flatpaks for single-locale repack builds.
    562                continue
    563 
    564            fetches = job.setdefault("fetches", {})
    565            # The keys are unique, like `shippable-l10n-signing-linux64-shippable-1/opt`, so we
    566            # can't ask for the tasks directly, we must filter for them.
    567            for t in config.kind_dependencies_tasks.values():
    568                # Filter out tasks that are either not the wrong kind, not the
    569                # right product or not the right platform to keep one langpack
    570                # per locale
    571                if attributes.get("shippable"):
    572                    if t.kind != "shippable-l10n-signing":
    573                        continue
    574                    if t.attributes["shipping_product"] != job["shipping-product"]:
    575                        continue
    576                    if t.attributes["build_platform"] not in (
    577                        "linux64-shippable",
    578                        "linux64-devedition",
    579                    ):
    580                        continue
    581                elif t.kind != "l10n" or t.attributes["build_platform"] != "linux64":
    582                    continue
    583                if t.attributes["build_type"] != "opt":
    584                    continue
    585 
    586                locales = t.attributes.get(
    587                    "chunk_locales", t.attributes.get("all_locales")
    588                )
    589 
    590                dependencies.update({t.label: t.label})
    591 
    592                fetches.update({
    593                    t.label: [
    594                        {
    595                            "artifact": f"{loc}/target.langpack.xpi",
    596                            "extract": False,
    597                            # Otherwise we can't disambiguate locales!
    598                            "dest": f"extensions/{loc}",
    599                        }
    600                        for loc in locales
    601                    ]
    602                })
    603 
    604        _fetch_subst_locale = "en-US"
    605        if locale:
    606            _fetch_subst_locale = locale
    607 
    608        worker_type = job["worker-type"]
    609        build_platform = attributes["build_platform"]
    610 
    611        use_stub = attributes.get("stub-installer")
    612 
    613        repackage_config = []
    614        package_formats = job.get("package-formats")
    615        if use_stub and not repackage_signing_task and "msix" not in package_formats:
    616            # if repackage_signing_task doesn't exists, generate the stub installer
    617            package_formats += ["installer-stub"]
    618        for format in package_formats:
    619            command = deepcopy(PACKAGE_FORMATS[format])
    620            substs = {
    621                "archive_format": archive_format(build_platform),
    622                "_locale": _fetch_subst_locale,
    623                "architecture": architecture(build_platform),
    624                "version_display": config.params["version"],
    625                "mar-channel-id": attributes["mar-channel-id"],
    626                "build_number": config.params["build_number"],
    627                "shipping_product": job["shipping-product"],
    628                "release_type": config.params["release_type"],
    629                "flatpak-name": job.get("flatpak", {}).get("name"),
    630                "flatpak-branch": job.get("flatpak", {}).get("branch"),
    631            }
    632            # Allow us to replace `args` as well, but specifying things expanded in mozharness
    633            # without breaking .format and without allowing unknown through.
    634            substs.update({name: f"{{{name}}}" for name in MOZHARNESS_EXPANSIONS})
    635 
    636            # We need to resolve `msix.*` values keyed by `package-format` for each format, not
    637            # just once, so we update a temporary copy just for extracting these values.
    638            temp_job = deepcopy(job)
    639            for msix_key in (
    640                "channel",
    641                "identity-name",
    642                "publisher",
    643                "publisher-display-name",
    644                "vendor",
    645            ):
    646                resolve_keyed_by(
    647                    item=temp_job,
    648                    field=f"msix.{msix_key}",
    649                    item_name="?",
    650                    **{
    651                        "package-format": format,
    652                        "release-type": config.params["release_type"],
    653                        "level": config.params["level"],
    654                    },
    655                )
    656 
    657                # Turn `msix.channel` into `msix-channel`, etc.
    658                value = temp_job.get("msix", {}).get(msix_key)
    659                if value:
    660                    substs.update(
    661                        {f"msix-{msix_key}": value},
    662                    )
    663 
    664            command["inputs"] = {
    665                name: filename.format(**substs)
    666                for name, filename in command["inputs"].items()
    667            }
    668            command["args"] = [arg.format(**substs) for arg in command["args"]]
    669            if "installer" in format and "aarch64" not in build_platform:
    670                command["args"].append("--use-upx")
    671 
    672            repackage_config.append(command)
    673 
    674        run = job.get("mozharness", {})
    675        run.update({
    676            "using": "mozharness",
    677            "script": "mozharness/scripts/repackage.py",
    678            "job-script": "taskcluster/scripts/builder/repackage.sh",
    679            "actions": ["setup", "repackage"],
    680            "extra-config": {
    681                "repackage_config": repackage_config,
    682            },
    683            "run-as-root": run.get("run-as-root", False),
    684        })
    685 
    686        worker = job.get("worker", {})
    687        worker.update({
    688            "chain-of-trust": True,
    689            # Don't add generic artifact directory.
    690            "skip-artifacts": True,
    691        })
    692        worker.setdefault("max-run-time", 3600)
    693 
    694        if locale:
    695            # Make sure we specify the locale-specific upload dir
    696            worker.setdefault("env", {})["LOCALE"] = locale
    697 
    698        worker["artifacts"] = _generate_task_output_files(
    699            dep_job,
    700            worker_type_implementation(config.graph_config, config.params, worker_type),
    701            repackage_config=repackage_config,
    702            locale=locale,
    703        )
    704        attributes["release_artifacts"] = [
    705            artifact["name"] for artifact in worker["artifacts"]
    706        ]
    707 
    708        task = {
    709            "label": job["label"],
    710            "description": description,
    711            "worker-type": worker_type,
    712            "dependencies": dependencies,
    713            "if-dependencies": [dep_job.kind],
    714            "attributes": attributes,
    715            "run-on-projects": job.get(
    716                "run-on-projects", dep_job.attributes.get("run_on_projects")
    717            ),
    718            "run-on-repo-type": job.get("run-on-repo-type", ["git", "hg"]),
    719            "optimization": dep_job.optimization,
    720            "treeherder": treeherder,
    721            "routes": job.get("routes", []),
    722            "extra": job.get("extra", {}),
    723            "worker": worker,
    724            "run": run,
    725            "fetches": _generate_download_config(
    726                config,
    727                dep_job,
    728                build_platform,
    729                signing_task,
    730                repackage_signing_task,
    731                locale=locale,
    732                existing_fetch=job.get("fetches"),
    733            ),
    734        }
    735 
    736        if build_platform.startswith("macosx"):
    737            task.setdefault("fetches", {}).setdefault("toolchain", []).extend([
    738                "linux64-libdmg",
    739                "linux64-hfsplus",
    740                "linux64-node",
    741                "linux64-xar",
    742                "linux64-mkbom",
    743            ])
    744 
    745        if "shipping-phase" in job:
    746            task["shipping-phase"] = job["shipping-phase"]
    747 
    748        if "shipping-product" in job and job["shipping-product"] is not None:
    749            task["shipping-product"] = job["shipping-product"]
    750 
    751        yield task
    752 
    753 
    754 def _generate_download_config(
    755    config,
    756    task,
    757    build_platform,
    758    signing_task,
    759    repackage_signing_task,
    760    locale=None,
    761    existing_fetch=None,
    762 ):
    763    locale_path = f"{locale}/" if locale else ""
    764    fetch = {}
    765    if existing_fetch:
    766        fetch.update(existing_fetch)
    767 
    768    if repackage_signing_task and build_platform.startswith("win"):
    769        fetch.update({
    770            repackage_signing_task: [f"{locale_path}target.installer.exe"],
    771        })
    772    elif build_platform.startswith("linux") or build_platform.startswith("macosx"):
    773        signing_fetch = [
    774            {
    775                "artifact": f"{locale_path}target{archive_format(build_platform)}",
    776                "extract": False,
    777            },
    778        ]
    779        if config.kind == "repackage-deb-l10n":
    780            signing_fetch.append({
    781                "artifact": f"{locale_path}target.langpack.xpi",
    782                "extract": False,
    783            })
    784        fetch.update({signing_task: signing_fetch})
    785    elif build_platform.startswith("win"):
    786        fetch.update({
    787            signing_task: [
    788                {
    789                    "artifact": f"{locale_path}target.zip",
    790                    "extract": False,
    791                },
    792                f"{locale_path}setup.exe",
    793            ],
    794        })
    795 
    796        use_stub = task.attributes.get("stub-installer")
    797        if use_stub:
    798            fetch[signing_task].append(f"{locale_path}setup-stub.exe")
    799 
    800    if fetch:
    801        return fetch
    802 
    803    raise NotImplementedError(f'Unsupported build_platform: "{build_platform}"')
    804 
    805 
    806 def _generate_task_output_files(
    807    task, worker_implementation, repackage_config, locale=None
    808 ):
    809    locale_output_path = f"{locale}/" if locale else ""
    810    artifact_prefix = get_artifact_prefix(task)
    811 
    812    if worker_implementation == ("docker-worker", "linux"):
    813        local_prefix = "/builds/worker/workspace/"
    814    elif worker_implementation == ("generic-worker", "windows"):
    815        local_prefix = "workspace/"
    816    else:
    817        raise NotImplementedError(
    818            f'Unsupported worker implementation: "{worker_implementation}"'
    819        )
    820 
    821    output_files = []
    822    for config in repackage_config:
    823        output_files.append({
    824            "type": "file",
    825            "path": "{}outputs/{}{}".format(
    826                local_prefix, locale_output_path, config["output"]
    827            ),
    828            "name": "{}/{}{}".format(
    829                artifact_prefix, locale_output_path, config["output"]
    830            ),
    831        })
    832    return output_files