2 # -*- coding: utf-8 -*-
4 # Copyright (C) 2011, 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 if name
.startswith("__cxa") or "__cxxabi" in name
or "__gxx" in name
:
64 _ignored_symbols
.add(name
)
66 type = fields
[2].strip()
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}
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
))
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"]
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.
100 system_symbols
= item
.get("system_symbols")
101 if system_symbols
== None: system_symbols
= item
["system_symbols"] = set()
102 files
= item
.get("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")
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
)))
136 def Process(root_path
):
137 """Loads dependencies.txt, reads the libraries' .o files, and processes them.
139 Modifies dependencies.items: Recursively builds each item's system_symbols and exports.
141 global _ignored_symbols
, _obj_files
, _return_value
142 global _virtual_classes
, _weak_destructors
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")
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
))
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
))
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
))
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."))
183 print "Info: ignored symbols:\n%s" % sorted(_ignored_symbols
)
184 if not _return_value
:
185 print "OK: Specified and actual dependencies match."
188 if __name__
== "__main__":