Eli Friedman | 77a1fe9 | 2009-07-10 20:15:12 +0000 | [diff] [blame] | 1 | #!/usr/bin/env python |
Daniel Dunbar | ab13061 | 2009-01-13 07:38:29 +0000 | [diff] [blame] | 2 | |
Stephen Hines | 6bcf27b | 2014-05-29 04:14:42 -0700 | [diff] [blame^] | 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 | |
Daniel Dunbar | ab13061 | 2009-01-13 07:38:29 +0000 | [diff] [blame] | 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 | |
Daniel Dunbar | 8f5fdbe | 2009-09-04 17:41:47 +0000 | [diff] [blame] | 41 | def cost(a, b): |
| 42 | return sum(map(dist, a + [None] * (len(b) - len(a)), b)) |
Daniel Dunbar | ab13061 | 2009-01-13 07:38:29 +0000 | [diff] [blame] | 43 | |
Daniel Dunbar | 8f5fdbe | 2009-09-04 17:41:47 +0000 | [diff] [blame] | 44 | # Normalize so a is shortest. |
| 45 | if len(b) < len(a): |
| 46 | b, a = insertMinimumPadding(b, a, dist) |
| 47 | return a,b |
Daniel Dunbar | ab13061 | 2009-01-13 07:38:29 +0000 | [diff] [blame] | 48 | |
Daniel Dunbar | 8f5fdbe | 2009-09-04 17:41:47 +0000 | [diff] [blame] | 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 |
Daniel Dunbar | ab13061 | 2009-01-13 07:38:29 +0000 | [diff] [blame] | 61 | |
| 62 | class ZipperDiff(object): |
Chris Lattner | fc8f0e1 | 2011-04-15 05:22:18 +0000 | [diff] [blame] | 63 | """ZipperDiff - Simple (slow) diff only accommodating inserts.""" |
Daniel Dunbar | ab13061 | 2009-01-13 07:38:29 +0000 | [diff] [blame] | 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 |
Daniel Dunbar | 7671591 | 2009-01-28 19:26:20 +0000 | [diff] [blame] | 104 | ln.startswith('gcc version') or |
Daniel Dunbar | ad8958c | 2009-09-04 18:35:09 +0000 | [diff] [blame] | 105 | ln.startswith('clang version')): |
Daniel Dunbar | ab13061 | 2009-01-13 07:38:29 +0000 | [diff] [blame] | 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(): |
Daniel Dunbar | 2253dc4 | 2009-01-14 20:06:04 +0000 | [diff] [blame] | 125 | import os, sys |
Daniel Dunbar | ab13061 | 2009-01-13 07:38:29 +0000 | [diff] [blame] | 126 | |
| 127 | args = sys.argv[1:] |
Daniel Dunbar | 2253dc4 | 2009-01-14 20:06:04 +0000 | [diff] [blame] | 128 | driverA = os.getenv('DRIVER_A') or 'gcc' |
Daniel Dunbar | 8f5fdbe | 2009-09-04 17:41:47 +0000 | [diff] [blame] | 129 | driverB = os.getenv('DRIVER_B') or 'clang' |
Daniel Dunbar | ab13061 | 2009-01-13 07:38:29 +0000 | [diff] [blame] | 130 | |
| 131 | infoA = captureDriverInfo(driverA, args) |
| 132 | infoB = captureDriverInfo(driverB, args) |
| 133 | |
Daniel Dunbar | c97c05f | 2009-01-17 00:50:45 +0000 | [diff] [blame] | 134 | differ = False |
| 135 | |
Daniel Dunbar | ab13061 | 2009-01-13 07:38:29 +0000 | [diff] [blame] | 136 | # Compare stdout. |
| 137 | if infoA.stdout != infoB.stdout: |
| 138 | print '-- STDOUT DIFFERS -' |
Daniel Dunbar | ad8958c | 2009-09-04 18:35:09 +0000 | [diff] [blame] | 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 | |
Daniel Dunbar | c97c05f | 2009-01-17 00:50:45 +0000 | [diff] [blame] | 154 | differ = True |
Daniel Dunbar | ab13061 | 2009-01-13 07:38:29 +0000 | [diff] [blame] | 155 | |
| 156 | # Compare stderr. |
| 157 | if infoA.stderr != infoB.stderr: |
| 158 | print '-- STDERR DIFFERS -' |
Daniel Dunbar | ad8958c | 2009-09-04 18:35:09 +0000 | [diff] [blame] | 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 | |
Daniel Dunbar | c97c05f | 2009-01-17 00:50:45 +0000 | [diff] [blame] | 174 | differ = True |
Daniel Dunbar | ab13061 | 2009-01-13 07:38:29 +0000 | [diff] [blame] | 175 | |
| 176 | # Compare commands. |
Daniel Dunbar | 163f31a | 2009-01-21 23:34:23 +0000 | [diff] [blame] | 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 | |
Daniel Dunbar | ab13061 | 2009-01-13 07:38:29 +0000 | [diff] [blame] | 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 |
Daniel Dunbar | 06172d6 | 2009-01-20 00:47:24 +0000 | [diff] [blame] | 202 | differ = True |
Daniel Dunbar | 163f31a | 2009-01-21 23:34:23 +0000 | [diff] [blame] | 203 | |
Daniel Dunbar | ab13061 | 2009-01-13 07:38:29 +0000 | [diff] [blame] | 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 |
Daniel Dunbar | c97c05f | 2009-01-17 00:50:45 +0000 | [diff] [blame] | 209 | differ = True |
| 210 | |
| 211 | if differ: |
| 212 | sys.exit(1) |
Daniel Dunbar | ab13061 | 2009-01-13 07:38:29 +0000 | [diff] [blame] | 213 | |
| 214 | if __name__ == '__main__': |
Daniel Dunbar | 8f5fdbe | 2009-09-04 17:41:47 +0000 | [diff] [blame] | 215 | main() |