Repository: mesonbuild/meson

Issue:
c_std and cpp_std are not honored when compiling Objective-C/C++ files
```
$ echo 'int main(void){return 0;}' > x.m
$ cat > meson.build
project('test', 'objc', default_options: ['c_std=c99'])
executable('testexe', 'x.m')
```

If you set this up and build with `ninja -v`, you'll see

```
[1/2] cc -Itestexe@exe -I. -I.. -pipe -Wall -Winvalid-pch -g -MD -MQ 'testexe@exe/x.m.o' -MF 'testexe@exe/x.m.o.d' -o 'testexe@exe/x.m.o' -c ../x.m
```

Notice how the c99 flag is not properly applied.

I see `objc_args` was a thing, but changing to `objc_std` does not fix it.

The same happens with `cpp_std` and Objective-C++. This is causing build errors for me as I'm using C++11-only features. (And that project also uses `add_language()` to add `objc` and `objcpp` on macOS only.)

Currently using meson 0.50.1 (through MacPorts) on macOS Sierra with Xcode 9.2.


Discussion:
Oh, forgot to mention: I didn't notice this before because apparently my version of clang automatically defaults to C99 for Objective-C code, but it appears not to for Objective-C++, or even to C++11 (maybe it does to C++03? IDK).
Still happening on 0.51.0. Here's an Objective-C++ reproducer.

```
pietros-mbp:mesonobjcpptest pietro$ echo 'int main(void){return ([]()->int{return 0;})();}' > x.mm
pietros-mbp:mesonobjcpptest pietro$ cat > meson.build
project('test', 'objcpp', default_options: ['cpp_std=c++11'])
executable('testexe', 'x.mm')
```

And the output:

```
pietros-mbp:mesonobjcpptest pietro$ meson setup build
The Meson build system
Version: 0.51.0
Source dir: /private/tmp/mesonobjcpptest
Build dir: /private/tmp/mesonobjcpptest/build
Build type: native build
Project name: test
Project version: undefined
Objective-C++ compiler for the build machine: c++ (clang 9.0.0)
Objective-C++ compiler for the host machine: c++ (clang 9.0.0)
Build machine cpu family: x86_64
Build machine cpu: x86_64
Build targets in project: 1
Found ninja-1.9.0 at /opt/local/bin/ninja
pietros-mbp:mesonobjcpptest pietro$ ninja -v -C build
ninja: Entering directory `build'
[1/2] c++ -Itestexe@exe -I. -I.. -pipe -Wall -Winvalid-pch -Wnon-virtual-dtor -g -MD -MQ 'testexe@exe/x.mm.o' -MF 'testexe@exe/x.mm.o.d' -o 'testexe@exe/x.mm.o' -c ../x.mm
FAILED: testexe@exe/x.mm.o 
c++ -Itestexe@exe -I. -I.. -pipe -Wall -Winvalid-pch -Wnon-virtual-dtor -g -MD -MQ 'testexe@exe/x.mm.o' -MF 'testexe@exe/x.mm.o.d' -o 'testexe@exe/x.mm.o' -c ../x.mm
../x.mm:1:25: error: expected expression
int main(void){return ([]()->int{return 0;})();}
                        ^
../x.mm:1:30: error: expected unqualified-id
int main(void){return ([]()->int{return 0;})();}
                             ^
2 errors generated.
ninja: build stopped: subcommand failed.
```

And using `objcpp_std` has no effect.
If you run `meson configure` in the build dir, does it print an entry for `objcpp_std`?
Nope. All that shows up are

```
  objcpp_args      []            Extra arguments passed to the Objective-C++ compiler
  objcpp_link_args []            Extra arguments passed to the Objective-C++ linker  
```
Seems like a bug. Either we should make `objc<X>_std` match the corresponding ´<X>_std` or add a std option for Objective-C/C++. Do Objective-C/C++ standards always match the corresponding C/C++ standards or do they have their own version strings? And if yes, is there ever a need to compile Objective-C with a different language version than plain C (and the same for C++)?
Not really; Objective-C and Objective-C++ themselves don't really have concrete compile-time versions, and just inherit whatever versions of C and C++ you ask for. The ideal approach would be to just reuse `c_std` and `cpp_std` with that in mind, but I'll ask a bunch of other Mac coders to comment as well.
@andlabs I would agree that using `c_std` and `cpp_std` seems like what I would expect to happen.

Resolving patch:
diff --git a/mesonbuild/compilers/objc.py b/mesonbuild/compilers/objc.py
--- a/mesonbuild/compilers/objc.py
+++ b/mesonbuild/compilers/objc.py
@@ -14,7 +14,8 @@
 
 import typing as T
 
-from ..mesonlib import MachineChoice
+from .. import coredata
+from ..mesonlib import MachineChoice, OptionKey
 
 from .compilers import Compiler
 from .mixins.clike import CLikeCompiler
@@ -84,6 +85,23 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic
                           '2': default_warn_args + ['-Wextra'],
                           '3': default_warn_args + ['-Wextra', '-Wpedantic']}
 
+    def get_options(self) -> 'coredata.KeyedOptionDictType':
+        opts = super().get_options()
+        opts.update({
+            OptionKey('std', machine=self.for_machine, lang='c'): coredata.UserComboOption(
+                'C language standard to use',
+                ['none', 'c89', 'c99', 'c11', 'c17'],
+                'none',
+            )
+        })
+        return opts
+
+    def get_option_compile_args(self, options: 'coredata.KeyedOptionDictType') -> T.List[str]:
+        args = []
+        std = options[OptionKey('std', machine=self.for_machine, lang='c')]
+        if std.value != 'none':
+            args.append('-std=' + std.value)
+        return args
 
 class AppleClangObjCCompiler(ClangObjCCompiler):
 
diff --git a/mesonbuild/compilers/objcpp.py b/mesonbuild/compilers/objcpp.py
--- a/mesonbuild/compilers/objcpp.py
+++ b/mesonbuild/compilers/objcpp.py
@@ -14,7 +14,8 @@
 
 import typing as T
 
-from ..mesonlib import MachineChoice
+from .. import coredata
+from ..mesonlib import MachineChoice, OptionKey
 
 from .mixins.clike import CLikeCompiler
 from .compilers import Compiler
@@ -85,6 +86,24 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic
                           '3': default_warn_args + ['-Wextra', '-Wpedantic']}
 
 
+    def get_options(self) -> 'coredata.KeyedOptionDictType':
+        opts = super().get_options()
+        opts.update({
+            OptionKey('std', machine=self.for_machine, lang='cpp'): coredata.UserComboOption(
+                'C++ language standard to use',
+                ['none', 'c++98', 'c++11', 'c++14', 'c++17'],
+                'none',
+            )
+        })
+        return opts
+
+    def get_option_compile_args(self, options: 'coredata.KeyedOptionDictType') -> T.List[str]:
+        args = []
+        std = options[OptionKey('std', machine=self.for_machine, lang='cpp')]
+        if std.value != 'none':
+            args.append('-std=' + std.value)
+        return args
+
 
 class AppleClangObjCPPCompiler(ClangObjCPPCompiler):
 
diff --git a/run_unittests.py b/run_unittests.py
--- a/run_unittests.py
+++ b/run_unittests.py
@@ -1725,6 +1725,9 @@ def setUp(self):
         self.unit_test_dir = os.path.join(src_root, 'test cases/unit')
         self.rewrite_test_dir = os.path.join(src_root, 'test cases/rewrite')
         self.linuxlike_test_dir = os.path.join(src_root, 'test cases/linuxlike')
+        self.objc_test_dir = os.path.join(src_root, 'test cases/objc')
+        self.objcpp_test_dir = os.path.join(src_root, 'test cases/objcpp')
+
         # Misc stuff
         self.orig_env = os.environ.copy()
         if self.backend is Backend.ninja:
@@ -6452,6 +6455,18 @@ def test_removing_unused_linker_args(self):
         env = {'CFLAGS': '-L/tmp -L /var/tmp -headerpad_max_install_names -Wl,-export_dynamic -framework Foundation'}
         self.init(testdir, override_envvars=env)
 
+    def test_objc_versions(self):
+        # Objective-C always uses the C standard version.
+        # Objecttive-C++ always uses the C++ standard version.
+        # This is what most people seem to want and in addition
+        # it is the only setup supported by Xcode.
+        testdir = os.path.join(self.objc_test_dir, '1 simple')
+        self.init(testdir)
+        self.assertIn('-std=c99', self.get_compdb()[0]['command'])
+        self.wipe()
+        testdir = os.path.join(self.objcpp_test_dir, '1 simple')
+        self.init(testdir)
+        self.assertIn('-std=c++14', self.get_compdb()[0]['command'])
 
 @unittest.skipUnless(not is_windows(), "requires something Unix-like")
 class LinuxlikeTests(BasePlatformTests):


Test patch:
diff --git a/test cases/objc/1 simple/meson.build b/test cases/objc/1 simple/meson.build
--- a/test cases/objc/1 simple/meson.build	
+++ b/test cases/objc/1 simple/meson.build	
@@ -1,4 +1,4 @@
-project('objective c', 'objc')
+project('objective c', 'objc', default_options: ['c_std=c99'])
 
 exe = executable('prog', 'prog.m')
 test('objctest', exe)
diff --git a/test cases/objcpp/1 simple/meson.build b/test cases/objcpp/1 simple/meson.build
--- a/test cases/objcpp/1 simple/meson.build	
+++ b/test cases/objcpp/1 simple/meson.build	
@@ -1,4 +1,4 @@
-project('Objective C++', 'objcpp')
+project('Objective C++', 'objcpp', default_options: 'cpp_std=c++14')
 
 exe = executable('objcppprog', 'prog.mm')
 test('objcpp', exe)