0
0
mirror of https://github.com/mpv-player/mpv.git synced 2024-09-20 20:03:10 +02:00
mpv/TOOLS/dylib_unhell.py
der richter 56b1078bfd osxbundle: use dylib script directly instead of in a subprocess
before errors and outputs where ignored from the subscript and the main
script didn't fail nor did it output anything.

with this change the script properly outputs everything to stdout and
stderr. in the case the dylib script fails the whole script fails now.

the main function in dylib_unhell was kept since it can still be used
individually without the oscbundle script. the script had to be renamed
with an underscore to make it importable.
2024-02-24 20:04:16 +01:00

262 lines
9.7 KiB
Python
Executable File

#!/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():
return subprocess.check_output("brew --prefix", universal_newlines=True, shell=True).strip()
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])