modules/python: use factory for dependency

This removes the odd 'pkgdep' attribute thing, and makes it behave more
like a proper dependency
pull/8992/head
Dylan Baker 4 years ago
parent a881e849b5
commit 4d67dd19e5
  1. 173
      mesonbuild/modules/python.py

@ -13,8 +13,9 @@
# limitations under the License.
from pathlib import Path
import os
import functools
import json
import os
import shutil
import typing as T
@ -23,6 +24,7 @@ from .. import mesonlib
from .. import mlog
from ..build import known_shmod_kwargs
from ..dependencies import DependencyMethods, PkgConfigDependency, NotFoundDependency, SystemDependency
from ..dependencies.base import process_method_kw
from ..environment import detect_cpu_family
from ..interpreter import ExternalProgramHolder, extract_required_kwarg, permitted_dependency_kwargs
from ..interpreterbase import (
@ -30,13 +32,14 @@ from ..interpreterbase import (
InvalidArguments, typed_pos_args, typed_kwargs, KwargInfo,
FeatureNew, FeatureNewKwargs, disablerIfNotFound
)
from ..mesonlib import MachineChoice, MesonException
from ..mesonlib import MachineChoice
from ..programs import ExternalProgram, NonExistingExternalProgram
if T.TYPE_CHECKING:
from . import ModuleState
from ..build import SharedModule, Data
from ..dependencies import ExternalDependency
from ..dependencies import ExternalDependency, Dependency
from ..dependencies.factory import DependencyGenerator
from ..environment import Environment
from ..interpreter import Interpreter
from ..interpreterbase.interpreterbase import TYPE_var, TYPE_kwargs
@ -49,17 +52,18 @@ mod_kwargs.update(known_shmod_kwargs)
mod_kwargs -= {'name_prefix', 'name_suffix'}
class PythonDependency(SystemDependency):
if T.TYPE_CHECKING:
_Base = ExternalDependency
else:
_Base = object
class _PythonDependencyBase(_Base):
def __init__(self, python_holder: 'PythonInstallation', environment: 'Environment',
kwargs: T.Dict[str, T.Any]):
super().__init__('python', environment, kwargs)
self.name = 'python'
self.static = kwargs.get('static', False)
self.embed = kwargs.get('embed', False)
self.version = python_holder.version
def __init__(self, python_holder: 'PythonInstallation', embed: bool):
self.name = 'python' # override the name from the "real" dependency lookup
self.embed = embed
self.version: str = python_holder.version
self.platform = python_holder.platform
self.pkgdep = None
self.variables = python_holder.variables
self.paths = python_holder.paths
self.link_libpython = python_holder.link_libpython
@ -69,73 +73,26 @@ class PythonDependency(SystemDependency):
else:
self.major_version = 2
# We first try to find the necessary python variables using pkgconfig
if DependencyMethods.PKGCONFIG in self.methods and not python_holder.is_pypy:
pkg_version = self.variables.get('LDVERSION') or self.version
pkg_libdir = self.variables.get('LIBPC')
pkg_embed = '-embed' if self.embed and mesonlib.version_compare(self.version, '>=3.8') else ''
pkg_name = f'python-{pkg_version}{pkg_embed}'
# If python-X.Y.pc exists in LIBPC, we will try to use it
if pkg_libdir is not None and Path(os.path.join(pkg_libdir, f'{pkg_name}.pc')).is_file():
old_pkg_libdir = os.environ.get('PKG_CONFIG_LIBDIR')
old_pkg_path = os.environ.get('PKG_CONFIG_PATH')
class PythonPkgConfigDependency(PkgConfigDependency, _PythonDependencyBase):
os.environ.pop('PKG_CONFIG_PATH', None)
def __init__(self, name: str, environment: 'Environment',
kwargs: T.Dict[str, T.Any], installation: 'PythonInstallation'):
PkgConfigDependency.__init__(self, name, environment, kwargs)
_PythonDependencyBase.__init__(self, installation, kwargs.get('embed', False))
if pkg_libdir:
os.environ['PKG_CONFIG_LIBDIR'] = pkg_libdir
try:
self.pkgdep = PkgConfigDependency(pkg_name, environment, kwargs)
mlog.debug(f'Found "{pkg_name}" via pkgconfig lookup in LIBPC ({pkg_libdir})')
py_lookup_method = 'pkgconfig'
except MesonException as e:
mlog.debug(f'"{pkg_name}" could not be found in LIBPC ({pkg_libdir})')
mlog.debug(e)
class PythonSystemDependency(SystemDependency, _PythonDependencyBase):
if old_pkg_path is not None:
os.environ['PKG_CONFIG_PATH'] = old_pkg_path
def __init__(self, name: str, environment: 'Environment',
kwargs: T.Dict[str, T.Any], installation: 'PythonInstallation'):
SystemDependency.__init__(self, name, environment, kwargs)
_PythonDependencyBase.__init__(self, installation, kwargs.get('embed', False))
if old_pkg_libdir is not None:
os.environ['PKG_CONFIG_LIBDIR'] = old_pkg_libdir
else:
os.environ.pop('PKG_CONFIG_LIBDIR', None)
else:
mlog.debug(f'"{pkg_name}" could not be found in LIBPC ({pkg_libdir}), this is likely due to a relocated python installation')
# If lookup via LIBPC failed, try to use fallback PKG_CONFIG_LIBDIR/PKG_CONFIG_PATH mechanisms
if self.pkgdep is None or not self.pkgdep.found():
try:
self.pkgdep = PkgConfigDependency(pkg_name, environment, kwargs)
mlog.debug(f'Found "{pkg_name}" via fallback pkgconfig lookup in PKG_CONFIG_LIBDIR/PKG_CONFIG_PATH')
py_lookup_method = 'pkgconfig-fallback'
except MesonException as e:
mlog.debug(f'"{pkg_name}" could not be found via fallback pkgconfig lookup in PKG_CONFIG_LIBDIR/PKG_CONFIG_PATH')
mlog.debug(e)
if self.pkgdep and self.pkgdep.found():
self.compile_args = self.pkgdep.get_compile_args()
self.link_args = self.pkgdep.get_link_args()
self.is_found = True
self.pcdep = self.pkgdep
else:
self.pkgdep = None
# Finally, try to find python via SYSCONFIG as a final measure
if DependencyMethods.SYSCONFIG in self.methods:
if mesonlib.is_windows():
self._find_libpy_windows(environment)
else:
self._find_libpy(python_holder, environment)
if self.is_found:
mlog.debug(f'Found "python-{self.version}" via SYSCONFIG module')
py_lookup_method = 'sysconfig'
if self.is_found:
mlog.log('Dependency', mlog.bold(self.name), 'found:', mlog.green(f'YES ({py_lookup_method})'))
if mesonlib.is_windows():
self._find_libpy_windows(environment)
else:
mlog.log('Dependency', mlog.bold(self.name), 'found:', mlog.red('NO'))
self._find_libpy(installation, environment)
def _find_libpy(self, python_holder: 'PythonInstallation', environment: 'Environment') -> None:
if python_holder.is_pypy:
@ -253,20 +210,45 @@ class PythonDependency(SystemDependency):
self.is_found = True
@staticmethod
def get_methods() -> T.List[DependencyMethods]:
if mesonlib.is_windows():
return [DependencyMethods.PKGCONFIG, DependencyMethods.SYSCONFIG]
elif mesonlib.is_osx():
return [DependencyMethods.PKGCONFIG, DependencyMethods.EXTRAFRAMEWORK]
else:
return [DependencyMethods.PKGCONFIG, DependencyMethods.SYSCONFIG]
def get_pkgconfig_variable(self, variable_name: str, kwargs: T.Dict[str, T.Any]) -> str:
if self.pkgdep:
return self.pkgdep.get_pkgconfig_variable(variable_name, kwargs)
else:
return super().get_pkgconfig_variable(variable_name, kwargs)
def python_factory(env: 'Environment', for_machine: 'MachineChoice',
kwargs: T.Dict[str, T.Any], methods: T.List[DependencyMethods],
installation: 'PythonInstallation') -> T.List['DependencyGenerator']:
# We can't use the factory_methods decorator here, as we need to pass the
# extra installation argument
embed = kwargs.get('embed', False)
candidates: T.List['DependencyGenerator'] = []
if DependencyMethods.PKGCONFIG in methods:
pkg_version = installation.variables.get('LDVERSION') or installation.version
pkg_libdir = installation.variables.get('LIBPC')
pkg_embed = '-embed' if embed and mesonlib.version_compare(installation.version, '>=3.8') else ''
pkg_name = f'python-{pkg_version}{pkg_embed}'
# 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':
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
try:
return PythonPkgConfigDependency(name, env, kwargs, installation)
finally:
if old_pkg_libdir is not None:
os.environ['PKG_CONFIG_LIBDIR'] = old_pkg_libdir
if old_pkg_path is not None:
os.environ['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)
])
if DependencyMethods.SYSTEM in methods:
candidates.append(functools.partial(PythonSystemDependency, 'python', env, kwargs, installation))
return candidates
INTROSPECT_COMMAND = '''\
@ -388,7 +370,7 @@ class PythonInstallation(ExternalProgramHolder):
if not self.link_libpython:
new_deps = []
for dep in mesonlib.extract_as_list(kwargs, 'dependencies'):
if isinstance(dep, PythonDependency):
if isinstance(dep, _PythonDependencyBase):
dep = dep.get_partial_dependency(compile_args=True)
new_deps.append(dep)
kwargs['dependencies'] = new_deps
@ -408,15 +390,26 @@ class PythonInstallation(ExternalProgramHolder):
@permittedKwargs(permitted_dependency_kwargs | {'embed'})
@FeatureNewKwargs('python_installation.dependency', '0.53.0', ['embed'])
@noPosargs
def dependency_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> 'ExternalDependency':
def dependency_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> 'Dependency':
disabled, required, feature = extract_required_kwarg(kwargs, self.subproject)
# it's theoretically (though not practically) possible for the else clse
# to not bind dep, let's ensure it is.
dep: 'Dependency' = NotFoundDependency(self.interpreter.environment)
if disabled:
mlog.log('Dependency', mlog.bold('python'), 'skipped: feature', mlog.bold(feature), 'disabled')
dep = NotFoundDependency(self.interpreter.environment)
else:
dep = PythonDependency(self, self.interpreter.environment, kwargs)
for d in python_factory(self.interpreter.environment,
MachineChoice.BUILD if kwargs.get('native', False) else MachineChoice.HOST,
kwargs,
process_method_kw({DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM}, kwargs),
self):
dep = d()
if dep.found():
break
if required and not dep.found():
raise mesonlib.MesonException('Python dependency not found')
return dep
@typed_pos_args('install_data', varargs=(str, mesonlib.File))
@ -509,7 +502,7 @@ class PythonModule(ExtensionModule):
return None
@staticmethod
def _check_version(name_or_path: str, version) -> bool:
def _check_version(name_or_path: str, version: str) -> bool:
if name_or_path == 'python2':
return mesonlib.version_compare(version, '< 3.0')
elif name_or_path == 'python3':

Loading…
Cancel
Save