From 2897a400731bc90c5ef559721341f3c3f6c2e579 Mon Sep 17 00:00:00 2001 From: Andrew McNulty Date: Mon, 19 Feb 2024 20:52:22 +0000 Subject: [PATCH] Cython: emit `CYTHON_LIMITED_API` 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. --- docs/markdown/Cython.md | 22 ++++++++++ docs/markdown/snippets/cython-limited-api.md | 4 ++ mesonbuild/modules/python.py | 34 ++++++++++++++-- test cases/cython/4 limited api/limited.pyx | 43 ++++++++++++++++++++ test cases/cython/4 limited api/meson.build | 22 ++++++++++ test cases/cython/4 limited api/run.py | 25 ++++++++++++ 6 files changed, 147 insertions(+), 3 deletions(-) create mode 100644 docs/markdown/snippets/cython-limited-api.md create mode 100644 test cases/cython/4 limited api/limited.pyx create mode 100644 test cases/cython/4 limited api/meson.build create mode 100755 test cases/cython/4 limited api/run.py diff --git a/docs/markdown/Cython.md b/docs/markdown/Cython.md index 304275043dcc..50b957807733 100644 --- a/docs/markdown/Cython.md +++ b/docs/markdown/Cython.md @@ -60,3 +60,25 @@ 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 in the Python +module's `extension_module` method 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 too old when attempting to use +this keyword with Cython sources, an error is issued and Meson stops. diff --git a/docs/markdown/snippets/cython-limited-api.md b/docs/markdown/snippets/cython-limited-api.md new file mode 100644 index 000000000000..f305c34bcdb8 --- /dev/null +++ b/docs/markdown/snippets/cython-limited-api.md @@ -0,0 +1,4 @@ +## `limited_api` support for Cython extension modules + +The `limited_api` keyword of the Python module's `extension_module` +now supports Cython by defining the `CYTHON_LIMITED_API` symbol. diff --git a/mesonbuild/modules/python.py b/mesonbuild/modules/python.py index 7a2cd2837733..dee62e94ba23 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,31 @@ 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..1427d5840abb --- /dev/null +++ b/test cases/cython/4 limited api/meson.build @@ -0,0 +1,22 @@ +project('cython limited api', ['cython', 'c'] + # setting limited_api in debug mode on Windows yields errors. + default_options: ['buildtype=release'] +) + +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.7' +) + +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