2 # -*- coding: utf-8 -*-
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.
9 # file name: depstest.py
11 # created on: 2011may24
13 """ICU dependency tester.
15 This probably works only on Linux.
17 The exit code is 0 if everything is fine, 1 for errors, 2 for only warnings.
20 ~/svn.icu/trunk/src/source/test/depstest$ ./depstest.py ~/svn.icu/trunk/dbg
23 __author__
= "Markus W. Scherer"
32 _ignored_symbols
= set()
34 _symbols_to_files
= {}
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()
43 def _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
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]
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
)
70 type = fields
[2].strip()
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}
85 def _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
))
90 def _Resolve(name
, parents
):
91 global _ignored_symbols
, _obj_files
, _symbols_to_files
, _return_value
92 item
= dependencies
.items
[name
]
93 item_type
= item
["type"]
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.
104 system_symbols
= item
.get("system_symbols")
105 if system_symbols
== None: system_symbols
= item
["system_symbols"] = set()
106 files
= item
.get("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")
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
)
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
))
145 def Process(root_path
):
146 """Loads dependencies.txt, reads the libraries' .o files, and processes them.
148 Modifies dependencies.items: Recursively builds each item's system_symbols and exports.
150 global _ignored_symbols
, _obj_files
, _return_value
151 global _virtual_classes
, _weak_destructors
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")
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
))
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
))
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
))
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."))
192 print "Info: ignored symbols:\n%s" % sorted(_ignored_symbols
)
193 if not _return_value
:
194 print "OK: Specified and actual dependencies match."
196 print "Error: There were errors, please fix them and re-run. Processing may have terminated abnormally."
199 if __name__
== "__main__":