Merge pull request #8992 from dcbaker/submit/modernize-python-module-dependency

Cleanup the python module
pull/9016/head
Jussi Pakkanen 3 years ago committed by GitHub
commit 4703f4c244
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      docs/markdown/snippets/python_dependency_args_removed.md
  2. 13
      mesonbuild/dependencies/base.py
  3. 6
      mesonbuild/dependencies/cmake.py
  4. 4
      mesonbuild/dependencies/coarrays.py
  5. 6
      mesonbuild/dependencies/configtool.py
  6. 17
      mesonbuild/dependencies/dev.py
  7. 6
      mesonbuild/dependencies/dub.py
  8. 6
      mesonbuild/dependencies/framework.py
  9. 44
      mesonbuild/dependencies/misc.py
  10. 6
      mesonbuild/dependencies/pkgconfig.py
  11. 18
      mesonbuild/dependencies/ui.py
  12. 9
      mesonbuild/interpreter/interpreter.py
  13. 12
      mesonbuild/modules/pkgconfig.py
  14. 473
      mesonbuild/modules/python.py
  15. 18
      run_unittests.py

@ -0,0 +1,3 @@
## The Python Modules dependency method no longer accepts positional arguments
Previously these were igrnoed with a warning, now they're a hard error.

@ -89,7 +89,6 @@ class Dependency(HoldableObject):
# If None, self.link_args will be used
self.raw_link_args: T.Optional[T.List[str]] = None
self.sources: T.List['FileOrString'] = []
self.methods = process_method_kw(self.get_methods(), kwargs)
self.include_type = self._process_include_type_kw(kwargs)
self.ext_deps: T.List[Dependency] = []
@ -148,10 +147,6 @@ class Dependency(HoldableObject):
As an example, gtest-all.cc when using GTest."""
return self.sources
@staticmethod
def get_methods() -> T.List[DependencyMethods]:
return [DependencyMethods.AUTO]
def get_name(self) -> str:
return self.name
@ -547,10 +542,6 @@ class SystemDependency(ExternalDependency):
super().__init__(DependencyTypeName('system'), env, kwargs, language=language)
self.name = name
@staticmethod
def get_methods() -> T.List[DependencyMethods]:
return [DependencyMethods.SYSTEM]
def log_tried(self) -> str:
return 'system'
@ -564,9 +555,5 @@ class BuiltinDependency(ExternalDependency):
super().__init__(DependencyTypeName('builtin'), env, kwargs, language=language)
self.name = name
@staticmethod
def get_methods() -> T.List[DependencyMethods]:
return [DependencyMethods.BUILTIN]
def log_tried(self) -> str:
return 'builtin'

@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from .base import ExternalDependency, DependencyException, DependencyMethods, DependencyTypeName
from .base import ExternalDependency, DependencyException, DependencyTypeName
from ..mesonlib import is_windows, MesonException, OptionKey, PerMachine, stringlistify, extract_as_list
from ..mesondata import mesondata
from ..cmake import CMakeExecutor, CMakeTraceParser, CMakeException, CMakeToolchain, CMakeExecScope, check_cmake_args
@ -632,10 +632,6 @@ class CMakeDependency(ExternalDependency):
build_dir = self._setup_cmake_dir(cmake_file)
return self.cmakebin.call(args, build_dir, env=env)
@staticmethod
def get_methods() -> T.List[DependencyMethods]:
return [DependencyMethods.CMAKE]
def log_tried(self) -> str:
return self.type_name

@ -84,7 +84,3 @@ class CoarrayDependency(SystemDependency):
elif cid == 'nagfor':
# NAG doesn't require any special arguments for Coarray
self.is_found = True
@staticmethod
def get_methods() -> T.List[DependencyMethods]:
return [DependencyMethods.AUTO, DependencyMethods.CMAKE, DependencyMethods.PKGCONFIG]

@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from .base import ExternalDependency, DependencyException, DependencyMethods, DependencyTypeName
from .base import ExternalDependency, DependencyException, DependencyTypeName
from ..mesonlib import listify, Popen_safe, split_args, version_compare, version_compare_many
from ..programs import find_external_program
from .. import mlog
@ -138,10 +138,6 @@ class ConfigToolDependency(ExternalDependency):
return []
return split_args(out)
@staticmethod
def get_methods() -> T.List[DependencyMethods]:
return [DependencyMethods.AUTO, DependencyMethods.CONFIG_TOOL]
def get_configtool_variable(self, variable_name: str) -> str:
p, out, _ = Popen_safe(self.config + [f'--{variable_name}'])
if p.returncode != 0:

@ -106,10 +106,6 @@ class GTestDependencySystem(SystemDependency):
def log_tried(self) -> str:
return 'system'
@staticmethod
def get_methods() -> T.List[DependencyMethods]:
return [DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM]
class GTestDependencyPC(PkgConfigDependency):
@ -181,10 +177,6 @@ class GMockDependencySystem(SystemDependency):
def log_tried(self) -> str:
return 'system'
@staticmethod
def get_methods() -> T.List[DependencyMethods]:
return [DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM]
class GMockDependencyPC(PkgConfigDependency):
@ -508,11 +500,6 @@ class ZlibSystemDependency(SystemDependency):
self.version = v.strip('"')
@staticmethod
def get_methods() -> T.List[DependencyMethods]:
return [DependencyMethods.SYSTEM]
class JDKSystemDependency(SystemDependency):
def __init__(self, environment: 'Environment', kwargs: T.Dict[str, T.Any]):
super().__init__('jdk', environment, kwargs)
@ -544,10 +531,6 @@ class JDKSystemDependency(SystemDependency):
self.compile_args.append(f'-I{java_home_include / platform_include_dir}')
self.is_found = True
@staticmethod
def get_methods() -> T.List[DependencyMethods]:
return [DependencyMethods.SYSTEM]
@staticmethod
def __machine_info_to_platform_include_dir(m: 'MachineInfo') -> T.Optional[str]:
"""Translates the machine information to the platform-dependent include directory

@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from .base import ExternalDependency, DependencyException, DependencyMethods, DependencyTypeName
from .base import ExternalDependency, DependencyException, DependencyTypeName
from .pkgconfig import PkgConfigDependency
from ..mesonlib import Popen_safe
from ..programs import ExternalProgram
@ -234,7 +234,3 @@ class DubDependency(ExternalDependency):
else:
mlog.log('Found DUB:', mlog.red('NO'))
return dubbin
@staticmethod
def get_methods() -> T.List[DependencyMethods]:
return [DependencyMethods.DUB]

@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from .base import DependencyTypeName, ExternalDependency, DependencyException, DependencyMethods
from .base import DependencyTypeName, ExternalDependency, DependencyException
from ..mesonlib import MesonException, Version, stringlistify
from .. import mlog
from pathlib import Path
@ -112,10 +112,6 @@ class ExtraFrameworkDependency(ExternalDependency):
return trial.as_posix()
return None
@staticmethod
def get_methods() -> T.List[DependencyMethods]:
return [DependencyMethods.EXTRAFRAMEWORK]
def log_info(self) -> str:
return self.framework_path or ''

@ -125,10 +125,6 @@ class ThreadDependency(SystemDependency):
self.compile_args = self.clib_compiler.thread_flags(environment)
self.link_args = self.clib_compiler.thread_link_flags(environment)
@staticmethod
def get_methods() -> T.List[DependencyMethods]:
return [DependencyMethods.AUTO, DependencyMethods.CMAKE]
class BlocksDependency(SystemDependency):
def __init__(self, environment: 'Environment', kwargs: T.Dict[str, T.Any]) -> None:
@ -262,15 +258,6 @@ class Python3DependencySystem(SystemDependency):
self.version = sysconfig.get_config_var('py_version')
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]
def log_tried(self) -> str:
return 'sysconfig'
@ -287,10 +274,6 @@ class PcapDependencyConfigTool(ConfigToolDependency):
self.link_args = self.get_config_value(['--libs'], 'link_args')
self.version = self.get_pcap_lib_version()
@staticmethod
def get_methods() -> T.List[DependencyMethods]:
return [DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL]
def get_pcap_lib_version(self) -> T.Optional[str]:
# Since we seem to need to run a program to discover the pcap version,
# we can't do that when cross-compiling
@ -317,13 +300,6 @@ class CupsDependencyConfigTool(ConfigToolDependency):
self.compile_args = self.get_config_value(['--cflags'], 'compile_args')
self.link_args = self.get_config_value(['--ldflags', '--libs'], 'link_args')
@staticmethod
def get_methods() -> T.List[DependencyMethods]:
if mesonlib.is_osx():
return [DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL, DependencyMethods.EXTRAFRAMEWORK, DependencyMethods.CMAKE]
else:
return [DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL, DependencyMethods.CMAKE]
class LibWmfDependencyConfigTool(ConfigToolDependency):
@ -337,10 +313,6 @@ class LibWmfDependencyConfigTool(ConfigToolDependency):
self.compile_args = self.get_config_value(['--cflags'], 'compile_args')
self.link_args = self.get_config_value(['--libs'], 'link_args')
@staticmethod
def get_methods() -> T.List[DependencyMethods]:
return [DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL]
class LibGCryptDependencyConfigTool(ConfigToolDependency):
@ -355,10 +327,6 @@ class LibGCryptDependencyConfigTool(ConfigToolDependency):
self.link_args = self.get_config_value(['--libs'], 'link_args')
self.version = self.get_config_value(['--version'], 'version')[0]
@staticmethod
def get_methods() -> T.List[DependencyMethods]:
return [DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL]
class GpgmeDependencyConfigTool(ConfigToolDependency):
@ -373,10 +341,6 @@ class GpgmeDependencyConfigTool(ConfigToolDependency):
self.link_args = self.get_config_value(['--libs'], 'link_args')
self.version = self.get_config_value(['--version'], 'version')[0]
@staticmethod
def get_methods() -> T.List[DependencyMethods]:
return [DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL]
class ShadercDependency(SystemDependency):
@ -406,10 +370,6 @@ class ShadercDependency(SystemDependency):
def log_tried(self) -> str:
return 'system'
@staticmethod
def get_methods() -> T.List[DependencyMethods]:
return [DependencyMethods.SYSTEM, DependencyMethods.PKGCONFIG]
class CursesConfigToolDependency(ConfigToolDependency):
@ -481,10 +441,6 @@ class CursesSystemDependency(SystemDependency):
if self.is_found:
break
@staticmethod
def get_methods() -> T.List[DependencyMethods]:
return [DependencyMethods.SYSTEM]
class IntlBuiltinDependency(BuiltinDependency):
def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]):

@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from .base import ExternalDependency, DependencyException, DependencyMethods, sort_libpaths, DependencyTypeName
from .base import ExternalDependency, DependencyException, sort_libpaths, DependencyTypeName
from ..mesonlib import LibType, MachineChoice, OptionKey, OrderedSet, PerMachine, Popen_safe
from ..programs import find_external_program, ExternalProgram
from .. import mlog
@ -396,10 +396,6 @@ class PkgConfigDependency(ExternalDependency):
mlog.debug(f'Got pkgconfig variable {variable_name} : {variable}')
return variable
@staticmethod
def get_methods() -> T.List[DependencyMethods]:
return [DependencyMethods.PKGCONFIG]
def check_pkgconfig(self, pkgbin: ExternalProgram) -> T.Optional[str]:
if not pkgbin.found():
mlog.log(f'Did not find pkg-config by name {pkgbin.name!r}')

@ -50,13 +50,6 @@ class GLDependencySystem(SystemDependency):
# FIXME: Detect version using self.clib_compiler
return
@staticmethod
def get_methods() -> T.List[DependencyMethods]:
if mesonlib.is_osx() or mesonlib.is_windows():
return [DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM]
else:
return [DependencyMethods.PKGCONFIG]
def log_tried(self) -> str:
return 'system'
@ -149,13 +142,6 @@ class SDL2DependencyConfigTool(ConfigToolDependency):
self.compile_args = self.get_config_value(['--cflags'], 'compile_args')
self.link_args = self.get_config_value(['--libs'], 'link_args')
@staticmethod
def get_methods() -> T.List[DependencyMethods]:
if mesonlib.is_osx():
return [DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL, DependencyMethods.EXTRAFRAMEWORK]
else:
return [DependencyMethods.PKGCONFIG, DependencyMethods.CONFIG_TOOL]
class WxDependency(ConfigToolDependency):
@ -251,10 +237,6 @@ class VulkanDependencySystem(SystemDependency):
self.link_args.append(lib)
return
@staticmethod
def get_methods() -> T.List[DependencyMethods]:
return [DependencyMethods.SYSTEM]
def log_tried(self) -> str:
return 'system'

@ -2006,9 +2006,12 @@ This will become a hard error in the future.''' % kwargs['input'], location=self
'"rename" and "sources" argument lists must be the same length if "rename" is given. '
f'Rename has {len(rename)} elements and sources has {len(sources)}.')
data = build.Data(
sources, kwargs['install_dir'], kwargs['install_mode'],
self.subproject, rename)
return self.install_data_impl(sources, kwargs['install_dir'], kwargs['install_mode'], rename)
def install_data_impl(self, sources: T.List[mesonlib.File], install_dir: str,
install_mode: FileMode, rename: T.Optional[str]) -> build.Data:
"""Just the implementation with no validation."""
data = build.Data(sources, install_dir, install_mode, self.subproject, rename)
self.build.data.append(data)
return data

@ -81,11 +81,6 @@ class DependenciesHelper:
if hasattr(obj, 'generated_pc'):
self._check_generated_pc_deprecation(obj)
processed_reqs.append(obj.generated_pc)
elif hasattr(obj, 'pcdep'):
pcdeps = mesonlib.listify(obj.pcdep)
for d in pcdeps:
processed_reqs.append(d.name)
self.add_version_reqs(d.name, obj.version_reqs)
elif isinstance(obj, dependencies.PkgConfigDependency):
if obj.found():
processed_reqs.append(obj.name)
@ -114,12 +109,7 @@ class DependenciesHelper:
processed_reqs = []
processed_cflags = []
for obj in libs:
if hasattr(obj, 'pcdep'):
pcdeps = mesonlib.listify(obj.pcdep)
for d in pcdeps:
processed_reqs.append(d.name)
self.add_version_reqs(d.name, obj.version_reqs)
elif hasattr(obj, 'generated_pc'):
if hasattr(obj, 'generated_pc'):
self._check_generated_pc_deprecation(obj)
processed_reqs.append(obj.generated_pc)
elif isinstance(obj, dependencies.PkgConfigDependency):

@ -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
# 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'
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))
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, 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
candidates.extend([
functools.partial(wrap_in_pythons_pc_dir, pkg_name, env, kwargs, installation),
functools.partial(PythonPkgConfigDependency, pkg_name, env, kwargs, installation)
])
INTROSPECT_COMMAND = '''import sysconfig
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
@ -268,7 +284,7 @@ def links_against_libpython():
cmd.ensure_finalized()
return bool(cmd.get_libraries(Extension('dummy', [])))
print (json.dumps ({
print(json.dumps({
'variables': sysconfig.get_config_vars(),
'paths': sysconfig.get_paths(),
'install_paths': install_paths,
@ -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.')
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)
@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'])
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:
raise InvalidArguments(f'{path_name} is not a valid path name')
return path
if fallback is not None:
return fallback
raise InvalidArguments(f'{path_name} is not a valid path name')
@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:
raise InvalidArguments(f'{var_name} is not a valid variable name')
return var
if fallback is not None:
return fallback
raise InvalidArguments(f'{var_name} is not a valid variable name')
@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
cmd = python.get_command() + ['-c', INTROSPECT_COMMAND]
p, stdout, stderr = mesonlib.Popen_safe(cmd)
try:
cmd = python.get_command() + ['-c', INTROSPECT_COMMAND]
p, stdout, stderr = mesonlib.Popen_safe(cmd)
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

@ -774,24 +774,6 @@ class InternalTests(unittest.TestCase):
kwargs = {'sources': [1, [2, [3]]]}
self.assertEqual([1, 2, 3], extract(kwargs, 'sources'))
def test_pkgconfig_module(self):
dummystate = mock.Mock()
dummystate.subproject = 'dummy'
_mock = mock.Mock(spec=mesonbuild.dependencies.ExternalDependency)
_mock.pcdep = mock.Mock()
_mock.pcdep.name = "some_name"
_mock.version_reqs = []
# pkgconfig dependency as lib
deps = mesonbuild.modules.pkgconfig.DependenciesHelper(dummystate, "thislib")
deps.add_pub_libs([_mock])
self.assertEqual(deps.format_reqs(deps.pub_reqs), "some_name")
# pkgconfig dependency as requires
deps = mesonbuild.modules.pkgconfig.DependenciesHelper(dummystate, "thislib")
deps.add_pub_reqs([_mock])
self.assertEqual(deps.format_reqs(deps.pub_reqs), "some_name")
def _test_all_naming(self, cc, env, patterns, platform):
shr = patterns[platform]['shared']
stc = patterns[platform]['static']

Loading…
Cancel
Save