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