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