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