|
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 |
|
6 upon that are in the same directory, followed by the library itself. |
|
7 ''' |
|
8 |
|
9 from optparse import OptionParser |
|
10 import os |
|
11 import re |
|
12 import fnmatch |
|
13 import subprocess |
|
14 import sys |
|
15 from mozpack.executables import ( |
|
16 get_type, |
|
17 ELF, |
|
18 MACHO, |
|
19 ) |
|
20 |
|
21 TOOLCHAIN_PREFIX = '' |
|
22 |
|
23 def dependentlibs_dumpbin(lib): |
|
24 '''Returns the list of dependencies declared in the given DLL''' |
|
25 try: |
|
26 proc = subprocess.Popen(['dumpbin', '-dependents', lib], stdout = subprocess.PIPE) |
|
27 except OSError: |
|
28 # dumpbin is missing, probably mingw compilation. Try using objdump. |
|
29 return dependentlibs_mingw_objdump(lib) |
|
30 deps = [] |
|
31 for line in proc.stdout: |
|
32 # Each line containing an imported library name starts with 4 spaces |
|
33 match = re.match(' (\S+)', line) |
|
34 if match: |
|
35 deps.append(match.group(1)) |
|
36 elif len(deps): |
|
37 # There may be several groups of library names, but only the |
|
38 # first one is interesting. The second one is for delayload-ed |
|
39 # libraries. |
|
40 break |
|
41 proc.wait() |
|
42 return deps |
|
43 |
|
44 def dependentlibs_mingw_objdump(lib): |
|
45 proc = subprocess.Popen(['objdump', '-x', lib], stdout = subprocess.PIPE) |
|
46 deps = [] |
|
47 for line in proc.stdout: |
|
48 match = re.match('\tDLL Name: (\S+)', line) |
|
49 if match: |
|
50 deps.append(match.group(1)) |
|
51 proc.wait() |
|
52 return deps |
|
53 |
|
54 def dependentlibs_readelf(lib): |
|
55 '''Returns the list of dependencies declared in the given ELF .so''' |
|
56 proc = subprocess.Popen([TOOLCHAIN_PREFIX + 'readelf', '-d', lib], stdout = subprocess.PIPE) |
|
57 deps = [] |
|
58 for line in proc.stdout: |
|
59 # Each line has the following format: |
|
60 # tag (TYPE) value |
|
61 # Looking for NEEDED type entries |
|
62 tmp = line.split(' ', 3) |
|
63 if len(tmp) > 3 and tmp[2] == '(NEEDED)': |
|
64 # NEEDED lines look like: |
|
65 # 0x00000001 (NEEDED) Shared library: [libname] |
|
66 match = re.search('\[(.*)\]', tmp[3]) |
|
67 if match: |
|
68 deps.append(match.group(1)) |
|
69 proc.wait() |
|
70 return deps |
|
71 |
|
72 def dependentlibs_otool(lib): |
|
73 '''Returns the list of dependencies declared in the given MACH-O dylib''' |
|
74 proc = subprocess.Popen(["../../../../../x-tools/x86_64-apple-darwin10/bin/" + TOOLCHAIN_PREFIX + 'otool', '-l', lib], stdout = subprocess.PIPE) |
|
75 deps= [] |
|
76 cmd = None |
|
77 for line in proc.stdout: |
|
78 # otool -l output contains many different things. The interesting data |
|
79 # is under "Load command n" sections, with the content: |
|
80 # cmd LC_LOAD_DYLIB |
|
81 # cmdsize 56 |
|
82 # name libname (offset 24) |
|
83 tmp = line.split() |
|
84 if len(tmp) < 2: |
|
85 continue |
|
86 if tmp[0] == 'cmd': |
|
87 cmd = tmp[1] |
|
88 elif cmd == 'LC_LOAD_DYLIB' and tmp[0] == 'name': |
|
89 deps.append(re.sub('^@executable_path/','',tmp[1])) |
|
90 proc.wait() |
|
91 return deps |
|
92 |
|
93 def dependentlibs(lib, libpaths, func): |
|
94 '''For a given library, returns the list of recursive dependencies that can |
|
95 be found in the given list of paths, followed by the library itself.''' |
|
96 assert(libpaths) |
|
97 assert(isinstance(libpaths, list)) |
|
98 deps = [] |
|
99 for dep in func(lib): |
|
100 if dep in deps or os.path.isabs(dep): |
|
101 continue |
|
102 for dir in libpaths: |
|
103 deppath = os.path.join(dir, dep) |
|
104 if os.path.exists(deppath): |
|
105 deps.extend([d for d in dependentlibs(deppath, libpaths, func) if not d in deps]) |
|
106 # Black list the ICU data DLL because preloading it at startup |
|
107 # leads to startup performance problems because of its excessive |
|
108 # size (around 10MB). |
|
109 if not dep.startswith("icu"): |
|
110 deps.append(dep) |
|
111 break |
|
112 |
|
113 return deps |
|
114 |
|
115 def main(): |
|
116 parser = OptionParser() |
|
117 parser.add_option("-L", dest="libpaths", action="append", metavar="PATH", help="Add the given path to the library search path") |
|
118 parser.add_option("-p", dest="toolchain_prefix", metavar="PREFIX", help="Use the given prefix to readelf") |
|
119 (options, args) = parser.parse_args() |
|
120 if options.toolchain_prefix: |
|
121 global TOOLCHAIN_PREFIX |
|
122 TOOLCHAIN_PREFIX = options.toolchain_prefix |
|
123 lib = args[0] |
|
124 binary_type = get_type(lib) |
|
125 if binary_type == ELF: |
|
126 func = dependentlibs_readelf |
|
127 elif binary_type == MACHO: |
|
128 func = dependentlibs_otool |
|
129 else: |
|
130 ext = os.path.splitext(lib)[1] |
|
131 assert(ext == '.dll') |
|
132 func = dependentlibs_dumpbin |
|
133 if not options.libpaths: |
|
134 options.libpaths = [os.path.dirname(lib)] |
|
135 |
|
136 print '\n'.join(dependentlibs(lib, options.libpaths, func) + [lib]) |
|
137 |
|
138 if __name__ == '__main__': |
|
139 main() |