From 62ff4f85e3294ff0e59aa743ec3e1614fc06d0be Mon Sep 17 00:00:00 2001 From: Leo Singer Date: Thu, 13 Jan 2022 13:19:31 -0500 Subject: [PATCH 1/3] BUG: Fix build of third-party extensions with Py_LIMITED_API The type `vectorcallfunc` is not defined by Python's limited API. Guard its use with a `#ifdef` so that third-party extensions that use the Numpy C API will build with Py_LIMITED_API defined. This fixes a regression introduced in #20315. --- numpy/core/include/numpy/ufuncobject.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/numpy/core/include/numpy/ufuncobject.h b/numpy/core/include/numpy/ufuncobject.h index 1d7050bbe5a3..bb0633100795 100644 --- a/numpy/core/include/numpy/ufuncobject.h +++ b/numpy/core/include/numpy/ufuncobject.h @@ -173,7 +173,11 @@ typedef struct _tagPyUFuncObject { * but this was never implemented. (This is also why the above * selector is called the "legacy" selector.) */ - vectorcallfunc vectorcall; + #ifndef Py_LIMITED_API + vectorcallfunc vectorcall; + #else + void *vectorcall; + #endif /* Was previously the `PyUFunc_MaskedInnerLoopSelectionFunc` */ void *_always_null_previously_masked_innerloop_selector; From e78f607767c3cce4d5d24d4cc27597fe20bbca69 Mon Sep 17 00:00:00 2001 From: Leo Singer Date: Thu, 13 Jan 2022 13:09:23 -0500 Subject: [PATCH 2/3] TST: Test building third party C extensions with Py_LIMITED_API Fixes #13784. --- .../core/tests/examples/example_limited_api.c | 22 +++++++++++++++++++ numpy/core/tests/examples/setup.py | 12 ++++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 numpy/core/tests/examples/example_limited_api.c diff --git a/numpy/core/tests/examples/example_limited_api.c b/numpy/core/tests/examples/example_limited_api.c new file mode 100644 index 000000000000..e811800b4241 --- /dev/null +++ b/numpy/core/tests/examples/example_limited_api.c @@ -0,0 +1,22 @@ +/* + * Test that third-party extensions that use the Numpy C API can be built with + * the limited Python C API (see https://docs.python.org/3/c-api/stable.html). + */ + +#define Py_LIMITED_API 0x03060000 + +#include +#include +#include + +static PyModuleDef moduledef = { + .m_base = PyModuleDef_HEAD_INIT, + .m_name = "example_limited_api" +}; + +PyMODINIT_FUNC PyInit_example_limited_api(void) +{ + import_array(); + import_umath(); + return PyModule_Create(&moduledef); +} diff --git a/numpy/core/tests/examples/setup.py b/numpy/core/tests/examples/setup.py index 6e34aa7787ad..6ec0b41972e0 100644 --- a/numpy/core/tests/examples/setup.py +++ b/numpy/core/tests/examples/setup.py @@ -9,16 +9,24 @@ from setuptools.extension import Extension import os +include_dirs = [np.get_include()] macros = [("NPY_NO_DEPRECATED_API", 0)] checks = Extension( "checks", sources=[os.path.join('.', "checks.pyx")], - include_dirs=[np.get_include()], + include_dirs=include_dirs, define_macros=macros, ) -extensions = [checks] +example_limited_api = Extension( + "example_limited_api", + sources=[os.path.join('.', "example_limited_api.c")], + include_dirs=include_dirs, + define_macros=macros, +) + +extensions = [checks, example_limited_api] setup( ext_modules=cythonize(extensions) From 593b1b28b685a79838f88ff9932a0cfb1859a910 Mon Sep 17 00:00:00 2001 From: Leo Singer Date: Thu, 13 Jan 2022 20:47:50 -0500 Subject: [PATCH 3/3] TST: Split example package, skip limited API test for debug --- .../tests/examples/{ => cython}/checks.pyx | 0 .../core/tests/examples/{ => cython}/setup.py | 12 +----- .../limited_api.c} | 9 +--- .../core/tests/examples/limited_api/setup.py | 22 ++++++++++ numpy/core/tests/test_cython.py | 2 +- numpy/core/tests/test_limited_api.py | 41 +++++++++++++++++++ 6 files changed, 68 insertions(+), 18 deletions(-) rename numpy/core/tests/examples/{ => cython}/checks.pyx (100%) rename numpy/core/tests/examples/{ => cython}/setup.py (60%) rename numpy/core/tests/examples/{example_limited_api.c => limited_api/limited_api.c} (52%) create mode 100644 numpy/core/tests/examples/limited_api/setup.py create mode 100644 numpy/core/tests/test_limited_api.py diff --git a/numpy/core/tests/examples/checks.pyx b/numpy/core/tests/examples/cython/checks.pyx similarity index 100% rename from numpy/core/tests/examples/checks.pyx rename to numpy/core/tests/examples/cython/checks.pyx diff --git a/numpy/core/tests/examples/setup.py b/numpy/core/tests/examples/cython/setup.py similarity index 60% rename from numpy/core/tests/examples/setup.py rename to numpy/core/tests/examples/cython/setup.py index 6ec0b41972e0..6e34aa7787ad 100644 --- a/numpy/core/tests/examples/setup.py +++ b/numpy/core/tests/examples/cython/setup.py @@ -9,24 +9,16 @@ from setuptools.extension import Extension import os -include_dirs = [np.get_include()] macros = [("NPY_NO_DEPRECATED_API", 0)] checks = Extension( "checks", sources=[os.path.join('.', "checks.pyx")], - include_dirs=include_dirs, + include_dirs=[np.get_include()], define_macros=macros, ) -example_limited_api = Extension( - "example_limited_api", - sources=[os.path.join('.', "example_limited_api.c")], - include_dirs=include_dirs, - define_macros=macros, -) - -extensions = [checks, example_limited_api] +extensions = [checks] setup( ext_modules=cythonize(extensions) diff --git a/numpy/core/tests/examples/example_limited_api.c b/numpy/core/tests/examples/limited_api/limited_api.c similarity index 52% rename from numpy/core/tests/examples/example_limited_api.c rename to numpy/core/tests/examples/limited_api/limited_api.c index e811800b4241..698c54c57706 100644 --- a/numpy/core/tests/examples/example_limited_api.c +++ b/numpy/core/tests/examples/limited_api/limited_api.c @@ -1,8 +1,3 @@ -/* - * Test that third-party extensions that use the Numpy C API can be built with - * the limited Python C API (see https://docs.python.org/3/c-api/stable.html). - */ - #define Py_LIMITED_API 0x03060000 #include @@ -11,10 +6,10 @@ static PyModuleDef moduledef = { .m_base = PyModuleDef_HEAD_INIT, - .m_name = "example_limited_api" + .m_name = "limited_api" }; -PyMODINIT_FUNC PyInit_example_limited_api(void) +PyMODINIT_FUNC PyInit_limited_api(void) { import_array(); import_umath(); diff --git a/numpy/core/tests/examples/limited_api/setup.py b/numpy/core/tests/examples/limited_api/setup.py new file mode 100644 index 000000000000..18747dc80896 --- /dev/null +++ b/numpy/core/tests/examples/limited_api/setup.py @@ -0,0 +1,22 @@ +""" +Build an example package using the limited Python C API. +""" + +import numpy as np +from setuptools import setup, Extension +import os + +macros = [("NPY_NO_DEPRECATED_API", 0), ("Py_LIMITED_API", "0x03060000")] + +limited_api = Extension( + "limited_api", + sources=[os.path.join('.', "limited_api.c")], + include_dirs=[np.get_include()], + define_macros=macros, +) + +extensions = [limited_api] + +setup( + ext_modules=extensions +) diff --git a/numpy/core/tests/test_cython.py b/numpy/core/tests/test_cython.py index a1f09d0fef12..9896de0ec29f 100644 --- a/numpy/core/tests/test_cython.py +++ b/numpy/core/tests/test_cython.py @@ -32,7 +32,7 @@ def install_temp(request, tmp_path): # Based in part on test_cython from random.tests.test_extending here = os.path.dirname(__file__) - ext_dir = os.path.join(here, "examples") + ext_dir = os.path.join(here, "examples", "cython") cytest = str(tmp_path / "cytest") diff --git a/numpy/core/tests/test_limited_api.py b/numpy/core/tests/test_limited_api.py new file mode 100644 index 000000000000..0bb543d593ae --- /dev/null +++ b/numpy/core/tests/test_limited_api.py @@ -0,0 +1,41 @@ +import os +import shutil +import subprocess +import sys +import sysconfig +import pytest + + +@pytest.mark.xfail( + sysconfig.get_config_var("Py_DEBUG"), + reason=( + "Py_LIMITED_API is incompatible with Py_DEBUG, Py_TRACE_REFS, " + "and Py_REF_DEBUG" + ), +) +def test_limited_api(tmp_path): + """Test building a third-party C extension with the limited API.""" + # Based in part on test_cython from random.tests.test_extending + + here = os.path.dirname(__file__) + ext_dir = os.path.join(here, "examples", "limited_api") + + cytest = str(tmp_path / "limited_api") + + shutil.copytree(ext_dir, cytest) + # build the examples and "install" them into a temporary directory + + install_log = str(tmp_path / "tmp_install_log.txt") + subprocess.check_call( + [ + sys.executable, + "setup.py", + "build", + "install", + "--prefix", str(tmp_path / "installdir"), + "--single-version-externally-managed", + "--record", + install_log, + ], + cwd=cytest, + )