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 15This probably works only on Linux. 16 17The exit code is 0 if everything is fine, 1 for errors, 2 for only warnings. 18 19Sample invocation: 20 ~/svn.icu/trunk/src/source/test/depstest$ ./depstest.py ~/svn.icu/trunk/dbg 21""" 22 23__author__ = "Markus W. Scherer" 24 25import glob 26import os.path 27import subprocess 28import sys 29 30import 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 43def _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 85def _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 90def _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 145def 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 185def 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 199if __name__ == "__main__": 200 sys.exit(main()) 201