diff --git a/docs/markdown/Builtin-options.md b/docs/markdown/Builtin-options.md index ca4fd14c0..f3cbcea75 100644 --- a/docs/markdown/Builtin-options.md +++ b/docs/markdown/Builtin-options.md @@ -296,6 +296,21 @@ is inherited from the main project. This is useful, for example, when the main project requires C++11, but a subproject requires C++14. The `cpp_std` value from the subproject's `default_options` is now respected. +Since *1.3.0* `c_std` and `cpp_std` options now accept a list of values. +Projects that prefer GNU C, but can fallback to ISO C, can now set, for +example, `default_options: 'c_std=gnu11,c11'`, and it will use `gnu11` when +available, but fallback to c11 otherwise. It is an error only if none of the +values are supported by the current compiler. +Likewise, a project that can take benefit of `c++17` but can still build with +`c++11` can set `default_options: 'cpp_std=c++17,c++11'`. +This allows us to deprecate `gnuXX` values from the MSVC compiler. That means +that `default_options: 'c_std=gnu11'` will now print a warning with MSVC +but fallback to `c11`. No warning is printed if at least one +of the values is valid, i.e. `default_options: 'c_std=gnu11,c11'`. +In the future that deprecation warning will become an hard error because +`c_std=gnu11` should mean GNU is required, for projects that cannot be +built with MSVC for example. + ## Specifying options per machine Since *0.51.0*, some options are specified per machine rather than diff --git a/docs/markdown/snippets/cstd.md b/docs/markdown/snippets/cstd.md new file mode 100644 index 000000000..cc1f08318 --- /dev/null +++ b/docs/markdown/snippets/cstd.md @@ -0,0 +1,18 @@ +## `c_std` and `cpp_std` options now accepts a list of values + +Projects that prefer GNU C, but can fallback to ISO C, can now set, for +example, `default_options: 'c_std=gnu11,c11'`, and it will use `gnu11` when +available, but fallback to `c11` otherwise. It is an error only if none of the +values are supported by the current compiler. + +Likewise, a project that can take benefit of `c++17` but can still build with +`c++11` can set `default_options: 'cpp_std=c++17,c++11'`. + +This allows us to deprecate `gnuXX` values from the MSVC compiler. That means +that `default_options: 'c_std=gnu11'` will now print a warning with MSVC +but fallback to `c11`. No warning is printed if at least one +of the values is valid, i.e. `default_options: 'c_std=gnu11,c11'`. + +In the future that deprecation warning will become an hard error because +`c_std=gnu11` should mean GNU is required, for projects that cannot be +built with MSVC for example. diff --git a/mesonbuild/compilers/c.py b/mesonbuild/compilers/c.py index d514650de..7f9e5844c 100644 --- a/mesonbuild/compilers/c.py +++ b/mesonbuild/compilers/c.py @@ -56,6 +56,10 @@ if T.TYPE_CHECKING: else: CompilerMixinBase = object +_ALL_STDS = ['c89', 'c9x', 'c90', 'c99', 'c1x', 'c11', 'c17', 'c18', 'c2x'] +_ALL_STDS += [f'gnu{std[1:]}' for std in _ALL_STDS] +_ALL_STDS += ['iso9899:1990', 'iso9899:199409', 'iso9899:1999', 'iso9899:2011', 'iso9899:2017', 'iso9899:2018'] + class CCompiler(CLikeCompiler, Compiler): def attribute_check_func(self, name: str) -> str: @@ -101,12 +105,9 @@ class CCompiler(CLikeCompiler, Compiler): def get_options(self) -> 'MutableKeyedOptionDictType': opts = super().get_options() + key = OptionKey('std', machine=self.for_machine, lang=self.language) opts.update({ - OptionKey('std', machine=self.for_machine, lang=self.language): coredata.UserComboOption( - 'C language standard to use', - ['none'], - 'none', - ) + key: coredata.UserStdOption('C', _ALL_STDS), }) return opts @@ -125,20 +126,18 @@ class _ClangCStds(CompilerMixinBase): def get_options(self) -> 'MutableKeyedOptionDictType': opts = super().get_options() - c_stds = ['c89', 'c99', 'c11'] - g_stds = ['gnu89', 'gnu99', 'gnu11'] + stds = ['c89', 'c99', 'c11'] # https://releases.llvm.org/6.0.0/tools/clang/docs/ReleaseNotes.html # https://en.wikipedia.org/wiki/Xcode#Latest_versions if version_compare(self.version, self._C17_VERSION): - c_stds += ['c17'] - g_stds += ['gnu17'] + stds += ['c17'] if version_compare(self.version, self._C18_VERSION): - c_stds += ['c18'] - g_stds += ['gnu18'] + stds += ['c18'] if version_compare(self.version, self._C2X_VERSION): - c_stds += ['c2x'] - g_stds += ['gnu2x'] - opts[OptionKey('std', machine=self.for_machine, lang=self.language)].choices = ['none'] + c_stds + g_stds + stds += ['c2x'] + std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] + assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + std_opt.set_versions(stds, gnu=True) return opts @@ -244,8 +243,9 @@ class ArmclangCCompiler(ArmclangCompiler, CCompiler): def get_options(self) -> 'MutableKeyedOptionDictType': opts = CCompiler.get_options(self) - key = OptionKey('std', machine=self.for_machine, lang=self.language) - opts[key].choices = ['none', 'c90', 'c99', 'c11', 'gnu90', 'gnu99', 'gnu11'] + std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] + assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + std_opt.set_versions(['c90', 'c99', 'c11'], gnu=True) return opts def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: @@ -285,16 +285,15 @@ class GnuCCompiler(GnuCompiler, CCompiler): def get_options(self) -> 'MutableKeyedOptionDictType': opts = CCompiler.get_options(self) - c_stds = ['c89', 'c99', 'c11'] - g_stds = ['gnu89', 'gnu99', 'gnu11'] + stds = ['c89', 'c99', 'c11'] if version_compare(self.version, self._C18_VERSION): - c_stds += ['c17', 'c18'] - g_stds += ['gnu17', 'gnu18'] + stds += ['c17', 'c18'] if version_compare(self.version, self._C2X_VERSION): - c_stds += ['c2x'] - g_stds += ['gnu2x'] + stds += ['c2x'] key = OptionKey('std', machine=self.for_machine, lang=self.language) - opts[key].choices = ['none'] + c_stds + g_stds + std_opt = opts[key] + assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + std_opt.set_versions(stds, gnu=True) if self.info.is_windows() or self.info.is_cygwin(): opts.update({ key.evolve('winlibs'): coredata.UserArrayOption( @@ -370,7 +369,9 @@ class ElbrusCCompiler(ElbrusCompiler, CCompiler): stds += ['c90', 'c1x', 'gnu90', 'gnu1x', 'iso9899:2011'] if version_compare(self.version, '>=1.26.00'): stds += ['c17', 'c18', 'iso9899:2017', 'iso9899:2018', 'gnu17', 'gnu18'] - opts[OptionKey('std', machine=self.for_machine, lang=self.language)].choices = ['none'] + stds + std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] + assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + std_opt.set_versions(stds) return opts # Elbrus C compiler does not have lchmod, but there is only linker warning, not compiler error. @@ -404,11 +405,12 @@ class IntelCCompiler(IntelGnuLikeCompiler, CCompiler): def get_options(self) -> 'MutableKeyedOptionDictType': opts = CCompiler.get_options(self) - c_stds = ['c89', 'c99'] - g_stds = ['gnu89', 'gnu99'] + stds = ['c89', 'c99'] if version_compare(self.version, '>=16.0.0'): - c_stds += ['c11'] - opts[OptionKey('std', machine=self.for_machine, lang=self.language)].choices = ['none'] + c_stds + g_stds + stds += ['c11'] + std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] + assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + std_opt.set_versions(stds, gnu=True) return opts def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: @@ -465,33 +467,23 @@ class VisualStudioCCompiler(MSVCCompiler, VisualStudioLikeCCompilerMixin, CCompi def get_options(self) -> 'MutableKeyedOptionDictType': opts = super().get_options() - c_stds = ['c89', 'c99'] - # Need to have these to be compatible with projects - # that set c_std to e.g. gnu99. - # https://github.com/mesonbuild/meson/issues/7611 - g_stds = ['gnu89', 'gnu90', 'gnu9x', 'gnu99'] + stds = ['c89', 'c99'] if version_compare(self.version, self._C11_VERSION): - c_stds += ['c11'] - g_stds += ['gnu1x', 'gnu11'] + stds += ['c11'] if version_compare(self.version, self._C17_VERSION): - c_stds += ['c17', 'c18'] - g_stds += ['gnu17', 'gnu18'] - key = OptionKey('std', machine=self.for_machine, lang=self.language) - opts[key].choices = ['none'] + c_stds + g_stds + stds += ['c17', 'c18'] + std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] + assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + std_opt.set_versions(stds, gnu=True, gnu_deprecated=True) return opts def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] std = options[OptionKey('std', machine=self.for_machine, lang=self.language)] - if std.value.startswith('gnu'): - mlog.log( - 'cl.exe does not actually support gnu standards, and meson ' - 'will instead demote to the nearest ISO C standard. This ' - 'may cause compilation to fail.', once=True) # As of MVSC 16.8, /std:c11 and /std:c17 are the only valid C standard options. - if std.value in {'c11', 'gnu1x', 'gnu11'}: + if std.value in {'c11'}: args.append('/std:c11') - elif std.value in {'c17', 'c18', 'gnu17', 'gnu18'}: + elif std.value in {'c17', 'c18'}: args.append('/std:c17') return args @@ -531,8 +523,9 @@ class IntelClCCompiler(IntelVisualStudioLikeCompiler, VisualStudioLikeCCompilerM def get_options(self) -> 'MutableKeyedOptionDictType': opts = super().get_options() - key = OptionKey('std', machine=self.for_machine, lang=self.language) - opts[key].choices = ['none', 'c89', 'c99', 'c11'] + std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] + assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + std_opt.set_versions(['c89', 'c99', 'c11']) return opts def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: @@ -564,8 +557,9 @@ class ArmCCompiler(ArmCompiler, CCompiler): def get_options(self) -> 'MutableKeyedOptionDictType': opts = CCompiler.get_options(self) - key = OptionKey('std', machine=self.for_machine, lang=self.language) - opts[key].choices = ['none', 'c89', 'c99', 'c11'] + std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] + assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + std_opt.set_versions(['c89', 'c99', 'c11']) return opts def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: @@ -593,8 +587,9 @@ class CcrxCCompiler(CcrxCompiler, CCompiler): def get_options(self) -> 'MutableKeyedOptionDictType': opts = CCompiler.get_options(self) - key = OptionKey('std', machine=self.for_machine, lang=self.language) - opts[key].choices = ['none', 'c89', 'c99'] + std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] + assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + std_opt.set_versions(['c89', 'c99']) return opts def get_no_stdinc_args(self) -> T.List[str]: @@ -640,8 +635,9 @@ class Xc16CCompiler(Xc16Compiler, CCompiler): def get_options(self) -> 'MutableKeyedOptionDictType': opts = CCompiler.get_options(self) - key = OptionKey('std', machine=self.for_machine, lang=self.language) - opts[key].choices = ['none', 'c89', 'c99', 'gnu89', 'gnu99'] + std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] + assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + std_opt.set_versions(['c89', 'c99'], gnu=True) return opts def get_no_stdinc_args(self) -> T.List[str]: @@ -685,8 +681,9 @@ class CompCertCCompiler(CompCertCompiler, CCompiler): def get_options(self) -> 'MutableKeyedOptionDictType': opts = CCompiler.get_options(self) - key = OptionKey('std', machine=self.for_machine, lang=self.language) - opts[key].choices = ['none', 'c89', 'c99'] + std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] + assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + std_opt.set_versions(['c89', 'c99']) return opts def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: @@ -722,8 +719,9 @@ class TICCompiler(TICompiler, CCompiler): def get_options(self) -> 'MutableKeyedOptionDictType': opts = CCompiler.get_options(self) - key = OptionKey('std', machine=self.for_machine, lang=self.language) - opts[key].choices = ['none', 'c89', 'c99', 'c11'] + std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] + assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + std_opt.set_versions(['c89', 'c99', 'c11']) return opts def get_no_stdinc_args(self) -> T.List[str]: diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index 7a8ae7247..c7af1cac6 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -1380,11 +1380,11 @@ def get_global_options(lang: str, cargs = coredata.UserArrayOption( description + ' compiler', - comp_options, split_args=True, user_input=True, allow_dups=True) + comp_options, split_args=True, allow_dups=True) largs = coredata.UserArrayOption( description + ' linker', - link_options, split_args=True, user_input=True, allow_dups=True) + link_options, split_args=True, allow_dups=True) if comp.INVOKES_LINKER and comp_key == envkey: # If the compiler acts as a linker driver, and we're using the diff --git a/mesonbuild/compilers/cpp.py b/mesonbuild/compilers/cpp.py index 8c8043763..3e9668262 100644 --- a/mesonbuild/compilers/cpp.py +++ b/mesonbuild/compilers/cpp.py @@ -55,6 +55,10 @@ if T.TYPE_CHECKING: else: CompilerMixinBase = object +_ALL_STDS = ['c++98', 'c++0x', 'c++03', 'c++1y', 'c++1z', 'c++11', 'c++14', 'c++17', 'c++2a', 'c++20', 'c++23'] +_ALL_STDS += [f'gnu{std[1:]}' for std in _ALL_STDS] +_ALL_STDS += ['vc++11', 'vc++14', 'vc++17', 'vc++20', 'vc++latest', 'c++latest'] + def non_msvc_eh_options(eh: str, args: T.List[str]) -> None: if eh == 'none': @@ -178,11 +182,7 @@ class CPPCompiler(CLikeCompiler, Compiler): opts = super().get_options() key = OptionKey('std', machine=self.for_machine, lang=self.language) opts.update({ - key: coredata.UserComboOption( - 'C++ language standard to use', - ['none'], - 'none', - ), + key: coredata.UserStdOption('C++', _ALL_STDS), }) return opts @@ -257,17 +257,15 @@ class ClangCPPCompiler(_StdCPPLibMixin, ClangCompiler, CPPCompiler): key.evolve('rtti'): coredata.UserBooleanOption('Enable RTTI', True), }) cppstd_choices = [ - 'none', 'c++98', 'c++03', 'c++11', 'c++14', 'c++17', 'c++1z', - 'c++2a', 'c++20', 'gnu++11', 'gnu++14', 'gnu++17', 'gnu++1z', - 'gnu++2a', 'gnu++20', + 'c++98', 'c++03', 'c++11', 'c++14', 'c++17', 'c++1z', 'c++2a', 'c++20', ] if version_compare(self.version, self._CPP23_VERSION): cppstd_choices.append('c++23') - cppstd_choices.append('gnu++23') if version_compare(self.version, self._CPP26_VERSION): cppstd_choices.append('c++26') - cppstd_choices.append('gnu++26') - opts[key.evolve('std')].choices = cppstd_choices + std_opt = opts[key.evolve('std')] + assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + std_opt.set_versions(cppstd_choices, gnu=True) if self.info.is_windows() or self.info.is_cygwin(): opts.update({ key.evolve('winlibs'): coredata.UserArrayOption( @@ -371,10 +369,9 @@ class ArmclangCPPCompiler(ArmclangCompiler, CPPCompiler): 'default', ), }) - opts[key].choices = [ - 'none', 'c++98', 'c++03', 'c++11', 'c++14', 'c++17', 'gnu++98', - 'gnu++03', 'gnu++11', 'gnu++14', 'gnu++17', - ] + std_opt = opts[key] + assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + std_opt.set_versions(['c++98', 'c++03', 'c++11', 'c++14', 'c++17'], gnu=True) return opts def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: @@ -426,17 +423,16 @@ class GnuCPPCompiler(_StdCPPLibMixin, GnuCompiler, CPPCompiler): ) }) cppstd_choices = [ - 'none', 'c++98', 'c++03', 'c++11', 'c++14', 'c++17', 'c++1z', - 'c++2a', 'c++20', 'gnu++03', 'gnu++11', 'gnu++14', 'gnu++17', - 'gnu++1z', 'gnu++2a', 'gnu++20', + 'c++98', 'c++03', 'c++11', 'c++14', 'c++17', 'c++1z', + 'c++2a', 'c++20', ] if version_compare(self.version, '>=12.2.0'): cppstd_choices.append('c++23') - cppstd_choices.append('gnu++23') if version_compare(self.version, '>=14.0.0'): cppstd_choices.append('c++26') - cppstd_choices.append('gnu++26') - opts[key].choices = cppstd_choices + std_opt = opts[key] + assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + std_opt.set_versions(cppstd_choices, gnu=True) if self.info.is_windows() or self.info.is_cygwin(): opts.update({ key.evolve('winlibs'): coredata.UserArrayOption( @@ -513,21 +509,21 @@ class ElbrusCPPCompiler(ElbrusCompiler, CPPCompiler): def get_options(self) -> 'MutableKeyedOptionDictType': opts = CPPCompiler.get_options(self) - cpp_stds = ['none', 'c++98', 'gnu++98'] + cpp_stds = ['c++98'] if version_compare(self.version, '>=1.20.00'): - cpp_stds += ['c++03', 'c++0x', 'c++11', 'gnu++03', 'gnu++0x', 'gnu++11'] + cpp_stds += ['c++03', 'c++0x', 'c++11'] if version_compare(self.version, '>=1.21.00') and version_compare(self.version, '<1.22.00'): - cpp_stds += ['c++14', 'gnu++14', 'c++1y', 'gnu++1y'] + cpp_stds += ['c++14', 'c++1y'] if version_compare(self.version, '>=1.22.00'): - cpp_stds += ['c++14', 'gnu++14'] + cpp_stds += ['c++14'] if version_compare(self.version, '>=1.23.00'): - cpp_stds += ['c++1y', 'gnu++1y'] + cpp_stds += ['c++1y'] if version_compare(self.version, '>=1.24.00'): - cpp_stds += ['c++1z', 'c++17', 'gnu++1z', 'gnu++17'] + cpp_stds += ['c++1z', 'c++17'] if version_compare(self.version, '>=1.25.00'): - cpp_stds += ['c++2a', 'gnu++2a'] + cpp_stds += ['c++2a'] if version_compare(self.version, '>=1.26.00'): - cpp_stds += ['c++20', 'gnu++20'] + cpp_stds += ['c++20'] key = OptionKey('std', machine=self.for_machine, lang=self.language) opts.update({ @@ -541,7 +537,9 @@ class ElbrusCPPCompiler(ElbrusCompiler, CPPCompiler): False, ), }) - opts[key].choices = cpp_stds + std_opt = opts[key] + assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + std_opt.set_versions(cpp_stds, gnu=True) return opts # Elbrus C++ compiler does not have lchmod, but there is only linker warning, not compiler error. @@ -615,7 +613,9 @@ class IntelCPPCompiler(IntelGnuLikeCompiler, CPPCompiler): key.evolve('rtti'): coredata.UserBooleanOption('Enable RTTI', True), key.evolve('debugstl'): coredata.UserBooleanOption('STL debug mode', False), }) - opts[key].choices = ['none'] + c_stds + g_stds + std_opt = opts[key] + assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + std_opt.set_versions(c_stds + g_stds) return opts def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: @@ -682,7 +682,9 @@ class VisualStudioLikeCPPCompilerMixin(CompilerMixinBase): msvc_winlibs, ), }) - opts[key.evolve('std')].choices = cpp_stds + std_opt = opts[key] + assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + std_opt.set_versions(cpp_stds) return opts def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: @@ -846,8 +848,9 @@ class ArmCPPCompiler(ArmCompiler, CPPCompiler): def get_options(self) -> 'MutableKeyedOptionDictType': opts = CPPCompiler.get_options(self) - key = OptionKey('std', machine=self.for_machine, lang=self.language) - opts[key].choices = ['none', 'c++03', 'c++11'] + std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] + assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + std_opt.set_versions(['c++03', 'c++11']) return opts def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: @@ -906,8 +909,9 @@ class TICPPCompiler(TICompiler, CPPCompiler): def get_options(self) -> 'MutableKeyedOptionDictType': opts = CPPCompiler.get_options(self) - key = OptionKey('std', machine=self.for_machine, lang=self.language) - opts[key].choices = ['none', 'c++03'] + std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] + assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + std_opt.set_versions(['c++03']) return opts def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index b0dad1395..4b0f9af58 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -248,23 +248,17 @@ class UserComboOption(UserOption[str]): class UserArrayOption(UserOption[T.List[str]]): def __init__(self, description: str, value: T.Union[str, T.List[str]], - split_args: bool = False, user_input: bool = False, + split_args: bool = False, allow_dups: bool = False, yielding: bool = DEFAULT_YIELDING, choices: T.Optional[T.List[str]] = None, deprecated: T.Union[bool, str, T.Dict[str, str], T.List[str]] = False): super().__init__(description, choices if choices is not None else [], yielding, deprecated) self.split_args = split_args self.allow_dups = allow_dups - self.value = self.validate_value(value, user_input=user_input) - - def listify(self, value: T.Union[str, T.List[str]], user_input: bool = True) -> T.List[str]: - # User input is for options defined on the command line (via -D - # options). Users can put their input in as a comma separated - # string, but for defining options in meson_options.txt the format - # should match that of a combo - if not user_input and isinstance(value, str) and not value.startswith('['): - raise MesonException('Value does not define an array: ' + value) + self.set_value(value) + @staticmethod + def listify_value(value: T.Union[str, T.List[str]], shlex_split_args: bool = False) -> T.List[str]: if isinstance(value, str): if value.startswith('['): try: @@ -274,7 +268,7 @@ class UserArrayOption(UserOption[T.List[str]]): elif value == '': newvalue = [] else: - if self.split_args: + if shlex_split_args: newvalue = split_args(value) else: newvalue = [v.strip() for v in value.split(',')] @@ -284,8 +278,11 @@ class UserArrayOption(UserOption[T.List[str]]): raise MesonException(f'"{value}" should be a string array, but it is not') return newvalue - def validate_value(self, value: T.Union[str, T.List[str]], user_input: bool = True) -> T.List[str]: - newvalue = self.listify(value, user_input) + def listify(self, value: T.Any) -> T.List[T.Any]: + return self.listify_value(value, self.split_args) + + def validate_value(self, value: T.Union[str, T.List[str]]) -> T.List[str]: + newvalue = self.listify(value) if not self.allow_dups and len(set(newvalue)) != len(newvalue): msg = 'Duplicated values in array option is deprecated. ' \ @@ -324,6 +321,59 @@ class UserFeatureOption(UserComboOption): def is_auto(self) -> bool: return self.value == 'auto' +class UserStdOption(UserComboOption): + ''' + UserOption specific to c_std and cpp_std options. User can set a list of + STDs in preference order and it selects the first one supported by current + compiler. + + For historical reasons, some compilers (msvc) allowed setting a GNU std and + silently fell back to C std. This is now deprecated. Projects that support + both GNU and MSVC compilers should set e.g. c_std=gnu11,c11. + + This is not using self.deprecated mechanism we already have for project + options because we want to print a warning if ALL values are deprecated, not + if SOME values are deprecated. + ''' + def __init__(self, lang: str, all_stds: T.List[str]) -> None: + self.lang = lang.lower() + self.all_stds = ['none'] + all_stds + # Map a deprecated std to its replacement. e.g. gnu11 -> c11. + self.deprecated_stds: T.Dict[str, str] = {} + super().__init__(f'{lang} language standard to use', ['none'], 'none') + + def set_versions(self, versions: T.List[str], gnu: bool = False, gnu_deprecated: bool = False) -> None: + assert all(std in self.all_stds for std in versions) + self.choices += versions + if gnu: + gnu_stds_map = {f'gnu{std[1:]}': std for std in versions} + if gnu_deprecated: + self.deprecated_stds.update(gnu_stds_map) + else: + self.choices += gnu_stds_map.keys() + + def validate_value(self, value: T.Union[str, T.List[str]]) -> str: + candidates = UserArrayOption.listify_value(value) + unknown = [std for std in candidates if std not in self.all_stds] + if unknown: + raise MesonException(f'Unknown {self.lang.upper()} std {unknown}. Possible values are {self.all_stds}.') + # Check first if any of the candidates are not deprecated + for std in candidates: + if std in self.choices: + return std + # Fallback to a deprecated std if any + for std in candidates: + newstd = self.deprecated_stds.get(std) + if newstd is not None: + mlog.deprecation( + f'None of the values {candidates} are supported by the {self.lang} compiler.\n' + + f'However, the deprecated {std} std currently falls back to {newstd}.\n' + + 'This will be an error in the future.\n' + + 'If the project supports both GNU and MSVC compilers, a value such as\n' + + '"c_std=gnu11,c11" specifies that GNU is prefered but it can safely fallback to plain c11.') + return newstd + raise MesonException(f'None of values {candidates} are supported by the {self.lang.upper()} compiler. ' + + f'Possible values are {self.choices}') class DependencyCacheType(enum.Enum): diff --git a/mesonbuild/mcompile.py b/mesonbuild/mcompile.py index c36c65bdb..19875c22a 100644 --- a/mesonbuild/mcompile.py +++ b/mesonbuild/mcompile.py @@ -35,7 +35,7 @@ if T.TYPE_CHECKING: import argparse def array_arg(value: str) -> T.List[str]: - return UserArrayOption(None, value, allow_dups=True, user_input=True).value + return UserArrayOption.listify_value(value) def validate_builddir(builddir: Path) -> None: if not (builddir / 'meson-private' / 'coredata.dat').is_file(): diff --git a/mesonbuild/optinterpreter.py b/mesonbuild/optinterpreter.py index 8377614fe..8ad84aaa8 100644 --- a/mesonbuild/optinterpreter.py +++ b/mesonbuild/optinterpreter.py @@ -20,7 +20,7 @@ from . import coredata from . import mesonlib from . import mparser from . import mlog -from .interpreterbase import FeatureNew, typed_pos_args, typed_kwargs, ContainerTypeInfo, KwargInfo +from .interpreterbase import FeatureNew, FeatureDeprecated, typed_pos_args, typed_kwargs, ContainerTypeInfo, KwargInfo from .interpreter.type_checking import NoneType, in_set_validator if T.TYPE_CHECKING: @@ -266,6 +266,11 @@ class OptionInterpreter: def string_array_parser(self, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: StringArrayArgs) -> coredata.UserOption: choices = kwargs['choices'] value = kwargs['value'] if kwargs['value'] is not None else choices + if isinstance(value, str): + if value.startswith('['): + FeatureDeprecated('String value for array option', '1.3.0').use(self.subproject) + else: + raise mesonlib.MesonException('Value does not define an array: ' + value) return coredata.UserArrayOption(description, value, choices=choices, yielding=args[0], diff --git a/test cases/unit/114 c cpp stds/meson.build b/test cases/unit/114 c cpp stds/meson.build new file mode 100644 index 000000000..0b15efc08 --- /dev/null +++ b/test cases/unit/114 c cpp stds/meson.build @@ -0,0 +1,6 @@ +project('c cpp stds', 'c', 'cpp', + default_options: [ + 'c_std=gnu89,c89', + 'cpp_std=gnu++98,vc++11', + ], +) diff --git a/unittests/allplatformstests.py b/unittests/allplatformstests.py index b5252f776..acab026e9 100644 --- a/unittests/allplatformstests.py +++ b/unittests/allplatformstests.py @@ -4820,3 +4820,36 @@ class AllPlatformTests(BasePlatformTests): self.assertNotEqual(olddata, newdata) olddata = newdata oldmtime = newmtime + + def test_c_cpp_stds(self): + testdir = os.path.join(self.unit_test_dir, '114 c cpp stds') + self.init(testdir) + # Invalid values should fail whatever compiler we have + with self.assertRaises(subprocess.CalledProcessError): + self.setconf('-Dc_std=invalid') + with self.assertRaises(subprocess.CalledProcessError): + self.setconf('-Dc_std=c89,invalid') + with self.assertRaises(subprocess.CalledProcessError): + self.setconf('-Dc_std=c++11') + env = get_fake_env() + cc = detect_c_compiler(env, MachineChoice.HOST) + if cc.get_id() == 'msvc': + # default_option should have selected those + self.assertEqual(self.getconf('c_std'), 'c89') + self.assertEqual(self.getconf('cpp_std'), 'vc++11') + # This is deprecated but works for C + self.setconf('-Dc_std=gnu99') + self.assertEqual(self.getconf('c_std'), 'c99') + # C++ however never accepted that fallback + with self.assertRaises(subprocess.CalledProcessError): + self.setconf('-Dcpp_std=gnu++11') + # The first supported std should be selected + self.setconf('-Dcpp_std=gnu++11,vc++11,c++11') + self.assertEqual(self.getconf('cpp_std'), 'vc++11') + elif cc.get_id() == 'gcc': + # default_option should have selected those + self.assertEqual(self.getconf('c_std'), 'gnu89') + self.assertEqual(self.getconf('cpp_std'), 'gnu++98') + # The first supported std should be selected + self.setconf('-Dcpp_std=c++11,gnu++11,vc++11') + self.assertEqual(self.getconf('cpp_std'), 'c++11')