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