Merge pull request #10332 from xclaesse/std-opt

c_std, cpp_std: Change to a list of desired versions in preference order
pull/12177/head
Jussi Pakkanen 2 years ago committed by GitHub
commit 08d83a4a97
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 15
      docs/markdown/Builtin-options.md
  2. 18
      docs/markdown/snippets/cstd.md
  3. 114
      mesonbuild/compilers/c.py
  4. 4
      mesonbuild/compilers/compilers.py
  5. 76
      mesonbuild/compilers/cpp.py
  6. 76
      mesonbuild/coredata.py
  7. 2
      mesonbuild/mcompile.py
  8. 7
      mesonbuild/optinterpreter.py
  9. 6
      test cases/unit/114 c cpp stds/meson.build
  10. 33
      unittests/allplatformstests.py

@ -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

@ -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.

@ -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]:

@ -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

@ -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]:

@ -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):

@ -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():

@ -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],

@ -0,0 +1,6 @@
project('c cpp stds', 'c', 'cpp',
default_options: [
'c_std=gnu89,c89',
'cpp_std=gnu++98,vc++11',
],
)

@ -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')

Loading…
Cancel
Save