From edb82c2b54e8fa114632ff26c97882ea398184e4 Mon Sep 17 00:00:00 2001 From: Alexandre Lissy Date: Wed, 17 Jun 2026 11:27:36 +0200 Subject: [PATCH 01/14] Bug 2053429 - Add trigger of a "comm" decision task on Firefox Enterprise repo --- .taskcluster.yml | 252 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 251 insertions(+), 1 deletion(-) diff --git a/.taskcluster.yml b/.taskcluster.yml index 3cf0a01522e9b..967710ad31fed 100644 --- a/.taskcluster.yml +++ b/.taskcluster.yml @@ -363,7 +363,7 @@ tasks: eventAction: null - $switch: 'tasks_for[:6] == "github"': - ownTaskId: {$eval: as_slugid("decision_task")} + ownTaskId: {$eval: as_slugid("gecko_decision_task")} eventType: '${tasks_for[7:]}' # strip out 'github-' eventAction: '${event["action"]}' # empty string if 'action' doesn't exist 'tasks_for in ["action", "cron", "pr-action"]': @@ -657,3 +657,253 @@ tasks: then: cron: {$json: {$eval: 'cron'}} - tasks_for: '${tasks_for}' + + # Second task: Comm (Thunderbird) decision task for git events. + - $switch: + 'tasks_for[:6] == "github" || (tasks_for in ["action", "pr-action"] && parameters["repository_type"] == "git") || (tasks_for == "cron" && repository["type"] == "git")': + $let: + $merge: + - trustDomain: enterprise + isPullRequest: false + prNumber: null + eventType: '${tasks_for}' + eventAction: null + - $switch: + 'tasks_for[:6] == "github"': + geckoTaskId: {$eval: as_slugid("gecko_decision_task")} + commTaskId: {$eval: as_slugid("comm_decision_task")} + eventType: '${tasks_for[7:]}' + eventAction: '${event["action"]}' + 'tasks_for in ["action", "cron", "pr-action"]': + geckoTaskId: '${ownTaskId}' + commTaskId: '${ownTaskId}' + ownerEmail: '${tasks_for}@noreply.mozilla.org' + baseRepoUrl: '${repository.url}' + repoUrl: '${repository.url}' + project: '${repository.project}' + ref: + $if: 'push.branch[:11] == "refs/heads/"' + then: ${push.branch} + else: 'refs/heads/${push.branch}' + baseRev: '${push.revision}' + headRev: '${push.revision}' + - $switch: + 'tasks_for == "github-push"': + ownerEmail: '${event.pusher.email}' + baseRepoUrl: '${event.repository.html_url}' + repoUrl: '${event.repository.html_url}' + project: '${event.repository.name}' + ref: '${event.ref}' + baseRev: '${event.before}' + headRev: '${event.after}' + 'tasks_for[:19] == "github-pull-request"': + ownerEmail: '${event.pull_request.user.login}@users.noreply.github.com' + baseRepoUrl: '${event.pull_request.base.repo.html_url}' + repoUrl: '${event.pull_request.head.repo.html_url}' + project: '${event.pull_request.base.repo.name}' + ref: 'refs/heads/${event.pull_request.head.ref}' + baseRef: 'refs/heads/${event.pull_request.base.ref}' + baseRev: '${event.pull_request.base.sha}' + headRev: '${event.pull_request.head.sha}' + prNumber: '${event.pull_request.number}' + isPullRequest: true + 'tasks_for == "pr-action"': + baseRepoUrl: '${repository.base_url}' + eventType: action + in: + $let: + repoType: + $switch: + 'repoUrl == "https://github.com/mozilla/enterprise-firefox"': "enterprise" + 'repoUrl == "https://github.com/mozilla/firefox-dev"': "dev" + 'repoUrl == "https://github.com/mozilla-releng/staging-firefox"': "dev" + 'repoUrl == "https://github.com/mozilla-firefox/firefox"': "prod" + $default: null + shortRef: + $if: 'ref[:11] == "refs/heads/"' + then: {$eval: 'ref[11:]'} + else: ${ref} + in: + $if: > + eventType in ["action", "cron"] + || (eventType == "push" && repoType == "enterprise" && shortRef in ["enterprise-main", "enterprise-beta", "enterprise-release"]) + || (eventType == "push" && project == "enterprise-firefox-try") + || (isPullRequest && eventAction in ["opened", "reopened", "synchronize"]) + then: + $let: + level: + $if: '(tasks_for == "action" || eventType in ["cron", "push"]) && repoType == "enterprise" && project == "enterprise-firefox"' + then: 3 + else: 1 + in: + taskId: {$if: 'eventType != "action"', then: '${commTaskId}'} + taskGroupId: {$if: 'eventType == "action"', then: '${action.taskGroupId}', else: '${commTaskId}'} + schedulerId: '${trustDomain}-level-${level}' + created: {$fromNow: ''} + deadline: {$fromNow: '1 day'} + expires: {$fromNow: '1 year 1 second'} + metadata: + owner: "${ownerEmail}" + source: "${repoUrl}/raw/${headRev}/.taskcluster.yml" + name: "Comm Decision Task (${eventType})" + description: 'The task that creates all Thunderbird tasks in the task graph' + + provisionerId: "${trustDomain}-${level}" + workerType: "decision" + + tags: + createdForUser: "${ownerEmail}" + project: enterprise-thunderbird + trust-domain: ${trustDomain} + worker-implementation: docker-worker + kind: decision-task + + routes: + $flattenDeep: + - checks + - $switch: + isPullRequest: + - "tc-treeherder.v2.enterprise-firefox-pr.${headRev}" + - "index.${trustDomain}.v2.enterprise-firefox-pr.revision.${headRev}.taskgraph.decision" + 'eventType == "push"': + - "tc-treeherder.v2.enterprise-firefox.${headRev}" + - "index.${trustDomain}.v2.enterprise-firefox.branch.${shortRef}.latest.taskgraph.decision" + - "index.${trustDomain}.v2.enterprise-firefox.revision.${headRev}.taskgraph.decision" + 'eventType == "cron"': + - "index.${trustDomain}.v2.enterprise-firefox.branch.${shortRef}.latest.taskgraph.decision-${cron.job_name}" + - "index.${trustDomain}.v2.enterprise-firefox.revision.${headRev}.taskgraph.decision-${cron.job_name}" + - 'index.${trustDomain}.v2.enterprise-firefox.revision.${headRev}.cron.${commTaskId}' + + scopes: + $switch: + isPullRequest: + - 'assume:repo:${baseRepoUrl[8:]}:${eventType}' + 'eventType == "push"': + - 'assume:repo:${repoUrl[8:]}:branch:${shortRef}' + 'eventType == "action"': + - 'assume:repo:${baseRepoUrl[8:]}:${tasks_for}:${action.action_perm}' + 'eventType == "cron"': + - 'assume:repo:${repoUrl[8:]}:cron:${cron.job_name}' + + dependencies: [] + requires: all-completed + + priority: + $switch: + 'eventType == "cron"': "low" + 'eventType == "action"': "lowest" + $default: "very-low" + retries: 5 + + payload: + env: + $merge: + - GECKO_BASE_REPOSITORY: '${baseRepoUrl}' + GECKO_BASE_REV: '${baseRev}' + GECKO_HEAD_REPOSITORY: '${repoUrl}' + GECKO_HEAD_REF: '${ref}' + GECKO_HEAD_REV: '${headRev}' + GECKO_REPOSITORY_TYPE: git + COMM_BASE_REPOSITORY: https://github.com/mozilla/enterprise-thunderbird + COMM_BASE_REF: '${baseRef}' + COMM_HEAD_REPOSITORY: https://github.com/lissyx/enterprise-thunderbird + COMM_HEAD_REF: refs/heads/comm-central_enterprise-main + COMM_REPOSITORY_TYPE: git + REPOSITORIES: {$json: {gecko: "Mozilla Firefox", comm: "Mozilla Thunderbird"}} + TASKCLUSTER_CACHES: /builds/worker/checkouts + TASKCLUSTER_VOLUMES: /builds/worker/artifacts + MOZ_UPLOAD_DIR: /builds/worker/artifacts + MOZ_AUTOMATION: '1' + PYTHONDONTWRITEBYTECODE: '1' + - $switch: + 'isPullRequest': + GECKO_PULL_REQUEST_NUMBER: "${prNumber}" + 'eventType == "action"': + ACTION_TASK_GROUP_ID: '${action.taskGroupId}' + ACTION_TASK_ID: {$json: {$eval: 'taskId'}} + ACTION_INPUT: {$json: {$eval: 'input'}} + ACTION_CALLBACK: '${action.cb_name}' + cache: + "${trustDomain}-level-${level}-enterprise-thunderbird-checkouts-git-shallow-v1": /builds/worker/checkouts + + features: + taskclusterProxy: true + chainOfTrust: true + + image: mozillareleases/taskgraph:run-task-latest + maxRunTime: 1800 + command: + - run-task + - '--gecko-checkout=/builds/worker/checkouts/gecko' + - '--gecko-shallow-clone' + - '--comm-checkout=/builds/worker/checkouts/gecko/comm' + - '--' + - bash + - -cx + - $let: + extraArgs: {$if: 'tasks_for == "cron"', then: '${cron.quoted_args}', else: ''} + in: + $if: 'eventType == "action"' + then: > + cd /builds/worker/checkouts/gecko && + ln -s /builds/worker/artifacts artifacts && + ./mach --log-no-times taskgraph action-callback + else: > + cd /builds/worker/checkouts/gecko && + ln -s /builds/worker/artifacts artifacts && + ./mach --log-no-times taskgraph decision \ + --root=comm/taskcluster \ + --pushlog-id='0' \ + --pushdate='0' \ + --project='enterprise-thunderbird' \ + --owner='${ownerEmail}' \ + --level='${level}' \ + --repository-type=git \ + --tasks-for='${tasks_for}' \ + --base-repository='${baseRepoUrl}' \ + --base-rev='${baseRev}' \ + --head-repository='${repoUrl}' \ + --head-ref='${ref}' \ + --head-rev='${headRev}' \ + ${extraArgs} + + artifacts: + 'public': + type: 'directory' + path: '/builds/worker/artifacts' + expires: {$fromNow: '1 year'} + 'public/docker-contexts': + type: 'directory' + path: '/builds/worker/checkouts/gecko/docker-contexts' + expires: {$fromNow: '7 day'} + + extra: + $merge: + - treeherder: + $merge: + - machine: + platform: gecko-decision + - $switch: + 'eventType == "action"': + groupName: 'action-callback' + groupSymbol: AC + symbol: '${action.symbol}' + 'eventType == "cron"': + groupSymbol: cron + symbol: "${cron.job_symbol}" + $default: + symbol: DC + - $if: 'eventType == "action"' + then: + parent: '${action.taskGroupId}' + action: + name: '${action.name}' + context: + taskGroupId: '${action.taskGroupId}' + taskId: {$eval: 'taskId'} + input: {$eval: 'input'} + clientId: {$eval: 'clientId'} + - $if: 'tasks_for == "cron"' + then: + cron: {$json: {$eval: 'cron'}} + - tasks_for: '${tasks_for}' From 2d6995c964741e823f75b6cbbc25ab3ab78f1c9d Mon Sep 17 00:00:00 2001 From: Alexandre Lissy Date: Wed, 17 Jun 2026 11:28:59 +0200 Subject: [PATCH 02/14] Bug 2053429 - Add Thunderbird Enterprise artifacts builds (INCOMPLETE, PLEASE FIX) --- python/mozbuild/mozbuild/artifacts.py | 37 ++++++++++++++++++++------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/python/mozbuild/mozbuild/artifacts.py b/python/mozbuild/mozbuild/artifacts.py index bc39923880cd9..55e8f36ea0eb2 100644 --- a/python/mozbuild/mozbuild/artifacts.py +++ b/python/mozbuild/mozbuild/artifacts.py @@ -150,6 +150,23 @@ class ThunderbirdJobConfiguration: try_tree = "try-comm-central" +class EnterpriseThunderbirdJobConfiguration: + trust_domain = "enterprise" + product = "thunderbird" + default_candidate_trees = [ + "enterprise-thunderbird.branch.enterprise-release", + ] + nightly_candidate_trees = [ + "enterprise-thunderbird.branch.enterprise-main", + ] + beta_candidate_trees = [ + "enterprise-thunderbird.branch.enterprise-beta", + ] + # The list below list should be updated when we have new ESRs. + esr_candidate_trees = [] + try_tree = "try" + + class ArtifactJob: def _get_orig_basename(self, filename): """Extract the original basename from a filename, removing hash prefixes if present.""" @@ -1306,13 +1323,15 @@ def __init__( self._no_process = no_process self._unfiltered_project_package = unfiltered_project_package - job_configuration = ( - ThunderbirdJobConfiguration - if substs.get("MOZ_BUILD_APP") == "comm/mail" - else EnterpriseJobConfiguration - if substs.get("MOZ_ENTERPRISE") == "1" - else None - ) + if substs.get("MOZ_BUILD_APP") == "comm/mail": # and substs.get("MOZ_ENTERPRISE") != "1": + job_configuration = ThunderbirdJobConfiguration + # elif substs.get("MOZ_BUILD_APP") == "comm/mail" and substs.get("MOZ_ENTERPRISE") == "1": + # job_configuration = EnterpriseThunderbirdJobConfiguration + elif substs.get("MOZ_ENTERPRISE") == "1": + job_configuration = EnterpriseJobConfiguration + else: + job_configuration = None + if not self._unfiltered_project_package: try: cls = JOB_DETAILS[self._job] @@ -1432,8 +1451,8 @@ def _guess_artifact_job(self): else: target_suffix = "-opt" - if self._substs.get("MOZ_ENTERPRISE"): - target_suffix = "-enterprise" + target_suffix + if self._substs.get("MOZ_ENTERPRISE") and not self._substs.get("MOZ_BUILD_APP", "") == "comm/mail": + target_suffix = "-enteprise" + target_suffix if self._substs.get("MOZ_BUILD_APP", "") == "mobile/android": if self._substs["ANDROID_CPU_ARCH"] == "x86_64": From acda102faace62afce7c26de3d60b7e290331451 Mon Sep 17 00:00:00 2001 From: Alexandre Lissy Date: Wed, 17 Jun 2026 11:30:48 +0200 Subject: [PATCH 03/14] Bug 2053429 - Add marionette-enterprise Thunderbird specific tests packaging --- python/mozbuild/mozbuild/action/test_archive.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/python/mozbuild/mozbuild/action/test_archive.py b/python/mozbuild/mozbuild/action/test_archive.py index 9c06ff1a268ae..720b1fc26fd30 100644 --- a/python/mozbuild/mozbuild/action/test_archive.py +++ b/python/mozbuild/mozbuild/action/test_archive.py @@ -761,7 +761,17 @@ "source": buildconfig.topsrcdir, "base": "testing/enterprise/", "patterns": ["**"], + "dest": "marionette/tests", }) + if buildconfig.substs.get("commtopsrcdir"): + commtopsrcdir = buildconfig.substs.get("commtopsrcdir") + thunderbirdinstance = { + "source": commtopsrcdir, + "base": "mail/test/marionette/enterprise", + "pattern": "**", + "dest": "marionette/tests/comm", + } + ARCHIVE_FILES["enterprise"].append(thunderbirdinstance) # "common" is our catch all archive and it ignores things from other archives. # Verify nothing sneaks into ARCHIVE_FILES without a corresponding exclusion From 9f0df3df13cb5884c55017c4f4e994c04e49d58b Mon Sep 17 00:00:00 2001 From: Alexandre Lissy Date: Wed, 17 Jun 2026 11:32:15 +0200 Subject: [PATCH 04/14] Bug 2053429 - Add partner repacks handling for Thunderbird Enterprise --- .../mozrelease/mozrelease/partner_repack.py | 39 +++++++++++++------ .../configs/partner_repacks/enterprise.py | 3 ++ .../scripts/desktop_partner_repacks.py | 12 ++++++ 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/python/mozrelease/mozrelease/partner_repack.py b/python/mozrelease/mozrelease/partner_repack.py index a224eb7e4b3f2..2db6cf88bf90f 100644 --- a/python/mozrelease/mozrelease/partner_repack.py +++ b/python/mozrelease/mozrelease/partner_repack.py @@ -37,10 +37,6 @@ UPSTREAM_ENUS_PATH = "public/build/{filename}" UPSTREAM_L10N_PATH = "public/build/{locale}/{filename}" -WINDOWS_DEST_DIR = Path("firefox") -MAC_DEST_DIR = Path("Contents/Resources") -LINUX_DEST_DIR = Path("firefox") - BOUNCER_PRODUCT_TEMPLATE = ( "partner-firefox-{release_type}-{partner}-{partner_distro}-latest" ) @@ -327,6 +323,7 @@ def __init__( final_dir: Path, ftp_platform: str, repack_info, + dest_dir, file_mode=0o644, quiet=False, source_locale=None, @@ -348,6 +345,7 @@ def __init__( self.source_locale = source_locale self.locale = locale self.working_dir.mkdir(mode=0o755, exist_ok=True, parents=True) + self.dest_dir = dest_dir def announceStart(self): log.info( @@ -462,14 +460,14 @@ def unpackBuild(self): sys.exit(1) def copyFiles(self): - super().copyFiles(LINUX_DEST_DIR) + super().copyFiles(self.dest_dir) def repackBuild(self): if options.quiet: tar_flags = "rf" else: tar_flags = "rvf" - tar_cmd = "tar %s %s %s" % (tar_flags, self.uncompressed_build, LINUX_DEST_DIR) + tar_cmd = "tar %s %s %s" % (tar_flags, self.uncompressed_build, self.dest_dir) shellCommand(tar_cmd) compress_cmd = "xz -f -z -e -9 %s" % self.uncompressed_build shellCommand(compress_cmd) @@ -520,7 +518,7 @@ def getAppName(self): sys.exit(1) def copyFiles(self): - super().copyFiles(Path(self.appName) / MAC_DEST_DIR) + super().copyFiles(Path(self.appName) / self.dest_dir) def repackBuild(self): if options.quiet: @@ -531,7 +529,7 @@ def repackBuild(self): tar_cmd = "tar %s %s '%s'" % ( tar_flags, self.uncompressed_build, - Path(self.appName) / MAC_DEST_DIR, + Path(self.appName) / self.dest_dir, ) shellCommand(tar_cmd) gzip_command = "gzip %s" % self.uncompressed_build @@ -560,14 +558,14 @@ def __init__( ) def copyFiles(self): - super().copyFiles(WINDOWS_DEST_DIR) + super().copyFiles(self.dest_dir) def repackBuild(self): if options.quiet: zip_flags = "-rq" else: zip_flags = "-r" - zip_cmd = f"zip {zip_flags} {self.build} {WINDOWS_DEST_DIR}" + zip_cmd = f"zip {zip_flags} {self.build} {self.dest_dir}" shellCommand(zip_cmd) # we generate the stub installer during the win32 build, so repack it on win32 too @@ -585,7 +583,7 @@ def repackBuild(self): partner_ini += l z.writestr("partner.ini", partner_ini) # we need an empty firefox directory to use the repackage code - d = zipfile.ZipInfo("firefox/") + d = zipfile.ZipInfo(f"{self.dest_dir}/") # https://stackoverflow.com/a/6297838, zip's representation of drwxr-xr-x permissions # is 040755 << 16L, bitwise OR with 0x10 for the MS-DOS directory flag d.external_attr = 1106051088 @@ -688,6 +686,14 @@ def stage(self): default=[], ) + parser.add_option( + "--windows-dest-dir", dest="windows_dest_dir", help="Windows dest dir" + ) + parser.add_option("--mac-dest-dir", dest="mac_dest_dir", help="Windows dest dir") + parser.add_option( + "--linux-dest-dir", dest="linux_dest_dir", help="Windows dest dir" + ) + (options, args) = parser.parse_args() if not options.quiet: @@ -877,6 +883,16 @@ def stage(self): log.info(f"Error: Unable to retrieve {file_name}\n") sys.exit(1) + dest_dir = None + if "win" in ftp_platform: + dest_dir = Path(options.windows_dest_dir) + elif "mac" in ftp_platform: + dest_dir = Path(options.mac_dest_dir) + elif "linux" in ftp_platform: + dest_dir = Path(options.linux_dest_dir) + else: + raise ValueError(f"Unsupported platform: {ftp_platform}") + repackObj = repack_build[ftp_platform]( file_name, full_partner_dir, @@ -884,6 +900,7 @@ def stage(self): final_dir, ftp_platform, repack_info, + dest_dir=dest_dir, locale=locale, source_locale=source_locale, ) diff --git a/testing/mozharness/configs/partner_repacks/enterprise.py b/testing/mozharness/configs/partner_repacks/enterprise.py index f7f462f900f63..20c3a302d0d96 100644 --- a/testing/mozharness/configs/partner_repacks/enterprise.py +++ b/testing/mozharness/configs/partner_repacks/enterprise.py @@ -4,6 +4,9 @@ config = { "appName": "Firefox Enterprise", + "windowsDestDir": "firefox", + "macDestDir": "Contents/Resources", + "linuxDestDir": "firefox", "log_name": "partner_repack", "repack_manifests_url": "git@github.com:mozilla/enterprise-manifests.git", "repo_file": "https://raw.githubusercontent.com/mozilla-releng/git-repo/main/repo", diff --git a/testing/mozharness/scripts/desktop_partner_repacks.py b/testing/mozharness/scripts/desktop_partner_repacks.py index 23e3927e5e87f..f0f1c4424399d 100755 --- a/testing/mozharness/scripts/desktop_partner_repacks.py +++ b/testing/mozharness/scripts/desktop_partner_repacks.py @@ -115,6 +115,12 @@ def _pre_config_lock(self, rw_config): ) if "taskIds" not in self.config: self.fatal("Need upstream taskIds from command line or in UPSTREAM_TASKIDS") + if "windowsDestDir" not in self.config: + self.fatal("Need windowsDestDir in config") + if "macDestDir" not in self.config: + self.fatal("Need macDestDir in config") + if "linuxDestDir" not in self.config: + self.fatal("Need linuxDestDir in config") def query_abs_dirs(self): if self.abs_dirs: @@ -191,6 +197,12 @@ def repack(self): self.config["version"], "-n", str(self.config["build_number"]), + "--windows-dest-dir", + str(self.config["windowsDestDir"]), + "--mac-dest-dir", + str(self.config["macDestDir"]), + "--linux-dest-dir", + str(self.config["linuxDestDir"]), ] if self.config.get("platform"): repack_cmd.extend(["--platform", self.config["platform"]]) From c8f96ed79007fb4d9143d2302e0153f899dc18ce Mon Sep 17 00:00:00 2001 From: Alexandre Lissy Date: Wed, 17 Jun 2026 11:38:19 +0200 Subject: [PATCH 05/14] Bug 2053429 - Add handling for Thunderbird Enterprise macOS entitlements published as artifacts (similar to firefox) --- taskcluster/gecko_taskgraph/transforms/build.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/taskcluster/gecko_taskgraph/transforms/build.py b/taskcluster/gecko_taskgraph/transforms/build.py index c7ed6ba87140f..b6ccc54957901 100644 --- a/taskcluster/gecko_taskgraph/transforms/build.py +++ b/taskcluster/gecko_taskgraph/transforms/build.py @@ -297,6 +297,9 @@ def add_signing_artifacts(config, jobs): elif "enterprise-firefox" == config.params["project"]: # Firefox Enterprise browser_entitlement = "firefoxenterprise.browser" + elif "enterprise-thunderbird" == config.params["project"]: + # Thunderbird Enterprise + browser_entitlement = "thunderbirdenterprise.browser" else: # Release and Beta browser_entitlement = "firefox.browser" @@ -322,7 +325,8 @@ def add_signing_artifacts(config, jobs): # For builds using docker-worker we can't use relative paths # Once we switch builds to generic-worker, this can be removed for entry in job.get("worker", {}).get("artifacts", []): - if entry.get("path", "").startswith("checkouts/gecko/security"): + entry_path = entry.get("path", "") + if entry_path.startswith("checkouts/gecko/security") or entry_path.startswith("checkouts/gecko/comm/build/macosx/hardenedruntime/v2"): entry["path"] = "/builds/worker/" + entry["path"] yield job From 7011ec2aa7f9fcf8afb963da8170c706e182b210 Mon Sep 17 00:00:00 2001 From: Alexandre Lissy Date: Wed, 17 Jun 2026 11:39:42 +0200 Subject: [PATCH 06/14] Bug 2053429 - Update tasks REPOSITORIES env var to allow Thunderbird Enterprise build --- taskcluster/gecko_taskgraph/transforms/job/common.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/taskcluster/gecko_taskgraph/transforms/job/common.py b/taskcluster/gecko_taskgraph/transforms/job/common.py index dfe1cc6677a7e..add3f94ab8147 100644 --- a/taskcluster/gecko_taskgraph/transforms/job/common.py +++ b/taskcluster/gecko_taskgraph/transforms/job/common.py @@ -130,7 +130,12 @@ def support_vcs_checkout(config, job, taskdesc, repo_configs): "COMM_BASE_REPOSITORY": config.params["comm_base_repository"], "COMM_HEAD_REPOSITORY": config.params["comm_head_repository"], "COMM_HEAD_REV": config.params["comm_head_rev"], + "COMM_REPOSITORY_TYPE": "git", }) + + repos = json.loads(taskdesc["worker"]["env"].get("REPOSITORIES", {})) + repos.update({"comm": "Mozilla Thunderbird"}) + taskdesc["worker"]["env"]["REPOSITORIES"] = json.dumps(repos) elif job["run"].get("comm-checkout", False): raise Exception( "Can't checkout from comm-* repository if not given a repository." From 651adfb2514095d95b3a104541e7570d3f7c135a Mon Sep 17 00:00:00 2001 From: Alexandre Lissy Date: Wed, 17 Jun 2026 11:41:38 +0200 Subject: [PATCH 07/14] Bug 2053429 - Build TC route for enterprise-thunderbird when running in enterprise-firefox --- .taskcluster.yml | 1 + python/mozbuild/mozbuild/toolchains.py | 1 + taskcluster/docs/parameters.md | 4 ++++ taskcluster/gecko_taskgraph/decision.py | 2 ++ taskcluster/gecko_taskgraph/parameters.py | 6 ++++++ taskcluster/gecko_taskgraph/transforms/task.py | 17 +++++++++++++++-- 6 files changed, 29 insertions(+), 2 deletions(-) diff --git a/.taskcluster.yml b/.taskcluster.yml index 967710ad31fed..0cad6dba6ed99 100644 --- a/.taskcluster.yml +++ b/.taskcluster.yml @@ -815,6 +815,7 @@ tasks: MOZ_UPLOAD_DIR: /builds/worker/artifacts MOZ_AUTOMATION: '1' PYTHONDONTWRITEBYTECODE: '1' + TRIGGER_PROJECT: '${project}' - $switch: 'isPullRequest': GECKO_PULL_REQUEST_NUMBER: "${prNumber}" diff --git a/python/mozbuild/mozbuild/toolchains.py b/python/mozbuild/mozbuild/toolchains.py index 19cf0e2b4ac30..20b25ea82503f 100644 --- a/python/mozbuild/mozbuild/toolchains.py +++ b/python/mozbuild/mozbuild/toolchains.py @@ -34,6 +34,7 @@ def toolchain_task_definitions(): "head_repository": "https://github.com/mozilla/enterprise-firefox", "repository_type": "git", "pull_request_number": 0, + "trigger_project": "enterprise-firefox", } import sys diff --git a/taskcluster/docs/parameters.md b/taskcluster/docs/parameters.md index b6995559acb7d..90c5be2d22f82 100644 --- a/taskcluster/docs/parameters.md +++ b/taskcluster/docs/parameters.md @@ -119,6 +119,10 @@ topic. : The GitHub pull request number, if any. +`trigger_project` + +: The name of the GitHub repo, distinct from --project CLI parameter + `dontbuild` : Set (to `true`) when the commit message requested that no tasks run on this push. diff --git a/taskcluster/gecko_taskgraph/decision.py b/taskcluster/gecko_taskgraph/decision.py index 5b5c7e515ab5c..2bfcf64931c71 100644 --- a/taskcluster/gecko_taskgraph/decision.py +++ b/taskcluster/gecko_taskgraph/decision.py @@ -31,6 +31,7 @@ from .parameters import ( get_app_version, get_release_type, + get_trigger_project, get_version, ) from .util.backstop import ANDROID_PERFTEST_BACKSTOP_INDEX, BACKSTOP_INDEX, is_backstop @@ -380,6 +381,7 @@ def get_decision_parameters(graph_config, options): parameters["test_manifest_loader"] = "default" parameters["try_mode"] = None parameters["try_task_config"] = {} + parameters["trigger_project"] = get_trigger_project(parameters) # owner must be an email, but sometimes (e.g., for ffxbld) it is not, in which # case, fake it diff --git a/taskcluster/gecko_taskgraph/parameters.py b/taskcluster/gecko_taskgraph/parameters.py index 837fa9664f621..fc2d0efe35655 100644 --- a/taskcluster/gecko_taskgraph/parameters.py +++ b/taskcluster/gecko_taskgraph/parameters.py @@ -92,6 +92,7 @@ class GeckoParametersSchema(Schema, kw_only=True, rename=None): head_git_repository: Optional[str] = None head_git_rev: Optional[str] = None pull_request_number: Optional[int] = None + trigger_project: Optional[str] = None def get_contents(path): @@ -121,6 +122,11 @@ def get_release_type(parameters): else: return "nightly-enterprise" +def get_trigger_project(parameters): + if trigger_project := os.environ.get("TRIGGER_PROJECT", None): + return trigger_project + return parameters["project"] + def get_defaults(repo_root=None): return { diff --git a/taskcluster/gecko_taskgraph/transforms/task.py b/taskcluster/gecko_taskgraph/transforms/task.py index 7295adada04fc..c03c2d958110c 100644 --- a/taskcluster/gecko_taskgraph/transforms/task.py +++ b/taskcluster/gecko_taskgraph/transforms/task.py @@ -296,9 +296,22 @@ def get_branch_repo(config): def get_project_alias(config): + # --project from mach taskgraph decision + project = config.params["project"] + # triger_project exposed from PR/push on Github + trigger_project = config.params["trigger_project"] + + # If they do not match, e.g., + # --project=enterprise-thunderbird and triger_project=enterprise-firefox + # then force use trigger_project + # this allows to run thunderbird decision task from enterprise-firefox repo + # and report its status and treeherder on the same enterprise-firefox push + if trigger_project != project: + project = trigger_project + if config.params["tasks_for"].startswith("github-pull-request"): - return f"{config.params['project']}-pr" - return config.params["project"] + return f"{project}-pr" + return project def get_head_ref(config) -> tuple[str, Optional[str]]: From 2bd9733848809ee2a113f5764431c2207063bd7c Mon Sep 17 00:00:00 2001 From: Alexandre Lissy Date: Wed, 17 Jun 2026 11:42:34 +0200 Subject: [PATCH 08/14] Bug 2053429 - Build TC Treeherder route to show enterprise-thunderbird tasks on enterprise-firefox treeherder project --- taskcluster/gecko_taskgraph/transforms/task.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/taskcluster/gecko_taskgraph/transforms/task.py b/taskcluster/gecko_taskgraph/transforms/task.py index c03c2d958110c..2ba8e0df5c10c 100644 --- a/taskcluster/gecko_taskgraph/transforms/task.py +++ b/taskcluster/gecko_taskgraph/transforms/task.py @@ -281,6 +281,10 @@ def get_branch_rev(config): ] +def get_root_branch_rev(config): + return config.params["head_rev"] + + def get_branch_git_rev(config): return config.params[ "{}head_git_rev".format(config.graph_config["project-repo-param-prefix"]) @@ -2566,10 +2570,10 @@ def build_task(config, tasks): treeherder["jobKind"] = task_th["kind"] treeherder["tier"] = task_th["tier"] - branch_rev = get_branch_rev(config) + root_branch_rev = get_root_branch_rev(config) routes.append( - f"{TREEHERDER_ROUTE_ROOT}.v2.{get_treeherder_project(config)}.{branch_rev}" + f"{TREEHERDER_ROUTE_ROOT}.v2.{get_treeherder_project(config)}.{root_branch_rev}" ) if "deadline-after" not in task: From 0026795cf166f09768b92757f29bfd04669e023b Mon Sep 17 00:00:00 2001 From: Alexandre Lissy Date: Wed, 17 Jun 2026 11:43:10 +0200 Subject: [PATCH 09/14] Bug 2053429 - Update Thunderbird Enterprise tasks' treeherder platform triggered within Enterprise Firefox project to be distincts --- .../gecko_taskgraph/transforms/task.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/taskcluster/gecko_taskgraph/transforms/task.py b/taskcluster/gecko_taskgraph/transforms/task.py index 2ba8e0df5c10c..b4d8adcaf5d83 100644 --- a/taskcluster/gecko_taskgraph/transforms/task.py +++ b/taskcluster/gecko_taskgraph/transforms/task.py @@ -2720,6 +2720,26 @@ def build_task(config, tasks): } +@transforms.add +def update_treeherder_platform_comm(config, tasks): + for task in tasks: + if "comm_base_repository" in config.params: + task_th = task.get("task", {}).get("extra", {}).get("treeherder", {}) + if task_th: + th_machine_platform = task_th.get("machine", {}).get("platform") + if th_machine_platform and "thunderbird" not in th_machine_platform: + task["task"]["extra"]["treeherder"]["machine"]["platform"] = ( + f"{th_machine_platform}-thunderbird" + ) + th_platform = task_th.get("treeherder-platform") + if th_platform and "thunderbird" not in th_platform: + th_platform_split = th_platform.split("/") + task["task"]["extra"]["treeherder-platform"] = ( + f"{th_platform_split[0]}-thunderbird/{th_platform_split[1]}" + ) + yield task + + @transforms.add def chain_of_trust(config, tasks): for task in tasks: From 7a675856ad9178c82e364420b35ec92823e91585 Mon Sep 17 00:00:00 2001 From: Alexandre Lissy Date: Wed, 17 Jun 2026 11:44:18 +0200 Subject: [PATCH 10/14] Bug 2053429 - Update repackage routes to consider Thunderbird Enterprise correctly --- taskcluster/gecko_taskgraph/transforms/repackage_routes.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/taskcluster/gecko_taskgraph/transforms/repackage_routes.py b/taskcluster/gecko_taskgraph/transforms/repackage_routes.py index f1f15c6730725..49e6c794625c2 100644 --- a/taskcluster/gecko_taskgraph/transforms/repackage_routes.py +++ b/taskcluster/gecko_taskgraph/transforms/repackage_routes.py @@ -17,10 +17,13 @@ def add_indexes(config, jobs): if repackage_type and job["attributes"]["build_type"] != "debug": build_platform = job["attributes"]["build_platform"] job_name = f"{build_platform}-{repackage_type}" - if job.get("shipping-product", "").startswith("thunderbird"): + if "thunderbird" in job.get("shipping-product", ""): product = job.get("index", {}).get("product", "thunderbird") - else: + elif "firefox" in config.params["project"]: product = job.get("index", {}).get("product", "firefox") + else: + yield job + continue index_type = "generic" if job["attributes"].get("shippable") and job["attributes"].get("locale"): index_type = "shippable-l10n" From 82b881ba8bf8e2af14f2d47c4b12d00656513fda Mon Sep 17 00:00:00 2001 From: Alexandre Lissy Date: Thu, 4 Jun 2026 15:49:50 +0200 Subject: [PATCH 11/14] Bug 2053429 - Update TC to handle also enterprise-thunderbird project --- taskcluster/gecko_taskgraph/target_tasks.py | 7 +++++-- taskcluster/gecko_taskgraph/transforms/build.py | 6 +++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/taskcluster/gecko_taskgraph/target_tasks.py b/taskcluster/gecko_taskgraph/target_tasks.py index f38007a3fd442..6974c61170cc4 100644 --- a/taskcluster/gecko_taskgraph/target_tasks.py +++ b/taskcluster/gecko_taskgraph/target_tasks.py @@ -514,12 +514,15 @@ def filter(task): return False # Only keep builds that have been explicitely flagged - if task.kind == "build" and not "enterprise-firefox" in task.attributes.get( + if task.kind == "build" and not parameters["project"] in task.attributes.get( "run_on_projects" ): return False - if task.attributes.get("shipping_product") not in (None, "firefox-enterprise"): + if task.attributes.get("shipping_product") not in ( + None, + parameters["release_product"], + ): return False build_platform = task.attributes.get("build_platform") diff --git a/taskcluster/gecko_taskgraph/transforms/build.py b/taskcluster/gecko_taskgraph/transforms/build.py index b6ccc54957901..3abc9ea3263a1 100644 --- a/taskcluster/gecko_taskgraph/transforms/build.py +++ b/taskcluster/gecko_taskgraph/transforms/build.py @@ -336,7 +336,11 @@ def add_enterprise_secret_scopes(config, jobs): """Enterprise builds re-use some secrets from the Gecko trust domain.""" level = config.params["level"] for job in jobs: - if config.params["project"] in ("enterprise-firefox", "enterprise-firefox-try"): + if config.params["project"] in ( + "enterprise-firefox", + "enterprise-firefox-try", + "enterprise-thunderbird", + ): job.setdefault("scopes", []).extend([ f"secrets:get:project/releng/gecko/build/level-{level}/gls-gapi.data", f"secrets:get:project/releng/gecko/build/level-{level}/sb-gapi.data", From fede631128ffbffd6a2212ba10cb2321a2c757b4 Mon Sep 17 00:00:00 2001 From: Alexandre Lissy Date: Tue, 30 Jun 2026 16:35:53 +0200 Subject: [PATCH 12/14] Bug 2053429 - Patch taskgraph to return enterprise-firefox instead of enterprise-thunderbird repo for CoT to pass --- taskcluster/gecko_taskgraph/__init__.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/taskcluster/gecko_taskgraph/__init__.py b/taskcluster/gecko_taskgraph/__init__.py index d191eb499f062..08abd090fba13 100644 --- a/taskcluster/gecko_taskgraph/__init__.py +++ b/taskcluster/gecko_taskgraph/__init__.py @@ -17,6 +17,8 @@ from gecko_taskgraph.config import GraphConfigSchema +from taskgraph.parameters import Parameters + GECKO = os.path.normpath(os.path.realpath(os.path.join(__file__, "..", "..", ".."))) TEST_CONFIGS = os.path.join(GECKO, "taskcluster", "test_configs") @@ -75,6 +77,14 @@ def _represent_ro_dict(dumper, data): del payload_builders["docker-worker"] del payload_builders["generic-worker"] +orig_file_url = Parameters.file_url +def local_file_url(self, path, pretty=False): + value = orig_file_url(self, path, pretty) + if self["trigger_project"] == self["project"]: + return value + return value.replace(self["comm_head_repository"], self["head_repository"]) + +Parameters.file_url = local_file_url def register(graph_config): """Used to register Gecko specific extensions. From b0b2488dd06c472d8bdb67a81e905290bea1799d Mon Sep 17 00:00:00 2001 From: Alexandre Lissy Date: Fri, 12 Jun 2026 13:52:01 +0200 Subject: [PATCH 13/14] WIP - FORCE TBIRD SHIPPABLE ON PR --- taskcluster/gecko_taskgraph/target_tasks.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/taskcluster/gecko_taskgraph/target_tasks.py b/taskcluster/gecko_taskgraph/target_tasks.py index 6974c61170cc4..5a96937e87b8b 100644 --- a/taskcluster/gecko_taskgraph/target_tasks.py +++ b/taskcluster/gecko_taskgraph/target_tasks.py @@ -532,6 +532,9 @@ def filter(task): level = int(parameters["level"]) if level < 3: + if "thunderbird-enterprise" == task.attributes.get("shipping_product"): + return True + if "shippable" in task.label or shippable: return False From 86f5345327f2bd2cd39b5e55db936c6d2c3eb4a1 Mon Sep 17 00:00:00 2001 From: Alexandre Lissy Date: Thu, 16 Jul 2026 07:30:30 +0200 Subject: [PATCH 14/14] Bug 2053429 - Add tbrust-vendor action on mozharness build script for comm tasks --- testing/mozharness/mozharness/base/script.py | 32 +++++++++++++++++++ testing/mozharness/scripts/desktop_l10n.py | 1 + .../mozharness/scripts/fx_desktop_build.py | 1 + 3 files changed, 34 insertions(+) diff --git a/testing/mozharness/mozharness/base/script.py b/testing/mozharness/mozharness/base/script.py index 1d884c6c0af06..f7705ecfd162b 100644 --- a/testing/mozharness/mozharness/base/script.py +++ b/testing/mozharness/mozharness/base/script.py @@ -2434,6 +2434,38 @@ def run_and_exit(self): self.warning("returning nonzero exit status %d" % rc) sys.exit(rc) + def tbrust_vendor(self): + args = ["tb-rust", "vendor"] + + restore_env = None + if "MACH_BUILD_PYTHON_NATIVE_PACKAGE_SOURCE" in os.environ: + restore_env = os.environ["MACH_BUILD_PYTHON_NATIVE_PACKAGE_SOURCE"] + del os.environ["MACH_BUILD_PYTHON_NATIVE_PACKAGE_SOURCE"] + + dirs = self.query_abs_dirs() + mach = [sys.executable, "mach"] + + return_code = self.run_command( + command=mach + ["--log-no-times"] + args, + cwd=dirs["abs_src_dir"], + env=os.environ, + output_timeout=self.config.get("max_build_output_timeout", 60 * 40), + ) + + if restore_env: + os.environ["MACH_BUILD_PYTHON_NATIVE_PACKAGE_SOURCE"] = restore_env + + if return_code: + self.return_code = self.worst_level( + EXIT_STATUS_DICT[TBPL_FAILURE], + self.return_code, + AUTOMATION_EXIT_CODES[::-1], + ) + self.fatal( + "'mach %s' did not run successfully. Please check " + "log for errors." % " ".join(args) + ) + def clobber(self): """ Delete the working directory diff --git a/testing/mozharness/scripts/desktop_l10n.py b/testing/mozharness/scripts/desktop_l10n.py index fdc66e7e79d5d..ae1c1d0a4a7e9 100755 --- a/testing/mozharness/scripts/desktop_l10n.py +++ b/testing/mozharness/scripts/desktop_l10n.py @@ -55,6 +55,7 @@ def __init__(self, require_config_file=True): "clone-locales", "list-locales", "setup", + "tbrust-vendor", "repack", "summary", ], diff --git a/testing/mozharness/scripts/fx_desktop_build.py b/testing/mozharness/scripts/fx_desktop_build.py index 69649ad17f145..7746a3c245576 100755 --- a/testing/mozharness/scripts/fx_desktop_build.py +++ b/testing/mozharness/scripts/fx_desktop_build.py @@ -29,6 +29,7 @@ def __init__(self): "all_actions": [ "get-secrets", "clobber", + "tbrust-vendor", "build", "static-analysis-autotest", "valgrind-test",