9 from collections
import defaultdict
10 from functools
import partial
11 from subprocess
import Popen, PIPE
13 def p(x):
print "==",x.name,
"==" 18 'ApplicationServices',
36 'SystemConfiguration',
47 def cached_property(func):
48 from functools
import wraps
53 return self.__dict__[name]
56 self.__dict__[name]= value
59 self.__dict__.pop(name,
None)
60 return property(_get,
None, _del)
62 def try_remove_item(list_object):
65 list_object.remove(item)
80 def no_executable_path_filter(dep):
82 DepFilter.dep_filter(dep)
83 and not dep.startswith(
'@executable_path')
90 ignored = defaultdict(set)
91 fw_re = re.compile(
'.*?(?P<fw>[^/]+\.framework.+)')
93 def __init__(self, path, dep_filter):
95 m = BinaryFile.fw_re.match(path)
97 self.
name = m.groupdict()[
'fw']
99 self.
name = os.path.basename(path)
104 otool = Popen([
'otool',
'-L', self.
path], stdout=PIPE)
105 output = otool.communicate()[0]
107 if not otool.returncode:
108 binfiles = [l.strip().split()[0]
for l
in output.splitlines()[1:]]
110 for binfile
in filter(self.
dep_filter, binfiles) ]
111 return dict((l.name, l)
for l
in binfiles)
116 return ''.join([
'BinaryFile(path=', repr(self.
path),
')'])
119 return ': '.join((self.
name, self.
path))
121 def __eq__(self,other):
122 return self.
name == other.name
125 return hash(self.
name)
128 def find(path, pattern, dep_filter):
129 reg = fnmatch.translate(pattern)
130 reg = re.compile(reg +
'|[^.]+\Z')
133 if os.path.isfile(path):
136 for (root, dirs, files)
in os.walk(path):
137 map(try_remove_item(dirs), [
'Headers',
'Resources'])
138 files = [
BinaryFile(os.path.join(root,f), dep_filter)
for f
in files]
139 binfiles += [f
for f
in files
if match(f.name)]
140 return dict((l.name, l)
for l
in binfiles)
143 def install_name_id(self, path_func):
144 return [[
'-id', path_func(self.
path)]]
146 def install_name_changes(self, lib_set, path_func, ignored = None):
148 deps_set = set(deps.values())
149 inter = lib_set.intersection(deps_set)
150 if ignored
is not None:
151 ignored_libs = [lib.name
for lib
in deps_set.difference(lib_set)]
152 ignored.update(ignored_libs)
153 for ig
in ignored_libs:
154 BinaryFile.ignored[ig].add(self)
156 [
'-change', deps[lib.name].path,
157 path_func(self.
db[lib.name].path) ]
for lib
in inter
164 def get_options(args):
165 from optparse
import OptionParser
167 usage =
"usage: %prog [options] binary_or_dir ..." 168 epilog =
"""Updates the install names of executable or shared libraries 169 found in argument list (binary_or_dir). 171 parser = OptionParser(usage=usage, epilog=
"")
173 parser.add_option(
"-a",
"--absolute-path",
174 action=
"store_true", dest=
"abs_path", default=
False,
175 help=(
"Uses absolute path for install names that will be " 179 parser.add_option(
"-e",
"--executable-path", dest=
"exec_path",
180 help=(
"specifies the executable path to which the install " 181 "names will be relocated. disabled if '-a' is used."),
188 parser.add_option(
"-f",
"--force",
189 action=
"store_true", dest=
"force", default=
False,
190 help=(
"Updates dependency even if it already has an " 191 "@executable_path "),
195 parser.add_option(
"-p",
"--progress",
196 action=
"store_true", dest=
"progress", default=
False,
197 help=
"show progress")
199 parser.add_option(
"-i",
"--show-ignored",
200 action=
"store_true", dest=
"show_ignored", default=
False,
201 help=
"show libraries that have not been relocated")
204 parser.add_option(
"-u",
"--show-suspiciously-ignored",
205 action=
"count", dest=
"show_suspiciously_ignored",
207 help=
"show libraries that have not been relocated " 208 "and should have. Set it twice to have more details on " 209 "wich libralibvtkVisuManagement.dylibries was requirering one of these.")
212 parser.add_option(
"-v",
"--verbose",
213 action=
"store_true", dest=
"verbose", default=
False,
214 help=
"verbose output")
216 parser.add_option(
"-P",
"--search-pattern", dest=
"search_pattern",
217 default=
"*.dylib", help= (
"specifies the executable path to " 218 "which the install names will be relocated. " 219 "disabled if '-a' is used."), metavar=
"PATTERN")
221 parser.add_option(
"-s",
"--search",
222 action=
"append", dest=
"search_dirs", default=[],
223 help=
"this directory will be searched with pattern " 224 "provided by -P", metavar=
"DIR")
228 (options, args) = parser.parse_args(args)
229 return (options, args)
232 def main(options, args):
234 print ' '.join(map(str,a))
236 print ' '.join(map(str,a)),
243 progress_print = mute
244 progress_print_n = mute
248 verbose_n = print_msg_n
251 progress_print = print_msg
252 progress_print_n = print_msg_n
256 dep_filter = DepFilter.dep_filter
258 dep_filter = DepFilter.no_executable_path_filter
260 find_binary_files = partial(BinaryFile.find, dep_filter = dep_filter)
261 update_db =
lambda pattern :
lambda path : BinaryFile.db.update(
262 find_binary_files(path, pattern)
265 map(update_db(
'*.dylib'), args)
266 map(update_db(
'*.framework/Versions/*'), args)
267 map(update_db(options.search_pattern), options.search_dirs)
269 relpath = os.path.relpath
270 librelpath =
lambda path : os.path.join(
'@executable_path', relpath( path, options.exec_path ) )
272 abspath = os.path.abspath
273 libabspath =
lambda path : abspath( path )
276 install_name_func = libabspath
278 install_name_func = librelpath
282 L = len(BinaryFile.db)
283 files = set(BinaryFile.db.values())
285 for n,lib
in enumerate(files):
287 progress_print_n(
' ',
'{0}/{1}'.format(n+1,L),
'\r')
289 if not os.path.islink(lib_file):
290 changes = lib.install_name_id(install_name_func)
291 changes += lib.install_name_changes(files, install_name_func, ignored)
293 for change
in changes:
294 os.chmod(lib_file, 0775)
295 cmd = [
'install_name_tool',] + change + [lib_file,]
296 res = Popen(cmd, stdout=PIPE).communicate()[0]
302 options.show_ignored,
303 options.show_suspiciously_ignored,
305 ignored = sorted( map(str,ignored))
307 if options.show_ignored:
308 print 'ignored libraries:', (os.linesep+
' ').join(ignored)
310 if options.show_suspiciously_ignored:
312 match =
lambda x :
not any( ig
in x
for ig
in usually_ignored )
313 suspicious = [ig
for ig
in ignored
if match(ig)]
314 libsep = (os.linesep+
' ')
315 depsep = (os.linesep+
' - ')
316 if options.show_suspiciously_ignored == 1:
317 print 'suspiciously ignored :', libsep + libsep.join(suspicious)
319 si = [[
"<%s>"%s +
', required by :']
320 + [l.name
for l
in BinaryFile.ignored[s]]
for s
in suspicious]
321 print 'suspiciously ignored :', (libsep
322 + libsep.join(depsep.join(d)
for d
in si))
325 if __name__ ==
'__main__':
326 (options, args) = get_options(sys.argv[1:])