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