forked from qt-creator/qt-creator
Due to the change of using json based plugin information instead of xml based (pluginspec) this script became disfunctional long time ago.. Adapt the script to use the json information generated while building QC. This script is still intended to find unused or unlisted dependecies of plugins. Prerequisites: Linux platform Usage (just as before): inside the build directory of QC just run the script directly or pass it to the Python interpreter. (still limited to Python 2.x with x>=7) Change-Id: I92b48327944df1239e9fac7fbd8ed1bee4fefa61 Reviewed-by: Tobias Hunger <tobias.hunger@qt.io>
265 lines
8.3 KiB
Python
Executable File
265 lines
8.3 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 json
|
|
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 = json.load(f)
|
|
self.name = content.get("Name")
|
|
dependencies = content.get("Dependencies")
|
|
if dependencies is not None:
|
|
for dep in dependencies:
|
|
depName = dep.get("Name")
|
|
depType = dep.get("Type")
|
|
if depName is None:
|
|
log.critical("Unnamed dependency inside '%s'?" % spec)
|
|
continue
|
|
if depType is None:
|
|
depType = 'strong'
|
|
self.specDependencies[depName] = depType
|
|
|
|
if self.name is None or self.name == '':
|
|
log.critical('Plugin name not set for spec "%s".', spec)
|
|
|
|
return os.path.normpath(os.path.join(dirname, "..", "..", "..", "lib", "qtcreator",
|
|
"plugins", "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, "src", "plugins", "*", "*.json"))
|
|
if len(pluginspecs) == 0:
|
|
log.critical("This script only works for qmake builds that have not been installed "
|
|
"already.")
|
|
|
|
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)
|