2 # -*- coding: utf-8 -*-
4 # Copyright (C) 2011-2015, International Business Machines
5 # Corporation and others. All Rights Reserved.
7 # file name: depstest.py
9 # created on: 2011may24
11 """ICU dependency tester.
13 This probably works only on Linux.
15 The exit code is 0 if everything is fine, 1 for errors, 2 for only warnings.
18 ~/svn.icu/trunk/src/source/test/depstest$ ./depstest.py ~/svn.icu/trunk/dbg
21 __author__
= "Markus W. Scherer"
30 _ignored_symbols
= set()
32 _symbols_to_files
= {}
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()
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
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]
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
)
68 type = fields
[2].strip()
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}
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
))
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"]
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.
102 system_symbols
= item
.get("system_symbols")
103 if system_symbols
== None: system_symbols
= item
["system_symbols"] = set()
104 files
= item
.get("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")
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
)
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
))
143 def Process(root_path
):
144 """Loads dependencies.txt, reads the libraries' .o files, and processes them.
146 Modifies dependencies.items: Recursively builds each item's system_symbols and exports.
148 global _ignored_symbols
, _obj_files
, _return_value
149 global _virtual_classes
, _weak_destructors
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")
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
))
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
))
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
))
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."))
190 print "Info: ignored symbols:\n%s" % sorted(_ignored_symbols
)
191 if not _return_value
:
192 print "OK: Specified and actual dependencies match."
194 print "Error: There were errors, please fix them and re-run. Processing may have terminated abnormally."
197 if __name__
== "__main__":