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)
 |