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
|
"""Methods to fix Mac OS X @rpath and /usr/local dependencies, and analyze the
app bundle for OpenShot to find all external dependencies"""
import os
import subprocess
from subprocess import call
import re
import shutil
# Ignore non library/executable extensions
non_executables = [".py", ".svg", ".png", ".blend", ".a", ".pak", ".qm", ".pyc", ".txt",
".jpg", ".zip", ".dat", ".conf", ".xml", ".h", ".ui", ".json", ".exe"]
def fix_rpath(PATH):
"""FIX broken @rpath on Qt, PyQt, and /usr/local/ dependencies with no @rpath"""
# Cleanup duplicate folder and files (that cx_freeze creates)
duplicate_path = os.path.join(PATH, "lib", "openshot_qt")
if os.path.exists(duplicate_path):
shutil.rmtree(duplicate_path)
# Find files matching patterns
for root, dirs, files in os.walk(PATH):
for basename in files:
file_path = os.path.join(root, basename)
relative_path = os.path.relpath(root, PATH)
# Skip common file extensions (speed things up)
if os.path.splitext(file_path)[-1] in non_executables or basename.startswith("."):
continue
# Build exe path (relative from the app dir)
executable_path = os.path.join("@executable_path", relative_path, basename)
if relative_path == ".":
executable_path = os.path.join("@executable_path", basename)
# Change ID path of library files
# Sometimes, the ID has an absolute path or invalid @rpath embedded in it, which breaks our frozen exe
print ("ID: %s: install_name_tool %s -id %s" % (basename, file_path, executable_path))
call(["install_name_tool", file_path, "-id", executable_path])
# Loop through all dependencies of each library/executable
raw_output = subprocess.Popen(["oTool", "-L", file_path], stdout=subprocess.PIPE).communicate()[0].decode('utf-8')
for output in raw_output.split("\n")[1:-1]:
if output and "is not an object file" not in output and ".o):" not in output:
dependency_path = output.split('\t')[1].split(' ')[0]
dependency_version = output.split('\t')[1].split(' (')[1].replace(')', '')
dependency_base_path, dependency_name = os.path.split(dependency_path)
# If @rpath or /usr/local found in dependency path, update with @executable_path instead
if "/usr/local" in dependency_path or "@rpath" in dependency_path:
dependency_exe_path = os.path.join("@executable_path", dependency_name)
if not os.path.exists(os.path.join(PATH, dependency_name)):
print("ERROR: /usr/local PATH not found in EXE folder: %s" % dependency_path)
else:
print('-- Dependency: %s: install_name_tool "%s" -change "%s" "%s"' % (dependency_name, file_path, dependency_path, dependency_exe_path))
call(["install_name_tool", file_path, "-change", dependency_path, dependency_exe_path])
def print_min_versions(PATH):
"""Print ALL MINIMUM and SDK VERSIONS for files in OpenShot build folder.
This does not list all dependent libraries though, and sometimes one of those can cause issues."""
# Use 2 different matches, due to different output from different libraries (depending on compiler)
REGEX_SDK_MATCH = re.compile(r'.*(LC_VERSION_MIN_MACOSX).*version (\d+\.\d+).*sdk (\d+\.\d+).*(cmd)', re.DOTALL)
REGEX_SDK_MATCH2 = re.compile(r'.*sdk\s(.*)\s*minos\s(.*)')
VERSIONS = {}
# Find files matching patterns
for root, dirs, files in os.walk(PATH):
for basename in files:
file_path = os.path.join(root, basename)
file_parts = file_path.split("/")
# Skip common file extensions (speed things up)
if os.path.splitext(file_path)[-1] in non_executables or basename.startswith("."):
continue
raw_output = subprocess.Popen(["oTool", "-l", file_path], stdout=subprocess.PIPE).communicate()[0].decode('utf-8')
matches = REGEX_SDK_MATCH.findall(raw_output)
matches2 = REGEX_SDK_MATCH2.findall(raw_output)
min_version = None
sdk_version = None
if matches and len(matches[0]) == 4:
min_version = matches[0][1]
sdk_version = matches[0][2]
elif matches2 and len(matches2[0]) == 2:
sdk_version = matches2[0][0]
min_version = matches2[0][1]
if min_version and sdk_version:
print("... scanning %s for min version (min: %s, sdk: %s)" % (file_path.replace(PATH, ""),
min_version, sdk_version))
# Organize by MIN version
if min_version in VERSIONS:
if file_path not in VERSIONS[min_version]:
VERSIONS[min_version].append(file_path)
else:
VERSIONS[min_version] = [file_path]
if min_version in ['11.0']:
print("ERROR!!!! Minimum OS X version not met for %s" % file_path)
print("\nSummary of Minimum Mac SDKs for Dependencies:")
for key in sorted(VERSIONS.keys()):
print("\n%s" % key)
for file_path in VERSIONS[key]:
print(" %s" % file_path)
print("\nCount of Minimum Mac SDKs for Dependencies:")
for key in sorted(VERSIONS.keys()):
print("%s (%d)" % (key, len(VERSIONS[key])))
if __name__ == "__main__":
# Run these methods manually for testing
# XXX: This path should be set programmatically, somehow
PATH = "/Users/jonathanthomas/apps/openshot-qt/build/exe.macosx-10.15-x86_64-3.7"
fix_rpath(PATH)
print_min_versions(PATH)
|