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