Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

python: add limited_api kwarg to find_installation() #14176

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions mesonbuild/dependencies/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ def __init__(self, name: str, command: T.Optional[T.List[str]] = None,
'version': '0.0',
}
self.pure: bool = True
self.limited_api: T.Optional[str] = None

def _check_version(self, version: str) -> bool:
if self.name == 'python2':
Expand Down Expand Up @@ -172,6 +173,8 @@ def __init__(self, python_holder: 'BasicPythonExternalProgram', embed: bool):
if mesonlib.is_windows() and self.is_freethreaded:
self.compile_args += ['-DPy_GIL_DISABLED']

self.limited_api: str = ''

def find_libpy(self, environment: 'Environment') -> None:
if self.is_pypy:
if self.major_version == 3:
Expand Down
43 changes: 27 additions & 16 deletions mesonbuild/modules/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class FindInstallationKw(ExtractRequired):
disabler: bool
modules: T.List[str]
pure: T.Optional[bool]
limited_api: T.Optional[str]

class ExtensionModuleKw(SharedModuleKw):

Expand Down Expand Up @@ -106,7 +107,7 @@ def _get_path(self, state: T.Optional['ModuleState'], key: str) -> str:

_PURE_KW = KwargInfo('pure', (bool, NoneType))
_SUBDIR_KW = KwargInfo('subdir', str, default='')
_LIMITED_API_KW = KwargInfo('limited_api', str, default='', since='1.3.0')
_LIMITED_API_KW = KwargInfo('limited_api', str, default='inherit', since='1.3.0')
_DEFAULTABLE_SUBDIR_KW = KwargInfo('subdir', (str, NoneType))

class PythonInstallation(_ExternalProgramHolder['PythonExternalProgram']):
Expand All @@ -120,6 +121,7 @@ def __init__(self, python: 'PythonExternalProgram', interpreter: 'Interpreter'):
self.limited_api_suffix = info['limited_api_suffix']
self.paths = info['paths']
self.pure = python.pure
self.limited_api = python.limited_api
self.platlib_install_path = os.path.join(prefix, python.platlib)
self.purelib_install_path = os.path.join(prefix, python.purelib)
self.version = info['version']
Expand Down Expand Up @@ -160,31 +162,19 @@ def extension_module_method(self, args: T.Tuple[str, T.List[BuildTargetSource]],
new_deps = mesonlib.extract_as_list(kwargs, 'dependencies')
pydep = next((dep for dep in new_deps if isinstance(dep, _PythonDependencyBase)), None)
if pydep is None:
pydep = self._dependency_method_impl({})
pydep = self._dependency_method_impl({'limited_api': kwargs.pop('limited_api', '')})
if not pydep.found():
raise mesonlib.MesonException('Python dependency not found')
new_deps.append(pydep)
FeatureNew.single_use('python_installation.extension_module with implicit dependency on python',
'0.63.0', self.subproject, 'use python_installation.dependency()',
self.current_node)

limited_api_version = kwargs.pop('limited_api')
allow_limited_api = self.interpreter.environment.coredata.get_option(OptionKey('python.allow_limited_api'))
if limited_api_version != '' and allow_limited_api:
if pydep.limited_api != '' and allow_limited_api:
lgarrison marked this conversation as resolved.
Show resolved Hide resolved

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

# 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():
Expand Down Expand Up @@ -249,6 +239,15 @@ def _convert_api_version_to_py_version_hex(self, api_version: str, detected_vers
return '0x{:02x}{:02x}0000'.format(major, minor)

def _dependency_method_impl(self, kwargs: TYPE_kwargs) -> Dependency:

# Need to early resolve an inherited limited_api to an actual version
# for cache key purposes
limited_api_version = kwargs.get('limited_api', '')
if limited_api_version == 'inherit':
limited_api_version = self.limited_api
kwargs = kwargs.copy()
kwargs['limited_api'] = limited_api_version

for_machine = self.interpreter.machine_from_native_kwarg(kwargs)
identifier = get_dep_identifier(self._full_path(), kwargs)

Expand All @@ -261,14 +260,24 @@ def _dependency_method_impl(self, kwargs: TYPE_kwargs) -> Dependency:
candidates = python_factory(self.interpreter.environment, for_machine, new_kwargs, self.held_object)
dep = find_external_dependency('python', self.interpreter.environment, new_kwargs, candidates)

allow_limited_api = self.interpreter.environment.coredata.get_option(OptionKey('python.allow_limited_api'))
if limited_api_version != '' and allow_limited_api:
limited_api_version_hex = self._convert_api_version_to_py_version_hex(limited_api_version, dep.version)
dep.limited_api = limited_api_version
dep.compile_args.append(f'-DPy_LIMITED_API={limited_api_version_hex}')

self.interpreter.coredata.deps[for_machine].put(identifier, dep)
return dep

@disablerIfNotFound
@permittedKwargs(permitted_dependency_kwargs | {'embed'})
@permittedKwargs(permitted_dependency_kwargs | {'embed', 'limited_api'})
@FeatureNewKwargs('python_installation.dependency', '0.53.0', ['embed'])
@FeatureNewKwargs('python_installation.dependency', '1.7.0', ['limited_api'])
@noPosargs
def dependency_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> 'Dependency':
if 'limited_api' not in kwargs:
kwargs['limited_api'] = 'inherit'
lgarrison marked this conversation as resolved.
Show resolved Hide resolved

disabled, required, feature = extract_required_kwarg(kwargs, self.subproject)
if disabled:
mlog.log('Dependency', mlog.bold('python'), 'skipped: feature', mlog.bold(feature), 'disabled')
Expand Down Expand Up @@ -481,6 +490,7 @@ def _find_installation_impl(self, state: 'ModuleState', display_name: str, name_
KwargInfo('disabler', bool, default=False, since='0.49.0'),
KwargInfo('modules', ContainerTypeInfo(list, str), listify=True, default=[], since='0.51.0'),
_PURE_KW.evolve(default=True, since='0.64.0'),
_LIMITED_API_KW.evolve(default='', since='1.7.0')
)
def find_installation(self, state: 'ModuleState', args: T.Tuple[T.Optional[str]],
kwargs: 'FindInstallationKw') -> MaybePythonProg:
Expand Down Expand Up @@ -548,6 +558,7 @@ def find_installation(self, state: 'ModuleState', args: T.Tuple[T.Optional[str]]
assert isinstance(python, PythonExternalProgram), 'for mypy'
python = copy.copy(python)
python.pure = kwargs['pure']
python.limited_api = kwargs['limited_api']
return python

raise mesonlib.MesonBugException('Unreachable code was reached (PythonModule.find_installation).')
Expand Down
28 changes: 28 additions & 0 deletions test cases/python/11 extmodule limited api lib/meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Test that we can build a library that uses the limited API
# and link it to an extension module.

project('Python limited api lib', 'c',
default_options : ['buildtype=release', 'werror=true'])

py_mod = import('python')
py = py_mod.find_installation(limited_api: '3.7')
py_dep = py.dependency(required: true)

nanolib = static_library(
'nanolib',
'nanolib.c',
dependencies: [py_dep],
)

ext_mod = py.extension_module('mymodule',
'module.c',
install: true,
link_with: [nanolib],
)

test('load-test',
py,
args: [files('test_module.py')],
env: { 'PYTHONPATH': meson.current_build_dir() },
workdir: meson.current_source_dir()
)
27 changes: 27 additions & 0 deletions test cases/python/11 extmodule limited api lib/module.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#include <Python.h>

#ifndef Py_LIMITED_API
#error Py_LIMITED_API must be defined.
#elif Py_LIMITED_API != 0x03070000
#error Wrong value for Py_LIMITED_API
#endif

PyObject *
hello(PyObject * Py_UNUSED(self), PyObject * Py_UNUSED(args));

static struct PyMethodDef methods[] = {
{ "hello", hello, METH_NOARGS, NULL },
{ NULL, NULL, 0, NULL },
};

static struct PyModuleDef mymodule = {
PyModuleDef_HEAD_INIT,
"mymodule",
NULL,
-1,
methods
};

PyMODINIT_FUNC PyInit_mymodule(void) {
return PyModule_Create(&mymodule);
}
12 changes: 12 additions & 0 deletions test cases/python/11 extmodule limited api lib/nanolib.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#include <Python.h>

#ifndef Py_LIMITED_API
#error Py_LIMITED_API must be defined.
#elif Py_LIMITED_API != 0x03070000
#error Wrong value for Py_LIMITED_API
#endif

PyObject *
hello(PyObject * Py_UNUSED(self), PyObject * Py_UNUSED(args)) {
return PyUnicode_FromString("hello world");
}
6 changes: 6 additions & 0 deletions test cases/python/11 extmodule limited api lib/test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"installed": [
{"type": "python_limited_lib", "file": "usr/@PYTHON_PLATLIB@/mymodule"},
{"type": "py_limited_implib", "file": "usr/@PYTHON_PLATLIB@/mymodule"}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from mymodule import hello

def test_hello():
assert hello() == "hello world"

test_hello()
29 changes: 29 additions & 0 deletions test cases/python/9 extmodule limited api/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,32 @@ test('load-test',
env: { 'PYTHONPATH': meson.current_build_dir() },
workdir: meson.current_source_dir()
)

# Test inheritance and override of limited_api

py_limited = py_mod.find_installation(limited_api: '3.7')

py_limited.extension_module('limited_inherit',
'limited.c',
)

py_limited.extension_module('not_limited_override',
'not_limited.c',
limited_api: '',
)

py_dep_limited = py.dependency(required: true, limited_api: '3.7')

static_library(
'limited_dependency',
'limited.c',
dependencies: [py_dep_limited],
)

py_dep_inherit = py_limited.dependency(required: true)

static_library(
'limited_dependency_inherit',
'limited.c',
dependencies: [py_dep_inherit],
)
Loading