forked from qt-creator/qt-creator
Plugin dependency checker script
Change-Id: Icf83c978cd576e0d23e5183d688ef0c96210d484 Reviewed-by: Tobias Hunger <tobias.hunger@digia.com>
This commit is contained in:
257
scripts/dependencyinfo.py
Normal file
257
scripts/dependencyinfo.py
Normal file
@@ -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)
|
Reference in New Issue
Block a user