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() |
216 | |