1#!/usr/bin/env python3 2 3import os 4import sys 5import shutil 6import subprocess 7 8nocopy_list = [ 9 "@rpath/QtCore.framework/Versions/5/QtCore", 10 "@rpath/QtDBus.framework/Versions/5/QtDBus", 11 "@rpath/QtGui.framework/Versions/5/QtGui", 12 "@rpath/QtOpenGL.framework/Versions/5/QtOpenGL", 13 "@rpath/QtPrintSupport.framework/Versions/5/QtPrintSupport", 14 "@rpath/QtWidgets.framework/Versions/5/QtWidgets", 15] 16 17 18def fix_qt_frameworks(app): 19 frameworks_dir = os.path.join(app, "Contents", "Frameworks") 20 for framework in os.listdir(frameworks_dir): 21 if not framework.startswith("Qt"): 22 continue 23 framework_dir = os.path.join(frameworks_dir, framework) 24 print(framework) 25 name, ext = framework.split(".") 26 v5 = os.path.join(framework_dir, "Versions", "5") 27 assert os.path.exists(v5) 28 current = os.path.join(framework_dir, "Versions", "Current") 29 if not os.path.exists(current): 30 os.symlink("5", current) 31 resources = os.path.join(v5, "Resources") 32 if not os.path.exists(resources): 33 os.makedirs(resources) 34 # library_link = os.path.join(framework_dir, name) 35 # if not os.path.exists(library_link): 36 # os.symlink(os.path.join("Versions", "Current", name), library_link) 37 resources_link = os.path.join(framework_dir, "Resources") 38 if not os.path.exists(resources_link): 39 os.symlink("Versions/Current/Resources", resources_link) 40 plist = os.path.join(resources, "Info.plist") 41 with open(plist, "w", encoding="UTF-8") as f: 42 f.write("""<?xml version="1.0" encoding="UTF-8"?> 43<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 44<plist version="1.0"> 45<dict> 46 <key>CFBundleExecutable</key> 47 <string>{name}</string> 48 <key>CFBundleGetInfoString</key> 49 <string>Created by standalone.py</string> 50 <key>CFBundleIdentifier</key> 51 <string>org.qt-project.{name}</string> 52 <key>CFBundlePackageType</key> 53 <string>FMWK</string> 54 <key>CFBundleShortVersionString</key> 55 <string>1.0</string> 56 <key>CFBundleSignature</key> 57 <string>????</string> 58 <key>CFBundleVersion</key> 59 <string>1.0</string> 60</dict> 61</plist> 62""".format(name=name)) 63 64 65def fix_binary(path, macos_dir): 66 args = ["file", path] 67 p = subprocess.Popen(args, stdout=subprocess.PIPE) 68 data = p.stdout.read().decode("UTF-8") 69 p.wait() 70 if "Mach-O" not in data: 71 return 0 72 73 print("fixing", path) 74 changes = 0 75 if not os.path.exists(path): 76 raise Exception("could not find " + repr(path)) 77 78 args = ["otool", "-l", path] 79 p = subprocess.Popen(args, stdout=subprocess.PIPE) 80 data = p.stdout.read().decode("UTF-8") 81 p.wait() 82 83 # Adding to rpath causes problems with signing 84 # "because larger updated load commands do not fit" 85 # if "@executable_path/../Frameworks" not in data: 86 # args = ["install_name_tool", "-add_rpath", 87 # "@executable_path/../Frameworks", path] 88 # print(args) 89 # p = subprocess.Popen(args) 90 # p.wait() 91 92 # PyQt modules 93 if "@loader_path/Qt/lib" in data: 94 args = ["install_name_tool", "-rpath", "@loader_path/Qt/lib", 95 "@executable_path/../Frameworks", path] 96 print(args) 97 p = subprocess.Popen(args) 98 p.wait() 99 # Qt frameworks 100 if "@loader_path/../../lib" in data: 101 args = ["install_name_tool", "-rpath", "@loader_path/../../lib", 102 "@executable_path/../Frameworks", path] 103 print(args) 104 p = subprocess.Popen(args) 105 p.wait() 106 107 args = ["otool", "-L", path] 108 p = subprocess.Popen(args, stdout=subprocess.PIPE) 109 data = p.stdout.read().decode("UTF-8") 110 p.wait() 111 for line in data.split('\n'): 112 line = line.strip() 113 if not line: 114 continue 115 if line.startswith("/usr/lib") or line.startswith("/System"): 116 # old = line.split(' ')[0] 117 # print("ignoring", old) 118 continue 119 if line.startswith("@executable_path"): 120 continue 121 122 old = line.split(" ")[0] 123 # if old in ignore_list: 124 # continue 125 if old == "@rpath/XCTest.framework/Versions/A/XCTest": 126 continue 127 if "Contents" in old: 128 continue 129 print(old) 130 131 if os.path.basename(path) == os.path.basename(old): 132 if "/" not in old: 133 continue 134 os.chmod(path, 0o755) 135 args = ["install_name_tool", "-id", os.path.basename(path), path] 136 print(args) 137 p = subprocess.Popen(args) 138 assert p.wait() == 0 139 changes += 1 140 continue 141 142 # if not os.path.isabs(old): 143 # old = os.path.join( 144 # os.environ["DYLD_FALLBACK_LIBRARY_PATH"].split(":")[0], old) 145 146 old_dir, name = os.path.split(old) 147 # new = old.replace(old, '@executable_path/../Frameworks/' + name) 148 new = old.replace(old, '@executable_path/' + name) 149 # dst = os.path.join(frameworks_dir, os.path.basename(old)) 150 dst = os.path.join(macos_dir, os.path.basename(old)) 151 if not os.path.exists(dst): 152 print("copying", old) 153 libs_f.write(old + "\n") 154 shutil.copy(old, dst) 155 os.chmod(dst, 0o755) 156 changes += 1 157 # print(os.path.basename(path), "vs", os.path.basename(old)) 158 # if os.path.basename(path) == os.path.basename(old): 159 # args = ["install_name_tool", "-id", new, path] 160 # else: 161 os.chmod(path, 0o755) 162 args = ["install_name_tool", "-change", old, new, path] 163 print(args) 164 p = subprocess.Popen(args) 165 assert p.wait() == 0 166 167 return changes 168 169 170def fix_iteration(app): 171 binaries = [] 172 macos_dir = os.path.join(app, "Contents", "MacOS") 173 extra_paths = [] 174 for dir_path, dir_names, file_names in os.walk(macos_dir): 175 for name in file_names: 176 p = os.path.join(dir_path, name) 177 if os.path.isdir(p): 178 pass 179 elif p.endswith("_debug.dylib"): 180 pass 181 else: 182 binaries.append(p) 183 # for name in os.listdir(macos_dir): 184 # p = os.path.join(macos_dir, name) 185 # if os.path.isdir(p): 186 # extra_paths.append(p) 187 # else: 188 # binaries.append(os.path.join(macos_dir, name)) 189 # for extra_dir in extra_paths: 190 # for name in os.listdir(extra_dir): 191 # p = os.path.join(extra_dir, name) 192 # if os.path.isdir(p): 193 # for name2 in os.listdir(p): 194 # if not name2.endswith("_debug.dylib"): 195 # binaries.append(os.path.join(p, name2)) 196 # else: 197 # binaries.append(p) 198 199 frameworks_dir = os.path.join(app, "Contents", "Frameworks") 200 if os.path.exists(frameworks_dir): 201 for dir_path, dir_names, file_names in os.walk(frameworks_dir): 202 for name in file_names: 203 p = os.path.join(dir_path, name) 204 binaries.append(p) 205 206 # Qt plugins 207 plugins_dir = os.path.join(app, "Contents", "PlugIns") 208 if os.path.exists(plugins_dir): 209 for dir_path, dir_names, file_names in os.walk(plugins_dir): 210 for name in file_names: 211 p = os.path.join(dir_path, name) 212 binaries.append(p) 213 214 changes = 0 215 for binary in binaries: 216 changes += fix_binary(binary, macos_dir) 217 return changes 218 219 220def main(): 221 global libs_f 222 app = sys.argv[1] 223 # fix_qt_frameworks(app) 224 with open("libs.txt", "w") as libs_f: 225 while True: 226 changes = fix_iteration(app) 227 if changes == 0: 228 break 229 230 231if __name__ == "__main__": 232 main() 233