| #!/usr/bin/env python3 |
| # -*- coding: utf-8 -*- |
| # Copyright (C) 2020 Igalia S.L. |
| # |
| # 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. |
| |
| import argparse |
| import os |
| import re |
| import sys |
| import shlex |
| import tempfile |
| import time |
| import subprocess |
| import urllib.request |
| from html.parser import HTMLParser |
| |
| REPO = "https://software.igalia.com/flatpak-refs/webkit-sdk.flatpakrepo" |
| SDK_BRANCH = "21.08" |
| USER_DIR = os.path.expanduser("~/.cache/wk-nightly") |
| |
| def spinning_cursor(): |
| while True: |
| for cursor in '|/-\\': |
| yield cursor |
| |
| # FIXME: Might be worth adding some JSON file listing builds on the servers. |
| class MyHTMLParser(HTMLParser): |
| builds = [] |
| def handle_starttag(self, tag, attrs): |
| if tag != "a": |
| return |
| for (name, value) in attrs: |
| if name == "href" and (value.startswith("release") or value.startswith("debug")): |
| self.builds.append(value) |
| |
| def flatpak(*args, env=None, stdout=None): |
| cmd_env = os.environ.copy() |
| if env: |
| cmd_env.update(env) |
| cmd_env["FLATPAK_USER_DIR"] = USER_DIR |
| return subprocess.call(("flatpak",) + args, env=cmd_env, stdout=stdout) |
| |
| def ensure_sdk(build_type): |
| branch = SDK_BRANCH |
| if not os.path.isdir(USER_DIR): |
| flatpak("remote-add", "--user", "webkit-nightly", REPO) |
| flatpak("install", "--user", "-y", "webkit-nightly", f"org.webkit.Platform//{branch}") |
| else: |
| flatpak("update", "--user", "-y") |
| if (build_type == "Debug") and (flatpak("info", "org.webkit.Sdk.Debug", stdout=subprocess.DEVNULL) >= 1): |
| flatpak("install", "--user", "-y", "webkit-nightly", f"org.webkit.Sdk//{branch}") |
| flatpak("install", "--user", "-y", "webkit-nightly", f"org.webkit.Sdk.Debug//{branch}") |
| |
| def get_build_path(platform, build_type, revision_number): |
| dirname = f"{platform}-{build_type}-r{revision_number}" |
| return os.path.join(tempfile.gettempdir(), dirname) |
| |
| def get_revision_number(build_name): |
| match = re.match(r'.*_r([0-9]+)\.zip$', build_name) |
| if match: |
| return int(match.group(1)) |
| |
| def ensure_extracted_build(args): |
| build_type = args.build_type.lower() |
| if args.revision_number: |
| path = get_build_path(args.platform, build_type, args.revision_number) |
| if os.path.isdir(path): |
| return path |
| |
| url = f"https://{args.platform}-{build_type}.igalia.com/built-products/" |
| with urllib.request.urlopen(url) as page_fd: |
| parser = MyHTMLParser() |
| parser.feed(page_fd.read().decode("utf-8")) |
| try: |
| latest = parser.builds[-1] |
| except IndexError: |
| print(f"No build found in {url}") |
| return "" |
| if args.revision_number: |
| try: |
| build = [b for b in parser.builds if b.endswith(f"r{args.revision_number}.zip")][0] |
| except IndexError: |
| print(f"Build {args.revision_number} not found. Falling back to latest: {latest}") |
| build = latest |
| else: |
| build = latest |
| print(f"Downloading build {build} from {url}") |
| with urllib.request.urlopen(f"{url}/{build}") as zip_fd: |
| with tempfile.NamedTemporaryFile() as zip_file: |
| data = zip_fd.read(8192) |
| spinner = spinning_cursor() |
| while data: |
| zip_file.write(data) |
| sys.stdout.write(next(spinner)) |
| sys.stdout.flush() |
| data = zip_fd.read(8192) |
| sys.stdout.write('\b') |
| |
| zip_file.flush() |
| revision_number = get_revision_number(build) |
| path = get_build_path(args.platform, build_type, revision_number) |
| print(f"Extracting build to {path}") |
| os.system(f"unzip -qq -o {zip_file.name} -d {path}") |
| return path |
| |
| def run(path, build_type, args): |
| env = { |
| "FLATPAK_BWRAP": os.path.realpath(__file__), |
| "WEBKIT_BUILD_DIR_BIND_MOUNT": f"/app/webkit/WebKitBuild/{build_type}:{path}", |
| } |
| flatpak("run", "--die-with-parent", "--user", |
| f"--env=PATH=/app/webkit/WebKitBuild/{build_type}/bin:/usr/bin", |
| "--device=dri", |
| "--share=ipc", |
| "--share=network", |
| "--socket=pulseaudio", |
| "--socket=system-bus", |
| "--socket=wayland", |
| "--socket=x11", |
| "--system-talk-name=org.a11y.Bus", |
| "--system-talk-name=org.freedesktop.GeoClue2", |
| "--talk-name=org.a11y.Bus", |
| "--talk-name=org.freedesktop.Flatpak", |
| "--talk-name=org.gtk.vfs", |
| "--talk-name=org.gtk.vfs.*", |
| f"--command={args[0]}", f"org.webkit.Platform//{SDK_BRANCH}", *args[1:], env=env) |
| |
| def main(args): |
| parser = argparse.ArgumentParser() |
| parser.add_argument("--revision-number", type=int, help="Download a specific revision (default: latest)", |
| action='store', dest='revision_number') |
| parser.add_argument("-p", action="store", dest="path", help="Reuse previously downloaded build from given path") |
| type_group = parser.add_mutually_exclusive_group() |
| type_group.add_argument("--debug", help="Download a debug build, also installs Sdk debug symbols.", |
| dest='build_type', action="store_const", const="Debug") |
| type_group.add_argument("--release", help="Download a release build.", |
| dest='build_type', action="store_const", const="Release") |
| platform_group = parser.add_mutually_exclusive_group() |
| platform_group.add_argument('--gtk', action='store_const', dest='platform', const='webkitgtk', |
| help='Download and run GTK port build artefacts') |
| platform_group.add_argument('--wpe', action='store_const', dest='platform', const='wpewebkit', |
| help=('Download and run WPE port build artefacts')) |
| parser.add_argument("command", nargs=argparse.REMAINDER, help="Command to execute (example: MiniBrowser, jsc)") |
| if len(args) == 0: |
| parser.print_help(sys.stderr) |
| sys.exit(1) |
| parsed, _ = parser.parse_known_args(args=args) |
| if not parsed.platform: |
| parsed.platform = "webkitgtk" |
| if not parsed.build_type: |
| parsed.build_type = "Release" |
| |
| ensure_sdk(parsed.build_type) |
| if not parsed.path: |
| parsed.path = ensure_extracted_build(parsed) |
| |
| if parsed.path: |
| run(parsed.path, parsed.build_type, parsed.command) |
| |
| def read_lines(fd: int): |
| new_fd = os.dup(fd) |
| pos = os.lseek(fd, 0, os.SEEK_CUR) |
| try: |
| with os.fdopen(new_fd, 'r') as handle: |
| data = handle.read() |
| lines = data.split('\x00') |
| for line in lines: |
| yield line |
| except Exception as e: |
| print("Error reading bwrap arguments", file=sys.stderr) |
| print(e, file=sys.stderr) |
| finally: |
| os.lseek(fd, pos, os.SEEK_SET) |
| |
| def bwrap_main(args): |
| bind_mounts = { |
| "/run/shm": "/dev/shm", |
| } |
| |
| args_idx = args.index('--args') |
| arg_fd = int(args[args_idx + 1]) |
| lines = read_lines(arg_fd) |
| |
| environ = os.environ.copy() |
| while True: |
| try: |
| arg = next(lines) |
| if arg == '--setenv': |
| key = next(lines) |
| value = next(lines) |
| environ[key] = value |
| except StopIteration: |
| break |
| |
| build_path = environ.get("WEBKIT_BUILD_DIR_BIND_MOUNT") |
| if build_path: |
| dest, src = build_path.split(":") |
| bind_mounts[dest] = src |
| |
| bwrap_args = ["bwrap", ] |
| for dst, src in bind_mounts.items(): |
| bwrap_args.extend(["--bind", src, dst]) |
| |
| os.execvpe(bwrap_args[0], bwrap_args + args, os.environ) |
| |
| if __name__ == "__main__": |
| ld_library_path = os.environ.get("LD_LIBRARY_PATH", "") |
| if '--args' in sys.argv: |
| bwrap_main(sys.argv[1:]) |
| else: |
| main(sys.argv[1:]) |