]>
Commit | Line | Data |
---|---|---|
4388f060 A |
1 | #! /usr/bin/python |
2 | # -*- coding: utf-8 -*- | |
3 | # | |
2ca993e8 | 4 | # Copyright (C) 2011-2015, International Business Machines |
4388f060 A |
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'. | |
2ca993e8 A |
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"): | |
4388f060 A |
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_system_symbols): | |
121 | print "Info: %s %s does not need to depend on %s\n" % (item_type, name, 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"]: | |
b331163b A |
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, dependencies.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)) | |
4388f060 A |
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." | |
b331163b A |
193 | else: |
194 | print "Error: There were errors, please fix them and re-run. Processing may have terminated abnormally." | |
4388f060 A |
195 | return _return_value |
196 | ||
197 | if __name__ == "__main__": | |
198 | sys.exit(main()) |