summaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
authorTobias Hunger <tobias.hunger@digia.com>2013-03-25 16:06:17 +0100
committerTobias Hunger <tobias.hunger@digia.com>2013-05-22 16:41:30 +0200
commitec442f55040180ff95a83ee338adfd275dc32d9e (patch)
treebe82b716387a0f0a298a4ee140fecafe4e5464d5 /scripts
parent8311e202464732106fab3f7e414a083704bcb5a5 (diff)
downloadqt-creator-ec442f55040180ff95a83ee338adfd275dc32d9e.tar.gz
Plugin dependency checker script
Change-Id: Icf83c978cd576e0d23e5183d688ef0c96210d484 Reviewed-by: Tobias Hunger <tobias.hunger@digia.com>
Diffstat (limited to 'scripts')
-rw-r--r--scripts/dependencyinfo.py257
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)