diff --git a/docs/markdown/Cython.md b/docs/markdown/Cython.md index 304275043dcc..8c9fe63d9de3 100644 --- a/docs/markdown/Cython.md +++ b/docs/markdown/Cython.md @@ -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. diff --git a/docs/markdown/snippets/cython-limited-api.md b/docs/markdown/snippets/cython-limited-api.md new file mode 100644 index 000000000000..ac42dd043976 --- /dev/null +++ b/docs/markdown/snippets/cython-limited-api.md @@ -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. diff --git a/mesonbuild/modules/python.py b/mesonbuild/modules/python.py index 7a2cd2837733..bf7e902a11f0 100644 --- a/mesonbuild/modules/python.py +++ b/mesonbuild/modules/python.py @@ -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 @@ -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 diff --git a/test cases/cython/4 limited api/limited.pyx b/test cases/cython/4 limited api/limited.pyx new file mode 100644 index 000000000000..8207b838a6f9 --- /dev/null +++ b/test cases/cython/4 limited api/limited.pyx @@ -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(values): + result += value + elif type(values) is tuple: + for value in reversed(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 diff --git a/test cases/cython/4 limited api/meson.build b/test cases/cython/4 limited api/meson.build new file mode 100644 index 000000000000..6160bb602e4e --- /dev/null +++ b/test cases/cython/4 limited api/meson.build @@ -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()] +) diff --git a/test cases/cython/4 limited api/run.py b/test cases/cython/4 limited api/run.py new file mode 100755 index 000000000000..aa5fb4c1d958 --- /dev/null +++ b/test cases/cython/4 limited api/run.py @@ -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 diff --git a/test cases/cython/4 limited api/test.json b/test cases/cython/4 limited api/test.json new file mode 100644 index 000000000000..ed7ac2b9b968 --- /dev/null +++ b/test cases/cython/4 limited api/test.json @@ -0,0 +1,10 @@ +{ + "matrix": { + "options": { + "cython_language": [ + { "val": "c" }, + { "val": "cpp" } + ] + } + } +} diff --git a/test cases/warning/10 cython limited api version/limited.pyx b/test cases/warning/10 cython limited api version/limited.pyx new file mode 100644 index 000000000000..cbc68d5f8367 --- /dev/null +++ b/test cases/warning/10 cython limited api version/limited.pyx @@ -0,0 +1,2 @@ +def basic(): + return 10 diff --git a/test cases/warning/10 cython limited api version/meson.build b/test cases/warning/10 cython limited api version/meson.build new file mode 100644 index 000000000000..9309a63373c0 --- /dev/null +++ b/test cases/warning/10 cython limited api version/meson.build @@ -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' +) diff --git a/test cases/warning/10 cython limited api version/test.json b/test cases/warning/10 cython limited api version/test.json new file mode 100644 index 000000000000..e79f22508457 --- /dev/null +++ b/test cases/warning/10 cython limited api version/test.json @@ -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]+)?\\)" + } + ] + }