1*7330f729Sjoerg#!/usr/bin/env python 2*7330f729Sjoerg 3*7330f729Sjoerg""" 4*7330f729SjoergA simple utility that compares tool invocations and exit codes issued by 5*7330f729Sjoergcompiler drivers that support -### (e.g. gcc and clang). 6*7330f729Sjoerg""" 7*7330f729Sjoerg 8*7330f729Sjoergimport subprocess 9*7330f729Sjoerg 10*7330f729Sjoergdef splitArgs(s): 11*7330f729Sjoerg it = iter(s) 12*7330f729Sjoerg current = '' 13*7330f729Sjoerg inQuote = False 14*7330f729Sjoerg for c in it: 15*7330f729Sjoerg if c == '"': 16*7330f729Sjoerg if inQuote: 17*7330f729Sjoerg inQuote = False 18*7330f729Sjoerg yield current + '"' 19*7330f729Sjoerg else: 20*7330f729Sjoerg inQuote = True 21*7330f729Sjoerg current = '"' 22*7330f729Sjoerg elif inQuote: 23*7330f729Sjoerg if c == '\\': 24*7330f729Sjoerg current += c 25*7330f729Sjoerg current += it.next() 26*7330f729Sjoerg else: 27*7330f729Sjoerg current += c 28*7330f729Sjoerg elif not c.isspace(): 29*7330f729Sjoerg yield c 30*7330f729Sjoerg 31*7330f729Sjoergdef insertMinimumPadding(a, b, dist): 32*7330f729Sjoerg """insertMinimumPadding(a,b) -> (a',b') 33*7330f729Sjoerg 34*7330f729Sjoerg Return two lists of equal length, where some number of Nones have 35*7330f729Sjoerg been inserted into the shorter list such that sum(map(dist, a', 36*7330f729Sjoerg b')) is minimized. 37*7330f729Sjoerg 38*7330f729Sjoerg Assumes dist(X, Y) -> int and non-negative. 39*7330f729Sjoerg """ 40*7330f729Sjoerg 41*7330f729Sjoerg def cost(a, b): 42*7330f729Sjoerg return sum(map(dist, a + [None] * (len(b) - len(a)), b)) 43*7330f729Sjoerg 44*7330f729Sjoerg # Normalize so a is shortest. 45*7330f729Sjoerg if len(b) < len(a): 46*7330f729Sjoerg b, a = insertMinimumPadding(b, a, dist) 47*7330f729Sjoerg return a,b 48*7330f729Sjoerg 49*7330f729Sjoerg # For each None we have to insert... 50*7330f729Sjoerg for i in range(len(b) - len(a)): 51*7330f729Sjoerg # For each position we could insert it... 52*7330f729Sjoerg current = cost(a, b) 53*7330f729Sjoerg best = None 54*7330f729Sjoerg for j in range(len(a) + 1): 55*7330f729Sjoerg a_0 = a[:j] + [None] + a[j:] 56*7330f729Sjoerg candidate = cost(a_0, b) 57*7330f729Sjoerg if best is None or candidate < best[0]: 58*7330f729Sjoerg best = (candidate, a_0, j) 59*7330f729Sjoerg a = best[1] 60*7330f729Sjoerg return a,b 61*7330f729Sjoerg 62*7330f729Sjoergclass ZipperDiff(object): 63*7330f729Sjoerg """ZipperDiff - Simple (slow) diff only accommodating inserts.""" 64*7330f729Sjoerg 65*7330f729Sjoerg def __init__(self, a, b): 66*7330f729Sjoerg self.a = a 67*7330f729Sjoerg self.b = b 68*7330f729Sjoerg 69*7330f729Sjoerg def dist(self, a, b): 70*7330f729Sjoerg return a != b 71*7330f729Sjoerg 72*7330f729Sjoerg def getDiffs(self): 73*7330f729Sjoerg a,b = insertMinimumPadding(self.a, self.b, self.dist) 74*7330f729Sjoerg for aElt,bElt in zip(a,b): 75*7330f729Sjoerg if self.dist(aElt, bElt): 76*7330f729Sjoerg yield aElt,bElt 77*7330f729Sjoerg 78*7330f729Sjoergclass DriverZipperDiff(ZipperDiff): 79*7330f729Sjoerg def isTempFile(self, filename): 80*7330f729Sjoerg if filename[0] != '"' or filename[-1] != '"': 81*7330f729Sjoerg return False 82*7330f729Sjoerg return (filename.startswith('/tmp/', 1) or 83*7330f729Sjoerg filename.startswith('/var/', 1)) 84*7330f729Sjoerg 85*7330f729Sjoerg def dist(self, a, b): 86*7330f729Sjoerg if a and b and self.isTempFile(a) and self.isTempFile(b): 87*7330f729Sjoerg return 0 88*7330f729Sjoerg return super(DriverZipperDiff, self).dist(a,b) 89*7330f729Sjoerg 90*7330f729Sjoergclass CompileInfo: 91*7330f729Sjoerg def __init__(self, out, err, res): 92*7330f729Sjoerg self.commands = [] 93*7330f729Sjoerg 94*7330f729Sjoerg # Standard out isn't used for much. 95*7330f729Sjoerg self.stdout = out 96*7330f729Sjoerg self.stderr = '' 97*7330f729Sjoerg 98*7330f729Sjoerg # FIXME: Compare error messages as well. 99*7330f729Sjoerg for ln in err.split('\n'): 100*7330f729Sjoerg if (ln == 'Using built-in specs.' or 101*7330f729Sjoerg ln.startswith('Target: ') or 102*7330f729Sjoerg ln.startswith('Configured with: ') or 103*7330f729Sjoerg ln.startswith('Thread model: ') or 104*7330f729Sjoerg ln.startswith('gcc version') or 105*7330f729Sjoerg ln.startswith('clang version')): 106*7330f729Sjoerg pass 107*7330f729Sjoerg elif ln.strip().startswith('"'): 108*7330f729Sjoerg self.commands.append(list(splitArgs(ln))) 109*7330f729Sjoerg else: 110*7330f729Sjoerg self.stderr += ln + '\n' 111*7330f729Sjoerg 112*7330f729Sjoerg self.stderr = self.stderr.strip() 113*7330f729Sjoerg self.exitCode = res 114*7330f729Sjoerg 115*7330f729Sjoergdef captureDriverInfo(cmd, args): 116*7330f729Sjoerg p = subprocess.Popen([cmd,'-###'] + args, 117*7330f729Sjoerg stdin=None, 118*7330f729Sjoerg stdout=subprocess.PIPE, 119*7330f729Sjoerg stderr=subprocess.PIPE) 120*7330f729Sjoerg out,err = p.communicate() 121*7330f729Sjoerg res = p.wait() 122*7330f729Sjoerg return CompileInfo(out,err,res) 123*7330f729Sjoerg 124*7330f729Sjoergdef main(): 125*7330f729Sjoerg import os, sys 126*7330f729Sjoerg 127*7330f729Sjoerg args = sys.argv[1:] 128*7330f729Sjoerg driverA = os.getenv('DRIVER_A') or 'gcc' 129*7330f729Sjoerg driverB = os.getenv('DRIVER_B') or 'clang' 130*7330f729Sjoerg 131*7330f729Sjoerg infoA = captureDriverInfo(driverA, args) 132*7330f729Sjoerg infoB = captureDriverInfo(driverB, args) 133*7330f729Sjoerg 134*7330f729Sjoerg differ = False 135*7330f729Sjoerg 136*7330f729Sjoerg # Compare stdout. 137*7330f729Sjoerg if infoA.stdout != infoB.stdout: 138*7330f729Sjoerg print '-- STDOUT DIFFERS -' 139*7330f729Sjoerg print 'A OUTPUT: ',infoA.stdout 140*7330f729Sjoerg print 'B OUTPUT: ',infoB.stdout 141*7330f729Sjoerg print 142*7330f729Sjoerg 143*7330f729Sjoerg diff = ZipperDiff(infoA.stdout.split('\n'), 144*7330f729Sjoerg infoB.stdout.split('\n')) 145*7330f729Sjoerg for i,(aElt,bElt) in enumerate(diff.getDiffs()): 146*7330f729Sjoerg if aElt is None: 147*7330f729Sjoerg print 'A missing: %s' % bElt 148*7330f729Sjoerg elif bElt is None: 149*7330f729Sjoerg print 'B missing: %s' % aElt 150*7330f729Sjoerg else: 151*7330f729Sjoerg print 'mismatch: A: %s' % aElt 152*7330f729Sjoerg print ' B: %s' % bElt 153*7330f729Sjoerg 154*7330f729Sjoerg differ = True 155*7330f729Sjoerg 156*7330f729Sjoerg # Compare stderr. 157*7330f729Sjoerg if infoA.stderr != infoB.stderr: 158*7330f729Sjoerg print '-- STDERR DIFFERS -' 159*7330f729Sjoerg print 'A STDERR: ',infoA.stderr 160*7330f729Sjoerg print 'B STDERR: ',infoB.stderr 161*7330f729Sjoerg print 162*7330f729Sjoerg 163*7330f729Sjoerg diff = ZipperDiff(infoA.stderr.split('\n'), 164*7330f729Sjoerg infoB.stderr.split('\n')) 165*7330f729Sjoerg for i,(aElt,bElt) in enumerate(diff.getDiffs()): 166*7330f729Sjoerg if aElt is None: 167*7330f729Sjoerg print 'A missing: %s' % bElt 168*7330f729Sjoerg elif bElt is None: 169*7330f729Sjoerg print 'B missing: %s' % aElt 170*7330f729Sjoerg else: 171*7330f729Sjoerg print 'mismatch: A: %s' % aElt 172*7330f729Sjoerg print ' B: %s' % bElt 173*7330f729Sjoerg 174*7330f729Sjoerg differ = True 175*7330f729Sjoerg 176*7330f729Sjoerg # Compare commands. 177*7330f729Sjoerg for i,(a,b) in enumerate(map(None, infoA.commands, infoB.commands)): 178*7330f729Sjoerg if a is None: 179*7330f729Sjoerg print 'A MISSING:',' '.join(b) 180*7330f729Sjoerg differ = True 181*7330f729Sjoerg continue 182*7330f729Sjoerg elif b is None: 183*7330f729Sjoerg print 'B MISSING:',' '.join(a) 184*7330f729Sjoerg differ = True 185*7330f729Sjoerg continue 186*7330f729Sjoerg 187*7330f729Sjoerg diff = DriverZipperDiff(a,b) 188*7330f729Sjoerg diffs = list(diff.getDiffs()) 189*7330f729Sjoerg if diffs: 190*7330f729Sjoerg print '-- COMMAND %d DIFFERS -' % i 191*7330f729Sjoerg print 'A COMMAND:',' '.join(a) 192*7330f729Sjoerg print 'B COMMAND:',' '.join(b) 193*7330f729Sjoerg print 194*7330f729Sjoerg for i,(aElt,bElt) in enumerate(diffs): 195*7330f729Sjoerg if aElt is None: 196*7330f729Sjoerg print 'A missing: %s' % bElt 197*7330f729Sjoerg elif bElt is None: 198*7330f729Sjoerg print 'B missing: %s' % aElt 199*7330f729Sjoerg else: 200*7330f729Sjoerg print 'mismatch: A: %s' % aElt 201*7330f729Sjoerg print ' B: %s' % bElt 202*7330f729Sjoerg differ = True 203*7330f729Sjoerg 204*7330f729Sjoerg # Compare result codes. 205*7330f729Sjoerg if infoA.exitCode != infoB.exitCode: 206*7330f729Sjoerg print '-- EXIT CODES DIFFER -' 207*7330f729Sjoerg print 'A: ',infoA.exitCode 208*7330f729Sjoerg print 'B: ',infoB.exitCode 209*7330f729Sjoerg differ = True 210*7330f729Sjoerg 211*7330f729Sjoerg if differ: 212*7330f729Sjoerg sys.exit(1) 213*7330f729Sjoerg 214*7330f729Sjoergif __name__ == '__main__': 215*7330f729Sjoerg main() 216