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