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

Allow users to target Py_LIMITED_API via python's extension_module #11745

Merged
merged 1 commit into from
Aug 15, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions data/test.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@
"exe",
"shared_lib",
"python_lib",
"python_limited_lib",
"pdb",
"implib",
"py_implib",
"py_limited_implib",
"implibempty",
"expr"
]
Expand Down
17 changes: 11 additions & 6 deletions docs/markdown/Builtin-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -370,12 +370,13 @@ install prefix. For example: if the install prefix is `/usr` and the

### Python module

| Option | Default value | Possible values | Description |
| ------ | ------------- | ----------------- | ----------- |
| 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) |
| 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) |
| Option | Default value | Possible values | Description |
| ------ | ------------- | ----------------- | ----------- |
| 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) |
| 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) |
| 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
by the python module methods `python.install_sources()` and
Expand Down Expand Up @@ -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
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.
5 changes: 5 additions & 0 deletions docs/markdown/Python-module.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ the addition of the following:
`/usr/lib/site-packages`. When subdir is passed to this method,
it will be appended to that location. This keyword argument is
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:

Expand Down
Original file line number Diff line number Diff line change
@@ -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`.
2 changes: 2 additions & 0 deletions mesonbuild/coredata.py
Original file line number Diff line number Diff line change
Expand Up @@ -1298,6 +1298,8 @@ def add_to_argparse(self, name: str, parser: argparse.ArgumentParser, help_suffi
BuiltinOption(UserStringOption, 'Directory for site-specific, platform-specific files.', '')),
(OptionKey('purelibdir', module='python'),
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()))
Expand Down
12 changes: 8 additions & 4 deletions mesonbuild/dependencies/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class PythonIntrospectionDict(TypedDict):
paths: T.Dict[str, str]
platform: str
suffix: str
limited_api_suffix: str
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation calls this limited_api, here it is limited_api_suffix. They should be aligned.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is part of the introspection data gathered from the interpreter the extension is being built for, it does not indicate whether the extension is built for the limited API or not. I think it is fine as it is.

variables: T.Dict[str, str]
version: str

Expand Down Expand Up @@ -94,6 +95,7 @@ def __init__(self, name: str, command: T.Optional[T.List[str]] = None,
'paths': {},
'platform': 'sentinel',
'suffix': 'sentinel',
'limited_api_suffix': 'sentinel',
'variables': {},
'version': '0.0',
}
Expand Down Expand Up @@ -197,7 +199,7 @@ def __init__(self, name: str, environment: 'Environment',
if self.link_libpython:
# link args
if mesonlib.is_windows():
self.find_libpy_windows(environment)
self.find_libpy_windows(environment, limited_api=False)
else:
self.find_libpy(environment)
else:
Expand Down Expand Up @@ -259,7 +261,7 @@ def get_windows_python_arch(self) -> T.Optional[str]:
mlog.log(f'Unknown Windows Python platform {self.platform!r}')
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'):
vernum = self.variables.get('py_version_nodot')
verdot = self.variables.get('py_version_short')
Expand All @@ -277,6 +279,8 @@ def get_windows_link_args(self) -> T.Optional[T.List[str]]:
else:
libpath = Path(f'python{vernum}.dll')
else:
if limited_api:
vernum = vernum[0]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will work until the major version goes above 9. Nit: you could use verdot.split(".")[0] instead, but probably other things will break before this becomes a problem.

libpath = Path('libs') / f'python{vernum}.lib'
# For a debug build, pyconfig.h may force linking with
# pythonX_d.lib (see meson#10776). This cannot be avoided
Expand Down Expand Up @@ -317,7 +321,7 @@ def get_windows_link_args(self) -> T.Optional[T.List[str]]:
return None
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
what we are building for.
Expand All @@ -332,7 +336,7 @@ def find_libpy_windows(self, env: 'Environment') -> None:
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(limited_api)
if largs is None:
self.is_found = False
return
Expand Down
83 changes: 75 additions & 8 deletions mesonbuild/modules/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# limitations under the License.
from __future__ import annotations

import copy, json, os, shutil
import copy, json, os, shutil, re
import typing as T

from . import ExtensionModule, ModuleInfo
Expand All @@ -32,7 +32,7 @@
InvalidArguments, typed_pos_args, typed_kwargs, KwargInfo,
FeatureNew, FeatureNewKwargs, disablerIfNotFound
)
from ..mesonlib import MachineChoice
from ..mesonlib import MachineChoice, OptionKey
from ..programs import ExternalProgram, NonExistingExternalProgram

if T.TYPE_CHECKING:
Expand Down Expand Up @@ -63,7 +63,7 @@ class ExtensionModuleKw(SharedModuleKw):
subdir: NotRequired[T.Optional[str]]


mod_kwargs = {'subdir'}
mod_kwargs = {'subdir', 'limited_api'}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you leave the empty line in there?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, done.

mod_kwargs.update(known_shmod_kwargs)
mod_kwargs -= {'name_prefix', 'name_suffix'}

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

_PURE_KW = KwargInfo('pure', (bool, NoneType))
_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))

class PythonInstallation(ExternalProgramHolder):
Expand All @@ -122,6 +123,7 @@ def __init__(self, python: 'PythonExternalProgram', interpreter: 'Interpreter'):
assert isinstance(prefix, str), 'for mypy'
self.variables = info['variables']
self.suffix = info['suffix']
self.limited_api_suffix = info['limited_api_suffix']
self.paths = info['paths']
self.pure = python.pure
self.platlib_install_path = os.path.join(prefix, python.platlib)
Expand All @@ -146,7 +148,7 @@ def __init__(self, python: 'PythonExternalProgram', interpreter: 'Interpreter'):

@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', *_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':
if 'install_dir' in kwargs:
if kwargs['subdir'] is not None:
Expand All @@ -159,32 +161,97 @@ def extension_module_method(self, args: T.Tuple[str, T.List[BuildTargetSource]],

kwargs['install_dir'] = self._get_install_dir_impl(False, subdir)

target_suffix = self.suffix

new_deps = mesonlib.extract_as_list(kwargs, 'dependencies')
has_pydep = any(isinstance(dep, _PythonDependencyBase) for dep in new_deps)
if not has_pydep:
pydep = next((dep for dep in new_deps if isinstance(dep, _PythonDependencyBase)), None)
if pydep is None:
pydep = self._dependency_method_impl({})
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('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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a shame you need this. I think the intention of the header was deal with linking the correct import lib, and meson should ideally not specify any link arguments to deal with the import lib at all. But it seems the header does not properly link to the "limited api + debug" import lib. I opened python/cpython#107585 to clarify that

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally meson would read a sysconfig setting specifying which import lib to link to, and use that. Doing this via MSVC specific pragmas is painful, weird, and bad, for a variety of reasons but perhaps the simplest and most persuasive one is that it cripples meson's ability to notice a bug in CPython and add a quirk like this to work around the problem.

You should see the crimes against computing that pybind11 does to allow building a python extension with debug symbols for user code without forcing to link against the debug version of python311.dll -- meson simply spawns a scary warning if you try to mix them.

Hot take: the computing industry would be better off if that MSVC pragma had not been invented.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also regret that this was necessary but it does seem that CPython's pyconfig.h is missing a bit of logic here.

Back when I was starting this work I recall finding a PR in CPython which aimed to fix the logic in PC/pyconfig.h, allowing a correct and peaceful coexistence between the Py_LIMITED_API and _DEBUG macros, but after searching for it again just now I can't seem to find it. That's frustrating: if I'm unable to find it again then I will work on a fix for CPython.

That being said, even if a fix makes it into CPython, I think Meson should probably still aim to support users with older versions of CPython that are already in the wild, right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Precisely.

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)
Comment on lines +200 to +207
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was trying to think whether there was value to making this part of py_inst.dependency(limited: ...) but after thinking about it a bit more I think the way you've done it is fine.

I think it's fairly unlikely that anyone will ever want to touch the limited API for reasons other than building an extension_module, so we do not need to make this directly exposed.


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

# msys2's python3 has "-cpython-36m.dll", we have to be clever
# 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])

kwargs['name_prefix'] = ''
kwargs['name_suffix'] = suffix
kwargs['name_suffix'] = target_suffix

if 'gnu_symbol_visibility' not in kwargs and \
(self.is_pypy or mesonlib.version_compare(self.version, '>=3.9')):
kwargs['gnu_symbol_visibility'] = 'inlineshidden'

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:
for_machine = self.interpreter.machine_from_native_kwarg(kwargs)
identifier = get_dep_identifier(self._full_path(), kwargs)
Expand Down
15 changes: 15 additions & 0 deletions mesonbuild/scripts/python_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,20 @@ def links_against_libpython():
else:
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]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not seem to be very robust. Is the order in which extension suffixes appear in importlib.machinery.EXTENSION_SUFFIXES documented anywhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't believe the order in which extensions appear in this list is guaranteed by any implementation of Python. In all the versions of CPython that I could test locally, the limited API suffix always appeared second. pypy supports the limited API but does not care about the suffix and so there is only one element in this list.

There isn't, to my knowledge, a documented way to reliably get this information from different Python implementations. My first implementation used imp.extension_suffixes() but I moved to using importlib.machinery as imp is scheduled to be removed in 3.12.

I would welcome any information from Python contributors about this. The current implementation seems to work for everything I have tested so far but I agree with you that this is not robust. I personally don't like this code and am eager to change it to something more robust.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Python will happily try to load any file that happens to have an extension contained in importlib.machinery.EXTENSION_SUFFIXES, therefore the fact that things work if you pick any value from that list is not indicative that things are working as they should.

The only thing I find in the documentation is:

On some platforms, Python will look for and load shared library files named with the abi3 tag (e.g. mymodule.abi3.so). It does not check if such extensions conform to a Stable ABI.
which is as good as useless.

I think the only way to know what is the correct thing to do is to check what distutils / setuptools do and do the same.

@FFY00 Do you have any insight? meson-python checks the extension filename suffix to determine if the extension is compiled for the stable ABI also on Windows, but, accordingly to the documentation that is not guaranteed to work.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is mirroring what setuptools does our best option here to something as robust as possible? I do find it troubling that there does not seem to be an ironclad way to get this information.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@amcnulty-fermat You should bring this up with the CPython developers. AFAIK there isn't any better way.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a new API design for sysconfig, I commented on the compile-specific issue

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't believe the order in which extensions appear in this list is guaranteed by any implementation of Python. In all the versions of CPython that I could test locally, the limited API suffix always appeared second.

Yes. This is an implementation detail. On current CPython versions, and most other implementations, the suffix list is sorted by extension module lookup order.

My recommendation here would be to look for the suffix matching the following regex ^.*\.abi(?P<abi_version>[0-9]+)\.[^\.]*$. Since the stable ABI tag is standardized on POSIX (via PEP 3149), this should be pretty reliable there. On Windows, even though it's not formalized anywhere, the same format is used.

That said, I don't think using EXTENSION_SUFFIXES[1] should be that problematic, if you wish to proceed with that instead.

In the future, this information should be provided by sysconfig.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Windows, even though it's not formalized anywhere, the same format is used.

Can you please clarify this? On Windows, for Python versions 3.7 to 3.11, importlib.machinery.EXTENSION_SUFFIXES does not contain any suffix that contain abi3 or similar.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, I thought that had changed at some point, but it's not the case. What changed was support in wheels, tooling should be able to handle abi3 Windows wheels, but the extension still does not contain the abi3 suffix indeed. Sorry for the confusion.

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({
'variables': variables,
'paths': paths,
Expand All @@ -76,4 +90,5 @@ def links_against_libpython():
'is_venv': sys.prefix != variables['base_prefix'],
'link_libpython': links_against_libpython(),
'suffix': suffix,
'limited_api_suffix': limited_api_suffix,
}))
14 changes: 12 additions & 2 deletions run_project_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ def get_path(self, compiler: str, env: environment.Environment) -> T.Optional[Pa
canonical_compiler = 'msvc'

python_suffix = python.info['suffix']

python_limited_suffix = python.info['limited_api_suffix']
has_pdb = False
if self.language in {'c', 'cpp'}:
has_pdb = canonical_compiler == 'msvc'
Expand All @@ -167,7 +167,7 @@ def get_path(self, compiler: str, env: environment.Environment) -> T.Optional[Pa
return None

# 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 = val.replace('@PYTHON_PLATLIB@', python.platlib)
val = val.replace('@PYTHON_PURELIB@', python.purelib)
Expand All @@ -176,6 +176,8 @@ def get_path(self, compiler: str, env: environment.Environment) -> T.Optional[Pa
return p
if self.typ == 'python_lib':
return p.with_suffix(python_suffix)
if self.typ == 'python_limited_lib':
return p.with_suffix(python_limited_suffix)
if self.typ == 'py_implib':
p = p.with_suffix(python_suffix)
if env.machines.host.is_windows() and canonical_compiler == 'msvc':
Expand All @@ -184,6 +186,14 @@ def get_path(self, compiler: str, env: environment.Environment) -> T.Optional[Pa
return p.with_suffix('.dll.a')
else:
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'}:
return p
elif self.typ == 'shared_lib':
Expand Down
10 changes: 10 additions & 0 deletions test cases/python/10 extmodule limited api disabled/meson.build
Original file line number Diff line number Diff line change
@@ -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',
)
17 changes: 17 additions & 0 deletions test cases/python/10 extmodule limited api disabled/module.c
Original file line number Diff line number Diff line change
@@ -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);
}
19 changes: 19 additions & 0 deletions test cases/python/9 extmodule limited api/limited.c
Original file line number Diff line number Diff line change
@@ -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);
}
Loading
Loading