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()) 190