1# Copyright 2012-2017 The Meson development team
2
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6
7#     http://www.apache.org/licenses/LICENSE-2.0
8
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15import copy
16import functools
17import os.path
18import typing as T
19
20from .. import coredata
21from .. import mlog
22from ..mesonlib import MesonException, MachineChoice, version_compare, OptionKey
23
24from .compilers import (
25    gnu_winlibs,
26    msvc_winlibs,
27    Compiler,
28    CompileCheckMode,
29)
30from .c_function_attributes import CXX_FUNC_ATTRIBUTES, C_FUNC_ATTRIBUTES
31from .mixins.clike import CLikeCompiler
32from .mixins.ccrx import CcrxCompiler
33from .mixins.c2000 import C2000Compiler
34from .mixins.arm import ArmCompiler, ArmclangCompiler
35from .mixins.visualstudio import MSVCCompiler, ClangClCompiler
36from .mixins.gnu import GnuCompiler
37from .mixins.intel import IntelGnuLikeCompiler, IntelVisualStudioLikeCompiler
38from .mixins.clang import ClangCompiler
39from .mixins.elbrus import ElbrusCompiler
40from .mixins.pgi import PGICompiler
41from .mixins.emscripten import EmscriptenMixin
42
43if T.TYPE_CHECKING:
44    from ..coredata import KeyedOptionDictType
45    from ..dependencies import Dependency
46    from ..envconfig import MachineInfo
47    from ..environment import Environment
48    from ..linkers import DynamicLinker
49    from ..programs import ExternalProgram
50    CompilerMixinBase = CLikeCompiler
51else:
52    CompilerMixinBase = object
53
54
55def non_msvc_eh_options(eh: str, args: T.List[str]) -> None:
56    if eh == 'none':
57        args.append('-fno-exceptions')
58    elif eh == 's' or eh == 'c':
59        mlog.warning('non-MSVC compilers do not support ' + eh + ' exception handling.' +
60                     'You may want to set eh to \'default\'.')
61
62class CPPCompiler(CLikeCompiler, Compiler):
63
64    @classmethod
65    def attribute_check_func(cls, name: str) -> str:
66        try:
67            return CXX_FUNC_ATTRIBUTES.get(name, C_FUNC_ATTRIBUTES[name])
68        except KeyError:
69            raise MesonException(f'Unknown function attribute "{name}"')
70
71    language = 'cpp'
72
73    def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool,
74                 info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None,
75                 linker: T.Optional['DynamicLinker'] = None,
76                 full_version: T.Optional[str] = None):
77        # If a child ObjCPP class has already set it, don't set it ourselves
78        Compiler.__init__(self, exelist, version, for_machine, info,
79                          is_cross=is_cross, linker=linker,
80                          full_version=full_version)
81        CLikeCompiler.__init__(self, exe_wrapper)
82
83    @staticmethod
84    def get_display_language() -> str:
85        return 'C++'
86
87    def get_no_stdinc_args(self) -> T.List[str]:
88        return ['-nostdinc++']
89
90    def sanity_check(self, work_dir: str, environment: 'Environment') -> None:
91        code = 'class breakCCompiler;int main(void) { return 0; }\n'
92        return self._sanity_check_impl(work_dir, environment, 'sanitycheckcpp.cc', code)
93
94    def get_compiler_check_args(self, mode: CompileCheckMode) -> T.List[str]:
95        # -fpermissive allows non-conforming code to compile which is necessary
96        # for many C++ checks. Particularly, the has_header_symbol check is
97        # too strict without this and always fails.
98        return super().get_compiler_check_args(mode) + ['-fpermissive']
99
100    def has_header_symbol(self, hname: str, symbol: str, prefix: str,
101                          env: 'Environment', *,
102                          extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]] = None,
103                          dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]:
104        # Check if it's a C-like symbol
105        found, cached = super().has_header_symbol(hname, symbol, prefix, env,
106                                                  extra_args=extra_args,
107                                                  dependencies=dependencies)
108        if found:
109            return True, cached
110        # Check if it's a class or a template
111        if extra_args is None:
112            extra_args = []
113        t = f'''{prefix}
114        #include <{hname}>
115        using {symbol};
116        int main(void) {{ return 0; }}'''
117        return self.compiles(t, env, extra_args=extra_args,
118                             dependencies=dependencies)
119
120    def _test_cpp_std_arg(self, cpp_std_value: str) -> bool:
121        # Test whether the compiler understands a -std=XY argument
122        assert cpp_std_value.startswith('-std=')
123
124        # This test does not use has_multi_arguments() for two reasons:
125        # 1. has_multi_arguments() requires an env argument, which the compiler
126        #    object does not have at this point.
127        # 2. even if it did have an env object, that might contain another more
128        #    recent -std= argument, which might lead to a cascaded failure.
129        CPP_TEST = 'int i = static_cast<int>(0);'
130        with self.compile(CPP_TEST, extra_args=[cpp_std_value], mode='compile') as p:
131            if p.returncode == 0:
132                mlog.debug(f'Compiler accepts {cpp_std_value}:', 'YES')
133                return True
134            else:
135                mlog.debug(f'Compiler accepts {cpp_std_value}:', 'NO')
136                return False
137
138    @functools.lru_cache()
139    def _find_best_cpp_std(self, cpp_std: str) -> str:
140        # The initial version mapping approach to make falling back
141        # from '-std=c++14' to '-std=c++1y' was too brittle. For instance,
142        # Apple's Clang uses a different versioning scheme to upstream LLVM,
143        # making the whole detection logic awfully brittle. Instead, let's
144        # just see if feeding GCC or Clang our '-std=' setting works, and
145        # if not, try the fallback argument.
146        CPP_FALLBACKS = {
147            'c++11': 'c++0x',
148            'gnu++11': 'gnu++0x',
149            'c++14': 'c++1y',
150            'gnu++14': 'gnu++1y',
151            'c++17': 'c++1z',
152            'gnu++17': 'gnu++1z',
153            'c++20': 'c++2a',
154            'gnu++20': 'gnu++2a',
155        }
156
157        # Currently, remapping is only supported for Clang, Elbrus and GCC
158        assert self.id in frozenset(['clang', 'lcc', 'gcc', 'emscripten'])
159
160        if cpp_std not in CPP_FALLBACKS:
161            # 'c++03' and 'c++98' don't have fallback types
162            return '-std=' + cpp_std
163
164        for i in (cpp_std, CPP_FALLBACKS[cpp_std]):
165            cpp_std_value = '-std=' + i
166            if self._test_cpp_std_arg(cpp_std_value):
167                return cpp_std_value
168
169        raise MesonException(f'C++ Compiler does not support -std={cpp_std}')
170
171    def get_options(self) -> 'KeyedOptionDictType':
172        opts = super().get_options()
173        key = OptionKey('std', machine=self.for_machine, lang=self.language)
174        opts.update({
175            key: coredata.UserComboOption(
176                'C++ language standard to use',
177                ['none'],
178                'none',
179            ),
180        })
181        return opts
182
183
184class ClangCPPCompiler(ClangCompiler, CPPCompiler):
185    def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool,
186                 info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None,
187                 linker: T.Optional['DynamicLinker'] = None,
188                 defines: T.Optional[T.Dict[str, str]] = None,
189                 full_version: T.Optional[str] = None):
190        CPPCompiler.__init__(self, exelist, version, for_machine, is_cross,
191                             info, exe_wrapper, linker=linker, full_version=full_version)
192        ClangCompiler.__init__(self, defines)
193        default_warn_args = ['-Wall', '-Winvalid-pch', '-Wnon-virtual-dtor']
194        self.warn_args = {'0': [],
195                          '1': default_warn_args,
196                          '2': default_warn_args + ['-Wextra'],
197                          '3': default_warn_args + ['-Wextra', '-Wpedantic']}
198
199    def get_options(self) -> 'KeyedOptionDictType':
200        opts = CPPCompiler.get_options(self)
201        key = OptionKey('key', machine=self.for_machine, lang=self.language)
202        opts.update({
203            key.evolve('eh'): coredata.UserComboOption(
204                'C++ exception handling type.',
205                ['none', 'default', 'a', 's', 'sc'],
206                'default',
207            ),
208            key.evolve('rtti'): coredata.UserBooleanOption('Enable RTTI', True),
209        })
210        opts[key.evolve('std')].choices = [
211            'none', 'c++98', 'c++03', 'c++11', 'c++14', 'c++17', 'c++1z',
212            'c++2a', 'c++20', 'gnu++11', 'gnu++14', 'gnu++17', 'gnu++1z',
213            'gnu++2a', 'gnu++20',
214        ]
215        if self.info.is_windows() or self.info.is_cygwin():
216            opts.update({
217                key.evolve('winlibs'): coredata.UserArrayOption(
218                    'Standard Win libraries to link against',
219                    gnu_winlibs,
220                ),
221            })
222        return opts
223
224    def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
225        args = []
226        key = OptionKey('std', machine=self.for_machine, lang=self.language)
227        std = options[key]
228        if std.value != 'none':
229            args.append(self._find_best_cpp_std(std.value))
230
231        non_msvc_eh_options(options[key.evolve('eh')].value, args)
232
233        if not options[key.evolve('rtti')].value:
234            args.append('-fno-rtti')
235
236        return args
237
238    def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
239        if self.info.is_windows() or self.info.is_cygwin():
240            # without a typedict mypy can't understand this.
241            key = OptionKey('winlibs', machine=self.for_machine, lang=self.language)
242            libs = options[key].value.copy()
243            assert isinstance(libs, list)
244            for l in libs:
245                assert isinstance(l, str)
246            return libs
247        return []
248
249    def language_stdlib_only_link_flags(self, env: 'Environment') -> T.List[str]:
250        # We need to apply the search prefix here, as these link arguments may
251        # be passed to a different compiler with a different set of default
252        # search paths, such as when using Clang for C/C++ and gfortran for
253        # fortran,
254        search_dir = self._get_search_dirs(env)
255        search_dirs: T.List[str] = []
256        if search_dir is not None:
257            for d in search_dir.split()[-1][len('libraries: ='):].split(':'):
258                search_dirs.append(f'-L{d}')
259        return search_dirs + ['-lstdc++']
260
261
262class AppleClangCPPCompiler(ClangCPPCompiler):
263    def language_stdlib_only_link_flags(self, env: 'Environment') -> T.List[str]:
264        # We need to apply the search prefix here, as these link arguments may
265        # be passed to a different compiler with a different set of default
266        # search paths, such as when using Clang for C/C++ and gfortran for
267        # fortran,
268        search_dir = self._get_search_dirs(env)
269        search_dirs: T.List[str] = []
270        if search_dir is not None:
271            for d in search_dir.split()[-1][len('libraries: ='):].split(':'):
272                search_dirs.append(f'-L{d}')
273        return search_dirs + ['-lc++']
274
275
276class EmscriptenCPPCompiler(EmscriptenMixin, ClangCPPCompiler):
277    def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool,
278                 info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None,
279                 linker: T.Optional['DynamicLinker'] = None,
280                 defines: T.Optional[T.Dict[str, str]] = None,
281                 full_version: T.Optional[str] = None):
282        if not is_cross:
283            raise MesonException('Emscripten compiler can only be used for cross compilation.')
284        ClangCPPCompiler.__init__(self, exelist, version, for_machine, is_cross,
285                                  info, exe_wrapper=exe_wrapper, linker=linker,
286                                  defines=defines, full_version=full_version)
287        self.id = 'emscripten'
288
289    def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
290        args = []
291        key = OptionKey('std', machine=self.for_machine, lang=self.language)
292        std = options[key]
293        if std.value != 'none':
294            args.append(self._find_best_cpp_std(std.value))
295        return args
296
297
298class ArmclangCPPCompiler(ArmclangCompiler, CPPCompiler):
299    def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool,
300                 info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None,
301                 linker: T.Optional['DynamicLinker'] = None,
302                 full_version: T.Optional[str] = None):
303        CPPCompiler.__init__(self, exelist, version, for_machine, is_cross,
304                             info, exe_wrapper, linker=linker, full_version=full_version)
305        ArmclangCompiler.__init__(self)
306        default_warn_args = ['-Wall', '-Winvalid-pch', '-Wnon-virtual-dtor']
307        self.warn_args = {'0': [],
308                          '1': default_warn_args,
309                          '2': default_warn_args + ['-Wextra'],
310                          '3': default_warn_args + ['-Wextra', '-Wpedantic']}
311
312    def get_options(self) -> 'KeyedOptionDictType':
313        opts = CPPCompiler.get_options(self)
314        key = OptionKey('std', machine=self.for_machine, lang=self.language)
315        opts.update({
316            key.evolve('eh'): coredata.UserComboOption(
317                'C++ exception handling type.',
318                ['none', 'default', 'a', 's', 'sc'],
319                'default',
320            ),
321        })
322        opts[key].choices = [
323            'none', 'c++98', 'c++03', 'c++11', 'c++14', 'c++17', 'gnu++98',
324            'gnu++03', 'gnu++11', 'gnu++14', 'gnu++17',
325        ]
326        return opts
327
328    def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
329        args = []
330        key = OptionKey('std', machine=self.for_machine, lang=self.language)
331        std = options[key]
332        if std.value != 'none':
333            args.append('-std=' + std.value)
334
335        non_msvc_eh_options(options[key.evolve('eh')].value, args)
336
337        return args
338
339    def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
340        return []
341
342
343class GnuCPPCompiler(GnuCompiler, CPPCompiler):
344    def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool,
345                 info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None,
346                 linker: T.Optional['DynamicLinker'] = None,
347                 defines: T.Optional[T.Dict[str, str]] = None,
348                 full_version: T.Optional[str] = None):
349        CPPCompiler.__init__(self, exelist, version, for_machine, is_cross,
350                             info, exe_wrapper, linker=linker, full_version=full_version)
351        GnuCompiler.__init__(self, defines)
352        default_warn_args = ['-Wall', '-Winvalid-pch', '-Wnon-virtual-dtor']
353        self.warn_args = {'0': [],
354                          '1': default_warn_args,
355                          '2': default_warn_args + ['-Wextra'],
356                          '3': default_warn_args + ['-Wextra', '-Wpedantic']}
357
358    def get_options(self) -> 'KeyedOptionDictType':
359        key = OptionKey('std', machine=self.for_machine, lang=self.language)
360        opts = CPPCompiler.get_options(self)
361        opts.update({
362            key.evolve('eh'): coredata.UserComboOption(
363                'C++ exception handling type.',
364                ['none', 'default', 'a', 's', 'sc'],
365                'default',
366            ),
367            key.evolve('rtti'): coredata.UserBooleanOption('Enable RTTI', True),
368            key.evolve('debugstl'): coredata.UserBooleanOption(
369                'STL debug mode',
370                False,
371            )
372        })
373        opts[key].choices = [
374            'none', 'c++98', 'c++03', 'c++11', 'c++14', 'c++17', 'c++1z',
375            'c++2a', 'c++20', 'gnu++03', 'gnu++11', 'gnu++14', 'gnu++17',
376            'gnu++1z', 'gnu++2a', 'gnu++20',
377        ]
378        if self.info.is_windows() or self.info.is_cygwin():
379            opts.update({
380                key.evolve('winlibs'): coredata.UserArrayOption(
381                    'Standard Win libraries to link against',
382                    gnu_winlibs,
383                ),
384            })
385        return opts
386
387    def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
388        args = []
389        key = OptionKey('std', machine=self.for_machine, lang=self.language)
390        std = options[key]
391        if std.value != 'none':
392            args.append(self._find_best_cpp_std(std.value))
393
394        non_msvc_eh_options(options[key.evolve('eh')].value, args)
395
396        if not options[key.evolve('rtti')].value:
397            args.append('-fno-rtti')
398
399        if options[key.evolve('debugstl')].value:
400            args.append('-D_GLIBCXX_DEBUG=1')
401        return args
402
403    def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
404        if self.info.is_windows() or self.info.is_cygwin():
405            # without a typedict mypy can't understand this.
406            key = OptionKey('winlibs', machine=self.for_machine, lang=self.language)
407            libs = options[key].value.copy()
408            assert isinstance(libs, list)
409            for l in libs:
410                assert isinstance(l, str)
411            return libs
412        return []
413
414    def get_pch_use_args(self, pch_dir: str, header: str) -> T.List[str]:
415        return ['-fpch-preprocess', '-include', os.path.basename(header)]
416
417    def language_stdlib_only_link_flags(self, env: 'Environment') -> T.List[str]:
418        # We need to apply the search prefix here, as these link arguments may
419        # be passed to a different compiler with a different set of default
420        # search paths, such as when using Clang for C/C++ and gfortran for
421        # fortran,
422        search_dir = self._get_search_dirs(env)
423        search_dirs: T.List[str] = []
424        if search_dir is not None:
425            for d in search_dir.split()[-1][len('libraries: ='):].split(':'):
426                search_dirs.append(f'-L{d}')
427        return ['-lstdc++']
428
429
430class PGICPPCompiler(PGICompiler, CPPCompiler):
431    def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool,
432                 info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None,
433                 linker: T.Optional['DynamicLinker'] = None,
434                 full_version: T.Optional[str] = None):
435        CPPCompiler.__init__(self, exelist, version, for_machine, is_cross,
436                             info, exe_wrapper, linker=linker, full_version=full_version)
437        PGICompiler.__init__(self)
438
439
440class NvidiaHPC_CPPCompiler(PGICompiler, CPPCompiler):
441    def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool,
442                 info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None,
443                 linker: T.Optional['DynamicLinker'] = None,
444                 full_version: T.Optional[str] = None):
445        CPPCompiler.__init__(self, exelist, version, for_machine, is_cross,
446                             info, exe_wrapper, linker=linker, full_version=full_version)
447        PGICompiler.__init__(self)
448
449        self.id = 'nvidia_hpc'
450
451
452class ElbrusCPPCompiler(ElbrusCompiler, CPPCompiler):
453    def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool,
454                 info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None,
455                 linker: T.Optional['DynamicLinker'] = None,
456                 defines: T.Optional[T.Dict[str, str]] = None,
457                 full_version: T.Optional[str] = None):
458        CPPCompiler.__init__(self, exelist, version, for_machine, is_cross,
459                             info, exe_wrapper, linker=linker, full_version=full_version)
460        ElbrusCompiler.__init__(self)
461
462    def get_options(self) -> 'KeyedOptionDictType':
463        opts = CPPCompiler.get_options(self)
464
465        cpp_stds = ['none', 'c++98', 'gnu++98']
466        if version_compare(self.version, '>=1.20.00'):
467            cpp_stds += ['c++03', 'c++0x', 'c++11', 'gnu++03', 'gnu++0x', 'gnu++11']
468        if version_compare(self.version, '>=1.21.00') and version_compare(self.version, '<1.22.00'):
469            cpp_stds += ['c++14', 'gnu++14', 'c++1y', 'gnu++1y']
470        if version_compare(self.version, '>=1.22.00'):
471            cpp_stds += ['c++14', 'gnu++14']
472        if version_compare(self.version, '>=1.23.00'):
473            cpp_stds += ['c++1y', 'gnu++1y']
474        if version_compare(self.version, '>=1.24.00'):
475            cpp_stds += ['c++1z', 'c++17', 'gnu++1z', 'gnu++17']
476        if version_compare(self.version, '>=1.25.00'):
477            cpp_stds += ['c++2a', 'gnu++2a']
478        if version_compare(self.version, '>=1.26.00'):
479            cpp_stds += ['c++20', 'gnu++20']
480
481        key = OptionKey('std', machine=self.for_machine, lang=self.language)
482        opts.update({
483            key.evolve('eh'): coredata.UserComboOption(
484                'C++ exception handling type.',
485                ['none', 'default', 'a', 's', 'sc'],
486                'default',
487            ),
488            key.evolve('debugstl'): coredata.UserBooleanOption(
489                'STL debug mode',
490                False,
491            ),
492        })
493        opts[key].choices = cpp_stds
494        return opts
495
496    # Elbrus C++ compiler does not have lchmod, but there is only linker warning, not compiler error.
497    # So we should explicitly fail at this case.
498    def has_function(self, funcname: str, prefix: str, env: 'Environment', *,
499                     extra_args: T.Optional[T.List[str]] = None,
500                     dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]:
501        if funcname == 'lchmod':
502            return False, False
503        else:
504            return super().has_function(funcname, prefix, env,
505                                        extra_args=extra_args,
506                                        dependencies=dependencies)
507
508    # Elbrus C++ compiler does not support RTTI, so don't check for it.
509    def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
510        args = []
511        key = OptionKey('std', machine=self.for_machine, lang=self.language)
512        std = options[key]
513        if std.value != 'none':
514            args.append(self._find_best_cpp_std(std.value))
515
516        non_msvc_eh_options(options[key.evolve('eh')].value, args)
517
518        if options[key.evolve('debugstl')].value:
519            args.append('-D_GLIBCXX_DEBUG=1')
520        return args
521
522
523class IntelCPPCompiler(IntelGnuLikeCompiler, CPPCompiler):
524    def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool,
525                 info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None,
526                 linker: T.Optional['DynamicLinker'] = None,
527                 full_version: T.Optional[str] = None):
528        CPPCompiler.__init__(self, exelist, version, for_machine, is_cross,
529                             info, exe_wrapper, linker=linker, full_version=full_version)
530        IntelGnuLikeCompiler.__init__(self)
531        self.lang_header = 'c++-header'
532        default_warn_args = ['-Wall', '-w3', '-diag-disable:remark',
533                             '-Wpch-messages', '-Wnon-virtual-dtor']
534        self.warn_args = {'0': [],
535                          '1': default_warn_args,
536                          '2': default_warn_args + ['-Wextra'],
537                          '3': default_warn_args + ['-Wextra']}
538
539    def get_options(self) -> 'KeyedOptionDictType':
540        opts = CPPCompiler.get_options(self)
541        # Every Unix compiler under the sun seems to accept -std=c++03,
542        # with the exception of ICC. Instead of preventing the user from
543        # globally requesting C++03, we transparently remap it to C++98
544        c_stds = ['c++98', 'c++03']
545        g_stds = ['gnu++98', 'gnu++03']
546        if version_compare(self.version, '>=15.0.0'):
547            c_stds += ['c++11', 'c++14']
548            g_stds += ['gnu++11']
549        if version_compare(self.version, '>=16.0.0'):
550            c_stds += ['c++17']
551        if version_compare(self.version, '>=17.0.0'):
552            g_stds += ['gnu++14']
553        if version_compare(self.version, '>=19.1.0'):
554            c_stds += ['c++2a']
555            g_stds += ['gnu++2a']
556
557        key = OptionKey('std', machine=self.for_machine, lang=self.language)
558        opts.update({
559            key.evolve('eh'): coredata.UserComboOption(
560                'C++ exception handling type.',
561                ['none', 'default', 'a', 's', 'sc'],
562                'default',
563            ),
564            key.evolve('rtti'): coredata.UserBooleanOption('Enable RTTI', True),
565            key.evolve('debugstl'): coredata.UserBooleanOption('STL debug mode', False),
566        })
567        opts[key].choices = ['none'] + c_stds + g_stds
568        return opts
569
570    def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
571        args = []
572        key = OptionKey('std', machine=self.for_machine, lang=self.language)
573        std = options[key]
574        if std.value != 'none':
575            remap_cpp03 = {
576                'c++03': 'c++98',
577                'gnu++03': 'gnu++98'
578            }
579            args.append('-std=' + remap_cpp03.get(std.value, std.value))
580        if options[key.evolve('eh')].value == 'none':
581            args.append('-fno-exceptions')
582        if not options[key.evolve('rtti')].value:
583            args.append('-fno-rtti')
584        if options[key.evolve('debugstl')].value:
585            args.append('-D_GLIBCXX_DEBUG=1')
586        return args
587
588    def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
589        return []
590
591
592class VisualStudioLikeCPPCompilerMixin(CompilerMixinBase):
593
594    """Mixin for C++ specific method overrides in MSVC-like compilers."""
595
596    VC_VERSION_MAP = {
597        'none': (True, None),
598        'vc++11': (True, 11),
599        'vc++14': (True, 14),
600        'vc++17': (True, 17),
601        'vc++20': (True, 20),
602        'vc++latest': (True, "latest"),
603        'c++11': (False, 11),
604        'c++14': (False, 14),
605        'c++17': (False, 17),
606        'c++20': (False, 20),
607        'c++latest': (False, "latest"),
608    }
609
610    def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
611        # need a typeddict for this
612        key = OptionKey('winlibs', machine=self.for_machine, lang=self.language)
613        return T.cast(T.List[str], options[key].value[:])
614
615    def _get_options_impl(self, opts: 'KeyedOptionDictType', cpp_stds: T.List[str]) -> 'KeyedOptionDictType':
616        key = OptionKey('std', machine=self.for_machine, lang=self.language)
617        opts.update({
618            key.evolve('eh'): coredata.UserComboOption(
619                'C++ exception handling type.',
620                ['none', 'default', 'a', 's', 'sc'],
621                'default',
622            ),
623            key.evolve('rtti'): coredata.UserBooleanOption('Enable RTTI', True),
624            key.evolve('winlibs'): coredata.UserArrayOption(
625                'Windows libs to link against.',
626                msvc_winlibs,
627            ),
628        })
629        opts[key.evolve('std')].choices = cpp_stds
630        return opts
631
632    def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
633        args = []
634        key = OptionKey('std', machine=self.for_machine, lang=self.language)
635
636        eh = options[key.evolve('eh')]
637        if eh.value == 'default':
638            args.append('/EHsc')
639        elif eh.value == 'none':
640            args.append('/EHs-c-')
641        else:
642            args.append('/EH' + eh.value)
643
644        if not options[key.evolve('rtti')].value:
645            args.append('/GR-')
646
647        permissive, ver = self.VC_VERSION_MAP[options[key].value]
648
649        if ver is not None:
650            args.append(f'/std:c++{ver}')
651
652        if not permissive:
653            args.append('/permissive-')
654
655        return args
656
657    def get_compiler_check_args(self, mode: CompileCheckMode) -> T.List[str]:
658        # XXX: this is a hack because so much GnuLike stuff is in the base CPPCompiler class.
659        return Compiler.get_compiler_check_args(self, mode)
660
661
662class CPP11AsCPP14Mixin(CompilerMixinBase):
663
664    """Mixin class for VisualStudio and ClangCl to replace C++11 std with C++14.
665
666    This is a limitation of Clang and MSVC that ICL doesn't share.
667    """
668
669    def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
670        # Note: there is no explicit flag for supporting C++11; we attempt to do the best we can
671        # which means setting the C++ standard version to C++14, in compilers that support it
672        # (i.e., after VS2015U3)
673        # if one is using anything before that point, one cannot set the standard.
674        key = OptionKey('std', machine=self.for_machine, lang=self.language)
675        if options[key].value in {'vc++11', 'c++11'}:
676            mlog.warning(self.id, 'does not support C++11;',
677                         'attempting best effort; setting the standard to C++14', once=True)
678            # Don't mutate anything we're going to change, we need to use
679            # deepcopy since we're messing with members, and we can't simply
680            # copy the members because the option proxy doesn't support it.
681            options = copy.deepcopy(options)
682            if options[key].value == 'vc++11':
683                options[key].value = 'vc++14'
684            else:
685                options[key].value = 'c++14'
686        return super().get_option_compile_args(options)
687
688
689class VisualStudioCPPCompiler(CPP11AsCPP14Mixin, VisualStudioLikeCPPCompilerMixin, MSVCCompiler, CPPCompiler):
690    def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice,
691                 is_cross: bool, info: 'MachineInfo', target: str,
692                 exe_wrapper: T.Optional['ExternalProgram'] = None,
693                 linker: T.Optional['DynamicLinker'] = None,
694                 full_version: T.Optional[str] = None):
695        CPPCompiler.__init__(self, exelist, version, for_machine, is_cross,
696                             info, exe_wrapper, linker=linker, full_version=full_version)
697        MSVCCompiler.__init__(self, target)
698        self.id = 'msvc'
699
700    def get_options(self) -> 'KeyedOptionDictType':
701        cpp_stds = ['none', 'c++11', 'vc++11']
702        # Visual Studio 2015 and later
703        if version_compare(self.version, '>=19'):
704            cpp_stds.extend(['c++14', 'c++latest', 'vc++latest'])
705        # Visual Studio 2017 and later
706        if version_compare(self.version, '>=19.11'):
707            cpp_stds.extend(['vc++14', 'c++17', 'vc++17'])
708        if version_compare(self.version, '>=19.29'):
709            cpp_stds.extend(['c++20', 'vc++20'])
710        return self._get_options_impl(super().get_options(), cpp_stds)
711
712    def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
713        key = OptionKey('std', machine=self.for_machine, lang=self.language)
714        if options[key].value != 'none' and version_compare(self.version, '<19.00.24210'):
715            mlog.warning('This version of MSVC does not support cpp_std arguments')
716            options = copy.copy(options)
717            options[key].value = 'none'
718
719        args = super().get_option_compile_args(options)
720
721        if version_compare(self.version, '<19.11'):
722            try:
723                i = args.index('/permissive-')
724            except ValueError:
725                return args
726            del args[i]
727        return args
728
729class ClangClCPPCompiler(CPP11AsCPP14Mixin, VisualStudioLikeCPPCompilerMixin, ClangClCompiler, CPPCompiler):
730    def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice,
731                 is_cross: bool, info: 'MachineInfo', target: str,
732                 exe_wrapper: T.Optional['ExternalProgram'] = None,
733                 linker: T.Optional['DynamicLinker'] = None,
734                 full_version: T.Optional[str] = None):
735        CPPCompiler.__init__(self, exelist, version, for_machine, is_cross,
736                             info, exe_wrapper, linker=linker, full_version=full_version)
737        ClangClCompiler.__init__(self, target)
738        self.id = 'clang-cl'
739
740    def get_options(self) -> 'KeyedOptionDictType':
741        cpp_stds = ['none', 'c++11', 'vc++11', 'c++14', 'vc++14', 'c++17', 'vc++17', 'c++latest']
742        return self._get_options_impl(super().get_options(), cpp_stds)
743
744
745class IntelClCPPCompiler(VisualStudioLikeCPPCompilerMixin, IntelVisualStudioLikeCompiler, CPPCompiler):
746
747    def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice,
748                 is_cross: bool, info: 'MachineInfo', target: str,
749                 exe_wrapper: T.Optional['ExternalProgram'] = None,
750                 linker: T.Optional['DynamicLinker'] = None,
751                 full_version: T.Optional[str] = None):
752        CPPCompiler.__init__(self, exelist, version, for_machine, is_cross,
753                             info, exe_wrapper, linker=linker, full_version=full_version)
754        IntelVisualStudioLikeCompiler.__init__(self, target)
755
756    def get_options(self) -> 'KeyedOptionDictType':
757        # This has only been tested with version 19.0,
758        cpp_stds = ['none', 'c++11', 'vc++11', 'c++14', 'vc++14', 'c++17', 'vc++17', 'c++latest']
759        return self._get_options_impl(super().get_options(), cpp_stds)
760
761    def get_compiler_check_args(self, mode: CompileCheckMode) -> T.List[str]:
762        # XXX: this is a hack because so much GnuLike stuff is in the base CPPCompiler class.
763        return IntelVisualStudioLikeCompiler.get_compiler_check_args(self, mode)
764
765
766class ArmCPPCompiler(ArmCompiler, CPPCompiler):
767    def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool,
768                 info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None,
769                 linker: T.Optional['DynamicLinker'] = None,
770                 full_version: T.Optional[str] = None):
771        CPPCompiler.__init__(self, exelist, version, for_machine, is_cross,
772                             info, exe_wrapper, linker=linker, full_version=full_version)
773        ArmCompiler.__init__(self)
774
775    def get_options(self) -> 'KeyedOptionDictType':
776        opts = CPPCompiler.get_options(self)
777        key = OptionKey('std', machine=self.for_machine, lang=self.language)
778        opts[key].choices = ['none', 'c++03', 'c++11']
779        return opts
780
781    def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
782        args = []
783        key = OptionKey('std', machine=self.for_machine, lang=self.language)
784        std = options[key]
785        if std.value == 'c++11':
786            args.append('--cpp11')
787        elif std.value == 'c++03':
788            args.append('--cpp')
789        return args
790
791    def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
792        return []
793
794    def get_compiler_check_args(self, mode: CompileCheckMode) -> T.List[str]:
795        return []
796
797
798class CcrxCPPCompiler(CcrxCompiler, CPPCompiler):
799    def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool,
800                 info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None,
801                 linker: T.Optional['DynamicLinker'] = None,
802                 full_version: T.Optional[str] = None):
803        CPPCompiler.__init__(self, exelist, version, for_machine, is_cross,
804                             info, exe_wrapper, linker=linker, full_version=full_version)
805        CcrxCompiler.__init__(self)
806
807    # Override CCompiler.get_always_args
808    def get_always_args(self) -> T.List[str]:
809        return ['-nologo', '-lang=cpp']
810
811    def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
812        return []
813
814    def get_compile_only_args(self) -> T.List[str]:
815        return []
816
817    def get_output_args(self, target: str) -> T.List[str]:
818        return ['-output=obj=%s' % target]
819
820    def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
821        return []
822
823    def get_compiler_check_args(self, mode: CompileCheckMode) -> T.List[str]:
824        return []
825
826class C2000CPPCompiler(C2000Compiler, CPPCompiler):
827    def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool,
828                 info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None,
829                 linker: T.Optional['DynamicLinker'] = None,
830                 full_version: T.Optional[str] = None):
831        CPPCompiler.__init__(self, exelist, version, for_machine, is_cross,
832                             info, exe_wrapper, linker=linker, full_version=full_version)
833        C2000Compiler.__init__(self)
834
835    def get_options(self) -> 'KeyedOptionDictType':
836        opts = CPPCompiler.get_options(self)
837        key = OptionKey('std', machine=self.for_machine, lang=self.language)
838        opts[key].choices = ['none', 'c++03']
839        return opts
840
841    def get_always_args(self) -> T.List[str]:
842        return ['-nologo', '-lang=cpp']
843
844    def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
845        return []
846
847    def get_compile_only_args(self) -> T.List[str]:
848        return []
849
850    def get_output_args(self, target: str) -> T.List[str]:
851        return ['-output=obj=%s' % target]
852
853    def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
854        return []
855
856    def get_compiler_check_args(self, mode: CompileCheckMode) -> T.List[str]:
857        return []
858