Selaa lähdekoodia

Rewrite the fix-install-names script for post-install on macOS.

Yuxin Wang 3 vuotta sitten
vanhempi
commit
9db16c011e
1 muutettua tiedostoa jossa 43 lisäystä ja 82 poistoa
  1. 43 82
      scripts/fix-install-names.py

+ 43 - 82
scripts/fix-install-names.py

@@ -1,86 +1,47 @@
-#!/usr/bin/python
-
-import hashlib
-import optparse
-import ConfigParser
-import os
-import platform
-import subprocess
+from pathlib import Path
 import sys
+import argparse
+from collections import deque
+import subprocess
 import shutil
 
-exts = (".dylib", ".so")
-exes = ("fc-cache", "macdeployqt", "qmake", "moc", "rcc", "qmlimportscanner", "QtWebEngineProcess")
-
-def exec_cmd(args, env={}, supress_output=False):
-    cmd = subprocess.Popen(args, stdout = subprocess.PIPE, stderr = subprocess.STDOUT, env = env)
-    output = ''
-    while True:
-      out = cmd.stdout.read(1)
-      if out == '' and cmd.poll() != None:
-        break
-      if out != '':
-        if not supress_output:
-          sys.stdout.write(out)
-        output += out
-    if cmd.wait() != 0:
-      raise Exception("Command failed: \"%s\"" % " ".join(args), output)
-    return output
-
-def fix_install_name(path):
-  for root, dirs, files in os.walk(path):
-    for f in files:
-      fpath = os.path.join(root, f)
-
-      if os.path.basename(f) == "QtWebEngineProcess":
-        print "-- Adding rpath (%s) to %s" % (os.path.join(path, "lib"), f)
-        exec_cmd(["install_name_tool", "-add_rpath", os.path.join(path, "lib"), fpath], supress_output=True)
-
-      if (f.endswith(exts) or os.path.basename(f) in exes or (".framework/Versions/" in root and os.access(fpath, os.X_OK))) and not os.path.islink(fpath) and os.path.exists(fpath):
-        # Fix permissions
-        if not os.access(fpath, os.W_OK) or not os.access(fpath, os.R_OK) or not os.access(fpath, os.X_OK):
-          os.chmod(fpath, 0o644)
-
-        try:
-          basename = os.path.basename(fpath)
-          otoolout = exec_cmd(["otool", "-L", fpath], supress_output=True)
-
-          for l in otoolout.split("\n")[1:]:
-            l = l.rstrip().strip()
-
-            # See if we need to fix it up.
-            if len(l) > 0 and (l.startswith("/Volumes/CI-OSX/") or (l[0] != '/' and not l.startswith("@rpath"))):
-              current_lib = l.split(" (compat")[0]
-              current_basename = os.path.basename(current_lib)
-              correct_lib = os.path.join(root, current_basename)
-
-              if ".framework" in current_lib:
-                current_basename = "/".join(current_lib.split("/")[-4:])
-
-              if not os.path.exists(correct_lib):
-                # look for it further up, like in the root path:
-                if os.path.exists(os.path.join(path, "lib", current_basename)):
-                  correct_lib = os.path.join(path, "lib", current_basename)
-                elif os.path.exists(os.path.join(path, "lib", current_lib)):
-                  correct_lib = os.path.join(path, "lib", current_lib)
-                else:
-                  print "Can't link %s" % current_lib
-                  continue
-
-              # print current_lib, correct_lib
-              if current_lib != correct_lib:
-                if current_basename.split('.')[0] == basename.split('.')[0]:
-                  print "-- Fixing ID for", basename
-                  exec_cmd(["install_name_tool", "-id", correct_lib, fpath], supress_output=True)
-                else:
-                  print "-- Fixing library link for %s (%s)" % (basename, current_basename)
-                  exec_cmd(["install_name_tool", "-change", current_lib, correct_lib, fpath], supress_output=True)
-
-        except:
-          print "** Fail when running installname on %s" % f
-          raise
-          continue
 
-if __name__=='__main__':
-  if os.path.isdir(sys.argv[1]):
-    fix_install_name(sys.argv[1])
+def main(argv=tuple(sys.argv[1:])):
+    arg_parser = argparse.ArgumentParser(description='Fix third party library paths in .app bundles.')
+    arg_parser.add_argument('bundle', metavar='BUNDLE', type=str, nargs=1)
+    arguments = arg_parser.parse_args(argv)
+
+    bundle_path = Path(arguments.bundle[0])
+    framework_path = bundle_path / 'Contents' / 'Frameworks'
+    framework_libs = set(file.name for file in framework_path.glob('*.dylib')).union(set(file.name for file in framework_path.glob('*.so')))
+    libs_to_fix = deque()
+    libs_to_fix.extend(file for file in bundle_path.glob('**/*.dylib'))
+    libs_to_fix.extend(file for file in bundle_path.glob('**/*.so'))
+    while libs_to_fix:
+        lib = libs_to_fix.popleft()
+        # find the dependencies of the library
+        result = subprocess.check_output(['otool', '-L', str(lib.resolve())], stderr=subprocess.STDOUT).decode('utf-8')
+        for dependency in result.splitlines():
+            dependency = dependency.strip().lstrip()
+            if dependency.startswith('/usr/local'):
+                # cut off trailing compatibility string
+                dependency = Path(dependency.split(' (compatibility')[0])
+
+                # if somehow macdeployqt didn't copy the lib for us, we do a manual copy
+                if dependency.name not in framework_libs:
+                    shutil.copy(str(dependency.resolve()), str(framework_path.resolve()))
+                    framework_libs.add(dependency.name)
+                    # add the newly added library in to the to fix queue
+                    libs_to_fix.append(framework_path / dependency.name)
+                    print((framework_path / dependency.name).resolve())
+                    print(f'Copied {dependency} to {framework_path / dependency.name}')
+
+                # now we fix the path using install_name_tool
+                target = f'@executable_path/../Frameworks/{dependency.name}'
+                print(f'Fixing dependency {dependency} of {lib} to {target}')
+                subprocess.run(['install_name_tool', '-id', target, lib])
+                subprocess.run(['install_name_tool', '-change', str(dependency), target, lib])
+
+
+if __name__ == '__main__':
+    main()