summaryrefslogtreecommitdiffstats
path: root/gfx/angle/update-angle.py
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/angle/update-angle.py')
-rwxr-xr-xgfx/angle/update-angle.py648
1 files changed, 648 insertions, 0 deletions
diff --git a/gfx/angle/update-angle.py b/gfx/angle/update-angle.py
new file mode 100755
index 0000000000..85639ba6b9
--- /dev/null
+++ b/gfx/angle/update-angle.py
@@ -0,0 +1,648 @@
+#! /usr/bin/env python3
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+assert __name__ == "__main__"
+
+"""
+To update ANGLE in Gecko, use Windows with git-bash, and setup depot_tools, python2, and
+python3. Because depot_tools expects `python` to be `python2` (shame!), python2 must come
+before python3 in your path.
+
+Upstream: https://chromium.googlesource.com/angle/angle
+
+Our repo: https://github.com/mozilla/angle
+It has branches like 'firefox-60' which is the branch we use for pulling into
+Gecko with this script.
+
+This script leaves a record of the merge-base and cherry-picks that we pull into
+Gecko. (gfx/angle/cherries.log)
+
+ANGLE<->Chrome version mappings are here: https://omahaproxy.appspot.com/
+An easy choice is to grab Chrome's Beta's ANGLE branch.
+
+## Usage
+
+Prepare your env:
+
+~~~
+export PATH="$PATH:/path/to/depot_tools"
+~~~
+
+If this is a new repo, don't forget:
+
+~~~
+# In the angle repo:
+./scripts/bootstrap.py
+gclient sync
+~~~
+
+Update: (in the angle repo)
+
+~~~
+# In the angle repo:
+/path/to/gecko/gfx/angle/update-angle.py origin/chromium/XXXX
+git push moz # Push the firefox-XX branch to github.com/mozilla/angle
+~~~~
+
+"""
+
+import json
+import os
+import pathlib
+import re
+import shutil
+import subprocess
+import sys
+from typing import * # mypy annotations
+
+REPO_DIR = pathlib.Path.cwd()
+GECKO_ANGLE_DIR = pathlib.Path(__file__).parent
+
+OUT_DIR = pathlib.Path("out")
+
+COMMON_HEADER = [
+ "# Generated by update-angle.py",
+ "",
+ 'include("../../moz.build.common")',
+]
+
+ROOTS = ["//:translator", "//:libEGL", "//:libGLESv2"]
+
+CHECK_ONLY = False
+args = sys.argv[1:]
+while True:
+ arg = args.pop(0)
+ if arg == "--check":
+ CHECK_ONLY = True
+ continue
+ args.insert(0, arg)
+ break
+
+GN_ENV = dict(os.environ)
+GN_ENV["DEPOT_TOOLS_WIN_TOOLCHAIN"] = "0"
+
+(GIT_REMOTE,) = args # Not always 'origin'!
+
+# ------------------------------------------------------------------------------
+
+
+def run_checked(*args, **kwargs):
+ print(" ", args)
+ sys.stdout.flush()
+ return subprocess.run(args, check=True, **kwargs)
+
+
+def sorted_items(x):
+ for k in sorted(x.keys()):
+ yield (k, x[k])
+
+
+def collapse_dotdots(path):
+ split = path.split("/")
+
+ ret = []
+ for x in split:
+ if x == ".." and ret:
+ ret.pop()
+ continue
+ ret.append(x)
+ continue
+
+ return "/".join(ret)
+
+
+def dag_traverse(root_keys: Sequence[str], pre_recurse_func: Callable[[str], list]):
+ visited_keys: Set[str] = set()
+
+ def recurse(key):
+ if key in visited_keys:
+ return
+ visited_keys.add(key)
+
+ t = pre_recurse_func(key)
+ try:
+ (next_keys, post_recurse_func) = t
+ except ValueError:
+ (next_keys,) = t
+ post_recurse_func = None
+
+ for x in next_keys:
+ recurse(x)
+
+ if post_recurse_func:
+ post_recurse_func(key)
+ return
+
+ for x in root_keys:
+ recurse(x)
+ return
+
+
+# ------------------------------------------------------------------------------
+
+print("Importing graph")
+
+# shutil.rmtree(str(OUT_DIR), True)
+OUT_DIR.mkdir(exist_ok=True)
+
+GN_ARGS = b"""
+# Build arguments go here.
+# See "gn args <out_dir> --list" for available build arguments.
+is_clang = true
+is_debug = false
+angle_enable_gl = false
+angle_enable_gl_null = false
+angle_enable_null = false
+angle_enable_vulkan = false
+"""[
+ 1:
+]
+args_gn_path = OUT_DIR / "args.gn"
+args_gn_path.write_bytes(GN_ARGS)
+
+try:
+ run_checked("gn", "gen", str(OUT_DIR), shell=True, env=GN_ENV)
+except subprocess.CalledProcessError:
+ sys.stderr.buffer.write(b"`gn` failed. Is depot_tools in your PATH?\n")
+ exit(1)
+
+p = run_checked(
+ "gn",
+ "desc",
+ "--format=json",
+ str(OUT_DIR),
+ "*",
+ stdout=subprocess.PIPE,
+ shell=True,
+ env=GN_ENV,
+)
+
+# -
+
+print("\nProcessing graph")
+descs = json.loads(p.stdout.decode())
+
+# -
+# HACKHACKHACK: Inject linux/mac sources instead of trying to merge graphs of different
+# platforms.
+descs["//:angle_common"]["sources"] += [
+ "//src/common/system_utils_linux.cpp",
+ "//src/common/system_utils_mac.cpp",
+ "//src/common/system_utils_posix.cpp",
+]
+
+# Ready to traverse
+# ------------------------------------------------------------------------------
+
+LIBRARY_TYPES = ("shared_library", "static_library")
+
+
+def flattened_target(target_name: str, descs: dict, stop_at_lib: bool = True) -> dict:
+ flattened = dict(descs[target_name])
+
+ EXPECTED_TYPES = LIBRARY_TYPES + ("source_set", "group", "action")
+
+ def pre(k):
+ dep = descs[k]
+
+ dep_type = dep["type"]
+ deps = dep["deps"]
+ if stop_at_lib and dep_type in LIBRARY_TYPES:
+ return ((),)
+
+ if dep_type == "copy":
+ assert not deps, (target_name, dep["deps"])
+ else:
+ assert dep_type in EXPECTED_TYPES, (k, dep_type)
+ for (k, v) in dep.items():
+ if type(v) in (list, tuple):
+ flattened[k] = flattened.get(k, []) + v
+ else:
+ # flattened.setdefault(k, v)
+ pass
+ return (deps,)
+
+ dag_traverse(descs[target_name]["deps"], pre)
+ return flattened
+
+
+# ------------------------------------------------------------------------------
+# Check that includes are valid. (gn's version of this check doesn't seem to work!)
+
+INCLUDE_REGEX = re.compile(b'(?:^|\\n) *# *include +([<"])([^>"]+)[>"]')
+assert INCLUDE_REGEX.match(b'#include "foo"')
+assert INCLUDE_REGEX.match(b'\n#include "foo"')
+
+IGNORED_INCLUDES = {
+ b"compiler/translator/TranslatorVulkan.h",
+ b"libANGLE/renderer/d3d/d3d11/winrt/NativeWindow11WinRT.h",
+ b"libANGLE/renderer/gl/glx/DisplayGLX.h",
+ b"libANGLE/renderer/gl/cgl/DisplayCGL.h",
+ b"libANGLE/renderer/gl/egl/ozone/DisplayOzone.h",
+ b"libANGLE/renderer/gl/egl/android/DisplayAndroid.h",
+ b"libANGLE/renderer/gl/wgl/DisplayWGL.h",
+ b"libANGLE/renderer/null/DisplayNULL.h",
+ b"libANGLE/renderer/vulkan/android/DisplayVkAndroid.h",
+ b"libANGLE/renderer/vulkan/fuchsia/DisplayVkFuchsia.h",
+ b"libANGLE/renderer/vulkan/win32/DisplayVkWin32.h",
+ b"libANGLE/renderer/vulkan/xcb/DisplayVkXcb.h",
+ b"kernel/image.h",
+}
+
+IGNORED_INCLUDE_PREFIXES = {
+ b"android",
+ b"Carbon",
+ b"CoreFoundation",
+ b"CoreServices",
+ b"IOSurface",
+ b"mach",
+ b"mach-o",
+ b"OpenGL",
+ b"pci",
+ b"sys",
+ b"wrl",
+ b"X11",
+}
+
+
+def has_all_includes(target_name: str, descs: dict) -> bool:
+ flat = flattened_target(target_name, descs, stop_at_lib=False)
+ acceptable_sources = flat.get("sources", []) + flat.get("outputs", [])
+ acceptable_sources = (x.rsplit("/", 1)[-1].encode() for x in acceptable_sources)
+ acceptable_sources = set(acceptable_sources)
+
+ ret = True
+ desc = descs[target_name]
+ for cur_file in desc.get("sources", []):
+ assert cur_file.startswith("/"), cur_file
+ if not cur_file.startswith("//"):
+ continue
+ cur_file = pathlib.Path(cur_file[2:])
+ text = cur_file.read_bytes()
+ for m in INCLUDE_REGEX.finditer(text):
+ if m.group(1) == b"<":
+ continue
+ include = m.group(2)
+ if include in IGNORED_INCLUDES:
+ continue
+ try:
+ (prefix, _) = include.split(b"/", 1)
+ if prefix in IGNORED_INCLUDE_PREFIXES:
+ continue
+ except ValueError:
+ pass
+
+ include_file = include.rsplit(b"/", 1)[-1]
+ if include_file not in acceptable_sources:
+ # print(' acceptable_sources:')
+ # for x in sorted(acceptable_sources):
+ # print(' ', x)
+ print(
+ "Warning in {}: {}: Invalid include: {}".format(
+ target_name, cur_file, include
+ )
+ )
+ ret = False
+ # print('Looks valid:', m.group())
+ continue
+
+ return ret
+
+
+# -
+# Gather real targets:
+
+
+def gather_libraries(roots: Sequence[str], descs: dict) -> Set[str]:
+ libraries = set()
+
+ def fn(target_name):
+ cur = descs[target_name]
+ print(" " + cur["type"], target_name)
+ assert has_all_includes(target_name, descs), target_name
+
+ if cur["type"] in ("shared_library", "static_library"):
+ libraries.add(target_name)
+ return (cur["deps"],)
+
+ dag_traverse(roots, fn)
+ return libraries
+
+
+# -
+
+libraries = gather_libraries(ROOTS, descs)
+print(f"\n{len(libraries)} libraries:")
+for k in libraries:
+ print(" ", k)
+
+if CHECK_ONLY:
+ print("\n--check complete.")
+ exit(0)
+
+# ------------------------------------------------------------------------------
+# Output to moz.builds
+
+import vendor_from_git
+
+print("")
+vendor_from_git.record_cherry_picks(GECKO_ANGLE_DIR, GIT_REMOTE)
+
+# --
+
+
+def sortedi(x):
+ return sorted(x, key=str.lower)
+
+
+def append_arr(dest, name, vals, indent=0):
+ if not vals:
+ return
+
+ dest.append("{}{} += [".format(" " * 4 * indent, name))
+ for x in sortedi(vals):
+ dest.append('{}"{}",'.format(" " * 4 * (indent + 1), x))
+ dest.append("{}]".format(" " * 4 * indent))
+ dest.append("")
+ return
+
+
+REGISTERED_DEFINES = {
+ "ANGLE_CAPTURE_ENABLED": True,
+ "ANGLE_EGL_LIBRARY_NAME": False,
+ "ANGLE_ENABLE_D3D11": True,
+ "ANGLE_ENABLE_D3D9": True,
+ "ANGLE_ENABLE_DEBUG_ANNOTATIONS": True,
+ "ANGLE_ENABLE_NULL": False,
+ "ANGLE_ENABLE_OPENGL": False,
+ "ANGLE_ENABLE_OPENGL_NULL": False,
+ "ANGLE_ENABLE_ESSL": True,
+ "ANGLE_ENABLE_GLSL": True,
+ "ANGLE_ENABLE_HLSL": True,
+ "ANGLE_GENERATE_SHADER_DEBUG_INFO": True,
+ "ANGLE_GLESV2_LIBRARY_NAME": True,
+ "ANGLE_IS_64_BIT_CPU": False,
+ "ANGLE_PRELOADED_D3DCOMPILER_MODULE_NAMES": False,
+ "ANGLE_USE_EGL_LOADER": True,
+ "CERT_CHAIN_PARA_HAS_EXTRA_FIELDS": False,
+ "CHROMIUM_BUILD": False,
+ "COMPONENT_BUILD": False,
+ "DYNAMIC_ANNOTATIONS_ENABLED": True,
+ "EGL_EGL_PROTOTYPES": True,
+ "EGL_EGLEXT_PROTOTYPES": True,
+ "EGLAPI": True,
+ "FIELDTRIAL_TESTING_ENABLED": False,
+ "FULL_SAFE_BROWSING": False,
+ "GL_API": True,
+ "GL_APICALL": True,
+ "GL_GLES_PROTOTYPES": True,
+ "GL_GLEXT_PROTOTYPES": True,
+ "GPU_INFO_USE_SETUPAPI": True,
+ "LIBANGLE_IMPLEMENTATION": True,
+ "LIBEGL_IMPLEMENTATION": True,
+ "LIBGLESV2_IMPLEMENTATION": True,
+ "NOMINMAX": True,
+ "NO_TCMALLOC": False,
+ # Else: gfx/angle/checkout/src/libANGLE/renderer/d3d/d3d11/win32/NativeWindow11Win32.cpp(89): error C2787: 'IDCompositionDevice': no GUID has been associated with this object
+ "NTDDI_VERSION": True,
+ "PSAPI_VERSION": False,
+ "SAFE_BROWSING_CSD": False,
+ "SAFE_BROWSING_DB_LOCAL": False,
+ "UNICODE": True,
+ "USE_AURA": False,
+ "V8_DEPRECATION_WARNINGS": False,
+ "WIN32": False,
+ "WIN32_LEAN_AND_MEAN": False,
+ "WINAPI_FAMILY": False,
+ "WINVER": True,
+ # Otherwise:
+ # gfx/angle/targets/libANGLE
+ # In file included from c:/dev/mozilla/gecko4/gfx/angle/checkout/src/libANGLE/renderer/d3d/d3d11/converged/CompositorNativeWindow11.cpp:10:
+ # In file included from c:/dev/mozilla/gecko4/gfx/angle/checkout/src\libANGLE/renderer/d3d/d3d11/converged/CompositorNativeWindow11.h:17:
+ # C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\winrt\Windows.ui.composition.interop.h(103,20): error: unknown type name 'POINTER_INFO'
+ # _In_ const POINTER_INFO& pointerInfo
+ # ^
+ "WTF_USE_DYNAMIC_ANNOTATIONS": False,
+ "_ATL_NO_OPENGL": True,
+ "_CRT_RAND_S": True,
+ "_CRT_SECURE_NO_DEPRECATE": True,
+ "_DEBUG": False,
+ "_HAS_EXCEPTIONS": True,
+ "_HAS_ITERATOR_DEBUGGING": False,
+ "_SCL_SECURE_NO_DEPRECATE": True,
+ "_SECURE_ATL": True,
+ "_UNICODE": True,
+ "_USING_V110_SDK71_": False,
+ "_WIN32_WINNT": False,
+ "_WINDOWS": False,
+ "__STD_C": False,
+ # clang specific
+ "CR_CLANG_REVISION": True,
+ "NDEBUG": False,
+ "NVALGRIND": False,
+ "_HAS_NODISCARD": False,
+}
+
+# -
+
+print("\nRun actions")
+required_files: Set[str] = set()
+
+run_checked("ninja", "-C", str(OUT_DIR), ":commit_id")
+required_files |= set(descs["//:commit_id"]["outputs"])
+
+# -
+
+# Export our targets
+print("\nExport targets")
+
+# Clear our dest directories
+targets_dir = pathlib.Path(GECKO_ANGLE_DIR, "targets")
+checkout_dir = pathlib.Path(GECKO_ANGLE_DIR, "checkout")
+
+shutil.rmtree(targets_dir, True)
+shutil.rmtree(checkout_dir, True)
+targets_dir.mkdir(exist_ok=True)
+checkout_dir.mkdir(exist_ok=True)
+
+# -
+
+
+def export_target(target_name) -> Set[str]:
+ # print(' ', target_name)
+ desc = descs[target_name]
+ flat = flattened_target(target_name, descs)
+ assert target_name.startswith("//:"), target_name
+ name = target_name[3:]
+
+ required_files: Set[str] = set(flat["sources"])
+
+ # Create our manifest lines
+ target_dir = targets_dir / name
+ target_dir.mkdir(exist_ok=True)
+
+ lines = list(COMMON_HEADER)
+ lines.append("")
+
+ for x in sorted(set(desc["defines"])):
+ try:
+ (k, v) = x.split("=", 1)
+ if v.startswith('"'):
+ v = f"'{v}'"
+ else:
+ v = f'"{v}"'
+ except ValueError:
+ (k, v) = (x, "True")
+ try:
+ line = f'DEFINES["{k}"] = {v}'
+ if REGISTERED_DEFINES[k] == False:
+ line = "# " + line
+ lines.append(line)
+ except KeyError:
+ print(f"[{name}] Unrecognized define: {k}")
+ lines.append("")
+
+ cxxflags = set(desc["cflags"] + desc["cflags_cc"])
+
+ def fixup_paths(listt):
+ for x in set(listt):
+ assert x.startswith("//"), x
+ yield "../../checkout/" + x[2:]
+
+ sources_by_config: Dict[str, List[str]] = {}
+ extras: Dict[str, str] = dict()
+ for x in fixup_paths(flat["sources"]):
+ # print(' '*5, x)
+ (b, e) = x.rsplit(".", 1)
+ if e in ["h", "y", "l", "inc", "inl"]:
+ continue
+ elif e in ["cpp", "cc", "c"]:
+ if b.endswith("_win"):
+ config = 'CONFIG["OS_ARCH"] == "WINNT"'
+ elif b.endswith("_linux"):
+ # Include these on BSDs too.
+ config = 'CONFIG["OS_ARCH"] not in ("Darwin", "WINNT")'
+ elif b.endswith("_mac"):
+ config = 'CONFIG["OS_ARCH"] == "Darwin"'
+ elif b.endswith("_posix"):
+ config = 'CONFIG["OS_ARCH"] != "WINNT"'
+ else:
+ config = "" # None can't compare against str.
+
+ sources_by_config.setdefault(config, []).append(x)
+ continue
+ elif e == "rc":
+ assert "RCFILE" not in extras, (target_name, extras["RCFILE"], x)
+ extras["RCFILE"] = f'"{x}"'
+ continue
+ elif e == "def":
+ assert "DEFFILE" not in extras, (target_name, extras["DEFFILE"], x)
+ extras["DEFFILE"] = f'"{x}"'
+ continue
+ else:
+ assert False, ("Unhandled ext:", x)
+
+ ldflags = set(desc["ldflags"])
+ DEF_PREFIX = "/DEF:"
+ for x in set(ldflags):
+ if x.startswith(DEF_PREFIX):
+ def_path = x[len(DEF_PREFIX) :]
+ required_files.add(def_path)
+ assert "DEFFILE" not in extras
+ ldflags.remove(x)
+
+ def_path = str(OUT_DIR) + "/" + def_path
+ def_path = "//" + collapse_dotdots(def_path)
+
+ def_rel_path = list(fixup_paths([def_path]))[0]
+ extras["DEFFILE"] = '"{}"'.format(def_rel_path)
+
+ os_libs = list(map(lambda x: x[: -len(".lib")], set(desc.get("libs", []))))
+
+ def append_arr_commented(dest, name, src):
+ lines = []
+ append_arr(lines, name, src)
+
+ def comment(x):
+ if x:
+ x = "# " + x
+ return x
+
+ lines = map(comment, lines)
+ dest += lines
+
+ append_arr(lines, "LOCAL_INCLUDES", fixup_paths(desc["include_dirs"]))
+ append_arr_commented(lines, "CXXFLAGS", cxxflags)
+
+ for (config, v) in sorted_items(sources_by_config):
+ indent = 0
+ if config:
+ lines.append("if {}:".format(config))
+ indent = 1
+ append_arr(lines, "SOURCES", v, indent=indent)
+
+ dep_libs: Set[str] = set()
+ for dep_name in set(flat["deps"]):
+ dep = descs[dep_name]
+ if dep["type"] in LIBRARY_TYPES:
+ assert dep_name.startswith("//:"), dep_name
+ dep_libs.add(dep_name[3:])
+
+ append_arr(lines, "USE_LIBS", dep_libs)
+ append_arr(lines, "DIRS", ["../" + x for x in dep_libs])
+ append_arr(lines, "OS_LIBS", os_libs)
+ append_arr_commented(lines, "LDFLAGS", ldflags)
+
+ for (k, v) in sorted(extras.items()):
+ lines.append("{} = {}".format(k, v))
+
+ lib_type = desc["type"]
+ if lib_type == "shared_library":
+ lines.append(f'GeckoSharedLibrary("{name}", linkage=None)')
+ elif lib_type == "static_library":
+ lines.append(f'Library("{name}")')
+ else:
+ assert False, lib_type
+
+ lines.append("")
+ # EOF newline
+
+ # Write it out
+
+ mozbuild = target_dir / "moz.build"
+ print(" ", " ", f"Writing {mozbuild}")
+ data = b"\n".join((x.encode() for x in lines))
+ mozbuild.write_bytes(data)
+
+ return required_files
+
+
+# -
+
+for target_name in libraries:
+ reqs = export_target(target_name)
+ required_files |= reqs
+
+# Copy all the files
+
+print("\nMigrate required files")
+
+i = 0
+for x in required_files:
+ i += 1
+ sys.stdout.write(f"\r Copying {i}/{len(required_files)}")
+ sys.stdout.flush()
+ assert x.startswith("//"), x
+ x = x[2:]
+
+ src = REPO_DIR / x
+ dest = checkout_dir / x
+
+ dest.parent.mkdir(parents=True, exist_ok=True)
+ data = src.read_bytes()
+ data = data.replace(b"\r\n", b"\n")
+ dest.write_bytes(data)
+
+print("\n\nDone")