]> git.saurik.com Git - apple/icu.git/blob - icuSources/test/depstest/depstest.py
ICU-491.11.1.tar.gz
[apple/icu.git] / icuSources / test / depstest / depstest.py
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())