macOS下,应用加载依赖的dylib或Framework需要根据rpath
等信息去加载,而链接后默认依赖的路径是机器上的路径(可以通过otool命令查看),不方便打包出去使用。
下面的脚本信息能把应用依赖的dylib等拷贝到相同的输出目录,并且使用install_name_tool
设置好相应的依赖,输出的目录下可以单独使用。
collect_bin.sh
#!/bin/bash
python $(dirname "$0")/collect_bin.py "$@"
collect_bin.py
# -*- coding: UTF-8 -*-
import sys
import os
import argparse
# not copied
blacklist = """/usr /System""".split()
# copied
whitelist = """/usr/local""".split()
from sys import argv
from glob import glob
from subprocess import check_output, call
from collections import namedtuple
from shutil import copy, copytree, rmtree
from os import makedirs, rename, walk, path as ospath
import plistlib
import subprocess
LibTarget = namedtuple("LibTarget", ("path", "external", "copy_as"))
inspect = list()
inspected = set()
build_path = ""
def cmd(cmd):
import subprocess
import shlex
return subprocess.check_output(shlex.split(cmd)).rstrip('\r\n')
def add(name, external=False, copy_as=None):
if external and copy_as is None:
copy_as = name.split("/")[-1]
t = LibTarget(name, external, copy_as)
if t in inspected:
return
inspect.append(t)
inspected.add(t)
def otoolAnalyse(bin_file, target_path):
add(bin_file, True)
rmtree(target_path)
makedirs(target_path)
# 基于otool分析循环依赖
while inspect:
target = inspect.pop()
print("inspecting", repr(target))
path = target.path
if path[0] == "@":
continue
out = check_output("otool -L '{0}'".format(path), shell=True,
universal_newlines=True)
for line in out.split("\n")[1:]:
new = line.strip().split(" (")[0]
if not new or new[0] == "@" or new.endswith(path.split("/")[-1]):
continue
whitelisted = False
for i in whitelist:
if new.startswith(i):
whitelisted = True
break
if not whitelisted:
blacklisted = False
for i in blacklist:
if new.startswith(i):
blacklisted = True
break
if blacklisted:
continue
add(new, True)
# 所有需要处理的依赖
changes = list()
for path, external, copy_as in inspected:
if not external:
continue # built with install_rpath hopefully
changes.append("-change '%s' '@rpath/%s'" % (path, copy_as))
changes = " ".join(changes)
# install_name_tool逐个处理
for path, external, copy_as in inspected:
id_ = ""
filename = path
rpath = ""
if external:
id_ = "-id '@rpath/%s'" % copy_as
filename = target_path + copy_as
rpath = "-add_rpath @loader_path/ -add_rpath @executable_path/"
if "/" in copy_as:
try:
dirs = copy_as.rsplit("/", 1)[0]
makedirs( target_path + dirs)
except:
pass
copy(path, filename)
os.chmod(filename, 0755)
tool_cmd = "install_name_tool {0} {1} {2} '{3}'".format(changes, id_, rpath, filename)
call(tool_cmd, shell=True)
def collect_run():
parser = argparse.ArgumentParser()
parser.add_argument("--bin", help="bin file")
parser.add_argument("--target", help="target path dir")
args = parser.parse_args()
bin_file = args.bin
target_path = args.target
if not target_path.endswith("/"):
target_path = target_path + "/"
print "Bin file: " + bin_file
print "Target path: " + target_path
otoolAnalyse(bin_file, target_path)
if __name__ == '__main__':
collect_run()
使用
./collect_bin.sh --bin "/usr/local/bin/ffmpeg" --target "/Users/xxx/tmpbin/"