modules/python: rework extension_module method for typing

This cleans up the typing by creating a new dictionary instead of
mutating the one passed in. This gives better typing controls. I've also
adding some missing keyword argument annotations, and done some casting
because lists are invariant.
This commit is contained in:
Dylan Baker authored and Paolo Bonzini committed 2026-04-24 14:49:45 +02:00
1 parent 866d042b59
commit b5f61587c3
1 file changed
+26 -24
+26 -24
View File
@@ -9,7 +9,7 @@ import typing as T
from . import ExtensionModule, ModuleInfo
from .. import mesonlib
from .. import mlog
from ..build import known_shmod_kwargs, CustomTarget, CustomTargetIndex, BuildTarget, GeneratedList, StructuredSources, ExtractedObjects, SharedModule
from ..build import CustomTarget, CustomTargetIndex, BuildTarget, GeneratedList, StructuredSources, ExtractedObjects, SharedModule
from ..dependencies import NotFoundDependency
from ..dependencies.detect import get_dep_identifier, find_external_dependency
from ..dependencies.python import BasicPythonExternalProgram, python_factory, _PythonDependencyBase
@@ -17,7 +17,7 @@ from ..interpreter import extract_required_kwarg, primitives as P_OBJ
from ..interpreter.interpreterobjects import ProgramHolder
from ..interpreter.type_checking import NoneType, DEPENDENCY_KWS, PRESERVE_PATH_KW, REQUIRED_KW, SHARED_MOD_KWS
from ..interpreterbase import (
noPosargs, noKwargs, permittedKwargs, ContainerTypeInfo,
noPosargs, noKwargs, ContainerTypeInfo,
InvalidArguments, typed_pos_args, typed_kwargs, KwargInfo,
FeatureNew, disablerIfNotFound, InterpreterObject
)
@@ -34,6 +34,7 @@ if T.TYPE_CHECKING:
from ..interpreter import Interpreter
from ..interpreter.interpreter import BuildTargetSource
from ..interpreter.kwargs import ExtractRequired, SharedModule as SharedModuleKw, FuncDependency
from ..interpreter.type_checking import SourcesVarargsType
from ..interpreterbase.baseobjects import TYPE_var, TYPE_kwargs
class PyInstallKw(TypedDict):
@@ -52,15 +53,12 @@ if T.TYPE_CHECKING:
# Yes, these are different between SharedModule and ExtensionModule
install_dir: T.Union[str, bool, None] # type: ignore[misc]
limited_api: str
subdir: NotRequired[T.Optional[str]]
MaybePythonProg = T.Union[NonExistingExternalProgram, 'PythonExternalProgram']
mod_kwargs = {'subdir', 'limited_api'}
mod_kwargs.update(known_shmod_kwargs)
mod_kwargs -= {'name_prefix', 'name_suffix'}
_MOD_KWARGS = [k for k in SHARED_MOD_KWS if
k.name not in {'name_prefix', 'name_suffix', 'install_dir'}]
@@ -139,7 +137,6 @@ class PythonInstallation(ProgramHolder['PythonExternalProgram']):
self.platlib_install_path = os.path.join(prefix, python.platlib)
self.purelib_install_path = os.path.join(prefix, python.purelib)
@permittedKwargs(mod_kwargs)
@typed_pos_args('python.extension_module', str, varargs=(str, mesonlib.File, CustomTarget, CustomTargetIndex, GeneratedList, StructuredSources, ExtractedObjects, BuildTarget))
@typed_kwargs(
'python.extension_module',
@@ -150,22 +147,24 @@ class PythonInstallation(ProgramHolder['PythonExternalProgram']):
)
@InterpreterObject.method('extension_module')
def extension_module_method(self, args: T.Tuple[str, T.List[BuildTargetSource]], kwargs: ExtensionModuleKw) -> 'SharedModule':
target_kwargs = T.cast('SharedModuleKw', {k: v for k, v in kwargs.items() if k not in {'install_dir', 'subdir', 'limited_api'}})
if kwargs['install_dir'] is not None:
if kwargs['subdir'] is not None:
raise InvalidArguments('"subdir" and "install_dir" are mutually exclusive')
# the build_target() method now expects this to be correct.
kwargs['install_dir'] = [kwargs['install_dir']]
target_kwargs['install_dir'] = [kwargs['install_dir']]
else:
# We want to remove 'subdir', but it may be None and we want to replace it with ''
# It must be done this way since we don't allow both `install_dir`
# and `subdir` to be set at the same time
subdir = kwargs.pop('subdir') or ''
subdir = kwargs.get('subdir') or ''
kwargs['install_dir'] = [self._get_install_dir_impl(False, subdir)]
target_kwargs['install_dir'] = [self._get_install_dir_impl(False, subdir)]
target_suffix = self.suffix
new_deps = mesonlib.extract_as_list(kwargs, 'dependencies')
new_deps = kwargs['dependencies'].copy()
pydep = next((dep for dep in new_deps if isinstance(dep, _PythonDependencyBase)), None)
if pydep is None:
pydep = self._dependency_method_impl({'native': kwargs['native']})
@@ -176,7 +175,7 @@ class PythonInstallation(ProgramHolder['PythonExternalProgram']):
'0.63.0', self.subproject, 'use python_installation.dependency()',
self.current_node)
limited_api_version = kwargs.pop('limited_api')
limited_api_version = kwargs.get('limited_api')
allow_limited_api = self.interpreter.environment.coredata.optstore.get_value_for(OptionKey('python.allow_limited_api'))
if limited_api_version != '' and allow_limited_api:
@@ -185,19 +184,20 @@ class PythonInstallation(ProgramHolder['PythonExternalProgram']):
limited_api_version_hex = self._convert_api_version_to_py_version_hex(limited_api_version, pydep.version)
limited_api_definition = f'-DPy_LIMITED_API={limited_api_version_hex}'
new_c_args = mesonlib.extract_as_list(kwargs, 'c_args')
new_c_args = kwargs['c_args'].copy()
new_c_args.append(limited_api_definition)
kwargs['c_args'] = new_c_args
target_kwargs['c_args'] = new_c_args
new_cpp_args = mesonlib.extract_as_list(kwargs, 'cpp_args')
new_cpp_args = kwargs['cpp_args'].copy()
new_cpp_args.append(limited_api_definition)
kwargs['cpp_args'] = new_cpp_args
target_kwargs['cpp_args'] = new_cpp_args
# On Windows, the limited API DLL is python3.dll, not python3X.dll.
for_machine = kwargs['native']
if self.interpreter.environment.machines[for_machine].is_windows():
pydep_copy = copy.copy(pydep)
pydep_copy.find_libpy_windows(self.env, limited_api=True)
if isinstance(pydep_copy, _PythonDependencyBase):
pydep_copy.find_libpy_windows(self.env, limited_api=True)
if not pydep_copy.found():
raise mesonlib.MesonException('Python dependency supporting limited API not found')
@@ -214,7 +214,7 @@ class PythonInstallation(ProgramHolder['PythonExternalProgram']):
python_windows_debug_link_exception = f'/NODEFAULTLIB:python{pyver}_d.lib'
python_windows_release_link_exception = f'/NODEFAULTLIB:python{pyver}.lib'
new_link_args = mesonlib.extract_as_list(kwargs, 'link_args')
new_link_args = kwargs['link_args'].copy()
is_debug = self.interpreter.environment.coredata.optstore.get_value_for('debug')
if is_debug:
@@ -222,24 +222,26 @@ class PythonInstallation(ProgramHolder['PythonExternalProgram']):
else:
new_link_args.append(python_windows_release_link_exception)
kwargs['link_args'] = new_link_args
target_kwargs['link_args'] = new_link_args
kwargs['dependencies'] = new_deps
target_kwargs['dependencies'] = new_deps
# msys2's python3 has "-cpython-36m.dll", we have to be clever
# FIXME: explain what the specific cleverness is here
split, target_suffix = target_suffix.rsplit('.', 1)
args = (args[0] + split, args[1])
kwargs['name_prefix'] = ''
kwargs['name_suffix'] = target_suffix
target_kwargs['name_prefix'] = ''
target_kwargs['name_suffix'] = target_suffix
if kwargs['gnu_symbol_visibility'] == '' and \
(self.is_pypy or mesonlib.version_compare(self.version, '>=3.9')):
kwargs['gnu_symbol_visibility'] = 'inlineshidden'
target_kwargs['gnu_symbol_visibility'] = 'inlineshidden'
kwargs.setdefault('rust_abi', 'c')
return self.interpreter.build_target(self.current_node, args, kwargs, SharedModule)
return self.interpreter.build_target(
self.current_node, T.cast('T.Tuple[str, SourcesVarargsType]', args),
target_kwargs, SharedModule)
def _convert_api_version_to_py_version_hex(self, api_version: str, detected_version: str) -> str:
python_api_version_format = re.compile(r'[0-9]\.[0-9]{1,2}')