Repository: mesonbuild/meson

Issue:
Trying to set c_std to 18 and got an error
I am trying to set ‘c_std’ to version 18 and it printed an error about that version not being valid.  The document on built-in options states that it is.

However the value “c17” is working.

Would this be fixed at some point?


Discussion:
Hi @squidfarts I get the same error with Meson v0.50. However, it has been fixed [EDIT: for gcc, but not yet for Clang] with Meson &ge; v0.51
Try upgrading to the latest Meson release and please reopen this issue if it's still a problem. Thanks!
Here was the MWE:

meson.build
```meson
project('C18 Standard', 'c', default_options: ['c_std=c18'])

exe = executable('C18program', 'main.c')
test('C18program', exe)
```

main.c
```c
int main(void){ return 0; }
```
But why do I get this?

Michaels-MacBook-Pro:~ shnitzel$ cd Developer/templates/c/
.DS_Store       example.zip     executable.zip  project.zip
Michaels-MacBook-Pro:~ shnitzel$ cd Developer/templates/c/example/
Michaels-MacBook-Pro:example shnitzel$ meson setup build
The Meson build system
Version: 0.51.0
Source dir: /Users/shnitzel/Developer/templates/c/example
Build dir: /Users/shnitzel/Developer/templates/c/example/build
Build type: native build
Project name: c_example
Project version: 0.1.0
C compiler for the build machine: cc (clang 10.0.1 "Apple LLVM version 10.0.1 (clang-1001.0.46.4)")

meson.build:39:0: ERROR: Value "c18" for combo option is not one of the choices. Possible choices are: "none", "c89", "c99", "c11", "c17", "gnu89", "gnu99", "gnu11", "gnu17".

A full log can be found at /Users/shnitzel/Developer/templates/c/example/build/meson-logs/meson-log.txt
Michaels-MacBook-Pro:example shnitzel$ 

I used this:
https://github.com/squidfarts/c-example.git
OK! Sorry about that I should have asked more about the setup. Thanks!
Yes, this initially appears to be particular to Clang compiler (not a compiler bug, just doesn't happen with GCC. It was a problem with GCC on Meson 0.50 but not 0.51, while Clang is still broken with 0.51 and master branch.). With Windows and Clang 8.0 and the MWE above I get error:

> C compiler for the build machine: clang (clang 8.0.0 "clang version 8.0.0 (tags/RELEASE_800/final)")
> meson.build:1:0: ERROR: Value "c18" for combo option is not one of the choices. Possible choices are: > "none", "c89", "c99", "c11", "c17", "gnu89", "gnu99", "gnu11", "gnu17".


Resolving patch:
diff --git a/mesonbuild/compilers/c.py b/mesonbuild/compilers/c.py
--- a/mesonbuild/compilers/c.py
+++ b/mesonbuild/compilers/c.py
@@ -89,13 +89,16 @@ def get_options(self):
         opts = CCompiler.get_options(self)
         c_stds = ['c89', 'c99', 'c11']
         g_stds = ['gnu89', 'gnu99', 'gnu11']
-        if self.compiler_type is CompilerType.CLANG_OSX:
-            v = '>=10.0.0'
-        else:
-            v = '>=7.0.0'
+        # https://releases.llvm.org/6.0.0/tools/clang/docs/ReleaseNotes.html
+        # https://en.wikipedia.org/wiki/Xcode#Latest_versions
+        v = '>=10.0.0' if self.compiler_type is CompilerType.CLANG_OSX else '>=6.0.0'
         if version_compare(self.version, v):
             c_stds += ['c17']
             g_stds += ['gnu17']
+        v = '>=11.0.0' if self.compiler_type is CompilerType.CLANG_OSX else '>=8.0.0'
+        if version_compare(self.version, v):
+            c_stds += ['c18']
+            g_stds += ['gnu18']
         opts.update({'c_std': coredata.UserComboOption('C language standard to use',
                                                        ['none'] + c_stds + g_stds,
                                                        'none')})
diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py
--- a/mesonbuild/mesonlib.py
+++ b/mesonbuild/mesonlib.py
@@ -14,7 +14,6 @@
 
 """A library of random helper functionality."""
 from pathlib import Path
-from typing import List
 import sys
 import stat
 import time
@@ -259,7 +258,7 @@ def absolute_path(self, srcdir: str, builddir: str) -> str:
     def endswith(self, ending: str) -> bool:
         return self.fname.endswith(ending)
 
-    def split(self, s: str) -> List[str]:
+    def split(self, s: str) -> typing.List[str]:
         return self.fname.split(s)
 
     def __eq__(self, other) -> bool:
@@ -463,7 +462,7 @@ def is_dragonflybsd() -> bool:
 def is_freebsd() -> bool:
     return platform.system().lower() == 'freebsd'
 
-def exe_exists(arglist: List[str]) -> bool:
+def exe_exists(arglist: typing.List[str]) -> bool:
     try:
         if subprocess.run(arglist, timeout=10).returncode == 0:
             return True
@@ -574,7 +573,7 @@ def __cmp(self, other, comparator):
         # otherwise, the version with a suffix remaining is greater
         return comparator(len(self._v), len(other._v))
 
-def _version_extract_cmpop(vstr2):
+def _version_extract_cmpop(vstr2: str) -> typing.Tuple[typing.Callable[[typing.Any, typing.Any], bool], str]:
     if vstr2.startswith('>='):
         cmpop = operator.ge
         vstr2 = vstr2[2:]
@@ -601,7 +600,7 @@ def _version_extract_cmpop(vstr2):
 
     return (cmpop, vstr2)
 
-def version_compare(vstr1, vstr2):
+def version_compare(vstr1: str, vstr2: str) -> bool:
     (cmpop, vstr2) = _version_extract_cmpop(vstr2)
     return cmpop(Version(vstr1), Version(vstr2))
 
@@ -619,7 +618,7 @@ def version_compare_many(vstr1, conditions):
 
 # determine if the minimum version satisfying the condition |condition| exceeds
 # the minimum version for a feature |minimum|
-def version_compare_condition_with_min(condition, minimum):
+def version_compare_condition_with_min(condition: str, minimum: str) -> bool:
     if condition.startswith('>='):
         cmpop = operator.le
         condition = condition[2:]
@@ -682,7 +681,7 @@ def default_libexecdir():
 def default_prefix():
     return 'c:/' if is_windows() else '/usr/local'
 
-def get_library_dirs() -> List[str]:
+def get_library_dirs() -> typing.List[str]:
     if is_windows():
         return ['C:/mingw/lib'] # TODO: get programatically
     if is_osx():
diff --git a/run_unittests.py b/run_unittests.py
--- a/run_unittests.py
+++ b/run_unittests.py
@@ -279,6 +279,7 @@ class PatchModule:
     Fancy monkey-patching! Whee! Can't use mock.patch because it only
     patches in the local namespace.
     '''
+
     def __init__(self, func, name, impl):
         self.func = func
         assert(isinstance(name, str))
@@ -1483,6 +1484,7 @@ class AllPlatformTests(BasePlatformTests):
     '''
     Tests that should run on all platforms
     '''
+
     def test_default_options_prefix(self):
         '''
         Tests that setting a prefix in default_options in project() works.
@@ -4042,6 +4044,7 @@ class WindowsTests(BasePlatformTests):
     '''
     Tests that should run on Cygwin, MinGW, and MSVC
     '''
+
     def setUp(self):
         super().setUp()
         self.platform_test_dir = os.path.join(self.src_root, 'test cases/windows')
@@ -4156,6 +4159,7 @@ class DarwinTests(BasePlatformTests):
     '''
     Tests that should run on macOS
     '''
+
     def setUp(self):
         super().setUp()
         self.platform_test_dir = os.path.join(self.src_root, 'test cases/osx')
@@ -4255,6 +4259,7 @@ class LinuxlikeTests(BasePlatformTests):
     '''
     Tests that should run on Linux, macOS, and *BSD
     '''
+
     def test_basic_soname(self):
         '''
         Test that the soname is set correctly for shared libraries. This can't
@@ -4543,20 +4548,32 @@ def test_compiler_check_flags_order(self):
             Oargs = [arg for arg in cmd if arg.startswith('-O')]
             self.assertEqual(Oargs, [Oflag, '-O0'])
 
-    def _test_stds_impl(self, testdir, compiler, p):
+    def _test_stds_impl(self, testdir, compiler, p: str):
         lang_std = p + '_std'
-        # Check that all the listed -std=xxx options for this compiler work
-        # just fine when used
+
+        has_cpp17 = (compiler.get_id() not in {'clang', 'gcc'} or
+                     compiler.get_id() == 'clang' and _clang_at_least(compiler, '>=5.0.0', '>=9.1') or
+                     compiler.get_id() == 'gcc' and version_compare(compiler.version, '>=5.0.0'))
+        has_cpp2a_c17 = (compiler.get_id() not in {'clang', 'gcc'} or
+                         compiler.get_id() == 'clang' and _clang_at_least(compiler, '>=6.0.0', '>=10.0') or
+                         compiler.get_id() == 'gcc' and version_compare(compiler.version, '>=8.0.0'))
+        has_c18 = (compiler.get_id() not in {'clang', 'gcc'} or
+                   compiler.get_id() == 'clang' and _clang_at_least(compiler, '>=8.0.0', '>=11.0') or
+                   compiler.get_id() == 'gcc' and version_compare(compiler.version, '>=8.0.0'))
+        # Check that all the listed -std=xxx options for this compiler work just fine when used
+        # https://en.wikipedia.org/wiki/Xcode#Latest_versions
+        # https://www.gnu.org/software/gcc/projects/cxx-status.html
         for v in compiler.get_options()[lang_std].choices:
-            if (compiler.get_id() == 'clang' and '17' in v and
-                (version_compare(compiler.version, '<5.0.0') or
-                 (compiler.compiler_type == mesonbuild.compilers.CompilerType.CLANG_OSX and version_compare(compiler.version, '<9.1')))):
+            # we do it like this to handle gnu++17,c++17 and gnu17,c17 cleanly
+            # thus, C++ first
+            if '++17' in v and not has_cpp17:
                 continue
-            if (compiler.get_id() == 'clang' and '2a' in v and
-                (version_compare(compiler.version, '<6.0.0') or
-                 (compiler.compiler_type == mesonbuild.compilers.CompilerType.CLANG_OSX and version_compare(compiler.version, '<9.1')))):
+            elif '++2a' in v and not has_cpp2a_c17:  # https://en.cppreference.com/w/cpp/compiler_support
                 continue
-            if (compiler.get_id() == 'gcc' and '2a' in v and version_compare(compiler.version, '<8.0.0')):
+            # now C
+            elif '17' in v and not has_cpp2a_c17:
+                continue
+            elif '18' in v and not has_c18:
                 continue
             std_opt = '{}={}'.format(lang_std, v)
             self.init(testdir, extra_args=['-D' + std_opt])
@@ -5313,6 +5330,7 @@ class LinuxCrossArmTests(BasePlatformTests):
     '''
     Tests that cross-compilation to Linux/ARM works
     '''
+
     def setUp(self):
         super().setUp()
         src_root = os.path.dirname(__file__)
@@ -5372,6 +5390,7 @@ class LinuxCrossMingwTests(BasePlatformTests):
     '''
     Tests that cross-compilation to Windows/MinGW works
     '''
+
     def setUp(self):
         super().setUp()
         src_root = os.path.dirname(__file__)
@@ -5425,6 +5444,7 @@ class PythonTests(BasePlatformTests):
     '''
     Tests that verify compilation of python extension modules
     '''
+
     def test_versions(self):
         if self.backend is not Backend.ninja:
             raise unittest.SkipTest('Skipping python tests with {} backend'.format(self.backend.name))
@@ -6469,6 +6489,30 @@ def test_yaml(self):
         self.assert_test(events, number=2, name='', result=TestResult.FAIL)
         self.assert_last(events)
 
+
+def _clang_at_least(compiler, minver: str, apple_minver: str) -> bool:
+    """
+    check that Clang compiler is at least a specified version, whether AppleClang or regular Clang
+
+    Parameters
+    ----------
+    compiler:
+        Meson compiler object
+    minver: str
+        Clang minimum version
+    apple_minver: str
+        AppleCLang minimum version
+
+    Returns
+    -------
+    at_least: bool
+        Clang is at least the specified version
+    """
+    if compiler.compiler_type == mesonbuild.compilers.CompilerType.CLANG_OSX:
+        return version_compare(compiler.version, apple_minver)
+    return version_compare(compiler.version, minver)
+
+
 def unset_envs():
     # For unit tests we must fully control all command lines
     # so that there are no unexpected changes coming from the


Test patch: