Python: Add 'limited_api' kwarg to extension_module

This commit adds a new keyword arg to extension_module() that enables
a user to target the Python Limited API, declaring the version of the
limited API that they wish to target.

Two new unittests have been added to test this functionality.
pull/12125/head
Andrew McNulty 2 years ago committed by Eli Schwartz
parent 9d32302032
commit c730807696
  1. 2
      data/test.schema.json
  2. 17
      docs/markdown/Builtin-options.md
  3. 5
      docs/markdown/Python-module.md
  4. 5
      docs/markdown/snippets/python_extension_module_limited_api.md
  5. 2
      mesonbuild/coredata.py
  6. 12
      mesonbuild/dependencies/python.py
  7. 83
      mesonbuild/modules/python.py
  8. 15
      mesonbuild/scripts/python_info.py
  9. 14
      run_project_tests.py
  10. 10
      test cases/python/10 extmodule limited api disabled/meson.build
  11. 17
      test cases/python/10 extmodule limited api disabled/module.c
  12. 19
      test cases/python/9 extmodule limited api/limited.c
  13. 16
      test cases/python/9 extmodule limited api/meson.build
  14. 59
      test cases/python/9 extmodule limited api/not_limited.c
  15. 8
      test cases/python/9 extmodule limited api/test.json

@ -26,9 +26,11 @@
"exe", "exe",
"shared_lib", "shared_lib",
"python_lib", "python_lib",
"python_limited_lib",
"pdb", "pdb",
"implib", "implib",
"py_implib", "py_implib",
"py_limited_implib",
"implibempty", "implibempty",
"expr" "expr"
] ]

@ -370,12 +370,13 @@ install prefix. For example: if the install prefix is `/usr` and the
### Python module ### Python module
| Option | Default value | Possible values | Description | | Option | Default value | Possible values | Description |
| ------ | ------------- | ----------------- | ----------- | | ------ | ------------- | ----------------- | ----------- |
| bytecompile | 0 | integer from -1 to 2 | What bytecode optimization level to use (Since 1.2.0) | | bytecompile | 0 | integer from -1 to 2 | What bytecode optimization level to use (Since 1.2.0) |
| install_env | prefix | {auto,prefix,system,venv} | Which python environment to install to (Since 0.62.0) | | install_env | prefix | {auto,prefix,system,venv} | Which python environment to install to (Since 0.62.0) |
| platlibdir | | Directory path | Directory for site-specific, platform-specific files (Since 0.60.0) | | platlibdir | | Directory path | Directory for site-specific, platform-specific files (Since 0.60.0) |
| purelibdir | | Directory path | Directory for site-specific, non-platform-specific files (Since 0.60.0) | | purelibdir | | Directory path | Directory for site-specific, non-platform-specific files (Since 0.60.0) |
| allow_limited_api | true | true, false | Disables project-wide use of the Python Limited API (Since 1.3.0) |
*Since 0.60.0* The `python.platlibdir` and `python.purelibdir` options are used *Since 0.60.0* The `python.platlibdir` and `python.purelibdir` options are used
by the python module methods `python.install_sources()` and by the python module methods `python.install_sources()` and
@ -405,3 +406,7 @@ python bytecode. Bytecode has 3 optimization levels:
To this, Meson adds level `-1`, which is to not attempt to compile bytecode at To this, Meson adds level `-1`, which is to not attempt to compile bytecode at
all. all.
*Since 1.3.0* The `python.allow_limited_api` option affects whether the
`limited_api` keyword argument of the `extension_module` method is respected.
If set to `false`, the effect of the `limited_api` argument is disabled.

@ -101,6 +101,11 @@ the addition of the following:
`/usr/lib/site-packages`. When subdir is passed to this method, `/usr/lib/site-packages`. When subdir is passed to this method,
it will be appended to that location. This keyword argument is it will be appended to that location. This keyword argument is
mutually exclusive with `install_dir` mutually exclusive with `install_dir`
- `limited_api`: *since 1.3.0* A string containing the Python version
of the [Py_LIMITED_API](https://docs.python.org/3/c-api/stable.html) that
the extension targets. For example, '3.7' to target Python 3.7's version of
the limited API. This behavior can be disabled by setting the value of
`python.allow_limited_api`. See [Python module options](Builtin-options.md#python-module).
Additionally, the following diverge from [[shared_module]]'s default behavior: Additionally, the following diverge from [[shared_module]]'s default behavior:

@ -0,0 +1,5 @@
## Support targeting Python's limited C API
The Python module's `extension_module` function has gained the ability
to build extensions which target Python's limited C API via a new keyword
argument: `limited_api`.

@ -1300,6 +1300,8 @@ BUILTIN_CORE_OPTIONS: 'MutableKeyedOptionDictType' = OrderedDict([
BuiltinOption(UserStringOption, 'Directory for site-specific, platform-specific files.', '')), BuiltinOption(UserStringOption, 'Directory for site-specific, platform-specific files.', '')),
(OptionKey('purelibdir', module='python'), (OptionKey('purelibdir', module='python'),
BuiltinOption(UserStringOption, 'Directory for site-specific, non-platform-specific files.', '')), BuiltinOption(UserStringOption, 'Directory for site-specific, non-platform-specific files.', '')),
(OptionKey('allow_limited_api', module='python'),
BuiltinOption(UserBooleanOption, 'Whether to allow use of the Python Limited API', True)),
]) ])
BUILTIN_OPTIONS = OrderedDict(chain(BUILTIN_DIR_OPTIONS.items(), BUILTIN_CORE_OPTIONS.items())) BUILTIN_OPTIONS = OrderedDict(chain(BUILTIN_DIR_OPTIONS.items(), BUILTIN_CORE_OPTIONS.items()))

@ -44,6 +44,7 @@ if T.TYPE_CHECKING:
paths: T.Dict[str, str] paths: T.Dict[str, str]
platform: str platform: str
suffix: str suffix: str
limited_api_suffix: str
variables: T.Dict[str, str] variables: T.Dict[str, str]
version: str version: str
@ -94,6 +95,7 @@ class BasicPythonExternalProgram(ExternalProgram):
'paths': {}, 'paths': {},
'platform': 'sentinel', 'platform': 'sentinel',
'suffix': 'sentinel', 'suffix': 'sentinel',
'limited_api_suffix': 'sentinel',
'variables': {}, 'variables': {},
'version': '0.0', 'version': '0.0',
} }
@ -197,7 +199,7 @@ class PythonSystemDependency(SystemDependency, _PythonDependencyBase):
if self.link_libpython: if self.link_libpython:
# link args # link args
if mesonlib.is_windows(): if mesonlib.is_windows():
self.find_libpy_windows(environment) self.find_libpy_windows(environment, limited_api=False)
else: else:
self.find_libpy(environment) self.find_libpy(environment)
else: else:
@ -259,7 +261,7 @@ class PythonSystemDependency(SystemDependency, _PythonDependencyBase):
mlog.log(f'Unknown Windows Python platform {self.platform!r}') mlog.log(f'Unknown Windows Python platform {self.platform!r}')
return None return None
def get_windows_link_args(self) -> T.Optional[T.List[str]]: def get_windows_link_args(self, limited_api: bool) -> T.Optional[T.List[str]]:
if self.platform.startswith('win'): if self.platform.startswith('win'):
vernum = self.variables.get('py_version_nodot') vernum = self.variables.get('py_version_nodot')
verdot = self.variables.get('py_version_short') verdot = self.variables.get('py_version_short')
@ -277,6 +279,8 @@ class PythonSystemDependency(SystemDependency, _PythonDependencyBase):
else: else:
libpath = Path(f'python{vernum}.dll') libpath = Path(f'python{vernum}.dll')
else: else:
if limited_api:
vernum = vernum[0]
libpath = Path('libs') / f'python{vernum}.lib' libpath = Path('libs') / f'python{vernum}.lib'
# For a debug build, pyconfig.h may force linking with # For a debug build, pyconfig.h may force linking with
# pythonX_d.lib (see meson#10776). This cannot be avoided # pythonX_d.lib (see meson#10776). This cannot be avoided
@ -317,7 +321,7 @@ class PythonSystemDependency(SystemDependency, _PythonDependencyBase):
return None return None
return [str(lib)] return [str(lib)]
def find_libpy_windows(self, env: 'Environment') -> None: def find_libpy_windows(self, env: 'Environment', limited_api: bool = False) -> None:
''' '''
Find python3 libraries on Windows and also verify that the arch matches Find python3 libraries on Windows and also verify that the arch matches
what we are building for. what we are building for.
@ -332,7 +336,7 @@ class PythonSystemDependency(SystemDependency, _PythonDependencyBase):
self.is_found = False self.is_found = False
return return
# This can fail if the library is not found # This can fail if the library is not found
largs = self.get_windows_link_args() largs = self.get_windows_link_args(limited_api)
if largs is None: if largs is None:
self.is_found = False self.is_found = False
return return

@ -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)

@ -65,6 +65,20 @@ elif sys.version_info < (3, 8, 7):
else: else:
suffix = variables.get('EXT_SUFFIX') suffix = variables.get('EXT_SUFFIX')
limited_api_suffix = None
if sys.version_info >= (3, 2):
try:
from importlib.machinery import EXTENSION_SUFFIXES
limited_api_suffix = EXTENSION_SUFFIXES[1]
except Exception:
pass
# pypy supports modules targetting the limited api but
# does not use a special suffix to distinguish them:
# https://doc.pypy.org/en/latest/cpython_differences.html#permitted-abi-tags-in-extensions
if '__pypy__' in sys.builtin_module_names:
limited_api_suffix = suffix
print(json.dumps({ print(json.dumps({
'variables': variables, 'variables': variables,
'paths': paths, 'paths': paths,
@ -76,4 +90,5 @@ print(json.dumps({
'is_venv': sys.prefix != variables['base_prefix'], 'is_venv': sys.prefix != variables['base_prefix'],
'link_libpython': links_against_libpython(), 'link_libpython': links_against_libpython(),
'suffix': suffix, 'suffix': suffix,
'limited_api_suffix': limited_api_suffix,
})) }))

@ -148,7 +148,7 @@ class InstalledFile:
canonical_compiler = 'msvc' canonical_compiler = 'msvc'
python_suffix = python.info['suffix'] python_suffix = python.info['suffix']
python_limited_suffix = python.info['limited_api_suffix']
has_pdb = False has_pdb = False
if self.language in {'c', 'cpp'}: if self.language in {'c', 'cpp'}:
has_pdb = canonical_compiler == 'msvc' has_pdb = canonical_compiler == 'msvc'
@ -167,7 +167,7 @@ class InstalledFile:
return None return None
# Handle the different types # Handle the different types
if self.typ in {'py_implib', 'python_lib', 'python_file'}: if self.typ in {'py_implib', 'py_limited_implib', 'python_lib', 'python_limited_lib', 'python_file'}:
val = p.as_posix() val = p.as_posix()
val = val.replace('@PYTHON_PLATLIB@', python.platlib) val = val.replace('@PYTHON_PLATLIB@', python.platlib)
val = val.replace('@PYTHON_PURELIB@', python.purelib) val = val.replace('@PYTHON_PURELIB@', python.purelib)
@ -176,6 +176,8 @@ class InstalledFile:
return p return p
if self.typ == 'python_lib': if self.typ == 'python_lib':
return p.with_suffix(python_suffix) return p.with_suffix(python_suffix)
if self.typ == 'python_limited_lib':
return p.with_suffix(python_limited_suffix)
if self.typ == 'py_implib': if self.typ == 'py_implib':
p = p.with_suffix(python_suffix) p = p.with_suffix(python_suffix)
if env.machines.host.is_windows() and canonical_compiler == 'msvc': if env.machines.host.is_windows() and canonical_compiler == 'msvc':
@ -184,6 +186,14 @@ class InstalledFile:
return p.with_suffix('.dll.a') return p.with_suffix('.dll.a')
else: else:
return None return None
if self.typ == 'py_limited_implib':
p = p.with_suffix(python_limited_suffix)
if env.machines.host.is_windows() and canonical_compiler == 'msvc':
return p.with_suffix('.lib')
elif env.machines.host.is_windows() or env.machines.host.is_cygwin():
return p.with_suffix('.dll.a')
else:
return None
elif self.typ in {'file', 'dir'}: elif self.typ in {'file', 'dir'}:
return p return p
elif self.typ == 'shared_lib': elif self.typ == 'shared_lib':

@ -0,0 +1,10 @@
project('Python limited api disabled', 'c',
default_options : ['buildtype=release', 'werror=true', 'python.allow_limited_api=false'])
py_mod = import('python')
py = py_mod.find_installation()
module = py.extension_module('my_module',
'module.c',
limited_api: '3.7',
)

@ -0,0 +1,17 @@
#include <Python.h>
#if defined(Py_LIMITED_API)
#error "Py_LIMITED_API's definition by Meson should have been disabled."
#endif
static struct PyModuleDef my_module = {
PyModuleDef_HEAD_INIT,
"my_module",
NULL,
-1,
NULL
};
PyMODINIT_FUNC PyInit_my_module(void) {
return PyModule_Create(&my_module);
}

@ -0,0 +1,19 @@
#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
static struct PyModuleDef limited_module = {
PyModuleDef_HEAD_INIT,
"limited_api_test",
NULL,
-1,
NULL
};
PyMODINIT_FUNC PyInit_limited(void) {
return PyModule_Create(&limited_module);
}

@ -0,0 +1,16 @@
project('Python limited api', 'c',
default_options : ['buildtype=release', 'werror=true'])
py_mod = import('python')
py = py_mod.find_installation()
ext_mod_limited = py.extension_module('limited',
'limited.c',
limited_api: '3.7',
install: true,
)
ext_mod = py.extension_module('not_limited',
'not_limited.c',
install: true,
)

@ -0,0 +1,59 @@
#include <Python.h>
#include <stdio.h>
#ifdef Py_LIMITED_API
#error Py_LIMITED_API must not be defined.
#endif
/* This function explicitly calls functions whose declaration is elided when
* Py_LIMITED_API is defined. This is to test that the linker is actually
* linking to the right version of the library on Windows. */
static PyObject *meth_not_limited(PyObject *self, PyObject *args)
{
PyObject *list;
Py_ssize_t size;
if (!PyArg_ParseTuple(args, "o", & list))
return NULL;
if (!PyList_Check(list)) {
PyErr_Format(PyExc_TypeError, "expected 'list'");
return NULL;
}
/* PyList_GET_SIZE and PyList_GET_ITEM are only available if Py_LIMITED_API
* is not defined. It seems likely that they will remain excluded from the
* limited API as their checked counterparts (PyList_GetSize and
* PyList_GetItem) are made available in that mode instead. */
size = PyList_GET_SIZE(list);
for(Py_ssize_t i = 0; i < size; ++i) {
PyObject *element = PyList_GET_ITEM(list, i);
if (element == NULL) {
return NULL;
}
if(PyObject_Print(element, stdout, Py_PRINT_RAW) == -1) {
return NULL;
}
}
Py_RETURN_NONE;
}
static struct PyMethodDef not_limited_methods[] = {
{ "not_limited", meth_not_limited, METH_VARARGS,
"Calls functions whose declaration is elided by Py_LIMITED_API" },
{ NULL, NULL, 0, NULL }
};
static struct PyModuleDef not_limited_module = {
PyModuleDef_HEAD_INIT,
"not_limited_api_test",
NULL,
-1,
not_limited_methods
};
PyMODINIT_FUNC PyInit_not_limited(void) {
return PyModule_Create(&not_limited_module);
}

@ -0,0 +1,8 @@
{
"installed": [
{"type": "python_limited_lib", "file": "usr/@PYTHON_PLATLIB@/limited"},
{"type": "py_limited_implib", "file": "usr/@PYTHON_PLATLIB@/limited"},
{"type": "python_lib", "file": "usr/@PYTHON_PLATLIB@/not_limited"},
{"type": "py_implib", "file": "usr/@PYTHON_PLATLIB@/not_limited"}
]
}
Loading…
Cancel
Save