1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/library/dependentlibs.py Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,139 @@ 1.4 +# This Source Code Form is subject to the terms of the Mozilla Public 1.5 +# License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 +# file, You can obtain one at http://mozilla.org/MPL/2.0/. 1.7 + 1.8 +'''Given a library, dependentlibs.py prints the list of libraries it depends 1.9 +upon that are in the same directory, followed by the library itself. 1.10 +''' 1.11 + 1.12 +from optparse import OptionParser 1.13 +import os 1.14 +import re 1.15 +import fnmatch 1.16 +import subprocess 1.17 +import sys 1.18 +from mozpack.executables import ( 1.19 + get_type, 1.20 + ELF, 1.21 + MACHO, 1.22 +) 1.23 + 1.24 +TOOLCHAIN_PREFIX = '' 1.25 + 1.26 +def dependentlibs_dumpbin(lib): 1.27 + '''Returns the list of dependencies declared in the given DLL''' 1.28 + try: 1.29 + proc = subprocess.Popen(['dumpbin', '-dependents', lib], stdout = subprocess.PIPE) 1.30 + except OSError: 1.31 + # dumpbin is missing, probably mingw compilation. Try using objdump. 1.32 + return dependentlibs_mingw_objdump(lib) 1.33 + deps = [] 1.34 + for line in proc.stdout: 1.35 + # Each line containing an imported library name starts with 4 spaces 1.36 + match = re.match(' (\S+)', line) 1.37 + if match: 1.38 + deps.append(match.group(1)) 1.39 + elif len(deps): 1.40 + # There may be several groups of library names, but only the 1.41 + # first one is interesting. The second one is for delayload-ed 1.42 + # libraries. 1.43 + break 1.44 + proc.wait() 1.45 + return deps 1.46 + 1.47 +def dependentlibs_mingw_objdump(lib): 1.48 + proc = subprocess.Popen(['objdump', '-x', lib], stdout = subprocess.PIPE) 1.49 + deps = [] 1.50 + for line in proc.stdout: 1.51 + match = re.match('\tDLL Name: (\S+)', line) 1.52 + if match: 1.53 + deps.append(match.group(1)) 1.54 + proc.wait() 1.55 + return deps 1.56 + 1.57 +def dependentlibs_readelf(lib): 1.58 + '''Returns the list of dependencies declared in the given ELF .so''' 1.59 + proc = subprocess.Popen([TOOLCHAIN_PREFIX + 'readelf', '-d', lib], stdout = subprocess.PIPE) 1.60 + deps = [] 1.61 + for line in proc.stdout: 1.62 + # Each line has the following format: 1.63 + # tag (TYPE) value 1.64 + # Looking for NEEDED type entries 1.65 + tmp = line.split(' ', 3) 1.66 + if len(tmp) > 3 and tmp[2] == '(NEEDED)': 1.67 + # NEEDED lines look like: 1.68 + # 0x00000001 (NEEDED) Shared library: [libname] 1.69 + match = re.search('\[(.*)\]', tmp[3]) 1.70 + if match: 1.71 + deps.append(match.group(1)) 1.72 + proc.wait() 1.73 + return deps 1.74 + 1.75 +def dependentlibs_otool(lib): 1.76 + '''Returns the list of dependencies declared in the given MACH-O dylib''' 1.77 + proc = subprocess.Popen(["../../../../../x-tools/x86_64-apple-darwin10/bin/" + TOOLCHAIN_PREFIX + 'otool', '-l', lib], stdout = subprocess.PIPE) 1.78 + deps= [] 1.79 + cmd = None 1.80 + for line in proc.stdout: 1.81 + # otool -l output contains many different things. The interesting data 1.82 + # is under "Load command n" sections, with the content: 1.83 + # cmd LC_LOAD_DYLIB 1.84 + # cmdsize 56 1.85 + # name libname (offset 24) 1.86 + tmp = line.split() 1.87 + if len(tmp) < 2: 1.88 + continue 1.89 + if tmp[0] == 'cmd': 1.90 + cmd = tmp[1] 1.91 + elif cmd == 'LC_LOAD_DYLIB' and tmp[0] == 'name': 1.92 + deps.append(re.sub('^@executable_path/','',tmp[1])) 1.93 + proc.wait() 1.94 + return deps 1.95 + 1.96 +def dependentlibs(lib, libpaths, func): 1.97 + '''For a given library, returns the list of recursive dependencies that can 1.98 + be found in the given list of paths, followed by the library itself.''' 1.99 + assert(libpaths) 1.100 + assert(isinstance(libpaths, list)) 1.101 + deps = [] 1.102 + for dep in func(lib): 1.103 + if dep in deps or os.path.isabs(dep): 1.104 + continue 1.105 + for dir in libpaths: 1.106 + deppath = os.path.join(dir, dep) 1.107 + if os.path.exists(deppath): 1.108 + deps.extend([d for d in dependentlibs(deppath, libpaths, func) if not d in deps]) 1.109 + # Black list the ICU data DLL because preloading it at startup 1.110 + # leads to startup performance problems because of its excessive 1.111 + # size (around 10MB). 1.112 + if not dep.startswith("icu"): 1.113 + deps.append(dep) 1.114 + break 1.115 + 1.116 + return deps 1.117 + 1.118 +def main(): 1.119 + parser = OptionParser() 1.120 + parser.add_option("-L", dest="libpaths", action="append", metavar="PATH", help="Add the given path to the library search path") 1.121 + parser.add_option("-p", dest="toolchain_prefix", metavar="PREFIX", help="Use the given prefix to readelf") 1.122 + (options, args) = parser.parse_args() 1.123 + if options.toolchain_prefix: 1.124 + global TOOLCHAIN_PREFIX 1.125 + TOOLCHAIN_PREFIX = options.toolchain_prefix 1.126 + lib = args[0] 1.127 + binary_type = get_type(lib) 1.128 + if binary_type == ELF: 1.129 + func = dependentlibs_readelf 1.130 + elif binary_type == MACHO: 1.131 + func = dependentlibs_otool 1.132 + else: 1.133 + ext = os.path.splitext(lib)[1] 1.134 + assert(ext == '.dll') 1.135 + func = dependentlibs_dumpbin 1.136 + if not options.libpaths: 1.137 + options.libpaths = [os.path.dirname(lib)] 1.138 + 1.139 + print '\n'.join(dependentlibs(lib, options.libpaths, func) + [lib]) 1.140 + 1.141 +if __name__ == '__main__': 1.142 + main()