1#!/usr/bin/env python
2
3"""
4A simple utility that compares tool invocations and exit codes issued by
5compiler drivers that support -### (e.g. gcc and clang).
6"""
7
8import subprocess
9
10def 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
31def 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
62class 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
78class 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
90class 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
115def 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
124def 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
214if __name__ == '__main__':
215    main()
216