Repository: mesonbuild/meson

Issue:
0.50 regression: Default option c_std not applied when cross-compiling
When providing `default_options: ['c_std=c99']` to the `project`, it is not being applied on `meson build --cross-file <cross-file>` invocation on a simple test project using flexible array members:

`ninja -v`

> [1/2] cc -Itest-executable@exe -I. -I../../work -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -g -pedantic -MD -MQ 'test-executable@exe/main.c.o' -MF 'test-executable@exe/main.c.o.d' -o 'test-executable@exe/main.c.o' -c ../../work/main.c
../../work/main.c:5:9: warning: ISO C90 does not support flexible array members [-Wpedantic]
     int array[];
         ^
[2/2] cc  -o test-executable 'test-executable@exe/main.c.o' -Wl,--no-undefined -Wl,--as-needed

However, after doing any kind of (even unrelated) configuration such as `meson configure -Db_asneeded=true`, the configuration is being fixed and applies as expected:

`ninja -v`

> [1/2] cc -Itest-executable@exe -I. -I../../work -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch **-std=c99** -g -pedantic -MD -MQ 'test-executable@exe/main.c.o' -MF 'test-executable@exe/main.c.o.d' -o 'test-executable@exe/main.c.o' -c ../../work/main.c
[2/2] cc  -o test-executable 'test-executable@exe/main.c.o' -Wl,--no-undefined -Wl,--as-needed

Note: I haven't tested whether this also applies to other default options.

---

#### main.c

```c
#include <stdio.h>

struct a {
    int foo;
    int array[];
};

int main(int argc, char* argv[]) {
    printf("Hey!\n");
    return 0;
}
```

#### meson.build

```meson
project('test-project', 'c', version: '0.0.1', default_options: ['c_std=c99'])
add_project_arguments('-pedantic', language: 'c')
executable('test-executable', 'main.c')
```

#### cross-file

```
[host_machine]
system = 'linux'
cpu_family = 'x86_64'
cpu = 'x86_64'
endian = 'little'

[properties]
root = '/usr/android-ndk/toolchains/llvm/prebuilt/linux-x86_64'
needs_exe_wrapper = true

[paths]
prefix = '/usr/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/sysroot'

[binaries]
c = '/usr/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android28-clang'
cpp = '/usr/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android28-clang++'
ar = '/usr/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android-ar'
as = '/usr/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android-as'
ld = '/usr/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/ld.lld'
strip = '/usr/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android-strip'
pkgconfig = '/usr/bin/pkg-config'
```


Discussion:
What's also weird is that it's trying to use native `cc` to compile instead of using the cross-compiler. I've opened a new issue for that (#5102). However, the above described issue also happens when cross-compiling for ARM (using the appropriate cross-compiler).
I have the same issue. (with cpp_std).
This is a regression with `0.50.0`. `0.49.2` correctly pass the option (`-std=c++11` in my case) to the compiler.

It seems also that meson doesn't append `LDFLAGS` and `CPPFLAGS` from environment for c and cpp compiler. (Also a regression, 0.49.2 works)
I can also confirm that it works with 0.49.2.
I'm seeing the same thing here. The fist broken commit is 19f81d3e33c70c9c902dabaad732e5d33bf05bd4.
CCing @Ericson2314 
Hmm so this option affected native and cross compilation previously? That is fine, but inconsistent with the other options. I'll take a look.
I'm not sure what you think should happen but systemd, mesa and libdrm set `c_std` in the `default_options` and with the current meson release the all fail to cross-compile with a compiler that uses an older standard by default.
@michaelolbrich for comparison what does `c_args` or `c_link_args` do in `default_options`, and what do all 3 do as `-D` cli args to meson?

(I'm in transit so can't check yet. Not just asking rhetorically :). )
As soon as I use `--cross-file`, `c_args` `c_link_args` and `c_std` are all ignored. For `default_options` and as `-D` cli args.
@michaelolbrich Thanks! That is the new behavior?
For `c_std` that's new. For `c_args` and `c_link_args` this was also the case in `0.49.0`.

I'm not sure what `c_args` is supposed to be used for but the correct C standard depends on the code that is compiled. Cross-compiling or not should not have an effect for this.

And after doing some testing, I must say that it is very confusing that command line options don't have any effect.
Yes it is confusing. So the regession was due to me trying to match the status quo in a pure refactor, but not realizing `c_std` worked differently. What I actually want is #4963. This would solve your issue completely in that `default_options` works reliably whether or not the user is cross or native compiling.
Could somebody assign this to me too?
I debugged why this happens. In `get_compiler_options_for_target` an `OverrideProxy` is created and it overrides `c_std` to be `none`. It should not do that since `c_std` is not a per-machine setting. It should be the same for native and cross compilation (at least for now to preserve backwards compatibility).

Maybe once we get `cross_c_args` et al we could also have a `cross_c_std`, but I'm not sure even that is necessary.
@jpakkane So right now all compiler options are per-machine, since compilers target a single machine (as far as meson knows). The solution I think is when reading in the file to make a temporary hack to duplicate this one. 

> Maybe once we get cross_c_args et al we could also have a cross_c_std, but I'm not sure even that is necessary.

I have done GCC native, Clang cross builds where the clang might support a newer standard.

Resolving patch:
diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py
--- a/mesonbuild/coredata.py
+++ b/mesonbuild/coredata.py
@@ -592,7 +592,13 @@ def process_new_compilers(self, lang: str, comp, cross_comp, env):
                         k in env.cmd_line_options):
                     # TODO think about cross and command-line interface.
                     o.set_value(env.cmd_line_options[k])
-                self.compiler_options[for_machine].setdefault(k, o)
+                if k in ['c_std', 'cpp_std']:
+                    # These CLI options should affect all platforms
+                    for m in iter(MachineChoice):
+                        self.compiler_options[m].setdefault(k, o)
+                else:
+                    # By default, CLI options just affect the native platform
+                    self.compiler_options[for_machine].setdefault(k, o)
 
             # Unlike compiler and linker flags, preprocessor flags are not in
             # compiler_options because they are not visible to user.
diff --git a/run_unittests.py b/run_unittests.py
--- a/run_unittests.py
+++ b/run_unittests.py
@@ -5043,6 +5043,14 @@ def test_identity_cross(self):
         # TODO should someday be explicit about build platform only here
         self.init(testdir)
 
+    def test_std_remains(self):
+        # C_std defined in project options must be in effect also when native compiling.
+        testdir = os.path.join(self.unit_test_dir, '50 std remains')
+        self.init(testdir)
+        compdb = self.get_compdb()
+        self.assertRegex(compdb[0]['command'], '-std=c99')
+        self.build()
+
 def should_run_cross_arm_tests():
     return shutil.which('arm-linux-gnueabihf-gcc') and not platform.machine().lower().startswith('arm')
 
@@ -5094,6 +5102,14 @@ def test_cross_libdir(self):
                 return
         self.assertTrue(False, 'Option libdir not in introspect data.')
 
+    def test_std_remains(self):
+        # C_std defined in project options must be in effect also when cross compiling.
+        testdir = os.path.join(self.unit_test_dir, '50 std remains')
+        self.init(testdir)
+        compdb = self.get_compdb()
+        self.assertRegex(compdb[0]['command'], '-std=c99')
+        self.build()
+
 
 def should_run_cross_mingw_tests():
     return shutil.which('x86_64-w64-mingw32-gcc') and not (is_windows() or is_cygwin())


Test patch:
diff --git a/test cases/unit/50 std remains/meson.build b/test cases/unit/50 std remains/meson.build
new file mode 100644
--- /dev/null
+++ b/test cases/unit/50 std remains/meson.build	
@@ -0,0 +1,2 @@
+project('std_remains', 'c', default_options: ['c_std=c99'])
+executable('prog', 'prog.c')
diff --git a/test cases/unit/50 std remains/prog.c b/test cases/unit/50 std remains/prog.c
new file mode 100644
--- /dev/null
+++ b/test cases/unit/50 std remains/prog.c	
@@ -0,0 +1 @@
+int main(int argc, char **argv) { return 0; }