217 lines · plain
1#!/usr/bin/env python2 3"""4A simple utility that compares tool invocations and exit codes issued by5compiler drivers that support -### (e.g. gcc and clang).6"""7 8from itertools import zip_longest9import subprocess10 11def splitArgs(s):12 it = iter(s)13 current = ''14 inQuote = False15 for c in it:16 if c == '"':17 if inQuote:18 inQuote = False19 yield current + '"'20 else:21 inQuote = True22 current = '"'23 elif inQuote:24 if c == '\\':25 current += c26 current += next(it)27 else:28 current += c29 elif not c.isspace():30 yield c31 32def insertMinimumPadding(a, b, dist):33 """insertMinimumPadding(a,b) -> (a',b')34 35 Return two lists of equal length, where some number of Nones have36 been inserted into the shorter list such that sum(map(dist, a',37 b')) is minimized.38 39 Assumes dist(X, Y) -> int and non-negative.40 """41 42 def cost(a, b):43 return sum(map(dist, a + [None] * (len(b) - len(a)), b))44 45 # Normalize so a is shortest.46 if len(b) < len(a):47 b, a = insertMinimumPadding(b, a, dist)48 return a,b49 50 # For each None we have to insert...51 for i in range(len(b) - len(a)):52 # For each position we could insert it...53 current = cost(a, b)54 best = None55 for j in range(len(a) + 1):56 a_0 = a[:j] + [None] + a[j:]57 candidate = cost(a_0, b)58 if best is None or candidate < best[0]:59 best = (candidate, a_0, j)60 a = best[1]61 return a,b62 63class ZipperDiff(object):64 """ZipperDiff - Simple (slow) diff only accommodating inserts."""65 66 def __init__(self, a, b):67 self.a = a68 self.b = b69 70 def dist(self, a, b):71 return a != b72 73 def getDiffs(self):74 a,b = insertMinimumPadding(self.a, self.b, self.dist)75 for aElt,bElt in zip(a,b):76 if self.dist(aElt, bElt):77 yield aElt,bElt78 79class DriverZipperDiff(ZipperDiff):80 def isTempFile(self, filename):81 if filename[0] != '"' or filename[-1] != '"':82 return False83 return (filename.startswith('/tmp/', 1) or84 filename.startswith('/var/', 1))85 86 def dist(self, a, b):87 if a and b and self.isTempFile(a) and self.isTempFile(b):88 return 089 return super(DriverZipperDiff, self).dist(a,b) 90 91class CompileInfo:92 def __init__(self, out, err, res):93 self.commands = []94 95 # Standard out isn't used for much.96 self.stdout = out97 self.stderr = ''98 99 # FIXME: Compare error messages as well.100 for ln in err.split('\n'):101 if (ln == 'Using built-in specs.' or102 ln.startswith('Target: ') or103 ln.startswith('Configured with: ') or104 ln.startswith('Thread model: ') or105 ln.startswith('gcc version') or106 ln.startswith('clang version')):107 pass108 elif ln.strip().startswith('"'):109 self.commands.append(list(splitArgs(ln)))110 else:111 self.stderr += ln + '\n'112 113 self.stderr = self.stderr.strip()114 self.exitCode = res115 116def captureDriverInfo(cmd, args):117 p = subprocess.Popen([cmd,'-###'] + args,118 stdin=None,119 stdout=subprocess.PIPE,120 stderr=subprocess.PIPE)121 out,err = p.communicate()122 res = p.wait()123 return CompileInfo(out,err,res)124 125def main():126 import os, sys127 128 args = sys.argv[1:]129 driverA = os.getenv('DRIVER_A') or 'gcc'130 driverB = os.getenv('DRIVER_B') or 'clang'131 132 infoA = captureDriverInfo(driverA, args)133 infoB = captureDriverInfo(driverB, args)134 135 differ = False136 137 # Compare stdout.138 if infoA.stdout != infoB.stdout:139 print('-- STDOUT DIFFERS -')140 print('A OUTPUT: ',infoA.stdout)141 print('B OUTPUT: ',infoB.stdout)142 print()143 144 diff = ZipperDiff(infoA.stdout.split('\n'),145 infoB.stdout.split('\n'))146 for i,(aElt,bElt) in enumerate(diff.getDiffs()):147 if aElt is None:148 print('A missing: %s' % bElt)149 elif bElt is None:150 print('B missing: %s' % aElt)151 else:152 print('mismatch: A: %s' % aElt)153 print(' B: %s' % bElt)154 155 differ = True156 157 # Compare stderr.158 if infoA.stderr != infoB.stderr:159 print('-- STDERR DIFFERS -')160 print('A STDERR: ',infoA.stderr)161 print('B STDERR: ',infoB.stderr)162 print()163 164 diff = ZipperDiff(infoA.stderr.split('\n'),165 infoB.stderr.split('\n'))166 for i,(aElt,bElt) in enumerate(diff.getDiffs()):167 if aElt is None:168 print('A missing: %s' % bElt)169 elif bElt is None:170 print('B missing: %s' % aElt)171 else:172 print('mismatch: A: %s' % aElt)173 print(' B: %s' % bElt)174 175 differ = True176 177 # Compare commands.178 for i,(a,b) in enumerate(zip_longest(infoA.commands, infoB.commands, fillvalue=None)):179 if a is None:180 print('A MISSING:',' '.join(b))181 differ = True182 continue183 elif b is None:184 print('B MISSING:',' '.join(a))185 differ = True186 continue187 188 diff = DriverZipperDiff(a,b)189 diffs = list(diff.getDiffs())190 if diffs:191 print('-- COMMAND %d DIFFERS -' % i)192 print('A COMMAND:',' '.join(a))193 print('B COMMAND:',' '.join(b))194 print()195 for i,(aElt,bElt) in enumerate(diffs):196 if aElt is None:197 print('A missing: %s' % bElt)198 elif bElt is None:199 print('B missing: %s' % aElt)200 else:201 print('mismatch: A: %s' % aElt)202 print(' B: %s' % bElt)203 differ = True204 205 # Compare result codes.206 if infoA.exitCode != infoB.exitCode:207 print('-- EXIT CODES DIFFER -')208 print('A: ',infoA.exitCode)209 print('B: ',infoB.exitCode)210 differ = True211 212 if differ:213 sys.exit(1)214 215if __name__ == '__main__':216 main()217