diff options
-rw-r--r-- | scripts/dependencyinfo.py | 257 |
1 files changed, 257 insertions, 0 deletions
diff --git a/scripts/dependencyinfo.py b/scripts/dependencyinfo.py new file mode 100644 index 0000000000..ee62162e11 --- /dev/null +++ b/scripts/dependencyinfo.py @@ -0,0 +1,257 @@ +#! /usr/bin/env python2 +################################################################################ +# Copyright (C) 2013 Digia Plc +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of Digia Plc, nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +################################################################################ + +import glob +import logging +import os +import re +import subprocess +import sys +import platform + +class Library: + def __init__(self, path): + self.path = path + self.name = '' + self.exportedSymbols = {} + + self.name = re.sub('^(.*/)?lib', '', path) + self.name = re.sub('\.so.*$', '', self.name) + + self._runNM(self.path) + + def isLibrary(self): + return True + + def isPlugin(self): + return False + + def debugDump(self): + log.debug('Library "%s" exports %d symbols.', self.name, len(self.exportedSymbols)) + + def _runNM(self, path): + try: + output = subprocess.check_output(['/usr/bin/nm', '--demangle', path], stderr=subprocess.STDOUT).splitlines() + except: + output = [] + for line in output: + self._parseNMline(line) + + def _parseNMline(self, line): + m = re.search('^[0-9a-fA-F]{8,16} [TD] (.*)$', line) + if m: + self.exportedSymbols[m.group(1)] = 1 + + + +class Plugin(Library): + def __init__(self, spec): + self.pluginSpec = spec + self.specDependencies = {} + self.symbolDependencies = {} + self.name = '' + self.importedSymbols = [] + self.path = self._parsePluginSpec(spec) + Library.__init__(self, self.path) + + self.importedSymbols.sort() + + def isLibrary(self): + return False + + def isPlugin(self): + return True + + def debugDump(self): + log.debug('Plugin "%s" imports %d symbols and exports %d symbols.', self.name, len(self.importedSymbols), + len(self.exportedSymbols)) + for i in self.specDependencies: + log.debug(' Spec declares dependency on "%s"', i) + for i in self.symbolDependencies: + tmp = 'plugin' + if i.isLibrary(): + tmp = 'lib' + log.debug(' Symbol dependency on %s "%s" (%d)', tmp, i.name, self.symbolDependencies[i]) + + def _parsePluginSpec(self, spec): + dirname = os.path.dirname(spec) + with open(spec) as f: + content = f.readlines() + for line in content: + m = re.search('(plugin|dependency)\s+name="([^"]+)"(?:.*\stype="([^"]+)")?', line) + if not(m): + continue + if m.group(1) == 'plugin': + if self.name != '': + log.critical('Plugin name already set to "%s"!', self.name) + else: + self.name = m.group(2) + else: + kind = m.group(3) + if not(kind): + kind = 'strong' + self.specDependencies[m.group(2)] = kind + + if self.name == '': + log.critical('Plugin name not set for spec "%s".', spec) + + return os.path.join(dirname, "lib%s.so" % self.name) + + def _parseNMline(self, line): + m = re.search('^\s+ U (.*)$', line) + if m: + self.importedSymbols.append(m.group(1)) + else: + Library._parseNMline(self, line) + + def addSymbolDependency(self, dep, symbol): + if dep in self.symbolDependencies: + self.symbolDependencies[dep]['total'] += 1 + else: + self.symbolDependencies[dep] = {} + self.symbolDependencies[dep]['total'] = 1 + + self.symbolDependencies[dep][symbol] = 1 + + + +class SymbolResolver: + def __init__(self, plugins, libraries): + self.libraries = libraries + self.libraries.extend(plugins) + + for i in plugins: + self._resolve(i) + + def _resolve(self, plugin): + for symbol in plugin.importedSymbols: + lib = self._resolveSymbol(symbol) + if lib: + plugin.addSymbolDependency(lib, symbol) + + def _resolveSymbol(self, symbol): + for i in self.libraries: + if symbol in i.exportedSymbols: + return i + return None + + + +class Reporter: + def __init__(self, plugins): + for i in plugins: + self._reportPluginSpecIssues(i) + + def _reportPluginSpecIssues(self, plugin): + print 'Plugin "{}" imports {} symbols and exports {} symbols.'.format( + plugin.name, len(plugin.importedSymbols), len(plugin.exportedSymbols)) + + spec = plugin.specDependencies + symb = {} + for p in plugin.symbolDependencies: + if p.isPlugin(): + symb[p.name] = plugin.symbolDependencies[p] + + for i in spec: + if i in symb: + total = symb[i]['total'] + print ' {}: OK ({} usages)'.format(i, total) + + self._printSome(symb[i]) + del symb[i] + else: + if spec[i] == 'optional': + print ' {}: OK (optional)'.format(i) + else: + print ' {}: WARNING: unused'.format(i) + for i in symb: + total = symb[i]['total'] + print ' {}: ERROR: undeclared ({} usages)'.format(i, total) + self._printSome(symb[i]) + + def _printSome(self, data): + keys = data.keys() + if len(keys) <= 11: + for i in keys: + if i != 'total': + print ' {}'.format(i) + + + +class BinaryDirExaminer: + def __init__(self, path): + self.libraries = [] + self.plugins = [] + self.binaryDir = path + + log.debug('Examining directory "%s".', path) + + self._findLibraries(path) + self._findPlugins(path) + + def _findLibraries(self, path): + libdir = glob.glob(os.path.join(path, "lib", "qtcreator", "lib*")) + for l in libdir: + if os.path.islink(l): + continue + log.debug(' Looking at library "%s".', l) + self.libraries.append(Library(l)) + + def _findPlugins(self, path): + vendordirs = glob.glob(os.path.join(path, "lib", "qtcreator", "plugins", "*")) + for dir in vendordirs: + pluginspecs = glob.glob(os.path.join(dir, "*.pluginspec")) + for spec in pluginspecs: + log.debug(' Looking at plugin "%s".', spec) + self.plugins.append(Plugin(spec)) + + + +if __name__ == '__main__': + # Setup logging: + log = logging.getLogger('log') + log.setLevel(logging.DEBUG) + ch = logging.StreamHandler() + ch.setLevel(logging.DEBUG) + log.addHandler(ch) + + # Make sure we are on linux: + if platform.system() != 'Linux': + log.critical("This check can only run on Linux") + sys.exit(1) + + # Sanity check: + if not(os.path.exists(os.path.join(os.getcwd(), "bin", "qtcreator"))): + log.critical('Not a top level Qt Creator build directory.') + sys.exit(1) + + binExaminer = BinaryDirExaminer(os.path.abspath(os.getcwd())) + # Find symbol dependencies: + resolver = SymbolResolver(binExaminer.plugins, binExaminer.libraries) + reporter = Reporter(binExaminer.plugins) |