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