forked from qt-creator/qt-creator
GPL-3.0 is deprecated by SPDX. Change done by find . -type f -exec perl -pi -e 's/LicenseRef-Qt-Commercial OR GPL-3.0(?!-)/LicenseRef-Qt-Commercial OR GPL-3.0-only/g' {} \; Change-Id: If316a498e3f27d2030b86d4e7743b3237ce09939 Reviewed-by: Lucie Gerard <lucie.gerard@qt.io> Reviewed-by: <github-actions-qt-creator@cristianadam.eu> Reviewed-by: Eike Ziller <eike.ziller@qt.io>
243 lines
7.3 KiB
Python
Executable File
243 lines
7.3 KiB
Python
Executable File
#! /usr/bin/env python2
|
|
|
|
# Copyright (C) 2016 The Qt Company Ltd.
|
|
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
|
|
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)
|