#!/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()