Repository: mesonbuild/meson

Issue:
Python configuration via pkg-config may differ from that of found Python on path.
It is fairly easy to have two Python installations on a system, only one of which reports to `pkg-config`.  If another Python is earlier on the path, then `mod = import('python')` will find one Python, but `mod.find_installation().dependency()` will find another, because it first uses `pkg_config`, regardless of whether that matches the found Python.

The consequence is that the code then compiles and links against a different Python to the one found.

**To Reproduce**

Install Python.org Python on Windows (no pkg-config files), to `c:\Python39` (e.g. with `choco install python --version=3.9`).  Install Msys2 gcc, that comes, by default, with a version of Python, along with `pkg-config` binary, and a `python3.pc` pkg-config configuration.   Make sure `c:\Python3.9` is earlier in the PATH.

In that case, with this in `meson.build`:

```
py_mod = import('python')
py_inst = py_mod.find_installation()
py_dep = py_inst.dependency()
```

the `py_inst` will be the `c:\Python39\Python.exe` Python, but the `py_dep` variables will refer to the Msys2 Python.

**Expected behavior**

I think what should happen is that the `dependency()` call should confirm that the `pkg-config` variables do in fact refer to the `py_inst` Python, and if not, then `dependency` should use the `system` method to get dependency information.

**system parameters**
* Native build
* Windows 10
* Python 3.9
* `meson --version` : 0.60.99
* `ninja --version` : 1.10.2.git.kitware.jobserver-1



Discussion:
We actually do try to find the pkg-config file from that python installation's `sysconfig.get_config_var('LIBPC')`, by overriding PKG_CONFIG_LIBDIR.

As far as I can tell, this means that even if you have a pkg-config install of python somewhere else, it will not ever actually be detected... it can only be detected if your default found python executable does not have a pkg-config file, AND it does have a `LIBPC` var that points to some other python install's pkg-config file.

If that is the case, I'd be very curious to know how we could distinguish the two...
The Python on the PATH returns None from `sysconfig.get_config_var('LIBPC')`.
Aha! That does seem like a trivially solvable problem.

Resolving patch:
diff --git a/mesonbuild/modules/python.py b/mesonbuild/modules/python.py
--- a/mesonbuild/modules/python.py
+++ b/mesonbuild/modules/python.py
@@ -79,10 +79,19 @@ def __init__(self, python_holder: 'PythonInstallation', embed: bool):
 class PythonPkgConfigDependency(PkgConfigDependency, _PythonDependencyBase):
 
     def __init__(self, name: str, environment: 'Environment',
-                 kwargs: T.Dict[str, T.Any], installation: 'PythonInstallation'):
+                 kwargs: T.Dict[str, T.Any], installation: 'PythonInstallation',
+                 libpc: bool = False):
+        if libpc:
+            mlog.debug(f'Searching for {name!r} via pkgconfig lookup in LIBPC')
+        else:
+            mlog.debug(f'Searching for {name!r} via fallback pkgconfig lookup in default paths')
+
         PkgConfigDependency.__init__(self, name, environment, kwargs)
         _PythonDependencyBase.__init__(self, installation, kwargs.get('embed', False))
 
+        if libpc and not self.is_found:
+            mlog.debug(f'"python-{self.version}" could not be found in LIBPC, this is likely due to a relocated python installation')
+
 
 class PythonFrameworkDependency(ExtraFrameworkDependency, _PythonDependencyBase):
 
@@ -240,12 +249,15 @@ def python_factory(env: 'Environment', for_machine: 'MachineChoice',
         # If python-X.Y.pc exists in LIBPC, we will try to use it
         def wrap_in_pythons_pc_dir(name: str, env: 'Environment', kwargs: T.Dict[str, T.Any],
                                    installation: 'PythonInstallation') -> 'ExternalDependency':
+            if not pkg_libdir:
+                # there is no LIBPC, so we can't search in it
+                return NotFoundDependency('python', env)
+
             old_pkg_libdir = os.environ.pop('PKG_CONFIG_LIBDIR', None)
             old_pkg_path = os.environ.pop('PKG_CONFIG_PATH', None)
-            if pkg_libdir:
-                os.environ['PKG_CONFIG_LIBDIR'] = pkg_libdir
+            os.environ['PKG_CONFIG_LIBDIR'] = pkg_libdir
             try:
-                return PythonPkgConfigDependency(name, env, kwargs, installation)
+                return PythonPkgConfigDependency(name, env, kwargs, installation, True)
             finally:
                 def set_env(name, value):
                     if value is not None:
@@ -255,10 +267,11 @@ def set_env(name, value):
                 set_env('PKG_CONFIG_LIBDIR', old_pkg_libdir)
                 set_env('PKG_CONFIG_PATH', old_pkg_path)
 
-        candidates.extend([
-            functools.partial(wrap_in_pythons_pc_dir, pkg_name, env, kwargs, installation),
-            functools.partial(PythonPkgConfigDependency, pkg_name, env, kwargs, installation)
-        ])
+        candidates.append(functools.partial(wrap_in_pythons_pc_dir, pkg_name, env, kwargs, installation))
+        # We only need to check both, if a python install has a LIBPC. It might point to the wrong location,
+        # e.g. relocated / cross compilation, but the presence of LIBPC indicates we should definitely look for something.
+        if pkg_libdir is not None:
+            candidates.append(functools.partial(PythonPkgConfigDependency, pkg_name, env, kwargs, installation))
 
     if DependencyMethods.SYSTEM in methods:
         candidates.append(functools.partial(PythonSystemDependency, 'python', env, kwargs, installation))


Test patch: