Merge branch 'py3-stable-abi'
* py3-stable-abi:
Add CI build using -py3-stable-abi option
Make Python buffer typemaps compatible with limited API
Don't use PyUnicode_AsUTF8() when python limited API is used
Make directors implementation for Python work with limited API
Support using stable Python ABI
Conflicts:
.github/workflows/ci.yml
Lib/python/pyhead.swg
Lib/python/pyrun.swg
Source/Modules/python.cxx
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index edd8c4d..deebbad 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -145,6 +145,8 @@
VER: '3.12'
CSTD: gnu99
- SWIGLANG: python
+ SWIG_FEATURES: -py3-stable-abi
+ - SWIGLANG: python
PY2: 2
SWIG_FEATURES: -builtin
- SWIGLANG: python
diff --git a/Lib/python/director.swg b/Lib/python/director.swg
index f6beafa..a1dd00f 100644
--- a/Lib/python/director.swg
+++ b/Lib/python/director.swg
@@ -14,6 +14,24 @@
#include <vector>
#include <map>
+#if defined(SWIG_PYTHON_THREADS)
+/* __THREAD__ is the old macro to activate some thread support */
+# if !defined(__THREAD__)
+# define __THREAD__ 1
+# endif
+#endif
+
+#ifdef __THREAD__
+#ifndef Py_LIMITED_API
+# include "pythread.h"
+#else
+# if defined(_WIN32)
+# include <windows.h>
+# else
+# include <pthread.h>
+# endif
+#endif
+#endif
/*
Use -DSWIG_PYTHON_DIRECTOR_NO_VTABLE if you don't want to generate a 'virtual
@@ -244,25 +262,89 @@
};
-#if defined(SWIG_PYTHON_THREADS)
-/* __THREAD__ is the old macro to activate some thread support */
-# if !defined(__THREAD__)
-# define __THREAD__ 1
-# endif
-#endif
-
#ifdef __THREAD__
-# include "pythread.h"
+#ifndef Py_LIMITED_API
+ class Mutex
+ {
+ public:
+ Mutex() {
+ mutex_ = PyThread_allocate_lock();
+ }
+
+ ~Mutex() {
+ PyThread_release_lock(mutex_);
+ }
+
+ private:
+ void Lock() {
+ PyThread_acquire_lock(mutex_, WAIT_LOCK);
+ }
+
+ void Unlock() {
+ PyThread_free_lock(mutex_);
+ }
+
+ PyThread_type_lock mutex_;
+
+ friend class Guard;
+ };
+#elif defined(_WIN32)
+ class Mutex : private CRITICAL_SECTION {
+ public:
+ Mutex() {
+ InitializeCriticalSection(this);
+ }
+
+ ~Mutex() {
+ DeleteCriticalSection(this);
+ }
+
+ private:
+ void Lock() {
+ EnterCriticalSection(this);
+ }
+
+ void Unlock() {
+ LeaveCriticalSection(this);
+ }
+
+ friend class Guard;
+ };
+#else
+ class Mutex {
+ public:
+ Mutex() {
+ pthread_mutex_init(&mutex_, NULL);
+ }
+
+ ~Mutex() {
+ pthread_mutex_destroy(&mutex_);
+ }
+
+ private:
+ void Lock() {
+ pthread_mutex_lock(&mutex_);
+ }
+
+ void Unlock() {
+ pthread_mutex_unlock(&mutex_);
+ }
+
+ friend class Guard;
+
+ pthread_mutex_t mutex_;
+ };
+#endif
class Guard {
- PyThread_type_lock &mutex_;
+ Mutex &mutex_;
public:
- Guard(PyThread_type_lock & mutex) : mutex_(mutex) {
- PyThread_acquire_lock(mutex_, WAIT_LOCK);
+ Guard(Mutex & mutex) : mutex_(mutex) {
+ mutex_.Lock();
}
~Guard() {
- PyThread_release_lock(mutex_);
+ mutex_.Unlock();
}
};
# define SWIG_GUARD(mutex) Guard _guard(mutex)
@@ -330,7 +412,7 @@
typedef std::map<void *, GCItem_var> swig_ownership_map;
mutable swig_ownership_map swig_owner;
#ifdef __THREAD__
- static PyThread_type_lock swig_mutex_own;
+ static Mutex swig_mutex_own;
#endif
public:
@@ -382,7 +464,7 @@
};
#ifdef __THREAD__
- PyThread_type_lock Director::swig_mutex_own = PyThread_allocate_lock();
+ Mutex Director::swig_mutex_own;
#endif
}
diff --git a/Lib/python/pybuffer.i b/Lib/python/pybuffer.i
index 2fdaa6d..9ebc36a 100644
--- a/Lib/python/pybuffer.i
+++ b/Lib/python/pybuffer.i
@@ -12,18 +12,43 @@
* }
*/
+/* Note that in Py_LIMITED_API case we have no choice, but to use deprecated
+ * functions, as they provides the only way to access buffer data with limited
+ * API, which doesn't include Py_buffer definition. We also disable the
+ * warnings about doing this because they're not useful in our case.
+ */
+
%define %pybuffer_mutable_binary(TYPEMAP, SIZE)
%typemap(in) (TYPEMAP, SIZE) {
int res; Py_ssize_t size = 0; void *buf = 0;
+%#ifndef Py_LIMITED_API
Py_buffer view;
res = PyObject_GetBuffer($input, &view, PyBUF_WRITABLE);
+%#else
+ %#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
+ %#pragma GCC diagnostic push
+ %#pragma GCC diagnostic ignored "-Wdeprecated"
+ %#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+ %#elif defined(_MSC_VER)
+ %#pragma warning(push)
+ %#pragma warning(disable: 4996)
+ %#endif
+ res = PyObject_AsWriteBuffer($input, &buf, &size);
+ %#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
+ %#pragma GCC diagnostic pop
+ %#elif defined(_MSC_VER)
+ %#pragma warning(pop)
+ %#endif
+%#endif
if (res < 0) {
PyErr_Clear();
%argument_fail(res, "(TYPEMAP, SIZE)", $symname, $argnum);
}
+%#ifndef Py_LIMITED_API
size = view.len;
buf = view.buf;
PyBuffer_Release(&view);
+%#endif
$1 = ($1_ltype) buf;
$2 = ($2_ltype) (size/sizeof($*1_type));
}
@@ -45,14 +70,34 @@
%define %pybuffer_mutable_string(TYPEMAP)
%typemap(in) (TYPEMAP) {
int res; void *buf = 0;
+%#ifndef Py_LIMITED_API
Py_buffer view;
res = PyObject_GetBuffer($input, &view, PyBUF_WRITABLE);
+%#else
+ %#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
+ %#pragma GCC diagnostic push
+ %#pragma GCC diagnostic ignored "-Wdeprecated"
+ %#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+ %#elif defined(_MSC_VER)
+ %#pragma warning(push)
+ %#pragma warning(disable: 4996)
+ %#endif
+ Py_ssize_t size;
+ res = PyObject_AsWriteBuffer($input, &buf, &size);
+ %#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
+ %#pragma GCC diagnostic pop
+ %#elif defined(_MSC_VER)
+ %#pragma warning(pop)
+ %#endif
+%#endif
if (res < 0) {
PyErr_Clear();
%argument_fail(res, "(TYPEMAP)", $symname, $argnum);
}
+%#ifndef Py_LIMITED_API
buf = view.buf;
PyBuffer_Release(&view);
+%#endif
$1 = ($1_ltype) buf;
}
%enddef
@@ -74,15 +119,34 @@
%define %pybuffer_binary(TYPEMAP, SIZE)
%typemap(in) (TYPEMAP, SIZE) {
int res; Py_ssize_t size = 0; const void *buf = 0;
+%#ifndef Py_LIMITED_API
Py_buffer view;
res = PyObject_GetBuffer($input, &view, PyBUF_CONTIG_RO);
+%#else
+ %#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
+ %#pragma GCC diagnostic push
+ %#pragma GCC diagnostic ignored "-Wdeprecated"
+ %#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+ %#elif defined(_MSC_VER)
+ %#pragma warning(push)
+ %#pragma warning(disable: 4996)
+ %#endif
+ res = PyObject_AsReadBuffer($input, &buf, &size);
+ %#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
+ %#pragma GCC diagnostic pop
+ %#elif defined(_MSC_VER)
+ %#pragma warning(pop)
+ %#endif
+%#endif
if (res < 0) {
PyErr_Clear();
%argument_fail(res, "(TYPEMAP, SIZE)", $symname, $argnum);
}
+%#ifndef Py_LIMITED_API
size = view.len;
buf = view.buf;
PyBuffer_Release(&view);
+%#endif
$1 = ($1_ltype) buf;
$2 = ($2_ltype) (size / sizeof($*1_type));
}
@@ -106,14 +170,34 @@
%define %pybuffer_string(TYPEMAP)
%typemap(in) (TYPEMAP) {
int res; const void *buf = 0;
+%#ifndef Py_LIMITED_API
Py_buffer view;
res = PyObject_GetBuffer($input, &view, PyBUF_CONTIG_RO);
+%#else
+ %#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
+ %#pragma GCC diagnostic push
+ %#pragma GCC diagnostic ignored "-Wdeprecated"
+ %#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+ %#elif defined(_MSC_VER)
+ %#pragma warning(push)
+ %#pragma warning(disable: 4996)
+ %#endif
+ Py_ssize_t size;
+ res = PyObject_AsReadBuffer($input, &buf, &size);
+ %#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
+ %#pragma GCC diagnostic pop
+ %#elif defined(_MSC_VER)
+ %#pragma warning(pop)
+ %#endif
+%#endif
if (res < 0) {
PyErr_Clear();
%argument_fail(res, "(TYPEMAP)", $symname, $argnum);
}
+%#ifndef Py_LIMITED_API
buf = view.buf;
PyBuffer_Release(&view);
+%#endif
$1 = ($1_ltype) buf;
}
%enddef
diff --git a/Lib/python/pyhead.swg b/Lib/python/pyhead.swg
index a5f804a..790da08 100644
--- a/Lib/python/pyhead.swg
+++ b/Lib/python/pyhead.swg
@@ -80,3 +80,17 @@
#define PyDescr_NAME(x) (((PyDescrObject *)(x))->d_name)
#define Py_hash_t long
#endif
+
+#ifdef Py_LIMITED_API
+# define PyTuple_GET_ITEM PyTuple_GetItem
+/* Note that PyTuple_SetItem() has different semantics from PyTuple_SET_ITEM as it decref's the original tuple item, so in general they cannot be used
+ interchangeably. However in SWIG-generated code PyTuple_SET_ITEM is only used with newly initialized tuples without any items and for them this does work. */
+# define PyTuple_SET_ITEM PyTuple_SetItem
+# define PyTuple_GET_SIZE PyTuple_Size
+# define PyCFunction_GET_FLAGS PyCFunction_GetFlags
+# define PyCFunction_GET_FUNCTION PyCFunction_GetFunction
+# define PyCFunction_GET_SELF PyCFunction_GetSelf
+# define PyList_GET_ITEM PyList_GetItem
+# define PyList_SET_ITEM PyList_SetItem
+# define PySliceObject PyObject
+#endif
diff --git a/Lib/python/pyrun.swg b/Lib/python/pyrun.swg
index 96d4ab8..3bb617e 100644
--- a/Lib/python/pyrun.swg
+++ b/Lib/python/pyrun.swg
@@ -338,6 +338,7 @@
SWIGINTERN PyTypeObject*
swig_varlink_type(void) {
static char varlink__doc__[] = "Swig var link object";
+#ifndef Py_LIMITED_API
static PyTypeObject varlink_type;
static int type_init = 0;
if (!type_init) {
@@ -405,12 +406,28 @@
return NULL;
}
return &varlink_type;
+#else // Py_LIMITED_API
+ PyType_Slot slots[] = {
+ { Py_tp_dealloc, (void*)swig_varlink_dealloc },
+ { Py_tp_repr, (void*)swig_varlink_repr },
+ { Py_tp_getattr, (void*)swig_varlink_getattr },
+ { Py_tp_setattr, (void*)swig_varlink_setattr },
+ { Py_tp_str, (void*)swig_varlink_str },
+ { Py_tp_doc, (void*)varlink__doc__ },
+ { 0, NULL }
+ };
+ PyType_Spec spec = {};
+ spec.name = "swigvarlink";
+ spec.basicsize = sizeof(swig_varlinkobject);
+ spec.slots = slots;
+ return (PyTypeObject*)PyType_FromSpec(&spec);
+#endif // Py_LIMITED_API
}
/* Create a variable linking object for use later */
SWIGINTERN PyObject *
SWIG_Python_newvarlink(void) {
- swig_varlinkobject *result = PyObject_NEW(swig_varlinkobject, swig_varlink_type());
+ swig_varlinkobject *result = PyObject_New(swig_varlinkobject, swig_varlink_type());
if (result) {
result->vars = 0;
}
@@ -723,6 +740,14 @@
if (PyType_IsSubtype(op->ob_type, target_tp))
return 1;
return (strcmp(op->ob_type->tp_name, "SwigPyObject") == 0);
+#elif defined(Py_LIMITED_API)
+ int cmp;
+ PyObject *tp_name = PyObject_GetAttrString((PyObject*)Py_TYPE(op), "__name__");
+ if (!tp_name)
+ return 0;
+ cmp = PyUnicode_CompareWithASCIIString(tp_name, "SwigPyObject");
+ Py_DECREF(tp_name);
+ return cmp == 0;
#else
return (Py_TYPE(op) == SwigPyObject_type())
|| (strcmp(Py_TYPE(op)->tp_name,"SwigPyObject") == 0);
@@ -869,7 +894,7 @@
SWIGRUNTIME PyTypeObject*
SwigPyObject_TypeOnce(void) {
static char swigobject_doc[] = "Swig object carries a C/C++ instance pointer";
-
+#ifndef Py_LIMITED_API
static PyNumberMethods SwigPyObject_as_number = {
(binaryfunc)0, /*nb_add*/
(binaryfunc)0, /*nb_subtract*/
@@ -1004,12 +1029,30 @@
return NULL;
}
return &swigpyobject_type;
+#else // Py_LIMITED_API
+ PyType_Slot slots[] = {
+ { Py_tp_dealloc, (void*)SwigPyObject_dealloc },
+ { Py_tp_repr, (void*)SwigPyObject_repr },
+ { Py_tp_getattro, (void*)PyObject_GenericGetAttr },
+ { Py_tp_doc, (void*)swigobject_doc },
+ { Py_tp_richcompare, (void*)SwigPyObject_richcompare },
+ { Py_tp_methods, (void*)swigobject_methods },
+ { Py_nb_int, (void*)SwigPyObject_long },
+ { 0, NULL }
+ };
+ PyType_Spec spec = {};
+ spec.name = "SwigPyObject";
+ spec.basicsize = sizeof(SwigPyObject);
+ spec.flags = Py_TPFLAGS_DEFAULT;
+ spec.slots = slots;
+ return (PyTypeObject*)PyType_FromSpec(&spec);
+#endif // Py_LIMITED_API
}
SWIGRUNTIME PyObject *
SwigPyObject_New(void *ptr, swig_type_info *ty, int own)
{
- SwigPyObject *sobj = PyObject_NEW(SwigPyObject, SwigPyObject_type());
+ SwigPyObject *sobj = PyObject_New(SwigPyObject, SwigPyObject_type());
if (sobj) {
sobj->ptr = ptr;
sobj->ty = ty;
@@ -1080,8 +1123,18 @@
SWIGRUNTIMEINLINE int
SwigPyPacked_Check(PyObject *op) {
+#ifndef Py_LIMITED_API
return ((op)->ob_type == SwigPyPacked_TypeOnce())
|| (strcmp((op)->ob_type->tp_name,"SwigPyPacked") == 0);
+#else
+ int cmp;
+ PyObject *tp_name = PyObject_GetAttrString((PyObject*)Py_TYPE(op), "__name__");
+ if (!tp_name)
+ return 0;
+ cmp = PyUnicode_CompareWithASCIIString(tp_name, "SwigPyPacked");
+ Py_DECREF(tp_name);
+ return cmp == 0;
+#endif
}
SWIGRUNTIME void
@@ -1097,6 +1150,7 @@
SWIGRUNTIME PyTypeObject*
SwigPyPacked_TypeOnce(void) {
static char swigpacked_doc[] = "Swig object carries a C/C++ instance pointer";
+#ifndef Py_LIMITED_API
static PyTypeObject swigpypacked_type;
static int type_init = 0;
if (!type_init) {
@@ -1187,12 +1241,28 @@
return NULL;
}
return &swigpypacked_type;
+#else
+ PyType_Slot slots[] = {
+ { Py_tp_dealloc, (void*)SwigPyPacked_dealloc },
+ { Py_tp_repr, (void*)SwigPyPacked_repr },
+ { Py_tp_str, (void*)SwigPyPacked_str },
+ { Py_tp_getattro, (void*)PyObject_GenericGetAttr },
+ { Py_tp_doc, swigpacked_doc },
+ { 0, NULL }
+ };
+ PyType_Spec spec = {};
+ spec.name = "SwigPyPacked";
+ spec.basicsize = sizeof(SwigPyPacked);
+ spec.flags = Py_TPFLAGS_DEFAULT;
+ spec.slots = slots;
+ return (PyTypeObject*)PyType_FromSpec(&spec);
+#endif
}
SWIGRUNTIME PyObject *
SwigPyPacked_New(void *ptr, size_t size, swig_type_info *ty)
{
- SwigPyPacked *sobj = PyObject_NEW(SwigPyPacked, SwigPyPacked_type());
+ SwigPyPacked *sobj = PyObject_New(SwigPyPacked, SwigPyPacked_type());
if (sobj) {
void *pack = malloc(size);
if (pack) {
@@ -1444,10 +1514,18 @@
swig_cast_info *tc;
/* here we get the method pointer for callbacks */
+#ifndef Py_LIMITED_API
const char *doc = (((PyCFunctionObject *)obj) -> m_ml -> ml_doc);
+#else
+ PyObject* pystr_doc = PyObject_GetAttrString(obj, "__doc__");
+ const char *doc = pystr_doc ? SWIG_Python_str_AsChar(pystr_doc) : 0;
+#endif
const char *desc = doc ? strstr(doc, "swig_ptr: ") : 0;
if (desc)
desc = ty ? SWIG_UnpackVoidPtr(desc + 10, &vptr, ty->name) : 0;
+#ifdef Py_LIMITED_API
+ SWIG_Python_str_DelForPy3(doc);
+#endif
if (!desc)
return SWIG_ERROR;
tc = SWIG_TypeCheck(desc,ty);
@@ -1523,7 +1601,12 @@
if (empty_args) {
PyObject *empty_kwargs = PyDict_New();
if (empty_kwargs) {
- inst = ((PyTypeObject *)data->newargs)->tp_new((PyTypeObject *)data->newargs, empty_args, empty_kwargs);
+#ifndef Py_LIMITED_API
+ newfunc newfn = ((PyTypeObject *)data->newargs)->tp_new;
+#else
+ newfunc newfn = (newfunc)PyType_GetSlot((PyTypeObject *)data->newargs, Py_tp_new);
+#endif
+ inst = newfn((PyTypeObject *)data->newargs, empty_args, empty_kwargs);
Py_DECREF(empty_kwargs);
if (inst) {
if (PyObject_SetAttr(inst, SWIG_This(), swig_this) == -1) {
@@ -1605,7 +1688,12 @@
if (flags & SWIG_BUILTIN_TP_INIT) {
newobj = (SwigPyObject*) self;
if (newobj->ptr) {
- PyObject *next_self = clientdata->pytype->tp_alloc(clientdata->pytype, 0);
+#ifndef Py_LIMITED_API
+ allocfunc alloc = clientdata->pytype->tp_alloc;
+#else
+ allocfunc alloc = (allocfunc)PyType_GetSlot(clientdata->pytype, Py_tp_alloc);
+#endif
+ PyObject *next_self = alloc(clientdata->pytype, 0);
while (newobj->next)
newobj = (SwigPyObject *) newobj->next;
newobj->next = next_self;
@@ -1831,6 +1919,7 @@
} else
#endif
{
+#ifndef Py_LIMITED_API // tp_name is not accessible
const char *otype = (obj ? obj->ob_type->tp_name : 0);
if (otype) {
PyObject *str = PyObject_Str(obj);
@@ -1845,6 +1934,7 @@
Py_XDECREF(str);
return;
}
+#endif
}
PyErr_Format(PyExc_TypeError, "a '%s' is expected", type);
} else {
diff --git a/Source/Modules/python.cxx b/Source/Modules/python.cxx
index 69e47d6..2326739 100644
--- a/Source/Modules/python.cxx
+++ b/Source/Modules/python.cxx
@@ -67,6 +67,7 @@
static int no_header_file = 0;
static int max_bases = 0;
static int builtin_bases_needed = 0;
+static int py3_stable_abi = 0;
/* C++ Support + Shadow Classes */
@@ -128,6 +129,7 @@
-nortti - Disable the use of the native C++ RTTI with directors\n\
-nothreads - Disable thread support for the entire interface\n\
-olddefs - Keep the old method definitions when using -fastproxy\n\
+ -py3-stable-abi - Generate code compatible with Python 3 stable ABI (PEP 384)\n\
-relativeimport - Use relative Python imports\n\
-threads - Add thread support for all the interface\n\
-O - Enable the following optimization options:\n\
@@ -392,6 +394,10 @@
fputs(usage1, stdout);
fputs(usage2, stdout);
fputs(usage3, stdout);
+ } else if (strcmp(argv[i], "-py3-stable-abi") == 0) {
+ py3_stable_abi = 1;
+ Preprocessor_define("SWIGPYTHON_PY3", 0);
+ Swig_mark_arg(i);
} else if (strcmp(argv[i], "-builtin") == 0) {
builtin = 1;
Preprocessor_define("SWIGPYTHON_BUILTIN", 0);
@@ -458,6 +464,16 @@
if (doxygen)
doxygenTranslator = new PyDocConverter(doxygen_translator_flags);
+ if (py3_stable_abi && builtin) {
+ Printf(stderr, "-py3-stable-abi and -builtin options are not compatible.\n");
+ SWIG_exit(EXIT_FAILURE);
+ }
+
+ if (py3_stable_abi && fastproxy) {
+ Printf(stderr, "-py3-stable-abi and -fastproxy options are not compatible. Disabling -fastproxy.\n");
+ fastproxy = 0;
+ }
+
if (!global_name)
global_name = NewString("cvar");
Preprocessor_define("SWIGPYTHON 1", 0);
@@ -626,6 +642,10 @@
Printf(f_runtime, "#define SWIGPYTHON_FASTPROXY\n");
}
+ if (py3_stable_abi) {
+ Printf(f_runtime, "#define Py_LIMITED_API 0x03040000\n");
+ }
+
Printf(f_runtime, "\n");
Printf(f_header, "#ifdef SWIG_TypeQuery\n");