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_system_symbols): 121 print "Info: %s %s does not need to depend on %s\n" % (item_type, name, 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, dependencies.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()) 199