summaryrefslogtreecommitdiffstats
path: root/TOOLS/dylib_unhell.py
diff options
context:
space:
mode:
Diffstat (limited to 'TOOLS/dylib_unhell.py')
-rwxr-xr-xTOOLS/dylib_unhell.py268
1 files changed, 268 insertions, 0 deletions
diff --git a/TOOLS/dylib_unhell.py b/TOOLS/dylib_unhell.py
new file mode 100755
index 0000000000..c885969284
--- /dev/null
+++ b/TOOLS/dylib_unhell.py
@@ -0,0 +1,268 @@
+#!/usr/bin/env python3
+
+import re
+import os
+import sys
+import shutil
+import subprocess
+import json
+from functools import partial
+
+sys_re = re.compile("^/System")
+usr_re = re.compile("^/usr/lib/")
+exe_re = re.compile("@executable_path")
+
+def is_user_lib(objfile, libname):
+ return not sys_re.match(libname) and \
+ not usr_re.match(libname) and \
+ not exe_re.match(libname) and \
+ not "libobjc." in libname and \
+ not "libSystem." in libname and \
+ not "libc." in libname and \
+ not "libgcc." in libname and \
+ not os.path.basename(libname) == 'Python' and \
+ not os.path.basename(objfile) in libname and \
+ not "libswift" in libname
+
+def otool(objfile, rapths):
+ command = "otool -L '%s' | grep -e '\t' | awk '{ print $1 }'" % objfile
+ output = subprocess.check_output(command, shell = True, universal_newlines=True)
+ libs = set(filter(partial(is_user_lib, objfile), output.split()))
+
+ libs_resolved = set()
+ libs_relative = set()
+ for lib in libs:
+ lib_path = resolve_lib_path(objfile, lib, rapths)
+ libs_resolved.add(lib_path)
+ if lib_path != lib:
+ libs_relative.add(lib)
+
+ return libs_resolved, libs_relative
+
+def get_rapths(objfile):
+ rpaths = []
+ command = "otool -l '%s' | grep -A2 LC_RPATH | grep path" % objfile
+ pathRe = re.compile(r"^\s*path (.*) \(offset \d*\)$")
+
+ try:
+ result = subprocess.check_output(command, shell = True, universal_newlines=True)
+ except:
+ return rpaths
+
+ for line in result.splitlines():
+ line_clean = pathRe.search(line).group(1).strip()
+ # resolve @loader_path
+ if line_clean.startswith('@loader_path/'):
+ line_clean = line_clean[len('@loader_path/'):]
+ line_clean = os.path.normpath(os.path.join(os.path.dirname(objfile), line_clean))
+ rpaths.append(line_clean)
+
+ return rpaths
+
+def get_rpaths_dev_tools(binary):
+ command = "otool -l '%s' | grep -A2 LC_RPATH | grep path | grep \"Xcode\\|CommandLineTools\"" % binary
+ result = subprocess.check_output(command, shell = True, universal_newlines=True)
+ pathRe = re.compile(r"^\s*path (.*) \(offset \d*\)$")
+ output = []
+
+ for line in result.splitlines():
+ output.append(pathRe.search(line).group(1).strip())
+
+ return output
+
+def resolve_lib_path(objfile, lib, rapths):
+ if os.path.exists(lib):
+ return lib
+
+ if lib.startswith('@rpath/'):
+ lib = lib[len('@rpath/'):]
+ for rpath in rapths:
+ lib_path = os.path.join(rpath, lib)
+ if os.path.exists(lib_path):
+ return lib_path
+ elif lib.startswith('@loader_path/'):
+ lib = lib[len('@loader_path/'):]
+ lib_path = os.path.normpath(os.path.join(objfile, lib))
+ if os.path.exists(lib_path):
+ return lib_path
+
+ raise Exception('Could not resolve library: ' + lib)
+
+def check_vulkan_max_version(version):
+ try:
+ result = subprocess.check_output("pkg-config vulkan --max-version=" + version, shell = True)
+ return True
+ except:
+ return False
+
+def get_homebrew_prefix():
+ # set default to standard ARM path, intel path is already in the vulkan loader search array
+ result = "/opt/homebrew"
+ try:
+ result = subprocess.check_output("brew --prefix", universal_newlines=True, shell=True, stderr=subprocess.DEVNULL).strip()
+ except:
+ pass
+
+ return result
+
+def install_name_tool_change(old, new, objfile):
+ subprocess.call(["install_name_tool", "-change", old, new, objfile], stderr=subprocess.DEVNULL)
+
+def install_name_tool_id(name, objfile):
+ subprocess.call(["install_name_tool", "-id", name, objfile], stderr=subprocess.DEVNULL)
+
+def install_name_tool_add_rpath(rpath, binary):
+ subprocess.call(["install_name_tool", "-add_rpath", rpath, binary])
+
+def install_name_tool_delete_rpath(rpath, binary):
+ subprocess.call(["install_name_tool", "-delete_rpath", rpath, binary])
+
+def libraries(objfile, result = dict(), result_relative = set(), rapths = []):
+ rapths = get_rapths(objfile) + rapths
+ libs_list, libs_relative = otool(objfile, rapths)
+ result[objfile] = libs_list
+ result_relative |= libs_relative
+
+ for lib in libs_list:
+ if lib not in result:
+ libraries(lib, result, result_relative, rapths)
+
+ return result, result_relative
+
+def lib_path(binary):
+ return os.path.join(os.path.dirname(binary), 'lib')
+
+def resources_path(binary):
+ return os.path.join(os.path.dirname(binary), '../Resources')
+
+def lib_name(lib):
+ return os.path.join("@executable_path", "lib", os.path.basename(lib))
+
+def process_libraries(libs_dict, libs_dyn, binary):
+ libs_set = set(libs_dict)
+ # Remove binary from libs_set to prevent a duplicate of the binary being
+ # added to the libs directory.
+ libs_set.remove(binary)
+
+ for src in libs_set:
+ name = lib_name(src)
+ dst = os.path.join(lib_path(binary), os.path.basename(src))
+
+ shutil.copy(src, dst)
+ os.chmod(dst, 0o755)
+ install_name_tool_id(name, dst)
+
+ if src in libs_dict[binary]:
+ install_name_tool_change(src, name, binary)
+
+ for p in libs_set:
+ if p in libs_dict[src]:
+ install_name_tool_change(p, lib_name(p), dst)
+
+ for lib in libs_dyn:
+ install_name_tool_change(lib, lib_name(lib), dst)
+
+ for lib in libs_dyn:
+ install_name_tool_change(lib, lib_name(lib), binary)
+
+def process_swift_libraries(binary):
+ command = ['xcrun', '--find', 'swift-stdlib-tool']
+ swiftStdlibTool = subprocess.check_output(command, universal_newlines=True).strip()
+ # from xcode11 on the dynamic swift libs reside in a separate directory from
+ # the std one, might need versioned paths for future swift versions
+ swiftLibPath = os.path.join(swiftStdlibTool, '../../lib/swift-5.0/macosx')
+ swiftLibPath = os.path.abspath(swiftLibPath)
+
+ command = [swiftStdlibTool, '--copy', '--platform', 'macosx', '--scan-executable', binary, '--destination', lib_path(binary)]
+
+ if os.path.exists(swiftLibPath):
+ command.extend(['--source-libraries', swiftLibPath])
+
+ subprocess.check_output(command, universal_newlines=True)
+
+ print(">> setting additional rpath for swift libraries")
+ install_name_tool_add_rpath("@executable_path/lib", binary)
+
+def process_vulkan_loader(binary, loaderName, loaderRelativeFolder, libraryNode):
+ # https://github.com/KhronosGroup/Vulkan-Loader/blob/main/docs/LoaderDriverInterface.md#example-macos-driver-search-path
+ # https://github.com/KhronosGroup/Vulkan-Loader/blob/main/docs/LoaderLayerInterface.md#macos-layer-discovery
+ loaderSystemSearchFolders = [
+ os.path.join(os.path.expanduser("~"), ".config", loaderRelativeFolder),
+ os.path.join("/etc/xdg", loaderRelativeFolder),
+ os.path.join("/usr/local/etc", loaderRelativeFolder),
+ os.path.join("/etc", loaderRelativeFolder),
+ os.path.join(os.path.expanduser("~"), ".local/share", loaderRelativeFolder),
+ os.path.join("/usr/local/share", loaderRelativeFolder),
+ os.path.join("/usr/share/vulkan", loaderRelativeFolder),
+ os.path.join(get_homebrew_prefix(), 'share', loaderRelativeFolder),
+ ]
+
+ loaderSystemFolder = ""
+ for loaderSystemSearchFolder in loaderSystemSearchFolders:
+ if os.path.exists(loaderSystemSearchFolder):
+ loaderSystemFolder = loaderSystemSearchFolder
+ break
+
+ if not loaderSystemFolder:
+ print(">>> could not find loader folder " + loaderRelativeFolder)
+ return
+
+ loaderBundleFolder = os.path.join(resources_path(binary), loaderRelativeFolder)
+ loaderSystemPath = os.path.join(loaderSystemFolder, loaderName)
+ loaderBundlePath = os.path.join(loaderBundleFolder, loaderName)
+ libraryRelativeFolder = "../../../Frameworks/"
+
+ if not os.path.exists(loaderSystemPath):
+ print(">>> could not find loader " + loaderName)
+ return
+
+ if not os.path.exists(loaderBundleFolder):
+ os.makedirs(loaderBundleFolder)
+
+ loaderSystemFile = open(loaderSystemPath, 'r')
+ loaderJsonData = json.load(loaderSystemFile)
+ librarySystemPath = os.path.join(loaderSystemFolder, loaderJsonData[libraryNode]["library_path"])
+
+ if not os.path.exists(librarySystemPath):
+ print(">>> could not find loader library " + librarySystemPath)
+ return
+
+ print(">>> modifiying and writing loader json " + loaderName)
+ loaderBundleFile = open(loaderBundlePath, 'w')
+ loaderLibraryName = os.path.basename(librarySystemPath)
+ loaderJsonData[libraryNode]["library_path"] = os.path.join(libraryRelativeFolder, loaderLibraryName)
+ json.dump(loaderJsonData, loaderBundleFile, indent=4)
+
+ print(">>> copying loader library " + loaderLibraryName)
+ frameworkBundleFolder = os.path.join(loaderBundleFolder, libraryRelativeFolder)
+ if not os.path.exists(frameworkBundleFolder):
+ os.makedirs(frameworkBundleFolder)
+ shutil.copy(librarySystemPath, os.path.join(frameworkBundleFolder, loaderLibraryName))
+
+def remove_dev_tools_rapths(binary):
+ for path in get_rpaths_dev_tools(binary):
+ install_name_tool_delete_rpath(path, binary)
+
+def process(binary):
+ binary = os.path.abspath(binary)
+ if not os.path.exists(lib_path(binary)):
+ os.makedirs(lib_path(binary))
+ print(">> gathering all linked libraries")
+ libs, libs_rel = libraries(binary)
+
+ print(">> copying and processing all linked libraries")
+ process_libraries(libs, libs_rel, binary)
+
+ print(">> removing rpath definitions towards dev tools")
+ remove_dev_tools_rapths(binary)
+
+ print(">> copying and processing swift libraries")
+ process_swift_libraries(binary)
+
+ print(">> copying and processing vulkan loader")
+ process_vulkan_loader(binary, "MoltenVK_icd.json", "vulkan/icd.d", "ICD")
+ if check_vulkan_max_version("1.3.261.1"):
+ process_vulkan_loader(binary, "VkLayer_khronos_synchronization2.json", "vulkan/explicit_layer.d", "layer")
+
+if __name__ == "__main__":
+ process(sys.argv[1])