Repository: mesonbuild/meson

Issue:
`native` keyword arg semantics
In 4.8, the rules for the `native` kwarg were improved, but I still worry they are not good enough http://mesonbuild.com/Release-notes-for-0-48-0.html#projects-args-can-be-set-separately-for-cross-and-native-builds-potentially-breaking-change

I'll quote them here.
```meson
# Added to native builds when compiling natively and to cross
# compilations when doing cross compiles.
add_project_arguments(...)

# Added only to native compilations, not used in cross compilations.
add_project_arguments(..., native : true)

# Added only to cross compilations, not used in native compilations.
add_project_arguments(..., native : false)
```

The problem I have with this is that since the rules are written in terms of "native" and "cross", rather than "build machine" and "host machine", the build plan behaves unnecessarily differently in native cross builds. For example, say we have something like:
```meson
executable('code-gen-bar', ..., native: true)
library('installed-foo', ..., native: false)
add_project_arguments('-Dfoo', .., native : true)
add_project_arguments('-Dbar', .., native : false)
```
In native builds, both targets get '-Dfoo' alone. In a cross build, `code-gen-bar` gets just `-Dfoo` and `installed-foo` gets just `-Dbar`.

I think this is no good: the `meson.build` author should be able to write the build plan and know which targets get which flags *regardless* of how the user parameterized the build. This means they have fewer possibilities to think about, test, etc. Also, I think the cross interpretation is better than the native ones: even in native builds the final installed build products and throwaway code generators rarely need the same flags. And if they *really* do, one can just call  `add_XXX_arguments` twice.

So what I propose instead is that:
```meson
# Added to native builds when compiling natively and to cross
# compilations when doing cross compiles.
add_project_arguments(...)

# Added only to compilations for the build platform, i.e. those that
# also have `native: true`. 
add_project_arguments(..., native : true)

# Added only to compilations for the host platform, i.e. those that
# also have `native: false`, or no `native` kwarg at all
# (`native: false` is the default).
add_project_arguments(..., native : false)
```
Not only are the affected targets affected by each `add_XXX_args` call always, but the rules are also purely syntactic: you don't even need to know what "build platform" and "host platform" mean, but just make sure the `native` kwargs match.

Here's the thing. Whenever the cross and native build plans differ, usually cross is adversely affected because it's a secondary priority. Rarely are both as tested, and rarely due maintainers worry about both equally. By keeping the differences to a minimum, the cross build is less likely to bit-rot while the native build works. And if the cross build does fail, the native build probably fails too. By "tying together their fates", so to speak, the one with less attention gets to free ride on the maintained of the other without the more attended one feeling the burden.

CC @dcbaker @textshell @ePirat

-----

`add_project_arguments` could also be changed to always affect all targets like before for the same reason of consistency, but I don't thinks worth it. Anyone that cares about cross can just be sure they always use `native` kwargs in all cases.

-----
 
For the record, this relates to what I wrote in https://github.com/mesonbuild/meson/pull/3921#issuecomment-411232178 but uses the exist `native` flag. If/when we support building for the target platform, we can come up with a new kwarg along those lines, but this is fine for now.


Discussion:
Also for the record, this is the exact same philosophy for `CC` vs `CC_FOR_BUILD` in https://github.com/mesonbuild/meson/issues/3969 . Define everything in terms of build platform vs host platform, not native vs cross, and make sure all actions affect the same targets regardless of the overall type of build.

Resolving patch:
diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py
--- a/mesonbuild/interpreter.py
+++ b/mesonbuild/interpreter.py
@@ -3829,47 +3829,29 @@ def func_add_test_setup(self, node, args, kwargs):
         env = self.unpack_env_kwarg(kwargs)
         self.build.test_setups[setup_name] = build.TestSetup(exe_wrapper, gdb, timeout_multiplier, env)
 
-    # TODO make cross agnostic, just taking into account for_machine
-    # TODO PerMachine[T], Iterator[T]
-    def get_argdict_on_crossness(self, dicts_per_machine: PerMachine, kwargs) -> Iterator:
-        for_native = kwargs.get('native', not self.environment.is_cross_build())
-        if not isinstance(for_native, bool):
-            raise InterpreterException('Keyword native must be a boolean.')
-        if self.environment.is_cross_build():
-            if for_native:
-                return iter([dicts_per_machine[MachineChoice.BUILD]])
-            else:
-                return iter([dicts_per_machine[MachineChoice.HOST]])
-        else:
-            if for_native:
-                return iter([dicts_per_machine[MachineChoice.BUILD],
-                             dicts_per_machine[MachineChoice.HOST]])
-            else:
-                return iter([])
-
     @permittedKwargs(permitted_kwargs['add_global_arguments'])
     @stringArgs
     def func_add_global_arguments(self, node, args, kwargs):
-        for argdict in self.get_argdict_on_crossness(self.build.global_args, kwargs):
-            self.add_global_arguments(node, argdict, args, kwargs)
+        for_machine = self.machine_from_native_kwarg(kwargs)
+        self.add_global_arguments(node, self.build.global_args[for_machine], args, kwargs)
 
     @permittedKwargs(permitted_kwargs['add_global_link_arguments'])
     @stringArgs
     def func_add_global_link_arguments(self, node, args, kwargs):
-        for argdict in self.get_argdict_on_crossness(self.build.global_link_args, kwargs):
-            self.add_global_arguments(node, argdict, args, kwargs)
+        for_machine = self.machine_from_native_kwarg(kwargs)
+        self.add_global_arguments(node, self.build.global_link_args[for_machine], args, kwargs)
 
     @permittedKwargs(permitted_kwargs['add_project_arguments'])
     @stringArgs
     def func_add_project_arguments(self, node, args, kwargs):
-        for argdict in self.get_argdict_on_crossness(self.build.projects_args, kwargs):
-            self.add_project_arguments(node, argdict, args, kwargs)
+        for_machine = self.machine_from_native_kwarg(kwargs)
+        self.add_project_arguments(node, self.build.projects_args[for_machine], args, kwargs)
 
     @permittedKwargs(permitted_kwargs['add_project_link_arguments'])
     @stringArgs
     def func_add_project_link_arguments(self, node, args, kwargs):
-        for argdict in self.get_argdict_on_crossness(self.build.projects_link_args, kwargs):
-            self.add_project_arguments(node, argdict, args, kwargs)
+        for_machine = self.machine_from_native_kwarg(kwargs)
+        self.add_project_arguments(node, self.build.projects_link_args[for_machine], args, kwargs)
 
     def add_global_arguments(self, node, argsdict, args, kwargs):
         if self.is_subproject():


Test patch:
diff --git a/test cases/common/21 global arg/meson.build b/test cases/common/21 global arg/meson.build
--- a/test cases/common/21 global arg/meson.build	
+++ b/test cases/common/21 global arg/meson.build	
@@ -1,22 +1,23 @@
 project('global arg test', 'cpp', 'c')
 
-add_global_arguments('-DMYTHING', language : 'c')
-add_global_arguments('-DMYCPPTHING', language : 'cpp')
+add_global_arguments('-DMYTHING', language : 'c', native : true)
+add_global_arguments('-DMYTHING', language : 'c', native : false)
+add_global_arguments('-DMYCPPTHING', language : 'cpp', native : true)
+add_global_arguments('-DMYCPPTHING', language : 'cpp', native : false)
 
-add_global_arguments('-DGLOBAL_NATIVE', language : 'c', native : true)
-add_global_arguments('-DGLOBAL_CROSS', language : 'c', native : false)
+add_global_arguments('-DGLOBAL_BUILD', language : 'c', native : true)
+add_global_arguments('-DGLOBAL_HOST', language : 'c', native : false)
 
-if meson.is_cross_build()
-  c_args = ['-DARG_CROSS']
-else
-  c_args = ['-DARG_NATIVE']
-endif
+build_c_args = ['-DARG_BUILD']
+c_args = ['-DARG_HOST']
 
-add_global_arguments('-DMYCANDCPPTHING', language: ['c', 'cpp'])
+add_global_arguments('-DMYCANDCPPTHING', language: ['c', 'cpp'], native: true)
+add_global_arguments('-DMYCANDCPPTHING', language: ['c', 'cpp'], native: false)
 
-exe1 = executable('prog', 'prog.c', c_args : c_args)
-exe2 = executable('prog2', 'prog.cc')
+exe1 = executable('prog1', 'prog.c', c_args : build_c_args, native : true)
+exe2 = executable('prog2', 'prog.c', c_args : c_args, native : false)
+exe3 = executable('prog3', 'prog.cc')
 
 test('prog1', exe1)
 test('prog2', exe2)
-
+test('prog3', exe3)
diff --git a/test cases/common/21 global arg/prog.c b/test cases/common/21 global arg/prog.c
--- a/test cases/common/21 global arg/prog.c	
+++ b/test cases/common/21 global arg/prog.c	
@@ -1,48 +1,40 @@
 #ifndef MYTHING
-#error "Global argument not set"
+  #error "Global argument not set"
 #endif
 
 #ifdef MYCPPTHING
-#error "Wrong global argument set"
+  #error "Wrong global argument set"
 #endif
 
 #ifndef MYCANDCPPTHING
-#error "Global argument not set"
+  #error "Global argument not set"
 #endif
 
-#ifdef GLOBAL_NATIVE
-  #ifndef ARG_NATIVE
-    #error "Global is native but arg_native is not set."
-  #endif
+#if !defined(GLOBAL_HOST) && !defined(GLOBAL_BUILD)
+  #error "Neither global_host nor glogal_build is set."
+#endif
 
-  #ifdef GLOBAL_CROSS
-    #error "Both global native and global cross set."
-  #endif
-#else
-  #ifndef GLOBAL_CROSS
-    #error "Neither global_cross nor glogal_native is set."
-  #endif
+#if defined(GLOBAL_HOST) && defined(GLOBAL_BUILD)
+  #error "Both global build and global host set."
+#endif
 
-  #ifndef ARG_CROSS
-    #error "Global is cross but arg_cross is not set."
+#ifdef GLOBAL_BUILD
+  #ifndef ARG_BUILD
+    #error "Global is build but arg_build is not set."
   #endif
 
-  #ifdef ARG_NATIVE
-    #error "Global is cross but arg_native is set."
+  #ifdef ARG_HOST
+    #error "Global is build but arg host is set."
   #endif
 #endif
 
-#ifdef GLOBAL_CROSS
-  #ifndef ARG_CROSS
-    #error "Global is cross but arg_cross is not set."
-  #endif
-#else
-  #ifdef ARG_CROSS
-    #error "Global is cross but arg_native is set."
+#ifdef GLOBAL_HOST
+  #ifndef ARG_HOST
+    #error "Global is host but arg_host is not set."
   #endif
 
-  #ifdef ARG_CROSS
-    #error "Global is native but arg cross is set."
+  #ifdef ARG_BUILD
+    #error "Global is host but arg_build is set."
   #endif
 #endif