no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / taskcluster / gecko_taskgraph / transforms / repackage.py
blobea684d71c59256f59ed47136081de8b14675d1e6
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 """
8 from taskgraph.transforms.base import TransformSequence
9 from taskgraph.util.dependencies import get_primary_dependency
10 from taskgraph.util.schema import Schema, optionally_keyed_by, resolve_keyed_by
11 from taskgraph.util.taskcluster import get_artifact_prefix
12 from voluptuous import Extra, Optional, Required
14 from gecko_taskgraph.transforms.job import job_description_schema
15 from gecko_taskgraph.util.attributes import copy_attributes_from_dependent_job
16 from gecko_taskgraph.util.copy_task import copy_task
17 from gecko_taskgraph.util.platforms import architecture, archive_format
18 from gecko_taskgraph.util.workertypes import worker_type_implementation
20 packaging_description_schema = Schema(
22 # unique label to describe this repackaging task
23 Optional("label"): str,
24 Optional("worker-type"): str,
25 Optional("worker"): object,
26 Optional("attributes"): job_description_schema["attributes"],
27 Optional("dependencies"): job_description_schema["dependencies"],
28 # treeherder is allowed here to override any defaults we use for repackaging. See
29 # taskcluster/gecko_taskgraph/transforms/task.py for the schema details, and the
30 # below transforms for defaults of various values.
31 Optional("treeherder"): job_description_schema["treeherder"],
32 # If a l10n task, the corresponding locale
33 Optional("locale"): str,
34 # Routes specific to this task, if defined
35 Optional("routes"): [str],
36 # passed through directly to the job description
37 Optional("extra"): job_description_schema["extra"],
38 # passed through to job description
39 Optional("fetches"): job_description_schema["fetches"],
40 Optional("run-on-projects"): job_description_schema["run-on-projects"],
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]
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,
56 Optional("identity-name"): optionally_keyed_by(
57 "package-format",
58 "level",
59 "build-platform",
60 "release-type",
61 "shipping-product",
62 str,
64 Optional("publisher"): optionally_keyed_by(
65 "package-format",
66 "level",
67 "build-platform",
68 "release-type",
69 "shipping-product",
70 str,
72 Optional("publisher-display-name"): optionally_keyed_by(
73 "package-format",
74 "level",
75 "build-platform",
76 "release-type",
77 "shipping-product",
78 str,
80 Optional("vendor"): str,
82 # All l10n jobs use mozharness
83 Required("mozharness"): {
84 Extra: object,
85 # Config files passed to the mozharness script
86 Required("config"): optionally_keyed_by("build-platform", [str]),
87 # Additional paths to look for mozharness configs in. These should be
88 # relative to the base of the source checkout
89 Optional("config-paths"): [str],
90 # if true, perform a checkout of a comm-central based branch inside the
91 # gecko checkout
92 Optional("comm-checkout"): bool,
93 Optional("run-as-root"): bool,
94 Optional("use-caches"): bool,
96 Optional("job-from"): job_description_schema["job-from"],
100 # The configuration passed to the mozharness repackage script. This defines the
101 # arguments passed to `mach repackage`
102 # - `args` is interpolated by mozharness (`{package-name}`, `{installer-tag}`,
103 # `{stub-installer-tag}`, `{sfx-stub}`, `{wsx-stub}`, `{fetch-dir}`), with values
104 # from mozharness.
105 # - `inputs` are passed as long-options, with the filename prefixed by
106 # `MOZ_FETCH_DIR`. The filename is interpolated by taskgraph
107 # (`{archive_format}`).
108 # - `output` is passed to `--output`, with the filename prefixed by the output
109 # directory.
110 PACKAGE_FORMATS = {
111 "mar": {
112 "args": [
113 "mar",
114 "--arch",
115 "{architecture}",
116 "--mar-channel-id",
117 "{mar-channel-id}",
119 "inputs": {
120 "input": "target{archive_format}",
121 "mar": "mar-tools/mar",
123 "output": "target.complete.mar",
125 "msi": {
126 "args": [
127 "msi",
128 "--wsx",
129 "{wsx-stub}",
130 "--version",
131 "{version_display}",
132 "--locale",
133 "{_locale}",
134 "--arch",
135 "{architecture}",
136 "--candle",
137 "{fetch-dir}/candle.exe",
138 "--light",
139 "{fetch-dir}/light.exe",
141 "inputs": {
142 "setupexe": "target.installer.exe",
144 "output": "target.installer.msi",
146 "msix": {
147 "args": [
148 "msix",
149 "--channel",
150 "{msix-channel}",
151 "--publisher",
152 "{msix-publisher}",
153 "--publisher-display-name",
154 "{msix-publisher-display-name}",
155 "--identity-name",
156 "{msix-identity-name}",
157 "--vendor",
158 "{msix-vendor}",
159 "--arch",
160 "{architecture}",
161 # For langpacks. Ignored if directory does not exist.
162 "--distribution-dir",
163 "{fetch-dir}/distribution",
164 "--verbose",
165 "--makeappx",
166 "{fetch-dir}/msix-packaging/makemsix",
168 "inputs": {
169 "input": "target{archive_format}",
171 "output": "target.installer.msix",
173 "msix-store": {
174 "args": [
175 "msix",
176 "--channel",
177 "{msix-channel}",
178 "--publisher",
179 "{msix-publisher}",
180 "--publisher-display-name",
181 "{msix-publisher-display-name}",
182 "--identity-name",
183 "{msix-identity-name}",
184 "--vendor",
185 "{msix-vendor}",
186 "--arch",
187 "{architecture}",
188 # For langpacks. Ignored if directory does not exist.
189 "--distribution-dir",
190 "{fetch-dir}/distribution",
191 "--verbose",
192 "--makeappx",
193 "{fetch-dir}/msix-packaging/makemsix",
195 "inputs": {
196 "input": "target{archive_format}",
198 "output": "target.store.msix",
200 "dmg": {
201 "args": ["dmg"],
202 "inputs": {
203 "input": "target{archive_format}",
205 "output": "target.dmg",
207 "dmg-attrib": {
208 "args": [
209 "dmg",
210 "--attribution_sentinel",
211 "__MOZCUSTOM__",
213 "inputs": {
214 "input": "target{archive_format}",
216 "output": "target.dmg",
218 "pkg": {
219 "args": ["pkg"],
220 "inputs": {
221 "input": "target{archive_format}",
223 "output": "target.pkg",
225 "installer": {
226 "args": [
227 "installer",
228 "--package-name",
229 "{package-name}",
230 "--tag",
231 "{installer-tag}",
232 "--sfx-stub",
233 "{sfx-stub}",
235 "inputs": {
236 "package": "target{archive_format}",
237 "setupexe": "setup.exe",
239 "output": "target.installer.exe",
241 "installer-stub": {
242 "args": [
243 "installer",
244 "--tag",
245 "{stub-installer-tag}",
246 "--sfx-stub",
247 "{sfx-stub}",
249 "inputs": {
250 "setupexe": "setup-stub.exe",
252 "output": "target.stub-installer.exe",
254 "deb": {
255 "args": [
256 "deb",
257 "--arch",
258 "{architecture}",
259 "--templates",
260 "browser/installer/linux/app/debian",
261 "--version",
262 "{version_display}",
263 "--build-number",
264 "{build_number}",
265 "--release-product",
266 "{release_product}",
267 "--release-type",
268 "{release_type}",
270 "inputs": {
271 "input": "target{archive_format}",
273 "output": "target.deb",
275 "deb-l10n": {
276 "args": [
277 "deb-l10n",
278 "--version",
279 "{version_display}",
280 "--build-number",
281 "{build_number}",
282 "--templates",
283 "browser/installer/linux/langpack/debian",
284 "--release-product",
285 "{release_product}",
287 "inputs": {
288 "input-xpi-file": "target.langpack.xpi",
289 "input-tar-file": "target{archive_format}",
291 "output": "target.langpack.deb",
294 MOZHARNESS_EXPANSIONS = [
295 "package-name",
296 "installer-tag",
297 "fetch-dir",
298 "stub-installer-tag",
299 "sfx-stub",
300 "wsx-stub",
303 transforms = TransformSequence()
306 @transforms.add
307 def remove_name(config, jobs):
308 for job in jobs:
309 if "name" in job:
310 del job["name"]
311 yield job
314 transforms.add_validate(packaging_description_schema)
317 @transforms.add
318 def copy_in_useful_magic(config, jobs):
319 """Copy attributes from upstream task to be used for keyed configuration."""
320 for job in jobs:
321 dep = get_primary_dependency(config, job)
322 assert dep
324 job["build-platform"] = dep.attributes.get("build_platform")
325 job["shipping-product"] = dep.attributes.get("shipping_product")
326 job["build-type"] = dep.attributes.get("build_type")
327 yield job
330 @transforms.add
331 def handle_keyed_by(config, jobs):
332 """Resolve fields that can be keyed by platform, etc, but not `msix.*` fields
333 that can be keyed by `package-format`. Such fields are handled specially below.
335 fields = [
336 "mozharness.config",
337 "package-formats",
338 "worker.max-run-time",
340 for job in jobs:
341 job = copy_task(job) # don't overwrite dict values here
342 for field in fields:
343 resolve_keyed_by(
344 item=job,
345 field=field,
346 item_name="?",
348 "release-type": config.params["release_type"],
349 "level": config.params["level"],
352 yield job
355 @transforms.add
356 def make_repackage_description(config, jobs):
357 for job in jobs:
358 dep_job = get_primary_dependency(config, job)
359 assert dep_job
361 label = job.get("label", dep_job.label.replace("signing-", "repackage-"))
362 job["label"] = label
364 yield job
367 @transforms.add
368 def make_job_description(config, jobs):
369 for job in jobs:
370 dep_job = get_primary_dependency(config, job)
371 assert dep_job
373 dependencies = {dep_job.kind: dep_job.label}
375 attributes = copy_attributes_from_dependent_job(dep_job)
376 attributes["repackage_type"] = "repackage"
378 locale = attributes.get("locale", job.get("locale"))
379 if locale:
380 attributes["locale"] = locale
382 description = (
383 "Repackaging for locale '{locale}' for build '"
384 "{build_platform}/{build_type}'".format(
385 locale=attributes.get("locale", "en-US"),
386 build_platform=attributes.get("build_platform"),
387 build_type=attributes.get("build_type"),
391 treeherder = job.get("treeherder", {})
392 treeherder.setdefault("symbol", "Rpk")
393 dep_th_platform = dep_job.task.get("extra", {}).get("treeherder-platform")
394 treeherder.setdefault("platform", dep_th_platform)
395 treeherder.setdefault("tier", 1)
396 treeherder.setdefault("kind", "build")
398 # Search dependencies before adding langpack dependencies.
399 signing_task = None
400 repackage_signing_task = None
401 for dependency in dependencies.keys():
402 if "repackage-signing" in dependency:
403 repackage_signing_task = dependency
404 elif "signing" in dependency or "notarization" in dependency:
405 signing_task = dependency
407 if config.kind == "repackage-msi":
408 treeherder["symbol"] = "MSI({})".format(locale or "N")
410 elif config.kind == "repackage-msix":
411 assert not locale
413 # Like "MSIXs(Bs)".
414 treeherder["symbol"] = "MSIX({})".format(
415 dep_job.task.get("extra", {}).get("treeherder", {}).get("symbol", "B")
418 elif config.kind == "repackage-shippable-l10n-msix":
419 assert not locale
421 if attributes.get("l10n_chunk") or attributes.get("chunk_locales"):
422 # We don't want to produce MSIXes for single-locale repack builds.
423 continue
425 description = (
426 "Repackaging with multiple locales for build '"
427 "{build_platform}/{build_type}'".format(
428 build_platform=attributes.get("build_platform"),
429 build_type=attributes.get("build_type"),
433 # Like "MSIXs(Bs-multi)".
434 treeherder["symbol"] = "MSIX({}-multi)".format(
435 dep_job.task.get("extra", {}).get("treeherder", {}).get("symbol", "B")
438 fetches = job.setdefault("fetches", {})
440 # The keys are unique, like `shippable-l10n-signing-linux64-shippable-1/opt`, so we
441 # can't ask for the tasks directly, we must filter for them.
442 for t in config.kind_dependencies_tasks.values():
443 if t.kind != "shippable-l10n-signing":
444 continue
445 if t.attributes["build_platform"] != "linux64-shippable":
446 continue
447 if t.attributes["build_type"] != "opt":
448 continue
450 dependencies.update({t.label: t.label})
452 fetches.update(
454 t.label: [
456 "artifact": f"{loc}/target.langpack.xpi",
457 "extract": False,
458 # Otherwise we can't disambiguate locales!
459 "dest": f"distribution/extensions/{loc}",
461 for loc in t.attributes["chunk_locales"]
466 elif config.kind == "repackage-deb":
467 attributes["repackage_type"] = "repackage-deb"
468 description = (
469 "Repackaging the '{build_platform}/{build_type}' "
470 "{version} build into a '.deb' package"
471 ).format(
472 build_platform=attributes.get("build_platform"),
473 build_type=attributes.get("build_type"),
474 version=config.params["version"],
477 _fetch_subst_locale = "en-US"
478 if locale:
479 _fetch_subst_locale = locale
481 worker_type = job["worker-type"]
482 build_platform = attributes["build_platform"]
484 use_stub = attributes.get("stub-installer")
486 repackage_config = []
487 package_formats = job.get("package-formats")
488 if use_stub and not repackage_signing_task and "msix" not in package_formats:
489 # if repackage_signing_task doesn't exists, generate the stub installer
490 package_formats += ["installer-stub"]
491 for format in package_formats:
492 command = copy_task(PACKAGE_FORMATS[format])
493 substs = {
494 "archive_format": archive_format(build_platform),
495 "_locale": _fetch_subst_locale,
496 "architecture": architecture(build_platform),
497 "version_display": config.params["version"],
498 "mar-channel-id": attributes["mar-channel-id"],
499 "build_number": config.params["build_number"],
500 "release_product": config.params["release_product"],
501 "release_type": config.params["release_type"],
503 # Allow us to replace `args` as well, but specifying things expanded in mozharness
504 # without breaking .format and without allowing unknown through.
505 substs.update({name: f"{{{name}}}" for name in MOZHARNESS_EXPANSIONS})
507 # We need to resolve `msix.*` values keyed by `package-format` for each format, not
508 # just once, so we update a temporary copy just for extracting these values.
509 temp_job = copy_task(job)
510 for msix_key in (
511 "channel",
512 "identity-name",
513 "publisher",
514 "publisher-display-name",
515 "vendor",
517 resolve_keyed_by(
518 item=temp_job,
519 field=f"msix.{msix_key}",
520 item_name="?",
522 "package-format": format,
523 "release-type": config.params["release_type"],
524 "level": config.params["level"],
528 # Turn `msix.channel` into `msix-channel`, etc.
529 value = temp_job.get("msix", {}).get(msix_key)
530 if value:
531 substs.update(
532 {f"msix-{msix_key}": value},
535 command["inputs"] = {
536 name: filename.format(**substs)
537 for name, filename in command["inputs"].items()
539 command["args"] = [arg.format(**substs) for arg in command["args"]]
540 if "installer" in format and "aarch64" not in build_platform:
541 command["args"].append("--use-upx")
543 repackage_config.append(command)
545 run = job.get("mozharness", {})
546 run.update(
548 "using": "mozharness",
549 "script": "mozharness/scripts/repackage.py",
550 "job-script": "taskcluster/scripts/builder/repackage.sh",
551 "actions": ["setup", "repackage"],
552 "extra-config": {
553 "repackage_config": repackage_config,
555 "run-as-root": run.get("run-as-root", False),
556 "use-caches": run.get("use-caches", True),
560 worker = job.get("worker", {})
561 worker.update(
563 "chain-of-trust": True,
564 # Don't add generic artifact directory.
565 "skip-artifacts": True,
568 worker.setdefault("max-run-time", 3600)
570 if locale:
571 # Make sure we specify the locale-specific upload dir
572 worker.setdefault("env", {})["LOCALE"] = locale
574 worker["artifacts"] = _generate_task_output_files(
575 dep_job,
576 worker_type_implementation(config.graph_config, config.params, worker_type),
577 repackage_config=repackage_config,
578 locale=locale,
580 attributes["release_artifacts"] = [
581 artifact["name"] for artifact in worker["artifacts"]
584 task = {
585 "label": job["label"],
586 "description": description,
587 "worker-type": worker_type,
588 "dependencies": dependencies,
589 "if-dependencies": [dep_job.kind],
590 "attributes": attributes,
591 "run-on-projects": job.get(
592 "run-on-projects", dep_job.attributes.get("run_on_projects")
594 "optimization": dep_job.optimization,
595 "treeherder": treeherder,
596 "routes": job.get("routes", []),
597 "extra": job.get("extra", {}),
598 "worker": worker,
599 "run": run,
600 "fetches": _generate_download_config(
601 config,
602 dep_job,
603 build_platform,
604 signing_task,
605 repackage_signing_task,
606 locale=locale,
607 existing_fetch=job.get("fetches"),
611 if build_platform.startswith("macosx"):
612 task.setdefault("fetches", {}).setdefault("toolchain", []).extend(
614 "linux64-libdmg",
615 "linux64-hfsplus",
616 "linux64-node",
617 "linux64-xar",
618 "linux64-mkbom",
622 if "shipping-phase" in job:
623 task["shipping-phase"] = job["shipping-phase"]
625 yield task
628 def _generate_download_config(
629 config,
630 task,
631 build_platform,
632 signing_task,
633 repackage_signing_task,
634 locale=None,
635 existing_fetch=None,
637 locale_path = f"{locale}/" if locale else ""
638 fetch = {}
639 if existing_fetch:
640 fetch.update(existing_fetch)
642 if repackage_signing_task and build_platform.startswith("win"):
643 fetch.update(
645 repackage_signing_task: [f"{locale_path}target.installer.exe"],
648 elif build_platform.startswith("linux") or build_platform.startswith("macosx"):
649 signing_fetch = [
651 "artifact": "{}target{}".format(
652 locale_path, archive_format(build_platform)
654 "extract": False,
657 if config.kind == "repackage-deb-l10n":
658 signing_fetch.append(
660 "artifact": f"{locale_path}target.langpack.xpi",
661 "extract": False,
664 fetch.update({signing_task: signing_fetch})
665 elif build_platform.startswith("win"):
666 fetch.update(
668 signing_task: [
670 "artifact": f"{locale_path}target.zip",
671 "extract": False,
673 f"{locale_path}setup.exe",
678 use_stub = task.attributes.get("stub-installer")
679 if use_stub:
680 fetch[signing_task].append(f"{locale_path}setup-stub.exe")
682 if fetch:
683 return fetch
685 raise NotImplementedError(f'Unsupported build_platform: "{build_platform}"')
688 def _generate_task_output_files(
689 task, worker_implementation, repackage_config, locale=None
691 locale_output_path = f"{locale}/" if locale else ""
692 artifact_prefix = get_artifact_prefix(task)
694 if worker_implementation == ("docker-worker", "linux"):
695 local_prefix = "/builds/worker/workspace/"
696 elif worker_implementation == ("generic-worker", "windows"):
697 local_prefix = "workspace/"
698 else:
699 raise NotImplementedError(
700 f'Unsupported worker implementation: "{worker_implementation}"'
703 output_files = []
704 for config in repackage_config:
705 output_files.append(
707 "type": "file",
708 "path": "{}outputs/{}{}".format(
709 local_prefix, locale_output_path, config["output"]
711 "name": "{}/{}{}".format(
712 artifact_prefix, locale_output_path, config["output"]
716 return output_files