diff options
author | Mukilan Thiyagarajan <me@mukilan.in> | 2023-04-29 19:41:41 +0200 |
---|---|---|
committer | Mukilan Thiyagarajan <me@mukilan.in> | 2023-05-12 00:14:38 +0530 |
commit | 8cfb19a8fba80a809af028223dd3a58d38123d02 (patch) | |
tree | ef051de063464259172ea52acfbf0a1d67dceb18 /python/servo/build_commands.py | |
parent | 425b0fe641e16083507c459041ff5dd19256ed7c (diff) | |
download | servo-8cfb19a8fba80a809af028223dd3a58d38123d02.tar.gz servo-8cfb19a8fba80a809af028223dd3a58d38123d02.zip |
Consume official GStreamer binaries on MacOS
This PR re-enables support for the gstreamer mediastack
in macOS by consuming the official binary '.pkg' files
from gstreamer.freedesktop.org
To maintain symmetry with other platforms, the '.pkg'
files are uploaded to servo-build-deps and fetched from
there using the new script 'etc/install_macos_gstreamer.sh'.
Unlike the Homebrew version, the official GStreamer is
distributed as a 'relocatable' framework i.e the dylibs all
have @rpath-relative install names and also link to other
dylibs using @rpath relative path. To address this difference
the 'servo' binary needs to be patched with 'install_name_tool'
to add an LC_RPATH command that sets the relative paths
that the dynamic linker should search when trying to satify
dependencies. In Servo's case, this will be a path relative to
the 'servo' binary itself i.e '@executable_path/lib/'
The additional 'lib' is due to a flaw in the gstreamer
packaging where the install names of some of the dylibs
have the prefix '@rpath/lib' and some of them just have '@rpath'.
This PR also fixes a couple of issues present in the
`mach build` process on MacOS:
1. `mach build` process was not copying transitive dependencies
of servo binary but only the first level dylibs
2. `mach build` process didn't patch the links to dylibs
in servo binary (and dependencies). This meant though
(some) dylibs were copied to local path, the binary
still loaded the dylibs from system GStreamer installation
i.e homebrew instead of the copieds dylibs
The build and runtime dependencies in etc/homebrew/Brewfile
and etc/homebrew/Brewfile-build have also been removed in This
PR.
Signed-off-by: Mukilan Thiyagarajan <me@mukilan.in>
Diffstat (limited to 'python/servo/build_commands.py')
-rw-r--r-- | python/servo/build_commands.py | 131 |
1 files changed, 109 insertions, 22 deletions
diff --git a/python/servo/build_commands.py b/python/servo/build_commands.py index 135a48ef7ed..f5c6e2aa749 100644 --- a/python/servo/build_commands.py +++ b/python/servo/build_commands.py @@ -34,7 +34,7 @@ from mach.registrar import Registrar from mach_bootstrap import _get_exec_path from servo.command_base import CommandBase, cd, call, check_call, append_to_path_env, gstreamer_root -from servo.gstreamer import windows_dlls, windows_plugins, macos_dylibs, macos_plugins +from servo.gstreamer import windows_dlls, windows_plugins, macos_plugins from servo.util import host_triple @@ -625,13 +625,20 @@ class MachCommands(CommandBase): status = 1 elif sys.platform == "darwin": - servo_exe_dir = os.path.dirname( - self.get_binary_path(release, dev, target=target, simpleservo=libsimpleservo) - ) - assert os.path.exists(servo_exe_dir) + servo_path = self.get_binary_path(release, dev, target=target, simpleservo=libsimpleservo) + servo_bin_dir = os.path.dirname(servo_path) + assert os.path.exists(servo_bin_dir) - if has_media_stack and not package_gstreamer_dylibs(servo_exe_dir): - return 1 + if has_media_stack: + gst_root = gstreamer_root(target, env) + if not package_gstreamer_dylibs(gst_root, servo_path): + return 1 + + # On Mac we use the relocatable dylibs from offical gstreamer + # .pkg distribution. We need to add an LC_RPATH to the servo binary + # to allow the dynamic linker to be able to locate these dylibs + # See `man dyld` for more info + add_rpath_to_binary(servo_path, "@executable_path/lib/") # On the Mac, set a lovely icon. This makes it easier to pick out the Servo binary in tools # like Instruments.app. @@ -791,22 +798,102 @@ def angle_root(target, nuget_env): return angle_default_path -def package_gstreamer_dylibs(servo_exe_dir): - missing = [] - gst_dylibs = macos_dylibs() + macos_plugins() - for gst_lib in gst_dylibs: - try: - dest_path = os.path.join(servo_exe_dir, os.path.basename(gst_lib)) - if os.path.isfile(dest_path): - os.remove(dest_path) - shutil.copy(gst_lib, servo_exe_dir) - except Exception as e: - print(e) - missing += [str(gst_lib)] +def otool(s): + o = subprocess.Popen(['/usr/bin/otool', '-L', s], stdout=subprocess.PIPE) + for line in map(lambda s: s.decode('ascii'), o.stdout): + if line[0] == '\t': + yield line.split(' ', 1)[0][1:] - for gst_lib in missing: - print("ERROR: could not find required GStreamer DLL: " + gst_lib) - return not missing + +def install_name_tool(binary, *args): + try: + subprocess.check_call(['install_name_tool', *args, binary]) + except subprocess.CalledProcessError as e: + print("install_name_tool exited with return value %d" % e.returncode) + + +def change_link_name(binary, old, new): + install_name_tool(binary, '-change', old, f"@executable_path/{new}") + + +def add_rpath_to_binary(binary, relative_path): + install_name_tool(binary, "-add_rpath", relative_path) + + +def change_rpath_in_binary(binary, old, new): + install_name_tool(binary, "-rpath", old, new) + + +def is_system_library(lib): + return lib.startswith("/System/Library") or lib.startswith("/usr/lib") + + +def is_relocatable_library(lib): + return lib.startswith("@rpath/") + + +def change_non_system_libraries_path(libraries, relative_path, binary): + for lib in libraries: + if is_system_library(lib) or is_relocatable_library(lib): + continue + new_path = path.join(relative_path, path.basename(lib)) + change_link_name(binary, lib, new_path) + + +def resolve_rpath(lib, rpath_root): + if not is_relocatable_library(lib): + return lib + + rpaths = ['', '../', 'gstreamer-1.0/'] + for rpath in rpaths: + full_path = rpath_root + lib.replace('@rpath/', rpath) + if path.exists(full_path): + return path.normpath(full_path) + + raise Exception("Unable to satisfy rpath dependency: " + lib) + + +def copy_dependencies(binary_path, lib_path, gst_root): + relative_path = path.relpath(lib_path, path.dirname(binary_path)) + "/" + + # Update binary libraries + binary_dependencies = set(otool(binary_path)) + binary_dependencies = binary_dependencies.union(macos_plugins()) + change_non_system_libraries_path(binary_dependencies, relative_path, binary_path) + + # Update dependencies libraries + need_checked = binary_dependencies + checked = set() + while need_checked: + checking = set(need_checked) + need_checked = set() + for f in checking: + # No need to check these for their dylibs + if is_system_library(f): + continue + full_path = resolve_rpath(f, gst_root) + need_relinked = set(otool(full_path)) + new_path = path.join(lib_path, path.basename(full_path)) + if not path.exists(new_path): + shutil.copyfile(full_path, new_path) + change_non_system_libraries_path(need_relinked, relative_path, new_path) + need_checked.update(need_relinked) + checked.update(checking) + need_checked.difference_update(checked) + + +def package_gstreamer_dylibs(gst_root, servo_bin): + lib_dir = path.join(path.dirname(servo_bin), "lib") + if os.path.exists(lib_dir): + shutil.rmtree(lib_dir) + os.mkdir(lib_dir) + try: + copy_dependencies(servo_bin, lib_dir, path.join(gst_root, 'lib', '')) + except Exception as e: + print("ERROR: could not package required dylibs") + print(e) + return False + return True def package_gstreamer_dlls(env, servo_exe_dir, target, uwp): |