|
|
|
@ -12,41 +12,59 @@ |
|
|
|
|
# See the License for the specific language governing permissions and |
|
|
|
|
# limitations under the License. |
|
|
|
|
|
|
|
|
|
import os |
|
|
|
|
from pathlib import Path |
|
|
|
|
import functools |
|
|
|
|
import json |
|
|
|
|
import os |
|
|
|
|
import shutil |
|
|
|
|
import typing as T |
|
|
|
|
|
|
|
|
|
from pathlib import Path |
|
|
|
|
from .. import mesonlib |
|
|
|
|
from ..mesonlib import MachineChoice, MesonException |
|
|
|
|
from . import ExtensionModule |
|
|
|
|
from .. import mesonlib |
|
|
|
|
from .. import mlog |
|
|
|
|
from ..coredata import UserFeatureOption |
|
|
|
|
from ..build import known_shmod_kwargs |
|
|
|
|
from ..dependencies import DependencyMethods, PkgConfigDependency, NotFoundDependency, SystemDependency, ExtraFrameworkDependency |
|
|
|
|
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 ( |
|
|
|
|
noPosargs, noKwargs, permittedKwargs, |
|
|
|
|
InvalidArguments, |
|
|
|
|
noPosargs, noKwargs, permittedKwargs, ContainerTypeInfo, |
|
|
|
|
InvalidArguments, typed_pos_args, typed_kwargs, KwargInfo, |
|
|
|
|
FeatureNew, FeatureNewKwargs, disablerIfNotFound |
|
|
|
|
) |
|
|
|
|
from ..interpreter import ExternalProgramHolder, extract_required_kwarg, permitted_dependency_kwargs |
|
|
|
|
from ..build import known_shmod_kwargs |
|
|
|
|
from .. import mlog |
|
|
|
|
from ..environment import detect_cpu_family |
|
|
|
|
from ..dependencies import DependencyMethods, PkgConfigDependency, NotFoundDependency, SystemDependency |
|
|
|
|
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, Dependency |
|
|
|
|
from ..dependencies.factory import DependencyGenerator |
|
|
|
|
from ..environment import Environment |
|
|
|
|
from ..interpreter import Interpreter |
|
|
|
|
from ..interpreterbase.interpreterbase import TYPE_var, TYPE_kwargs |
|
|
|
|
|
|
|
|
|
from typing_extensions import TypedDict |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mod_kwargs = {'subdir'} |
|
|
|
|
mod_kwargs.update(known_shmod_kwargs) |
|
|
|
|
mod_kwargs -= {'name_prefix', 'name_suffix'} |
|
|
|
|
|
|
|
|
|
class PythonDependency(SystemDependency): |
|
|
|
|
|
|
|
|
|
def __init__(self, python_holder, environment, kwargs): |
|
|
|
|
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 |
|
|
|
|
if T.TYPE_CHECKING: |
|
|
|
|
_Base = ExternalDependency |
|
|
|
|
else: |
|
|
|
|
_Base = object |
|
|
|
|
|
|
|
|
|
class _PythonDependencyBase(_Base): |
|
|
|
|
|
|
|
|
|
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 |
|
|
|
@ -56,75 +74,36 @@ 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 PythonFrameworkDependency(ExtraFrameworkDependency, _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'): |
|
|
|
|
ExtraFrameworkDependency.__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 |
|
|
|
|
class PythonSystemDependency(SystemDependency, _PythonDependencyBase): |
|
|
|
|
|
|
|
|
|
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)) |
|
|
|
|
|
|
|
|
|
# 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})')) |
|
|
|
|
else: |
|
|
|
|
mlog.log('Dependency', mlog.bold(self.name), 'found:', mlog.red('NO')) |
|
|
|
|
self._find_libpy(installation, environment) |
|
|
|
|
|
|
|
|
|
def _find_libpy(self, python_holder, environment): |
|
|
|
|
def _find_libpy(self, python_holder: 'PythonInstallation', environment: 'Environment') -> None: |
|
|
|
|
if python_holder.is_pypy: |
|
|
|
|
if self.major_version == 3: |
|
|
|
|
libname = 'pypy3-c' |
|
|
|
@ -153,7 +132,7 @@ class PythonDependency(SystemDependency): |
|
|
|
|
|
|
|
|
|
self.compile_args += ['-I' + path for path in inc_paths if path] |
|
|
|
|
|
|
|
|
|
def get_windows_python_arch(self): |
|
|
|
|
def _get_windows_python_arch(self) -> T.Optional[str]: |
|
|
|
|
if self.platform == 'mingw': |
|
|
|
|
pycc = self.variables.get('CC') |
|
|
|
|
if pycc.startswith('x86_64'): |
|
|
|
@ -171,7 +150,7 @@ class PythonDependency(SystemDependency): |
|
|
|
|
mlog.log(f'Unknown Windows Python platform {self.platform!r}') |
|
|
|
|
return None |
|
|
|
|
|
|
|
|
|
def get_windows_link_args(self): |
|
|
|
|
def _get_windows_link_args(self) -> T.Optional[T.List[str]]: |
|
|
|
|
if self.platform.startswith('win'): |
|
|
|
|
vernum = self.variables.get('py_version_nodot') |
|
|
|
|
if self.static: |
|
|
|
@ -179,7 +158,7 @@ class PythonDependency(SystemDependency): |
|
|
|
|
else: |
|
|
|
|
comp = self.get_compiler() |
|
|
|
|
if comp.id == "gcc": |
|
|
|
|
libpath = f'python{vernum}.dll' |
|
|
|
|
libpath = Path(f'python{vernum}.dll') |
|
|
|
|
else: |
|
|
|
|
libpath = Path('libs') / f'python{vernum}.lib' |
|
|
|
|
lib = Path(self.variables.get('base')) / libpath |
|
|
|
@ -189,17 +168,20 @@ class PythonDependency(SystemDependency): |
|
|
|
|
else: |
|
|
|
|
libname = self.variables.get('LDLIBRARY') |
|
|
|
|
lib = Path(self.variables.get('LIBDIR')) / libname |
|
|
|
|
else: |
|
|
|
|
raise mesonlib.MesonBugException( |
|
|
|
|
'On a Windows path, but the OS doesn\'t appear to be Windows or MinGW.') |
|
|
|
|
if not lib.exists(): |
|
|
|
|
mlog.log('Could not find Python3 library {!r}'.format(str(lib))) |
|
|
|
|
return None |
|
|
|
|
return [str(lib)] |
|
|
|
|
|
|
|
|
|
def _find_libpy_windows(self, env): |
|
|
|
|
def _find_libpy_windows(self, env: 'Environment') -> None: |
|
|
|
|
''' |
|
|
|
|
Find python3 libraries on Windows and also verify that the arch matches |
|
|
|
|
what we are building for. |
|
|
|
|
''' |
|
|
|
|
pyarch = self.get_windows_python_arch() |
|
|
|
|
pyarch = self._get_windows_python_arch() |
|
|
|
|
if pyarch is None: |
|
|
|
|
self.is_found = False |
|
|
|
|
return |
|
|
|
@ -221,7 +203,7 @@ class PythonDependency(SystemDependency): |
|
|
|
|
self.is_found = False |
|
|
|
|
return |
|
|
|
|
# This can fail if the library is not found |
|
|
|
|
largs = self.get_windows_link_args() |
|
|
|
|
largs = self._get_windows_link_args() |
|
|
|
|
if largs is None: |
|
|
|
|
self.is_found = False |
|
|
|
|
return |
|
|
|
@ -240,23 +222,57 @@ class PythonDependency(SystemDependency): |
|
|
|
|
|
|
|
|
|
self.is_found = True |
|
|
|
|
|
|
|
|
|
@staticmethod |
|
|
|
|
def get_methods(): |
|
|
|
|
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, kwargs): |
|
|
|
|
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'] = [] |
|
|
|
|
pkg_version = installation.variables.get('LDVERSION') or installation.version |
|
|
|
|
|
|
|
|
|
if DependencyMethods.PKGCONFIG in methods: |
|
|
|
|
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 |
|
|
|
|
|
|
|
|
|
INTROSPECT_COMMAND = '''import sysconfig |
|
|
|
|
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)) |
|
|
|
|
|
|
|
|
|
if DependencyMethods.EXTRAFRAMEWORK in methods: |
|
|
|
|
nkwargs = kwargs.copy() |
|
|
|
|
if mesonlib.version_compare(pkg_version, '>= 3'): |
|
|
|
|
# There is a python in /System/Library/Frameworks, but thats python 2.x, |
|
|
|
|
# Python 3 will always be in /Library |
|
|
|
|
nkwargs['paths'] = ['/Library/Frameworks'] |
|
|
|
|
candidates.append(functools.partial(PythonFrameworkDependency, 'Python', env, nkwargs, installation)) |
|
|
|
|
|
|
|
|
|
return candidates |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
INTROSPECT_COMMAND = '''\ |
|
|
|
|
import sysconfig |
|
|
|
|
import json |
|
|
|
|
import sys |
|
|
|
|
|
|
|
|
@ -279,21 +295,60 @@ print (json.dumps ({ |
|
|
|
|
})) |
|
|
|
|
''' |
|
|
|
|
|
|
|
|
|
if T.TYPE_CHECKING: |
|
|
|
|
class PythonIntrospectionDict(TypedDict): |
|
|
|
|
|
|
|
|
|
install_paths: T.Dict[str, str] |
|
|
|
|
is_pypy: bool |
|
|
|
|
link_libpython: bool |
|
|
|
|
paths: T.Dict[str, str] |
|
|
|
|
platform: str |
|
|
|
|
variables: T.Dict[str, str] |
|
|
|
|
version: str |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PythonExternalProgram(ExternalProgram): |
|
|
|
|
def __init__(self, name: str, command: T.Optional[T.List[str]] = None, ext_prog: T.Optional[ExternalProgram] = None): |
|
|
|
|
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 = ext_prog.name |
|
|
|
|
self.command = ext_prog.command |
|
|
|
|
self.path = ext_prog.path |
|
|
|
|
self.info: T.Dict[str, str] = {} |
|
|
|
|
|
|
|
|
|
# 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 konw that the introspection data will be |
|
|
|
|
# complete |
|
|
|
|
self.info: 'PythonIntrospectionDict' = { |
|
|
|
|
'install_paths': {}, |
|
|
|
|
'is_pypy': False, |
|
|
|
|
'link_libpython': False, |
|
|
|
|
'paths': {}, |
|
|
|
|
'platform': 'sentinal', |
|
|
|
|
'variables': {}, |
|
|
|
|
'version': '0.0', |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_PURE_KW = KwargInfo('pure', bool, default=True) |
|
|
|
|
_SUBDIR_KW = KwargInfo('subdir', str, default='') |
|
|
|
|
|
|
|
|
|
if T.TYPE_CHECKING: |
|
|
|
|
|
|
|
|
|
class PyInstallKw(TypedDict): |
|
|
|
|
|
|
|
|
|
pure: bool |
|
|
|
|
subdir: str |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PythonInstallation(ExternalProgramHolder): |
|
|
|
|
def __init__(self, python, interpreter): |
|
|
|
|
def __init__(self, python: 'PythonExternalProgram', interpreter: 'Interpreter'): |
|
|
|
|
ExternalProgramHolder.__init__(self, python, interpreter) |
|
|
|
|
info = python.info |
|
|
|
|
prefix = self.interpreter.environment.coredata.get_option(mesonlib.OptionKey('prefix')) |
|
|
|
|
assert isinstance(prefix, str), 'for mypy' |
|
|
|
|
self.variables = info['variables'] |
|
|
|
|
self.paths = info['paths'] |
|
|
|
|
install_paths = info['install_paths'] |
|
|
|
@ -318,7 +373,7 @@ class PythonInstallation(ExternalProgramHolder): |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
@permittedKwargs(mod_kwargs) |
|
|
|
|
def extension_module_method(self, args, kwargs): |
|
|
|
|
def extension_module_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> 'SharedModule': |
|
|
|
|
if 'subdir' in kwargs and 'install_dir' in kwargs: |
|
|
|
|
raise InvalidArguments('"subdir" and "install_dir" are mutually exclusive') |
|
|
|
|
|
|
|
|
@ -335,7 +390,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 |
|
|
|
@ -354,136 +409,113 @@ class PythonInstallation(ExternalProgramHolder): |
|
|
|
|
|
|
|
|
|
@permittedKwargs(permitted_dependency_kwargs | {'embed'}) |
|
|
|
|
@FeatureNewKwargs('python_installation.dependency', '0.53.0', ['embed']) |
|
|
|
|
def dependency_method(self, args, kwargs): |
|
|
|
|
if args: |
|
|
|
|
mlog.warning('python_installation.dependency() does not take any ' |
|
|
|
|
'positional arguments. It always returns a Python ' |
|
|
|
|
'dependency. This will become an error in the future.', |
|
|
|
|
location=self.interpreter.current_node) |
|
|
|
|
@noPosargs |
|
|
|
|
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 |
|
|
|
|
|
|
|
|
|
@permittedKwargs(['pure', 'subdir']) |
|
|
|
|
def install_sources_method(self, args, kwargs): |
|
|
|
|
pure = kwargs.pop('pure', True) |
|
|
|
|
if not isinstance(pure, bool): |
|
|
|
|
raise InvalidArguments('"pure" argument must be a boolean.') |
|
|
|
|
|
|
|
|
|
subdir = kwargs.pop('subdir', '') |
|
|
|
|
if not isinstance(subdir, str): |
|
|
|
|
raise InvalidArguments('"subdir" argument must be a string.') |
|
|
|
|
|
|
|
|
|
if pure: |
|
|
|
|
kwargs['install_dir'] = os.path.join(self.purelib_install_path, subdir) |
|
|
|
|
else: |
|
|
|
|
kwargs['install_dir'] = os.path.join(self.platlib_install_path, subdir) |
|
|
|
|
return dep |
|
|
|
|
|
|
|
|
|
return self.interpreter.func_install_data(None, args, kwargs) |
|
|
|
|
@typed_pos_args('install_data', varargs=(str, mesonlib.File)) |
|
|
|
|
@typed_kwargs('python_installation.install_sources', _PURE_KW, _SUBDIR_KW) |
|
|
|
|
def install_sources_method(self, args: T.Tuple[T.List[T.Union[str, mesonlib.File]]], |
|
|
|
|
kwargs: 'PyInstallKw') -> 'Data': |
|
|
|
|
return self.interpreter.install_data_impl( |
|
|
|
|
self.interpreter.source_strings_to_files(args[0]), |
|
|
|
|
self._get_install_dir_impl(kwargs['pure'], kwargs['subdir']), |
|
|
|
|
mesonlib.FileMode(), |
|
|
|
|
None) |
|
|
|
|
|
|
|
|
|
@noPosargs |
|
|
|
|
@permittedKwargs(['pure', 'subdir']) |
|
|
|
|
def get_install_dir_method(self, args, kwargs): |
|
|
|
|
pure = kwargs.pop('pure', True) |
|
|
|
|
if not isinstance(pure, bool): |
|
|
|
|
raise InvalidArguments('"pure" argument must be a boolean.') |
|
|
|
|
@typed_kwargs('python_installation.install_dir', _PURE_KW, _SUBDIR_KW) |
|
|
|
|
def get_install_dir_method(self, args: T.List['TYPE_var'], kwargs: 'PyInstallKw') -> str: |
|
|
|
|
return self._get_install_dir_impl(kwargs['pure'], kwargs['subdir']) |
|
|
|
|
|
|
|
|
|
subdir = kwargs.pop('subdir', '') |
|
|
|
|
if not isinstance(subdir, str): |
|
|
|
|
raise InvalidArguments('"subdir" argument must be a string.') |
|
|
|
|
|
|
|
|
|
if pure: |
|
|
|
|
res = os.path.join(self.purelib_install_path, subdir) |
|
|
|
|
else: |
|
|
|
|
res = os.path.join(self.platlib_install_path, subdir) |
|
|
|
|
|
|
|
|
|
return res |
|
|
|
|
def _get_install_dir_impl(self, pure: bool, subdir: str) -> str: |
|
|
|
|
return os.path.join( |
|
|
|
|
self.purelib_install_path if pure else self.platlib_install_path, subdir) |
|
|
|
|
|
|
|
|
|
@noPosargs |
|
|
|
|
@noKwargs |
|
|
|
|
def language_version_method(self, args, kwargs): |
|
|
|
|
def language_version_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> str: |
|
|
|
|
return self.version |
|
|
|
|
|
|
|
|
|
@typed_pos_args('python_installation.has_path', str) |
|
|
|
|
@noKwargs |
|
|
|
|
def has_path_method(self, args, kwargs): |
|
|
|
|
if len(args) != 1: |
|
|
|
|
raise InvalidArguments('has_path takes exactly one positional argument.') |
|
|
|
|
path_name = args[0] |
|
|
|
|
if not isinstance(path_name, str): |
|
|
|
|
raise InvalidArguments('has_path argument must be a string.') |
|
|
|
|
|
|
|
|
|
return path_name in self.paths |
|
|
|
|
def has_path_method(self, args: T.Tuple[str], kwargs: 'TYPE_kwargs') -> bool: |
|
|
|
|
return args[0] in self.paths |
|
|
|
|
|
|
|
|
|
@typed_pos_args('python_installation.get_path', str, optargs=[object]) |
|
|
|
|
@noKwargs |
|
|
|
|
def get_path_method(self, args, kwargs): |
|
|
|
|
if len(args) not in (1, 2): |
|
|
|
|
raise InvalidArguments('get_path must have one or two arguments.') |
|
|
|
|
path_name = args[0] |
|
|
|
|
if not isinstance(path_name, str): |
|
|
|
|
raise InvalidArguments('get_path argument must be a string.') |
|
|
|
|
|
|
|
|
|
def get_path_method(self, args: T.Tuple[str, T.Optional['TYPE_var']], kwargs: 'TYPE_kwargs') -> 'TYPE_var': |
|
|
|
|
path_name, fallback = args |
|
|
|
|
try: |
|
|
|
|
path = self.paths[path_name] |
|
|
|
|
return self.paths[path_name] |
|
|
|
|
except KeyError: |
|
|
|
|
if len(args) == 2: |
|
|
|
|
path = args[1] |
|
|
|
|
else: |
|
|
|
|
if fallback is not None: |
|
|
|
|
return fallback |
|
|
|
|
raise InvalidArguments(f'{path_name} is not a valid path name') |
|
|
|
|
|
|
|
|
|
return path |
|
|
|
|
|
|
|
|
|
@typed_pos_args('python_installation.has_variable', str) |
|
|
|
|
@noKwargs |
|
|
|
|
def has_variable_method(self, args, kwargs): |
|
|
|
|
if len(args) != 1: |
|
|
|
|
raise InvalidArguments('has_variable takes exactly one positional argument.') |
|
|
|
|
var_name = args[0] |
|
|
|
|
if not isinstance(var_name, str): |
|
|
|
|
raise InvalidArguments('has_variable argument must be a string.') |
|
|
|
|
|
|
|
|
|
return var_name in self.variables |
|
|
|
|
def has_variable_method(self, args: T.Tuple[str], kwargs: 'TYPE_kwargs') -> bool: |
|
|
|
|
return args[0] in self.variables |
|
|
|
|
|
|
|
|
|
@typed_pos_args('python_installation.get_variable', str, optargs=[object]) |
|
|
|
|
@noKwargs |
|
|
|
|
def get_variable_method(self, args, kwargs): |
|
|
|
|
if len(args) not in (1, 2): |
|
|
|
|
raise InvalidArguments('get_variable must have one or two arguments.') |
|
|
|
|
var_name = args[0] |
|
|
|
|
if not isinstance(var_name, str): |
|
|
|
|
raise InvalidArguments('get_variable argument must be a string.') |
|
|
|
|
|
|
|
|
|
def get_variable_method(self, args: T.Tuple[str, T.Optional['TYPE_var']], kwargs: 'TYPE_kwargs') -> 'TYPE_var': |
|
|
|
|
var_name, fallback = args |
|
|
|
|
try: |
|
|
|
|
var = self.variables[var_name] |
|
|
|
|
return self.variables[var_name] |
|
|
|
|
except KeyError: |
|
|
|
|
if len(args) == 2: |
|
|
|
|
var = args[1] |
|
|
|
|
else: |
|
|
|
|
if fallback is not None: |
|
|
|
|
return fallback |
|
|
|
|
raise InvalidArguments(f'{var_name} is not a valid variable name') |
|
|
|
|
|
|
|
|
|
return var |
|
|
|
|
|
|
|
|
|
@noPosargs |
|
|
|
|
@noKwargs |
|
|
|
|
@FeatureNew('Python module path method', '0.50.0') |
|
|
|
|
def path_method(self, args, kwargs): |
|
|
|
|
def path_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> str: |
|
|
|
|
return super().path_method(args, kwargs) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if T.TYPE_CHECKING: |
|
|
|
|
from ..interpreter.kwargs import ExtractRequired |
|
|
|
|
|
|
|
|
|
class FindInstallationKw(ExtractRequired): |
|
|
|
|
|
|
|
|
|
disabler: bool |
|
|
|
|
modules: T.List[str] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PythonModule(ExtensionModule): |
|
|
|
|
|
|
|
|
|
@FeatureNew('Python Module', '0.46.0') |
|
|
|
|
def __init__(self, *args, **kwargs): |
|
|
|
|
super().__init__(*args, **kwargs) |
|
|
|
|
def __init__(self, interpreter: 'Interpreter') -> None: |
|
|
|
|
super().__init__(interpreter) |
|
|
|
|
self.methods.update({ |
|
|
|
|
'find_installation': self.find_installation, |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
# https://www.python.org/dev/peps/pep-0397/ |
|
|
|
|
def _get_win_pythonpath(self, name_or_path): |
|
|
|
|
@staticmethod |
|
|
|
|
def _get_win_pythonpath(name_or_path: str) -> T.Optional[str]: |
|
|
|
|
if name_or_path not in ['python2', 'python3']: |
|
|
|
|
return None |
|
|
|
|
if not shutil.which('py'): |
|
|
|
@ -498,32 +530,41 @@ class PythonModule(ExtensionModule): |
|
|
|
|
else: |
|
|
|
|
return None |
|
|
|
|
|
|
|
|
|
def _check_version(self, name_or_path, version): |
|
|
|
|
@staticmethod |
|
|
|
|
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': |
|
|
|
|
return mesonlib.version_compare(version, '>= 3.0') |
|
|
|
|
return True |
|
|
|
|
|
|
|
|
|
@FeatureNewKwargs('python.find_installation', '0.49.0', ['disabler']) |
|
|
|
|
@FeatureNewKwargs('python.find_installation', '0.51.0', ['modules']) |
|
|
|
|
@disablerIfNotFound |
|
|
|
|
@permittedKwargs({'required', 'modules'}) |
|
|
|
|
def find_installation(self, state, args, kwargs): |
|
|
|
|
@typed_pos_args('python.find_installation', optargs=[str]) |
|
|
|
|
@typed_kwargs( |
|
|
|
|
'python.find_installation', |
|
|
|
|
KwargInfo('required', (bool, UserFeatureOption), default=True), |
|
|
|
|
KwargInfo('disabler', bool, default=False, since='0.49.0'), |
|
|
|
|
KwargInfo('modules', ContainerTypeInfo(list, str), listify=True, default=[], since='0.51.0'), |
|
|
|
|
) |
|
|
|
|
def find_installation(self, state: 'ModuleState', args: T.Tuple[T.Optional[str]], |
|
|
|
|
kwargs: 'FindInstallationKw') -> ExternalProgram: |
|
|
|
|
feature_check = FeatureNew('Passing "feature" option to find_installation', '0.48.0') |
|
|
|
|
disabled, required, feature = extract_required_kwarg(kwargs, state.subproject, feature_check) |
|
|
|
|
want_modules = mesonlib.extract_as_list(kwargs, 'modules') # type: T.List[str] |
|
|
|
|
found_modules = [] # type: T.List[str] |
|
|
|
|
missing_modules = [] # type: T.List[str] |
|
|
|
|
|
|
|
|
|
if len(args) > 1: |
|
|
|
|
raise InvalidArguments('find_installation takes zero or one positional argument.') |
|
|
|
|
|
|
|
|
|
name_or_path = state.environment.lookup_binary_entry(MachineChoice.HOST, 'python') |
|
|
|
|
if name_or_path is None and args: |
|
|
|
|
name_or_path = args[0] |
|
|
|
|
if not isinstance(name_or_path, str): |
|
|
|
|
raise InvalidArguments('find_installation argument must be a string.') |
|
|
|
|
want_modules = kwargs['modules'] |
|
|
|
|
found_modules: T.List[str] = [] |
|
|
|
|
missing_modules: T.List[str] = [] |
|
|
|
|
|
|
|
|
|
# FIXME: this code is *full* of sharp corners. It assumes that it's |
|
|
|
|
# going to get a string value (or now a list of lenght 1), of `python2` |
|
|
|
|
# or `python3` which is completely nonsense. On windows the value could |
|
|
|
|
# easily be `['py', '-3']`, or `['py', '-3.7']` to get a very specific |
|
|
|
|
# version of python. On Linux we might want a python that's not in |
|
|
|
|
# $PATH, or that uses a wrapper of some kind. |
|
|
|
|
np: T.List[str] = state.environment.lookup_binary_entry(MachineChoice.HOST, 'python') or [] |
|
|
|
|
fallback = args[0] |
|
|
|
|
if not np and fallback is not None: |
|
|
|
|
np = [fallback] |
|
|
|
|
name_or_path = np[0] if np else None |
|
|
|
|
|
|
|
|
|
if disabled: |
|
|
|
|
mlog.log('Program', name_or_path or 'python', 'found:', mlog.red('NO'), '(disabled by:', mlog.bold(feature), ')') |
|
|
|
@ -550,7 +591,7 @@ class PythonModule(ExtensionModule): |
|
|
|
|
|
|
|
|
|
if python.found() and want_modules: |
|
|
|
|
for mod in want_modules: |
|
|
|
|
p, out, err = mesonlib.Popen_safe( |
|
|
|
|
p, *_ = mesonlib.Popen_safe( |
|
|
|
|
python.command + |
|
|
|
|
['-c', f'import {mod}']) |
|
|
|
|
if p.returncode != 0: |
|
|
|
@ -558,7 +599,7 @@ class PythonModule(ExtensionModule): |
|
|
|
|
else: |
|
|
|
|
found_modules.append(mod) |
|
|
|
|
|
|
|
|
|
msg = ['Program', python.name] |
|
|
|
|
msg: T.List['mlog.TV_Loggable'] = ['Program', python.name] |
|
|
|
|
if want_modules: |
|
|
|
|
msg.append('({})'.format(', '.join(want_modules))) |
|
|
|
|
msg.append('found:') |
|
|
|
@ -582,9 +623,9 @@ class PythonModule(ExtensionModule): |
|
|
|
|
return NonExistingExternalProgram() |
|
|
|
|
else: |
|
|
|
|
# Sanity check, we expect to have something that at least quacks in tune |
|
|
|
|
try: |
|
|
|
|
cmd = python.get_command() + ['-c', INTROSPECT_COMMAND] |
|
|
|
|
p, stdout, stderr = mesonlib.Popen_safe(cmd) |
|
|
|
|
try: |
|
|
|
|
info = json.loads(stdout) |
|
|
|
|
except json.JSONDecodeError: |
|
|
|
|
info = None |
|
|
|
@ -595,7 +636,7 @@ class PythonModule(ExtensionModule): |
|
|
|
|
mlog.debug(stderr) |
|
|
|
|
|
|
|
|
|
if isinstance(info, dict) and 'version' in info and self._check_version(name_or_path, info['version']): |
|
|
|
|
python.info = info |
|
|
|
|
python.info = T.cast('PythonIntrospectionDict', info) |
|
|
|
|
return python |
|
|
|
|
else: |
|
|
|
|
if required: |
|
|
|
@ -605,7 +646,7 @@ class PythonModule(ExtensionModule): |
|
|
|
|
raise mesonlib.MesonBugException('Unreachable code was reached (PythonModule.find_installation).') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def initialize(*args, **kwargs): |
|
|
|
|
mod = PythonModule(*args, **kwargs) |
|
|
|
|
def initialize(interpreter: 'Interpreter') -> PythonModule: |
|
|
|
|
mod = PythonModule(interpreter) |
|
|
|
|
mod.interpreter.append_holder_map(PythonExternalProgram, PythonInstallation) |
|
|
|
|
return mod |
|
|
|
|