diff options
author | Jussi Pakkanen <jpakkane@gmail.com> | 2021-07-22 01:16:24 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-07-22 01:16:24 +0300 |
commit | 4703f4c2444c4a5d61645c4d3d1118f87946335a (patch) | |
tree | de55e0127d82859d553b0850f3c0b9c44911034a | |
parent | 06bf056264e5614ecd779b0e785fb03bba1dfb63 (diff) | |
parent | b4e826bff3c9e72230634fb0f7f10904b777d795 (diff) | |
download | meson-4703f4c2444c4a5d61645c4d3d1118f87946335a.zip meson-4703f4c2444c4a5d61645c4d3d1118f87946335a.tar.gz meson-4703f4c2444c4a5d61645c4d3d1118f87946335a.tar.bz2 |
Merge pull request #8992 from dcbaker/submit/modernize-python-module-dependency
Cleanup the python module
-rw-r--r-- | docs/markdown/snippets/python_dependency_args_removed.md | 3 | ||||
-rw-r--r-- | mesonbuild/dependencies/base.py | 13 | ||||
-rw-r--r-- | mesonbuild/dependencies/cmake.py | 6 | ||||
-rw-r--r-- | mesonbuild/dependencies/coarrays.py | 4 | ||||
-rw-r--r-- | mesonbuild/dependencies/configtool.py | 6 | ||||
-rw-r--r-- | mesonbuild/dependencies/dev.py | 17 | ||||
-rw-r--r-- | mesonbuild/dependencies/dub.py | 6 | ||||
-rw-r--r-- | mesonbuild/dependencies/framework.py | 6 | ||||
-rw-r--r-- | mesonbuild/dependencies/misc.py | 44 | ||||
-rw-r--r-- | mesonbuild/dependencies/pkgconfig.py | 6 | ||||
-rw-r--r-- | mesonbuild/dependencies/ui.py | 18 | ||||
-rw-r--r-- | mesonbuild/interpreter/interpreter.py | 9 | ||||
-rw-r--r-- | mesonbuild/modules/pkgconfig.py | 12 | ||||
-rw-r--r-- | mesonbuild/modules/python.py | 473 | ||||
-rwxr-xr-x | run_unittests.py | 18 |
15 files changed, 272 insertions, 369 deletions
diff --git a/docs/markdown/snippets/python_dependency_args_removed.md b/docs/markdown/snippets/python_dependency_args_removed.md new file mode 100644 index 0000000..7258083 --- /dev/null +++ b/docs/markdown/snippets/python_dependency_args_removed.md @@ -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. diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index 2ecc909..7d6bf62 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -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' diff --git a/mesonbuild/dependencies/cmake.py b/mesonbuild/dependencies/cmake.py index 8219cc5..ed89622 100644 --- a/mesonbuild/dependencies/cmake.py +++ b/mesonbuild/dependencies/cmake.py @@ -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 diff --git a/mesonbuild/dependencies/coarrays.py b/mesonbuild/dependencies/coarrays.py index d9af191..b50fb0a 100644 --- a/mesonbuild/dependencies/coarrays.py +++ b/mesonbuild/dependencies/coarrays.py @@ -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] diff --git a/mesonbuild/dependencies/configtool.py b/mesonbuild/dependencies/configtool.py index 623affb..7dccee4 100644 --- a/mesonbuild/dependencies/configtool.py +++ b/mesonbuild/dependencies/configtool.py @@ -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: diff --git a/mesonbuild/dependencies/dev.py b/mesonbuild/dependencies/dev.py index 7300e2f..9c2594f 100644 --- a/mesonbuild/dependencies/dev.py +++ b/mesonbuild/dependencies/dev.py @@ -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) @@ -545,10 +532,6 @@ class JDKSystemDependency(SystemDependency): 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 diff --git a/mesonbuild/dependencies/dub.py b/mesonbuild/dependencies/dub.py index 8dfb486..6aebe7b 100644 --- a/mesonbuild/dependencies/dub.py +++ b/mesonbuild/dependencies/dub.py @@ -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] diff --git a/mesonbuild/dependencies/framework.py b/mesonbuild/dependencies/framework.py index 4822398..3a947f0 100644 --- a/mesonbuild/dependencies/framework.py +++ b/mesonbuild/dependencies/framework.py @@ -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 '' diff --git a/mesonbuild/dependencies/misc.py b/mesonbuild/dependencies/misc.py index 483212f..9aa26c2 100644 --- a/mesonbuild/dependencies/misc.py +++ b/mesonbuild/dependencies/misc.py @@ -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]): diff --git a/mesonbuild/dependencies/pkgconfig.py b/mesonbuild/dependencies/pkgconfig.py index 1e8d913..28a3f74 100644 --- a/mesonbuild/dependencies/pkgconfig.py +++ b/mesonbuild/dependencies/pkgconfig.py @@ -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}') diff --git a/mesonbuild/dependencies/ui.py b/mesonbuild/dependencies/ui.py index f256a37..530a101 100644 --- a/mesonbuild/dependencies/ui.py +++ b/mesonbuild/dependencies/ui.py @@ -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' diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index 97940d3..22d0282 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -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 diff --git a/mesonbuild/modules/pkgconfig.py b/mesonbuild/modules/pkgconfig.py index c6eaedc..48bbc34 100644 --- a/mesonbuild/modules/pkgconfig.py +++ b/mesonbuild/modules/pkgconfig.py @@ -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): diff --git a/mesonbuild/modules/python.py b/mesonbuild/modules/python.py index 46fd27b..450991b 100644 --- a/mesonbuild/modules/python.py +++ b/mesonbuild/modules/python.py @@ -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 diff --git a/run_unittests.py b/run_unittests.py index 200c4ed..2241812 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -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'] |