Skip to content

Commit

Permalink
Cython: emit CYTHON_LIMITED_API
Browse files Browse the repository at this point in the history
If the user has specified the `limited_api` keyword arg
on an 'extension_module' invocation, then add `CYTHON_LIMITED_API`
to the C and C++ definitions of the target (along with the existing
`Py_LIMITED_API`).

Cython's support for this functionality was added in version 3.0, so
we issue a warning if the detected Cython compiler is not at least this
version.

Two tests have been added: one based on Cython's limited API test which
aims to test that it works in the case of the user's compiler supporting
this function, and another to test that a warning is issued in the opposite
case.
  • Loading branch information
amcn committed Feb 20, 2024
1 parent c80ece2 commit ba7cb89
Show file tree
Hide file tree
Showing 10 changed files with 176 additions and 3 deletions.
21 changes: 21 additions & 0 deletions docs/markdown/Cython.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,24 @@ py.extension_module(
dependencies : dep_py,
)
```

## Limited API support

*(New in 1.4)*

Meson version 1.4 extends the support of the `limited_api` keyword to Cython.

```meson
project('my project', 'cython')
py.extension_module(
'foo',
'foo.pyx',
dependencies : dep_py,
limited_api : '3.8'
)
```

Cython's support for Python's Limited API was introduced in Cython version 3.0.
If Meson detects that the user's Cython compiler is not greater than or equal to
version 3.0, a warning is issued.
6 changes: 6 additions & 0 deletions docs/markdown/snippets/cython-limited-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
## `limited_api` support for Cython extension modules

Using the `limited_api` keyword when declaring a Python `extension_module`
now causes Meson to define the `CYTHON_LIMITED_API` symbol. Cython's support
for Python's Limited API was added in Cython version 3. If Meson detects a
version of Cython earlier than this, a warning is raised.
33 changes: 30 additions & 3 deletions mesonbuild/modules/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,14 +173,17 @@ def extension_module_method(self, args: T.Tuple[str, T.List[BuildTargetSource]],
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}'
limited_api_definitions = [
f'-DPy_LIMITED_API={limited_api_version_hex}',
'-DCYTHON_LIMITED_API=1'
]

new_c_args = mesonlib.extract_as_list(kwargs, 'c_args')
new_c_args.append(limited_api_definition)
new_c_args.extend(limited_api_definitions)
kwargs['c_args'] = new_c_args

new_cpp_args = mesonlib.extract_as_list(kwargs, 'cpp_args')
new_cpp_args.append(limited_api_definition)
new_cpp_args.extend(limited_api_definitions)
kwargs['cpp_args'] = new_cpp_args

# When compiled under MSVC, Python's PC/pyconfig.h forcibly inserts pythonMAJOR.MINOR.lib
Expand Down Expand Up @@ -212,6 +215,30 @@ def extension_module_method(self, args: T.Tuple[str, T.List[BuildTargetSource]],

kwargs['link_args'] = new_link_args

cython_compiler = next((c for c in compilers.values() if c.get_id() == 'cython'), None)
if cython_compiler is not None:
# Determine if any of the supplied source files are Cython source.
cython_suffixes = cython_compiler.file_suffixes
def has_cython_files(args: T.List[BuildTargetSource]):
for arg in args:
if isinstance(arg, StructuredSources):
if has_cython_files(arg.as_list()):
return True
continue
if isinstance(arg, GeneratedList):
if has_cython_files(arg.get_outputs()):
return True
continue
if isinstance(arg, mesonlib.File):
arg = arg.fname
suffix = os.path.splitext(arg)[1][1:].lower()
if suffix in cython_suffixes:
return True
return False

if mesonlib.version_compare(cython_compiler.version, '< 3.0.0') and has_cython_files(args[1]):
raise mesonlib.MesonException(f'Python Limited API not supported by Cython versions < 3.0.0 (detected {cython_compiler.version})')

kwargs['dependencies'] = new_deps

# msys2's python3 has "-cpython-36m.dll", we have to be clever
Expand Down
43 changes: 43 additions & 0 deletions test cases/cython/4 limited api/limited.pyx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# This is taken from Cython's limited API tests and
# as a result is under Apache 2.0

import cython

@cython.binding(False)
def fib(int n):
cdef int a, b
a, b = 0, 1
while b < n:
a, b = b, a + b
return b

def lsum(values):
cdef long result = 0
for value in values:
result += value
if type(values) is list:
for value in reversed(<list>values):
result += value
elif type(values) is tuple:
for value in reversed(<tuple>values):
result += value
return result

@cython.binding(False)
def raises():
raise RuntimeError()

def decode(bytes b, bytearray ba):
return b.decode("utf-8") + ba.decode("utf-8")

def cast_float(object o):
return float(o)

class C:
pass

cdef class D:
pass

cdef class E(D):
pass
19 changes: 19 additions & 0 deletions test cases/cython/4 limited api/meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
project('cython limited api', ['cython', 'c'])

cythonc = meson.get_compiler('cython')
if cythonc.version() < '3.0'
error('MESON_SKIP_TEST: Cython compiler version does not support limited API')
endif

py3 = import('python').find_installation()

ext_mod = py3.extension_module('limited',
'limited.pyx',
limited_api: '3.10'
)

test('limited_api runner',
py3,
args : files('run.py'),
env : ['PYTHONPATH=' + meson.current_build_dir()]
)
25 changes: 25 additions & 0 deletions test cases/cython/4 limited api/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# This is taken from Cython's limited API tests and
# as a result is under Apache 2.0

import limited

limited.fib(11)

assert limited.lsum(list(range(10))) == 90
assert limited.lsum(tuple(range(10))) == 90
assert limited.lsum(iter(range(10))) == 45

try:
limited.raises()
except RuntimeError:
pass

limited.C()
limited.D()
limited.E()

assert limited.decode(b'a', bytearray(b'b')) == "ab"

assert limited.cast_float(1) == 1.0
assert limited.cast_float("2.0") == 2.0
assert limited.cast_float(bytearray(b"3")) == 3.0
10 changes: 10 additions & 0 deletions test cases/cython/4 limited api/test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"matrix": {
"options": {
"cython_language": [
{ "val": "c" },
{ "val": "cpp" }
]
}
}
}
2 changes: 2 additions & 0 deletions test cases/warning/10 cython limited api version/limited.pyx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def basic():
return 10
12 changes: 12 additions & 0 deletions test cases/warning/10 cython limited api version/meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
project('cython limited api version', ['c', 'cython'])

cythonc = meson.get_compiler('cython')
if cythonc.version() >= '3.0'
error('MESON_SKIP_TEST: Cython compiler supports this functionality, no warning necessary')
endif

py3 = import('python').find_installation()
extension = py3.extension_module('limited',
'limited.pyx',
limited_api: '3.10'
)
8 changes: 8 additions & 0 deletions test cases/warning/10 cython limited api version/test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"stdout": [
{
"match": "re",
"line": ".*WARNING: Limited API not supported by Cython versions < 3.0.0 \\(detected: [0-9.]+(\\.rc[0-9]+)?\\)"
}
]
}

0 comments on commit ba7cb89

Please sign in to comment.