Repository: mesonbuild/meson

Issue:
Cannot compile spice when cross compiling
**Describe the bug**
Worked under 0.56.2. Broke with 0.57

**To Reproduce**
https://gitlab.freedesktop.org/spice/spice/-/blob/master/meson.build I guess.

**Expected behavior**
Compile

**system parameters**
* cross build
* Fedora 33
* Python 3.9.2
* latest master - 0.57.1 tested
* 1.10.2

Sample log: https://downloads.openwrt.org/snapshots/faillogs/mips64_octeonplus/packages/spice/compile.txt



Discussion:
What's your cross files look like?
```
[binaries]
c = ['ccache_cc',]
cpp = ['ccache_cxx',]
ar = 'mips64el-openwrt-linux-musl-gcc-ar'
strip = 'mips64el-openwrt-linux-musl-strip'
nm = 'mips64el-openwrt-linux-musl-gcc-nm'
pkgconfig = '/home/mangix/devstuff/openwrt/staging_dir/host/bin/pkg-config'

[built-in options]
c_args = ['-Os', '-pipe', '-mno-branch-likely', '-mips64r2', '-mtune=mips64r2', '-mabi=64', '-fno-caller-saves', '-fno-plt', '-fhonour-copts', '-Wno-error=unused-but-set-variable', '-Wno-error=unused-result', '-msoft-float', '-fmacro-prefix-map=/home/mangix/devstuff/openwrt/build_dir/target-mips64el_mips64r2_64_musl/spice-0.14.91=spice-0.14.91', '-Wformat', '-Werror=format-security', '-fstack-protector', '-D_FORTIFY_SOURCE=1', '-Wl,-z,now', '-Wl,-z,relro', '-I/home/mangix/devstuff/openwrt/staging_dir/target-mips64el_mips64r2_64_musl/usr/lib/libiconv-full/include', '-I/home/mangix/devstuff/openwrt/staging_dir/target-mips64el_mips64r2_64_musl/usr/lib/libintl-full/include', '-I/home/mangix/devstuff/openwrt/staging_dir/toolchain-mips64el_mips64r2_64_gcc-10.2.0_musl/usr/include', '-I/home/mangix/devstuff/openwrt/staging_dir/toolchain-mips64el_mips64r2_64_gcc-10.2.0_musl/include', '-I/home/mangix/devstuff/openwrt/staging_dir/target-mips64el_mips64r2_64_musl/usr/lib/libiconv-full/include', '-I/home/mangix/devstuff/openwrt/staging_dir/target-mips64el_mips64r2_64_musl/usr/lib/libintl-full/include',]
c_link_args = ['-L/home/mangix/devstuff/openwrt/staging_dir/toolchain-mips64el_mips64r2_64_gcc-10.2.0_musl/usr/lib', '-L/home/mangix/devstuff/openwrt/staging_dir/toolchain-mips64el_mips64r2_64_gcc-10.2.0_musl/lib', '-znow', '-zrelro', '-L/home/mangix/devstuff/openwrt/staging_dir/target-mips64el_mips64r2_64_musl/usr/lib/libiconv-full/lib', '-Wl,-rpath-link=/home/mangix/devstuff/openwrt/staging_dir/target-mips64el_mips64r2_64_musl/usr/lib/libiconv-full/lib', '-L/home/mangix/devstuff/openwrt/staging_dir/target-mips64el_mips64r2_64_musl/usr/lib/libintl-full/lib', '-Wl,-rpath-link=/home/mangix/devstuff/openwrt/staging_dir/target-mips64el_mips64r2_64_musl/usr/lib/libintl-full/lib',]
cpp_args = ['-Os', '-pipe', '-mno-branch-likely', '-mips64r2', '-mtune=mips64r2', '-mabi=64', '-fno-caller-saves', '-fno-plt', '-fhonour-copts', '-Wno-error=unused-but-set-variable', '-Wno-error=unused-result', '-msoft-float', '-fmacro-prefix-map=/home/mangix/devstuff/openwrt/build_dir/target-mips64el_mips64r2_64_musl/spice-0.14.91=spice-0.14.91', '-Wformat', '-Werror=format-security', '-fstack-protector', '-D_FORTIFY_SOURCE=1', '-Wl,-z,now', '-Wl,-z,relro', '-I/home/mangix/devstuff/openwrt/staging_dir/target-mips64el_mips64r2_64_musl/usr/lib/libiconv-full/include', '-I/home/mangix/devstuff/openwrt/staging_dir/target-mips64el_mips64r2_64_musl/usr/lib/libintl-full/include', '-I/home/mangix/devstuff/openwrt/staging_dir/toolchain-mips64el_mips64r2_64_gcc-10.2.0_musl/usr/include', '-I/home/mangix/devstuff/openwrt/staging_dir/toolchain-mips64el_mips64r2_64_gcc-10.2.0_musl/include', '-I/home/mangix/devstuff/openwrt/staging_dir/target-mips64el_mips64r2_64_musl/usr/lib/libiconv-full/include', '-I/home/mangix/devstuff/openwrt/staging_dir/target-mips64el_mips64r2_64_musl/usr/lib/libintl-full/include',]
cpp_link_args = ['-L/home/mangix/devstuff/openwrt/staging_dir/toolchain-mips64el_mips64r2_64_gcc-10.2.0_musl/usr/lib', '-L/home/mangix/devstuff/openwrt/staging_dir/toolchain-mips64el_mips64r2_64_gcc-10.2.0_musl/lib', '-znow', '-zrelro', '-L/home/mangix/devstuff/openwrt/staging_dir/target-mips64el_mips64r2_64_musl/usr/lib/libiconv-full/lib', '-Wl,-rpath-link=/home/mangix/devstuff/openwrt/staging_dir/target-mips64el_mips64r2_64_musl/usr/lib/libiconv-full/lib', '-L/home/mangix/devstuff/openwrt/staging_dir/target-mips64el_mips64r2_64_musl/usr/lib/libintl-full/lib', '-Wl,-rpath-link=/home/mangix/devstuff/openwrt/staging_dir/target-mips64el_mips64r2_64_musl/usr/lib/libintl-full/lib',]
prefix = '/usr'

[host_machine]
system = 'linux'
cpu_family = 'mips64'
cpu = 'generic)'
endian = 'little'

[properties]
needs_exe_wrapper = true
```
I just noticed that cpu typo. Fixing it makes no difference.
I can reproduce this, do you happen to be using a native file as well?

Resolving patch:
diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py
--- a/mesonbuild/coredata.py
+++ b/mesonbuild/coredata.py
@@ -759,7 +759,11 @@ def set_default_options(self, default_options: T.MutableMapping[OptionKey, str],
             if k.subproject and k.subproject != subproject:
                 continue
             # If the option is a builtin and is yielding then it's not allowed per subproject.
-            if subproject and k.is_builtin() and self.options[k.as_root()].yielding:
+            #
+            # Always test this using the HOST machine, as many builtin options
+            # are not valid for the BUILD machine, but the yielding value does
+            # not differ between them even when they are valid for both.
+            if subproject and k.is_builtin() and self.options[k.evolve(subproject='', machine=MachineChoice.HOST)].yielding:
                 continue
             # Skip base, compiler, and backend options, they are handled when
             # adding languages and setting backend.
diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py
--- a/mesonbuild/environment.py
+++ b/mesonbuild/environment.py
@@ -642,7 +642,9 @@ def __init__(self, source_dir: T.Optional[str], build_dir: T.Optional[str], opti
             binaries.build = BinaryTable(config.get('binaries', {}))
             properties.build = Properties(config.get('properties', {}))
             cmakevars.build = CMakeVariables(config.get('cmake', {}))
-            self._load_machine_file_options(config, properties.build, MachineChoice.BUILD)
+            self._load_machine_file_options(
+                config, properties.build,
+                MachineChoice.BUILD if self.coredata.cross_files else MachineChoice.HOST)
 
         ## Read in cross file(s) to override host machine configuration
 
@@ -661,12 +663,6 @@ def __init__(self, source_dir: T.Optional[str], build_dir: T.Optional[str], opti
                 if self.coredata.is_per_machine_option(key):
                     self.options[key.as_build()] = value
             self._load_machine_file_options(config, properties.host, MachineChoice.HOST)
-        else:
-            # IF we aren't cross compiling, but we hav ea native file, the
-            # native file is for the host. This is due to an mismatch between
-            # meson internals which talk about build an host, and external
-            # interfaces which talk about native and cross.
-            self.options = {k.as_host(): v for k, v in self.options.items()}
 
 
         ## "freeze" now initialized configuration, and "save" to the class.
@@ -749,12 +745,20 @@ def __init__(self, source_dir: T.Optional[str], build_dir: T.Optional[str], opti
 
     def _load_machine_file_options(self, config: 'ConfigParser', properties: Properties, machine: MachineChoice) -> None:
         """Read the contents of a Machine file and put it in the options store."""
+
+        # Look for any options in the deprecated paths section, warn about
+        # those, then assign them. They will be overwritten by the ones in the
+        # "built-in options" section if they're in both sections.
         paths = config.get('paths')
         if paths:
             mlog.deprecation('The [paths] section is deprecated, use the [built-in options] section instead.')
             for k, v in paths.items():
                 self.options[OptionKey.from_string(k).evolve(machine=machine)] = v
-        deprecated_properties = set()
+
+        # Next look for compiler options in the "properties" section, this is
+        # also deprecated, and these will also be overwritten by the "built-in
+        # options" section. We need to remove these from this section, as well.
+        deprecated_properties: T.Set[str] = set()
         for lang in compilers.all_languages:
             deprecated_properties.add(lang + '_args')
             deprecated_properties.add(lang + '_link_args')
@@ -763,6 +767,7 @@ def _load_machine_file_options(self, config: 'ConfigParser', properties: Propert
                 mlog.deprecation(f'{k} in the [properties] section of the machine file is deprecated, use the [built-in options] section.')
                 self.options[OptionKey.from_string(k).evolve(machine=machine)] = v
                 del properties.properties[k]
+
         for section, values in config.items():
             if ':' in section:
                 subproject, section = section.split(':')
@@ -777,9 +782,11 @@ def _load_machine_file_options(self, config: 'ConfigParser', properties: Propert
                     if key.subproject:
                         raise MesonException('Do not set subproject options in [built-in options] section, use [subproject:built-in options] instead.')
                     self.options[key.evolve(subproject=subproject, machine=machine)] = v
-            elif section == 'project options':
+            elif section == 'project options' and machine is MachineChoice.HOST:
+                # Project options are only for the host machine, we don't want
+                # to read these from the native file
                 for k, v in values.items():
-                    # Project options are always for the machine machine
+                    # Project options are always for the host machine
                     key = OptionKey.from_string(k)
                     if key.subproject:
                         raise MesonException('Do not set subproject options in [built-in options] section, use [subproject:built-in options] instead.')
diff --git a/run_unittests.py b/run_unittests.py
--- a/run_unittests.py
+++ b/run_unittests.py
@@ -9202,7 +9202,7 @@ def helper_create_cross_file(self, values):
             for section, entries in values.items():
                 f.write(f'[{section}]\n')
                 for k, v in entries.items():
-                    f.write(f"{k}='{v}'\n")
+                    f.write(f"{k}={v!r}\n")
         return filename
 
     def test_cross_file_dirs(self):
@@ -9368,6 +9368,22 @@ def test_for_build_env_vars(self) -> None:
                 break
         self.assertEqual(found, 2, 'Did not find all sections.')
 
+    def test_project_options_native_only(self) -> None:
+        # Do not load project options from a native file when doing a cross
+        # build
+        testcase = os.path.join(self.unit_test_dir, '19 array option')
+        config = self.helper_create_cross_file({'project options': {'list': ['bar', 'foo']}})
+        cross = self.helper_create_cross_file({'binaries': {}})
+
+        self.init(testcase, extra_args=['--native-file', config, '--cross-file', cross])
+        configuration = self.introspect('--buildoptions')
+        for each in configuration:
+            if each['name'] == 'list':
+                self.assertEqual(each['value'], ['foo', 'bar'])
+                break
+        else:
+            self.fail('Did not find expected option.')
+
 
 class TAPParserTests(unittest.TestCase):
     def assert_test(self, events, **kwargs):


Test patch: