forked from qt-creator/qt-creator
Change-Id: I406665390406dca324c2c98dda37540b22a065df Reviewed-by: Eike Ziller <eike.ziller@theqtcompany.com>
261 lines
8.1 KiB
Python
Executable File
261 lines
8.1 KiB
Python
Executable File
#! /usr/bin/env python2
|
|
|
|
############################################################################
|
|
#
|
|
# Copyright (C) 2016 The Qt Company Ltd.
|
|
# Contact: https://www.qt.io/licensing/
|
|
#
|
|
# This file is part of Qt Creator.
|
|
#
|
|
# Commercial License Usage
|
|
# Licensees holding valid commercial Qt licenses may use this file in
|
|
# accordance with the commercial license agreement provided with the
|
|
# Software or, alternatively, in accordance with the terms contained in
|
|
# a written agreement between you and The Qt Company. For licensing terms
|
|
# and conditions see https://www.qt.io/terms-conditions. For further
|
|
# information use the contact form at https://www.qt.io/contact-us.
|
|
#
|
|
# GNU General Public License Usage
|
|
# Alternatively, this file may be used under the terms of the GNU
|
|
# General Public License version 3 as published by the Free Software
|
|
# Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
|
# included in the packaging of this file. Please review the following
|
|
# information to ensure the GNU General Public License requirements will
|
|
# be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
|
#
|
|
############################################################################
|
|
|
|
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):
|
|
print 'Resolving symbols for {}...'.format(plugin.name)
|
|
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 = {}
|
|
lib = {}
|
|
for p in plugin.symbolDependencies:
|
|
if p.isPlugin():
|
|
symb[p.name] = plugin.symbolDependencies[p]
|
|
else:
|
|
lib[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])
|
|
for i in lib:
|
|
total = lib[i]['total']
|
|
print ' LIBRARY {} used ({} usages)'.format(i, total)
|
|
|
|
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):
|
|
pluginspecs = glob.glob(os.path.join(path, "lib", "qtcreator", "plugins", "*.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)
|