author Bobby Holley <>
Sun, 19 Jul 2015 19:23:55 -0700
changeset 280067 a37164c4c1bbec3a11efb47e5a4f92034c8843d5
parent 230002 9f67efecf1b42b529fe1c65779895008b0de56df
child 366894 4b87b5738f150c717164be42bc9c8fec126242e3
child 366964 882ea6c62b9a0a6be14d35ac605afe356dffb3da
child 389659 8786985093f68c81b708bae41981db9dcf8647ca
permissions -rw-r--r--
Bug 1161831 - Tests. r=billm

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at

'''Given a library, prints the list of libraries it depends
upon that are in the same directory, followed by the library itself.

from optparse import OptionParser
import os
import re
import fnmatch
import subprocess
import sys
from mozpack.executables import (
from buildconfig import substs


def dependentlibs_dumpbin(lib):
    '''Returns the list of dependencies declared in the given DLL'''
        proc = subprocess.Popen(['dumpbin', '-dependents', lib], stdout = subprocess.PIPE)
    except OSError:
        # dumpbin is missing, probably mingw compilation. Try using objdump.
        return dependentlibs_mingw_objdump(lib)
    deps = []
    for line in proc.stdout:
        # Each line containing an imported library name starts with 4 spaces
        match = re.match('    (\S+)', line)
        if match:
        elif len(deps):
             # There may be several groups of library names, but only the
             # first one is interesting. The second one is for delayload-ed
             # libraries.
    return deps

def dependentlibs_mingw_objdump(lib):
    proc = subprocess.Popen(['objdump', '-x', lib], stdout = subprocess.PIPE)
    deps = []
    for line in proc.stdout:
        match = re.match('\tDLL Name: (\S+)', line)
        if match:
    return deps

def dependentlibs_readelf(lib):
    '''Returns the list of dependencies declared in the given ELF .so'''
    proc = subprocess.Popen([TOOLCHAIN_PREFIX + 'readelf', '-d', lib], stdout = subprocess.PIPE)
    deps = []
    for line in proc.stdout:
        # Each line has the following format:
        #  tag (TYPE)          value
        # Looking for NEEDED type entries
        tmp = line.split(' ', 3)
        if len(tmp) > 3 and tmp[2] == '(NEEDED)':
            # NEEDED lines look like:
            # 0x00000001 (NEEDED)             Shared library: [libname]
            match ='\[(.*)\]', tmp[3])
            if match:
    return deps

def dependentlibs_otool(lib):
    '''Returns the list of dependencies declared in the given MACH-O dylib'''
    proc = subprocess.Popen([substs['OTOOL'], '-l', lib], stdout = subprocess.PIPE)
    deps= []
    cmd = None
    for line in proc.stdout:
        # otool -l output contains many different things. The interesting data
        # is under "Load command n" sections, with the content:
        #           cmd LC_LOAD_DYLIB
        #       cmdsize 56
        #          name libname (offset 24)
        tmp = line.split()
        if len(tmp) < 2:
        if tmp[0] == 'cmd':
            cmd = tmp[1]
        elif cmd == 'LC_LOAD_DYLIB' and tmp[0] == 'name':
    return deps

def dependentlibs(lib, libpaths, func):
    '''For a given library, returns the list of recursive dependencies that can
    be found in the given list of paths, followed by the library itself.'''
    assert(isinstance(libpaths, list))
    deps = []
    for dep in func(lib):
        if dep in deps or os.path.isabs(dep):
        for dir in libpaths:
            deppath = os.path.join(dir, dep)
            if os.path.exists(deppath):
                deps.extend([d for d in dependentlibs(deppath, libpaths, func) if not d in deps])
                # Black list the ICU data DLL because preloading it at startup
                # leads to startup performance problems because of its excessive
                # size (around 10MB).
                if not dep.startswith("icu"):

    return deps

def main():
    parser = OptionParser()
    parser.add_option("-L", dest="libpaths", action="append", metavar="PATH", help="Add the given path to the library search path")
    parser.add_option("-p", dest="toolchain_prefix", metavar="PREFIX", help="Use the given prefix to readelf")
    (options, args) = parser.parse_args()
    if options.toolchain_prefix:
        global TOOLCHAIN_PREFIX
        TOOLCHAIN_PREFIX = options.toolchain_prefix
    lib = args[0]
    binary_type = get_type(lib)
    if binary_type == ELF:
        func = dependentlibs_readelf
    elif binary_type == MACHO:
        func = dependentlibs_otool
        ext = os.path.splitext(lib)[1]
        assert(ext == '.dll')
        func = dependentlibs_dumpbin
    if not options.libpaths:
        options.libpaths = [os.path.dirname(lib)]

    print '\n'.join(dependentlibs(lib, options.libpaths, func) + [lib])

if __name__ == '__main__':