OLD | NEW |
| (Empty) |
1 #! /usr/bin/python | |
2 # -*- coding: utf-8 -*- | |
3 # | |
4 # Copyright (C) 2011-2015, International Business Machines | |
5 # Corporation and others. All Rights Reserved. | |
6 # | |
7 # file name: depstest.py | |
8 # | |
9 # created on: 2011may24 | |
10 | |
11 """ICU dependency tester. | |
12 | |
13 This probably works only on Linux. | |
14 | |
15 The exit code is 0 if everything is fine, 1 for errors, 2 for only warnings. | |
16 | |
17 Sample invocation: | |
18 ~/svn.icu/trunk/src/source/test/depstest$ ./depstest.py ~/svn.icu/trunk/dbg | |
19 """ | |
20 | |
21 __author__ = "Markus W. Scherer" | |
22 | |
23 import glob | |
24 import os.path | |
25 import subprocess | |
26 import sys | |
27 | |
28 import dependencies | |
29 | |
30 _ignored_symbols = set() | |
31 _obj_files = {} | |
32 _symbols_to_files = {} | |
33 _return_value = 0 | |
34 | |
35 # Classes with vtables (and thus virtual methods). | |
36 _virtual_classes = set() | |
37 # Classes with weakly defined destructors. | |
38 # nm shows a symbol class of "W" rather than "T". | |
39 _weak_destructors = set() | |
40 | |
41 def _ReadObjFile(root_path, library_name, obj_name): | |
42 global _ignored_symbols, _obj_files, _symbols_to_files | |
43 global _virtual_classes, _weak_destructors | |
44 lib_obj_name = library_name + "/" + obj_name | |
45 if lib_obj_name in _obj_files: | |
46 print "Warning: duplicate .o file " + lib_obj_name | |
47 _return_value = 2 | |
48 return | |
49 | |
50 path = os.path.join(root_path, library_name, obj_name) | |
51 nm_result = subprocess.Popen(["nm", "--demangle", "--format=sysv", | |
52 "--extern-only", "--no-sort", path], | |
53 stdout=subprocess.PIPE).communicate()[0] | |
54 obj_imports = set() | |
55 obj_exports = set() | |
56 for line in nm_result.splitlines(): | |
57 fields = line.split("|") | |
58 if len(fields) == 1: continue | |
59 name = fields[0].strip() | |
60 # Ignore symbols like '__cxa_pure_virtual', | |
61 # 'vtable for __cxxabiv1::__si_class_type_info' or | |
62 # 'DW.ref.__gxx_personality_v0'. | |
63 # '__dso_handle' belongs to __cxa_atexit(). | |
64 if (name.startswith("__cxa") or "__cxxabi" in name or "__gxx" in name or | |
65 name == "__dso_handle"): | |
66 _ignored_symbols.add(name) | |
67 continue | |
68 type = fields[2].strip() | |
69 if type == "U": | |
70 obj_imports.add(name) | |
71 else: | |
72 obj_exports.add(name) | |
73 _symbols_to_files[name] = lib_obj_name | |
74 # Is this a vtable? E.g., "vtable for icu_49::ByteSink". | |
75 if name.startswith("vtable for icu"): | |
76 _virtual_classes.add(name[name.index("::") + 2:]) | |
77 # Is this a destructor? E.g., "icu_49::ByteSink::~ByteSink()". | |
78 index = name.find("::~") | |
79 if index >= 0 and type == "W": | |
80 _weak_destructors.add(name[index + 3:name.index("(", index)]) | |
81 _obj_files[lib_obj_name] = {"imports": obj_imports, "exports": obj_exports} | |
82 | |
83 def _ReadLibrary(root_path, library_name): | |
84 obj_paths = glob.glob(os.path.join(root_path, library_name, "*.o")) | |
85 for path in obj_paths: | |
86 _ReadObjFile(root_path, library_name, os.path.basename(path)) | |
87 | |
88 def _Resolve(name, parents): | |
89 global _ignored_symbols, _obj_files, _symbols_to_files, _return_value | |
90 item = dependencies.items[name] | |
91 item_type = item["type"] | |
92 if name in parents: | |
93 sys.exit("Error: %s %s has a circular dependency on itself: %s" % | |
94 (item_type, name, parents)) | |
95 # Check if already cached. | |
96 exports = item.get("exports") | |
97 if exports != None: return item | |
98 # Calculcate recursively. | |
99 parents.append(name) | |
100 imports = set() | |
101 exports = set() | |
102 system_symbols = item.get("system_symbols") | |
103 if system_symbols == None: system_symbols = item["system_symbols"] = set() | |
104 files = item.get("files") | |
105 if files: | |
106 for file_name in files: | |
107 obj_file = _obj_files[file_name] | |
108 imports |= obj_file["imports"] | |
109 exports |= obj_file["exports"] | |
110 imports -= exports | _ignored_symbols | |
111 deps = item.get("deps") | |
112 if deps: | |
113 for dep in deps: | |
114 dep_item = _Resolve(dep, parents) | |
115 # Detect whether this item needs to depend on dep, | |
116 # except when this item has no files, that is, when it is just | |
117 # a deliberate umbrella group or library. | |
118 dep_exports = dep_item["exports"] | |
119 dep_system_symbols = dep_item["system_symbols"] | |
120 if files and imports.isdisjoint(dep_exports) and imports.isdisjoint(dep_sy
stem_symbols): | |
121 print "Info: %s %s does not need to depend on %s\n" % (item_type, nam
e, dep) | |
122 # We always include the dependency's exports, even if we do not need them | |
123 # to satisfy local imports. | |
124 exports |= dep_exports | |
125 system_symbols |= dep_system_symbols | |
126 item["exports"] = exports | |
127 item["system_symbols"] = system_symbols | |
128 imports -= exports | system_symbols | |
129 for symbol in imports: | |
130 for file_name in files: | |
131 if symbol in _obj_files[file_name]["imports"]: | |
132 neededFile = _symbols_to_files.get(symbol) | |
133 if neededFile in dependencies.file_to_item: | |
134 neededItem = "but %s does not depend on %s (for %s)" % (name, dependen
cies.file_to_item[neededFile], neededFile) | |
135 else: | |
136 neededItem = "- is this a new system symbol?" | |
137 sys.stderr.write("Error: in %s %s: %s imports %s %s\n" % | |
138 (item_type, name, file_name, symbol, neededItem)) | |
139 _return_value = 1 | |
140 del parents[-1] | |
141 return item | |
142 | |
143 def Process(root_path): | |
144 """Loads dependencies.txt, reads the libraries' .o files, and processes them. | |
145 | |
146 Modifies dependencies.items: Recursively builds each item's system_symbols and
exports. | |
147 """ | |
148 global _ignored_symbols, _obj_files, _return_value | |
149 global _virtual_classes, _weak_destructors | |
150 dependencies.Load() | |
151 for name_and_item in dependencies.items.iteritems(): | |
152 name = name_and_item[0] | |
153 item = name_and_item[1] | |
154 system_symbols = item.get("system_symbols") | |
155 if system_symbols: | |
156 for symbol in system_symbols: | |
157 _symbols_to_files[symbol] = name | |
158 for library_name in dependencies.libraries: | |
159 _ReadLibrary(root_path, library_name) | |
160 o_files_set = set(_obj_files.keys()) | |
161 files_missing_from_deps = o_files_set - dependencies.files | |
162 files_missing_from_build = dependencies.files - o_files_set | |
163 if files_missing_from_deps: | |
164 sys.stderr.write("Error: files missing from dependencies.txt:\n%s\n" % | |
165 sorted(files_missing_from_deps)) | |
166 _return_value = 1 | |
167 if files_missing_from_build: | |
168 sys.stderr.write("Error: files in dependencies.txt but not built:\n%s\n" % | |
169 sorted(files_missing_from_build)) | |
170 _return_value = 1 | |
171 if not _return_value: | |
172 for library_name in dependencies.libraries: | |
173 _Resolve(library_name, []) | |
174 if not _return_value: | |
175 virtual_classes_with_weak_destructors = _virtual_classes & _weak_destructors | |
176 if virtual_classes_with_weak_destructors: | |
177 sys.stderr.write("Error: Some classes have virtual methods, and " | |
178 "an implicit or inline destructor " | |
179 "(see ICU ticket #8454 for details):\n%s\n" % | |
180 sorted(virtual_classes_with_weak_destructors)) | |
181 _return_value = 1 | |
182 | |
183 def main(): | |
184 global _return_value | |
185 if len(sys.argv) <= 1: | |
186 sys.exit(("Command line error: " + | |
187 "need one argument with the root path to the built ICU libraries/*.
o files.")) | |
188 Process(sys.argv[1]) | |
189 if _ignored_symbols: | |
190 print "Info: ignored symbols:\n%s" % sorted(_ignored_symbols) | |
191 if not _return_value: | |
192 print "OK: Specified and actual dependencies match." | |
193 else: | |
194 print "Error: There were errors, please fix them and re-run. Processing may
have terminated abnormally." | |
195 return _return_value | |
196 | |
197 if __name__ == "__main__": | |
198 sys.exit(main()) | |
OLD | NEW |