| 
									
										
										
										
											2013-03-25 16:06:17 +01:00
										 |  |  | #! /usr/bin/env python2 | 
					
						
							|  |  |  | ################################################################################ | 
					
						
							| 
									
										
										
										
											2014-01-07 13:27:11 +01:00
										 |  |  | # Copyright (C) 2014 Digia Plc | 
					
						
							| 
									
										
										
										
											2013-03-25 16:06:17 +01:00
										 |  |  | # All rights reserved. | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | # Redistribution and use in source and binary forms, with or without | 
					
						
							|  |  |  | # modification, are permitted provided that the following conditions are met: | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | #   * Redistributions of source code must retain the above copyright notice, | 
					
						
							|  |  |  | #     this list of conditions and the following disclaimer. | 
					
						
							|  |  |  | #   * Redistributions in binary form must reproduce the above copyright notice, | 
					
						
							|  |  |  | #     this list of conditions and the following disclaimer in the documentation | 
					
						
							|  |  |  | #     and/or other materials provided with the distribution. | 
					
						
							|  |  |  | #   * Neither the name of Digia Plc, nor the names of its contributors | 
					
						
							|  |  |  | #     may be used to endorse or promote products derived from this software | 
					
						
							|  |  |  | #     without specific prior written permission. | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | 
					
						
							|  |  |  | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | 
					
						
							|  |  |  | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | 
					
						
							|  |  |  | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE | 
					
						
							|  |  |  | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | 
					
						
							|  |  |  | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | 
					
						
							|  |  |  | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | 
					
						
							|  |  |  | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | 
					
						
							|  |  |  | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
					
						
							|  |  |  | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 
					
						
							|  |  |  | ################################################################################ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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) |