1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
|
import argparse
import os
import subprocess
import re
def is_system_lib(path):
return path.startswith('/System/Library/') or path.startswith('/usr/lib/')
def macos_collect_rpaths(file):
result = []
with subprocess.Popen(['otool', '-l', file], stdout=subprocess.PIPE) as p:
lines = [line.decode('utf-8').strip() for line in p.stdout.readlines()]
for line_index in range(len(lines)):
if lines[line_index] == 'cmd LC_RPATH':
rpath_match = re.match(r'path\s+(.*)\s+\(', lines[line_index + 2])
if rpath_match:
rpath = rpath_match.group(1)
if rpath not in result:
result.append(rpath)
line_index = line_index + 1
return result
def get_dylib_id(file):
with subprocess.Popen(['otool', '-D', file], stdout=subprocess.PIPE) as p:
lines = [line.decode('utf-8').strip() for line in p.stdout.readlines()]
if len(lines) == 2:
return lines[1].split('/')[-1]
return ''
def macos_collect_dependencies(file):
result = []
dylib_id = get_dylib_id(file)
with subprocess.Popen(['otool', '-L', file], stdout=subprocess.PIPE) as p:
lines = [line.decode('utf-8').strip() for line in p.stdout.readlines()]
for line in lines:
match = re.match(r'(.*)\s+\(', line)
if not match:
continue
lib_line = match.group(1)
if not lib_line.startswith('@loader_path') and not is_system_lib(lib_line):
name = lib_line.split('/')[-1]
if name != dylib_id:
result.append((name, lib_line))
return result
def run_install_name_tool(file, dep_path_prefix):
dependencies = macos_collect_dependencies(file)
rpaths = macos_collect_rpaths(file)
if len(dependencies) == 0 and len(rpaths) == 0:
return
install_name_tool = ['install_name_tool']
for name, lib_line in dependencies:
install_name_tool.append('-change')
install_name_tool.append(lib_line)
install_name_tool.append(dep_path_prefix + '/' + name)
for rpath in rpaths:
install_name_tool.append('-delete_rpath')
install_name_tool.append(rpath)
install_name_tool.append(file)
print(' '.join(install_name_tool))
try:
subprocess.check_call(install_name_tool)
except subprocess.CalledProcessError as err:
print(f'=========\ninstall_name_tool failed with code {err.returncode}\n\tstdout: {err.stdout}\n\tstderr: {err.stderr}\n=========')
if err.returncode != -9:
raise
print("install_name_tool was killed. Retrying with x86_64 architecture...")
install_name_tool = ["arch", "-arch", "x86_64"] + install_name_tool
try:
subprocess.run(install_name_tool, capture_output=True, check=True, text=True)
except subprocess.CalledProcessError as inner_err:
print(f'=========\ninstall_name_tool failed with code {inner_err.returncode}\n\tstdout: {inner_err.stdout}\n\tstderr: {inner_err.stderr}\n=========')
raise
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('bundle', help='The bundle to fix')
parser.add_argument('-config', help='Configuration type')
args = parser.parse_args()
if (args.config == "Debug"):
exit(0)
bundle = args.bundle
if not os.path.exists(bundle):
print('Bundle not found: ' + bundle)
exit(1)
if not os.path.isdir(bundle):
run_install_name_tool(bundle, '@loader_path/../Frameworks')
if bundle.find('modules') != -1:
exit(0)
bundle = os.path.join(os.path.dirname(args.bundle), '..', '..')
bundle = os.path.abspath(bundle)
print(f"Fixing bundle {bundle}")
frameworks_dir = os.path.join(bundle, 'Contents', 'Frameworks')
if not os.path.exists(frameworks_dir):
print('Bundle does not contain a Frameworks directory: ' + bundle)
exit(1)
for file in os.listdir(frameworks_dir):
file_path = os.path.join(frameworks_dir, file)
if os.path.isfile(file_path):
run_install_name_tool(file_path, '@loader_path')
|