flatpak.py (10907B)
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 file, 3 # You can obtain one at http://mozilla.org/MPL/2.0/. 4 5 import glob 6 import json 7 import logging 8 import os 9 import shlex 10 import shutil 11 import subprocess 12 import tempfile 13 import zipfile 14 from pathlib import Path 15 from string import Template 16 17 from mozbuild.repackaging.utils import ( 18 application_ini_data_from_directory, 19 get_build_variables, 20 ) 21 22 # When updating this, please make sure to keep in sync the script for symbol 23 # scraping at 24 # https://github.com/mozilla/symbol-scrapers/blob/master/firefox-flatpak/script.sh 25 FREEDESKTOP_VERSION = "24.08" 26 # The base app is shared by firefox and thunderbird 27 FIREFOX_BASEAPP = "org.mozilla.firefox.BaseApp" 28 FIREFOX_BASEAPP_CHANNEL = FREEDESKTOP_VERSION 29 30 31 def run_command(log, *args, **kwargs): 32 log( 33 logging.INFO, 34 "flatpak", 35 {"command": shlex.join(args[0]), "cwd": str(kwargs.get("cwd", os.getcwd()))}, 36 "Running: {command} (in {cwd})", 37 ) 38 return subprocess.run(*args, check=True, **kwargs) 39 40 41 def _inject_flatpak_distribution_ini(log, target): 42 with tempfile.TemporaryDirectory() as git_clone_dir: 43 run_command( 44 log, 45 [ 46 "git", 47 "clone", 48 "https://github.com/mozilla-partners/flatpak.git", 49 git_clone_dir, 50 ], 51 check=True, 52 ) 53 shutil.copyfile( 54 os.path.join( 55 git_clone_dir, "desktop/flatpak/distribution/distribution.ini" 56 ), 57 target, 58 ) 59 60 61 def _langpack_manifest(xpi): 62 with zipfile.ZipFile(xpi) as f: 63 return json.load(f.open("manifest.json")) 64 65 66 def _render_template(source, dest, variables): 67 if source.endswith(".in"): 68 with open(source) as f: 69 template = Template(f.read()) 70 with open(dest[:-3], "w") as f: 71 f.write(template.substitute(variables)) 72 else: 73 shutil.copy(source, dest) 74 75 76 def _render_flatpak_templates(template_dir, build_dir, variables): 77 for root, dirs, files in os.walk(template_dir): 78 relative = os.path.relpath(root, template_dir) 79 for d in dirs: 80 os.makedirs(build_dir / relative / d, exist_ok=True) 81 for f in files: 82 _render_template( 83 os.path.join(root, f), os.path.join(build_dir, relative, f), variables 84 ) 85 86 87 def repackage_flatpak( 88 log, 89 infile, 90 output, 91 arch, 92 version, 93 product, 94 release_type, 95 flatpak_name, 96 flatpak_branch, 97 template_dir, 98 langpack_pattern, 99 ): 100 with tempfile.TemporaryDirectory() as tmpdir: 101 tmpdir = Path(tmpdir) 102 build_dir = tmpdir / "build" 103 app_dir = build_dir / "files" 104 lib_dir = app_dir / "lib" 105 106 # Fetch and install the base app 107 run_command( 108 log, 109 [ 110 "flatpak", 111 "remote-add", 112 "--user", 113 "--if-not-exists", 114 "--from", 115 "flathub", 116 "https://dl.flathub.org/repo/flathub.flatpakrepo", 117 ], 118 check=True, 119 ) 120 run_command( 121 log, 122 [ 123 "flatpak", 124 "install", 125 "--user", 126 "-y", 127 "flathub", 128 f"{FIREFOX_BASEAPP}/{arch}/{FIREFOX_BASEAPP_CHANNEL}", 129 "--no-deps", 130 ], 131 check=True, 132 ) 133 # Copy files from the base app to our build dir 134 base = ( 135 Path.home() 136 / f".local/share/flatpak/app/{FIREFOX_BASEAPP}/{arch}/{FIREFOX_BASEAPP_CHANNEL}/active/files" 137 ) 138 shutil.copytree(base, app_dir, symlinks=True) 139 140 # Extract our build to the app dir 141 lib_dir.mkdir(exist_ok=True) 142 run_command( 143 log, ["tar", "xf", os.path.abspath(infile)], cwd=lib_dir, check=True 144 ) 145 146 if product == "firefox": 147 distribution_ini = lib_dir / "firefox" / "distribution" / "distribution.ini" 148 distribution_ini.parent.mkdir(parents=True) 149 _inject_flatpak_distribution_ini(log, distribution_ini) 150 151 application_ini_data = application_ini_data_from_directory(str(lib_dir)) 152 variables = get_build_variables(application_ini_data, arch, version) 153 variables.update({ 154 "FREEDESKTOP_VERSION": FREEDESKTOP_VERSION, 155 "FIREFOX_BASEAPP_CHANNEL": FIREFOX_BASEAPP_CHANNEL, 156 "FLATPAK_BRANCH": flatpak_branch, 157 "DATE": variables["TIMESTAMP"].strftime("%Y-%m-%d"), 158 # Override PKG_NAME since we use branches for beta vs release 159 "PKG_NAME": product, 160 "DBusActivatable": "false", 161 # Override Icon to match the flatpak's name 162 "Icon": flatpak_name, 163 }) 164 _render_flatpak_templates(template_dir, build_dir, variables) 165 166 from fluent.runtime.fallback import FluentLocalization, FluentResourceLoader 167 168 from mozbuild.repackaging.desktop_file import generate_browser_desktop_entry 169 170 desktop = generate_browser_desktop_entry( 171 log, 172 variables, 173 product, 174 release_type, 175 FluentLocalization, 176 FluentResourceLoader, 177 ) 178 desktop_dir = app_dir / "share" / "applications" 179 desktop_dir.mkdir(parents=True, exist_ok=True) 180 desktop_file_name = desktop_dir / f"{flatpak_name}.desktop" 181 with desktop_file_name.open("w") as f: 182 for line in desktop: 183 print(line, file=f) 184 185 if product == "firefox": 186 icon_path = "lib/firefox/browser/chrome/icons/default" 187 elif product == "thunderbird": 188 icon_path = "lib/thunderbird/chrome/icons/default" 189 else: 190 raise NotImplementedError() 191 192 for size in (16, 32, 48, 64, 128): 193 os.makedirs( 194 app_dir / f"share/icons/hicolor/{size}x{size}/apps", exist_ok=True 195 ) 196 shutil.copy( 197 app_dir / icon_path / f"default{size}.png", 198 app_dir / f"share/icons/hicolor/{size}x{size}/apps/{flatpak_name}.png", 199 ) 200 201 run_command( 202 log, 203 [ 204 "appstream-compose", 205 f"--prefix={app_dir}", 206 "--origin=flatpak", 207 f"--basename={flatpak_name}", 208 flatpak_name, 209 ], 210 check=True, 211 ) 212 run_command( 213 log, 214 [ 215 "appstream-util", 216 "mirror-screenshots", 217 f"{app_dir}/share/app-info/xmls/{flatpak_name}.xml.gz", 218 f"https://dl.flathub.org/repo/screenshots/{flatpak_name}-{flatpak_branch}", 219 "build/screenshots", 220 f"build/screenshots/{flatpak_name}-{flatpak_branch}", 221 ], 222 check=True, 223 cwd=tmpdir, 224 ) 225 226 os.makedirs(app_dir / f"lib/{product}/distribution/extensions", exist_ok=True) 227 for langpack in glob.iglob(langpack_pattern): 228 manifest = _langpack_manifest(langpack) 229 locale = manifest["langpack_id"] 230 name = manifest["browser_specific_settings"]["gecko"]["id"] 231 232 lang = locale.split("-", 1)[0] 233 os.makedirs(app_dir / "share/runtime/langpack" / lang, exist_ok=True) 234 shutil.copy( 235 langpack, app_dir / "share/runtime/langpack" / lang / f"{name}.xpi" 236 ) 237 os.symlink( 238 f"/app/share/runtime/langpack/{lang}/{name}.xpi", 239 app_dir / f"lib/{product}/distribution/extensions/{name}.xpi", 240 ) 241 242 run_command( 243 log, 244 [ 245 "flatpak", 246 "build-finish", 247 "build", 248 "--allow=devel", 249 "--share=ipc", 250 "--share=network", 251 "--socket=pulseaudio", 252 "--socket=wayland", 253 "--socket=fallback-x11", 254 "--socket=pcsc", 255 "--socket=cups", 256 "--require-version=0.11.1", 257 "--persist=.mozilla", 258 "--env=DICPATH=/usr/share/hunspell", 259 "--filesystem=xdg-config/gtk-3.0:ro", 260 "--filesystem=xdg-download:rw", 261 "--filesystem=/run/.heim_org.h5l.kcm-socket", 262 "--filesystem=xdg-run/speech-dispatcher:ro", 263 "--device=all", 264 "--talk-name=org.freedesktop.FileManager1", 265 "--system-talk-name=org.freedesktop.NetworkManager", 266 "--talk-name=org.a11y.Bus", 267 "--talk-name=org.gtk.vfs.*", 268 "--own-name=org.mpris.MediaPlayer2.firefox.*", 269 "--own-name=org.mozilla.firefox.*", 270 "--own-name=org.mozilla.firefox_beta.*", 271 "--command=firefox", 272 ], 273 check=True, 274 cwd=tmpdir, 275 ) 276 277 run_command( 278 log, 279 ["find", "build"], 280 check=True, 281 cwd=tmpdir, 282 ) 283 284 run_command( 285 log, 286 [ 287 "flatpak", 288 "build-export", 289 f"--arch={arch}", 290 "--disable-sandbox", 291 "--no-update-summary", 292 "--exclude=/share/runtime/langpack/*/*", 293 "repo", 294 "build", 295 flatpak_branch, 296 ], 297 check=True, 298 cwd=tmpdir, 299 ) 300 run_command( 301 log, 302 [ 303 "flatpak", 304 "build-export", 305 f"--arch={arch}", 306 "--disable-sandbox", 307 "--no-update-summary", 308 "--metadata=metadata.locale", 309 "--files=files/share/runtime/langpack", 310 "repo", 311 "build", 312 flatpak_branch, 313 ], 314 check=True, 315 cwd=tmpdir, 316 ) 317 run_command( 318 log, 319 [ 320 "ostree", 321 "commit", 322 "--repo=repo", 323 "--canonical-permissions", 324 f"--branch=screenshots/{arch}", 325 "build/screenshots", 326 ], 327 check=True, 328 cwd=tmpdir, 329 ) 330 run_command( 331 log, 332 ["flatpak", "build-update-repo", "--generate-static-deltas", "repo"], 333 check=True, 334 cwd=tmpdir, 335 ) 336 env = os.environ.copy() 337 env["XZ_OPT"] = "-e9" 338 run_command( 339 log, 340 ["tar", "cvfJ", os.path.abspath(output), "repo"], 341 check=True, 342 env=env, 343 cwd=tmpdir, 344 )