Repository: mesonbuild/meson

Issue:
Meson CMake dependency lookup ignores CMAKE_PREFIX_PATH after recent changes
After upgrading from 0.49.2 to 0.50.1, my build which relies on a CMake dependency lookup started failing to find the dependency. By using `git bisect` on the Meson repo I found that the lookup starts failing with [this PR](https://github.com/mesonbuild/meson/pull/4914), specifically [this commit](https://github.com/mesonbuild/meson/pull/4914/commits/cdc338b74337e29c903bb73b61dc6f22877208e7) (cc @mensinda). I've also tested on 0.51.0 and master and the problem persists.

As per that PR, Meson now checks if the expected CMake modules exist before actually invoking CMake, except I believe it fails to consider all the places CMake itself would look. Specifically, as I understand it, CMake looks in the paths of the [`CMAKE_PREFIX_PATH`](https://cmake.org/cmake/help/latest/variable/CMAKE_PREFIX_PATH.html) environment variable, which I believe is automatically set by my dependency handling system and was key to finding the dependencies when I used 0.49.2. In newer versions, Meson bails out and never gives CMake the chance to run and check those paths.

I can seemingly work around this by configuring with `-Dcmake_prefix_path=$(echo "$CMAKE_PREFIX_PATH" | tr : ,)`, but this feels a bit redundant. It's unclear to me whether it's intended design, given that Meson is happy to read other env vars like CXX/LDFLAGS without passing them through `-D` flags, for example. Previously, Meson's CMake support was really "plug and play" and didn't require any thought beyond what would make CMake itself work in the normal way, and I think it would be ideal to preserve that property.

Let me know if you need any more specific details.


Discussion:
No, this wasn't a design decision, I just forgot to add it. This should probably also go into 0.51.2.

Resolving patch:
diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py
--- a/mesonbuild/dependencies/base.py
+++ b/mesonbuild/dependencies/base.py
@@ -1035,6 +1035,12 @@ def __init__(self, name: str, environment: Environment, kwargs, language=None):
             cm_args.append('-DCMAKE_MODULE_PATH=' + ';'.join(cm_path))
 
         pref_path = self.env.coredata.builtins_per_machine[self.for_machine]['cmake_prefix_path'].value
+        if 'CMAKE_PREFIX_PATH' in os.environ:
+            env_pref_path = os.environ['CMAKE_PREFIX_PATH'].split(':')
+            env_pref_path = [x for x in env_pref_path if x]  # Filter out enpty strings
+            if not pref_path:
+                pref_path = []
+            pref_path += env_pref_path
         if pref_path:
             cm_args.append('-DCMAKE_PREFIX_PATH={}'.format(';'.join(pref_path)))
 


Test patch:
diff --git a/run_project_tests.py b/run_project_tests.py
--- a/run_project_tests.py
+++ b/run_project_tests.py
@@ -35,6 +35,7 @@
 from mesonbuild.mesonlib import MachineChoice, stringlistify, Popen_safe
 from mesonbuild.coredata import backendlist
 import argparse
+import json
 import xml.etree.ElementTree as ET
 import time
 import multiprocessing
@@ -348,6 +349,7 @@ def _run_test(testdir, test_build_dir, install_dir, extra_args, compiler, backen
     compile_commands, clean_commands, install_commands, uninstall_commands = commands
     test_args = parse_test_args(testdir)
     gen_start = time.time()
+    setup_env = None
     # Configure in-process
     if pass_prefix_to_test(testdir):
         gen_args = ['--prefix', '/usr']
@@ -362,7 +364,15 @@ def _run_test(testdir, test_build_dir, install_dir, extra_args, compiler, backen
     crossfile = os.path.join(testdir, 'crossfile.ini')
     if os.path.exists(crossfile):
         gen_args.extend(['--cross-file', crossfile])
-    (returncode, stdo, stde) = run_configure(gen_args)
+    setup_env_file = os.path.join(testdir, 'setup_env.json')
+    if os.path.exists(setup_env_file):
+        setup_env = os.environ.copy()
+        with open(setup_env_file, 'r') as fp:
+            data = json.load(fp)
+            for key, val in data.items():
+                val = val.replace('@ROOT@', os.path.abspath(testdir))
+                setup_env[key] = val
+    (returncode, stdo, stde) = run_configure(gen_args, env=setup_env)
     try:
         logfile = Path(test_build_dir, 'meson-logs', 'meson-log.txt')
         mesonlog = logfile.open(errors='ignore', encoding='utf-8').read()
diff --git a/run_tests.py b/run_tests.py
--- a/run_tests.py
+++ b/run_tests.py
@@ -222,28 +222,33 @@ def clear_meson_configure_class_caches():
     mesonbuild.dependencies.PkgConfigDependency.pkgbin_cache = {}
     mesonbuild.dependencies.PkgConfigDependency.class_pkgbin = mesonlib.PerMachine(None, None)
 
-def run_configure_inprocess(commandlist):
+def run_configure_inprocess(commandlist, env=None):
     old_stdout = sys.stdout
     sys.stdout = mystdout = StringIO()
     old_stderr = sys.stderr
     sys.stderr = mystderr = StringIO()
+    old_environ = os.environ.copy()
+    if env is not None:
+        os.environ.update(env)
     try:
         returncode = mesonmain.run(commandlist, get_meson_script())
     finally:
         sys.stdout = old_stdout
         sys.stderr = old_stderr
         clear_meson_configure_class_caches()
+        os.environ.clear()
+        os.environ.update(old_environ)
     return returncode, mystdout.getvalue(), mystderr.getvalue()
 
-def run_configure_external(full_command):
-    pc, o, e = mesonlib.Popen_safe(full_command)
+def run_configure_external(full_command, env=None):
+    pc, o, e = mesonlib.Popen_safe(full_command, env=env)
     return pc.returncode, o, e
 
-def run_configure(commandlist):
+def run_configure(commandlist, env=None):
     global meson_exe
     if meson_exe:
-        return run_configure_external(meson_exe + commandlist)
-    return run_configure_inprocess(commandlist)
+        return run_configure_external(meson_exe + commandlist, env=env)
+    return run_configure_inprocess(commandlist, env=env)
 
 def print_system_info():
     print(mlog.bold('System information.').get_text(mlog.colorize_console))
diff --git a/test cases/linuxlike/13 cmake dependency/cmake_pref_env/lib/cmake/cmMesonTestDep/cmMesonTestDepConfig.cmake b/test cases/linuxlike/13 cmake dependency/cmake_pref_env/lib/cmake/cmMesonTestDep/cmMesonTestDepConfig.cmake
new file mode 100644
--- /dev/null
+++ b/test cases/linuxlike/13 cmake dependency/cmake_pref_env/lib/cmake/cmMesonTestDep/cmMesonTestDepConfig.cmake	
@@ -0,0 +1,9 @@
+find_package(ZLIB)
+
+if(ZLIB_FOUND OR ZLIB_Found)
+  set(cmMesonTestDep_FOUND        ON)
+  set(cmMesonTestDep_LIBRARIES    ${ZLIB_LIBRARY})
+  set(cmMesonTestDep_INCLUDE_DIRS ${ZLIB_INCLUDE_DIR})
+else()
+  set(cmMesonTestDep_FOUND       OFF)
+endif()
diff --git a/test cases/linuxlike/13 cmake dependency/meson.build b/test cases/linuxlike/13 cmake dependency/meson.build
--- a/test cases/linuxlike/13 cmake dependency/meson.build	
+++ b/test cases/linuxlike/13 cmake dependency/meson.build	
@@ -36,6 +36,10 @@ depf2 = dependency('ZLIB', required : false, method : 'cmake', modules : 'dfggh:
 
 assert(depf2.found() == false, 'Invalid CMake targets should fail')
 
+# Try to find cmMesonTestDep in a custom prefix
+
+depPrefEnv = dependency('cmMesonTestDep', required : true, method : 'cmake')
+
 # Try to find a dependency with a custom CMake module
 
 depm1 = dependency('SomethingLikeZLIB', required : true, method : 'cmake', cmake_module_path : 'cmake')
diff --git a/test cases/linuxlike/13 cmake dependency/setup_env.json b/test cases/linuxlike/13 cmake dependency/setup_env.json
new file mode 100644
--- /dev/null
+++ b/test cases/linuxlike/13 cmake dependency/setup_env.json	
@@ -0,0 +1,3 @@
+{
+  "CMAKE_PREFIX_PATH": "@ROOT@/cmake_pref_env"
+}