• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#===----------------------------------------------------------------------===##
2#
3# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4# See https://llvm.org/LICENSE.txt for license information.
5# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6#
7#===----------------------------------------------------------------------===##
8
9import platform
10import os
11import libcxx.util
12
13
14class CXXCompiler(object):
15    CM_Default = 0
16    CM_PreProcess = 1
17    CM_Compile = 2
18    CM_Link = 3
19
20    def __init__(self, config, path, flags=None, compile_flags=None, link_flags=None,
21                 warning_flags=None, verify_supported=None,
22                 verify_flags=None, use_verify=False,
23                 modules_flags=None, use_modules=False,
24                 use_ccache=False, use_warnings=False, compile_env=None,
25                 cxx_type=None, cxx_version=None):
26        self.libcxx_config = config
27        self.source_lang = 'c++'
28        self.path = path
29        self.flags = list(flags or [])
30        self.compile_flags = list(compile_flags or [])
31        self.link_flags = list(link_flags or [])
32        self.link_libcxxabi_flag = '-lc++abi'
33        self.warning_flags = list(warning_flags or [])
34        self.verify_supported = verify_supported
35        self.use_verify = use_verify
36        self.verify_flags = list(verify_flags or [])
37        assert not use_verify or verify_supported
38        assert not use_verify or verify_flags is not None
39        self.modules_flags = list(modules_flags or [])
40        self.use_modules = use_modules
41        assert not use_modules or modules_flags is not None
42        self.use_ccache = use_ccache
43        self.use_warnings = use_warnings
44        if compile_env is not None:
45            self.compile_env = dict(compile_env)
46        else:
47            self.compile_env = None
48        self.type = cxx_type
49        self.version = cxx_version
50        if self.type is None or self.version is None:
51            self._initTypeAndVersion()
52
53    def isVerifySupported(self):
54        if self.verify_supported is None:
55            self.verify_supported = self.hasCompileFlag(['-Xclang',
56                                        '-verify-ignore-unexpected'])
57            if self.verify_supported:
58                self.verify_flags = [
59                    '-Xclang', '-verify',
60                    '-Xclang', '-verify-ignore-unexpected=note',
61                    '-ferror-limit=1024'
62                ]
63        return self.verify_supported
64
65    def useVerify(self, value=True):
66        self.use_verify = value
67        assert not self.use_verify or self.verify_flags is not None
68
69    def useModules(self, value=True):
70        self.use_modules = value
71        assert not self.use_modules or self.modules_flags is not None
72
73    def useCCache(self, value=True):
74        self.use_ccache = value
75
76    def useWarnings(self, value=True):
77        self.use_warnings = value
78
79    def _initTypeAndVersion(self):
80        # Get compiler type and version
81        macros = self.dumpMacros()
82        if macros is None:
83            return
84        compiler_type = None
85        major_ver = minor_ver = patchlevel = None
86        if '__clang__' in macros.keys():
87            compiler_type = 'clang'
88            # Treat apple's llvm fork differently.
89            if '__apple_build_version__' in macros.keys():
90                compiler_type = 'apple-clang'
91            major_ver = macros['__clang_major__']
92            minor_ver = macros['__clang_minor__']
93            patchlevel = macros['__clang_patchlevel__']
94        elif '__GNUC__' in macros.keys():
95            compiler_type = 'gcc'
96            major_ver = macros['__GNUC__']
97            minor_ver = macros['__GNUC_MINOR__']
98            patchlevel = macros['__GNUC_PATCHLEVEL__']
99        self.type = compiler_type
100        self.version = (major_ver, minor_ver, patchlevel)
101
102    def _basicCmd(self, source_files, out, mode=CM_Default, flags=[],
103                  input_is_cxx=False):
104        cmd = []
105        if self.use_ccache \
106                and not mode == self.CM_Link \
107                and not mode == self.CM_PreProcess:
108            cmd += ['ccache']
109        cmd += [self.path]
110        if out is not None:
111            cmd += ['-o', out]
112        if input_is_cxx:
113            cmd += ['-x', self.source_lang]
114        if isinstance(source_files, list):
115            cmd += source_files
116        elif isinstance(source_files, str):
117            cmd += [source_files]
118        else:
119            raise TypeError('source_files must be a string or list')
120        if mode == self.CM_PreProcess:
121            cmd += ['-E']
122        elif mode == self.CM_Compile:
123            cmd += ['-c']
124        cmd += self.flags
125        if self.use_verify:
126            cmd += self.verify_flags
127            assert mode in [self.CM_Default, self.CM_Compile]
128        if self.use_modules:
129            cmd += self.modules_flags
130        if mode != self.CM_Link:
131            cmd += self.compile_flags
132            if self.use_warnings:
133                cmd += self.warning_flags
134        if mode != self.CM_PreProcess and mode != self.CM_Compile:
135            cmd += self.link_flags
136        cmd += flags
137        return cmd
138
139    def preprocessCmd(self, source_files, out=None, flags=[]):
140        return self._basicCmd(source_files, out, flags=flags,
141                             mode=self.CM_PreProcess,
142                             input_is_cxx=True)
143
144    def compileCmd(self, source_files, out=None, flags=[]):
145        return self._basicCmd(source_files, out, flags=flags,
146                             mode=self.CM_Compile,
147                             input_is_cxx=True) + ['-c']
148
149    def linkCmd(self, source_files, out=None, flags=[]):
150        return self._basicCmd(source_files, out, flags=flags,
151                              mode=self.CM_Link)
152
153    def compileLinkCmd(self, source_files, out=None, flags=[]):
154        return self._basicCmd(source_files, out, flags=flags)
155
156    def preprocess(self, source_files, out=None, flags=[], cwd=None):
157        cmd = self.preprocessCmd(source_files, out, flags)
158        out, err, rc = libcxx.util.executeCommand(cmd, env=self.compile_env,
159                                                  cwd=cwd)
160        return cmd, out, err, rc
161
162    def compile(self, source_files, out=None, flags=[], cwd=None):
163        cmd = self.compileCmd(source_files, out, flags)
164        out, err, rc = libcxx.util.executeCommand(cmd, env=self.compile_env,
165                                                  cwd=cwd)
166        return cmd, out, err, rc
167
168    def link(self, source_files, exec_path=None, flags=[], cwd=None):
169        cmd = self.linkCmd(source_files, exec_path, flags)
170        out, err, rc = libcxx.util.executeCommand(cmd, env=self.compile_env,
171                                                  cwd=cwd)
172        cs_cmd, cs_out, cs_err, cs_rc = self.codesign(exec_path, cwd)
173        if cs_rc != 0:
174            return cs_cmd, cs_out, cs_err, cs_rc
175        return cmd, out, err, rc
176
177    def compileLink(self, source_files, exec_path=None, flags=[],
178                    cwd=None):
179        cmd = self.compileLinkCmd(source_files, exec_path, flags)
180        out, err, rc = libcxx.util.executeCommand(cmd, env=self.compile_env,
181                                                  cwd=cwd)
182        cs_cmd, cs_out, cs_err, cs_rc = self.codesign(exec_path, cwd)
183        if cs_rc != 0:
184            return cs_cmd, cs_out, cs_err, cs_rc
185        return cmd, out, err, rc
186
187    def codesign(self, exec_path, cwd=None):
188        null_op = [], '', '', 0
189        if not exec_path:
190            return null_op
191        codesign_ident = self.libcxx_config.get_lit_conf('llvm_codesign_identity', '')
192        if not codesign_ident:
193            return null_op
194        cmd = ['xcrun', 'codesign', '-s', codesign_ident, exec_path]
195        out, err, rc = libcxx.util.executeCommand(cmd, cwd=cwd)
196        return cmd, out, err, rc
197
198    def compileLinkTwoSteps(self, source_file, out=None, object_file=None,
199                            flags=[], cwd=None):
200        if not isinstance(source_file, str):
201            raise TypeError('This function only accepts a single input file')
202        if object_file is None:
203            # Create, use and delete a temporary object file if none is given.
204            with_fn = lambda: libcxx.util.guardedTempFilename(suffix='.o')
205        else:
206            # Otherwise wrap the filename in a context manager function.
207            with_fn = lambda: libcxx.util.nullContext(object_file)
208        with with_fn() as object_file:
209            cc_cmd, cc_stdout, cc_stderr, rc = self.compile(
210                source_file, object_file, flags=flags, cwd=cwd)
211            if rc != 0:
212                return cc_cmd, cc_stdout, cc_stderr, rc
213
214            link_cmd, link_stdout, link_stderr, rc = self.link(
215                object_file, exec_path=out, flags=flags, cwd=cwd)
216            return (cc_cmd + ['&&'] + link_cmd, cc_stdout + link_stdout,
217                    cc_stderr + link_stderr, rc)
218
219    def dumpMacros(self, source_files=None, flags=[], cwd=None):
220        if source_files is None:
221            source_files = os.devnull
222        flags = ['-dM'] + flags
223        cmd, out, err, rc = self.preprocess(source_files, flags=flags, cwd=cwd)
224        if rc != 0:
225            return cmd, out, err, rc
226        parsed_macros = {}
227        lines = [l.strip() for l in out.split('\n') if l.strip()]
228        for l in lines:
229            assert l.startswith('#define ')
230            l = l[len('#define '):]
231            macro, _, value = l.partition(' ')
232            parsed_macros[macro] = value
233        return parsed_macros
234
235    def getTriple(self):
236        cmd = [self.path] + self.flags + ['-dumpmachine']
237        return libcxx.util.capture(cmd).strip()
238
239    def hasCompileFlag(self, flag):
240        if isinstance(flag, list):
241            flags = list(flag)
242        else:
243            flags = [flag]
244        # Add -Werror to ensure that an unrecognized flag causes a non-zero
245        # exit code. -Werror is supported on all known compiler types.
246        if self.type is not None:
247            flags += ['-Werror', '-fsyntax-only']
248        cmd, out, err, rc = self.compile(os.devnull, out=os.devnull,
249                                         flags=flags)
250        return rc == 0
251
252    def addFlagIfSupported(self, flag):
253        if isinstance(flag, list):
254            flags = list(flag)
255        else:
256            flags = [flag]
257        if self.hasCompileFlag(flags):
258            self.flags += flags
259            return True
260        else:
261            return False
262
263    def addCompileFlagIfSupported(self, flag):
264        if isinstance(flag, list):
265            flags = list(flag)
266        else:
267            flags = [flag]
268        if self.hasCompileFlag(flags):
269            self.compile_flags += flags
270            return True
271        else:
272            return False
273
274    def hasWarningFlag(self, flag):
275        """
276        hasWarningFlag - Test if the compiler supports a given warning flag.
277        Unlike addCompileFlagIfSupported, this function detects when
278        "-Wno-<warning>" flags are unsupported. If flag is a
279        "-Wno-<warning>" GCC will not emit an unknown option diagnostic unless
280        another error is triggered during compilation.
281        """
282        assert isinstance(flag, str)
283        assert flag.startswith('-W')
284        if not flag.startswith('-Wno-'):
285            return self.hasCompileFlag(flag)
286        flags = ['-Werror', flag]
287        old_use_warnings = self.use_warnings
288        self.useWarnings(False)
289        cmd = self.compileCmd('-', os.devnull, flags)
290        self.useWarnings(old_use_warnings)
291        # Remove '-v' because it will cause the command line invocation
292        # to be printed as part of the error output.
293        # TODO(EricWF): Are there other flags we need to worry about?
294        if '-v' in cmd:
295            cmd.remove('-v')
296        out, err, rc = libcxx.util.executeCommand(
297            cmd, input=libcxx.util.to_bytes('#error\n'))
298
299        assert rc != 0
300        if flag in err:
301            return False
302        return True
303
304    def addWarningFlagIfSupported(self, flag):
305        if self.hasWarningFlag(flag):
306            if flag not in self.warning_flags:
307                self.warning_flags += [flag]
308            return True
309        return False
310