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 differen 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 differen 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 differen 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(GnuCPPCompiler, ElbrusCompiler):
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        GnuCPPCompiler.__init__(self, exelist, version, for_machine, is_cross,
459                                info, exe_wrapper, linker=linker,
460                                full_version=full_version, defines=defines)
461        ElbrusCompiler.__init__(self)
462
463    def get_options(self) -> 'KeyedOptionDictType':
464        opts = CPPCompiler.get_options(self)
465
466        cpp_stds = [
467            'none', 'c++98', 'c++03', 'c++0x', 'c++11', 'c++14', 'c++1y',
468            'gnu++98', 'gnu++03', 'gnu++0x', 'gnu++11', 'gnu++14', 'gnu++1y',
469        ]
470
471        if version_compare(self.version, '>=1.24.00'):
472            cpp_stds += [ 'c++1z', 'c++17', 'gnu++1z', 'gnu++17' ]
473
474        if version_compare(self.version, '>=1.25.00'):
475            cpp_stds += [ 'c++2a', 'gnu++2a' ]
476
477        key = OptionKey('std', machine=self.for_machine, lang=self.language)
478        opts.update({
479            key.evolve('eh'): coredata.UserComboOption(
480                'C++ exception handling type.',
481                ['none', 'default', 'a', 's', 'sc'],
482                'default',
483            ),
484            key.evolve('debugstl'): coredata.UserBooleanOption(
485                'STL debug mode',
486                False,
487            ),
488        })
489        opts[key].choices = cpp_stds
490        return opts
491
492    # Elbrus C++ compiler does not have lchmod, but there is only linker warning, not compiler error.
493    # So we should explicitly fail at this case.
494    def has_function(self, funcname: str, prefix: str, env: 'Environment', *,
495                     extra_args: T.Optional[T.List[str]] = None,
496                     dependencies: T.Optional[T.List['Dependency']] = None) -> T.Tuple[bool, bool]:
497        if funcname == 'lchmod':
498            return False, False
499        else:
500            return super().has_function(funcname, prefix, env,
501                                        extra_args=extra_args,
502                                        dependencies=dependencies)
503
504    # Elbrus C++ compiler does not support RTTI, so don't check for it.
505    def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
506        args = []
507        key = OptionKey('std', machine=self.for_machine, lang=self.language)
508        std = options[key]
509        if std.value != 'none':
510            args.append(self._find_best_cpp_std(std.value))
511
512        non_msvc_eh_options(options[key.evolve('eh')].value, args)
513
514        if options[key.evolve('debugstl')].value:
515            args.append('-D_GLIBCXX_DEBUG=1')
516        return args
517
518
519class IntelCPPCompiler(IntelGnuLikeCompiler, CPPCompiler):
520    def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool,
521                 info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None,
522                 linker: T.Optional['DynamicLinker'] = None,
523                 full_version: T.Optional[str] = None):
524        CPPCompiler.__init__(self, exelist, version, for_machine, is_cross,
525                             info, exe_wrapper, linker=linker, full_version=full_version)
526        IntelGnuLikeCompiler.__init__(self)
527        self.lang_header = 'c++-header'
528        default_warn_args = ['-Wall', '-w3', '-diag-disable:remark',
529                             '-Wpch-messages', '-Wnon-virtual-dtor']
530        self.warn_args = {'0': [],
531                          '1': default_warn_args,
532                          '2': default_warn_args + ['-Wextra'],
533                          '3': default_warn_args + ['-Wextra']}
534
535    def get_options(self) -> 'KeyedOptionDictType':
536        opts = CPPCompiler.get_options(self)
537        # Every Unix compiler under the sun seems to accept -std=c++03,
538        # with the exception of ICC. Instead of preventing the user from
539        # globally requesting C++03, we transparently remap it to C++98
540        c_stds = ['c++98', 'c++03']
541        g_stds = ['gnu++98', 'gnu++03']
542        if version_compare(self.version, '>=15.0.0'):
543            c_stds += ['c++11', 'c++14']
544            g_stds += ['gnu++11']
545        if version_compare(self.version, '>=16.0.0'):
546            c_stds += ['c++17']
547        if version_compare(self.version, '>=17.0.0'):
548            g_stds += ['gnu++14']
549        if version_compare(self.version, '>=19.1.0'):
550            c_stds += ['c++2a']
551            g_stds += ['gnu++2a']
552
553
554        key = OptionKey('std', machine=self.for_machine, lang=self.language)
555        opts.update({
556            key.evolve('eh'): coredata.UserComboOption(
557                'C++ exception handling type.',
558                ['none', 'default', 'a', 's', 'sc'],
559                'default',
560            ),
561            key.evolve('rtti'): coredata.UserBooleanOption('Enable RTTI', True),
562            key.evolve('debugstl'): coredata.UserBooleanOption('STL debug mode', False),
563        })
564        opts[key].choices = ['none'] + c_stds + g_stds
565        return opts
566
567    def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
568        args = []
569        key = OptionKey('std', machine=self.for_machine, lang=self.language)
570        std = options[key]
571        if std.value != 'none':
572            remap_cpp03 = {
573                'c++03': 'c++98',
574                'gnu++03': 'gnu++98'
575            }
576            args.append('-std=' + remap_cpp03.get(std.value, std.value))
577        if options[key.evolve('eh')].value == 'none':
578            args.append('-fno-exceptions')
579        if not options[key.evolve('rtti')].value:
580            args.append('-fno-rtti')
581        if options[key.evolve('debugstl')].value:
582            args.append('-D_GLIBCXX_DEBUG=1')
583        return args
584
585    def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
586        return []
587
588
589class VisualStudioLikeCPPCompilerMixin(CompilerMixinBase):
590
591    """Mixin for C++ specific method overrides in MSVC-like compilers."""
592
593    VC_VERSION_MAP = {
594        'none': (True, None),
595        'vc++11': (True, 11),
596        'vc++14': (True, 14),
597        'vc++17': (True, 17),
598        'vc++latest': (True, "latest"),
599        'c++11': (False, 11),
600        'c++14': (False, 14),
601        'c++17': (False, 17),
602        'c++latest': (False, "latest"),
603    }
604
605    def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
606        # need a typeddict for this
607        key = OptionKey('winlibs', machine=self.for_machine, lang=self.language)
608        return T.cast(T.List[str], options[key].value[:])
609
610    def _get_options_impl(self, opts: 'KeyedOptionDictType', cpp_stds: T.List[str]) -> 'KeyedOptionDictType':
611        key = OptionKey('std', machine=self.for_machine, lang=self.language)
612        opts.update({
613            key.evolve('eh'): coredata.UserComboOption(
614                'C++ exception handling type.',
615                ['none', 'default', 'a', 's', 'sc'],
616                'default',
617            ),
618            key.evolve('rtti'): coredata.UserBooleanOption('Enable RTTI', True),
619            key.evolve('winlibs'): coredata.UserArrayOption(
620                'Windows libs to link against.',
621                msvc_winlibs,
622            ),
623        })
624        opts[key.evolve('std')].choices = cpp_stds
625        return opts
626
627    def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
628        args = []
629        key = OptionKey('std', machine=self.for_machine, lang=self.language)
630
631        eh = options[key.evolve('eh')]
632        if eh.value == 'default':
633            args.append('/EHsc')
634        elif eh.value == 'none':
635            args.append('/EHs-c-')
636        else:
637            args.append('/EH' + eh.value)
638
639        if not options[key.evolve('rtti')].value:
640            args.append('/GR-')
641
642        permissive, ver = self.VC_VERSION_MAP[options[key].value]
643
644        if ver is not None:
645            args.append(f'/std:c++{ver}')
646
647        if not permissive:
648            args.append('/permissive-')
649
650        return args
651
652    def get_compiler_check_args(self, mode: CompileCheckMode) -> T.List[str]:
653        # XXX: this is a hack because so much GnuLike stuff is in the base CPPCompiler class.
654        return Compiler.get_compiler_check_args(self, mode)
655
656
657class CPP11AsCPP14Mixin(CompilerMixinBase):
658
659    """Mixin class for VisualStudio and ClangCl to replace C++11 std with C++14.
660
661    This is a limitation of Clang and MSVC that ICL doesn't share.
662    """
663
664    def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
665        # Note: there is no explicit flag for supporting C++11; we attempt to do the best we can
666        # which means setting the C++ standard version to C++14, in compilers that support it
667        # (i.e., after VS2015U3)
668        # if one is using anything before that point, one cannot set the standard.
669        key = OptionKey('std', machine=self.for_machine, lang=self.language)
670        if options[key].value in {'vc++11', 'c++11'}:
671            mlog.warning(self.id, 'does not support C++11;',
672                         'attempting best effort; setting the standard to C++14', once=True)
673            # Don't mutate anything we're going to change, we need to use
674            # deepcopy since we're messing with members, and we can't simply
675            # copy the members because the option proxy doesn't support it.
676            options = copy.deepcopy(options)
677            if options[key].value == 'vc++11':
678                options[key].value = 'vc++14'
679            else:
680                options[key].value = 'c++14'
681        return super().get_option_compile_args(options)
682
683
684class VisualStudioCPPCompiler(CPP11AsCPP14Mixin, VisualStudioLikeCPPCompilerMixin, MSVCCompiler, CPPCompiler):
685    def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice,
686                 is_cross: bool, info: 'MachineInfo', target: str,
687                 exe_wrapper: T.Optional['ExternalProgram'] = None,
688                 linker: T.Optional['DynamicLinker'] = None,
689                 full_version: T.Optional[str] = None):
690        CPPCompiler.__init__(self, exelist, version, for_machine, is_cross,
691                             info, exe_wrapper, linker=linker, full_version=full_version)
692        MSVCCompiler.__init__(self, target)
693        self.id = 'msvc'
694
695    def get_options(self) -> 'KeyedOptionDictType':
696        cpp_stds = ['none', 'c++11', 'vc++11']
697        # Visual Studio 2015 and later
698        if version_compare(self.version, '>=19'):
699            cpp_stds.extend(['c++14', 'c++latest', 'vc++latest'])
700        # Visual Studio 2017 and later
701        if version_compare(self.version, '>=19.11'):
702            cpp_stds.extend(['vc++14', 'c++17', 'vc++17'])
703        return self._get_options_impl(super().get_options(), cpp_stds)
704
705    def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
706        key = OptionKey('std', machine=self.for_machine, lang=self.language)
707        if options[key].value != 'none' and version_compare(self.version, '<19.00.24210'):
708            mlog.warning('This version of MSVC does not support cpp_std arguments')
709            options = copy.copy(options)
710            options[key].value = 'none'
711
712        args = super().get_option_compile_args(options)
713
714        if version_compare(self.version, '<19.11'):
715            try:
716                i = args.index('/permissive-')
717            except ValueError:
718                return args
719            del args[i]
720        return args
721
722class ClangClCPPCompiler(CPP11AsCPP14Mixin, VisualStudioLikeCPPCompilerMixin, ClangClCompiler, CPPCompiler):
723    def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice,
724                 is_cross: bool, info: 'MachineInfo', target: str,
725                 exe_wrapper: T.Optional['ExternalProgram'] = None,
726                 linker: T.Optional['DynamicLinker'] = None,
727                 full_version: T.Optional[str] = None):
728        CPPCompiler.__init__(self, exelist, version, for_machine, is_cross,
729                             info, exe_wrapper, linker=linker, full_version=full_version)
730        ClangClCompiler.__init__(self, target)
731        self.id = 'clang-cl'
732
733    def get_options(self) -> 'KeyedOptionDictType':
734        cpp_stds = ['none', 'c++11', 'vc++11', 'c++14', 'vc++14', 'c++17', 'vc++17', 'c++latest']
735        return self._get_options_impl(super().get_options(), cpp_stds)
736
737
738class IntelClCPPCompiler(VisualStudioLikeCPPCompilerMixin, IntelVisualStudioLikeCompiler, CPPCompiler):
739
740    def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice,
741                 is_cross: bool, info: 'MachineInfo', target: str,
742                 exe_wrapper: T.Optional['ExternalProgram'] = None,
743                 linker: T.Optional['DynamicLinker'] = None,
744                 full_version: T.Optional[str] = None):
745        CPPCompiler.__init__(self, exelist, version, for_machine, is_cross,
746                             info, exe_wrapper, linker=linker, full_version=full_version)
747        IntelVisualStudioLikeCompiler.__init__(self, target)
748
749    def get_options(self) -> 'KeyedOptionDictType':
750        # This has only been tested with version 19.0,
751        cpp_stds = ['none', 'c++11', 'vc++11', 'c++14', 'vc++14', 'c++17', 'vc++17', 'c++latest']
752        return self._get_options_impl(super().get_options(), cpp_stds)
753
754    def get_compiler_check_args(self, mode: CompileCheckMode) -> T.List[str]:
755        # XXX: this is a hack because so much GnuLike stuff is in the base CPPCompiler class.
756        return IntelVisualStudioLikeCompiler.get_compiler_check_args(self, mode)
757
758
759class ArmCPPCompiler(ArmCompiler, CPPCompiler):
760    def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool,
761                 info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None,
762                 linker: T.Optional['DynamicLinker'] = None,
763                 full_version: T.Optional[str] = None):
764        CPPCompiler.__init__(self, exelist, version, for_machine, is_cross,
765                             info, exe_wrapper, linker=linker, full_version=full_version)
766        ArmCompiler.__init__(self)
767
768    def get_options(self) -> 'KeyedOptionDictType':
769        opts = CPPCompiler.get_options(self)
770        key = OptionKey('std', machine=self.for_machine, lang=self.language)
771        opts[key].choices = ['none', 'c++03', 'c++11']
772        return opts
773
774    def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
775        args = []
776        key = OptionKey('std', machine=self.for_machine, lang=self.language)
777        std = options[key]
778        if std.value == 'c++11':
779            args.append('--cpp11')
780        elif std.value == 'c++03':
781            args.append('--cpp')
782        return args
783
784    def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
785        return []
786
787    def get_compiler_check_args(self, mode: CompileCheckMode) -> T.List[str]:
788        return []
789
790
791class CcrxCPPCompiler(CcrxCompiler, CPPCompiler):
792    def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool,
793                 info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None,
794                 linker: T.Optional['DynamicLinker'] = None,
795                 full_version: T.Optional[str] = None):
796        CPPCompiler.__init__(self, exelist, version, for_machine, is_cross,
797                             info, exe_wrapper, linker=linker, full_version=full_version)
798        CcrxCompiler.__init__(self)
799
800    # Override CCompiler.get_always_args
801    def get_always_args(self) -> T.List[str]:
802        return ['-nologo', '-lang=cpp']
803
804    def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
805        return []
806
807    def get_compile_only_args(self) -> T.List[str]:
808        return []
809
810    def get_output_args(self, target: str) -> T.List[str]:
811        return ['-output=obj=%s' % target]
812
813    def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
814        return []
815
816    def get_compiler_check_args(self, mode: CompileCheckMode) -> T.List[str]:
817        return []
818
819class C2000CPPCompiler(C2000Compiler, CPPCompiler):
820    def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, is_cross: bool,
821                 info: 'MachineInfo', exe_wrapper: T.Optional['ExternalProgram'] = None,
822                 linker: T.Optional['DynamicLinker'] = None,
823                 full_version: T.Optional[str] = None):
824        CPPCompiler.__init__(self, exelist, version, for_machine, is_cross,
825                             info, exe_wrapper, linker=linker, full_version=full_version)
826        C2000Compiler.__init__(self)
827
828    def get_options(self) -> 'KeyedOptionDictType':
829        opts = CPPCompiler.get_options(self)
830        key = OptionKey('std', machine=self.for_machine, lang=self.language)
831        opts[key].choices = ['none', 'c++03']
832        return opts
833
834    def get_always_args(self) -> T.List[str]:
835        return ['-nologo', '-lang=cpp']
836
837    def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
838        return []
839
840    def get_compile_only_args(self) -> T.List[str]:
841        return []
842
843    def get_output_args(self, target: str) -> T.List[str]:
844        return ['-output=obj=%s' % target]
845
846    def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]:
847        return []
848
849    def get_compiler_check_args(self, mode: CompileCheckMode) -> T.List[str]:
850        return []
851