Repository: mesonbuild/meson

Issue:
Emscripten cross-build gets stuck in an endless reconfigure loop
This is a 0.51 regression introduced in 65e59c84a198062f21ab68fdae41b3e5de8d8480

I frankly have no idea what is actually causing it, and so far I've been only able to reproduce it with an Emscripten toolchain.

These steps, I believe, should reproduce the bug:

1) Install the latest Emscripten as instructed [here](https://emscripten.org/docs/getting_started/downloads.html). Make sure to source the `emsdk_env.sh` script as well.
2) Create a cross file as follows:
```ini
[binaries]
c='/path/to/emscripten/emcc'
cpp='/path/to/emscripten/em++'
ar='/path/to/emscripten/emar'
ranlib='/path/to/emscripten/emranlib'
pkgconfig=['/path/to/emscripten/emconfigure', 'pkg-config']
file_packager=['/usr/bin/python3', '/path/to/emscripten/tools/file_packager.py']

[properties]
root='/path/to/emscripten/system'
needs_exe_wrapper=true

[host_machine]
system='emscripten'
cpu_family='wasm'
cpu='wasm32'
endian='little'
```
(Some of these fields might be extraneous for the purpose of testing. I kept it as similar to my local configuration as possible.)

3. Create and build a minimal project: `echo "project('fail', 'c')" > meson.build && meson --cross-file emscripten builddir && ninja -C builddir`
4. Hit Ctrl+C when you've had enough.





Discussion:

Resolving patch:
diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py
--- a/mesonbuild/coredata.py
+++ b/mesonbuild/coredata.py
@@ -26,7 +26,7 @@
 import ast
 import argparse
 import configparser
-from typing import Optional, Any, TypeVar, Generic, Type
+from typing import Optional, Any, TypeVar, Generic, Type, List
 
 version = '0.50.999'
 backendlist = ['ninja', 'vs', 'vs2010', 'vs2015', 'vs2017', 'vs2019', 'xcode']
@@ -212,32 +212,10 @@ def is_auto(self):
         return self.value == 'auto'
 
 
-def load_configs(filenames, subdir):
+def load_configs(filenames: List[str]) -> configparser.ConfigParser:
     """Load configuration files from a named subdirectory."""
-    def gen():
-        for f in filenames:
-            f = os.path.expanduser(os.path.expandvars(f))
-            if os.path.exists(f):
-                yield f
-                continue
-            elif sys.platform != 'win32':
-                f = os.path.basename(f)
-                paths = [
-                    os.environ.get('XDG_DATA_HOME', os.path.expanduser('~/.local/share')),
-                ] + os.environ.get('XDG_DATA_DIRS', '/usr/local/share:/usr/share').split(':')
-                for path in paths:
-                    path_to_try = os.path.join(path, 'meson', subdir, f)
-                    if os.path.isfile(path_to_try):
-                        yield path_to_try
-                        break
-                else:
-                    raise MesonException('Cannot find specified native file: ' + f)
-                continue
-
-            raise MesonException('Cannot find specified native file: ' + f)
-
     config = configparser.ConfigParser()
-    config.read(gen())
+    config.read(filenames)
     return config
 
 
@@ -265,23 +243,42 @@ def __init__(self, options):
         self.user_options = {}
         self.compiler_options = PerMachine({}, {}, {})
         self.base_options = {}
-        self.cross_files = self.__load_config_files(options.cross_file)
+        self.cross_files = self.__load_config_files(options.cross_file, 'cross')
         self.compilers = OrderedDict()
         self.cross_compilers = OrderedDict()
         self.deps = OrderedDict()
         # Only to print a warning if it changes between Meson invocations.
-        self.config_files = self.__load_config_files(options.native_file)
+        self.config_files = self.__load_config_files(options.native_file, 'native')
         self.libdir_cross_fixup()
 
     @staticmethod
-    def __load_config_files(filenames):
+    def __load_config_files(filenames: Optional[List[str]], ftype: str) -> List[str]:
         # Need to try and make the passed filenames absolute because when the
         # files are parsed later we'll have chdir()d.
         if not filenames:
             return []
-        filenames = [os.path.abspath(os.path.expanduser(os.path.expanduser(f)))
-                     for f in filenames]
-        return filenames
+
+        real = []
+        for f in filenames:
+            f = os.path.expanduser(os.path.expandvars(f))
+            if os.path.exists(f):
+                real.append(os.path.abspath(f))
+                continue
+            elif sys.platform != 'win32':
+                paths = [
+                    os.environ.get('XDG_DATA_HOME', os.path.expanduser('~/.local/share')),
+                ] + os.environ.get('XDG_DATA_DIRS', '/usr/local/share:/usr/share').split(':')
+                for path in paths:
+                    path_to_try = os.path.join(path, 'meson', ftype, f)
+                    if os.path.isfile(path_to_try):
+                        real.append(path_to_try)
+                        break
+                else:
+                    raise MesonException('Cannot find specified {} file: {}'.format(ftype, f))
+                continue
+
+            raise MesonException('Cannot find specified {} file: {}'.format(ftype, f))
+        return real
 
     def libdir_cross_fixup(self):
         # By default set libdir to "lib" when cross compiling since
diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py
--- a/mesonbuild/environment.py
+++ b/mesonbuild/environment.py
@@ -422,13 +422,13 @@ def __init__(self, source_dir, build_dir, options):
 
         if self.coredata.config_files is not None:
             config = MesonConfigFile.from_config_parser(
-                coredata.load_configs(self.coredata.config_files, 'native'))
+                coredata.load_configs(self.coredata.config_files))
             self.binaries.build = BinaryTable(config.get('binaries', {}))
             self.paths.build = Directories(**config.get('paths', {}))
 
         if self.coredata.cross_files:
             config = MesonConfigFile.from_config_parser(
-                coredata.load_configs(self.coredata.cross_files, 'cross'))
+                coredata.load_configs(self.coredata.cross_files))
             self.properties.host = Properties(config.get('properties', {}), False)
             self.binaries.host = BinaryTable(config.get('binaries', {}), False)
             if 'host_machine' in config:
diff --git a/run_unittests.py b/run_unittests.py
--- a/run_unittests.py
+++ b/run_unittests.py
@@ -5862,6 +5862,21 @@ def test_native_file_dirs_overriden(self):
                   extra_args=['--native-file', os.path.join(testcase, 'nativefile'),
                               '-Ddef_libdir=liblib', '-Dlibdir=liblib'])
 
+    def test_compile_sys_path(self):
+        """Compiling with a native file stored in a system path works.
+
+        There was a bug which caused the paths to be stored incorrectly and
+        would result in ninja invoking meson in an infinite loop. This tests
+        for that by actually invoking ninja.
+        """
+        testcase = os.path.join(self.common_test_dir, '1 trivial')
+
+        # It really doesn't matter what's in the native file, just that it exists
+        config = self.helper_create_native_file({'binaries': {'bash': 'false'}})
+
+        self.init(testcase, extra_args=['--native-file', config])
+        self.build()
+
 
 class CrossFileTests(BasePlatformTests):
 


Test patch: