diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6f43aa5..72d91db 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,15 +21,15 @@ jobs: python: # Python versions (CPython): # https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json - - "3.7" - "3.8" - "3.9" - "3.10" - "3.11" - "3.12" - # CPython 3.13 final is scheduled for October 2024: - # https://peps.python.org/pep-0719/ - "3.13" + # CPython 3.14 final is scheduled for October 2025: + # https://peps.python.org/pep-0719/ + - "3.14" # PyPy versions: # - https://github.com/actions/setup-python/blob/main/docs/advanced-usage.md @@ -42,6 +42,7 @@ jobs: - "pypy3.8" - "pypy3.9" - "pypy3.10" + - "pypy3.11" # Old PyPy versions # See https://foss.heptapod.net/pypy/pypy/-/issues/3991 @@ -50,20 +51,41 @@ jobs: - "pypy3.7-v7.3.2" include: - # Windows: test old and new Python + # Windows + - os: windows-latest + python: "3.6" + - os: windows-latest + python: "3.7" + - os: windows-latest + python: "3.8" + - os: windows-latest + python: "3.9" - os: windows-latest - python: 3.6 + python: "3.10" - os: windows-latest - python: 3.12 + python: "3.11" + - os: windows-latest + python: "3.12" + - os: windows-latest + python: "3.13" - # macOS: test only new Python + # macOS + # Python 3.9 is the oldest version available on macOS/arm64. + - os: macos-latest + python: "3.9" + - os: macos-latest + python: "3.10" + - os: macos-latest + python: "3.11" + - os: macos-latest + python: "3.12" - os: macos-latest - python: 3.12 + python: "3.13" # Ubuntu: test deadsnakes Python versions which are not supported by # GHA python-versions. - - os: ubuntu-20.04 - python: 3.6 + - os: ubuntu-22.04 + python: "3.7" steps: # https://github.com/actions/checkout diff --git a/docs/api.rst b/docs/api.rst index a75053e..6efae15 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -10,7 +10,7 @@ Supported Python versions: * Python 3.6 - 3.14 * PyPy 2.7 and PyPy 3.6 - 3.10 -Python 2.7 and Python 3.4 are no longer officially supported since GitHub +Python 2.7 and Python 3.5 are no longer officially supported since GitHub Actions doesn't support them anymore: only best effort support is provided. C++03 and C++11 are supported on Python 3.6 and newer. @@ -23,16 +23,214 @@ Some functions related to frame objects and ``PyThreadState`` are not available on PyPy. Latest version of the header file: -`pythoncapi_compat.h `_. +`pythoncapi_compat.h `_. +Python 3.15 +----------- + +.. c:function:: PyObject* PySys_GetAttr(const char *name) + + See `PySys_GetAttr() documentation `__. + +.. c:function:: PyObject* PySys_GetAttrString(const char *name) + + See `PySys_GetAttrString() documentation `__. + +.. c:function:: PyObject* PySys_GetOptionalAttr(const char *name) + + See `PySys_GetOptionalAttr() documentation `__. + +.. c:function:: PyObject* PySys_GetOptionalAttrString(const char *name) + + See `PySys_GetOptionalAttrString() documentation `__. + Python 3.14 ----------- +.. c:struct:: PyLongLayout + + See `PyLongLayout documentation `__. + +.. c:function:: const PyLongLayout* PyLong_GetNativeLayout(void) + + See `PyLong_GetNativeLayout() documentation `__. + +.. c:struct:: PyLongExport + + See `PyLongExport documentation `__. + +.. c:function:: int PyLong_Export(PyObject *obj, PyLongExport *export_long) + + See `PyLong_Export() documentation `__. + +.. c:function:: void PyLong_FreeExport(PyLongExport *export_long) + + See `PyLong_FreeExport() documentation `__. + +.. c:struct:: PyLongWriter + + See `PyLongWriter documentation `__. + +.. c:function:: PyLongWriter* PyLongWriter_Create(int negative, Py_ssize_t ndigits, void **digits) + + See `PyLongWriter_Create() documentation `__. + +.. c:function:: PyObject* PyLongWriter_Finish(PyLongWriter *writer) + + See `PyLongWriter_Finish() documentation `__. + +.. c:function:: void PyLongWriter_Discard(PyLongWriter *writer) + + See `PyLongWriter_Discard() documentation `__. + +.. c:function:: int PyLong_IsPositive(PyObject *obj) + + See `PyLong_IsPositive() documentation `__. + +.. c:function:: int PyLong_IsNegative(PyObject *obj) + + See `PyLong_IsNegative() documentation `__. + +.. c:function:: int PyLong_IsZero(PyObject *obj) + + See `PyLong_IsZero() documentation `__. + .. c:function:: int PyLong_GetSign(PyObject *obj, int *sign) See `PyLong_GetSign() documentation `__. +.. c:function:: PyObject* PyIter_NextItem(PyObject *sep, PyObject *iterable) + + See `PyIter_NextItem() documentation `__. + +.. c:function:: PyObject* PyBytes_Join(PyObject *sep, PyObject *iterable) + + See `PyBytes_Join() documentation `__. + +.. c:function:: Py_hash_t Py_HashBuffer(const void *ptr, Py_ssize_t len) + + See `Py_HashBuffer() documentation `__. + +.. c:function:: int PyUnicode_Equal(PyObject *str1, PyObject *str2) + + See `PyUnicode_Equal() documentation `__. + +.. c:function:: PyUnicodeWriter* PyUnicodeWriter_Create(Py_ssize_t length) + + See `PyUnicodeWriter_Create() documentation `__. + +.. c:function:: PyObject* PyUnicodeWriter_Finish(PyUnicodeWriter *writer) + + See `PyUnicodeWriter_Finish() documentation `__. + +.. c:function:: void PyUnicodeWriter_Discard(PyUnicodeWriter *writer) + + See `PyUnicodeWriter_Discard() documentation `__. + +.. c:function:: int PyUnicodeWriter_WriteChar(PyUnicodeWriter *writer, Py_UCS4 ch) + + See `PyUnicodeWriter_WriteChar() documentation `__. + +.. c:function:: int PyUnicodeWriter_WriteUTF8(PyUnicodeWriter *writer, const char *str, Py_ssize_t size) + + See `PyUnicodeWriter_WriteUTF8() documentation `__. + +.. c:function:: int PyUnicodeWriter_WriteASCII(PyUnicodeWriter *writer, const char *str, Py_ssize_t size) + + See `PyUnicodeWriter_WriteASCII() documentation `__. + +.. c:function:: int PyUnicodeWriter_WriteWideChar(PyUnicodeWriter *writer, const wchar_t *str, Py_ssize_t size) + + See `PyUnicodeWriter_WriteWideChar() documentation `__. + +.. c:function:: int PyUnicodeWriter_WriteStr(PyUnicodeWriter *writer, PyObject *obj) + + See `PyUnicodeWriter_WriteStr() documentation `__. + +.. c:function:: int PyUnicodeWriter_WriteRepr(PyUnicodeWriter *writer, PyObject *obj) + + See `PyUnicodeWriter_WriteRepr() documentation `__. + +.. c:function:: int PyUnicodeWriter_WriteSubstring(PyUnicodeWriter *writer, PyObject *str, Py_ssize_t start, Py_ssize_t end) + + See `PyUnicodeWriter_WriteSubstring() documentation `__. + +.. c:function:: int PyUnicodeWriter_Format(PyUnicodeWriter *writer, const char *format, ...) + + See `PyUnicodeWriter_Format() documentation `__. + +.. c:function:: int PyLong_AsInt32(PyObject *obj, int32_t *pvalue) + + See `PyLong_AsInt32() documentation `__. + +.. c:function:: int PyLong_AsInt64(PyObject *obj, int64_t *pvalue) + + See `PyLong_AsInt64() documentation `__. + +.. c:function:: int PyLong_AsUInt32(PyObject *obj, uint32_t *pvalue) + + See `PyLong_AsUInt32() documentation `__. + +.. c:function:: int PyLong_AsUInt64(PyObject *obj, uint64_t *pvalue) + + See `PyLong_AsUInt64() documentation `__. + +.. c:function:: PyObject* PyLong_FromInt32(int32_t value) + + See `PyLong_FromInt32() documentation `__. + +.. c:function:: PyObject* PyLong_FromInt64(int64_t value) + + See `PyLong_FromInt64() documentation `__. + +.. c:function:: PyObject* PyLong_FromUInt32(uint32_t value) + + See `PyLong_FromUInt32() documentation `__. + +.. c:function:: PyObject* PyLong_FromUInt64(uint64_t value) + + See `PyLong_FromUInt64() documentation `__. + +.. c:function:: FILE* Py_fopen(PyObject *path, const char *mode) + + See `Py_fopen() documentation `__. + +.. c:function:: int Py_fclose(FILE *file) + + See `Py_fclose() documentation `__. + +.. c:function:: PyObject* PyConfig_Get(const char *name) + + See `PyConfig_Get() documentation `__. + +.. c:function:: int PyConfig_GetInt(const char *name, int *value) + + See `PyConfig_GetInt() documentation `__. + + +Not supported: + +* ``PyConfig_Names()`` +* ``PyConfig_Set()`` +* ``PyInitConfig_AddModule()`` +* ``PyInitConfig_Create()`` +* ``PyInitConfig_Free()`` +* ``PyInitConfig_FreeStrList()`` +* ``PyInitConfig_GetError()`` +* ``PyInitConfig_GetExitCode()`` +* ``PyInitConfig_GetInt()`` +* ``PyInitConfig_GetStr()`` +* ``PyInitConfig_GetStrList()`` +* ``PyInitConfig_HasOption()`` +* ``PyInitConfig_SetInt()`` +* ``PyInitConfig_SetStr()`` +* ``PyInitConfig_SetStrList()`` +* ``PyType_GetBaseByToken()`` +* ``PyUnicodeWriter_DecodeUTF8Stateful()`` +* ``Py_InitializeFromInitConfig()`` + + Python 3.13 ----------- diff --git a/docs/changelog.rst b/docs/changelog.rst index 1e7ba2a..b8e3a86 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,75 @@ Changelog ========= +* 2025-06-09: Add ``PyUnicodeWriter_WriteASCII()`` function. +* 2025-06-03: Add functions: + + * ``PySys_GetAttr()`` + * ``PySys_GetAttrString()`` + * ``PySys_GetOptionalAttr()`` + * ``PySys_GetOptionalAttrString()`` + +* 2025-01-19: Add ``PyConfig_Get()`` functions. +* 2025-01-06: Add ``Py_fopen()`` and ``Py_fclose()`` functions. +* 2024-12-16: Add ``structmember.h`` constants: + + * ``Py_T_BOOL`` + * ``Py_T_BYTE`` + * ``Py_T_CHAR`` + * ``Py_T_DOUBLE`` + * ``Py_T_FLOAT`` + * ``Py_T_INT`` + * ``Py_T_LONGLONG`` + * ``Py_T_LONG`` + * ``Py_T_OBJECT_EX`` + * ``Py_T_PYSSIZET`` + * ``Py_T_SHORT`` + * ``Py_T_STRING_INPLACE`` + * ``Py_T_STRING`` + * ``Py_T_UBYTE`` + * ``Py_T_UINT`` + * ``Py_T_ULONGLONG`` + * ``Py_T_ULONG`` + * ``Py_T_USHORT`` + * ``_Py_T_NONE`` + * ``_Py_T_OBJECT`` + * ``Py_AUDIT_READ`` + * ``Py_READONLY`` + * ``_Py_WRITE_RESTRICTED`` + +* 2024-12-13: Add functions and structs: + + * ``PyLongLayout`` + * ``PyLong_GetNativeLayout()`` + * ``PyLongExport`` + * ``PyLong_Export()`` + * ``PyLong_FreeExport()`` + * ``PyLongWriter`` + * ``PyLongWriter_Create()`` + * ``PyLongWriter_Finish()`` + * ``PyLongWriter_Discard()`` + +* 2024-11-12: Add functions: + + * ``PyLong_IsPositive()`` + * ``PyLong_IsNegative()`` + * ``PyLong_IsZero()`` + +* 2024-10-09: Add functions: + + * ``PyBytes_Join()`` + * ``PyIter_NextItem()`` + * ``PyLong_AsInt32()`` + * ``PyLong_AsInt64()`` + * ``PyLong_AsUInt32()`` + * ``PyLong_AsUInt64()`` + * ``PyLong_FromInt32()`` + * ``PyLong_FromInt64()`` + * ``PyLong_FromUInt32()`` + * ``PyLong_FromUInt64()`` + * ``PyUnicode_Equal()`` + * ``Py_HashBuffer()`` + * 2024-07-18: Add functions: * ``PyUnicodeWriter_Create()`` diff --git a/docs/users.rst b/docs/users.rst index b90466f..abf4307 100644 --- a/docs/users.rst +++ b/docs/users.rst @@ -45,7 +45,10 @@ Examples of projects using pythoncapi_compat.h `__) * `PyTorch `_ (`pythoncapi_compat.h copy `__) - +* `PyGObject `_ + (`commit `__, + `pythoncapi-compat Meson subproject + `__) Projects not using pythoncapi_compat.h ====================================== diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index d45828f..3320f68 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -7,7 +7,7 @@ // https://github.com/python/pythoncapi_compat // // Latest version: -// https://raw.githubusercontent.com/python/pythoncapi_compat/master/pythoncapi_compat.h +// https://raw.githubusercontent.com/python/pythoncapi-compat/main/pythoncapi_compat.h // // SPDX-License-Identifier: 0BSD @@ -19,11 +19,15 @@ extern "C" { #endif #include +#include // offsetof() // Python 3.11.0b4 added PyFrame_Back() to Python.h #if PY_VERSION_HEX < 0x030b00B4 && !defined(PYPY_VERSION) # include "frameobject.h" // PyFrameObject, PyFrame_GetBack() #endif +#if PY_VERSION_HEX < 0x030C00A3 +# include // T_SHORT, READONLY +#endif #ifndef _Py_CAST @@ -33,11 +37,13 @@ extern "C" { // Static inline functions should use _Py_NULL rather than using directly NULL // to prevent C++ compiler warnings. On C23 and newer and on C++11 and newer, // _Py_NULL is defined as nullptr. -#if (defined (__STDC_VERSION__) && __STDC_VERSION__ > 201710L) \ - || (defined(__cplusplus) && __cplusplus >= 201103) -# define _Py_NULL nullptr -#else -# define _Py_NULL NULL +#ifndef _Py_NULL +# if (defined (__STDC_VERSION__) && __STDC_VERSION__ > 201710L) \ + || (defined(__cplusplus) && __cplusplus >= 201103) +# define _Py_NULL nullptr +# else +# define _Py_NULL NULL +# endif #endif // Cast argument to PyObject* type. @@ -45,6 +51,13 @@ extern "C" { # define _PyObject_CAST(op) _Py_CAST(PyObject*, op) #endif +#ifndef Py_BUILD_ASSERT +# define Py_BUILD_ASSERT(cond) \ + do { \ + (void)sizeof(char [1 - 2 * !(cond)]); \ + } while(0) +#endif + // bpo-42262 added Py_NewRef() to Python 3.10.0a3 #if PY_VERSION_HEX < 0x030A00A3 && !defined(Py_NewRef) @@ -280,7 +293,7 @@ PyFrame_GetVarString(PyFrameObject *frame, const char *name) // bpo-39947 added PyThreadState_GetInterpreter() to Python 3.9.0a5 -#if PY_VERSION_HEX < 0x030900A5 || defined(PYPY_VERSION) +#if PY_VERSION_HEX < 0x030900A5 || (defined(PYPY_VERSION) && PY_VERSION_HEX < 0x030B0000) static inline PyInterpreterState * PyThreadState_GetInterpreter(PyThreadState *tstate) { @@ -573,7 +586,7 @@ static inline int PyWeakref_GetRef(PyObject *ref, PyObject **pobj) return 0; } *pobj = Py_NewRef(obj); - return (*pobj != NULL); + return 1; } #endif @@ -911,7 +924,7 @@ static inline int PyObject_VisitManagedDict(PyObject *obj, visitproc visit, void *arg) { PyObject **dict = _PyObject_GetDictPtr(obj); - if (*dict == NULL) { + if (dict == NULL || *dict == NULL) { return -1; } Py_VISIT(*dict); @@ -922,7 +935,7 @@ static inline void PyObject_ClearManagedDict(PyObject *obj) { PyObject **dict = _PyObject_GetDictPtr(obj); - if (*dict == NULL) { + if (dict == NULL || *dict == NULL) { return; } Py_CLEAR(*dict); @@ -1197,11 +1210,11 @@ static inline int PyTime_PerfCounter(PyTime_t *result) #endif // gh-111389 added hash constants to Python 3.13.0a5. These constants were -// added first as private macros to Python 3.4.0b1 and PyPy 7.3.9. +// added first as private macros to Python 3.4.0b1 and PyPy 7.3.8. #if (!defined(PyHASH_BITS) \ && ((!defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x030400B1) \ || (defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x03070000 \ - && PYPY_VERSION_NUM >= 0x07090000))) + && PYPY_VERSION_NUM >= 0x07030800))) # define PyHASH_BITS _PyHASH_BITS # define PyHASH_MODULUS _PyHASH_MODULUS # define PyHASH_INF _PyHASH_INF @@ -1338,6 +1351,13 @@ PyDict_SetDefaultRef(PyObject *d, PyObject *key, PyObject *default_value, } #endif +#if PY_VERSION_HEX < 0x030D00B3 +# define Py_BEGIN_CRITICAL_SECTION(op) { +# define Py_END_CRITICAL_SECTION() } +# define Py_BEGIN_CRITICAL_SECTION2(a, b) { +# define Py_END_CRITICAL_SECTION2() } +#endif + #if PY_VERSION_HEX < 0x030E0000 && PY_VERSION_HEX >= 0x03060000 && !defined(PYPY_VERSION) typedef struct PyUnicodeWriter PyUnicodeWriter; @@ -1392,7 +1412,7 @@ PyUnicodeWriter_WriteChar(PyUnicodeWriter *writer, Py_UCS4 ch) return _PyUnicodeWriter_WriteChar((_PyUnicodeWriter*)writer, ch); } -int +static inline int PyUnicodeWriter_WriteStr(PyUnicodeWriter *writer, PyObject *obj) { PyObject *str = PyObject_Str(obj); @@ -1405,7 +1425,7 @@ PyUnicodeWriter_WriteStr(PyUnicodeWriter *writer, PyObject *obj) return res; } -int +static inline int PyUnicodeWriter_WriteRepr(PyUnicodeWriter *writer, PyObject *obj) { PyObject *str = PyObject_Repr(obj); @@ -1436,6 +1456,18 @@ PyUnicodeWriter_WriteUTF8(PyUnicodeWriter *writer, return res; } +static inline int +PyUnicodeWriter_WriteASCII(PyUnicodeWriter *writer, + const char *str, Py_ssize_t size) +{ + if (size < 0) { + size = (Py_ssize_t)strlen(str); + } + + return _PyUnicodeWriter_WriteASCIIString((_PyUnicodeWriter*)writer, + str, size); +} + static inline int PyUnicodeWriter_WriteWideChar(PyUnicodeWriter *writer, const wchar_t *str, Py_ssize_t size) @@ -1459,7 +1491,8 @@ PyUnicodeWriter_WriteSubstring(PyUnicodeWriter *writer, PyObject *str, Py_ssize_t start, Py_ssize_t end) { if (!PyUnicode_Check(str)) { - PyErr_Format(PyExc_TypeError, "expect str, not %T", str); + PyErr_Format(PyExc_TypeError, "expect str, not %s", + Py_TYPE(str)->tp_name); return -1; } if (start < 0 || start > end) { @@ -1506,6 +1539,743 @@ static inline int PyLong_GetSign(PyObject *obj, int *sign) } #endif +// gh-126061 added PyLong_IsPositive/Negative/Zero() to Python in 3.14.0a2 +#if PY_VERSION_HEX < 0x030E00A2 +static inline int PyLong_IsPositive(PyObject *obj) +{ + if (!PyLong_Check(obj)) { + PyErr_Format(PyExc_TypeError, "expected int, got %s", Py_TYPE(obj)->tp_name); + return -1; + } + return _PyLong_Sign(obj) == 1; +} + +static inline int PyLong_IsNegative(PyObject *obj) +{ + if (!PyLong_Check(obj)) { + PyErr_Format(PyExc_TypeError, "expected int, got %s", Py_TYPE(obj)->tp_name); + return -1; + } + return _PyLong_Sign(obj) == -1; +} + +static inline int PyLong_IsZero(PyObject *obj) +{ + if (!PyLong_Check(obj)) { + PyErr_Format(PyExc_TypeError, "expected int, got %s", Py_TYPE(obj)->tp_name); + return -1; + } + return _PyLong_Sign(obj) == 0; +} +#endif + + +// gh-124502 added PyUnicode_Equal() to Python 3.14.0a0 +#if PY_VERSION_HEX < 0x030E00A0 +static inline int PyUnicode_Equal(PyObject *str1, PyObject *str2) +{ + if (!PyUnicode_Check(str1)) { + PyErr_Format(PyExc_TypeError, "first argument must be str, not %s", + Py_TYPE(str1)->tp_name); + return -1; + } + if (!PyUnicode_Check(str2)) { + PyErr_Format(PyExc_TypeError, "second argument must be str, not %s", + Py_TYPE(str2)->tp_name); + return -1; + } + +#if PY_VERSION_HEX >= 0x030d0000 && !defined(PYPY_VERSION) + PyAPI_FUNC(int) _PyUnicode_Equal(PyObject *str1, PyObject *str2); + + return _PyUnicode_Equal(str1, str2); +#elif PY_VERSION_HEX >= 0x03060000 && !defined(PYPY_VERSION) + return _PyUnicode_EQ(str1, str2); +#elif PY_VERSION_HEX >= 0x03090000 && defined(PYPY_VERSION) + return _PyUnicode_EQ(str1, str2); +#else + return (PyUnicode_Compare(str1, str2) == 0); +#endif +} +#endif + + +// gh-121645 added PyBytes_Join() to Python 3.14.0a0 +#if PY_VERSION_HEX < 0x030E00A0 +static inline PyObject* PyBytes_Join(PyObject *sep, PyObject *iterable) +{ + return _PyBytes_Join(sep, iterable); +} +#endif + + +#if PY_VERSION_HEX < 0x030E00A0 +static inline Py_hash_t Py_HashBuffer(const void *ptr, Py_ssize_t len) +{ +#if PY_VERSION_HEX >= 0x03000000 && !defined(PYPY_VERSION) + PyAPI_FUNC(Py_hash_t) _Py_HashBytes(const void *src, Py_ssize_t len); + + return _Py_HashBytes(ptr, len); +#else + Py_hash_t hash; + PyObject *bytes = PyBytes_FromStringAndSize((const char*)ptr, len); + if (bytes == NULL) { + return -1; + } + hash = PyObject_Hash(bytes); + Py_DECREF(bytes); + return hash; +#endif +} +#endif + + +#if PY_VERSION_HEX < 0x030E00A0 +static inline int PyIter_NextItem(PyObject *iter, PyObject **item) +{ + iternextfunc tp_iternext; + + assert(iter != NULL); + assert(item != NULL); + + tp_iternext = Py_TYPE(iter)->tp_iternext; + if (tp_iternext == NULL) { + *item = NULL; + PyErr_Format(PyExc_TypeError, "expected an iterator, got '%s'", + Py_TYPE(iter)->tp_name); + return -1; + } + + if ((*item = tp_iternext(iter))) { + return 1; + } + if (!PyErr_Occurred()) { + return 0; + } + if (PyErr_ExceptionMatches(PyExc_StopIteration)) { + PyErr_Clear(); + return 0; + } + return -1; +} +#endif + + +#if PY_VERSION_HEX < 0x030E00A0 +static inline PyObject* PyLong_FromInt32(int32_t value) +{ + Py_BUILD_ASSERT(sizeof(long) >= 4); + return PyLong_FromLong(value); +} + +static inline PyObject* PyLong_FromInt64(int64_t value) +{ + Py_BUILD_ASSERT(sizeof(long long) >= 8); + return PyLong_FromLongLong(value); +} + +static inline PyObject* PyLong_FromUInt32(uint32_t value) +{ + Py_BUILD_ASSERT(sizeof(unsigned long) >= 4); + return PyLong_FromUnsignedLong(value); +} + +static inline PyObject* PyLong_FromUInt64(uint64_t value) +{ + Py_BUILD_ASSERT(sizeof(unsigned long long) >= 8); + return PyLong_FromUnsignedLongLong(value); +} + +static inline int PyLong_AsInt32(PyObject *obj, int32_t *pvalue) +{ + Py_BUILD_ASSERT(sizeof(int) == 4); + int value = PyLong_AsInt(obj); + if (value == -1 && PyErr_Occurred()) { + return -1; + } + *pvalue = (int32_t)value; + return 0; +} + +static inline int PyLong_AsInt64(PyObject *obj, int64_t *pvalue) +{ + Py_BUILD_ASSERT(sizeof(long long) == 8); + long long value = PyLong_AsLongLong(obj); + if (value == -1 && PyErr_Occurred()) { + return -1; + } + *pvalue = (int64_t)value; + return 0; +} + +static inline int PyLong_AsUInt32(PyObject *obj, uint32_t *pvalue) +{ + Py_BUILD_ASSERT(sizeof(long) >= 4); + unsigned long value = PyLong_AsUnsignedLong(obj); + if (value == (unsigned long)-1 && PyErr_Occurred()) { + return -1; + } +#if SIZEOF_LONG > 4 + if ((unsigned long)UINT32_MAX < value) { + PyErr_SetString(PyExc_OverflowError, + "Python int too large to convert to C uint32_t"); + return -1; + } +#endif + *pvalue = (uint32_t)value; + return 0; +} + +static inline int PyLong_AsUInt64(PyObject *obj, uint64_t *pvalue) +{ + Py_BUILD_ASSERT(sizeof(long long) == 8); + unsigned long long value = PyLong_AsUnsignedLongLong(obj); + if (value == (unsigned long long)-1 && PyErr_Occurred()) { + return -1; + } + *pvalue = (uint64_t)value; + return 0; +} +#endif + + +// gh-102471 added import and export API for integers to 3.14.0a2. +#if PY_VERSION_HEX < 0x030E00A2 && PY_VERSION_HEX >= 0x03000000 && !defined(PYPY_VERSION) +// Helpers to access PyLongObject internals. +static inline void +_PyLong_SetSignAndDigitCount(PyLongObject *op, int sign, Py_ssize_t size) +{ +#if PY_VERSION_HEX >= 0x030C0000 + op->long_value.lv_tag = (uintptr_t)(1 - sign) | ((uintptr_t)(size) << 3); +#elif PY_VERSION_HEX >= 0x030900A4 + Py_SET_SIZE(op, sign * size); +#else + Py_SIZE(op) = sign * size; +#endif +} + +static inline Py_ssize_t +_PyLong_DigitCount(const PyLongObject *op) +{ +#if PY_VERSION_HEX >= 0x030C0000 + return (Py_ssize_t)(op->long_value.lv_tag >> 3); +#else + return _PyLong_Sign((PyObject*)op) < 0 ? -Py_SIZE(op) : Py_SIZE(op); +#endif +} + +static inline digit* +_PyLong_GetDigits(const PyLongObject *op) +{ +#if PY_VERSION_HEX >= 0x030C0000 + return (digit*)(op->long_value.ob_digit); +#else + return (digit*)(op->ob_digit); +#endif +} + +typedef struct PyLongLayout { + uint8_t bits_per_digit; + uint8_t digit_size; + int8_t digits_order; + int8_t digit_endianness; +} PyLongLayout; + +typedef struct PyLongExport { + int64_t value; + uint8_t negative; + Py_ssize_t ndigits; + const void *digits; + Py_uintptr_t _reserved; +} PyLongExport; + +typedef struct PyLongWriter PyLongWriter; + +static inline const PyLongLayout* +PyLong_GetNativeLayout(void) +{ + static const PyLongLayout PyLong_LAYOUT = { + PyLong_SHIFT, + sizeof(digit), + -1, // least significant first + PY_LITTLE_ENDIAN ? -1 : 1, + }; + + return &PyLong_LAYOUT; +} + +static inline int +PyLong_Export(PyObject *obj, PyLongExport *export_long) +{ + if (!PyLong_Check(obj)) { + memset(export_long, 0, sizeof(*export_long)); + PyErr_Format(PyExc_TypeError, "expected int, got %s", + Py_TYPE(obj)->tp_name); + return -1; + } + + // Fast-path: try to convert to a int64_t + PyLongObject *self = (PyLongObject*)obj; + int overflow; +#if SIZEOF_LONG == 8 + long value = PyLong_AsLongAndOverflow(obj, &overflow); +#else + // Windows has 32-bit long, so use 64-bit long long instead + long long value = PyLong_AsLongLongAndOverflow(obj, &overflow); +#endif + Py_BUILD_ASSERT(sizeof(value) == sizeof(int64_t)); + // the function cannot fail since obj is a PyLongObject + assert(!(value == -1 && PyErr_Occurred())); + + if (!overflow) { + export_long->value = value; + export_long->negative = 0; + export_long->ndigits = 0; + export_long->digits = 0; + export_long->_reserved = 0; + } + else { + export_long->value = 0; + export_long->negative = _PyLong_Sign(obj) < 0; + export_long->ndigits = _PyLong_DigitCount(self); + if (export_long->ndigits == 0) { + export_long->ndigits = 1; + } + export_long->digits = _PyLong_GetDigits(self); + export_long->_reserved = (Py_uintptr_t)Py_NewRef(obj); + } + return 0; +} + +static inline void +PyLong_FreeExport(PyLongExport *export_long) +{ + PyObject *obj = (PyObject*)export_long->_reserved; + + if (obj) { + export_long->_reserved = 0; + Py_DECREF(obj); + } +} + +static inline PyLongWriter* +PyLongWriter_Create(int negative, Py_ssize_t ndigits, void **digits) +{ + if (ndigits <= 0) { + PyErr_SetString(PyExc_ValueError, "ndigits must be positive"); + return NULL; + } + assert(digits != NULL); + + PyLongObject *obj = _PyLong_New(ndigits); + if (obj == NULL) { + return NULL; + } + _PyLong_SetSignAndDigitCount(obj, negative?-1:1, ndigits); + + *digits = _PyLong_GetDigits(obj); + return (PyLongWriter*)obj; +} + +static inline void +PyLongWriter_Discard(PyLongWriter *writer) +{ + PyLongObject *obj = (PyLongObject *)writer; + + assert(Py_REFCNT(obj) == 1); + Py_DECREF(obj); +} + +static inline PyObject* +PyLongWriter_Finish(PyLongWriter *writer) +{ + PyObject *obj = (PyObject *)writer; + PyLongObject *self = (PyLongObject*)obj; + Py_ssize_t j = _PyLong_DigitCount(self); + Py_ssize_t i = j; + int sign = _PyLong_Sign(obj); + + assert(Py_REFCNT(obj) == 1); + + // Normalize and get singleton if possible + while (i > 0 && _PyLong_GetDigits(self)[i-1] == 0) { + --i; + } + if (i != j) { + if (i == 0) { + sign = 0; + } + _PyLong_SetSignAndDigitCount(self, sign, i); + } + if (i <= 1) { + long val = sign * (long)(_PyLong_GetDigits(self)[0]); + Py_DECREF(obj); + return PyLong_FromLong(val); + } + + return obj; +} +#endif + + +#if PY_VERSION_HEX < 0x030C00A3 +# define Py_T_SHORT T_SHORT +# define Py_T_INT T_INT +# define Py_T_LONG T_LONG +# define Py_T_FLOAT T_FLOAT +# define Py_T_DOUBLE T_DOUBLE +# define Py_T_STRING T_STRING +# define _Py_T_OBJECT T_OBJECT +# define Py_T_CHAR T_CHAR +# define Py_T_BYTE T_BYTE +# define Py_T_UBYTE T_UBYTE +# define Py_T_USHORT T_USHORT +# define Py_T_UINT T_UINT +# define Py_T_ULONG T_ULONG +# define Py_T_STRING_INPLACE T_STRING_INPLACE +# define Py_T_BOOL T_BOOL +# define Py_T_OBJECT_EX T_OBJECT_EX +# define Py_T_LONGLONG T_LONGLONG +# define Py_T_ULONGLONG T_ULONGLONG +# define Py_T_PYSSIZET T_PYSSIZET + +# if PY_VERSION_HEX >= 0x03000000 && !defined(PYPY_VERSION) +# define _Py_T_NONE T_NONE +# endif + +# define Py_READONLY READONLY +# define Py_AUDIT_READ READ_RESTRICTED +# define _Py_WRITE_RESTRICTED PY_WRITE_RESTRICTED +#endif + + +// gh-127350 added Py_fopen() and Py_fclose() to Python 3.14a4 +#if PY_VERSION_HEX < 0x030E00A4 +static inline FILE* Py_fopen(PyObject *path, const char *mode) +{ +#if 0x030400A2 <= PY_VERSION_HEX && !defined(PYPY_VERSION) + PyAPI_FUNC(FILE*) _Py_fopen_obj(PyObject *path, const char *mode); + + return _Py_fopen_obj(path, mode); +#else + FILE *f; + PyObject *bytes; +#if PY_VERSION_HEX >= 0x03000000 + if (!PyUnicode_FSConverter(path, &bytes)) { + return NULL; + } +#else + if (!PyString_Check(path)) { + PyErr_SetString(PyExc_TypeError, "except str"); + return NULL; + } + bytes = Py_NewRef(path); +#endif + const char *path_bytes = PyBytes_AS_STRING(bytes); + + f = fopen(path_bytes, mode); + Py_DECREF(bytes); + + if (f == NULL) { + PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, path); + return NULL; + } + return f; +#endif +} + +static inline int Py_fclose(FILE *file) +{ + return fclose(file); +} +#endif + + +#if 0x03090000 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x030E0000 && !defined(PYPY_VERSION) +static inline PyObject* +PyConfig_Get(const char *name) +{ + typedef enum { + _PyConfig_MEMBER_INT, + _PyConfig_MEMBER_UINT, + _PyConfig_MEMBER_ULONG, + _PyConfig_MEMBER_BOOL, + _PyConfig_MEMBER_WSTR, + _PyConfig_MEMBER_WSTR_OPT, + _PyConfig_MEMBER_WSTR_LIST, + } PyConfigMemberType; + + typedef struct { + const char *name; + size_t offset; + PyConfigMemberType type; + const char *sys_attr; + } PyConfigSpec; + +#define PYTHONCAPI_COMPAT_SPEC(MEMBER, TYPE, sys_attr) \ + {#MEMBER, offsetof(PyConfig, MEMBER), \ + _PyConfig_MEMBER_##TYPE, sys_attr} + + static const PyConfigSpec config_spec[] = { + PYTHONCAPI_COMPAT_SPEC(argv, WSTR_LIST, "argv"), + PYTHONCAPI_COMPAT_SPEC(base_exec_prefix, WSTR_OPT, "base_exec_prefix"), + PYTHONCAPI_COMPAT_SPEC(base_executable, WSTR_OPT, "_base_executable"), + PYTHONCAPI_COMPAT_SPEC(base_prefix, WSTR_OPT, "base_prefix"), + PYTHONCAPI_COMPAT_SPEC(bytes_warning, UINT, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(exec_prefix, WSTR_OPT, "exec_prefix"), + PYTHONCAPI_COMPAT_SPEC(executable, WSTR_OPT, "executable"), + PYTHONCAPI_COMPAT_SPEC(inspect, BOOL, _Py_NULL), +#if 0x030C0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(int_max_str_digits, UINT, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(interactive, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(module_search_paths, WSTR_LIST, "path"), + PYTHONCAPI_COMPAT_SPEC(optimization_level, UINT, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(parser_debug, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(platlibdir, WSTR, "platlibdir"), + PYTHONCAPI_COMPAT_SPEC(prefix, WSTR_OPT, "prefix"), + PYTHONCAPI_COMPAT_SPEC(pycache_prefix, WSTR_OPT, "pycache_prefix"), + PYTHONCAPI_COMPAT_SPEC(quiet, BOOL, _Py_NULL), +#if 0x030B0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(stdlib_dir, WSTR_OPT, "_stdlib_dir"), +#endif + PYTHONCAPI_COMPAT_SPEC(use_environment, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(verbose, UINT, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(warnoptions, WSTR_LIST, "warnoptions"), + PYTHONCAPI_COMPAT_SPEC(write_bytecode, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(xoptions, WSTR_LIST, "_xoptions"), + PYTHONCAPI_COMPAT_SPEC(buffered_stdio, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(check_hash_pycs_mode, WSTR, _Py_NULL), +#if 0x030B0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(code_debug_ranges, BOOL, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(configure_c_stdio, BOOL, _Py_NULL), +#if 0x030D0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(cpu_count, INT, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(dev_mode, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(dump_refs, BOOL, _Py_NULL), +#if 0x030B0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(dump_refs_file, WSTR_OPT, _Py_NULL), +#endif +#ifdef Py_GIL_DISABLED + PYTHONCAPI_COMPAT_SPEC(enable_gil, INT, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(faulthandler, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(filesystem_encoding, WSTR, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(filesystem_errors, WSTR, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(hash_seed, ULONG, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(home, WSTR_OPT, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(import_time, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(install_signal_handlers, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(isolated, BOOL, _Py_NULL), +#ifdef MS_WINDOWS + PYTHONCAPI_COMPAT_SPEC(legacy_windows_stdio, BOOL, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(malloc_stats, BOOL, _Py_NULL), +#if 0x030A0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(orig_argv, WSTR_LIST, "orig_argv"), +#endif + PYTHONCAPI_COMPAT_SPEC(parse_argv, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(pathconfig_warnings, BOOL, _Py_NULL), +#if 0x030C0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(perf_profiling, UINT, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(program_name, WSTR, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(run_command, WSTR_OPT, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(run_filename, WSTR_OPT, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(run_module, WSTR_OPT, _Py_NULL), +#if 0x030B0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(safe_path, BOOL, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(show_ref_count, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(site_import, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(skip_source_first_line, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(stdio_encoding, WSTR, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(stdio_errors, WSTR, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(tracemalloc, UINT, _Py_NULL), +#if 0x030B0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(use_frozen_modules, BOOL, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(use_hash_seed, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(user_site_directory, BOOL, _Py_NULL), +#if 0x030A0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(warn_default_encoding, BOOL, _Py_NULL), +#endif + }; + +#undef PYTHONCAPI_COMPAT_SPEC + + const PyConfigSpec *spec; + int found = 0; + for (size_t i=0; i < sizeof(config_spec) / sizeof(config_spec[0]); i++) { + spec = &config_spec[i]; + if (strcmp(spec->name, name) == 0) { + found = 1; + break; + } + } + if (found) { + if (spec->sys_attr != NULL) { + PyObject *value = PySys_GetObject(spec->sys_attr); + if (value == NULL) { + PyErr_Format(PyExc_RuntimeError, "lost sys.%s", spec->sys_attr); + return NULL; + } + return Py_NewRef(value); + } + + PyAPI_FUNC(const PyConfig*) _Py_GetConfig(void); + + const PyConfig *config = _Py_GetConfig(); + void *member = (char *)config + spec->offset; + switch (spec->type) { + case _PyConfig_MEMBER_INT: + case _PyConfig_MEMBER_UINT: + { + int value = *(int *)member; + return PyLong_FromLong(value); + } + case _PyConfig_MEMBER_BOOL: + { + int value = *(int *)member; + return PyBool_FromLong(value != 0); + } + case _PyConfig_MEMBER_ULONG: + { + unsigned long value = *(unsigned long *)member; + return PyLong_FromUnsignedLong(value); + } + case _PyConfig_MEMBER_WSTR: + case _PyConfig_MEMBER_WSTR_OPT: + { + wchar_t *wstr = *(wchar_t **)member; + if (wstr != NULL) { + return PyUnicode_FromWideChar(wstr, -1); + } + else { + return Py_NewRef(Py_None); + } + } + case _PyConfig_MEMBER_WSTR_LIST: + { + const PyWideStringList *list = (const PyWideStringList *)member; + PyObject *tuple = PyTuple_New(list->length); + if (tuple == NULL) { + return NULL; + } + + for (Py_ssize_t i = 0; i < list->length; i++) { + PyObject *item = PyUnicode_FromWideChar(list->items[i], -1); + if (item == NULL) { + Py_DECREF(tuple); + return NULL; + } + PyTuple_SET_ITEM(tuple, i, item); + } + return tuple; + } + default: + Py_UNREACHABLE(); + } + } + + PyErr_Format(PyExc_ValueError, "unknown config option name: %s", name); + return NULL; +} + +static inline int +PyConfig_GetInt(const char *name, int *value) +{ + PyObject *obj = PyConfig_Get(name); + if (obj == NULL) { + return -1; + } + + if (!PyLong_Check(obj)) { + Py_DECREF(obj); + PyErr_Format(PyExc_TypeError, "config option %s is not an int", name); + return -1; + } + + int as_int = PyLong_AsInt(obj); + Py_DECREF(obj); + if (as_int == -1 && PyErr_Occurred()) { + PyErr_Format(PyExc_OverflowError, + "config option %s value does not fit into a C int", name); + return -1; + } + + *value = as_int; + return 0; +} +#endif // PY_VERSION_HEX > 0x03090000 && !defined(PYPY_VERSION) + + +#if PY_VERSION_HEX < 0x030F0000 +static inline PyObject* +PySys_GetAttrString(const char *name) +{ +#if PY_VERSION_HEX >= 0x03000000 + PyObject *value = Py_XNewRef(PySys_GetObject(name)); +#else + PyObject *value = Py_XNewRef(PySys_GetObject((char*)name)); +#endif + if (value != NULL) { + return value; + } + if (!PyErr_Occurred()) { + PyErr_Format(PyExc_RuntimeError, "lost sys.%s", name); + } + return NULL; +} + +static inline PyObject* +PySys_GetAttr(PyObject *name) +{ +#if PY_VERSION_HEX >= 0x03000000 + const char *name_str = PyUnicode_AsUTF8(name); +#else + const char *name_str = PyString_AsString(name); +#endif + if (name_str == NULL) { + return NULL; + } + + return PySys_GetAttrString(name_str); +} + +static inline int +PySys_GetOptionalAttrString(const char *name, PyObject **value) +{ +#if PY_VERSION_HEX >= 0x03000000 + *value = Py_XNewRef(PySys_GetObject(name)); +#else + *value = Py_XNewRef(PySys_GetObject((char*)name)); +#endif + if (*value != NULL) { + return 1; + } + return 0; +} + +static inline int +PySys_GetOptionalAttr(PyObject *name, PyObject **value) +{ +#if PY_VERSION_HEX >= 0x03000000 + const char *name_str = PyUnicode_AsUTF8(name); +#else + const char *name_str = PyString_AsString(name); +#endif + if (name_str == NULL) { + *value = NULL; + return -1; + } + + return PySys_GetOptionalAttrString(name_str, value); +} +#endif // PY_VERSION_HEX < 0x030F00A1 + #ifdef __cplusplus } diff --git a/runtests.py b/runtests.py index 5064550..c858516 100755 --- a/runtests.py +++ b/runtests.py @@ -13,7 +13,6 @@ import argparse import os.path import shutil -import subprocess import sys try: from shutil import which @@ -53,6 +52,7 @@ "pypy3.8", "pypy3.9", "pypy3.10", + "pypy3.11", ) diff --git a/tests/setup.py b/tests/setup.py index 2e10c15..825241e 100755 --- a/tests/setup.py +++ b/tests/setup.py @@ -13,38 +13,42 @@ # C++ is only supported on Python 3.6 and newer -TEST_CPP = (sys.version_info >= (3, 6)) +TEST_CXX = (sys.version_info >= (3, 6)) SRC_DIR = os.path.normpath(os.path.join(os.path.dirname(__file__), '..')) # Windows uses MSVC compiler MSVC = (os.name == "nt") -# C compiler flags for GCC and clang COMMON_FLAGS = [ - # Treat warnings as error - '-Werror', - # Enable all warnings - '-Wall', '-Wextra', - # Extra warnings - '-Wconversion', - # /usr/lib64/pypy3.7/include/pyport.h:68:20: error: redefinition of typedef - # 'Py_hash_t' is a C11 feature - "-Wno-typedef-redefinition", + '-I' + SRC_DIR, ] -CFLAGS = COMMON_FLAGS + [ - # Use C99 for pythoncapi_compat.c which initializes PyModuleDef with a - # mixture of designated and non-designated initializers - '-std=c99', -] -CPPFLAGS = list(COMMON_FLAGS) -# FIXME: _Py_CAST() emits C++ compilers on Python 3.12. -# See: https://github.com/python/cpython/issues/94731 -if 0: - CPPFLAGS.extend(( - '-Wold-style-cast', - '-Wzero-as-null-pointer-constant', +if not MSVC: + # C compiler flags for GCC and clang + COMMON_FLAGS.extend(( + # Treat warnings as error + '-Werror', + # Enable all warnings + '-Wall', '-Wextra', + # Extra warnings + '-Wconversion', + # /usr/lib64/pypy3.7/include/pyport.h:68:20: error: redefinition of typedef + # 'Py_hash_t' is a C11 feature + "-Wno-typedef-redefinition", + )) + CFLAGS = COMMON_FLAGS + [ + # Use C99 for pythoncapi_compat.c which initializes PyModuleDef with a + # mixture of designated and non-designated initializers + '-std=c99', + ] +else: + # C compiler flags for MSVC + COMMON_FLAGS.extend(( + # Treat all compiler warnings as compiler errors + '/WX', )) + CFLAGS = list(COMMON_FLAGS) +CXXFLAGS = list(COMMON_FLAGS) def main(): @@ -66,34 +70,31 @@ def main(): # CC env var overrides sysconfig CC variable in setuptools os.environ['CC'] = cmd - cflags = ['-I' + SRC_DIR] - cppflags = list(cflags) - if not MSVC: - cflags.extend(CFLAGS) - cppflags.extend(CPPFLAGS) - # C extension c_ext = Extension( 'test_pythoncapi_compat_cext', sources=['test_pythoncapi_compat_cext.c'], - extra_compile_args=cflags) + extra_compile_args=CFLAGS) extensions = [c_ext] - if TEST_CPP: + if TEST_CXX: # C++ extension # MSVC has /std flag but doesn't support /std:c++11 if not MSVC: versions = [ - ('test_pythoncapi_compat_cpp03ext', '-std=c++03'), - ('test_pythoncapi_compat_cpp11ext', '-std=c++11'), + ('test_pythoncapi_compat_cpp03ext', ['-std=c++03']), + ('test_pythoncapi_compat_cpp11ext', ['-std=c++11']), ] else: - versions = [('test_pythoncapi_compat_cppext', None)] - for name, flag in versions: - flags = list(cppflags) - if flag is not None: - flags.append(flag) + versions = [ + ('test_pythoncapi_compat_cppext', None), + ('test_pythoncapi_compat_cpp14ext', ['/std:c++14', '/Zc:__cplusplus']), + ] + for name, std_flags in versions: + flags = list(CXXFLAGS) + if std_flags is not None: + flags.extend(std_flags) cpp_ext = Extension( name, sources=['test_pythoncapi_compat_cppext.cpp'], diff --git a/tests/test_pythoncapi_compat.py b/tests/test_pythoncapi_compat.py index 17ade5a..8480415 100644 --- a/tests/test_pythoncapi_compat.py +++ b/tests/test_pythoncapi_compat.py @@ -24,12 +24,23 @@ from utils import run_command, command_stdout +# Windows uses MSVC compiler +MSVC = (os.name == "nt") + TESTS = [ ("test_pythoncapi_compat_cext", "C"), - ("test_pythoncapi_compat_cppext", "C++"), - ("test_pythoncapi_compat_cpp03ext", "C++03"), - ("test_pythoncapi_compat_cpp11ext", "C++11"), ] +if not MSVC: + TESTS.extend(( + ("test_pythoncapi_compat_cpp03ext", "C++03"), + ("test_pythoncapi_compat_cpp11ext", "C++11"), + )) +else: + TESTS.extend(( + ("test_pythoncapi_compat_cppext", "C++"), + ("test_pythoncapi_compat_cpp14ext", "C++14"), + )) + VERBOSE = False diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index f813548..6e76316 100644 --- a/tests/test_pythoncapi_compat_cext.c +++ b/tests/test_pythoncapi_compat_cext.c @@ -15,12 +15,14 @@ # define PYTHON3 1 #endif -#if defined(_MSC_VER) && defined(__cplusplus) -# define MODULE_NAME test_pythoncapi_compat_cppext +#if defined(__cplusplus) && __cplusplus >= 201402 +# define MODULE_NAME test_pythoncapi_compat_cpp14ext #elif defined(__cplusplus) && __cplusplus >= 201103 # define MODULE_NAME test_pythoncapi_compat_cpp11ext -#elif defined(__cplusplus) +#elif defined(__cplusplus) && !defined(_MSC_VER) # define MODULE_NAME test_pythoncapi_compat_cpp03ext +#elif defined(__cplusplus) +# define MODULE_NAME test_pythoncapi_compat_cppext #else # define MODULE_NAME test_pythoncapi_compat_cext #endif @@ -756,7 +758,7 @@ test_import(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) static void gc_collect(void) { -#if defined(PYPY_VERSION) +#if defined(PYPY_VERSION) && PY_VERSION_HEX < 0x030B0000 PyObject *mod = PyImport_ImportModule("gc"); assert(mod != _Py_NULL); @@ -1419,14 +1421,63 @@ test_long_api(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) assert(PyLong_GetSign(obj, &sign) == 0); assert(sign == 1); + // test PyLong_IsPositive(), PyLong_IsNegative() and PyLong_IsZero() + assert(PyLong_IsPositive(obj) == 1); + assert(PyLong_IsNegative(obj) == 0); + assert(PyLong_IsZero(obj) == 0); + +#if defined(PYTHON3) && !defined(PYPY_VERSION) + // test import/export API + digit *digits; + PyLongWriter *writer; + static PyLongExport long_export; + + writer = PyLongWriter_Create(1, 1, (void**)&digits); + PyLongWriter_Discard(writer); + + writer = PyLongWriter_Create(1, 1, (void**)&digits); + digits[0] = 123; + obj = PyLongWriter_Finish(writer); + + check_int(obj, -123); + PyLong_Export(obj, &long_export); + assert(long_export.value == -123); + assert(long_export.digits == NULL); + PyLong_FreeExport(&long_export); + Py_DECREF(obj); + + writer = PyLongWriter_Create(0, 5, (void**)&digits); + digits[0] = 1; + digits[1] = 0; + digits[2] = 0; + digits[3] = 0; + digits[4] = 1; + obj = PyLongWriter_Finish(writer); + + PyLong_Export(obj, &long_export); + assert(long_export.value == 0); + digits = (digit*)long_export.digits; + assert(digits[0] == 1); + assert(digits[1] == 0); + assert(digits[2] == 0); + assert(digits[3] == 0); + assert(digits[4] == 1); + PyLong_FreeExport(&long_export); + Py_DECREF(obj); + + const PyLongLayout *layout = PyLong_GetNativeLayout(); + assert(layout->digits_order == -1); + assert(layout->digit_size == sizeof(digit)); +#endif // defined(PYTHON3) && !defined(PYPY_VERSION) + Py_RETURN_NONE; } // --- HeapCTypeWithManagedDict -------------------------------------------- -// Py_TPFLAGS_MANAGED_DICT was added to Python 3.11.0a3 -#if PY_VERSION_HEX >= 0x030B00A3 +// Py_TPFLAGS_MANAGED_DICT was added to Python 3.11.0a3 but is not implemented on PyPy +#if PY_VERSION_HEX >= 0x030B00A3 && ! defined(PYPY_VERSION) # define TEST_MANAGED_DICT typedef struct { @@ -1437,12 +1488,14 @@ static int heapmanaged_traverse(PyObject *self, visitproc visit, void *arg) { Py_VISIT(Py_TYPE(self)); + // Test PyObject_VisitManagedDict() return PyObject_VisitManagedDict(self, visit, arg); } static int heapmanaged_clear(PyObject *self) { + // Test PyObject_ClearManagedDict() PyObject_ClearManagedDict(self); return 0; } @@ -1475,6 +1528,7 @@ static PyType_Spec HeapCTypeWithManagedDict_spec = { static PyObject * test_managed_dict(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) { + // Test PyObject_VisitManagedDict() and PyObject_ClearManagedDict() PyObject *type = PyType_FromSpec(&HeapCTypeWithManagedDict_spec); if (type == NULL) { return NULL; @@ -1531,6 +1585,13 @@ test_unicode(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) assert(PyErr_ExceptionMatches(PyExc_MemoryError)); PyErr_Clear(); + // Test PyUnicode_Equal() + assert(PyUnicode_Equal(abc, abc) == 1); + assert(PyUnicode_Equal(abc, abc0def) == 0); + assert(PyUnicode_Equal(abc, Py_True) == -1); + assert(PyErr_ExceptionMatches(PyExc_TypeError)); + PyErr_Clear(); + Py_DECREF(abc); Py_DECREF(abc0def); Py_RETURN_NONE; @@ -1599,7 +1660,7 @@ test_hash(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) #if ((!defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x030400B1) \ || (defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x03070000 \ - && PYPY_VERSION_NUM >= 0x07090000)) + && PYPY_VERSION_NUM >= 0x07030800)) // Just check that constants are available size_t bits = PyHASH_BITS; assert(bits >= 8); @@ -1611,6 +1672,20 @@ test_hash(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) assert(imag != 0); #endif + // Test Py_HashBuffer() + { + PyObject *abc = PyBytes_FromString("abc"); + if (abc == NULL) { + return NULL; + } + Py_hash_t hash = Py_HashBuffer(PyBytes_AS_STRING(abc), + PyBytes_GET_SIZE(abc)); + Py_hash_t hash2 = PyObject_Hash(abc); + assert(hash == hash2); + + Py_DECREF(abc); + } + Py_RETURN_NONE; } @@ -1662,7 +1737,7 @@ check_get_constant(PyObject* (*get_constant)(unsigned int), int borrowed) // Py_CONSTANT_FALSE obj = get_constant(Py_CONSTANT_FALSE); - assert(obj = Py_False); + assert(obj == Py_False); CLEAR(obj); // Py_CONSTANT_TRUE @@ -1772,6 +1847,11 @@ test_unicodewriter(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) goto error; } + // test PyUnicodeWriter_WriteASCII() + if (PyUnicodeWriter_WriteASCII(writer, " non-ASCII", -1) < 0) { + goto error; + } + // test PyUnicodeWriter_WriteUTF8() if (PyUnicodeWriter_WriteUTF8(writer, " valu\xC3\xA9", -1) < 0) { goto error; @@ -1795,7 +1875,7 @@ test_unicodewriter(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) if (result == NULL) { return NULL; } - assert(PyUnicode_EqualToUTF8(result, "var=long valu\xC3\xA9 'repr'")); + assert(PyUnicode_EqualToUTF8(result, "var=long non-ASCII valu\xC3\xA9 'repr'")); Py_DECREF(result); } @@ -1874,6 +1954,309 @@ test_unicodewriter_format(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) #endif +static PyObject * +test_bytes(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + // Test PyBytes_Join() + PyObject *abc = PyBytes_FromString("a b c"); + if (abc == NULL) { + return NULL; + } + PyObject *list = PyObject_CallMethod(abc, "split", NULL); + Py_DECREF(abc); + if (list == NULL) { + return NULL; + } + PyObject *sep = PyBytes_FromString("-"); + if (sep == NULL) { + Py_DECREF(list); + return NULL; + } + + PyObject *join = PyBytes_Join(sep, list); + assert(join != NULL); + assert(PyBytes_Check(join)); + assert(memcmp(PyBytes_AS_STRING(join), "a-b-c", 5) == 0); + Py_DECREF(join); + + Py_DECREF(list); + Py_DECREF(sep); + Py_RETURN_NONE; +} + + +static PyObject * +test_iter(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + // Test PyIter_NextItem() + PyObject *tuple = Py_BuildValue("(i)", 123); + if (tuple == NULL) { + return NULL; + } + PyObject *iter = PyObject_GetIter(tuple); + Py_DECREF(tuple); + if (iter == NULL) { + return NULL; + } + + // first item + PyObject *item = UNINITIALIZED_OBJ; + assert(PyIter_NextItem(iter, &item) == 1); + { + PyObject *expected = PyLong_FromLong(123); + assert(PyObject_RichCompareBool(item, expected, Py_EQ) == 1); + assert(expected != NULL); + Py_DECREF(expected); + } + + // StopIteration + item = UNINITIALIZED_OBJ; + assert(PyIter_NextItem(iter, &item) == 0); + assert(item == NULL); + assert(!PyErr_Occurred()); + + // non-iterable object + item = UNINITIALIZED_OBJ; + assert(PyIter_NextItem(Py_None, &item) == -1); + assert(item == NULL); + assert(PyErr_ExceptionMatches(PyExc_TypeError)); + PyErr_Clear(); + + Py_DECREF(iter); + Py_RETURN_NONE; +} + + +static PyObject * +test_long_stdint(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + PyObject *obj; + + // Test PyLong_FromInt32() and PyLong_AsInt32() + obj = PyLong_FromInt32(INT32_C(-0x12345678)); + assert(obj != NULL); + int32_t i32; + assert(PyLong_AsInt32(obj, &i32) == 0); + assert(i32 == INT32_C(-0x12345678)); + Py_DECREF(obj); + + // Test PyLong_FromUInt32() and PyLong_AsUInt32() + obj = PyLong_FromUInt32(UINT32_C(0xDEADBEEF)); + assert(obj != NULL); + uint32_t u32; + assert(PyLong_AsUInt32(obj, &u32) == 0); + assert(u32 == UINT32_C(0xDEADBEEF)); + Py_DECREF(obj); + + // Test PyLong_FromInt64() and PyLong_AsInt64() + obj = PyLong_FromInt64(INT64_C(-0x12345678DEADBEEF)); + assert(obj != NULL); + int64_t i64; + assert(PyLong_AsInt64(obj, &i64) == 0); + assert(i64 == INT64_C(-0x12345678DEADBEEF)); + Py_DECREF(obj); + + // Test PyLong_FromUInt64() and PyLong_AsUInt64() + obj = PyLong_FromUInt64(UINT64_C(0xDEADBEEF12345678)); + assert(obj != NULL); + uint64_t u64; + assert(PyLong_AsUInt64(obj, &u64) == 0); + assert(u64 == UINT64_C(0xDEADBEEF12345678)); + Py_DECREF(obj); + + Py_RETURN_NONE; +} + + +static PyObject * +test_structmember(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + assert(Py_T_SHORT >= 0); + assert(Py_T_INT >= 0); + assert(Py_T_LONG >= 0); + assert(Py_T_FLOAT >= 0); + assert(Py_T_DOUBLE >= 0); + assert(Py_T_STRING >= 0); + assert(_Py_T_OBJECT >= 0); + assert(Py_T_CHAR >= 0); + assert(Py_T_BYTE >= 0); + assert(Py_T_UBYTE >= 0); + assert(Py_T_USHORT >= 0); + assert(Py_T_UINT >= 0); + assert(Py_T_ULONG >= 0); + assert(Py_T_STRING_INPLACE >= 0); + assert(Py_T_BOOL >= 0); + assert(Py_T_OBJECT_EX >= 0); + assert(Py_T_LONGLONG >= 0); + assert(Py_T_ULONGLONG >= 0); + assert(Py_T_PYSSIZET >= 0); +#if PY_VERSION_HEX >= 0x03000000 && !defined(PYPY_VERSION) + assert(_Py_T_NONE >= 0); +#endif + + assert(Py_READONLY >= 0); + assert(Py_AUDIT_READ >= 0); + assert(_Py_WRITE_RESTRICTED >= 0); + + Py_RETURN_NONE; +} + + +static PyObject * +test_file(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + const char *filename = __FILE__; + PyObject *path = create_string(filename); + + FILE *fp = Py_fopen(path, "rb"); + Py_DECREF(path); + assert(fp != NULL); + Py_fclose(fp); + + Py_RETURN_NONE; +} + + +#if 0x03090000 <= PY_VERSION_HEX && !defined(PYPY_VERSION) +static PyObject * +test_config(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + // Test PyConfig_Get() + PyObject *sys = PyImport_ImportModule("sys"); + if (sys == _Py_NULL) { + return _Py_NULL; + } + + PyObject *obj = PyConfig_Get("argv"); + PyObject *sys_attr = PyObject_GetAttrString(sys, "argv"); + assert(obj == sys_attr); + Py_DECREF(obj); + Py_DECREF(sys_attr); + + obj = PyConfig_Get("module_search_paths"); + sys_attr = PyObject_GetAttrString(sys, "path"); + assert(obj == sys_attr); + Py_DECREF(obj); + Py_DECREF(sys_attr); + + obj = PyConfig_Get("xoptions"); + sys_attr = PyObject_GetAttrString(sys, "_xoptions"); + assert(obj == sys_attr); + Py_DECREF(obj); + Py_DECREF(sys_attr); + + obj = PyConfig_Get("use_environment"); + assert(PyBool_Check(obj)); + Py_DECREF(obj); + + obj = PyConfig_Get("verbose"); + assert(PyLong_Check(obj)); + Py_DECREF(obj); + + // Get the last member +#if 0x030A0000 <= PY_VERSION_HEX + obj = PyConfig_Get("warn_default_encoding"); +#else + obj = PyConfig_Get("user_site_directory"); +#endif + assert(PyLong_Check(obj)); + Py_DECREF(obj); + + assert(PyConfig_Get("nonexistent") == NULL); + assert(PyErr_ExceptionMatches(PyExc_ValueError)); + PyErr_Clear(); + + // Test PyConfig_GetInt() + int value = -3; + + assert(PyConfig_GetInt("verbose", &value) == 0); + assert(value >= 0); + + assert(PyConfig_GetInt("argv", &value) == -1); + assert(PyErr_ExceptionMatches(PyExc_TypeError)); + PyErr_Clear(); + + assert(PyConfig_GetInt("nonexistent", &value) == -1); + assert(PyErr_ExceptionMatches(PyExc_ValueError)); + PyErr_Clear(); + + Py_DECREF(sys); + Py_RETURN_NONE; +} +#endif + + +static PyObject * +test_sys(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + const char *stdout_str = "stdout"; + PyObject *stdout_obj = create_string(stdout_str); +#if PYTHON3 + PyObject *sys_stdout = PySys_GetObject(stdout_str); // borrowed ref +#else + PyObject *sys_stdout = PySys_GetObject((char*)stdout_str); // borrowed ref +#endif + const char *nonexistent_str = "nonexistent"; + PyObject *nonexistent_obj = create_string(nonexistent_str); + PyObject *error_obj = PyLong_FromLong(1); + PyObject *value; + + // get sys.stdout + value = PySys_GetAttr(stdout_obj); + assert(value == sys_stdout); + Py_DECREF(value); + + value = PySys_GetAttrString(stdout_str); + assert(value == sys_stdout); + Py_DECREF(value); + + value = UNINITIALIZED_OBJ; + assert(PySys_GetOptionalAttr(stdout_obj, &value) == 1); + assert(value == sys_stdout); + Py_DECREF(value); + + value = UNINITIALIZED_OBJ; + assert(PySys_GetOptionalAttrString(stdout_str, &value) == 1); + assert(value == sys_stdout); + Py_DECREF(value); + + // non existent attribute + value = PySys_GetAttr(nonexistent_obj); + assert(value == NULL); + assert(PyErr_ExceptionMatches(PyExc_RuntimeError)); + PyErr_Clear(); + + value = PySys_GetAttrString(nonexistent_str); + assert(value == NULL); + assert(PyErr_ExceptionMatches(PyExc_RuntimeError)); + PyErr_Clear(); + + value = UNINITIALIZED_OBJ; + assert(PySys_GetOptionalAttr(nonexistent_obj, &value) == 0); + assert(value == NULL); + + value = UNINITIALIZED_OBJ; + assert(PySys_GetOptionalAttrString(nonexistent_str, &value) == 0); + assert(value == NULL); + + // invalid attribute type + value = PySys_GetAttr(error_obj); + assert(value == NULL); + assert(PyErr_ExceptionMatches(PyExc_TypeError)); + PyErr_Clear(); + + value = UNINITIALIZED_OBJ; + assert(PySys_GetOptionalAttr(error_obj, &value) == -1); + assert(value == NULL); + assert(PyErr_ExceptionMatches(PyExc_TypeError)); + PyErr_Clear(); + + Py_DECREF(stdout_obj); + Py_DECREF(nonexistent_obj); + Py_RETURN_NONE; +} + + static struct PyMethodDef methods[] = { {"test_object", test_object, METH_NOARGS, _Py_NULL}, {"test_py_is", test_py_is, METH_NOARGS, _Py_NULL}, @@ -1917,6 +2300,15 @@ static struct PyMethodDef methods[] = { {"test_unicodewriter_widechar", test_unicodewriter_widechar, METH_NOARGS, _Py_NULL}, {"test_unicodewriter_format", test_unicodewriter_format, METH_NOARGS, _Py_NULL}, #endif + {"test_bytes", test_bytes, METH_NOARGS, _Py_NULL}, + {"test_iter", test_iter, METH_NOARGS, _Py_NULL}, + {"test_long_stdint", test_long_stdint, METH_NOARGS, _Py_NULL}, + {"test_structmember", test_structmember, METH_NOARGS, _Py_NULL}, + {"test_file", test_file, METH_NOARGS, _Py_NULL}, +#if 0x03090000 <= PY_VERSION_HEX && !defined(PYPY_VERSION) + {"test_config", test_config, METH_NOARGS, _Py_NULL}, +#endif + {"test_sys", test_sys, METH_NOARGS, _Py_NULL}, {_Py_NULL, _Py_NULL, 0, _Py_NULL} }; diff --git a/tests/test_upgrade_pythoncapi.py b/tests/test_upgrade_pythoncapi.py index bf6f648..48bad12 100644 --- a/tests/test_upgrade_pythoncapi.py +++ b/tests/test_upgrade_pythoncapi.py @@ -43,33 +43,12 @@ def reformat(source): class Tests(unittest.TestCase): maxDiff = 80 * 30 - def _test_patch_file(self, tmp_dir): - # test Patcher.patcher() - source = """ - PyTypeObject* - test_type(PyObject *obj, PyTypeObject *type) - { - Py_TYPE(obj) = type; - return Py_TYPE(obj); - } - """ - expected = """ - #include "pythoncapi_compat.h" - - PyTypeObject* - test_type(PyObject *obj, PyTypeObject *type) - { - Py_SET_TYPE(obj, type); - return Py_TYPE(obj); - } - """ - source = reformat(source) - expected = reformat(expected) - + def _patch_file(self, source, tmp_dir=None): + # test Patcher.patcher() filename = tempfile.mktemp(suffix='.c', dir=tmp_dir) old_filename = filename + ".old" try: - with open(filename, "w", encoding="utf-8") as fp: + with open(filename, "w", encoding="utf-8", newline="") as fp: fp.write(source) old_stderr = sys.stderr @@ -93,10 +72,10 @@ def _test_patch_file(self, tmp_dir): sys.stderr = old_stderr sys.argv = old_argv - with open(filename, encoding="utf-8") as fp: + with open(filename, encoding="utf-8", newline="") as fp: new_contents = fp.read() - with open(old_filename, encoding="utf-8") as fp: + with open(old_filename, encoding="utf-8", newline="") as fp: old_contents = fp.read() finally: try: @@ -108,15 +87,53 @@ def _test_patch_file(self, tmp_dir): except FileNotFoundError: pass - self.assertEqual(new_contents, expected) self.assertEqual(old_contents, source) + return new_contents def test_patch_file(self): - self._test_patch_file(None) + source = """ + PyTypeObject* + test_type(PyObject *obj, PyTypeObject *type) + { + Py_TYPE(obj) = type; + return Py_TYPE(obj); + } + """ + expected = """ + #include "pythoncapi_compat.h" + + PyTypeObject* + test_type(PyObject *obj, PyTypeObject *type) + { + Py_SET_TYPE(obj, type); + return Py_TYPE(obj); + } + """ + source = reformat(source) + expected = reformat(expected) + + new_contents = self._patch_file(source) + self.assertEqual(new_contents, expected) - def test_patch_directory(self): with tempfile.TemporaryDirectory() as tmp_dir: - self._test_patch_file(tmp_dir) + new_contents = self._patch_file(source, tmp_dir) + self.assertEqual(new_contents, expected) + + def test_patch_file_preserve_newlines(self): + source = """ + Py_ssize_t get_size(PyVarObject *obj)\r\n\ + \n\ + { return obj->ob_size; }\r\ + """ + expected = """ + Py_ssize_t get_size(PyVarObject *obj)\r\n\ + \n\ + { return Py_SIZE(obj); }\r\ + """ + source = reformat(source) + expected = reformat(expected) + new_contents = self._patch_file(source) + self.assertEqual(new_contents, expected) def check_replace(self, source, expected, **kwargs): source = reformat(source) diff --git a/upgrade_pythoncapi.py b/upgrade_pythoncapi.py index ea6e722..68b5c53 100755 --- a/upgrade_pythoncapi.py +++ b/upgrade_pythoncapi.py @@ -10,7 +10,7 @@ PYTHONCAPI_COMPAT_URL = ('https://raw.githubusercontent.com/python/' - 'pythoncapi_compat/master/pythoncapi_compat.h') + 'pythoncapi-compat/main/pythoncapi_compat.h') PYTHONCAPI_COMPAT_H = 'pythoncapi_compat.h' INCLUDE_PYTHONCAPI_COMPAT = f'#include "{PYTHONCAPI_COMPAT_H}"' INCLUDE_PYTHONCAPI_COMPAT2 = f'#include <{PYTHONCAPI_COMPAT_H}>' @@ -554,13 +554,17 @@ def _get_operations(self, parser): return operations def add_line(self, content, line): - line = line + '\n' + # Use the first matching newline + match = re.search(r'(?:\r\n|\n|\r)', content) + newline = match.group(0) if match else '\n' + + line = line + newline # FIXME: tolerate trailing spaces if line not in content: # FIXME: add macro after the first header comment # FIXME: add macro after includes # FIXME: add macro after: #define PY_SSIZE_T_CLEAN - return line + '\n' + content + return line + newline + content else: return content @@ -601,7 +605,7 @@ def patch_file(self, filename): encoding = "utf-8" errors = "surrogateescape" - with open(filename, encoding=encoding, errors=errors) as fp: + with open(filename, encoding=encoding, errors=errors, newline="") as fp: old_contents = fp.read() new_contents, operations = self._patch(old_contents) @@ -620,7 +624,7 @@ def patch_file(self, filename): # If old_filename already exists, replace it os.replace(filename, old_filename) - with open(filename, "w", encoding=encoding, errors=errors) as fp: + with open(filename, "w", encoding=encoding, errors=errors, newline="") as fp: fp.write(new_contents) self.applied_operations |= set(operations) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy