我有一个Cocoa应用程序,它使用otool来查找应用程序需要正常运行所需的共享库。例如,假设我在使用QTKit.framework的应用程序上运行otool -L。我得到了程序使用的共享库列表(包括Cocoa.framework和AppKit.framework等基本框架):
/System/Library/Frameworks/QTKit.framework/Versions/A/QTKit (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 476.0.0)
/System/Library/Frameworks/AppKit.framework/Versions/C/AppKit (compatibility version 45.0.0, current version 949.0.0)
..... and so on for a bunch of other frameworks
这表明该应用使用了QTKit.framework。但是,如果我在QTKit.framework(/System/Library/Frameworks/QTKit.framework/Versions/A/QTKit)的二进制文件上再次使用“otool -L”,我会得到:
/System/Library/Frameworks/QTKit.framework/Versions/A/QTKit (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/AudioToolbox.framework/Versions/A/AudioToolbox (compatibility version 1.0.0, current version 1.0.0)
/System/Library/PrivateFrameworks/CoreMedia.framework/Versions/A/CoreMedia (compatibility version 1.0.0, current version 1.0.0)
/System/Library/PrivateFrameworks/MediaToolbox.framework/Versions/A/MediaToolbox (compatibility version 1.0.0, current version 1.0.0)
/System/Library/PrivateFrameworks/VideoToolbox.framework/Versions/A/VideoToolbox (compatibility version 1.0.0, current version 1.0.0)
/System/Library/PrivateFrameworks/CoreMediaIOServices.framework/Versions/A/CoreMediaIOServices (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation (compatibility version 300.0.0, current version 751.0.0)
/System/Library/Frameworks/AppKit.framework/Versions/C/AppKit (compatibility version 45.0.0, current version 1038.0.0)
/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit (compatibility version 1.0.0, current version 275.0.0)
/System/Library/Frameworks/QuickTime.framework/Versions/A/QuickTime (compatibility version 1.0.0, current version 1584.0.0)
/System/Library/Frameworks/CoreAudio.framework/Versions/A/CoreAudio (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/OpenGL.framework/Versions/A/OpenGL (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/QuartzCore.framework/Versions/A/QuartzCore (compatibility version 1.2.0, current version 1.6.0)
/System/Library/Frameworks/IOSurface.framework/Versions/A/IOSurface (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/HIToolbox (compatibility version 1.0.0, current version 435.0.0)
/usr/lib/libstdc++.6.dylib (compatibility version 7.0.0, current version 7.9.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 123.0.0)
/usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 227.0.0)
/System/Library/Frameworks/CoreServices.framework/Versions/A/CoreServices (compatibility version 1.0.0, current version 44.0.0)
/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 550.0.0)
/System/Library/Frameworks/ApplicationServices.framework/Versions/A/ApplicationServices (compatibility version 1.0.0, current version 38.0.0)
/System/Library/Frameworks/CoreVideo.framework/Versions/A/CoreVideo (compatibility version 1.2.0, current version 1.6.0)
这表明加载了更多框架,这些框架是应用程序二进制文件上的原始otool输出显示的。 有没有办法让otool以递归方式运行,这意味着它抓住应用程序需要的框架,然后进入并搜索每个框架中的依赖项?
答案 0 :(得分:9)
不,您必须重复运行otool,或者合并其解析代码(here)。不要忘记处理@executable_path
。
这是Python(没有@executable_path
,规范化或支持空格的文件名),因为这比尝试调试伪代码更容易:
import subprocess
def otool(s):
o = subprocess.Popen(['/usr/bin/otool', '-L', s], stdout=subprocess.PIPE)
for l in o.stdout:
if l[0] == '\t':
yield l.split(' ', 1)[0][1:]
need = set(['/Applications/iTunes.app/Contents/MacOS/iTunes'])
done = set()
while need:
needed = set(need)
need = set()
for f in needed:
need.update(otool(f))
done.update(needed)
need.difference_update(done)
for f in sorted(done):
print f
答案 1 :(得分:1)
这是我的解决方案,用于在使用Homebrew安装的库时修复macdeployqt
的输出。我发现macdeployqt
很好地将dylib放在Framework文件夹中,但是无法修复路径。
https://github.com/jveitchmichaelis/deeplabel/blob/master/fix_paths_mac.py
我修改了Nicholas的脚本,使其更加实用-它可以更正@executable_path
,@rpath
和@loader_path
。这不完全是生产代码,但是它使我可以在其他Mac上运行应用程序,而无需安装任何依赖项。
运行:python fix_paths_mac.py ./path/to/your.app/Contents/MacOS/your_exe
。也就是说,将其指向应用程序包中的二进制文件,它将找出其余的二进制文件。
我假设大多数问题来自与/usr/local
链接的内容。因此,如果代码检测到存在指向/usr/local
中文件的依赖项,它将适当地修复路径。您可以更改pass
语句以将其复制到文件中(如果该文件不在Frameworks
文件夹中,但是我没有遇到缺少dylib的情况,它只是链接错误。
import subprocess
import os
import sys
from shutil import copyfile
executable = sys.argv[1]
app_folder = os.path.join(*executable.split('/')[:-3])
content_folder = os.path.join(app_folder, "Contents")
framework_path = os.path.join(content_folder, "Frameworks")
print(executable)
print("Working in {} ".format(app_folder))
def file_in_folder(file, folder):
return os.path.exists(os.path.join(folder, file))
def otool(s):
o = subprocess.Popen(['/usr/bin/otool', '-L', s], stdout=subprocess.PIPE)
for l in o.stdout:
l = l.decode()
if l[0] == '\t':
path = l.split(' ', 1)[0][1:]
if "@executable_path" in path:
path = path.replace("@executable_path", "")
# fudge here to strip /../ from the start of the path.
path = os.path.join(content_folder, path[4:])
if "@loader_path" in path:
path = path.replace("@loader_path", framework_path)
if "@rpath" in path:
path = path.replace("@rpath", framework_path)
dependency_dylib_name = os.path.split(path)[-1]
if "usr/local" in path:
if app_folder in s:
print("Warning: {} depends on {}".format(s, path))
if file_in_folder(dependency_dylib_name, framework_path):
print("Dependent library {} is already in framework folder".format(dependency_dylib_name))
print("Running install name tool to fix {}.".format(s))
if dependency_dylib_name == os.path.split(s)[-1]:
_ = subprocess.Popen(['install_name_tool', '-id', os.path.join("@loader_path", dependency_dylib_name), s], stdout=subprocess.PIPE)
_ = subprocess.Popen(['install_name_tool', '-change', path, os.path.join("@loader_path", dependency_dylib_name), s], stdout=subprocess.PIPE)
else:
# Potentially you could copy in the offending dylib here.
pass
yield path
need = set([executable])
done = set()
while need:
needed = set(need)
need = set()
for f in needed:
need.update(otool(f))
done.update(needed)
need.difference_update(done)
答案 2 :(得分:0)
我下面的虚拟脚本运行良好。没有过度设计,因为它只是一个简单的实用程序脚本,很少用于调试目的。
#!/usr/bin/env python
import subprocess
import re
import sys
discovered = []
def library_finder(lib):
lib = lib.split(':')[0]
lib = lib.split(' ')[0]
lib = re.sub(r"[\n\t\s]*", "", lib)
if lib in discovered:
return
discovered.append(lib)
print(lib)
if lib.startswith("@rpath"):
return
process = subprocess.Popen(['otool', '-L', lib],
stdout=subprocess.PIPE,
universal_newlines=True)
deps = process.stdout.readlines()
for dep in deps:
library_finder(dep)
if len(sys.argv) < 2:
print("usage: {} <binary path>".format(sys.argv[0]))
sys.exit(1)
library_finder(sys.argv[1])