File indexing completed on 2024-06-16 04:06:49
0001 #!/usr/bin/env python2.7 0002 0003 # Script to list recursive dylib dependencies for binaries/dylibs passed as arguments. 0004 # Modified from https://github.com/mixxxdj/mixxx/blob/master/build/osx/otool.py. 0005 # 0006 # SPDX-FileCopyrightText: 2015 by Shanti <listaccount at revenant dot org> 0007 # SPDX-FileCopyrightText: 2015-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0008 # 0009 # SPDX-License-Identifier: BSD-3-Clause 0010 # 0011 0012 import sys 0013 import os 0014 import re 0015 import argparse 0016 0017 # --------------------------------------------------------------- 0018 0019 def system(s): 0020 "wrap system() to give us feedback on what it's doing" 0021 "anything using this call should be fixed to use SCons's declarative style (once you figure that out, right nick?)" 0022 print s, 0023 sys.stdout.flush() # ignore line buffering. 0024 result = os.system(s) 0025 print 0026 return result 0027 0028 # --------------------------------------------------------------- 0029 0030 SYSTEM_FRAMEWORKS = ["/System/Library/Frameworks"] 0031 SYSTEM_LIBPATH = ["/usr/lib"] # anything else? 0032 0033 # paths to libs that we should copy in 0034 LOCAL_FRAMEWORKS = [ 0035 os.path.expanduser("~/Library/Frameworks"), 0036 "/Library/Frameworks", 0037 "/Network/Library/Frameworks" 0038 ] 0039 0040 LOCAL_LIBPATH = filter(lambda x: 0041 os.path.isdir(x), 0042 ["/usr/local/lib", 0043 "/opt/local/lib", 0044 "/sw/local/lib"] 0045 ) 0046 0047 # However 0048 FRAMEWORKS = LOCAL_FRAMEWORKS + SYSTEM_FRAMEWORKS 0049 LIBPATH = LOCAL_LIBPATH + SYSTEM_LIBPATH 0050 0051 # --------------------------------------------------------------- 0052 0053 # otool parsing 0054 def otool(binary): 0055 0056 "return in a list of strings the OS X 'install names' of Mach-O binaries (dylibs and programs)" 0057 "Do not run this on object code archive (.a) files, it is not designed for that." 0058 #if not os.path.exists(binary): raise Exception("'%s' not found." % binary) 0059 0060 if not type(binary) == str: raise ValueError("otool() requires a path (as a string)") 0061 0062 stdin, stdout, stderr = os.popen3('otool -L "%s"' % binary) 0063 0064 try: 0065 # discard the first line since it is just the name of the file or an error message (or if reading .as, the first item on the list) 0066 header = stdout.readline() 0067 0068 if not binary+":\n" == header: 0069 0070 # as far as I know otool -L only parses .dylibs and .a (but if it does anything else we should cover that case here) 0071 if header.startswith("Archive : "): 0072 raise Exception("'%s' an archive (.a) file." % binary) 0073 else: 0074 raise Exception(stderr.readline().strip()) 0075 0076 def parse(l): 0077 return l[1:l.rfind("(")-1] 0078 0079 return [parse(l[:-1]) for l in stdout] #[:-1] is for stripping the trailing \n 0080 0081 finally: 0082 stdin.close() 0083 stdout.close() 0084 stderr.close() 0085 0086 # --------------------------------------------------------------- 0087 0088 def dependencies(binary): 0089 l = otool(binary) 0090 0091 if os.path.basename(l[0]) == os.path.basename(binary): 0092 id = l.pop(0) 0093 #print "Removing -id field result %s from %s" % (id, binary) 0094 return l 0095 0096 # --------------------------------------------------------------- 0097 0098 def embed_dependencies(binary, 0099 # Defaults from 0100 # http://www.kernelthread.com/mac/osx/programming.html 0101 LOCAL = [os.path.expanduser("~/Library/Frameworks"), 0102 "/Library/Frameworks", 0103 "/Network/Library/Frameworks", 0104 "/usr/local/lib", 0105 "/opt/local/lib", 0106 "/sw/local/lib"], 0107 SYSTEM = ["/System/Library/Frameworks", 0108 "/Network/Library/Frameworks", 0109 "/usr/lib"]): 0110 "LOCAL: a list of paths to consider to be for installed libs that must therefore be bundled. Override this if you are not getting the right libs." 0111 "SYSTEM: a list of system library search paths that should be searched for system libs but should not be recursed into; this is needed. Override this if you want to bunde the system libs for some reason." #XXX it's useful to expose LOCAL but is this really needed? 0112 "this is a badly named function" 0113 "Note: sometimes Mach-O binaries depend on themselves. Deal with it." 0114 0115 # "ignore_missing means whether to ignore if we can't load a binary for examination 0116 # (e.g. if you have references to plugins) XXX is the list" 0117 0118 #binary = os.path.abspath(binary) 0119 0120 todo = dependencies(binary) 0121 done = [] 0122 orig = [] 0123 0124 # This code can be factored. 0125 0126 while todo: 0127 0128 # because of how this is written, popping from the end is a depth-first search. 0129 # Popping from the front would be a breadth-first search. Neat! 0130 0131 e = todo.pop() 0132 0133 # Figure out the absolute path to the library 0134 0135 if e.startswith('/'): 0136 p = e 0137 elif e.startswith('@'): 0138 # it's a relative load path 0139 raise Exception("Unable to make heads nor tails, sah, of install name '%s'. Relative paths are for already-bundled binaries, this function does not support them." % e) 0140 else: 0141 # experiments show that giving an unspecified path is asking dyld(1) to find the library for us. This covers that case. 0142 0143 for P in ['']+LOCAL+SYSTEM: 0144 p = os.path.abspath(os.path.join(P, e)) 0145 #print "SEARCHING IN LIBPATH; TRYING", p 0146 0147 if os.path.exists(p): 0148 break 0149 else: 0150 p = e # fallthrough to the exception below #XXX icky bad logic, there must be a way to avoid saying exists() twice 0151 0152 # if not os.path.exists(p): 0153 # raise Exception("Dependent library '%s' not found. Make sure it is installed." % e) 0154 0155 if not any(p.startswith(P) for P in SYSTEM): 0156 0157 if ".framework/Versions/" in p: 0158 # If dependency is a framework, return 0159 # framework directory not shared library 0160 0161 f = re.sub("/Versions/[0-9]*/.*","",p) 0162 0163 if f not in done: 0164 done.append(f) 0165 todo.extend(dependencies(p)) 0166 orig.append(e) 0167 elif p not in done: 0168 done.append(p) 0169 todo.extend(dependencies(p)) 0170 orig.append(e) 0171 0172 assert all(e.startswith("/") for e in done), "embed_dependencies() is broken, some path in this list is not absolute: %s" % (done,) 0173 0174 return sorted(done) 0175 0176 # --------------------------------------------------------------- 0177 0178 deplist = [] 0179 0180 for dep in sys.argv[1:]: 0181 deplist = deplist + embed_dependencies(dep) 0182 0183 depset = set(deplist) 0184 0185 for dep in depset: 0186 print dep