tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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        )