Repository: mesonbuild/meson

Issue:
meson 1.8 ignores buildtype defined in native-file
I ran into an issue with one of my Conan recipes, but I was able to narrow it down and reproduce it directly with Meson.

> meson.build

```
project('example', 'c',  default_options: ['buildtype=debugoptimized'])
message('Buildtype: ' + get_option('buildtype'))
```


> toolchain.ini

```[built-in options]
buildtype = 'debug'
```

When I now run `meson --native-file=toolchain.ini builddir`
Meson 1.8.0 outputs the message
> Message: Buildtype: debugoptimized

In contrast, Meson 1.7.2 shows
> Message: Buildtype: release

From my perspective, the output in version 1.8.0 is unexpected, and it seems possible that this change in behavior was not intended.

Discussion:
@bonzini: Can you test with Meson's master branch? (I doubt it's fixed but at the same time there were many changes so maybe we're lucky)@sboehmann: I just tested master - same behavior as 1.8.0 _(Message: Buildtype: debugoptimized)_@jpakkane: This is confusing. Why would it report

> Message: Buildtype: release

When you set it to `debug` in the machine file and `debugoptimized` in the project call? Was this a typo because I can't see how it could have gotten the value `release`.@bonzini: Yes, it is a typo but the pull request has a unit test too.

Solution patch:
diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml
index 9013beac6..675807bd8 100644
--- a/.github/workflows/macos.yml
+++ b/.github/workflows/macos.yml
@@ -94,17 +94,10 @@ jobs:
         # productive -- only victim blaming -- however it bites particularly badly because this is a container/VM
         # See commit 5c479d7a13a518c18ccb4dc3b6bdd7bfc2a9bdb5 for a more thorough analysis.
         find /opt/homebrew/Cellar/python* -name EXTERNALLY-MANAGED -print0 | xargs -0 rm -vf
-        # Homebrew's installation of pip 25.0.1 in Python 3.13.3 is broken; it
-        # doesn't include a RECORD file, so it can't be upgraded by pip. Create
-        # a dummy RECORD file.
-        PIP_RECORD=$(python3 -c "import pip, os; print(f'{os.path.dirname(pip.__file__)}-{pip.__version__}.dist-info/RECORD')")
-        touch $PIP_RECORD
     # use python3 from homebrew because it is a valid framework, unlike the actions one:
     # https://github.com/actions/setup-python/issues/58
     - run: brew install pkg-config ninja llvm qt@5 boost ldc hdf5 openmpi lapack scalapack sdl2 boost-python3 gtk-doc zstd ncurses objfw libomp
     - run: |
-        python3 -m pip install --upgrade setuptools
-        python3 -m pip install --upgrade pip
         python3 -m pip install cython coverage
     - env:
         CPPFLAGS: "-I/opt/homebrew/include"
diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py
index 92f7a5a0e..3edf07062 100644
--- a/mesonbuild/modules/gnome.py
+++ b/mesonbuild/modules/gnome.py
@@ -1168,13 +1168,13 @@ class GnomeModule(ExtensionModule):
         scan_cflags += list(self._get_scanner_cflags(self._get_external_args_for_langs(state, [lc[0] for lc in langs_compilers])))
         scan_internal_ldflags = []
         scan_external_ldflags = []
-        scan_env_ldflags = []
+        scan_env_ldflags = state.environment.coredata.get_external_link_args(MachineChoice.HOST, 'c')
         for cli_flags, env_flags in (self._get_scanner_ldflags(internal_ldflags), self._get_scanner_ldflags(dep_internal_ldflags)):
             scan_internal_ldflags += cli_flags
-            scan_env_ldflags = env_flags
+            scan_env_ldflags += env_flags
         for cli_flags, env_flags in (self._get_scanner_ldflags(external_ldflags), self._get_scanner_ldflags(dep_external_ldflags)):
             scan_external_ldflags += cli_flags
-            scan_env_ldflags = env_flags
+            scan_env_ldflags += env_flags
         girtargets_inc_dirs = self._get_gir_targets_inc_dirs(girtargets)
         inc_dirs = kwargs['include_directories']
 
diff --git a/mesonbuild/options.py b/mesonbuild/options.py
index ac90b2292..cd7d2eb6d 100644
--- a/mesonbuild/options.py
+++ b/mesonbuild/options.py
@@ -1289,21 +1289,6 @@ class OptionStore:
         (project_default_options, cmd_line_options, machine_file_options) = self.first_handle_prefix(project_default_options_in,
                                                                                                      cmd_line_options_in,
                                                                                                      machine_file_options_in)
-        for key, valstr in machine_file_options.items():
-            # Due to backwards compatibility we ignore all build-machine options
-            # when building natively.
-            if not self.is_cross and key.is_for_build():
-                continue
-            if key.subproject:
-                self.augments[key] = valstr
-            elif key in self.options:
-                self.set_option(key, valstr, first_invocation)
-            else:
-                proj_key = key.as_root()
-                if proj_key in self.options:
-                    self.set_option(proj_key, valstr, first_invocation)
-                else:
-                    self.pending_options[key] = valstr
         for keystr, valstr in project_default_options.items():
             # Ths is complicated by the fact that a string can have two meanings:
             #
@@ -1338,6 +1323,21 @@ class OptionStore:
                     self.set_option(proj_key, valstr)
                 else:
                     self.pending_options[key] = valstr
+        for key, valstr in machine_file_options.items():
+            # Due to backwards compatibility we ignore all build-machine options
+            # when building natively.
+            if not self.is_cross and key.is_for_build():
+                continue
+            if key.subproject:
+                self.augments[key] = valstr
+            elif key in self.options:
+                self.set_option(key, valstr, first_invocation)
+            else:
+                proj_key = key.as_root()
+                if proj_key in self.options:
+                    self.set_option(proj_key, valstr, first_invocation)
+                else:
+                    self.pending_options[key] = valstr
         for keystr, valstr in cmd_line_options.items():
             if isinstance(keystr, str):
                 key = OptionKey.from_string(keystr)


Test patch:
diff --git a/unittests/optiontests.py b/unittests/optiontests.py
index 0bdd7dc45..5758a2d5c 100644
--- a/unittests/optiontests.py
+++ b/unittests/optiontests.py
@@ -35,6 +35,22 @@ class OptionTests(unittest.TestCase):
         optstore.initialize_from_top_level_project_call({OptionKey('someoption'): new_value}, {}, {})
         self.assertEqual(optstore.get_value_for(k), new_value)
 
+    def test_machine_vs_project(self):
+        optstore = OptionStore(False)
+        name = 'backend'
+        default_value = 'ninja'
+        proj_value = 'xcode'
+        mfile_value = 'vs2010'
+        k = OptionKey(name)
+        prefix = UserStringOption('prefix', 'This is needed by OptionStore', '/usr')
+        optstore.add_system_option('prefix', prefix)
+        vo = UserStringOption(k.name, 'You know what this is', default_value)
+        optstore.add_system_option(k.name, vo)
+        self.assertEqual(optstore.get_value_for(k), default_value)
+        optstore.initialize_from_top_level_project_call({OptionKey(name): proj_value}, {},
+                                                        {OptionKey(name): mfile_value})
+        self.assertEqual(optstore.get_value_for(k), mfile_value)
+
     def test_subproject_system_option(self):
         """Test that subproject system options get their default value from the global
            option (e.g. "sub:b_lto" can be initialized from "b_lto")."""