From ba1fdc821eacd7664ec0b83075a6622676eba37a Mon Sep 17 00:00:00 2001 From: Julien Cristau Date: Fri, 21 Feb 2025 15:03:17 +0000 Subject: [PATCH] Bug 1726251 - add `mach repackage flatpak` command. r=releng-reviewers,gerard-majax,bhearsum Copy commands from taskcluster/docker/firefox-flatpak/runme.sh into a standalone repackage command that can be pointed at a firefox build and spit out a flatpak repository. The browser/installer/linux/app/flatpak directory contains templates and extra files that get injected into the flatpak build process. Differential Revision: https://phabricator.services.mozilla.com/D238800 --- .../linux/app/flatpak/files/bin/firefox | 3 + .../flatpak/files/etc/firefox/.placeholder | 0 .../app/flatpak/files/lib/ffmpeg/.placeholder | 0 .../org.mozilla.firefox.appdata.xml.in | 45 +++ .../apps/org.mozilla.firefox-symbolic.svg | 6 + .../installer/linux/app/flatpak/metadata.in | 19 ++ .../linux/app/flatpak/metadata.locale.in | 5 + python/mozbuild/mozbuild/mach_commands.py | 66 ++++ .../mozbuild/mozbuild/repackaging/flatpak.py | 306 ++++++++++++++++++ 9 files changed, 450 insertions(+) create mode 100755 browser/installer/linux/app/flatpak/files/bin/firefox create mode 100644 browser/installer/linux/app/flatpak/files/etc/firefox/.placeholder create mode 100644 browser/installer/linux/app/flatpak/files/lib/ffmpeg/.placeholder create mode 100644 browser/installer/linux/app/flatpak/files/share/appdata/org.mozilla.firefox.appdata.xml.in create mode 100644 browser/installer/linux/app/flatpak/files/share/icons/hicolor/symbolic/apps/org.mozilla.firefox-symbolic.svg create mode 100644 browser/installer/linux/app/flatpak/metadata.in create mode 100644 browser/installer/linux/app/flatpak/metadata.locale.in create mode 100644 python/mozbuild/mozbuild/repackaging/flatpak.py diff --git a/browser/installer/linux/app/flatpak/files/bin/firefox b/browser/installer/linux/app/flatpak/files/bin/firefox new file mode 100755 index 000000000000..e41bf66f33aa --- /dev/null +++ b/browser/installer/linux/app/flatpak/files/bin/firefox @@ -0,0 +1,3 @@ +#!/bin/bash +export TMPDIR=$XDG_CACHE_HOME/tmp +exec /app/lib/firefox/firefox --name org.mozilla.firefox "$@" diff --git a/browser/installer/linux/app/flatpak/files/etc/firefox/.placeholder b/browser/installer/linux/app/flatpak/files/etc/firefox/.placeholder new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/browser/installer/linux/app/flatpak/files/lib/ffmpeg/.placeholder b/browser/installer/linux/app/flatpak/files/lib/ffmpeg/.placeholder new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/browser/installer/linux/app/flatpak/files/share/appdata/org.mozilla.firefox.appdata.xml.in b/browser/installer/linux/app/flatpak/files/share/appdata/org.mozilla.firefox.appdata.xml.in new file mode 100644 index 000000000000..de8a8c883770 --- /dev/null +++ b/browser/installer/linux/app/flatpak/files/share/appdata/org.mozilla.firefox.appdata.xml.in @@ -0,0 +1,45 @@ + + + org.mozilla.firefox + org.mozilla.firefox.desktop + Firefox + Mozilla + Fast, Private & Safe Web Browser + CC0-1.0 + MPL-2.0 + +

When it comes to your life online, you have a choice: accept the factory settings or put your privacy first. When you choose Firefox as your default browser, you’re choosing to protect your data while supporting an independent tech company. Firefox is also the only major browser backed by a non-profit fighting to give you more openness, transparency and control of your life online. Join hundreds of millions of people who choose to protect what's important by choosing Firefox - a web browser designed to be fast, easy to use, customizable and private.

+
+ + + + + mozilla + internet + web + + + https://www.mozilla.org/firefox/ + https://donate.mozilla.org/ + https://bugzilla.mozilla.org/ + https://support.mozilla.org/ + https://wiki.mozilla.org/L10n:Starting_a_localization + + + + https://raw.githubusercontent.com/mozilla-releng/scriptworker-scripts/master/pushflatpakscript/media/screenshots/image1.png + The “New Tab” page + + + https://raw.githubusercontent.com/mozilla-releng/scriptworker-scripts/master/pushflatpakscript/media/screenshots/image2.png + A Wikipedia article displayed in Firefox + + + https://raw.githubusercontent.com/mozilla-releng/scriptworker-scripts/master/pushflatpakscript/media/screenshots/image3.png + The “Welcome to Firefox” page: “Open up an amazing internet” + + + + https://hg.mozilla.org/mozilla-central/file/tip/browser/installer/linux/app/flatpak + +
diff --git a/browser/installer/linux/app/flatpak/files/share/icons/hicolor/symbolic/apps/org.mozilla.firefox-symbolic.svg b/browser/installer/linux/app/flatpak/files/share/icons/hicolor/symbolic/apps/org.mozilla.firefox-symbolic.svg new file mode 100644 index 000000000000..1858ced70e96 --- /dev/null +++ b/browser/installer/linux/app/flatpak/files/share/icons/hicolor/symbolic/apps/org.mozilla.firefox-symbolic.svg @@ -0,0 +1,6 @@ + + + + diff --git a/browser/installer/linux/app/flatpak/metadata.in b/browser/installer/linux/app/flatpak/metadata.in new file mode 100644 index 000000000000..c1adfce13aaa --- /dev/null +++ b/browser/installer/linux/app/flatpak/metadata.in @@ -0,0 +1,19 @@ +[Application] +name=org.mozilla.firefox +runtime=org.freedesktop.Platform/${ARCH}/${FREEDESKTOP_VERSION} +sdk=org.freedesktop.Sdk/${ARCH}/${FREEDESKTOP_VERSION} +base=app/org.mozilla.firefox.BaseApp/${ARCH}/${FIREFOX_BASEAPP_CHANNEL} +[Extension org.mozilla.firefox.Locale] +directory=share/runtime/langpack +autodelete=true +locale-subset=true + +[Extension org.freedesktop.Platform.ffmpeg-full] +directory=lib/ffmpeg +add-ld-path=. +no-autodownload=true +version=${FREEDESKTOP_VERSION} + +[Extension org.mozilla.firefox.systemconfig] +directory=etc/firefox +no-autodownload=true diff --git a/browser/installer/linux/app/flatpak/metadata.locale.in b/browser/installer/linux/app/flatpak/metadata.locale.in new file mode 100644 index 000000000000..f701a4ac5f00 --- /dev/null +++ b/browser/installer/linux/app/flatpak/metadata.locale.in @@ -0,0 +1,5 @@ +[Runtime] +name=org.mozilla.firefox.Locale + +[ExtensionOf] +ref=app/org.mozilla.firefox/${ARCH}/${FLATPAK_BRANCH} diff --git a/python/mozbuild/mozbuild/mach_commands.py b/python/mozbuild/mozbuild/mach_commands.py index 4f09a93b233f..b6bbad025edb 100644 --- a/python/mozbuild/mozbuild/mach_commands.py +++ b/python/mozbuild/mozbuild/mach_commands.py @@ -3482,3 +3482,69 @@ def _prepend_debugger_args(args, debugger, debugger_args): # Prepend the debugger args. args = [debuggerInfo.path] + debuggerInfo.args + args return args + + +@SubCommand( + "repackage", + "flatpak", + description="Repackage a tar file into a flatpak", + virtualenv_name="repackage-desktop-file", +) +@CommandArgument("--input", "-i", type=str, required=True, help="Input filename") +@CommandArgument("--output", "-o", type=str, required=True, help="Output filename") +@CommandArgument("--name", type=str, required=True, help="flatpak package name") +@CommandArgument("--arch", type=str, required=True, help="flatpak architecture") +@CommandArgument("--version", type=str, required=True, help="package version") +@CommandArgument("--product", type=str, required=True, help="release product") +@CommandArgument( + "--release-type", + type=str, + required=True, + help="The release being shipped. Used to disambiguate nightly/try etc.", +) +@CommandArgument( + "--flatpak-branch", + type=str, + required=True, + help="flatpak branch", +) +@CommandArgument( + "--template-dir", type=str, required=True, help="path to template directory" +) +@CommandArgument( + "--langpack-pattern", + type=str, + help="shell pattern matching language packs to include in the flatpak", +) +def repackage_flatpak( + command_context, + input, + output, + name, + arch, + version, + product, + release_type, + flatpak_branch, + template_dir, + langpack_pattern, +): + if not os.path.exists(input): + print("Input file does not exist: %s" % input) + return 1 + + from mozbuild.repackaging.flatpak import repackage_flatpak + + repackage_flatpak( + command_context.log, + input, + output, + arch, + version, + product, + release_type, + name, + flatpak_branch, + template_dir, + langpack_pattern, + ) diff --git a/python/mozbuild/mozbuild/repackaging/flatpak.py b/python/mozbuild/mozbuild/repackaging/flatpak.py new file mode 100644 index 000000000000..4c3adaf9956c --- /dev/null +++ b/python/mozbuild/mozbuild/repackaging/flatpak.py @@ -0,0 +1,306 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +import datetime +import glob +import json +import os +import shutil +import subprocess +import tempfile +import zipfile +from pathlib import Path +from string import Template + +# When updating this, please make sure to keep in sync the script for symbol +# scraping at +# https://github.com/mozilla/symbol-scrapers/blob/master/firefox-flatpak/script.sh +FREEDESKTOP_VERSION = "23.08" +# The base app is shared by firefox and thunderbird +FIREFOX_BASEAPP = "org.mozilla.firefox.BaseApp" +FIREFOX_BASEAPP_CHANNEL = "23.08" + + +def _inject_flatpak_distribution_ini(target): + with tempfile.TemporaryDirectory() as git_clone_dir: + subprocess.check_call( + [ + "git", + "clone", + "https://github.com/mozilla-partners/flatpak.git", + git_clone_dir, + ], + ) + shutil.copyfile( + os.path.join( + git_clone_dir, "desktop/flatpak/distribution/distribution.ini" + ), + target, + ) + + +def _langpack_manifest(xpi): + with zipfile.ZipFile(xpi) as f: + return json.load(f.open("manifest.json")) + + +def _render_template(source, dest, variables): + if source.endswith(".in"): + with open(source) as f: + template = Template(f.read()) + with open(dest[:-3], "w") as f: + f.write(template.substitute(variables)) + else: + shutil.copy(source, dest) + + +def _render_flatpak_templates(template_dir, build_dir, variables): + for root, dirs, files in os.walk(template_dir): + relative = os.path.relpath(root, template_dir) + for d in dirs: + os.makedirs(build_dir / relative / d, exist_ok=True) + for f in files: + _render_template( + os.path.join(root, f), os.path.join(build_dir, relative, f), variables + ) + + +def repackage_flatpak( + log, + infile, + output, + arch, + version, + release_product, + release_type, + flatpak_name, + flatpak_branch, + template_dir, + langpack_pattern, +): + with tempfile.TemporaryDirectory() as tmpdir: + tmpdir = Path(tmpdir) + build_dir = tmpdir / "build" + app_dir = build_dir / "files" + lib_dir = app_dir / "lib" + + # Fetch and install the base app + subprocess.run( + [ + "flatpak", + "remote-add", + "--user", + "--if-not-exists", + "--from", + "flathub", + "https://dl.flathub.org/repo/flathub.flatpakrepo", + ], + check=True, + ) + subprocess.run( + [ + "flatpak", + "install", + "--user", + "-y", + "flathub", + f"{FIREFOX_BASEAPP}/{arch}/{FIREFOX_BASEAPP_CHANNEL}", + "--no-deps", + ], + check=True, + ) + # Copy files from the base app to our build dir + base = ( + Path.home() + / f".local/share/flatpak/app/{FIREFOX_BASEAPP}/{arch}/{FIREFOX_BASEAPP_CHANNEL}/active/files" + ) + shutil.copytree(base, app_dir, symlinks=True) + + # Extract our build to the app dir + lib_dir.mkdir(exist_ok=True) + subprocess.check_call(["tar", "xf", os.path.abspath(infile)], cwd=lib_dir) + + if release_product == "firefox": + distribution_ini = lib_dir / "firefox" / "distribution" / "distribution.ini" + distribution_ini.parent.mkdir(parents=True) + _inject_flatpak_distribution_ini(distribution_ini) + + date = datetime.date.today().strftime("%Y-%m-%d") + variables = { + "ARCH": arch, + "FREEDESKTOP_VERSION": FREEDESKTOP_VERSION, + "FIREFOX_BASEAPP_CHANNEL": FIREFOX_BASEAPP_CHANNEL, + "FLATPAK_BRANCH": flatpak_branch, + "VERSION": version, + "DATE": date, + "DEB_PKG_NAME": release_product, + "DBusActivatable": "false", + "Icon": flatpak_name, + "StartupWMClass": release_product, + } + _render_flatpak_templates(template_dir, build_dir, variables) + + from fluent.runtime.fallback import FluentLocalization, FluentResourceLoader + + from mozbuild.repackaging.desktop_file import generate_browser_desktop_entry + + desktop = generate_browser_desktop_entry( + log, + variables, + release_product, + release_type, + FluentLocalization, + FluentResourceLoader, + ) + desktop_dir = app_dir / "share" / "applications" + desktop_dir.mkdir(parents=True, exist_ok=True) + desktop_file_name = desktop_dir / f"{flatpak_name}.desktop" + with desktop_file_name.open("w") as f: + for line in desktop: + print(line, file=f) + + if release_product == "firefox": + icon_path = "lib/firefox/browser/chrome/icons/default" + elif release_product == "thunderbird": + icon_path = "lib/thunderbird/chrome/icons/default" + else: + raise NotImplementedError() + + for size in (16, 32, 48, 64, 128): + os.makedirs( + app_dir / f"share/icons/hicolor/{size}x{size}/apps", exist_ok=True + ) + shutil.copy( + app_dir / icon_path / f"default{size}.png", + app_dir / f"share/icons/hicolor/{size}x{size}/apps/{flatpak_name}.png", + ) + + subprocess.run( + [ + "appstream-compose", + f"--prefix={app_dir}", + "--origin=flatpak", + f"--basename={flatpak_name}", + flatpak_name, + ], + check=True, + ) + subprocess.run( + [ + "appstream-util", + "mirror-screenshots", + f"{app_dir}/share/app-info/xmls/{flatpak_name}.xml.gz", + f"https://dl.flathub.org/repo/screenshots/{flatpak_name}-{flatpak_branch}", + "build/screenshots", + f"build/screenshots/{flatpak_name}-{flatpak_branch}", + ], + check=True, + cwd=tmpdir, + ) + + os.makedirs( + app_dir / f"lib/{release_product}/distribution/extensions", exist_ok=True + ) + for langpack in glob.iglob(langpack_pattern): + manifest = _langpack_manifest(langpack) + locale = manifest["langpack_id"] + name = manifest["browser_specific_settings"]["gecko"]["id"] + + lang = locale.split("-", 1)[0] + os.makedirs(app_dir / "share/runtime/langpack" / lang, exist_ok=True) + shutil.copy( + langpack, app_dir / "share/runtime/langpack" / lang / f"{name}.xpi" + ) + os.symlink( + f"/app/share/runtime/langpack/{lang}/{name}.xpi", + app_dir / f"lib/{release_product}/distribution/extensions/{name}.xpi", + ) + + subprocess.run( + [ + "flatpak", + "build-finish", + "build", + "--allow=devel", + "--share=ipc", + "--share=network", + "--socket=pulseaudio", + "--socket=wayland", + "--socket=fallback-x11", + "--socket=pcsc", + "--socket=cups", + "--require-version=0.11.1", + "--persist=.mozilla", + "--env=DICPATH=/usr/share/hunspell", + "--filesystem=xdg-download:rw", + "--filesystem=/run/.heim_org.h5l.kcm-socket", + "--filesystem=xdg-run/speech-dispatcher:ro", + "--device=all", + "--talk-name=org.freedesktop.FileManager1", + "--system-talk-name=org.freedesktop.NetworkManager", + "--talk-name=org.a11y.Bus", + "--talk-name=org.gtk.vfs.*", + "--own-name=org.mpris.MediaPlayer2.firefox.*", + "--own-name=org.mozilla.firefox.*", + "--own-name=org.mozilla.firefox_beta.*", + "--command=firefox", + ], + check=True, + cwd=tmpdir, + ) + + subprocess.run( + [ + "flatpak", + "build-export", + "--disable-sandbox", + "--no-update-summary", + "--exclude=/share/runtime/langpack/*/*", + "repo", + "build", + flatpak_branch, + ], + check=True, + cwd=tmpdir, + ) + subprocess.run( + [ + "flatpak", + "build-export", + "--disable-sandbox", + "--no-update-summary", + "--metadata=metadata.locale", + "--files=files/share/runtime/langpack", + "repo", + "build", + flatpak_branch, + ], + check=True, + cwd=tmpdir, + ) + subprocess.run( + [ + "ostree", + "commit", + "--repo=repo", + "--canonical-permissions", + "--branch=screenshots/x86_64", + "build/screenshots", + ], + check=True, + cwd=tmpdir, + ) + subprocess.run( + ["flatpak", "build-update-repo", "--generate-static-deltas", "repo"], + check=True, + cwd=tmpdir, + ) + env = os.environ.copy() + env["XZ_OPT"] = "-e9" + subprocess.run( + ["tar", "cvfJ", os.path.abspath(output), "repo"], + check=True, + env=env, + cwd=tmpdir, + )