| OLD | NEW |
| (Empty) |
| 1 #! /usr/bin/python | |
| 2 # -*- coding: utf-8 -*- | |
| 3 # | |
| 4 # Copyright (C) 2011-2015, 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 # '__dso_handle' belongs to __cxa_atexit(). | |
| 64 if (name.startswith("__cxa") or "__cxxabi" in name or "__gxx" in name or | |
| 65 name == "__dso_handle"): | |
| 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_sy
stem_symbols): | |
| 121 print "Info: %s %s does not need to depend on %s\n" % (item_type, nam
e, 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"]: | |
| 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, dependen
cies.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)) | |
| 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." | |
| 193 else: | |
| 194 print "Error: There were errors, please fix them and re-run. Processing may
have terminated abnormally." | |
| 195 return _return_value | |
| 196 | |
| 197 if __name__ == "__main__": | |
| 198 sys.exit(main()) | |
| OLD | NEW |