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