216 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
			
		
		
	
	
			216 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
| #!/usr/bin/env python
 | |
| 
 | |
| """
 | |
| A simple utility that compares tool invocations and exit codes issued by
 | |
| compiler drivers that support -### (e.g. gcc and clang).
 | |
| """
 | |
| 
 | |
| import subprocess
 | |
| 
 | |
| def splitArgs(s):
 | |
|     it = iter(s)
 | |
|     current = ''
 | |
|     inQuote = False
 | |
|     for c in it:
 | |
|         if c == '"':
 | |
|             if inQuote:
 | |
|                 inQuote = False
 | |
|                 yield current + '"'
 | |
|             else:
 | |
|                 inQuote = True
 | |
|                 current = '"'
 | |
|         elif inQuote:
 | |
|             if c == '\\':
 | |
|                 current += c
 | |
|                 current += it.next()
 | |
|             else:
 | |
|                 current += c
 | |
|         elif not c.isspace():
 | |
|             yield c
 | |
| 
 | |
| def insertMinimumPadding(a, b, dist):
 | |
|     """insertMinimumPadding(a,b) -> (a',b')
 | |
| 
 | |
|     Return two lists of equal length, where some number of Nones have
 | |
|     been inserted into the shorter list such that sum(map(dist, a',
 | |
|     b')) is minimized.
 | |
| 
 | |
|     Assumes dist(X, Y) -> int and non-negative.
 | |
|     """
 | |
|     
 | |
|     def cost(a, b):
 | |
|         return sum(map(dist, a + [None] * (len(b) - len(a)), b))
 | |
| 
 | |
|     # Normalize so a is shortest.
 | |
|     if len(b) < len(a):
 | |
|         b, a = insertMinimumPadding(b, a, dist)
 | |
|         return a,b
 | |
| 
 | |
|     # For each None we have to insert...
 | |
|     for i in range(len(b) - len(a)):
 | |
|         # For each position we could insert it...
 | |
|         current = cost(a, b)
 | |
|         best = None
 | |
|         for j in range(len(a) + 1):
 | |
|             a_0 = a[:j] + [None] + a[j:]
 | |
|             candidate = cost(a_0, b)
 | |
|             if best is None or candidate < best[0]:
 | |
|                 best = (candidate, a_0, j)
 | |
|         a = best[1]
 | |
|     return a,b
 | |
| 
 | |
| class ZipperDiff(object):
 | |
|     """ZipperDiff - Simple (slow) diff only accommodating inserts."""
 | |
|     
 | |
|     def __init__(self, a, b):
 | |
|         self.a = a
 | |
|         self.b = b
 | |
| 
 | |
|     def dist(self, a, b):
 | |
|         return a != b
 | |
| 
 | |
|     def getDiffs(self):
 | |
|         a,b =  insertMinimumPadding(self.a, self.b, self.dist)
 | |
|         for aElt,bElt in zip(a,b):
 | |
|             if self.dist(aElt, bElt):
 | |
|                 yield aElt,bElt
 | |
| 
 | |
| class DriverZipperDiff(ZipperDiff):
 | |
|     def isTempFile(self, filename):
 | |
|         if filename[0] != '"' or filename[-1] != '"':
 | |
|             return False
 | |
|         return (filename.startswith('/tmp/', 1) or
 | |
|                 filename.startswith('/var/', 1))
 | |
| 
 | |
|     def dist(self, a, b):
 | |
|         if a and b and self.isTempFile(a) and self.isTempFile(b):
 | |
|             return 0
 | |
|         return super(DriverZipperDiff, self).dist(a,b)        
 | |
| 
 | |
| class CompileInfo:
 | |
|     def __init__(self, out, err, res):
 | |
|         self.commands = []
 | |
|         
 | |
|         # Standard out isn't used for much.
 | |
|         self.stdout = out
 | |
|         self.stderr = ''
 | |
| 
 | |
|         # FIXME: Compare error messages as well.
 | |
|         for ln in err.split('\n'):
 | |
|             if (ln == 'Using built-in specs.' or
 | |
|                 ln.startswith('Target: ') or
 | |
|                 ln.startswith('Configured with: ') or
 | |
|                 ln.startswith('Thread model: ') or
 | |
|                 ln.startswith('gcc version') or
 | |
|                 ln.startswith('clang version')):
 | |
|                 pass
 | |
|             elif ln.strip().startswith('"'):
 | |
|                 self.commands.append(list(splitArgs(ln)))
 | |
|             else:
 | |
|                 self.stderr += ln + '\n'
 | |
|         
 | |
|         self.stderr = self.stderr.strip()
 | |
|         self.exitCode = res
 | |
| 
 | |
| def captureDriverInfo(cmd, args):
 | |
|     p = subprocess.Popen([cmd,'-###'] + args,
 | |
|                          stdin=None,
 | |
|                          stdout=subprocess.PIPE,
 | |
|                          stderr=subprocess.PIPE)
 | |
|     out,err = p.communicate()
 | |
|     res = p.wait()
 | |
|     return CompileInfo(out,err,res)
 | |
| 
 | |
| def main():
 | |
|     import os, sys
 | |
| 
 | |
|     args = sys.argv[1:]
 | |
|     driverA = os.getenv('DRIVER_A') or 'gcc'
 | |
|     driverB = os.getenv('DRIVER_B') or 'clang'
 | |
| 
 | |
|     infoA = captureDriverInfo(driverA, args)
 | |
|     infoB = captureDriverInfo(driverB, args)
 | |
| 
 | |
|     differ = False
 | |
| 
 | |
|     # Compare stdout.
 | |
|     if infoA.stdout != infoB.stdout:
 | |
|         print '-- STDOUT DIFFERS -'
 | |
|         print 'A OUTPUT: ',infoA.stdout
 | |
|         print 'B OUTPUT: ',infoB.stdout
 | |
|         print
 | |
| 
 | |
|         diff = ZipperDiff(infoA.stdout.split('\n'),
 | |
|                           infoB.stdout.split('\n'))
 | |
|         for i,(aElt,bElt) in enumerate(diff.getDiffs()):
 | |
|             if aElt is None:
 | |
|                 print 'A missing: %s' % bElt
 | |
|             elif bElt is None:
 | |
|                 print 'B missing: %s' % aElt
 | |
|             else:
 | |
|                 print 'mismatch: A: %s' % aElt
 | |
|                 print '          B: %s' % bElt
 | |
| 
 | |
|         differ = True
 | |
| 
 | |
|     # Compare stderr.
 | |
|     if infoA.stderr != infoB.stderr:
 | |
|         print '-- STDERR DIFFERS -'
 | |
|         print 'A STDERR: ',infoA.stderr
 | |
|         print 'B STDERR: ',infoB.stderr
 | |
|         print
 | |
| 
 | |
|         diff = ZipperDiff(infoA.stderr.split('\n'),
 | |
|                           infoB.stderr.split('\n'))
 | |
|         for i,(aElt,bElt) in enumerate(diff.getDiffs()):
 | |
|             if aElt is None:
 | |
|                 print 'A missing: %s' % bElt
 | |
|             elif bElt is None:
 | |
|                 print 'B missing: %s' % aElt
 | |
|             else:
 | |
|                 print 'mismatch: A: %s' % aElt
 | |
|                 print '          B: %s' % bElt
 | |
| 
 | |
|         differ = True
 | |
| 
 | |
|     # Compare commands.
 | |
|     for i,(a,b) in enumerate(map(None, infoA.commands, infoB.commands)):
 | |
|         if a is None:
 | |
|             print 'A MISSING:',' '.join(b)
 | |
|             differ = True
 | |
|             continue
 | |
|         elif b is None:
 | |
|             print 'B MISSING:',' '.join(a)
 | |
|             differ = True
 | |
|             continue
 | |
| 
 | |
|         diff = DriverZipperDiff(a,b)
 | |
|         diffs = list(diff.getDiffs())
 | |
|         if diffs:
 | |
|             print '-- COMMAND %d DIFFERS -' % i
 | |
|             print 'A COMMAND:',' '.join(a)
 | |
|             print 'B COMMAND:',' '.join(b)
 | |
|             print
 | |
|             for i,(aElt,bElt) in enumerate(diffs):
 | |
|                 if aElt is None:
 | |
|                     print 'A missing: %s' % bElt
 | |
|                 elif bElt is None:
 | |
|                     print 'B missing: %s' % aElt
 | |
|                 else:
 | |
|                     print 'mismatch: A: %s' % aElt
 | |
|                     print '          B: %s' % bElt
 | |
|             differ = True
 | |
|     
 | |
|     # Compare result codes.
 | |
|     if infoA.exitCode != infoB.exitCode:
 | |
|         print '-- EXIT CODES DIFFER -'
 | |
|         print 'A: ',infoA.exitCode
 | |
|         print 'B: ',infoB.exitCode
 | |
|         differ = True
 | |
| 
 | |
|     if differ:
 | |
|         sys.exit(1)
 | |
| 
 | |
| if __name__ == '__main__':
 | |
|     main()
 |