[WPE][GTK] Unreviewed, fix a pervasive typo in the webkit-flatpak script
[WebKit-https.git] / Tools / flatpak / flatpakutils.py
1 # Copyright (C) 2017 Igalia S.L.
2 #
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU Lesser General Public
5 # License as published by the Free Software Foundation; either
6 # version 2.1 of the License, or (at your option) any later version.
7 #
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 # Lesser General Public License for more details.
12 #
13 # You should have received a copy of the GNU Lesser General Public
14 # License along with this program; if not, write to the
15 # Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
16 # Boston, MA 02110-1301, USA.
17 import argparse
18 import logging
19 try:
20     import configparser
21 except ImportError:
22     import ConfigParser as configparser
23 from contextlib import contextmanager
24 import errno
25 import json
26 import os
27 import shlex
28 import shutil
29 import signal
30 import subprocess
31 import sys
32 import tempfile
33 import re
34
35 from webkitpy.common.system.systemhost import SystemHost
36 from webkitpy.port.factory import PortFactory
37 from webkitpy.common.system.logutils import configure_logging
38
39 try:
40     from urllib.parse import urlparse  # pylint: disable=E0611
41 except ImportError:
42     from urlparse import urlparse
43
44 try:
45     from urllib.request import urlopen  # pylint: disable=E0611
46 except ImportError:
47     from urllib2 import urlopen
48
49 FLATPAK_REQ = [
50     ("flatpak", "0.10.0"),
51     ("flatpak-builder", "0.10.0"),
52 ]
53
54 scriptdir = os.path.abspath(os.path.dirname(__file__))
55 _log = logging.getLogger(__name__)
56
57
58 class Colors:
59     HEADER = "\033[95m"
60     OKBLUE = "\033[94m"
61     OKGREEN = "\033[92m"
62     WARNING = "\033[93m"
63     FAIL = "\033[91m"
64     ENDC = "\033[0m"
65
66
67 class Console:
68
69     quiet = False
70
71     @classmethod
72     def message(cls, str_format, *args):
73         if cls.quiet:
74             return
75
76         if args:
77             print(str_format % args)
78         else:
79             print(str_format)
80
81         # Flush so that messages are printed at the right time
82         # as we use many subprocesses.
83         sys.stdout.flush()
84
85
86 def check_flatpak(verbose=True):
87     # Flatpak is only supported on Linux.
88     if not sys.platform.startswith("linux"):
89         return False
90
91     for app, required_version in FLATPAK_REQ:
92         try:
93             output = subprocess.check_output([app, "--version"])
94         except (subprocess.CalledProcessError, OSError):
95             if verbose:
96                 Console.message("\n%sYou need to install %s >= %s"
97                                 " to be able to use the '%s' script.\n\n"
98                                 "You can find some informations about"
99                                 " how to install it for your distribution at:\n"
100                                 "    * http://flatpak.org/%s\n", Colors.FAIL,
101                                 app, required_version, sys.argv[0], Colors.ENDC)
102             return False
103
104         def comparable_version(version):
105             return tuple(map(int, (version.split("."))))
106
107         version = output.decode("utf-8").split(" ")[1].strip("\n")
108         if comparable_version(version) < comparable_version(required_version):
109             Console.message("\n%s%s %s required but %s found."
110                             " Please update and try again%s\n", Colors.FAIL,
111                             app, required_version, version, Colors.ENDC)
112             return False
113
114     return True
115
116
117 def remove_extension_points(array):
118     result_args = []
119     for arg in array:
120         if(not arg.startswith('--extension')):
121             result_args.append(arg)
122     return result_args
123
124
125 def remove_comments(string):
126     pattern = r"(\".*?\"|\'.*?\')|(/\*.*?\*/|//[^\r\n]*$)"
127     # first group captures quoted strings (double or single)
128     # second group captures comments (//single-line or /* multi-line */)
129     regex = re.compile(pattern, re.MULTILINE | re.DOTALL)
130
131     def _replacer(match):
132         # if the 2nd group (capturing comments) is not None,
133         # it means we have captured a non-quoted (real) comment string.
134         if match.group(2) is not None:
135             return ""  # so we will return empty to remove the comment
136         else:  # otherwise, we will return the 1st group
137             return match.group(1)  # captured quoted-string
138     return regex.sub(_replacer, string)
139
140
141 def load_manifest(manifest_path, port_name=None, command=None):
142     is_yaml = manifest_path.endswith('.yaml')
143     with open(manifest_path, "r") as mr:
144         contents = mr.read()
145
146         contents = contents % {"COMMAND": command, "PORTNAME": port_name}
147         if is_yaml:
148             import yaml
149
150             manifest = yaml.load(contents)
151         else:
152             contents = remove_comments(contents)
153             manifest = json.loads(contents)
154
155     return manifest
156
157
158 def expand_manifest(manifest_path, outfile, port_name, source_root, command):
159     """Creates the manifest file."""
160     try:
161         os.remove(outfile)
162     except OSError:
163         pass
164
165     manifest = load_manifest(manifest_path, port_name, command)
166     if not manifest:
167         return False
168
169     if "sdk-hash" in manifest:
170         del manifest["sdk-hash"]
171     if "runtime-hash" in manifest:
172         del manifest["runtime-hash"]
173     i = 0
174     all_modules = []
175
176     overriden_modules = []
177     if "WEBKIT_EXTRA_MODULESETS" in os.environ:
178         overriden_modules = load_manifest(os.environ["WEBKIT_EXTRA_MODULESETS"])
179         if not overriden_modules:
180             overriden_modules = []
181     for modules in manifest["modules"]:
182         submanifest_path = None
183         if type(modules) is str:
184             submanifest_path = os.path.join(os.path.dirname(manifest_path), modules)
185             modules = load_manifest(submanifest_path, port_name, command)
186
187         if not isinstance(modules, list):
188             modules = [modules]
189
190         for module in modules:
191             for overriden_module in overriden_modules:
192                 if module['name'] == overriden_module['name']:
193                     module = overriden_module
194                     overriden_modules.remove(module)
195                     break
196
197             all_modules.append(module)
198
199     # And add overriden modules right before the webkit port build def.
200     for overriden_module in overriden_modules:
201         all_modules.insert(-1, overriden_module)
202
203     manifest["modules"] = all_modules
204     for module in manifest["modules"]:
205         if not module.get("sources"):
206             continue
207
208         if module["sources"][0]["type"] == "git":
209             if port_name == module["name"]:
210                 repo = "file://" + source_root
211                 module["sources"][0]["url"] = repo
212
213         for source in module["sources"]:
214             if source["type"] == "patch" or (source["type"] == "file" and source.get('path')):
215                 source["path"] = os.path.join(os.path.dirname(manifest_path), source["path"])
216         i += 1
217
218     with open(outfile, "w") as of:
219         of.write(json.dumps(manifest, indent=4))
220
221     return True
222
223
224 class FlatpakObject:
225
226     def __init__(self, user):
227         self.user = user
228
229     def flatpak(self, command, *args, **kwargs):
230         show_output = kwargs.pop("show_output", False)
231         comment = kwargs.pop("commend", None)
232         if comment:
233             Console.message(comment)
234
235         command = ["flatpak", command]
236         if self.user:
237             res = subprocess.check_output(command + ["--help"]).decode("utf-8")
238             if "--user" in res:
239                 command.append("--user")
240         command.extend(args)
241
242         if not show_output:
243             return subprocess.check_output(command).decode("utf-8")
244
245         return subprocess.check_call(command)
246
247
248 class FlatpakPackages(FlatpakObject):
249
250     def __init__(self, repos, user=True):
251         FlatpakObject.__init__(self, user=user)
252
253         self.repos = repos
254
255         self.runtimes = self.__detect_runtimes()
256         self.apps = self.__detect_apps()
257         self.packages = self.runtimes + self.apps
258
259     def __detect_packages(self, *args):
260         packs = []
261         package_defs = [rd
262                         for rd in self.flatpak("list", "-d", "--all", *args).split("\n")
263                         if rd]
264         for package_def in package_defs:
265             splited_packaged_def = package_def.split()
266             name, arch, branch = splited_packaged_def[0].split("/")
267
268             # If installed from a file, the package is in no repo
269             repo_name = splited_packaged_def[1]
270             repo = self.repos.repos.get(repo_name)
271
272             packs.append(FlatpakPackage(name, branch, repo, arch))
273
274         return packs
275
276     def __detect_runtimes(self):
277         return self.__detect_packages("--runtime")
278
279     def __detect_apps(self):
280         return self.__detect_packages()
281
282     def __iter__(self):
283         for package in self.packages:
284             yield package
285
286
287 class FlatpakRepos(FlatpakObject):
288
289     def __init__(self, user=True):
290         FlatpakObject.__init__(self, user=user)
291         self.repos = {}
292         self.update()
293
294     def update(self):
295         self.repos = {}
296         remotes = [row
297                    for row in self.flatpak("remote-list", "-d").split("\n")
298                    if row]
299         for repo in remotes:
300             for components in [repo.split(" "), repo.split("\t")]:
301                 if len(components) == 1:
302                     components = repo.split("\t")
303                 name = components[0]
304                 desc = ""
305                 url = None
306                 for elem in components[1:]:
307                     if not elem:
308                         continue
309                     parsed_url = urlparse(elem)
310                     if parsed_url.scheme:
311                         url = elem
312                         break
313
314                     if desc:
315                         desc += " "
316                     desc += elem
317
318                 if url:
319                     break
320
321             if not url:
322                 Console.message("No valid URI found for: %s", repo)
323                 continue
324
325             self.repos[name] = FlatpakRepo(name, url, desc, repos=self)
326
327         self.packages = FlatpakPackages(self)
328
329     def add(self, repo, override=True):
330         same_name = None
331         for name, tmprepo in self.repos.items():
332             if repo.url == tmprepo.url:
333                 return tmprepo
334             elif repo.name == name:
335                 same_name = tmprepo
336
337         if same_name:
338             if override:
339                 self.flatpak("remote-modify", repo.name, "--url=" + repo.url,
340                              comment="Setting repo %s URL from %s to %s"
341                              % (repo.name, same_name.url, repo.url))
342                 same_name.url = repo.url
343
344                 return same_name
345             else:
346                 return None
347         else:
348             self.flatpak("remote-add", repo.name, "--from", repo.repo_file.name,
349                          "--if-not-exists",
350                          comment="Adding repo %s" % repo.name)
351
352         repo.repos = self
353         return repo
354
355
356 class FlatpakRepo(FlatpakObject):
357
358     def __init__(self, name, desc=None, url=None,
359                  repo_file=None, user=True, repos=None):
360         FlatpakObject.__init__(self, user=user)
361
362         self.name = name
363         self.url = url
364         self.desc = desc
365         self.repo_file_name = repo_file
366         self._repo_file = None
367         self.repos = repos
368         assert name
369         if repo_file and not url:
370             repo = configparser.ConfigParser()
371             repo.read(self.repo_file.name)
372             self.url = repo["Flatpak Repo"]["Url"]
373         else:
374             assert url
375
376     @property
377     def repo_file(self):
378         if self._repo_file:
379             return self._repo_file
380
381         assert self.repo_file_name
382         self._repo_file = tempfile.NamedTemporaryFile(mode="wb")
383         self._repo_file.write(urlopen(self.repo_file_name).read())
384         self._repo_file.flush()
385
386         return self._repo_file
387
388
389 class FlatpakPackage(FlatpakObject):
390     """A flatpak app."""
391
392     def __init__(self, name, branch, repo, arch, user=True, hash=None, assumeyes=False):
393         FlatpakObject.__init__(self, user=user)
394
395         self.name = name
396         self.branch = str(branch)
397         self.repo = repo
398         self.arch = arch
399         self.hash = hash
400         self.assumeyes = assumeyes
401
402     def __str__(self):
403         return "%s/%s/%s %s" % (self.name, self.arch, self.branch, self.repo.name)
404
405     def is_installed(self, branch):
406         if not self.repo:
407             # Bundle installed from file
408             return True
409
410         self.repo.repos.update()
411         for package in self.repo.repos.packages:
412             if package.name == self.name and \
413                     package.branch == branch and \
414                     package.arch == self.arch:
415                 return True
416
417         return False
418
419     def install(self):
420         if not self.repo:
421             return False
422
423         args = ["install", self.repo.name, self.name, "--reinstall", self.branch]
424
425         if self.assumeyes:
426             args.append("--assumeyes")
427
428         self.flatpak(*args, show_output=True,
429                      comment="Installing from " + self.repo.name + " " +
430                              self.name + " " + self.arch + " " + self.branch)
431
432     def update(self):
433         if not self.is_installed(self.branch):
434             return self.install()
435
436         extra_args = []
437         comment = "Updating %s" % self.name
438         if self.hash:
439             extra_args = ["--commit", self.hash]
440             comment += " to %s" % self.hash
441
442         if self.assumeyes:
443             extra_args.append("--assumeyes")
444
445         self.flatpak("update", self.name, self.branch, show_output=True,
446                     *extra_args, comment=comment)
447
448
449 @contextmanager
450 def disable_signals(signals=[signal.SIGINT]):
451     old_signal_handlers = []
452
453     for disabled_signal in signals:
454         old_signal_handlers.append((disabled_signal, signal.getsignal(disabled_signal)))
455         signal.signal(disabled_signal, signal.SIG_IGN)
456
457     yield
458
459     for disabled_signal, previous_handler in old_signal_handlers:
460         signal.signal(disabled_signal, previous_handler)
461
462
463 class WebkitFlatpak:
464
465     @staticmethod
466     def load_from_args(args=None):
467         self = WebkitFlatpak()
468
469         parser = argparse.ArgumentParser(prog="webkit-flatpak")
470         general = parser.add_argument_group("General")
471         general.add_argument('--verbose', action='store_true',
472                              help='Show debug message')
473         general.add_argument("--debug",
474                             help="Compile with Debug configuration, also installs Sdk debug symboles.",
475                             action="store_true")
476         general.add_argument("--release", help="Compile with Release configuration.", action="store_true")
477         general.add_argument('--platform', action='store', help='Platform to use (e.g., "mac-lion")'),
478         general.add_argument('--gtk', action='store_const', dest='platform', const='gtk',
479                              help='Alias for --platform=gtk')
480         general.add_argument('--wpe', action='store_const', dest='platform', const='wpe',
481                             help=('Alias for --platform=wpe'))
482         general.add_argument("-nf", "--no-flatpak-update", dest="no_flatpak_update",
483                             action="store_true",
484                             help="Do not update flaptak runtime/sdk")
485         general.add_argument("-u", "--update", dest="update",
486                             action="store_true",
487                             help="Update the runtime/sdk/app and rebuild the development environment if needed")
488         general.add_argument("-b", "--build-webkit", dest="build_webkit",
489                             action="store_true",
490                             help="Force rebuilding the app.")
491         general.add_argument("-ba", "--build-all", dest="build_all",
492                             action="store_true",
493                             help="Force rebuilding the app and its dependencies.")
494         general.add_argument("-q", "--quiet", dest="quiet",
495                             action="store_true",
496                             help="Do not print anything")
497         general.add_argument("-t", "--tests", dest="run_tests",
498                             nargs=argparse.REMAINDER,
499                             help="Run LayoutTests")
500         general.add_argument("-c", "--command",
501                             nargs=argparse.REMAINDER,
502                             help="The command to run in the sandbox",
503                             dest="user_command")
504         general.add_argument("-y", "--assumeyes",
505                             help="Automatically answer yes for all questions.",
506                             action="store_true")
507         general.add_argument('--available', action='store_true', dest="check_available", help='Check if required dependencies are available.'),
508
509         debugoptions = parser.add_argument_group("Debugging")
510         debugoptions.add_argument("--gdb", nargs="?", help="Activate gdb, passing extra args to it if wanted.")
511         debugoptions.add_argument("-m", "--coredumpctl-matches", default="", help='Arguments to pass to gdb.')
512
513         buildoptions = parser.add_argument_group("Extra build arguments")
514         buildoptions.add_argument("--makeargs", help="Optional Makefile flags")
515         buildoptions.add_argument("--cmakeargs",
516                                 help="One or more optional CMake flags (e.g. --cmakeargs=\"-DFOO=bar -DCMAKE_PREFIX_PATH=/usr/local\")")
517
518         general.add_argument("--clean", dest="clean", action="store_true",
519             help="Clean previous builds and restart from scratch")
520
521         _, self.args = parser.parse_known_args(args=args, namespace=self)
522
523         return self
524
525     def __init__(self):
526         self.sdk_repo = None
527         self.runtime = None
528         self.locale = None
529         self.sdk = None
530         self.sdk_debug = None
531         self.app = None
532
533         self.verbose = False
534         self.quiet = False
535         self.packs = []
536         self.update = False
537         self.args = []
538         self.finish_args = None
539
540         self.no_flatpak_update = False
541         self.release = False
542         self.debug = False
543         self.clean = False
544         self.run_tests = None
545         self.source_root = os.path.normpath(os.path.abspath(os.path.join(scriptdir, '../../')))
546         # Where the source folder is mounted inside the sandbox.
547         self.sandbox_source_root = "/app/webkit"
548
549         self.build_webkit = False
550         self.build_all = False
551
552         self.sdk_branch = None
553         self.platform = "gtk"
554         self.build_type = "Release"
555         self.manifest_path = None
556         self.name = None
557         self.build_name = None
558         self.flatpak_root_path = None
559         self.cache_path = None
560         self.app_module = None
561         self.flatpak_default_args = []
562         self.check_available = False
563         self.assumeyes = False
564
565         # Default application to run in the sandbox
566         self.command = None
567         self.user_command = []
568
569         # debug options
570         self.gdb = None
571         self.coredumpctl_matches = ""
572
573         # Extra build options
574         self.cmakeargs = ""
575         self.makeargs = ""
576
577     def clean_args(self):
578         configure_logging(logging.DEBUG if self.verbose else logging.INFO)
579         if not self.debug and not self.release:
580             factory = PortFactory(SystemHost())
581             port = factory.get(self.platform)
582             self.debug = port.default_configuration() == "Debug"
583         self.build_type = "Debug" if self.debug else "Release"
584
585         self.platform = self.platform.upper()
586
587         if self.gdb is None and '--gdb' in sys.argv:
588             self.gdb = ""
589
590         self.command = "%s %s %s" % (os.path.join(self.sandbox_source_root,
591             "Tools/Scripts/run-minibrowser"),
592             "--" + self.platform.lower(),
593             " --debug" if self.debug else " --release")
594
595         self.name = "org.webkit.%s" % self.platform
596         self.manifest_path = os.path.abspath(os.path.join(scriptdir, '../flatpak/org.webkit.WebKit.yaml'))
597         self.build_name = self.name + "-generated"
598
599         build_root = os.path.join(self.source_root, 'WebKitBuild')
600         self.flatpak_build_path = os.path.join(build_root, self.platform, "FlatpakTree" + self.build_type)
601         self.cache_path = os.path.join(build_root, "FlatpakCache")
602         self.build_path = os.path.join(build_root, self.platform, self.build_type)
603         try:
604             os.makedirs(self.build_path)
605         except OSError as e:
606             if e.errno != errno.EEXIST:
607                 raise e
608
609         Console.quiet = self.quiet
610         if not check_flatpak():
611             return False
612
613         repos = FlatpakRepos()
614         self.sdk_repo = repos.add(
615             FlatpakRepo("gnome-nightly",
616                         url="https://sdk.gnome.org/nightly/repo/",
617                         repo_file="https://sdk.gnome.org/gnome-nightly.flatpakrepo"))
618
619         manifest = load_manifest(self.manifest_path)
620         if not manifest:
621             return False
622
623         self.sdk_branch = manifest["runtime-version"]
624         self.finish_args = manifest.get("finish-args", [])
625         self.finish_args = remove_extension_points(self.finish_args)
626         self.runtime = FlatpakPackage("org.gnome.Platform", self.sdk_branch,
627                                       self.sdk_repo, "x86_64",
628                                       hash=manifest.get("runtime-hash"),
629                                       assumeyes=self.assumeyes)
630         self.locale = FlatpakPackage("org.gnome.Platform.Locale",
631                                      self.sdk_branch, self.sdk_repo, "x86_64",
632                                      assumeyes=self.assumeyes)
633         self.sdk = FlatpakPackage("org.gnome.Sdk", self.sdk_branch,
634                                   self.sdk_repo, "x86_64",
635                                   hash=manifest.get("sdk-hash"),
636                                   assumeyes=self.assumeyes)
637         self.packs = [self.runtime, self.locale, self.sdk]
638
639         if self.debug:
640             self.sdk_debug = FlatpakPackage("org.gnome.Sdk.Debug", self.sdk_branch,
641                                       self.sdk_repo, "x86_64",
642                                       assumeyes=self.assumeyes)
643             self.packs.append(self.sdk_debug)
644         self.manifest_generated_path = os.path.join(self.cache_path,
645                                                     self.build_name + ".json")
646
647         return True
648
649     def run_in_sandbox(self, *args, **kwargs):
650         cwd = kwargs.pop("cwd", None)
651         stdout = kwargs.pop("stdout", sys.stdout)
652         extra_flatpak_args = kwargs.pop("extra_flatpak_args", [])
653
654         if not isinstance(args, list):
655             args = list(args)
656         if args:
657             if os.path.exists(args[0]):
658                 command = os.path.normpath(os.path.abspath(args[0]))
659                 # Take into account the fact that the webkit source dir is remounted inside the sandbox.
660                 args[0] = command.replace(self.source_root, self.sandbox_source_root)
661             if args[0].endswith("build-webkit"):
662                 args.append("--prefix=/app")
663
664         sandbox_build_path = os.path.join(self.sandbox_source_root, "WebKitBuild", self.build_type)
665         with tempfile.NamedTemporaryFile(mode="w") as tmpscript:
666             flatpak_command = ["flatpak", "build", "--die-with-parent",
667                 "--bind-mount=/run/shm=/dev/shm",
668                 # Workaround for https://webkit.org/b/187384 to have our own perl modules usable inside the sandbox
669                 # as setting the PERL5LIB envvar won't work inside apache (and for scripts using `perl -T``).
670                 "--bind-mount=/etc/perl=%s" % os.path.join(self.flatpak_build_path, "files/lib/perl"),
671                 "--bind-mount=/run/host/%s=%s" % (tempfile.gettempdir(), tempfile.gettempdir()),
672                 "--bind-mount=%s=%s" % (self.sandbox_source_root, self.source_root),
673                 # We mount WebKitBuild/PORTNAME/BuildType to /app/webkit/WebKitBuild/BuildType
674                 # so we can build WPE and GTK in a same source tree.
675                 "--bind-mount=%s=%s" % (sandbox_build_path, self.build_path)]
676
677             forwarded = {
678                 "WEBKIT_TOP_LEVEL": "/app/",
679                 "TEST_RUNNER_INJECTED_BUNDLE_FILENAME": "/app/webkit/lib/libTestRunnerInjectedBundle.so",
680             }
681
682             env_var_prefixes_to_keep = [
683                 "GST",
684                 "GTK",
685                 "G",
686                 "JSC",
687                 "WEBKIT",
688                 "WEBKIT2",
689                 "WPE",
690                 "GIGACAGE",
691             ]
692
693             env_vars_to_keep = [
694                 "JavaScriptCoreUseJIT",
695                 "Malloc",
696                 "WAYLAND_DISPLAY",
697                 "DISPLAY",
698                 "LANG",
699             ]
700
701             for envvar, value in os.environ.items():
702                 if envvar.split("_")[0] in env_var_prefixes_to_keep or envvar in env_vars_to_keep:
703                     forwarded[envvar] = value
704
705             for envvar, value in forwarded.items():
706                 flatpak_command.append("--env=%s=%s" % (envvar, value))
707
708             flatpak_command += self.finish_args + extra_flatpak_args + [self.flatpak_build_path]
709
710             shell_string = ""
711             if args:
712                 if cwd:
713                     shell_string = 'cd "%s" && "%s"' % (cwd, '" "'.join(args))
714                 else:
715                     shell_string = '"%s"' % ('" "'.join(args))
716             else:
717                 shell_string = self.command
718                 if self.args:
719                     shell_string += ' "%s"' % '" "'.join(self.args)
720
721             tmpscript.write(shell_string)
722             tmpscript.flush()
723
724             _log.debug('Running in sandbox: "%s" %s\n' % ('" "'.join(flatpak_command), shell_string))
725             flatpak_command.extend(['sh', "/run/host/" + tmpscript.name])
726
727             with disable_signals():
728                 try:
729                     subprocess.check_call(flatpak_command, stdout=stdout)
730                 except subprocess.CalledProcessError as e:
731                     sys.stderr.write(str(e) + "\n")
732                     return e.returncode
733
734         return 0
735
736     def run(self):
737         if not self.clean_args():
738             return 1
739
740         if self.check_available:
741             return 0
742
743         if self.clean:
744             if os.path.exists(self.flatpak_build_path):
745                 shutil.rmtree(self.flatpak_build_path)
746             if os.path.exists(self.build_path):
747                 shutil.rmtree(self.build_path)
748
749         if self.update:
750             Console.message("Updating Flatpak environment for %s (%s)" % (
751                 self.platform, self.build_type))
752             if not self.no_flatpak_update:
753                 self.update_all()
754
755         return self.setup_dev_env()
756
757     def has_environment(self):
758         return os.path.exists(os.path.join(self.build_path, self.flatpak_build_path))
759
760     def setup_dev_env(self):
761         if not os.path.exists(os.path.join(self.build_path, self.flatpak_build_path)) \
762                 or self.update or self.build_all:
763             self.install_all()
764             Console.message("Building %s and dependencies in %s",
765                             self.name, self.flatpak_build_path)
766
767             # Create environment dirs if necessary
768             try:
769                 os.makedirs(os.path.dirname(self.manifest_generated_path))
770             except OSError as e:
771                 if e.errno != errno.EEXIST:
772                     raise e
773             if not expand_manifest(self.manifest_path, self.manifest_generated_path,
774                                    self.name, self.sandbox_source_root, self.command):
775                 return 1
776
777             builder_args = ["flatpak-builder", "--disable-rofiles-fuse", "--state-dir",
778                             self.cache_path, "--ccache", self.flatpak_build_path, "--force-clean",
779                             self.manifest_generated_path]
780             builder_args.append("--build-only")
781             builder_args.append("--stop-at=%s" % self.name)
782             subprocess.check_call(builder_args)
783
784         if self.build_webkit:
785             builder = [os.path.join(self.sandbox_source_root, 'Tools/Scripts/build-webkit'),
786                 "--debug" if self.debug  else "--release", '--' + self.platform.lower()]
787             if self.makeargs:
788                 builder.append("--makeargs=%s" % self.makeargs)
789             if self.cmakeargs:
790                 builder.append("--cmakeargs=%s" % self.cmakeargs)
791             Console.message("Building webkit")
792             res = self.run_in_sandbox(*builder)
793
794             if res:
795                 return res
796         else:
797             Console.message("Using %s prefix in %s", self.name, self.flatpak_build_path)
798
799         if self.run_tests is not None:
800             test_launcher = [os.path.join(self.sandbox_source_root, 'Tools/Scripts/run-webkit-tests'),
801                 "--debug" if self.debug  else "--release", '--' + self.platform.lower()] + self.run_tests
802             return self.run_in_sandbox(*test_launcher)
803         elif self.gdb is not None:
804             return self.run_gdb()
805         elif self.user_command:
806             return self.run_in_sandbox(*self.user_command)
807         elif not self.update and not self.build_webkit:
808             return self.run_in_sandbox()
809
810         return 0
811
812     def install_all(self):
813         for package in self.packs:
814             if not package.is_installed(self.sdk_branch):
815                 package.install()
816
817     def run_gdb(self):
818         with disable_signals():
819             try:
820                 subprocess.check_output(['which', 'coredumpctl'])
821             except subprocess.CalledProcessError as e:
822                 sys.stderr.write("'coredumpctl' not present on the system, can't run. (%s)\n" % e)
823                 return e.returncode
824
825         # We need access to the host from the sandbox to run.
826         with tempfile.NamedTemporaryFile() as coredump:
827             with tempfile.NamedTemporaryFile() as stderr:
828                 subprocess.check_call(["coredumpctl", "dump"] + shlex.split(self.coredumpctl_matches),
829                                       stdout=coredump, stderr=stderr)
830
831                 with open(stderr.name, 'r') as stderrf:
832                     stderr = stderrf.read()
833                 executable, = re.findall(".*Executable: (.*)", stderr)
834                 if not executable.startswith("/newroot"):
835                     sys.stderr.write("Executable %s doesn't seem to be a flatpaked application.\n" % executable)
836
837                 executable = executable.replace("/newroot", "")
838                 args = ["gdb", executable, "/run/host/%s" % coredump.name] + shlex.split(self.gdb)
839
840                 return self.run_in_sandbox(*args)
841
842     def update_all(self):
843         for m in [self.runtime, self.sdk, self.sdk_debug]:
844             if m:
845                 m.update()
846
847
848 def is_sandboxed():
849     return os.path.exists("/usr/manifest.json")
850
851
852 def run_in_sandbox_if_available(args):
853     if is_sandboxed():
854         return None
855
856     if not check_flatpak(verbose=False):
857         return None
858
859     flatpak_runner = WebkitFlatpak.load_from_args(args)
860     if not flatpak_runner.clean_args():
861         return None
862
863     if not flatpak_runner.has_environment():
864         return None
865
866     sys.exit(flatpak_runner.run_in_sandbox(*args))