toolkit/library/dependentlibs.py

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/toolkit/library/dependentlibs.py	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,139 @@
     1.4 +# This Source Code Form is subject to the terms of the Mozilla Public
     1.5 +# License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 +# file, You can obtain one at http://mozilla.org/MPL/2.0/.
     1.7 +
     1.8 +'''Given a library, dependentlibs.py prints the list of libraries it depends
     1.9 +upon that are in the same directory, followed by the library itself.
    1.10 +'''
    1.11 +
    1.12 +from optparse import OptionParser
    1.13 +import os
    1.14 +import re
    1.15 +import fnmatch
    1.16 +import subprocess
    1.17 +import sys
    1.18 +from mozpack.executables import (
    1.19 +    get_type,
    1.20 +    ELF,
    1.21 +    MACHO,
    1.22 +)
    1.23 +
    1.24 +TOOLCHAIN_PREFIX = ''
    1.25 +
    1.26 +def dependentlibs_dumpbin(lib):
    1.27 +    '''Returns the list of dependencies declared in the given DLL'''
    1.28 +    try:
    1.29 +        proc = subprocess.Popen(['dumpbin', '-dependents', lib], stdout = subprocess.PIPE)
    1.30 +    except OSError:
    1.31 +        # dumpbin is missing, probably mingw compilation. Try using objdump.
    1.32 +        return dependentlibs_mingw_objdump(lib)
    1.33 +    deps = []
    1.34 +    for line in proc.stdout:
    1.35 +        # Each line containing an imported library name starts with 4 spaces
    1.36 +        match = re.match('    (\S+)', line)
    1.37 +        if match:
    1.38 +             deps.append(match.group(1))
    1.39 +        elif len(deps):
    1.40 +             # There may be several groups of library names, but only the
    1.41 +             # first one is interesting. The second one is for delayload-ed
    1.42 +             # libraries.
    1.43 +             break
    1.44 +    proc.wait()
    1.45 +    return deps
    1.46 +
    1.47 +def dependentlibs_mingw_objdump(lib):
    1.48 +    proc = subprocess.Popen(['objdump', '-x', lib], stdout = subprocess.PIPE)
    1.49 +    deps = []
    1.50 +    for line in proc.stdout:
    1.51 +        match = re.match('\tDLL Name: (\S+)', line)
    1.52 +        if match:
    1.53 +            deps.append(match.group(1))
    1.54 +    proc.wait()
    1.55 +    return deps
    1.56 +
    1.57 +def dependentlibs_readelf(lib):
    1.58 +    '''Returns the list of dependencies declared in the given ELF .so'''
    1.59 +    proc = subprocess.Popen([TOOLCHAIN_PREFIX + 'readelf', '-d', lib], stdout = subprocess.PIPE)
    1.60 +    deps = []
    1.61 +    for line in proc.stdout:
    1.62 +        # Each line has the following format:
    1.63 +        #  tag (TYPE)          value
    1.64 +        # Looking for NEEDED type entries
    1.65 +        tmp = line.split(' ', 3)
    1.66 +        if len(tmp) > 3 and tmp[2] == '(NEEDED)':
    1.67 +            # NEEDED lines look like:
    1.68 +            # 0x00000001 (NEEDED)             Shared library: [libname]
    1.69 +            match = re.search('\[(.*)\]', tmp[3])
    1.70 +            if match:
    1.71 +                deps.append(match.group(1))
    1.72 +    proc.wait()
    1.73 +    return deps
    1.74 +
    1.75 +def dependentlibs_otool(lib):
    1.76 +    '''Returns the list of dependencies declared in the given MACH-O dylib'''
    1.77 +    proc = subprocess.Popen(["../../../../../x-tools/x86_64-apple-darwin10/bin/" + TOOLCHAIN_PREFIX + 'otool', '-l', lib], stdout = subprocess.PIPE)
    1.78 +    deps= []
    1.79 +    cmd = None
    1.80 +    for line in proc.stdout:
    1.81 +        # otool -l output contains many different things. The interesting data
    1.82 +        # is under "Load command n" sections, with the content:
    1.83 +        #           cmd LC_LOAD_DYLIB
    1.84 +        #       cmdsize 56
    1.85 +        #          name libname (offset 24)
    1.86 +        tmp = line.split()
    1.87 +        if len(tmp) < 2:
    1.88 +            continue
    1.89 +        if tmp[0] == 'cmd':
    1.90 +            cmd = tmp[1]
    1.91 +        elif cmd == 'LC_LOAD_DYLIB' and tmp[0] == 'name':
    1.92 +            deps.append(re.sub('^@executable_path/','',tmp[1]))
    1.93 +    proc.wait()
    1.94 +    return deps
    1.95 +
    1.96 +def dependentlibs(lib, libpaths, func):
    1.97 +    '''For a given library, returns the list of recursive dependencies that can
    1.98 +    be found in the given list of paths, followed by the library itself.'''
    1.99 +    assert(libpaths)
   1.100 +    assert(isinstance(libpaths, list))
   1.101 +    deps = []
   1.102 +    for dep in func(lib):
   1.103 +        if dep in deps or os.path.isabs(dep):
   1.104 +            continue
   1.105 +        for dir in libpaths:
   1.106 +            deppath = os.path.join(dir, dep)
   1.107 +            if os.path.exists(deppath):
   1.108 +                deps.extend([d for d in dependentlibs(deppath, libpaths, func) if not d in deps])
   1.109 +                # Black list the ICU data DLL because preloading it at startup
   1.110 +                # leads to startup performance problems because of its excessive
   1.111 +                # size (around 10MB).
   1.112 +                if not dep.startswith("icu"):
   1.113 +                    deps.append(dep)
   1.114 +                break
   1.115 +
   1.116 +    return deps
   1.117 +
   1.118 +def main():
   1.119 +    parser = OptionParser()
   1.120 +    parser.add_option("-L", dest="libpaths", action="append", metavar="PATH", help="Add the given path to the library search path")
   1.121 +    parser.add_option("-p", dest="toolchain_prefix", metavar="PREFIX", help="Use the given prefix to readelf")
   1.122 +    (options, args) = parser.parse_args()
   1.123 +    if options.toolchain_prefix:
   1.124 +        global TOOLCHAIN_PREFIX
   1.125 +        TOOLCHAIN_PREFIX = options.toolchain_prefix
   1.126 +    lib = args[0]
   1.127 +    binary_type = get_type(lib)
   1.128 +    if binary_type == ELF:
   1.129 +        func = dependentlibs_readelf
   1.130 +    elif binary_type == MACHO:
   1.131 +        func = dependentlibs_otool
   1.132 +    else:
   1.133 +        ext = os.path.splitext(lib)[1]
   1.134 +        assert(ext == '.dll')
   1.135 +        func = dependentlibs_dumpbin
   1.136 +    if not options.libpaths:
   1.137 +        options.libpaths = [os.path.dirname(lib)]
   1.138 +
   1.139 +    print '\n'.join(dependentlibs(lib, options.libpaths, func) + [lib])
   1.140 +
   1.141 +if __name__ == '__main__':
   1.142 +    main()

mercurial