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 try: 45 proc = subprocess.Popen(['objdump', '-x', lib], stdout = subprocess.PIPE) 46 except OSError: 47 # objdump is missing, try using llvm-objdump. 48 proc = subprocess.Popen(['llvm-objdump', '-private-headers', lib], stdout = subprocess.PIPE) 49 deps = [] 50 for line in proc.stdout: 51 match = re.match('\s+DLL Name: (\S+)', line) 52 if match: 53 deps.append(match.group(1)) 54 proc.wait() 55 return deps 56 57def dependentlibs_readelf(lib): 58 '''Returns the list of dependencies declared in the given ELF .so''' 59 proc = subprocess.Popen([substs.get('TOOLCHAIN_PREFIX', '') + 'readelf', '-d', lib], stdout = subprocess.PIPE) 60 deps = [] 61 for line in proc.stdout: 62 # Each line has the following format: 63 # tag (TYPE) value 64 # or with BSD readelf: 65 # tag TYPE value 66 # Looking for NEEDED type entries 67 tmp = line.split(' ', 3) 68 if len(tmp) > 3 and 'NEEDED' in tmp[2]: 69 # NEEDED lines look like: 70 # 0x00000001 (NEEDED) Shared library: [libname] 71 # or with BSD readelf: 72 # 0x00000001 NEEDED Shared library: [libname] 73 match = re.search('\[(.*)\]', tmp[3]) 74 if match: 75 deps.append(match.group(1)) 76 proc.wait() 77 return deps 78 79def dependentlibs_otool(lib): 80 '''Returns the list of dependencies declared in the given MACH-O dylib''' 81 proc = subprocess.Popen([substs['OTOOL'], '-l', lib], stdout = subprocess.PIPE) 82 deps= [] 83 cmd = None 84 for line in proc.stdout: 85 # otool -l output contains many different things. The interesting data 86 # is under "Load command n" sections, with the content: 87 # cmd LC_LOAD_DYLIB 88 # cmdsize 56 89 # name libname (offset 24) 90 tmp = line.split() 91 if len(tmp) < 2: 92 continue 93 if tmp[0] == 'cmd': 94 cmd = tmp[1] 95 elif cmd == 'LC_LOAD_DYLIB' and tmp[0] == 'name': 96 deps.append(re.sub('^@executable_path/','',tmp[1])) 97 proc.wait() 98 return deps 99 100def dependentlibs(lib, libpaths, func): 101 '''For a given library, returns the list of recursive dependencies that can 102 be found in the given list of paths, followed by the library itself.''' 103 assert(libpaths) 104 assert(isinstance(libpaths, list)) 105 deps = OrderedDict() 106 for dep in func(lib): 107 if dep in deps or os.path.isabs(dep): 108 continue 109 for dir in libpaths: 110 deppath = os.path.join(dir, dep) 111 if os.path.exists(deppath): 112 deps.update(dependentlibs(deppath, libpaths, func)) 113 # Black list the ICU data DLL because preloading it at startup 114 # leads to startup performance problems because of its excessive 115 # size (around 10MB). 116 if not dep.startswith("icu"): 117 deps[dep] = deppath 118 break 119 120 return deps 121 122def gen_list(output, lib): 123 libpaths = [os.path.join(substs['DIST'], 'bin')] 124 binary_type = get_type(lib) 125 if binary_type == ELF: 126 func = dependentlibs_readelf 127 elif binary_type == MACHO: 128 func = dependentlibs_otool 129 else: 130 ext = os.path.splitext(lib)[1] 131 assert(ext == '.dll') 132 func = dependentlibs_dumpbin 133 134 deps = dependentlibs(lib, libpaths, func) 135 deps[lib] = mozpath.join(libpaths[0], lib) 136 output.write('\n'.join(deps.keys()) + '\n') 137 138 with open(output.name + ".gtest", 'w') as gtest_out: 139 libs = deps.keys() 140 libs[-1] = 'gtest/' + libs[-1] 141 gtest_out.write('\n'.join(libs) + '\n') 142 143 return set(deps.values()) 144 145def main(): 146 gen_list(sys.stdout, sys.argv[1]) 147 148if __name__ == '__main__': 149 main() 150