Files
qt-creator/scripts/dependencyinfo.py
Christian Stenger ce69c5da15 Scripts: Make dependencyinfo script working again
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>
2017-02-23 10:43:29 +00:00

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)