summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/c-api/apiabiversion.rst102
-rw-r--r--Doc/data/stable_abi.dat2
-rw-r--r--Doc/whatsnew/3.14.rst4
-rw-r--r--Include/patchlevel.h26
-rw-r--r--Include/pymacro.h9
-rw-r--r--Lib/test/test_capi/test_misc.py43
-rw-r--r--Lib/test/test_stable_abi_ctypes.py2
-rw-r--r--Misc/NEWS.d/next/C_API/2025-01-08-13-13-18.gh-issue-128629.gSmzyl.rst2
-rw-r--r--Misc/stable_abi.toml4
-rw-r--r--Modules/Setup.stdlib.in2
-rw-r--r--Modules/_testlimitedcapi.c3
-rw-r--r--Modules/_testlimitedcapi/clinic/version.c.h93
-rw-r--r--Modules/_testlimitedcapi/parts.h1
-rw-r--r--Modules/_testlimitedcapi/version.c77
-rwxr-xr-xPC/python3dll.c2
-rw-r--r--PCbuild/_testlimitedcapi.vcxproj1
-rw-r--r--PCbuild/_testlimitedcapi.vcxproj.filters1
-rw-r--r--Python/modsupport.c17
18 files changed, 358 insertions, 33 deletions
diff --git a/Doc/c-api/apiabiversion.rst b/Doc/c-api/apiabiversion.rst
index f6c8284..96050f5 100644
--- a/Doc/c-api/apiabiversion.rst
+++ b/Doc/c-api/apiabiversion.rst
@@ -6,9 +6,13 @@
API and ABI Versioning
***********************
+
+Build-time version constants
+----------------------------
+
CPython exposes its version number in the following macros.
-Note that these correspond to the version code is **built** with,
-not necessarily the version used at **run time**.
+Note that these correspond to the version code is **built** with.
+See :c:var:`Py_Version` for the version used at **run time**.
See :ref:`stable` for a discussion of API and ABI stability across versions.
@@ -37,37 +41,83 @@ See :ref:`stable` for a discussion of API and ABI stability across versions.
.. c:macro:: PY_VERSION_HEX
The Python version number encoded in a single integer.
+ See :c:func:`Py_PACK_FULL_VERSION` for the encoding details.
- The underlying version information can be found by treating it as a 32 bit
- number in the following manner:
-
- +-------+-------------------------+-------------------------+--------------------------+
- | Bytes | Bits (big endian order) | Meaning | Value for ``3.4.1a2`` |
- +=======+=========================+=========================+==========================+
- | 1 | 1-8 | ``PY_MAJOR_VERSION`` | ``0x03`` |
- +-------+-------------------------+-------------------------+--------------------------+
- | 2 | 9-16 | ``PY_MINOR_VERSION`` | ``0x04`` |
- +-------+-------------------------+-------------------------+--------------------------+
- | 3 | 17-24 | ``PY_MICRO_VERSION`` | ``0x01`` |
- +-------+-------------------------+-------------------------+--------------------------+
- | 4 | 25-28 | ``PY_RELEASE_LEVEL`` | ``0xA`` |
- + +-------------------------+-------------------------+--------------------------+
- | | 29-32 | ``PY_RELEASE_SERIAL`` | ``0x2`` |
- +-------+-------------------------+-------------------------+--------------------------+
+ Use this for numeric comparisons, for example,
+ ``#if PY_VERSION_HEX >= ...``.
- Thus ``3.4.1a2`` is hexversion ``0x030401a2`` and ``3.10.0`` is
- hexversion ``0x030a00f0``.
- Use this for numeric comparisons, e.g. ``#if PY_VERSION_HEX >= ...``.
-
- This version is also available via the symbol :c:var:`Py_Version`.
+Run-time version
+----------------
.. c:var:: const unsigned long Py_Version
- The Python runtime version number encoded in a single constant integer, with
- the same format as the :c:macro:`PY_VERSION_HEX` macro.
+ The Python runtime version number encoded in a single constant integer.
+ See :c:func:`Py_PACK_FULL_VERSION` for the encoding details.
This contains the Python version used at run time.
+ Use this for numeric comparisons, for example, ``if (Py_Version >= ...)``.
+
.. versionadded:: 3.11
-All the given macros are defined in :source:`Include/patchlevel.h`.
+
+Bit-packing macros
+------------------
+
+.. c:function:: uint32_t Py_PACK_FULL_VERSION(int major, int minor, int micro, int release_level, int release_serial)
+
+ Return the given version, encoded as a single 32-bit integer with
+ the following structure:
+
+ +------------------+-------+----------------+-----------+--------------------------+
+ | | No. | | | Example values |
+ | | of | | +-------------+------------+
+ | Argument | bits | Bit mask | Bit shift | ``3.4.1a2`` | ``3.10.0`` |
+ +==================+=======+================+===========+=============+============+
+ | *major* | 8 | ``0xFF000000`` | 24 | ``0x03`` | ``0x03`` |
+ +------------------+-------+----------------+-----------+-------------+------------+
+ | *minor* | 8 | ``0x00FF0000`` | 16 | ``0x04`` | ``0x0A`` |
+ +------------------+-------+----------------+-----------+-------------+------------+
+ | *micro* | 8 | ``0x0000FF00`` | 8 | ``0x01`` | ``0x00`` |
+ +------------------+-------+----------------+-----------+-------------+------------+
+ | *release_level* | 4 | ``0x000000F0`` | 4 | ``0xA`` | ``0xF`` |
+ +------------------+-------+----------------+-----------+-------------+------------+
+ | *release_serial* | 4 | ``0x0000000F`` | 0 | ``0x2`` | ``0x0`` |
+ +------------------+-------+----------------+-----------+-------------+------------+
+
+ For example:
+
+ +-------------+------------------------------------+-----------------+
+ | Version | ``Py_PACK_FULL_VERSION`` arguments | Encoded version |
+ +=============+====================================+=================+
+ | ``3.4.1a2`` | ``(3, 4, 1, 0xA, 2)`` | ``0x030401a2`` |
+ +-------------+------------------------------------+-----------------+
+ | ``3.10.0`` | ``(3, 10, 0, 0xF, 0)`` | ``0x030a00f0`` |
+ +-------------+------------------------------------+-----------------+
+
+ Out-of range bits in the arguments are ignored.
+ That is, the macro can be defined as:
+
+ .. code-block:: c
+
+ #ifndef Py_PACK_FULL_VERSION
+ #define Py_PACK_FULL_VERSION(X, Y, Z, LEVEL, SERIAL) ( \
+ (((X) & 0xff) << 24) | \
+ (((Y) & 0xff) << 16) | \
+ (((Z) & 0xff) << 8) | \
+ (((LEVEL) & 0xf) << 4) | \
+ (((SERIAL) & 0xf) << 0))
+ #endif
+
+ ``Py_PACK_FULL_VERSION`` is primarily a macro, intended for use in
+ ``#if`` directives, but it is also available as an exported function.
+
+ .. versionadded:: 3.14
+
+.. c:function:: uint32_t Py_PACK_VERSION(int major, int minor)
+
+ Equivalent to ``Py_PACK_FULL_VERSION(major, minor, 0, 0, 0)``.
+ The result does not correspond to any Python release, but is useful
+ in numeric comparisons.
+
+ .. versionadded:: 3.14
diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat
index 6f9d272..c15f826 100644
--- a/Doc/data/stable_abi.dat
+++ b/Doc/data/stable_abi.dat
@@ -883,6 +883,8 @@ func,Py_Main,3.2,,
func,Py_MakePendingCalls,3.2,,
func,Py_NewInterpreter,3.2,,
func,Py_NewRef,3.10,,
+func,Py_PACK_FULL_VERSION,3.14,,
+func,Py_PACK_VERSION,3.14,,
func,Py_REFCNT,3.14,,
func,Py_ReprEnter,3.2,,
func,Py_ReprLeave,3.2,,
diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst
index 16851b4..72abfeb 100644
--- a/Doc/whatsnew/3.14.rst
+++ b/Doc/whatsnew/3.14.rst
@@ -1243,6 +1243,10 @@ New features
file.
(Contributed by Victor Stinner in :gh:`127350`.)
+* Add macros :c:func:`Py_PACK_VERSION` and :c:func:`Py_PACK_FULL_VERSION` for
+ bit-packing Python version numbers.
+ (Contributed by Petr Viktorin in :gh:`128629`.)
+
Porting to Python 3.14
----------------------
diff --git a/Include/patchlevel.h b/Include/patchlevel.h
index 6d4f719..eca2ca0 100644
--- a/Include/patchlevel.h
+++ b/Include/patchlevel.h
@@ -1,4 +1,5 @@
-
+#ifndef _Py_PATCHLEVEL_H
+#define _Py_PATCHLEVEL_H
/* Python version identification scheme.
When the major or minor version changes, the VERSION variable in
@@ -26,10 +27,23 @@
#define PY_VERSION "3.14.0a3+"
/*--end constants--*/
+
+#define _Py_PACK_FULL_VERSION(X, Y, Z, LEVEL, SERIAL) ( \
+ (((X) & 0xff) << 24) | \
+ (((Y) & 0xff) << 16) | \
+ (((Z) & 0xff) << 8) | \
+ (((LEVEL) & 0xf) << 4) | \
+ (((SERIAL) & 0xf) << 0))
+
/* Version as a single 4-byte hex number, e.g. 0x010502B2 == 1.5.2b2.
Use this for numeric comparisons, e.g. #if PY_VERSION_HEX >= ... */
-#define PY_VERSION_HEX ((PY_MAJOR_VERSION << 24) | \
- (PY_MINOR_VERSION << 16) | \
- (PY_MICRO_VERSION << 8) | \
- (PY_RELEASE_LEVEL << 4) | \
- (PY_RELEASE_SERIAL << 0))
+#define PY_VERSION_HEX _Py_PACK_FULL_VERSION( \
+ PY_MAJOR_VERSION, \
+ PY_MINOR_VERSION, \
+ PY_MICRO_VERSION, \
+ PY_RELEASE_LEVEL, \
+ PY_RELEASE_SERIAL)
+
+// Public Py_PACK_VERSION is declared in pymacro.h; it needs <inttypes.h>.
+
+#endif //_Py_PATCHLEVEL_H
diff --git a/Include/pymacro.h b/Include/pymacro.h
index e0378f9..a82f347 100644
--- a/Include/pymacro.h
+++ b/Include/pymacro.h
@@ -190,4 +190,13 @@
// "comparison of unsigned expression in '< 0' is always false".
#define _Py_IS_TYPE_SIGNED(type) ((type)(-1) <= 0)
+#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030E0000 // 3.14
+// Version helpers. These are primarily macros, but have exported equivalents.
+PyAPI_FUNC(uint32_t) Py_PACK_FULL_VERSION(int x, int y, int z, int level, int serial);
+PyAPI_FUNC(uint32_t) Py_PACK_VERSION(int x, int y);
+#define Py_PACK_FULL_VERSION _Py_PACK_FULL_VERSION
+#define Py_PACK_VERSION(X, Y) Py_PACK_FULL_VERSION(X, Y, 0, 0, 0)
+#endif // Py_LIMITED_API < 3.14
+
+
#endif /* Py_PYMACRO_H */
diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py
index ada3018..b62bc4c 100644
--- a/Lib/test/test_capi/test_misc.py
+++ b/Lib/test/test_capi/test_misc.py
@@ -3335,6 +3335,49 @@ class TestPyThreadId(unittest.TestCase):
self.assertEqual(len(set(py_thread_ids)), len(py_thread_ids),
py_thread_ids)
+class TestVersions(unittest.TestCase):
+ full_cases = (
+ (3, 4, 1, 0xA, 2, 0x030401a2),
+ (3, 10, 0, 0xF, 0, 0x030a00f0),
+ (0x103, 0x10B, 0xFF00, -1, 0xF0, 0x030b00f0), # test masking
+ )
+ xy_cases = (
+ (3, 4, 0x03040000),
+ (3, 10, 0x030a0000),
+ (0x103, 0x10B, 0x030b0000), # test masking
+ )
+
+ def test_pack_full_version(self):
+ for *args, expected in self.full_cases:
+ with self.subTest(hexversion=hex(expected)):
+ result = _testlimitedcapi.pack_full_version(*args)
+ self.assertEqual(result, expected)
+
+ def test_pack_version(self):
+ for *args, expected in self.xy_cases:
+ with self.subTest(hexversion=hex(expected)):
+ result = _testlimitedcapi.pack_version(*args)
+ self.assertEqual(result, expected)
+
+ def test_pack_full_version_ctypes(self):
+ ctypes = import_helper.import_module('ctypes')
+ ctypes_func = ctypes.pythonapi.Py_PACK_FULL_VERSION
+ ctypes_func.restype = ctypes.c_uint32
+ ctypes_func.argtypes = [ctypes.c_int] * 5
+ for *args, expected in self.full_cases:
+ with self.subTest(hexversion=hex(expected)):
+ result = ctypes_func(*args)
+ self.assertEqual(result, expected)
+
+ def test_pack_version_ctypes(self):
+ ctypes = import_helper.import_module('ctypes')
+ ctypes_func = ctypes.pythonapi.Py_PACK_VERSION
+ ctypes_func.restype = ctypes.c_uint32
+ ctypes_func.argtypes = [ctypes.c_int] * 2
+ for *args, expected in self.xy_cases:
+ with self.subTest(hexversion=hex(expected)):
+ result = ctypes_func(*args)
+ self.assertEqual(result, expected)
if __name__ == "__main__":
unittest.main()
diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py
index fa08dc6..f3724ce 100644
--- a/Lib/test/test_stable_abi_ctypes.py
+++ b/Lib/test/test_stable_abi_ctypes.py
@@ -901,6 +901,8 @@ SYMBOL_NAMES = (
"Py_MakePendingCalls",
"Py_NewInterpreter",
"Py_NewRef",
+ "Py_PACK_FULL_VERSION",
+ "Py_PACK_VERSION",
"Py_REFCNT",
"Py_ReprEnter",
"Py_ReprLeave",
diff --git a/Misc/NEWS.d/next/C_API/2025-01-08-13-13-18.gh-issue-128629.gSmzyl.rst b/Misc/NEWS.d/next/C_API/2025-01-08-13-13-18.gh-issue-128629.gSmzyl.rst
new file mode 100644
index 0000000..cde5bf3
--- /dev/null
+++ b/Misc/NEWS.d/next/C_API/2025-01-08-13-13-18.gh-issue-128629.gSmzyl.rst
@@ -0,0 +1,2 @@
+Add macros :c:func:`Py_PACK_VERSION` and :c:func:`Py_PACK_FULL_VERSION` for
+bit-packing Python version numbers.
diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml
index f9e51f0..276526a 100644
--- a/Misc/stable_abi.toml
+++ b/Misc/stable_abi.toml
@@ -2540,3 +2540,7 @@
added = '3.14'
[function.PyType_Freeze]
added = '3.14'
+[function.Py_PACK_FULL_VERSION]
+ added = '3.14'
+[function.Py_PACK_VERSION]
+ added = '3.14'
diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in
index 52c0f88..b7357f4 100644
--- a/Modules/Setup.stdlib.in
+++ b/Modules/Setup.stdlib.in
@@ -163,7 +163,7 @@
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c
@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/monitoring.c _testcapi/config.c
-@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/sys.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c
+@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/sys.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
@MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c
diff --git a/Modules/_testlimitedcapi.c b/Modules/_testlimitedcapi.c
index ba83a23..bcc69a3 100644
--- a/Modules/_testlimitedcapi.c
+++ b/Modules/_testlimitedcapi.c
@@ -83,5 +83,8 @@ PyInit__testlimitedcapi(void)
if (_PyTestLimitedCAPI_Init_VectorcallLimited(mod) < 0) {
return NULL;
}
+ if (_PyTestLimitedCAPI_Init_Version(mod) < 0) {
+ return NULL;
+ }
return mod;
}
diff --git a/Modules/_testlimitedcapi/clinic/version.c.h b/Modules/_testlimitedcapi/clinic/version.c.h
new file mode 100644
index 0000000..096c7dd
--- /dev/null
+++ b/Modules/_testlimitedcapi/clinic/version.c.h
@@ -0,0 +1,93 @@
+/*[clinic input]
+preserve
+[clinic start generated code]*/
+
+PyDoc_STRVAR(_testlimitedcapi_pack_full_version__doc__,
+"pack_full_version($module, major, minor, micro, level, serial, /)\n"
+"--\n"
+"\n");
+
+#define _TESTLIMITEDCAPI_PACK_FULL_VERSION_METHODDEF \
+ {"pack_full_version", (PyCFunction)(void(*)(void))_testlimitedcapi_pack_full_version, METH_FASTCALL, _testlimitedcapi_pack_full_version__doc__},
+
+static PyObject *
+_testlimitedcapi_pack_full_version_impl(PyObject *module, int major,
+ int minor, int micro, int level,
+ int serial);
+
+static PyObject *
+_testlimitedcapi_pack_full_version(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
+{
+ PyObject *return_value = NULL;
+ int major;
+ int minor;
+ int micro;
+ int level;
+ int serial;
+
+ if (nargs != 5) {
+ PyErr_Format(PyExc_TypeError, "pack_full_version expected 5 arguments, got %zd", nargs);
+ goto exit;
+ }
+ major = PyLong_AsInt(args[0]);
+ if (major == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ minor = PyLong_AsInt(args[1]);
+ if (minor == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ micro = PyLong_AsInt(args[2]);
+ if (micro == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ level = PyLong_AsInt(args[3]);
+ if (level == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ serial = PyLong_AsInt(args[4]);
+ if (serial == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ return_value = _testlimitedcapi_pack_full_version_impl(module, major, minor, micro, level, serial);
+
+exit:
+ return return_value;
+}
+
+PyDoc_STRVAR(_testlimitedcapi_pack_version__doc__,
+"pack_version($module, major, minor, /)\n"
+"--\n"
+"\n");
+
+#define _TESTLIMITEDCAPI_PACK_VERSION_METHODDEF \
+ {"pack_version", (PyCFunction)(void(*)(void))_testlimitedcapi_pack_version, METH_FASTCALL, _testlimitedcapi_pack_version__doc__},
+
+static PyObject *
+_testlimitedcapi_pack_version_impl(PyObject *module, int major, int minor);
+
+static PyObject *
+_testlimitedcapi_pack_version(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
+{
+ PyObject *return_value = NULL;
+ int major;
+ int minor;
+
+ if (nargs != 2) {
+ PyErr_Format(PyExc_TypeError, "pack_version expected 2 arguments, got %zd", nargs);
+ goto exit;
+ }
+ major = PyLong_AsInt(args[0]);
+ if (major == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ minor = PyLong_AsInt(args[1]);
+ if (minor == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ return_value = _testlimitedcapi_pack_version_impl(module, major, minor);
+
+exit:
+ return return_value;
+}
+/*[clinic end generated code: output=aed3e226da77f2d2 input=a9049054013a1b77]*/
diff --git a/Modules/_testlimitedcapi/parts.h b/Modules/_testlimitedcapi/parts.h
index 4107b15..56d566b 100644
--- a/Modules/_testlimitedcapi/parts.h
+++ b/Modules/_testlimitedcapi/parts.h
@@ -40,5 +40,6 @@ int _PyTestLimitedCAPI_Init_Sys(PyObject *module);
int _PyTestLimitedCAPI_Init_Tuple(PyObject *module);
int _PyTestLimitedCAPI_Init_Unicode(PyObject *module);
int _PyTestLimitedCAPI_Init_VectorcallLimited(PyObject *module);
+int _PyTestLimitedCAPI_Init_Version(PyObject *module);
#endif // Py_TESTLIMITEDCAPI_PARTS_H
diff --git a/Modules/_testlimitedcapi/version.c b/Modules/_testlimitedcapi/version.c
new file mode 100644
index 0000000..57cd6e4
--- /dev/null
+++ b/Modules/_testlimitedcapi/version.c
@@ -0,0 +1,77 @@
+/* Test version macros in the limited API */
+
+#include "pyconfig.h" // Py_GIL_DISABLED
+#ifndef Py_GIL_DISABLED
+# define Py_LIMITED_API 0x030e0000 // Added in 3.14
+#endif
+
+#include "parts.h"
+#include "clinic/version.c.h"
+#include <stdio.h>
+
+/*[clinic input]
+module _testlimitedcapi
+[clinic start generated code]*/
+/*[clinic end generated code: output=da39a3ee5e6b4b0d input=2700057f9c1135ba]*/
+
+/*[clinic input]
+_testlimitedcapi.pack_full_version
+
+ major: int
+ minor: int
+ micro: int
+ level: int
+ serial: int
+ /
+[clinic start generated code]*/
+
+static PyObject *
+_testlimitedcapi_pack_full_version_impl(PyObject *module, int major,
+ int minor, int micro, int level,
+ int serial)
+/*[clinic end generated code: output=b87a1e9805648861 input=2a304423be61d2ac]*/
+{
+ uint32_t macro_result = Py_PACK_FULL_VERSION(
+ major, minor, micro, level, serial);
+#undef Py_PACK_FULL_VERSION
+ uint32_t func_result = Py_PACK_FULL_VERSION(
+ major, minor, micro, level, serial);
+
+ assert(macro_result == func_result);
+ return PyLong_FromUnsignedLong((unsigned long)func_result);
+}
+
+/*[clinic input]
+_testlimitedcapi.pack_version
+
+ major: int
+ minor: int
+ /
+[clinic start generated code]*/
+
+static PyObject *
+_testlimitedcapi_pack_version_impl(PyObject *module, int major, int minor)
+/*[clinic end generated code: output=771247bbd06e7883 input=3e39e9dcbc09e86a]*/
+{
+ uint32_t macro_result = Py_PACK_VERSION(major, minor);
+#undef Py_PACK_VERSION
+ uint32_t func_result = Py_PACK_VERSION(major, minor);
+
+ assert(macro_result == func_result);
+ return PyLong_FromUnsignedLong((unsigned long)func_result);
+}
+
+static PyMethodDef TestMethods[] = {
+ _TESTLIMITEDCAPI_PACK_FULL_VERSION_METHODDEF
+ _TESTLIMITEDCAPI_PACK_VERSION_METHODDEF
+ {NULL},
+};
+
+int
+_PyTestLimitedCAPI_Init_Version(PyObject *m)
+{
+ if (PyModule_AddFunctions(m, TestMethods) < 0) {
+ return -1;
+ }
+ return 0;
+}
diff --git a/PC/python3dll.c b/PC/python3dll.c
index 8657ddb..84b3c73 100755
--- a/PC/python3dll.c
+++ b/PC/python3dll.c
@@ -81,6 +81,8 @@ EXPORT_FUNC(Py_Main)
EXPORT_FUNC(Py_MakePendingCalls)
EXPORT_FUNC(Py_NewInterpreter)
EXPORT_FUNC(Py_NewRef)
+EXPORT_FUNC(Py_PACK_FULL_VERSION)
+EXPORT_FUNC(Py_PACK_VERSION)
EXPORT_FUNC(Py_REFCNT)
EXPORT_FUNC(Py_ReprEnter)
EXPORT_FUNC(Py_ReprLeave)
diff --git a/PCbuild/_testlimitedcapi.vcxproj b/PCbuild/_testlimitedcapi.vcxproj
index 846e027..0ea5edb 100644
--- a/PCbuild/_testlimitedcapi.vcxproj
+++ b/PCbuild/_testlimitedcapi.vcxproj
@@ -112,6 +112,7 @@
<ClCompile Include="..\Modules\_testlimitedcapi\tuple.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\unicode.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\vectorcall_limited.c" />
+ <ClCompile Include="..\Modules\_testlimitedcapi\version.c" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="..\PC\python_nt.rc" />
diff --git a/PCbuild/_testlimitedcapi.vcxproj.filters b/PCbuild/_testlimitedcapi.vcxproj.filters
index 57be2e2..b379090 100644
--- a/PCbuild/_testlimitedcapi.vcxproj.filters
+++ b/PCbuild/_testlimitedcapi.vcxproj.filters
@@ -28,6 +28,7 @@
<ClCompile Include="..\Modules\_testlimitedcapi\tuple.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\unicode.c" />
<ClCompile Include="..\Modules\_testlimitedcapi\vectorcall_limited.c" />
+ <ClCompile Include="..\Modules\_testlimitedcapi\version.c" />
<ClCompile Include="..\Modules\_testlimitedcapi.c" />
</ItemGroup>
<ItemGroup>
diff --git a/Python/modsupport.c b/Python/modsupport.c
index 0fb7783..517dc97 100644
--- a/Python/modsupport.c
+++ b/Python/modsupport.c
@@ -648,3 +648,20 @@ PyModule_AddType(PyObject *module, PyTypeObject *type)
return PyModule_AddObjectRef(module, name, (PyObject *)type);
}
+
+
+/* Exported functions for version helper macros */
+
+#undef Py_PACK_FULL_VERSION
+uint32_t
+Py_PACK_FULL_VERSION(int x, int y, int z, int level, int serial)
+{
+ return _Py_PACK_FULL_VERSION(x, y, z, level, serial);
+}
+
+#undef Py_PACK_VERSION
+uint32_t
+Py_PACK_VERSION(int x, int y)
+{
+ return Py_PACK_FULL_VERSION(x, y, 0, 0, 0);
+}