]>
Commit | Line | Data |
---|---|---|
4388f060 A |
1 | #! /usr/bin/python |
2 | # -*- coding: utf-8 -*- | |
3 | # | |
4 | # Copyright (C) 2011, 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 | if name.startswith("__cxa") or "__cxxabi" in name or "__gxx" in name: | |
64 | _ignored_symbols.add(name) | |
65 | continue | |
66 | type = fields[2].strip() | |
67 | if type == "U": | |
68 | obj_imports.add(name) | |
69 | else: | |
70 | obj_exports.add(name) | |
71 | _symbols_to_files[name] = lib_obj_name | |
72 | # Is this a vtable? E.g., "vtable for icu_49::ByteSink". | |
73 | if name.startswith("vtable for icu"): | |
74 | _virtual_classes.add(name[name.index("::") + 2:]) | |
75 | # Is this a destructor? E.g., "icu_49::ByteSink::~ByteSink()". | |
76 | index = name.find("::~") | |
77 | if index >= 0 and type == "W": | |
78 | _weak_destructors.add(name[index + 3:name.index("(", index)]) | |
79 | _obj_files[lib_obj_name] = {"imports": obj_imports, "exports": obj_exports} | |
80 | ||
81 | def _ReadLibrary(root_path, library_name): | |
82 | obj_paths = glob.glob(os.path.join(root_path, library_name, "*.o")) | |
83 | for path in obj_paths: | |
84 | _ReadObjFile(root_path, library_name, os.path.basename(path)) | |
85 | ||
86 | def _Resolve(name, parents): | |
87 | global _ignored_symbols, _obj_files, _symbols_to_files, _return_value | |
88 | item = dependencies.items[name] | |
89 | item_type = item["type"] | |
90 | if name in parents: | |
91 | sys.exit("Error: %s %s has a circular dependency on itself: %s" % | |
92 | (item_type, name, parents)) | |
93 | # Check if already cached. | |
94 | exports = item.get("exports") | |
95 | if exports != None: return item | |
96 | # Calculcate recursively. | |
97 | parents.append(name) | |
98 | imports = set() | |
99 | exports = set() | |
100 | system_symbols = item.get("system_symbols") | |
101 | if system_symbols == None: system_symbols = item["system_symbols"] = set() | |
102 | files = item.get("files") | |
103 | if files: | |
104 | for file_name in files: | |
105 | obj_file = _obj_files[file_name] | |
106 | imports |= obj_file["imports"] | |
107 | exports |= obj_file["exports"] | |
108 | imports -= exports | _ignored_symbols | |
109 | deps = item.get("deps") | |
110 | if deps: | |
111 | for dep in deps: | |
112 | dep_item = _Resolve(dep, parents) | |
113 | # Detect whether this item needs to depend on dep, | |
114 | # except when this item has no files, that is, when it is just | |
115 | # a deliberate umbrella group or library. | |
116 | dep_exports = dep_item["exports"] | |
117 | dep_system_symbols = dep_item["system_symbols"] | |
118 | if files and imports.isdisjoint(dep_exports) and imports.isdisjoint(dep_system_symbols): | |
119 | print "Info: %s %s does not need to depend on %s\n" % (item_type, name, dep) | |
120 | # We always include the dependency's exports, even if we do not need them | |
121 | # to satisfy local imports. | |
122 | exports |= dep_exports | |
123 | system_symbols |= dep_system_symbols | |
124 | item["exports"] = exports | |
125 | item["system_symbols"] = system_symbols | |
126 | imports -= exports | system_symbols | |
127 | for symbol in imports: | |
128 | for file_name in files: | |
129 | if symbol in _obj_files[file_name]["imports"]: | |
130 | sys.stderr.write("Error: %s %s file %s imports %s but %s does not depend on %s\n" % | |
131 | (item_type, name, file_name, symbol, name, _symbols_to_files.get(symbol))) | |
132 | _return_value = 1 | |
133 | del parents[-1] | |
134 | return item | |
135 | ||
136 | def Process(root_path): | |
137 | """Loads dependencies.txt, reads the libraries' .o files, and processes them. | |
138 | ||
139 | Modifies dependencies.items: Recursively builds each item's system_symbols and exports. | |
140 | """ | |
141 | global _ignored_symbols, _obj_files, _return_value | |
142 | global _virtual_classes, _weak_destructors | |
143 | dependencies.Load() | |
144 | for name_and_item in dependencies.items.iteritems(): | |
145 | name = name_and_item[0] | |
146 | item = name_and_item[1] | |
147 | system_symbols = item.get("system_symbols") | |
148 | if system_symbols: | |
149 | for symbol in system_symbols: | |
150 | _symbols_to_files[symbol] = name | |
151 | for library_name in dependencies.libraries: | |
152 | _ReadLibrary(root_path, library_name) | |
153 | o_files_set = set(_obj_files.keys()) | |
154 | files_missing_from_deps = o_files_set - dependencies.files | |
155 | files_missing_from_build = dependencies.files - o_files_set | |
156 | if files_missing_from_deps: | |
157 | sys.stderr.write("Error: files missing from dependencies.txt:\n%s\n" % | |
158 | sorted(files_missing_from_deps)) | |
159 | _return_value = 1 | |
160 | if files_missing_from_build: | |
161 | sys.stderr.write("Error: files in dependencies.txt but not built:\n%s\n" % | |
162 | sorted(files_missing_from_build)) | |
163 | _return_value = 1 | |
164 | if not _return_value: | |
165 | for library_name in dependencies.libraries: | |
166 | _Resolve(library_name, []) | |
167 | if not _return_value: | |
168 | virtual_classes_with_weak_destructors = _virtual_classes & _weak_destructors | |
169 | if virtual_classes_with_weak_destructors: | |
170 | sys.stderr.write("Error: Some classes have virtual methods, and " | |
171 | "an implicit or inline destructor " | |
172 | "(see ICU ticket #8454 for details):\n%s\n" % | |
173 | sorted(virtual_classes_with_weak_destructors)) | |
174 | _return_value = 1 | |
175 | ||
176 | def main(): | |
177 | global _return_value | |
178 | if len(sys.argv) <= 1: | |
179 | sys.exit(("Command line error: " + | |
180 | "need one argument with the root path to the built ICU libraries/*.o files.")) | |
181 | Process(sys.argv[1]) | |
182 | if _ignored_symbols: | |
183 | print "Info: ignored symbols:\n%s" % sorted(_ignored_symbols) | |
184 | if not _return_value: | |
185 | print "OK: Specified and actual dependencies match." | |
186 | return _return_value | |
187 | ||
188 | if __name__ == "__main__": | |
189 | sys.exit(main()) |