xref: /llvm-project/clang/utils/CmpDriver (revision 773649296da935b62f9c62e42b08f51f09c196c2)
1837f574eSEli Friedman#!/usr/bin/env python
2c8385688SDaniel Dunbar
3*77364929SAlp Toker"""
4*77364929SAlp TokerA simple utility that compares tool invocations and exit codes issued by
5*77364929SAlp Tokercompiler drivers that support -### (e.g. gcc and clang).
6*77364929SAlp Toker"""
7*77364929SAlp Toker
8c8385688SDaniel Dunbarimport subprocess
9c8385688SDaniel Dunbar
10c8385688SDaniel Dunbardef splitArgs(s):
11c8385688SDaniel Dunbar    it = iter(s)
12c8385688SDaniel Dunbar    current = ''
13c8385688SDaniel Dunbar    inQuote = False
14c8385688SDaniel Dunbar    for c in it:
15c8385688SDaniel Dunbar        if c == '"':
16c8385688SDaniel Dunbar            if inQuote:
17c8385688SDaniel Dunbar                inQuote = False
18c8385688SDaniel Dunbar                yield current + '"'
19c8385688SDaniel Dunbar            else:
20c8385688SDaniel Dunbar                inQuote = True
21c8385688SDaniel Dunbar                current = '"'
22c8385688SDaniel Dunbar        elif inQuote:
23c8385688SDaniel Dunbar            if c == '\\':
24c8385688SDaniel Dunbar                current += c
25c8385688SDaniel Dunbar                current += it.next()
26c8385688SDaniel Dunbar            else:
27c8385688SDaniel Dunbar                current += c
28c8385688SDaniel Dunbar        elif not c.isspace():
29c8385688SDaniel Dunbar            yield c
30c8385688SDaniel Dunbar
31c8385688SDaniel Dunbardef insertMinimumPadding(a, b, dist):
32c8385688SDaniel Dunbar    """insertMinimumPadding(a,b) -> (a',b')
33c8385688SDaniel Dunbar
34c8385688SDaniel Dunbar    Return two lists of equal length, where some number of Nones have
35c8385688SDaniel Dunbar    been inserted into the shorter list such that sum(map(dist, a',
36c8385688SDaniel Dunbar    b')) is minimized.
37c8385688SDaniel Dunbar
38c8385688SDaniel Dunbar    Assumes dist(X, Y) -> int and non-negative.
39c8385688SDaniel Dunbar    """
40c8385688SDaniel Dunbar
41156fa7ddSDaniel Dunbar    def cost(a, b):
42156fa7ddSDaniel Dunbar        return sum(map(dist, a + [None] * (len(b) - len(a)), b))
43c8385688SDaniel Dunbar
44156fa7ddSDaniel Dunbar    # Normalize so a is shortest.
45156fa7ddSDaniel Dunbar    if len(b) < len(a):
46156fa7ddSDaniel Dunbar        b, a = insertMinimumPadding(b, a, dist)
47156fa7ddSDaniel Dunbar        return a,b
48c8385688SDaniel Dunbar
49156fa7ddSDaniel Dunbar    # For each None we have to insert...
50156fa7ddSDaniel Dunbar    for i in range(len(b) - len(a)):
51156fa7ddSDaniel Dunbar        # For each position we could insert it...
52156fa7ddSDaniel Dunbar        current = cost(a, b)
53156fa7ddSDaniel Dunbar        best = None
54156fa7ddSDaniel Dunbar        for j in range(len(a) + 1):
55156fa7ddSDaniel Dunbar            a_0 = a[:j] + [None] + a[j:]
56156fa7ddSDaniel Dunbar            candidate = cost(a_0, b)
57156fa7ddSDaniel Dunbar            if best is None or candidate < best[0]:
58156fa7ddSDaniel Dunbar                best = (candidate, a_0, j)
59156fa7ddSDaniel Dunbar        a = best[1]
60156fa7ddSDaniel Dunbar    return a,b
61c8385688SDaniel Dunbar
62c8385688SDaniel Dunbarclass ZipperDiff(object):
6357540c5bSChris Lattner    """ZipperDiff - Simple (slow) diff only accommodating inserts."""
64c8385688SDaniel Dunbar
65c8385688SDaniel Dunbar    def __init__(self, a, b):
66c8385688SDaniel Dunbar        self.a = a
67c8385688SDaniel Dunbar        self.b = b
68c8385688SDaniel Dunbar
69c8385688SDaniel Dunbar    def dist(self, a, b):
70c8385688SDaniel Dunbar        return a != b
71c8385688SDaniel Dunbar
72c8385688SDaniel Dunbar    def getDiffs(self):
73c8385688SDaniel Dunbar        a,b =  insertMinimumPadding(self.a, self.b, self.dist)
74c8385688SDaniel Dunbar        for aElt,bElt in zip(a,b):
75c8385688SDaniel Dunbar            if self.dist(aElt, bElt):
76c8385688SDaniel Dunbar                yield aElt,bElt
77c8385688SDaniel Dunbar
78c8385688SDaniel Dunbarclass DriverZipperDiff(ZipperDiff):
79c8385688SDaniel Dunbar    def isTempFile(self, filename):
80c8385688SDaniel Dunbar        if filename[0] != '"' or filename[-1] != '"':
81c8385688SDaniel Dunbar            return False
82c8385688SDaniel Dunbar        return (filename.startswith('/tmp/', 1) or
83c8385688SDaniel Dunbar                filename.startswith('/var/', 1))
84c8385688SDaniel Dunbar
85c8385688SDaniel Dunbar    def dist(self, a, b):
86c8385688SDaniel Dunbar        if a and b and self.isTempFile(a) and self.isTempFile(b):
87c8385688SDaniel Dunbar            return 0
88c8385688SDaniel Dunbar        return super(DriverZipperDiff, self).dist(a,b)
89c8385688SDaniel Dunbar
90c8385688SDaniel Dunbarclass CompileInfo:
91c8385688SDaniel Dunbar    def __init__(self, out, err, res):
92c8385688SDaniel Dunbar        self.commands = []
93c8385688SDaniel Dunbar
94c8385688SDaniel Dunbar        # Standard out isn't used for much.
95c8385688SDaniel Dunbar        self.stdout = out
96c8385688SDaniel Dunbar        self.stderr = ''
97c8385688SDaniel Dunbar
98c8385688SDaniel Dunbar        # FIXME: Compare error messages as well.
99c8385688SDaniel Dunbar        for ln in err.split('\n'):
100c8385688SDaniel Dunbar            if (ln == 'Using built-in specs.' or
101c8385688SDaniel Dunbar                ln.startswith('Target: ') or
102c8385688SDaniel Dunbar                ln.startswith('Configured with: ') or
103c8385688SDaniel Dunbar                ln.startswith('Thread model: ') or
10436ccb30aSDaniel Dunbar                ln.startswith('gcc version') or
10556b534d2SDaniel Dunbar                ln.startswith('clang version')):
106c8385688SDaniel Dunbar                pass
107c8385688SDaniel Dunbar            elif ln.strip().startswith('"'):
108c8385688SDaniel Dunbar                self.commands.append(list(splitArgs(ln)))
109c8385688SDaniel Dunbar            else:
110c8385688SDaniel Dunbar                self.stderr += ln + '\n'
111c8385688SDaniel Dunbar
112c8385688SDaniel Dunbar        self.stderr = self.stderr.strip()
113c8385688SDaniel Dunbar        self.exitCode = res
114c8385688SDaniel Dunbar
115c8385688SDaniel Dunbardef captureDriverInfo(cmd, args):
116c8385688SDaniel Dunbar    p = subprocess.Popen([cmd,'-###'] + args,
117c8385688SDaniel Dunbar                         stdin=None,
118c8385688SDaniel Dunbar                         stdout=subprocess.PIPE,
119c8385688SDaniel Dunbar                         stderr=subprocess.PIPE)
120c8385688SDaniel Dunbar    out,err = p.communicate()
121c8385688SDaniel Dunbar    res = p.wait()
122c8385688SDaniel Dunbar    return CompileInfo(out,err,res)
123c8385688SDaniel Dunbar
124c8385688SDaniel Dunbardef main():
12535926e10SDaniel Dunbar    import os, sys
126c8385688SDaniel Dunbar
127c8385688SDaniel Dunbar    args = sys.argv[1:]
12835926e10SDaniel Dunbar    driverA = os.getenv('DRIVER_A') or 'gcc'
129156fa7ddSDaniel Dunbar    driverB = os.getenv('DRIVER_B') or 'clang'
130c8385688SDaniel Dunbar
131c8385688SDaniel Dunbar    infoA = captureDriverInfo(driverA, args)
132c8385688SDaniel Dunbar    infoB = captureDriverInfo(driverB, args)
133c8385688SDaniel Dunbar
134563dc04fSDaniel Dunbar    differ = False
135563dc04fSDaniel Dunbar
136c8385688SDaniel Dunbar    # Compare stdout.
137c8385688SDaniel Dunbar    if infoA.stdout != infoB.stdout:
138c8385688SDaniel Dunbar        print '-- STDOUT DIFFERS -'
13956b534d2SDaniel Dunbar        print 'A OUTPUT: ',infoA.stdout
14056b534d2SDaniel Dunbar        print 'B OUTPUT: ',infoB.stdout
14156b534d2SDaniel Dunbar        print
14256b534d2SDaniel Dunbar
14356b534d2SDaniel Dunbar        diff = ZipperDiff(infoA.stdout.split('\n'),
14456b534d2SDaniel Dunbar                          infoB.stdout.split('\n'))
14556b534d2SDaniel Dunbar        for i,(aElt,bElt) in enumerate(diff.getDiffs()):
14656b534d2SDaniel Dunbar            if aElt is None:
14756b534d2SDaniel Dunbar                print 'A missing: %s' % bElt
14856b534d2SDaniel Dunbar            elif bElt is None:
14956b534d2SDaniel Dunbar                print 'B missing: %s' % aElt
15056b534d2SDaniel Dunbar            else:
15156b534d2SDaniel Dunbar                print 'mismatch: A: %s' % aElt
15256b534d2SDaniel Dunbar                print '          B: %s' % bElt
15356b534d2SDaniel Dunbar
154563dc04fSDaniel Dunbar        differ = True
155c8385688SDaniel Dunbar
156c8385688SDaniel Dunbar    # Compare stderr.
157c8385688SDaniel Dunbar    if infoA.stderr != infoB.stderr:
158c8385688SDaniel Dunbar        print '-- STDERR DIFFERS -'
15956b534d2SDaniel Dunbar        print 'A STDERR: ',infoA.stderr
16056b534d2SDaniel Dunbar        print 'B STDERR: ',infoB.stderr
16156b534d2SDaniel Dunbar        print
16256b534d2SDaniel Dunbar
16356b534d2SDaniel Dunbar        diff = ZipperDiff(infoA.stderr.split('\n'),
16456b534d2SDaniel Dunbar                          infoB.stderr.split('\n'))
16556b534d2SDaniel Dunbar        for i,(aElt,bElt) in enumerate(diff.getDiffs()):
16656b534d2SDaniel Dunbar            if aElt is None:
16756b534d2SDaniel Dunbar                print 'A missing: %s' % bElt
16856b534d2SDaniel Dunbar            elif bElt is None:
16956b534d2SDaniel Dunbar                print 'B missing: %s' % aElt
17056b534d2SDaniel Dunbar            else:
17156b534d2SDaniel Dunbar                print 'mismatch: A: %s' % aElt
17256b534d2SDaniel Dunbar                print '          B: %s' % bElt
17356b534d2SDaniel Dunbar
174563dc04fSDaniel Dunbar        differ = True
175c8385688SDaniel Dunbar
176c8385688SDaniel Dunbar    # Compare commands.
17770e06bdbSDaniel Dunbar    for i,(a,b) in enumerate(map(None, infoA.commands, infoB.commands)):
17870e06bdbSDaniel Dunbar        if a is None:
17970e06bdbSDaniel Dunbar            print 'A MISSING:',' '.join(b)
18070e06bdbSDaniel Dunbar            differ = True
18170e06bdbSDaniel Dunbar            continue
18270e06bdbSDaniel Dunbar        elif b is None:
18370e06bdbSDaniel Dunbar            print 'B MISSING:',' '.join(a)
18470e06bdbSDaniel Dunbar            differ = True
18570e06bdbSDaniel Dunbar            continue
18670e06bdbSDaniel Dunbar
187c8385688SDaniel Dunbar        diff = DriverZipperDiff(a,b)
188c8385688SDaniel Dunbar        diffs = list(diff.getDiffs())
189c8385688SDaniel Dunbar        if diffs:
190c8385688SDaniel Dunbar            print '-- COMMAND %d DIFFERS -' % i
191c8385688SDaniel Dunbar            print 'A COMMAND:',' '.join(a)
192c8385688SDaniel Dunbar            print 'B COMMAND:',' '.join(b)
193c8385688SDaniel Dunbar            print
194c8385688SDaniel Dunbar            for i,(aElt,bElt) in enumerate(diffs):
195c8385688SDaniel Dunbar                if aElt is None:
196c8385688SDaniel Dunbar                    print 'A missing: %s' % bElt
197c8385688SDaniel Dunbar                elif bElt is None:
198c8385688SDaniel Dunbar                    print 'B missing: %s' % aElt
199c8385688SDaniel Dunbar                else:
200c8385688SDaniel Dunbar                    print 'mismatch: A: %s' % aElt
201c8385688SDaniel Dunbar                    print '          B: %s' % bElt
202563dc04fSDaniel Dunbar            differ = True
203c8385688SDaniel Dunbar
204c8385688SDaniel Dunbar    # Compare result codes.
205c8385688SDaniel Dunbar    if infoA.exitCode != infoB.exitCode:
206c8385688SDaniel Dunbar        print '-- EXIT CODES DIFFER -'
207c8385688SDaniel Dunbar        print 'A: ',infoA.exitCode
208c8385688SDaniel Dunbar        print 'B: ',infoB.exitCode
209563dc04fSDaniel Dunbar        differ = True
210563dc04fSDaniel Dunbar
211563dc04fSDaniel Dunbar    if differ:
212563dc04fSDaniel Dunbar        sys.exit(1)
213c8385688SDaniel Dunbar
214c8385688SDaniel Dunbarif __name__ == '__main__':
215c8385688SDaniel Dunbar    main()
216