author Landry Breuil <landry@openbsd.org>
Tue, 26 Jun 2012 22:48:57 +0200
changeset 97722 c7b0713958d6dbcb7fd4b8c2c17c8f6b939d99aa
parent 97424 03f1fdde213775c09ccfa8827ee9d682928b0443
child 98819 9e25fe33268dac875aca9d2ccae420c29e6293de
permissions -rw-r--r--
Bug 767403 - also match readelf case on *.so.* to fix *BSDs. r=glandium

# 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 http://mozilla.org/MPL/2.0/.

'''Given a library, dependentlibs.py prints the list of libraries it depends
upon that are in the same directory.

from optparse import OptionParser
import os
import re
import fnmatch
import subprocess
import sys


def dependentlibs_dumpbin(lib):
    '''Returns the list of dependencies declared in the given DLL'''
    proc = subprocess.Popen(['dumpbin', '-imports', lib], stdout = subprocess.PIPE)
    deps = []
    for line in proc.stdout:
        # Each line containing an imported library name starts with 4 spaces
        match = re.match('    (\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 = re.search('\[(.*)\]', 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(['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'''
    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])

    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]
    ext = os.path.splitext(lib)[1]
    if ext == '.dll':
        func = dependentlibs_dumpbin
    elif ext == '.so' or fnmatch.fnmatch(lib, '*.so.*'):
        func = dependentlibs_readelf
    elif ext == '.dylib':
        func = dependentlibs_otool
    if not options.libpaths:
        options.libpaths = [os.path.dirname(lib)]

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

if __name__ == '__main__':