1# This Source Code Form is subject to the terms of the Mozilla Public 2# License, v. 2.0. If a copy of the MPL was not distributed with this 3# file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 5'''Given a library, dependentlibs.py prints the list of libraries it depends 6upon that are in the same directory, followed by the library itself. 7''' 8 9import os 10import re 11import subprocess 12import sys 13import mozpack.path as mozpath 14from collections import OrderedDict 15from mozpack.executables import ( 16 get_type, 17 ELF, 18 MACHO, 19) 20from buildconfig import substs 21 22def dependentlibs_dumpbin(lib): 23 '''Returns the list of dependencies declared in the given DLL''' 24 try: 25 proc = subprocess.Popen(['dumpbin', '-dependents', lib], stdout = subprocess.PIPE) 26 except OSError: 27 # dumpbin is missing, probably mingw compilation. Try using objdump. 28 return dependentlibs_mingw_objdump(lib) 29 deps = [] 30 for line in proc.stdout: 31 # Each line containing an imported library name starts with 4 spaces 32 match = re.match(' (\S+)', line) 33 if match: 34 deps.append(match.group(1)) 35 elif len(deps): 36 # There may be several groups of library names, but only the 37 # first one is interesting. The second one is for delayload-ed 38 # libraries. 39 break 40 proc.wait() 41 return deps 42 43def dependentlibs_mingw_objdump(lib): 44 proc = subprocess.Popen(['objdump', '-x', lib], stdout = subprocess.PIPE) 45 deps = [] 46 for line in proc.stdout: 47 match = re.match('\tDLL Name: (\S+)', line) 48 if match: 49 deps.append(match.group(1)) 50 proc.wait() 51 return deps 52 53def dependentlibs_readelf(lib): 54 '''Returns the list of dependencies declared in the given ELF .so''' 55 proc = subprocess.Popen([substs.get('TOOLCHAIN_PREFIX', '') + 'readelf', '-d', lib], stdout = subprocess.PIPE) 56 deps = [] 57 for line in proc.stdout: 58 # Each line has the following format: 59 # tag (TYPE) value 60 # or with BSD readelf: 61 # tag TYPE value 62 # Looking for NEEDED type entries 63 tmp = line.split(' ', 3) 64 if len(tmp) > 3 and 'NEEDED' in tmp[2]: 65 # NEEDED lines look like: 66 # 0x00000001 (NEEDED) Shared library: [libname] 67 # or with BSD readelf: 68 # 0x00000001 NEEDED Shared library: [libname] 69 match = re.search('\[(.*)\]', tmp[3]) 70 if match: 71 deps.append(match.group(1)) 72 proc.wait() 73 return deps 74 75def dependentlibs_otool(lib): 76 '''Returns the list of dependencies declared in the given MACH-O dylib''' 77 proc = subprocess.Popen([substs['OTOOL'], '-l', lib], stdout = subprocess.PIPE) 78 deps= [] 79 cmd = None 80 for line in proc.stdout: 81 # otool -l output contains many different things. The interesting data 82 # is under "Load command n" sections, with the content: 83 # cmd LC_LOAD_DYLIB 84 # cmdsize 56 85 # name libname (offset 24) 86 tmp = line.split() 87 if len(tmp) < 2: 88 continue 89 if tmp[0] == 'cmd': 90 cmd = tmp[1] 91 elif cmd == 'LC_LOAD_DYLIB' and tmp[0] == 'name': 92 deps.append(re.sub('^@executable_path/','',tmp[1])) 93 proc.wait() 94 return deps 95 96def dependentlibs(lib, libpaths, func): 97 '''For a given library, returns the list of recursive dependencies that can 98 be found in the given list of paths, followed by the library itself.''' 99 assert(libpaths) 100 assert(isinstance(libpaths, list)) 101 deps = OrderedDict() 102 for dep in func(lib): 103 if dep in deps or os.path.isabs(dep): 104 continue 105 for dir in libpaths: 106 deppath = os.path.join(dir, dep) 107 if os.path.exists(deppath): 108 deps.update(dependentlibs(deppath, libpaths, func)) 109 # Black list the ICU data DLL because preloading it at startup 110 # leads to startup performance problems because of its excessive 111 # size (around 10MB). 112 if not dep.startswith("icu"): 113 deps[dep] = deppath 114 break 115 116 return deps 117 118def gen_list(output, lib): 119 libpaths = [os.path.join(substs['DIST'], 'bin')] 120 binary_type = get_type(lib) 121 if binary_type == ELF: 122 func = dependentlibs_readelf 123 elif binary_type == MACHO: 124 func = dependentlibs_otool 125 else: 126 ext = os.path.splitext(lib)[1] 127 assert(ext == '.dll') 128 func = dependentlibs_dumpbin 129 130 deps = dependentlibs(lib, libpaths, func) 131 deps[lib] = mozpath.join(libpaths[0], lib) 132 output.write('\n'.join(deps.keys()) + '\n') 133 return set(deps.values()) 134 135def main(): 136 gen_list(sys.stdout, sys.argv[1]) 137 138if __name__ == '__main__': 139 main() 140