• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#! /usr/bin/python
2# -*- coding: utf-8 -*-
3#
4# Copyright (C) 2011-2015, 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
13This probably works only on Linux.
14
15The exit code is 0 if everything is fine, 1 for errors, 2 for only warnings.
16
17Sample invocation:
18  ~/svn.icu/trunk/src/source/test/depstest$ ./depstest.py ~/svn.icu/trunk/dbg
19"""
20
21__author__ = "Markus W. Scherer"
22
23import glob
24import os.path
25import subprocess
26import sys
27
28import 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
41def _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    # '__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)
67      continue
68    type = fields[2].strip()
69    if type == "U":
70      obj_imports.add(name)
71    else:
72      obj_exports.add(name)
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}
82
83def _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))
87
88def _Resolve(name, parents):
89  global _ignored_symbols, _obj_files, _symbols_to_files, _return_value
90  item = dependencies.items[name]
91  item_type = item["type"]
92  if name in parents:
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.
99  parents.append(name)
100  imports = set()
101  exports = set()
102  system_symbols = item.get("system_symbols")
103  if system_symbols == None: system_symbols = item["system_symbols"] = set()
104  files = item.get("files")
105  if 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")
112  if deps:
113    for dep in 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)
135        else:
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))
139    _return_value = 1
140  del parents[-1]
141  return item
142
143def Process(root_path):
144  """Loads dependencies.txt, reads the libraries' .o files, and processes them.
145
146  Modifies dependencies.items: Recursively builds each item's system_symbols and exports.
147  """
148  global _ignored_symbols, _obj_files, _return_value
149  global _virtual_classes, _weak_destructors
150  dependencies.Load()
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")
155    if 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))
166    _return_value = 1
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))
170    _return_value = 1
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))
181      _return_value = 1
182
183def main():
184  global _return_value
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."))
188  Process(sys.argv[1])
189  if _ignored_symbols:
190    print "Info: ignored symbols:\n%s" % sorted(_ignored_symbols)
191  if not _return_value:
192    print "OK: Specified and actual dependencies match."
193  else:
194    print "Error: There were errors, please fix them and re-run. Processing may have terminated abnormally."
195  return _return_value
196
197if __name__ == "__main__":
198  sys.exit(main())
199