From f0246258ba0de5ddf093b3ef7fe3063fc2d76800 Mon Sep 17 00:00:00 2001
From: Min RK <benjaminrk@gmail.com>
Date: Mon, 2 Dec 2024 16:01:50 +0100
Subject: [PATCH] wip: limited api wheels

---
 .github/workflows/wheels.yml    |  2 +-
 CMakeLists.txt                  | 14 ++++++++++++--
 pyproject.toml                  | 19 ++++++++++++++++---
 zmq/backend/cython/_cpython.pxd | 10 ++++++++++
 zmq/backend/cython/_zmq.py      | 23 +++++++++++++++++------
 zmq/utils/mutex.h               |  2 ++
 6 files changed, 58 insertions(+), 12 deletions(-)
 create mode 100644 zmq/backend/cython/_cpython.pxd

diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml
index 59fc5a642..ede2fca0d 100644
--- a/.github/workflows/wheels.yml
+++ b/.github/workflows/wheels.yml
@@ -64,7 +64,7 @@ jobs:
     env:
       MACOSX_DEPLOYMENT_TARGET: "10.9"
       CIBW_BUILD: "${{ matrix.cibw.build || '*' }}"
-      CIBW_SKIP: "${{ matrix.cibw.skip || '' }}"
+      CIBW_SKIP: "cp38-* cp39-* cp310-* cp311-* cp313-* ${{ matrix.cibw.skip || '' }}"
       CIBW_ARCHS: "${{ matrix.cibw.arch || 'auto' }}"
       CIBW_MANYLINUX_X86_64_IMAGE: "${{ matrix.cibw.manylinux_x86_64_image || '' }}"
 
diff --git a/CMakeLists.txt b/CMakeLists.txt
index bc02c21b8..595ded455 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -5,13 +5,13 @@ set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
 list(PREPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
 find_package(
   Python
-  COMPONENTS Interpreter Development.Module
+  COMPONENTS Interpreter Development.Module ${SKBUILD_SABI_COMPONENT}
   REQUIRED)
 
 # Python_SOABI isn't always right when cross-compiling
 # SKBUILD_SOABI seems to be
 if (DEFINED SKBUILD_SOABI AND NOT "${SKBUILD_SOABI}" STREQUAL "${Python_SOABI}")
-  message(WARNING "SKBUILD_SOABI=${SKBUILD_SOABI} != Python_SOABI=${Python_SOABI}; likely cross-compiling, using SOABI=${SKBUILD_SOABI} from scikit-build")
+  message(WARNING "SKBUILD_SOABI=${SKBUILD_SOABI} != Python_SOABI=${Python_SOABI}; likely cross-compiling or Limited API, using SOABI=${SKBUILD_SOABI} from scikit-build")
   set(Python_SOABI "${SKBUILD_SOABI}")
 endif()
 
@@ -404,9 +404,19 @@ endif()
 
 file(MAKE_DIRECTORY ${ZMQ_BACKEND_DEST})
 
+if(NOT "${SKBUILD_SABI_COMPONENT}" STREQUAL "")
+  # set stable API
+  # assume we are targeting >= current Python version
+  # this isn't required, but we can't seem to get `wheel.py-api
+  message("SKBUILD_LIMITED_API=${SKBUILD_LIMITED_API}")
+  set(SABI_ARG "USE_SABI;${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}")
+  message("Building with stable API ${SABI_ARG} for ${Python_SOABI}")
+endif()
+
 python_add_library(
   ${ZMQ_EXT_NAME} MODULE
   WITH_SOABI
+  ${SABI_ARG}
   ${ZMQ_C}
 )
 
diff --git a/pyproject.toml b/pyproject.toml
index 5bbc5cc91..29c592199 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -2,9 +2,9 @@
 [build-system]
 requires = [
   "cffi; implementation_name == 'pypy'",
-  "cython>=3.0.0; implementation_name != 'pypy'",
+  "cython>=3.1.0a1; implementation_name != 'pypy'",
   "packaging",
-  "scikit-build-core",
+  "scikit-build-core>=0.10",
 ]
 build-backend = "scikit_build_core.build"
 
@@ -56,7 +56,7 @@ wheel.license-files = ["licenses/LICENSE*"]
 # 3.15 is required by scikit-build-core
 cmake.version = ">=3.15"
 # only build/install the pyzmq component
-cmake.targets = ["pyzmq"]
+build.targets = ["pyzmq"]
 install.components = ["pyzmq"]
 
 [tool.ruff]
@@ -202,6 +202,19 @@ select = "cp3{7,8,9}-* pp3{7,8}-*"
 manylinux-x86_64-image = "manylinux2010"
 manylinux-i686-image = "manylinux2010"
 
+# build limited-api wheels for 3.7, 3.12
+[[tool.cibuildwheel.overrides]]
+select = "cp312-*"
+config-settings = {
+    wheel = {py-api = "cp312"},
+}
+
+[[tool.cibuildwheel.overrides]]
+select = "cp37-*"
+config-settings = {
+    wheel = {py-api = "cp37"},
+}
+
 # note: manylinux_2_28 builds are added
 # in .github/workflows/wheels.yml
 
diff --git a/zmq/backend/cython/_cpython.pxd b/zmq/backend/cython/_cpython.pxd
new file mode 100644
index 000000000..b37f4645d
--- /dev/null
+++ b/zmq/backend/cython/_cpython.pxd
@@ -0,0 +1,10 @@
+# These should be cimported from cpython.bytes
+# but that has a transitive import of cpython.type
+# which currently conflicts with limited API
+cdef extern from "Python.h":
+  # cpython.bytes
+  char* PyBytes_AsString(object string) except NULL
+  bytes PyBytes_FromStringAndSize(char *v, Py_ssize_t len)
+  Py_ssize_t PyBytes_Size(object string) except -1
+  # cpython.exc
+  int PyErr_CheckSignals() except -1
diff --git a/zmq/backend/cython/_zmq.py b/zmq/backend/cython/_zmq.py
index 8cac83bee..c85ff703b 100644
--- a/zmq/backend/cython/_zmq.py
+++ b/zmq/backend/cython/_zmq.py
@@ -56,12 +56,15 @@
     size_t,
     sizeof,
 )
-from cython.cimports.cpython import (
-    PyBytes_AsString,
-    PyBytes_FromStringAndSize,
-    PyBytes_Size,
-    PyErr_CheckSignals,
-)
+
+# Cannot cimport these with Limited API yet
+# see https://github.com/cython/cython/issues/5634
+# from cython.cimports.cpython.bytes import (
+#     PyBytes_AsString,
+#     PyBytes_FromStringAndSize,
+#     PyBytes_Size,
+# )
+# from cython.cimports.cpython.exc import PyErr_CheckSignals
 from cython.cimports.libc.errno import EAGAIN, EINTR, ENAMETOOLONG, ENOENT, ENOTSOCK
 
 # cimports require Cython 3
@@ -70,6 +73,14 @@
 from cython.cimports.libc.stdio import stderr as cstderr
 from cython.cimports.libc.stdlib import free, malloc
 from cython.cimports.libc.string import memcpy
+
+# these should be from cython.cimports.cpython
+from cython.cimports.zmq.backend.cython._cpython import (
+    PyBytes_AsString,
+    PyBytes_FromStringAndSize,
+    PyBytes_Size,
+    PyErr_CheckSignals,
+)
 from cython.cimports.zmq.backend.cython._externs import (
     get_ipc_path_max_len,
     getpid,
diff --git a/zmq/utils/mutex.h b/zmq/utils/mutex.h
index 2191d08d1..b6275ea28 100644
--- a/zmq/utils/mutex.h
+++ b/zmq/utils/mutex.h
@@ -9,6 +9,8 @@
 
 #pragma once
 
+#include <stdlib.h>
+
 #if defined(_WIN32)
 #  include <windows.h>
 #else