pitivi-flatpak 28.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
#!/usr/bin/env python3
# Pitivi video editor
# Copyright (c) 2016, Thibault Saunier <tsaunier@gnome.org>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this program; if not, write to the
# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
# Boston, MA 02110-1301, USA.
# pylint: disable=missing-docstring,invalid-name
import argparse
import configparser
import json
import os
24
import re
25
import shlex
26 27 28 29
import shutil
import subprocess
import sys
import tempfile
30
import venv
31
from urllib.error import URLError
32 33 34
from urllib.parse import urlparse
from urllib.request import urlretrieve

35 36
# The default branch is master because the script is used most often
# for development.
37
PITIVI_BRANCH = "master"
38
MANIFEST_NAME = "org.pitivi.Pitivi.json"
39 40 41 42
FLATPAK_REQ = [
    ("flatpak", "0.10.0"),
    ("flatpak-builder", "0.10.0"),
]
43
FLATPAK_VERSION = {}
44
DEFAULT_GST_BRANCH = 'master'
45 46 47 48 49 50 51 52 53 54 55


class Colors:  # pylint: disable=too-few-public-methods
    HEADER = "\033[95m"
    OKBLUE = "\033[94m"
    OKGREEN = "\033[92m"
    WARNING = "\033[93m"
    FAIL = "\033[91m"
    ENDC = "\033[0m"


56 57 58 59 60 61 62 63 64 65 66 67 68
class Console:  # pylint: disable=too-few-public-methods

    quiet = False

    @classmethod
    def message(cls, str_format, *args):
        if cls.quiet:
            return

        if args:
            print(str_format % args)
        else:
            print(str_format)
69

70 71 72
        # Flush so that messages are printed at the right time
        # as we use many subprocesses.
        sys.stdout.flush()
73 74


75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
def remove_comments(string):
    pattern = r"(\".*?\"|\'.*?\')|(/\*.*?\*/|//[^\r\n]*$)"
    # first group captures quoted strings (double or single)
    # second group captures comments (//single-line or /* multi-line */)
    regex = re.compile(pattern, re.MULTILINE | re.DOTALL)

    def _replacer(match):
        # if the 2nd group (capturing comments) is not None,
        # it means we have captured a non-quoted (real) comment string.
        if match.group(2) is not None:
            return ""  # so we will return empty to remove the comment

        return match.group(1)  # captured quoted-string
    return regex.sub(_replacer, string)


def load_manifest(manifest_path):
    with open(manifest_path, "r") as mr:
        contents = mr.read()
        contents = remove_comments(contents)
        manifest = json.loads(contents)

    return manifest


def expand_manifest(manifest_path, outfile, basedir, gst_version, branchname):
101 102 103 104 105 106
    """Creates the manifest file."""
    try:
        os.remove(outfile)
    except FileNotFoundError:
        pass

107
    template = load_manifest(manifest_path)
108 109 110 111 112 113 114 115 116 117
    if branchname == "stable":
        try:
            del template["desktop-file-name-prefix"]
        except KeyError:
            pass
    elif branchname == "master":
        template["desktop-file-name-prefix"] = "(Rolling) "
    else:
        template["desktop-file-name-prefix"] = "(%s) " % branchname

118 119
    Console.message("-> Generating %s against GStreamer %s",
                    outfile, gst_version)
120 121 122 123 124 125 126

    for module in template["modules"]:
        if module["sources"][0]["type"] != "git":
            continue

        if module["name"].startswith("gst"):
            module["sources"][0]["branch"] = gst_version
127
            if gst_version != DEFAULT_GST_BRANCH:
128 129 130 131
                continue

        repo = os.path.join(basedir, module["name"])
        if not os.path.exists(os.path.join(repo, ".git")):
132 133
            Console.message("-> Module: %s using online repo: %s",
                            module["name"], module["sources"][0]["url"])
134 135 136 137 138 139 140 141
            continue

        branch = subprocess.check_output(
            r"git branch 2>&1 | grep \*", shell=True,
            cwd=repo).decode(
                "utf-8").split(" ")[1].strip("\n")

        repo = "file://" + repo
142 143
        Console.message("-> Module: %s repo: %s branch: %s",
                        module["name"], repo, branch)
144 145 146 147 148 149 150
        module["sources"][0]["url"] = repo
        module["sources"][0]["branch"] = branch

    with open(outfile, "w") as of:
        print(json.dumps(template, indent=4), file=of)


151 152 153 154 155 156 157 158
def check_flatpak(verbose=True):
    for app, required_version in FLATPAK_REQ:
        try:
            output = subprocess.check_output([app, "--version"])
        except (subprocess.CalledProcessError, OSError):
            if verbose:
                Console.message("\n%sYou need to install %s >= %s"
                                " to be able to use the '%s' script.\n\n"
luz.paz's avatar
luz.paz committed
159
                                "You can find some information about"
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
                                " how to install it for your distribution at:\n"
                                "    * http://flatpak.org/%s\n", Colors.FAIL,
                                app, required_version, sys.argv[0], Colors.ENDC)
            return False

        def comparable_version(version):
            return tuple(map(int, (version.split("."))))

        version = output.decode("utf-8").split(" ")[1].strip("\n")
        current = comparable_version(version)
        FLATPAK_VERSION[app] = current
        if current < comparable_version(required_version):
            Console.message("\n%s%s %s required but %s found."
                            " Please update and try again%s\n", Colors.FAIL,
                            app, required_version, version, Colors.ENDC)
            return False

    return True


180 181 182 183 184 185 186
class FlatpakObject:  # pylint: disable=too-few-public-methods

    def __init__(self, user):
        self.user = user

    def flatpak(self, command, *args, show_output=False, comment=None):
        if comment:
187
            Console.message(comment)
188 189 190 191 192 193 194 195

        command = ["flatpak", command]
        if self.user:
            res = subprocess.check_output(command + ["--help"]).decode("utf-8")
            if "--user" in res:
                command.append("--user")
        command.extend(args)

196 197
        if comment:
            Console.message(" ".join(command))
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
        if not show_output:
            return subprocess.check_output(command).decode("utf-8")

        return subprocess.check_call(command)


class FlatpakPackages(FlatpakObject):  # pylint: disable=too-few-public-methods

    def __init__(self, repos, user=True):
        FlatpakObject.__init__(self, user=user)

        self.repos = repos

        self.runtimes = self.__detect_runtimes()
        self.apps = self.__detect_apps()
        self.packages = self.runtimes + self.apps

    def __detect_packages(self, *args):
        packs = []
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
        if FLATPAK_VERSION["flatpak"] < (1, 1, 2):
            out = self.flatpak("list", "-d", *args)
            package_defs = [line for line in out.split("\n") if line]
            for package_def in package_defs:
                splited_packaged_def = package_def.split()
                name, arch, branch = splited_packaged_def[0].split("/")

                # If installed from a file, the package is in no repo
                repo_name = splited_packaged_def[1]
                repo = self.repos.repos.get(repo_name)

                packs.append(FlatpakPackage(name, branch, repo, arch))
        else:
            out = self.flatpak("list", "--columns=application,arch,branch,origin", *args)
            package_defs = [line for line in out.split("\n") if line]
            for package_def in package_defs:
                name, arch, branch, origin = package_def.split("\t")
234

235 236
                # If installed from a file, the package is in no repo
                repo = self.repos.repos.get(origin)
237

238
                packs.append(FlatpakPackage(name, branch, repo, arch))
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256

        return packs

    def __detect_runtimes(self):
        return self.__detect_packages("--runtime")

    def __detect_apps(self):
        return self.__detect_packages()

    def __iter__(self):
        for package in self.packages:
            yield package


class FlatpakRepos(FlatpakObject):

    def __init__(self, user=True):
        FlatpakObject.__init__(self, user=user)
257
        # The remote repositories, name -> FlatpakRepo
258 259 260
        self.repos = {}
        self.update()

261
    # pylint: disable=too-many-branches
262 263
    def update(self):
        self.repos = {}
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286
        if FLATPAK_VERSION["flatpak"] < (1, 1, 2):
            out = self.flatpak("remote-list", "-d")
            remotes = [line for line in out.split("\n") if line]
            for repo in remotes:
                for components in [repo.split(" "), repo.split("\t")]:
                    if len(components) == 1:
                        components = repo.split("\t")
                    name = components[0]
                    desc = ""
                    url = None
                    for elem in components[1:]:
                        if not elem:
                            continue
                        parsed_url = urlparse(elem)
                        if parsed_url.scheme:
                            url = elem
                            break

                        if desc:
                            desc += " "
                        desc += elem

                    if url:
287 288
                        break

289 290 291
                if not url:
                    Console.message("No valid URI found for: %s", repo)
                    continue
292

293 294 295 296 297 298 299 300 301 302
                self.repos[name] = FlatpakRepo(name, url, desc, repos=self)
        else:
            out = self.flatpak("remote-list", "--columns=name,title,url")
            remotes = [line for line in out.split("\n") if line]
            for remote in remotes:
                name, title, url = remote.split("\t")
                parsed_url = urlparse(url)
                if not parsed_url.scheme:
                    Console.message("No valid URI found for: %s", remote)
                    continue
303

304
                self.repos[name] = FlatpakRepo(name, url, title, repos=self)
305 306 307

        self.packages = FlatpakPackages(self)

308 309
    def add(self, name, flatpakrepo_url, override=True):
        flatpakrepo = tempfile.NamedTemporaryFile(mode="w")
310 311 312 313 314 315 316
        try:
            urlretrieve(flatpakrepo_url, flatpakrepo.name)
            repo = configparser.ConfigParser()
            repo.read(flatpakrepo.name)
            url = repo["Flatpak Repo"]["Url"]
        except URLError:
            url = None
317

318
        same_name = None
319
        for tmpname, tmprepo in self.repos.items():
320 321 322
            # If the URL is None (meaning we couldn't retrieve Repo infos)
            # just check if the repo names match.
            if url == tmprepo.url or url is None and name == tmpname:
323
                return tmprepo
324
            if name == tmpname:
325 326 327
                same_name = tmprepo

        if same_name:
328
            Console.message("Flatpak remote with the same name already exists: %s", same_name)
329
            if not override:
330 331
                Console.message("%sNote the URL is %s, not %s%s",
                                Colors.WARNING, same_name.url, url, Colors.ENDC)
332 333
                return None

334 335
            Console.message("The URL is different. Overriding.")
            self.flatpak("remote-modify", name, "--url=" + url,
336
                         comment="Setting repo %s URL from %s to %s"
337 338
                         % (name, same_name.url, url))
            same_name.url = url
339 340
            return same_name

341 342 343 344 345
        self.flatpak("remote-add", "--if-not-exists", name,
                     "--from", flatpakrepo_url,
                     comment="Adding repo %s" % name)
        self.update()
        return self.repos[name]
346 347 348 349


class FlatpakRepo(FlatpakObject):  # pylint: disable=too-few-public-methods

350
    def __init__(self, name, url, desc=None, user=True, repos=None):  # pylint: disable=too-many-arguments
351 352 353
        FlatpakObject.__init__(self, user=user)

        self.name = name
354
        assert name
355 356
        self.desc = desc
        self.repos = repos
357
        self.url = url
358 359 360


class FlatpakPackage(FlatpakObject):
361
    """A flatpak app."""
362 363 364 365 366 367 368 369 370 371 372 373 374

    def __init__(self, name, branch, repo, arch, user=True):  # pylint: disable=too-many-arguments
        FlatpakObject.__init__(self, user=user)

        self.name = name
        self.branch = branch
        self.repo = repo
        self.arch = arch

    def __str__(self):
        return "%s/%s/%s %s" % (self.name, self.arch, self.branch, self.repo.name)

    def is_installed(self):
375 376 377 378
        if not self.repo:
            # Bundle installed from file
            return True

379 380 381 382 383 384 385 386 387
        if len(self.name.split(".")) <= 3:
            self.repo.repos.update()
            for package in self.repo.repos.packages:
                if package.name == self.name and \
                        package.arch == self.arch and \
                        package.branch == self.branch:
                    return True
        else:
            try:
388
                self.flatpak("info", self.name, self.branch)
389
                return True
390
            except subprocess.CalledProcessError:
391
                pass
392 393 394 395

        return False

    def install(self):
396
        if not self.repo:
397
            return
398

399
        self.flatpak("install", self.repo.name, self.name,
400
                     self.branch, "--reinstall", "-y", show_output=True,
401 402 403 404
                     comment="Installing %s" % self.name)

    def update(self):
        if not self.is_installed():
405 406
            self.install()
            return
407

408
        self.flatpak("update", self.name, self.branch, "-y", show_output=True,
409 410
                     comment="Updating %s" % self.name)

411 412
    def run_app(self, *args):
        """Starts the app represented by this instance."""
413 414
        self.flatpak("run", "--branch=" + self.branch, self.name, *args,
                     show_output=True,
415
                     comment="Running %s (%s)" % (self.name, self.branch))
416 417 418 419 420 421 422 423 424 425 426 427


class PitiviFlatpak:  # pylint: disable=too-many-instance-attributes

    def __init__(self):
        self.name = "Pitivi"
        self.sdk_repo = None
        self.runtime = None
        self.locale = None
        self.sdk = None

        self.packs = []
428
        self.init = False
429
        self.update = False
430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447
        self.json = None
        self.args = []
        self.build = False
        self.scriptdir = os.path.abspath(os.path.dirname(__file__))
        self.envpath = os.environ.get("FLATPAK_ENVPATH",
                                      os.path.expanduser("~/%s-flatpak" %
                                                         self.name.lower()))
        self.prefix = os.path.join(
            self.envpath, "%s-prefix" % self.name.lower())
        self.repodir = os.path.join(
            self.envpath, "flatpak-repos", self.name.lower())
        self.local_repos_path = os.path.abspath(os.path.join(
            self.scriptdir, os.pardir, os.pardir, os.pardir))
        self.topdir = os.path.abspath(os.path.join(
            self.scriptdir, os.pardir, os.pardir))

        self.build_name = self.name
        if os.path.exists(os.path.join(self.topdir, ".git")):
448
            devnull = open(os.devnull)
449
            try:
450 451 452
                branch = subprocess.check_output(
                    "git rev-parse --abbrev-ref HEAD".split(" "),
                    stderr=devnull,
453
                    cwd=self.topdir).decode("utf-8").strip("\n")
454
                self.build_name = self.name + "." + branch
455
                self.build_name = self.build_name.replace(os.path.sep, "_")
456 457 458 459 460
            except subprocess.CalledProcessError:
                pass
            finally:
                devnull.close()

461 462 463
        self.coredumpgdb = None
        self.coredumpctl_matches = ""

464
    def clean_args(self):
465 466
        Console.quiet = self.quiet

467
        if not check_flatpak():
468
            exit(1)
469 470

        repos = FlatpakRepos()
471
        self.sdk_repo = repos.add("flathub",
472
                                  "https://dl.flathub.org/repo/flathub.flatpakrepo")
473

474 475 476 477 478 479
        manifest_path = os.path.join(self.scriptdir, MANIFEST_NAME)
        manifest = load_manifest(manifest_path)
        if not manifest:
            return

        sdk_branch = manifest["runtime-version"]
480
        self.runtime = FlatpakPackage(
481
            "org.gnome.Platform", sdk_branch, self.sdk_repo, "x86_64")
482
        self.locale = FlatpakPackage(
483
            "org.gnome.Platform.Locale", sdk_branch, self.sdk_repo, "x86_64")
484
        self.sdk = FlatpakPackage(
485
            "org.gnome.Sdk", sdk_branch, self.sdk_repo, "x86_64")
486
        self.packs = [self.runtime, self.locale, self.sdk]
487

488 489 490
        if self.coredumpgdb is None and "--coredumpgdb" in sys.argv or "-gdb" in sys.argv:
            self.coredumpgdb = ""

491 492 493 494 495
        if self.bundle:
            self.build = True

        self.json = os.path.join(self.scriptdir, self.build_name + ".json")

496 497
        if not self.args:
            self.args.append(os.path.join(self.scriptdir, "enter-env"))
498

499 500
    def run(self):
        if self.clean:
501
            Console.message("Removing prefix")
502 503 504 505
            try:
                shutil.rmtree(self.prefix)
            except FileNotFoundError:
                pass
506

507 508
        missing_prefix = not os.path.exists(self.prefix)
        if self.init or self.update:
509
            self.install_flatpak_runtimes()
510 511
            if missing_prefix or self.update:
                self.setup_sandbox()
512
            self.install_development_tools()
513 514 515 516 517
        else:
            if missing_prefix:
                Console.message("%sPrefix missing, create it with: %s --init%s",
                                Colors.FAIL, __file__, Colors.ENDC)
                exit(1)
518

519 520 521 522
        if self.coredumpgdb is not None:
            self.run_gdb()
            return

523
        if self.check:
524
            self.run_in_sandbox("gst-validate-launcher",
525 526
                                os.path.join(
                                    self.topdir, "tests/ptv_testsuite.py"),
527
                                "--xunit-file", "xunit.xml", exit_on_failure=True,
528 529 530 531 532
                                cwd=self.topdir)

        if self.bundle:
            self.update_bundle()

533
        if not self.check and not self.init and not self.update and not self.bundle:
534 535
            assert self.args
            self.run_in_sandbox(*self.args, exit_on_failure=True)
536 537 538 539 540 541 542 543 544 545 546 547 548 549

    def update_bundle(self):
        if not os.path.exists(self.repodir):
            os.mkdir(self.repodir)

        build_export_args = ["flatpak",
                             "build-export", self.repodir, self.prefix]
        if self.gpg_key:
            build_export_args.append("--gpg-sign=%s" % self.gpg_key)
        if self.commit_subject:
            build_export_args.append("--subject=%s" % self.commit_subject)
        if self.commit_body:
            build_export_args.append("--body=%s" % self.commit_body)

550 551
        build_export_args.append(self.branch)

552 553 554
        Console.message('-> Exporting repo %s %s (--body="%s" --subject="%s")',
                        self.repodir, self.branch, self.commit_body,
                        self.commit_subject)
555 556 557
        try:
            subprocess.check_call(build_export_args)
        except subprocess.CalledProcessError:
558
            exit(1)
559 560 561 562 563 564 565 566

        update_repo_args = ["flatpak", "build-update-repo"]

        if self.generate_static_deltas:
            update_repo_args.append("--generate-static-deltas")

        update_repo_args.append(self.repodir)

567
        Console.message("Updating repo: '%s'", "' '".join(update_repo_args))
568 569 570
        try:
            subprocess.check_call(update_repo_args)
        except subprocess.CalledProcessError:
571
            exit(1)
572

573
    def setup_sandbox(self):
574 575 576
        """Creates and updates the sandbox."""
        Console.message("Building Pitivi %s and dependencies in %s",
                        self.branch, self.prefix)
577

578 579 580 581
        manifest_path = os.path.join(self.scriptdir, MANIFEST_NAME)
        expand_manifest(manifest_path, self.json,
                        self.local_repos_path, self.gst_version,
                        self.branch)
582

583 584 585 586
        builder_args = ["flatpak-builder", "--force-clean",
                        "--ccache", self.prefix, self.json]
        if not self.bundle:
            builder_args.append("--build-only")
587

588 589 590 591 592
        try:
            subprocess.check_call(["flatpak-builder", "--version"])
        except FileNotFoundError:
            Console.message("\n%sYou need to install flatpak-builder%s\n",
                            Colors.FAIL, Colors.ENDC)
593
            exit(1)
594 595 596 597 598 599 600 601
        subprocess.check_call(builder_args, cwd=self.scriptdir)

        if not os.path.isdir("mesonbuild/"):
            # Create the build directory.
            meson_args = ["meson", "mesonbuild/", "--prefix=/app",
                          "--libdir=lib", "-Ddisable_gtkdoc=true",
                          "-Ddisable_doc=true"]
            self.run_in_sandbox(*meson_args, exit_on_failure=True,
602 603
                                cwd=self.topdir)

604 605 606 607 608
        # Build the buildable parts of Pitivi.
        ninja_args = ["ninja", "-C", "mesonbuild/"]
        self.run_in_sandbox(*ninja_args, exit_on_failure=True,
                            cwd=self.topdir)

609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631
    def run_gdb(self):
        if not shutil.which("coredumpctl"):
            Console.message("%s'coredumpctl' not present on the system, can't run.%s"
                            % (Colors.WARNING, Colors.ENDC))
            sys.exit(1)

        # We need access to the host from the sandbox to run.
        with tempfile.NamedTemporaryFile() as coredump:
            with tempfile.NamedTemporaryFile() as stderr:
                subprocess.check_call(["coredumpctl", "dump"] + shlex.split(self.coredumpctl_matches),
                                      stdout=coredump, stderr=stderr)

                with open(stderr.name, 'r') as stderrf:
                    stderr = stderrf.read()
                executable, = re.findall(".*Executable: (.*)", stderr)
                if not executable.startswith("/newroot"):
                    print("Executable %s doesn't seem to be a flatpaked application." % executable,
                          file=sys.stderr)
                executable = executable.replace("/newroot", "")
                args = ["gdb", executable, coredump.name] + shlex.split(self.coredumpgdb)

                self.run_in_sandbox(*args, mount_tmp=True)

632
    def install_development_tools(self):
633
        venv_path = os.path.join(self.scriptdir, "pyvenv")
634 635
        Console.message(
            "Setting up a Python virtual env for development: %s", venv_path)
636 637 638
        if not os.path.isdir(venv_path):
            venv.create(venv_path, with_pip=True)

639
        activate_script = os.path.join(venv_path, "bin", "activate")
640
        pip_modules = "git_pep8_commit_hook git-pylint-commit-hook pre-commit pylint"
641 642
        Console.message(
            "Installing development tools in the Python virtual env: %s", pip_modules)
643
        subprocess.check_call(
644
            ". %s && pip install -q -U %s" % (activate_script, pip_modules),
645
            shell=True)
646

647 648 649 650
        # git-pylint-commit-hook must run pylint in the sandbox so it uses
        # the gi Python modules in there. At the same time it also has to be
        # installed in the development pyvenv which is outside the sandbox
        # because git-pylint-commit-hook needs to import pylint.config.
651
        pip_modules = ["pylint"]
652 653
        Console.message(
            "Installing development tools in the sandbox: %s", pip_modules)
654 655
        self.run_in_sandbox("pip3", "install", "-q", "-U", "--user",
                            "--no-warn-script-location", *pip_modules,
656
                            exit_on_failure=True)
657

658
    def run_in_sandbox(self, *args, exit_on_failure=False, cwd=None, mount_tmp=False):
659 660 661
        if not args:
            return

662 663
        flatpak_command = ["flatpak", "build", "--socket=x11", "--socket=wayland",
                           "--talk-name=org.freedesktop.Flatpak",
664 665
                           "--socket=session-bus", "--socket=pulseaudio",
                           "--share=network", "--env=PITIVI_DEVELOPMENT=1",
666
                           "--env=PYTHONUSERBASE=/app/",
667
                           "--env=CC=ccache gcc",
668
                           "--env=CXX=ccache g++", "--device=dri"]
669 670
        if mount_tmp:
            flatpak_command.append("--filesystem=/tmp/")
671 672 673 674

        # The forwarded environment variables.
        forwarded = {}
        for envvar, value in os.environ.items():
675
            if envvar.split("_")[0] in ("G", "GDK", "GST", "GTK", "LC", "PITIVI") or \
676 677 678 679 680
                    envvar in ["DISPLAY", "LANG"]:
                forwarded[envvar] = value

        prefixes = {
            "GST_ENCODING_TARGET_PATH":
681
                "/app/share/gstreamer-1.0/encoding-profiles/:/app/share/pitivi/encoding-profiles/",
682
            "GST_PLUGIN_SYSTEM_PATH": "/app/lib/gstreamer-1.0/",
683
            "FREI0R_PATH": "/app/lib/frei0r-1/",
684
            "GST_PRESET_PATH":
685 686
                "/app/share/gstreamer-1.0/presets/:/app/share/pitivi/gstpresets/",
        }
687 688 689 690 691 692 693 694
        for envvar, path in prefixes.items():
            value = forwarded.get(envvar, "")
            forwarded[envvar] = "%s:%s" % (path, value)

        for envvar, value in forwarded.items():
            flatpak_command.append("--env=%s=%s" % (envvar, value))

        flatpak_command.append(self.prefix)
695

696
        flatpak_command.extend(args)
697

698
        Console.message("Running in sandbox: %s", ' '.join(args))
699
        Console.message(" ".join(flatpak_command))
700
        try:
701
            subprocess.check_call(flatpak_command, cwd=cwd)
702 703
        except subprocess.CalledProcessError as e:
            if exit_on_failure:
704
                exit(e.returncode)
705

706 707 708 709 710
    def install_flatpak_runtimes(self):
        for runtime in self.packs:
            if not runtime.is_installed():
                runtime.install()
            else:
711 712 713
                # Update only if requested.
                if self.update:
                    runtime.update()
714

715

716
if __name__ == "__main__":
717 718
    app_flatpak = PitiviFlatpak()

719
    parser = argparse.ArgumentParser(prog="pitivi-flatpak")
720 721

    general = parser.add_argument_group("General")
722 723 724
    general.add_argument("--init", dest="init",
                         action="store_true",
                         help="Initialize the runtime/sdk/app and build the development environment if needed")
725 726
    general.add_argument("--update", dest="update",
                         action="store_true",
727
                         help="Update the runtime/sdk/app and rebuild the development environment")
728 729 730
    general.add_argument("-q", "--quiet", dest="quiet",
                         action="store_true",
                         help="Do not print anything")
731 732
    general.add_argument("args",
                         nargs=argparse.REMAINDER,
733
                         help="The command to run in the sandbox")
734

735 736 737 738 739 740
    devel = parser.add_argument_group("Development")
    devel.add_argument("--branch", dest="branch",
                       help="The flatpak branch to use (stable, master...)",
                       default="master")
    devel.add_argument("--gst-version", dest="gst_version",
                       help="The GStreamer version to build.",
741
                       default=DEFAULT_GST_BRANCH)
742 743 744 745 746 747 748
    devel.add_argument("--check", dest="check",
                       help="Run unit tests once the build is done.",
                       action="store_true")
    devel.add_argument("-c", "--clean", dest="clean",
                       action="store_true",
                       help="Clean previous builds and restart from scratch")

749 750 751 752 753 754
    debug_options = parser.add_argument_group("Debugging")
    debug_options.add_argument("-gdb", "--coredumpgdb", nargs="?",
                               help="Activate gdb, passing extra args to it if wanted.")
    debug_options.add_argument("-m", "--coredumpctl-matches", default="",
                               help="Arguments to pass to gdb.")

755 756 757 758 759 760 761 762 763 764 765 766 767 768 769
    bundling = parser.add_argument_group("Building bundle for distribution")
    bundling.add_argument("--bundle", dest="bundle",
                          action="store_true",
                          help="Create bundle repository, implies --build")
    bundling.add_argument(
        "--repo-commit-subject", dest="commit_subject", default=None,
        help="The commit subject to be used when updating the ostree repository")
    bundling.add_argument(
        "--repo-commit-body", dest="commit_body", default=None,
        help="The commit body to be used when updating the ostree repository")
    bundling.add_argument(
        "--gpg-sign", dest="gpg_key", default=None,
        help="The GPG key to sign the commit with (work only when --bundle is used)")
    bundling.add_argument(
        "--generate-static-deltas", dest="generate_static_deltas",
770
        action="store_true",
771
        help="Generate static deltas (check 'man flatpak-build-update-repo'"
772
        " for more information)")
773

774 775
    parser.parse_args(namespace=app_flatpak)
    app_flatpak.clean_args()
776
    app_flatpak.run()