michael@0: # This Source Code Form is subject to the terms of the Mozilla Public michael@0: # License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: # file, You can obtain one at http://mozilla.org/MPL/2.0/. michael@0: michael@0: '''Given a library, dependentlibs.py prints the list of libraries it depends michael@0: upon that are in the same directory, followed by the library itself. michael@0: ''' michael@0: michael@0: from optparse import OptionParser michael@0: import os michael@0: import re michael@0: import fnmatch michael@0: import subprocess michael@0: import sys michael@0: from mozpack.executables import ( michael@0: get_type, michael@0: ELF, michael@0: MACHO, michael@0: ) michael@0: michael@0: TOOLCHAIN_PREFIX = '' michael@0: michael@0: def dependentlibs_dumpbin(lib): michael@0: '''Returns the list of dependencies declared in the given DLL''' michael@0: try: michael@0: proc = subprocess.Popen(['dumpbin', '-dependents', lib], stdout = subprocess.PIPE) michael@0: except OSError: michael@0: # dumpbin is missing, probably mingw compilation. Try using objdump. michael@0: return dependentlibs_mingw_objdump(lib) michael@0: deps = [] michael@0: for line in proc.stdout: michael@0: # Each line containing an imported library name starts with 4 spaces michael@0: match = re.match(' (\S+)', line) michael@0: if match: michael@0: deps.append(match.group(1)) michael@0: elif len(deps): michael@0: # There may be several groups of library names, but only the michael@0: # first one is interesting. The second one is for delayload-ed michael@0: # libraries. michael@0: break michael@0: proc.wait() michael@0: return deps michael@0: michael@0: def dependentlibs_mingw_objdump(lib): michael@0: proc = subprocess.Popen(['objdump', '-x', lib], stdout = subprocess.PIPE) michael@0: deps = [] michael@0: for line in proc.stdout: michael@0: match = re.match('\tDLL Name: (\S+)', line) michael@0: if match: michael@0: deps.append(match.group(1)) michael@0: proc.wait() michael@0: return deps michael@0: michael@0: def dependentlibs_readelf(lib): michael@0: '''Returns the list of dependencies declared in the given ELF .so''' michael@0: proc = subprocess.Popen([TOOLCHAIN_PREFIX + 'readelf', '-d', lib], stdout = subprocess.PIPE) michael@0: deps = [] michael@0: for line in proc.stdout: michael@0: # Each line has the following format: michael@0: # tag (TYPE) value michael@0: # Looking for NEEDED type entries michael@0: tmp = line.split(' ', 3) michael@0: if len(tmp) > 3 and tmp[2] == '(NEEDED)': michael@0: # NEEDED lines look like: michael@0: # 0x00000001 (NEEDED) Shared library: [libname] michael@0: match = re.search('\[(.*)\]', tmp[3]) michael@0: if match: michael@0: deps.append(match.group(1)) michael@0: proc.wait() michael@0: return deps michael@0: michael@0: def dependentlibs_otool(lib): michael@0: '''Returns the list of dependencies declared in the given MACH-O dylib''' michael@0: proc = subprocess.Popen(["../../../../../x-tools/x86_64-apple-darwin10/bin/" + TOOLCHAIN_PREFIX + 'otool', '-l', lib], stdout = subprocess.PIPE) michael@0: deps= [] michael@0: cmd = None michael@0: for line in proc.stdout: michael@0: # otool -l output contains many different things. The interesting data michael@0: # is under "Load command n" sections, with the content: michael@0: # cmd LC_LOAD_DYLIB michael@0: # cmdsize 56 michael@0: # name libname (offset 24) michael@0: tmp = line.split() michael@0: if len(tmp) < 2: michael@0: continue michael@0: if tmp[0] == 'cmd': michael@0: cmd = tmp[1] michael@0: elif cmd == 'LC_LOAD_DYLIB' and tmp[0] == 'name': michael@0: deps.append(re.sub('^@executable_path/','',tmp[1])) michael@0: proc.wait() michael@0: return deps michael@0: michael@0: def dependentlibs(lib, libpaths, func): michael@0: '''For a given library, returns the list of recursive dependencies that can michael@0: be found in the given list of paths, followed by the library itself.''' michael@0: assert(libpaths) michael@0: assert(isinstance(libpaths, list)) michael@0: deps = [] michael@0: for dep in func(lib): michael@0: if dep in deps or os.path.isabs(dep): michael@0: continue michael@0: for dir in libpaths: michael@0: deppath = os.path.join(dir, dep) michael@0: if os.path.exists(deppath): michael@0: deps.extend([d for d in dependentlibs(deppath, libpaths, func) if not d in deps]) michael@0: # Black list the ICU data DLL because preloading it at startup michael@0: # leads to startup performance problems because of its excessive michael@0: # size (around 10MB). michael@0: if not dep.startswith("icu"): michael@0: deps.append(dep) michael@0: break michael@0: michael@0: return deps michael@0: michael@0: def main(): michael@0: parser = OptionParser() michael@0: parser.add_option("-L", dest="libpaths", action="append", metavar="PATH", help="Add the given path to the library search path") michael@0: parser.add_option("-p", dest="toolchain_prefix", metavar="PREFIX", help="Use the given prefix to readelf") michael@0: (options, args) = parser.parse_args() michael@0: if options.toolchain_prefix: michael@0: global TOOLCHAIN_PREFIX michael@0: TOOLCHAIN_PREFIX = options.toolchain_prefix michael@0: lib = args[0] michael@0: binary_type = get_type(lib) michael@0: if binary_type == ELF: michael@0: func = dependentlibs_readelf michael@0: elif binary_type == MACHO: michael@0: func = dependentlibs_otool michael@0: else: michael@0: ext = os.path.splitext(lib)[1] michael@0: assert(ext == '.dll') michael@0: func = dependentlibs_dumpbin michael@0: if not options.libpaths: michael@0: options.libpaths = [os.path.dirname(lib)] michael@0: michael@0: print '\n'.join(dependentlibs(lib, options.libpaths, func) + [lib]) michael@0: michael@0: if __name__ == '__main__': michael@0: main()