| 
									
										
										
										
											2013-03-25 16:06:17 +01:00
										 |  |  | #! /usr/bin/env python2 | 
					
						
							| 
									
										
										
										
											2016-01-15 14:52:30 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | ############################################################################ | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | # Copyright (C) 2016 The Qt Company Ltd. | 
					
						
							|  |  |  | # Contact: https://www.qt.io/licensing/ | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | # This file is part of Qt Creator. | 
					
						
							| 
									
										
										
										
											2013-03-25 16:06:17 +01:00
										 |  |  | # | 
					
						
							| 
									
										
										
										
											2016-01-15 14:52:30 +01:00
										 |  |  | # 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. | 
					
						
							| 
									
										
										
										
											2013-03-25 16:06:17 +01:00
										 |  |  | # | 
					
						
							| 
									
										
										
										
											2016-01-15 14:52:30 +01:00
										 |  |  | # 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. | 
					
						
							| 
									
										
										
										
											2013-03-25 16:06:17 +01:00
										 |  |  | # | 
					
						
							| 
									
										
										
										
											2016-01-15 14:52:30 +01:00
										 |  |  | ############################################################################ | 
					
						
							| 
									
										
										
										
											2013-03-25 16:06:17 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 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): | 
					
						
							| 
									
										
										
										
											2013-05-28 12:31:49 +02:00
										 |  |  |         print 'Resolving symbols for {}...'.format(plugin.name) | 
					
						
							| 
									
										
										
										
											2013-03-25 16:06:17 +01:00
										 |  |  |         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 = {} | 
					
						
							| 
									
										
										
										
											2013-05-28 12:31:49 +02:00
										 |  |  |         lib = {} | 
					
						
							| 
									
										
										
										
											2013-03-25 16:06:17 +01:00
										 |  |  |         for p in plugin.symbolDependencies: | 
					
						
							|  |  |  |             if p.isPlugin(): | 
					
						
							|  |  |  |                 symb[p.name] = plugin.symbolDependencies[p] | 
					
						
							| 
									
										
										
										
											2013-05-28 12:31:49 +02:00
										 |  |  |             else: | 
					
						
							|  |  |  |                 lib[p.name] = plugin.symbolDependencies[p] | 
					
						
							| 
									
										
										
										
											2013-03-25 16:06:17 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         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]) | 
					
						
							| 
									
										
										
										
											2013-05-28 12:31:49 +02:00
										 |  |  |         for i in lib: | 
					
						
							|  |  |  |             total = lib[i]['total'] | 
					
						
							|  |  |  |             print '    LIBRARY {} used ({} usages)'.format(i, total) | 
					
						
							| 
									
										
										
										
											2013-03-25 16:06:17 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     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): | 
					
						
							| 
									
										
										
										
											2014-05-26 16:23:35 +02:00
										 |  |  |         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)) | 
					
						
							| 
									
										
										
										
											2013-03-25 16:06:17 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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) |