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