1import os
2import lit.util
3import libcxx.util
4
5
6class CXXCompiler(object):
7    def __init__(self, path, flags=None, compile_flags=None, link_flags=None,
8                 use_ccache=False):
9        self.path = path
10        self.flags = list(flags or [])
11        self.compile_flags = list(compile_flags or [])
12        self.link_flags = list(link_flags or [])
13        self.use_ccache = use_ccache
14        self.type = None
15        self.version = None
16        self._initTypeAndVersion()
17
18    def _initTypeAndVersion(self):
19        # Get compiler type and version
20        macros = self.dumpMacros()
21        if macros is None:
22            return
23        compiler_type = None
24        major_ver = minor_ver = patchlevel = None
25        if '__clang__' in macros.keys():
26            compiler_type = 'clang'
27            # Treat apple's llvm fork differently.
28            if '__apple_build_version__' in macros.keys():
29                compiler_type = 'apple-clang'
30            major_ver = macros['__clang_major__']
31            minor_ver = macros['__clang_minor__']
32            patchlevel = macros['__clang_patchlevel__']
33        elif '__GNUC__' in macros.keys():
34            compiler_type = 'gcc'
35            major_ver = macros['__GNUC__']
36            minor_ver = macros['__GNUC_MINOR__']
37            patchlevel = macros['__GNUC_PATCHLEVEL__']
38        self.type = compiler_type
39        self.version = (major_ver, minor_ver, patchlevel)
40
41    def _basicCmd(self, source_files, out, is_link=False, input_is_cxx=False):
42        cmd = []
43        if self.use_ccache and not is_link:
44            cmd += ['ccache']
45        cmd += [self.path]
46        if out is not None:
47            cmd += ['-o', out]
48        if input_is_cxx:
49            cmd += ['-x', 'c++']
50        if isinstance(source_files, list):
51            cmd += source_files
52        elif isinstance(source_files, str):
53            cmd += [source_files]
54        else:
55            raise TypeError('source_files must be a string or list')
56        return cmd
57
58    def preprocessCmd(self, source_files, out=None, flags=[]):
59        cmd = self._basicCmd(source_files, out, input_is_cxx=True) + ['-E']
60        cmd += self.flags + self.compile_flags + flags
61        return cmd
62
63    def compileCmd(self, source_files, out=None, flags=[]):
64        cmd = self._basicCmd(source_files, out, input_is_cxx=True) + ['-c']
65        cmd += self.flags + self.compile_flags + flags
66        return cmd
67
68    def linkCmd(self, source_files, out=None, flags=[]):
69        cmd = self._basicCmd(source_files, out, is_link=True)
70        cmd += self.flags + self.link_flags + flags
71        return cmd
72
73    def compileLinkCmd(self, source_files, out=None, flags=[]):
74        cmd = self._basicCmd(source_files, out, is_link=True)
75        cmd += self.flags + self.compile_flags + self.link_flags + flags
76        return cmd
77
78    def preprocess(self, source_files, out=None, flags=[], env=None, cwd=None):
79        cmd = self.preprocessCmd(source_files, out, flags)
80        out, err, rc = lit.util.executeCommand(cmd, env=env, cwd=cwd)
81        return cmd, out, err, rc
82
83    def compile(self, source_files, out=None, flags=[], env=None, cwd=None):
84        cmd = self.compileCmd(source_files, out, flags)
85        out, err, rc = lit.util.executeCommand(cmd, env=env, cwd=cwd)
86        return cmd, out, err, rc
87
88    def link(self, source_files, out=None, flags=[], env=None, cwd=None):
89        cmd = self.linkCmd(source_files, out, flags)
90        out, err, rc = lit.util.executeCommand(cmd, env=env, cwd=cwd)
91        return cmd, out, err, rc
92
93    def compileLink(self, source_files, out=None, flags=[], env=None,
94                    cwd=None):
95        cmd = self.compileLinkCmd(source_files, out, flags)
96        out, err, rc = lit.util.executeCommand(cmd, env=env, cwd=cwd)
97        return cmd, out, err, rc
98
99    def compileLinkTwoSteps(self, source_file, out=None, object_file=None,
100                            flags=[], env=None, cwd=None):
101        if not isinstance(source_file, str):
102            raise TypeError('This function only accepts a single input file')
103        if object_file is None:
104            # Create, use and delete a temporary object file if none is given.
105            with_fn = lambda: libcxx.util.guardedTempFilename(suffix='.o')
106        else:
107            # Otherwise wrap the filename in a context manager function.
108            with_fn = lambda: libcxx.util.nullContext(object_file)
109        with with_fn() as object_file:
110            cc_cmd, cc_stdout, cc_stderr, rc = self.compile(
111                    source_file, object_file, flags=flags, env=env, cwd=cwd)
112            if rc != 0:
113                return cc_cmd, cc_stdout, cc_stderr, rc
114
115            link_cmd, link_stdout, link_stderr, rc = self.link(
116                    object_file, out=out, flags=flags, env=env, cwd=cwd)
117            return (cc_cmd + ['&&'] + link_cmd, cc_stdout + link_stdout,
118                    cc_stderr + link_stderr, rc)
119
120    def dumpMacros(self, source_files=None, flags=[], env=None, cwd=None):
121        if source_files is None:
122            source_files = os.devnull
123        flags = ['-dM'] + flags
124        cmd, out, err, rc = self.preprocess(source_files, flags=flags, env=env,
125                                            cwd=cwd)
126        if rc != 0:
127            return None
128        parsed_macros = {}
129        lines = [l.strip() for l in out.split('\n') if l.strip()]
130        for l in lines:
131            assert l.startswith('#define ')
132            l = l[len('#define '):]
133            macro, _, value = l.partition(' ')
134            parsed_macros[macro] = value
135        return parsed_macros
136
137    def getTriple(self):
138        cmd = [self.path] + self.flags + ['-dumpmachine']
139        return lit.util.capture(cmd).strip()
140
141    def hasCompileFlag(self, flag):
142        if isinstance(flag, list):
143            flags = list(flag)
144        else:
145            flags = [flag]
146        # Add -Werror to ensure that an unrecognized flag causes a non-zero
147        # exit code. -Werror is supported on all known compiler types.
148        if self.type is not None:
149            flags += ['-Werror']
150        cmd, out, err, rc = self.compile(os.devnull, out=os.devnull,
151                                         flags=flags)
152        return rc == 0
153
154    def addCompileFlagIfSupported(self, flag):
155        if isinstance(flag, list):
156            flags = list(flag)
157        else:
158            flags = [flag]
159        if self.hasCompileFlag(flags):
160            self.compile_flags += flags
161            return True
162        else:
163            return False
164