Commit ec442f55 authored by Tobias Hunger's avatar Tobias Hunger
Browse files

Plugin dependency checker script



Change-Id: Icf83c978cd576e0d23e5183d688ef0c96210d484
Reviewed-by: default avatarTobias Hunger <tobias.hunger@digia.com>
parent 8311e202
#! /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)
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment