python module/dependency: move the specialized external program

In preparation for handling more work inside dependencies.*, we need to
be able to run a PythonExternalProgram from the python dependency. Move
most of the definition -- but only the parts that have no interest in a
ModuleState -- and subclass a bit of sanity checking that we need to
handle specially when used in the module.
pull/11250/head
Eli Schwartz 2 years ago committed by Dylan Baker
parent 6719724c7c
commit 9fa4da3ba9
  1. 81
      mesonbuild/dependencies/python.py
  2. 88
      mesonbuild/modules/python.py

@ -13,18 +13,95 @@
# limitations under the License. # limitations under the License.
from __future__ import annotations from __future__ import annotations
import json, sysconfig
from pathlib import Path from pathlib import Path
import sysconfig
import typing as T import typing as T
from .. import mlog from .. import mesonlib, mlog
from .base import DependencyMethods, SystemDependency from .base import DependencyMethods, SystemDependency
from .factory import DependencyFactory from .factory import DependencyFactory
from ..environment import detect_cpu_family from ..environment import detect_cpu_family
from ..programs import ExternalProgram
if T.TYPE_CHECKING: if T.TYPE_CHECKING:
from typing_extensions import TypedDict
from ..environment import Environment from ..environment import Environment
class PythonIntrospectionDict(TypedDict):
install_paths: T.Dict[str, str]
is_pypy: bool
is_venv: bool
link_libpython: bool
sysconfig_paths: T.Dict[str, str]
paths: T.Dict[str, str]
platform: str
suffix: str
variables: T.Dict[str, str]
version: str
class BasicPythonExternalProgram(ExternalProgram):
def __init__(self, name: str, command: T.Optional[T.List[str]] = None,
ext_prog: T.Optional[ExternalProgram] = None):
if ext_prog is None:
super().__init__(name, command=command, silent=True)
else:
self.name = name
self.command = ext_prog.command
self.path = ext_prog.path
# We want strong key values, so we always populate this with bogus data.
# Otherwise to make the type checkers happy we'd have to do .get() for
# everycall, even though we know that the introspection data will be
# complete
self.info: 'PythonIntrospectionDict' = {
'install_paths': {},
'is_pypy': False,
'is_venv': False,
'link_libpython': False,
'sysconfig_paths': {},
'paths': {},
'platform': 'sentinal',
'suffix': 'sentinel',
'variables': {},
'version': '0.0',
}
self.pure: bool = True
def _check_version(self, version: str) -> bool:
if self.name == 'python2':
return mesonlib.version_compare(version, '< 3.0')
elif self.name == 'python3':
return mesonlib.version_compare(version, '>= 3.0')
return True
def sanity(self) -> bool:
# Sanity check, we expect to have something that at least quacks in tune
import importlib.resources
with importlib.resources.path('mesonbuild.scripts', 'python_info.py') as f:
cmd = self.get_command() + [str(f)]
p, stdout, stderr = mesonlib.Popen_safe(cmd)
try:
info = json.loads(stdout)
except json.JSONDecodeError:
info = None
mlog.debug('Could not introspect Python (%s): exit code %d' % (str(p.args), p.returncode))
mlog.debug('Program stdout:\n')
mlog.debug(stdout)
mlog.debug('Program stderr:\n')
mlog.debug(stderr)
if info is not None and self._check_version(info['version']):
self.info = T.cast('PythonIntrospectionDict', info)
return True
else:
return False
class Python3DependencySystem(SystemDependency): class Python3DependencySystem(SystemDependency):
def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]) -> None: def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]) -> None:

@ -16,7 +16,6 @@ from __future__ import annotations
from pathlib import Path from pathlib import Path
import copy import copy
import functools import functools
import json
import os import os
import shutil import shutil
import typing as T import typing as T
@ -30,6 +29,7 @@ from ..dependencies import (DependencyMethods, PkgConfigDependency, NotFoundDepe
DependencyTypeName, ExternalDependency) DependencyTypeName, ExternalDependency)
from ..dependencies.base import process_method_kw from ..dependencies.base import process_method_kw
from ..dependencies.detect import get_dep_identifier from ..dependencies.detect import get_dep_identifier
from ..dependencies.python import BasicPythonExternalProgram
from ..environment import detect_cpu_family from ..environment import detect_cpu_family
from ..interpreter import ExternalProgramHolder, extract_required_kwarg, permitted_dependency_kwargs from ..interpreter import ExternalProgramHolder, extract_required_kwarg, permitted_dependency_kwargs
from ..interpreter import primitives as P_OBJ from ..interpreter import primitives as P_OBJ
@ -54,19 +54,6 @@ if T.TYPE_CHECKING:
from ..interpreter.kwargs import ExtractRequired from ..interpreter.kwargs import ExtractRequired
from ..interpreterbase.interpreterbase import TYPE_var, TYPE_kwargs from ..interpreterbase.interpreterbase import TYPE_var, TYPE_kwargs
class PythonIntrospectionDict(TypedDict):
install_paths: T.Dict[str, str]
is_pypy: bool
is_venv: bool
link_libpython: bool
sysconfig_paths: T.Dict[str, str]
paths: T.Dict[str, str]
platform: str
suffix: str
variables: T.Dict[str, str]
version: str
class PyInstallKw(TypedDict): class PyInstallKw(TypedDict):
pure: T.Optional[bool] pure: T.Optional[bool]
@ -91,7 +78,7 @@ mod_kwargs -= {'name_prefix', 'name_suffix'}
class _PythonDependencyBase(_Base): class _PythonDependencyBase(_Base):
def __init__(self, python_holder: 'PythonExternalProgram', embed: bool): def __init__(self, python_holder: 'BasicPythonExternalProgram', embed: bool):
self.embed = embed self.embed = embed
self.version: str = python_holder.info['version'] self.version: str = python_holder.info['version']
self.platform = python_holder.info['platform'] self.platform = python_holder.info['platform']
@ -109,7 +96,7 @@ class _PythonDependencyBase(_Base):
class PythonPkgConfigDependency(PkgConfigDependency, _PythonDependencyBase): class PythonPkgConfigDependency(PkgConfigDependency, _PythonDependencyBase):
def __init__(self, name: str, environment: 'Environment', def __init__(self, name: str, environment: 'Environment',
kwargs: T.Dict[str, T.Any], installation: 'PythonExternalProgram', kwargs: T.Dict[str, T.Any], installation: 'BasicPythonExternalProgram',
libpc: bool = False): libpc: bool = False):
if libpc: if libpc:
mlog.debug(f'Searching for {name!r} via pkgconfig lookup in LIBPC') mlog.debug(f'Searching for {name!r} via pkgconfig lookup in LIBPC')
@ -137,7 +124,7 @@ class PythonPkgConfigDependency(PkgConfigDependency, _PythonDependencyBase):
class PythonFrameworkDependency(ExtraFrameworkDependency, _PythonDependencyBase): class PythonFrameworkDependency(ExtraFrameworkDependency, _PythonDependencyBase):
def __init__(self, name: str, environment: 'Environment', def __init__(self, name: str, environment: 'Environment',
kwargs: T.Dict[str, T.Any], installation: 'PythonExternalProgram'): kwargs: T.Dict[str, T.Any], installation: 'BasicPythonExternalProgram'):
ExtraFrameworkDependency.__init__(self, name, environment, kwargs) ExtraFrameworkDependency.__init__(self, name, environment, kwargs)
_PythonDependencyBase.__init__(self, installation, kwargs.get('embed', False)) _PythonDependencyBase.__init__(self, installation, kwargs.get('embed', False))
@ -145,7 +132,7 @@ class PythonFrameworkDependency(ExtraFrameworkDependency, _PythonDependencyBase)
class PythonSystemDependency(SystemDependency, _PythonDependencyBase): class PythonSystemDependency(SystemDependency, _PythonDependencyBase):
def __init__(self, name: str, environment: 'Environment', def __init__(self, name: str, environment: 'Environment',
kwargs: T.Dict[str, T.Any], installation: 'PythonExternalProgram'): kwargs: T.Dict[str, T.Any], installation: 'BasicPythonExternalProgram'):
SystemDependency.__init__(self, name, environment, kwargs) SystemDependency.__init__(self, name, environment, kwargs)
_PythonDependencyBase.__init__(self, installation, kwargs.get('embed', False)) _PythonDependencyBase.__init__(self, installation, kwargs.get('embed', False))
@ -283,7 +270,7 @@ class PythonSystemDependency(SystemDependency, _PythonDependencyBase):
def python_factory(env: 'Environment', for_machine: 'MachineChoice', def python_factory(env: 'Environment', for_machine: 'MachineChoice',
kwargs: T.Dict[str, T.Any], kwargs: T.Dict[str, T.Any],
installation: 'PythonExternalProgram') -> T.List['DependencyGenerator']: installation: 'BasicPythonExternalProgram') -> T.List['DependencyGenerator']:
# We can't use the factory_methods decorator here, as we need to pass the # We can't use the factory_methods decorator here, as we need to pass the
# extra installation argument # extra installation argument
methods = process_method_kw({DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM}, kwargs) methods = process_method_kw({DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM}, kwargs)
@ -298,7 +285,7 @@ def python_factory(env: 'Environment', for_machine: 'MachineChoice',
# If python-X.Y.pc exists in LIBPC, we will try to use it # 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], def wrap_in_pythons_pc_dir(name: str, env: 'Environment', kwargs: T.Dict[str, T.Any],
installation: 'PythonExternalProgram') -> 'ExternalDependency': installation: 'BasicPythonExternalProgram') -> 'ExternalDependency':
if not pkg_libdir: if not pkg_libdir:
# there is no LIBPC, so we can't search in it # there is no LIBPC, so we can't search in it
empty = ExternalDependency(DependencyTypeName('pkgconfig'), env, {}) empty = ExternalDependency(DependencyTypeName('pkgconfig'), env, {})
@ -339,66 +326,13 @@ def python_factory(env: 'Environment', for_machine: 'MachineChoice',
return candidates return candidates
class PythonExternalProgram(ExternalProgram): class PythonExternalProgram(BasicPythonExternalProgram):
def __init__(self, name: str, command: T.Optional[T.List[str]] = None,
ext_prog: T.Optional[ExternalProgram] = None):
if ext_prog is None:
super().__init__(name, command=command, silent=True)
else:
self.name = name
self.command = ext_prog.command
self.path = ext_prog.path
# We want strong key values, so we always populate this with bogus data.
# Otherwise to make the type checkers happy we'd have to do .get() for
# everycall, even though we know that the introspection data will be
# complete
self.info: 'PythonIntrospectionDict' = {
'install_paths': {},
'is_pypy': False,
'is_venv': False,
'link_libpython': False,
'sysconfig_paths': {},
'paths': {},
'platform': 'sentinal',
'suffix': 'sentinel',
'variables': {},
'version': '0.0',
}
self.pure: bool = True
def _check_version(self, version: str) -> bool:
if self.name == 'python2':
return mesonlib.version_compare(version, '< 3.0')
elif self.name == 'python3':
return mesonlib.version_compare(version, '>= 3.0')
return True
def sanity(self, state: T.Optional['ModuleState'] = None) -> bool: def sanity(self, state: T.Optional['ModuleState'] = None) -> bool:
# Sanity check, we expect to have something that at least quacks in tune ret = super().sanity()
if ret:
import importlib.resources
with importlib.resources.path('mesonbuild.scripts', 'python_info.py') as f:
cmd = self.get_command() + [str(f)]
p, stdout, stderr = mesonlib.Popen_safe(cmd)
try:
info = json.loads(stdout)
except json.JSONDecodeError:
info = None
mlog.debug('Could not introspect Python (%s): exit code %d' % (str(p.args), p.returncode))
mlog.debug('Program stdout:\n')
mlog.debug(stdout)
mlog.debug('Program stderr:\n')
mlog.debug(stderr)
if info is not None and self._check_version(info['version']):
self.info = T.cast('PythonIntrospectionDict', info)
self.platlib = self._get_path(state, 'platlib') self.platlib = self._get_path(state, 'platlib')
self.purelib = self._get_path(state, 'purelib') self.purelib = self._get_path(state, 'purelib')
return True return ret
else:
return False
def _get_path(self, state: T.Optional['ModuleState'], key: str) -> None: def _get_path(self, state: T.Optional['ModuleState'], key: str) -> None:
rel_path = self.info['install_paths'][key][1:] rel_path = self.info['install_paths'][key][1:]

Loading…
Cancel
Save