@ -13,7 +13,7 @@
# limitations under the License.
# limitations under the License.
from __future__ import annotations
from __future__ import annotations
import copy , json , os , shutil
import copy , json , os , shutil , re
import typing as T
import typing as T
from . import ExtensionModule , ModuleInfo
from . import ExtensionModule , ModuleInfo
@ -32,7 +32,7 @@ from ..interpreterbase import (
InvalidArguments , typed_pos_args , typed_kwargs , KwargInfo ,
InvalidArguments , typed_pos_args , typed_kwargs , KwargInfo ,
FeatureNew , FeatureNewKwargs , disablerIfNotFound
FeatureNew , FeatureNewKwargs , disablerIfNotFound
)
)
from . . mesonlib import MachineChoice
from . . mesonlib import MachineChoice , OptionKey
from . . programs import ExternalProgram , NonExistingExternalProgram
from . . programs import ExternalProgram , NonExistingExternalProgram
if T . TYPE_CHECKING :
if T . TYPE_CHECKING :
@ -65,7 +65,7 @@ if T.TYPE_CHECKING:
MaybePythonProg = T . Union [ NonExistingExternalProgram , ' PythonExternalProgram ' ]
MaybePythonProg = T . Union [ NonExistingExternalProgram , ' PythonExternalProgram ' ]
mod_kwargs = { ' subdir ' }
mod_kwargs = { ' subdir ' , ' limited_api ' }
mod_kwargs . update ( known_shmod_kwargs )
mod_kwargs . update ( known_shmod_kwargs )
mod_kwargs - = { ' name_prefix ' , ' name_suffix ' }
mod_kwargs - = { ' name_prefix ' , ' name_suffix ' }
@ -114,6 +114,7 @@ class PythonExternalProgram(BasicPythonExternalProgram):
_PURE_KW = KwargInfo ( ' pure ' , ( bool , NoneType ) )
_PURE_KW = KwargInfo ( ' pure ' , ( bool , NoneType ) )
_SUBDIR_KW = KwargInfo ( ' subdir ' , str , default = ' ' )
_SUBDIR_KW = KwargInfo ( ' subdir ' , str , default = ' ' )
_LIMITED_API_KW = KwargInfo ( ' limited_api ' , str , default = ' ' , since = ' 1.3.0 ' )
_DEFAULTABLE_SUBDIR_KW = KwargInfo ( ' subdir ' , ( str , NoneType ) )
_DEFAULTABLE_SUBDIR_KW = KwargInfo ( ' subdir ' , ( str , NoneType ) )
class PythonInstallation ( _ExternalProgramHolder [ ' PythonExternalProgram ' ] ) :
class PythonInstallation ( _ExternalProgramHolder [ ' PythonExternalProgram ' ] ) :
@ -124,6 +125,7 @@ class PythonInstallation(_ExternalProgramHolder['PythonExternalProgram']):
assert isinstance ( prefix , str ) , ' for mypy '
assert isinstance ( prefix , str ) , ' for mypy '
self . variables = info [ ' variables ' ]
self . variables = info [ ' variables ' ]
self . suffix = info [ ' suffix ' ]
self . suffix = info [ ' suffix ' ]
self . limited_api_suffix = info [ ' limited_api_suffix ' ]
self . paths = info [ ' paths ' ]
self . paths = info [ ' paths ' ]
self . pure = python . pure
self . pure = python . pure
self . platlib_install_path = os . path . join ( prefix , python . platlib )
self . platlib_install_path = os . path . join ( prefix , python . platlib )
@ -148,7 +150,7 @@ class PythonInstallation(_ExternalProgramHolder['PythonExternalProgram']):
@permittedKwargs ( mod_kwargs )
@permittedKwargs ( mod_kwargs )
@typed_pos_args ( ' python.extension_module ' , str , varargs = ( str , mesonlib . File , CustomTarget , CustomTargetIndex , GeneratedList , StructuredSources , ExtractedObjects , BuildTarget ) )
@typed_pos_args ( ' python.extension_module ' , str , varargs = ( str , mesonlib . File , CustomTarget , CustomTargetIndex , GeneratedList , StructuredSources , ExtractedObjects , BuildTarget ) )
@typed_kwargs ( ' python.extension_module ' , * _MOD_KWARGS , _DEFAULTABLE_SUBDIR_KW , allow_unknown = True )
@typed_kwargs ( ' python.extension_module ' , * _MOD_KWARGS , _DEFAULTABLE_SUBDIR_KW , _LIMITED_API_KW , allow_unknown = True )
def extension_module_method ( self , args : T . Tuple [ str , T . List [ BuildTargetSource ] ] , kwargs : ExtensionModuleKw ) - > ' SharedModule ' :
def extension_module_method ( self , args : T . Tuple [ str , T . List [ BuildTargetSource ] ] , kwargs : ExtensionModuleKw ) - > ' SharedModule ' :
if ' install_dir ' in kwargs :
if ' install_dir ' in kwargs :
if kwargs [ ' subdir ' ] is not None :
if kwargs [ ' subdir ' ] is not None :
@ -161,9 +163,11 @@ class PythonInstallation(_ExternalProgramHolder['PythonExternalProgram']):
kwargs [ ' install_dir ' ] = self . _get_install_dir_impl ( False , subdir )
kwargs [ ' install_dir ' ] = self . _get_install_dir_impl ( False , subdir )
target_suffix = self . suffix
new_deps = mesonlib . extract_as_list ( kwargs , ' dependencies ' )
new_deps = mesonlib . extract_as_list ( kwargs , ' dependencies ' )
has_pydep = any ( isinstance ( dep , _PythonDependencyBase ) for dep in new_deps )
pydep = next ( ( dep for dep in new_deps if isinstance ( dep , _PythonDependencyBase ) ) , None )
if not has_pydep :
if pydep is None :
pydep = self . _dependency_method_impl ( { } )
pydep = self . _dependency_method_impl ( { } )
if not pydep . found ( ) :
if not pydep . found ( ) :
raise mesonlib . MesonException ( ' Python dependency not found ' )
raise mesonlib . MesonException ( ' Python dependency not found ' )
@ -171,15 +175,62 @@ class PythonInstallation(_ExternalProgramHolder['PythonExternalProgram']):
FeatureNew . single_use ( ' python_installation.extension_module with implicit dependency on python ' ,
FeatureNew . single_use ( ' python_installation.extension_module with implicit dependency on python ' ,
' 0.63.0 ' , self . subproject , ' use python_installation.dependency() ' ,
' 0.63.0 ' , self . subproject , ' use python_installation.dependency() ' ,
self . current_node )
self . current_node )
limited_api_version = kwargs . pop ( ' limited_api ' )
allow_limited_api = self . interpreter . environment . coredata . get_option ( OptionKey ( ' allow_limited_api ' , module = ' python ' ) )
if limited_api_version != ' ' and allow_limited_api :
target_suffix = self . limited_api_suffix
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 . append ( limited_api_definition )
kwargs [ ' c_args ' ] = new_c_args
new_cpp_args = mesonlib . extract_as_list ( kwargs , ' cpp_args ' )
new_cpp_args . append ( limited_api_definition )
kwargs [ ' cpp_args ' ] = new_cpp_args
# When compiled under MSVC, Python's PC/pyconfig.h forcibly inserts pythonMAJOR.MINOR.lib
# into the linker path when not running in debug mode via a series #pragma comment(lib, "")
# directives. We manually override these here as this interferes with the intended
# use of the 'limited_api' kwarg
for_machine = self . interpreter . machine_from_native_kwarg ( kwargs )
compilers = self . interpreter . environment . coredata . compilers [ for_machine ]
if any ( compiler . get_id ( ) == ' msvc ' for compiler in compilers . values ( ) ) :
pydep_copy = copy . copy ( pydep )
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 ' )
new_deps . remove ( pydep )
new_deps . append ( pydep_copy )
pyver = pydep . version . replace ( ' . ' , ' ' )
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 ' )
is_debug = self . interpreter . environment . coredata . options [ OptionKey ( ' debug ' ) ] . value
if is_debug :
new_link_args . append ( python_windows_debug_link_exception )
else :
new_link_args . append ( python_windows_release_link_exception )
kwargs [ ' link_args ' ] = new_link_args
kwargs [ ' dependencies ' ] = new_deps
kwargs [ ' dependencies ' ] = new_deps
# msys2's python3 has "-cpython-36m.dll", we have to be clever
# msys2's python3 has "-cpython-36m.dll", we have to be clever
# FIXME: explain what the specific cleverness is here
# FIXME: explain what the specific cleverness is here
split , suffix = self . suffix . rsplit ( ' . ' , 1 )
split , target_ suffix = target_ suffix. rsplit ( ' . ' , 1 )
args = ( args [ 0 ] + split , args [ 1 ] )
args = ( args [ 0 ] + split , args [ 1 ] )
kwargs [ ' name_prefix ' ] = ' '
kwargs [ ' name_prefix ' ] = ' '
kwargs [ ' name_suffix ' ] = suffix
kwargs [ ' name_suffix ' ] = target_ suffix
if ' gnu_symbol_visibility ' not in kwargs and \
if ' gnu_symbol_visibility ' not in kwargs and \
( self . is_pypy or mesonlib . version_compare ( self . version , ' >=3.9 ' ) ) :
( self . is_pypy or mesonlib . version_compare ( self . version , ' >=3.9 ' ) ) :
@ -187,6 +238,22 @@ class PythonInstallation(_ExternalProgramHolder['PythonExternalProgram']):
return self . interpreter . build_target ( self . current_node , args , kwargs , SharedModule )
return self . interpreter . build_target ( self . current_node , args , 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} ' )
decimal_match = python_api_version_format . fullmatch ( api_version )
if not decimal_match :
raise InvalidArguments ( f ' Python API version invalid: " { api_version } " . ' )
if mesonlib . version_compare ( api_version , ' <3.2 ' ) :
raise InvalidArguments ( f ' Python Limited API version invalid: { api_version } (must be greater than 3.2) ' )
if mesonlib . version_compare ( api_version , ' > ' + detected_version ) :
raise InvalidArguments ( f ' Python Limited API version too high: { api_version } (detected { detected_version } ) ' )
version_components = api_version . split ( ' . ' )
major = int ( version_components [ 0 ] )
minor = int ( version_components [ 1 ] )
return ' 0x {:02x} {:02x} 0000 ' . format ( major , minor )
def _dependency_method_impl ( self , kwargs : TYPE_kwargs ) - > Dependency :
def _dependency_method_impl ( self , kwargs : TYPE_kwargs ) - > Dependency :
for_machine = self . interpreter . machine_from_native_kwarg ( kwargs )
for_machine = self . interpreter . machine_from_native_kwarg ( kwargs )
identifier = get_dep_identifier ( self . _full_path ( ) , kwargs )
identifier = get_dep_identifier ( self . _full_path ( ) , kwargs )