Skip to content

Commit 0d566a8

Browse files
gh-157487: Add structured version info for ctypes
Add constants ctypes.LIBFFI_VERSION, ctypes.libffi_version, ctypes.LIBFFI_VERSION_INFO and ctypes.libffi_version_info, which provide information about the version of the libffi library in use. They are only available with libffi 3.5 or later. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent a844747 commit 0d566a8

7 files changed

Lines changed: 194 additions & 0 deletions

File tree

Doc/library/ctypes.rst

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3383,3 +3383,47 @@ Exceptions
33833383
.. availability:: Windows
33843384

33853385
.. versionadded:: 3.14
3386+
3387+
3388+
Library version
3389+
^^^^^^^^^^^^^^^
3390+
3391+
The following constants are only available if :mod:`!ctypes` was built with
3392+
libffi 3.5 or later, which is the first version providing this information.
3393+
3394+
.. data:: LIBFFI_VERSION
3395+
3396+
The version string of the libffi library that was used for building
3397+
the module, like ``'3.5.2'``.
3398+
This may be different from the libffi library actually used at runtime,
3399+
which is available as :const:`libffi_version`.
3400+
3401+
.. versionadded:: next
3402+
3403+
.. data:: libffi_version
3404+
3405+
The version string of the libffi library actually loaded by the interpreter.
3406+
3407+
.. versionadded:: next
3408+
3409+
.. data:: LIBFFI_VERSION_INFO
3410+
3411+
A named tuple containing the three components of the libffi library
3412+
version that was used for building the module:
3413+
*major*, *minor*, and *patch*.
3414+
All values are integers.
3415+
The components can also be accessed by name,
3416+
so ``ctypes.LIBFFI_VERSION_INFO[0]`` is equivalent to
3417+
``ctypes.LIBFFI_VERSION_INFO.major`` and so on.
3418+
This may be different from the libffi library actually used at runtime,
3419+
which is available as :const:`libffi_version_info`.
3420+
3421+
.. versionadded:: next
3422+
3423+
.. data:: libffi_version_info
3424+
3425+
A named tuple containing the version of the libffi library
3426+
actually loaded by the interpreter,
3427+
with the same fields as :const:`LIBFFI_VERSION_INFO`.
3428+
3429+
.. versionadded:: next

Doc/whatsnew/3.16.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,13 @@ ctypes
184184
through a function signature.
185185
(Contributed by Peter Bierma in :gh:`153903`.)
186186

187+
* Added constants :const:`~ctypes.LIBFFI_VERSION`,
188+
:const:`~ctypes.libffi_version`, :const:`~ctypes.LIBFFI_VERSION_INFO`
189+
and :const:`~ctypes.libffi_version_info`, which provide information
190+
about the version of the libffi library in use.
191+
They are only available with libffi 3.5 or later.
192+
(Contributed by Serhiy Storchaka in :gh:`157487`.)
193+
187194

188195
curses
189196
------

Lib/ctypes/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@
1212
from _ctypes import RTLD_LOCAL, RTLD_GLOBAL
1313
from _ctypes import ArgumentError
1414
from _ctypes import SIZEOF_TIME_T
15+
try:
16+
from _ctypes import (LIBFFI_VERSION, libffi_version,
17+
LIBFFI_VERSION_INFO, libffi_version_info)
18+
except ImportError:
19+
# libffi < 3.5 does not provide version information.
20+
pass
1521
from _ctypes import CField
1622

1723
from struct import calcsize as _calcsize

Lib/test/pythoninfo.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,16 @@ def collect_zstd(info_add):
745745
copy_attributes(info_add, _zstd, 'zstd.%s', attributes)
746746

747747

748+
def collect_ctypes(info_add):
749+
try:
750+
import _ctypes
751+
except ImportError:
752+
return
753+
754+
attributes = ('LIBFFI_VERSION', 'libffi_version')
755+
copy_attributes(info_add, _ctypes, 'ctypes.%s', attributes)
756+
757+
748758
def collect_expat(info_add):
749759
try:
750760
from xml.parsers import expat
@@ -1353,6 +1363,7 @@ def collect_info(info):
13531363
collect_curses,
13541364
collect_datetime,
13551365
collect_decimal,
1366+
collect_ctypes,
13561367
collect_expat,
13571368
collect_fips,
13581369
collect_gdb,

Lib/test/test_ctypes/test_values.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,48 @@
33
"""
44

55
import _imp
6+
import ctypes
67
import importlib.util
78
import sys
89
import unittest
910
from ctypes import (Structure, CDLL, POINTER, pythonapi,
1011
c_ubyte, c_char_p, c_int)
12+
from test import support
1113
from test.support import import_helper, thread_unsafe
1214

1315

16+
class LibffiVersionTest(unittest.TestCase):
17+
18+
def _test_libffi_version(self, v, string):
19+
self.assertIsInstance(v[:], tuple)
20+
self.assertEqual(len(v), 3)
21+
self.assertIsInstance(v[0], int)
22+
self.assertIsInstance(v[1], int)
23+
self.assertIsInstance(v[2], int)
24+
self.assertIsInstance(v.major, int)
25+
self.assertIsInstance(v.minor, int)
26+
self.assertIsInstance(v.patch, int)
27+
self.assertEqual(v[0], v.major)
28+
self.assertEqual(v[1], v.minor)
29+
self.assertEqual(v[2], v.patch)
30+
self.assertGreaterEqual(v.major, 3)
31+
self.assertGreaterEqual(v.minor, 0)
32+
self.assertGreaterEqual(v.patch, 0)
33+
self.assertEqual(string, '%d.%d.%d' % v)
34+
35+
@unittest.skipUnless(hasattr(ctypes, 'LIBFFI_VERSION_INFO'),
36+
'requires libffi >= 3.5')
37+
def test_libffi_version(self):
38+
if support.verbose:
39+
print(f'LIBFFI_VERSION = {ctypes.LIBFFI_VERSION}', flush=True)
40+
print(f'libffi_version = {ctypes.libffi_version}', flush=True)
41+
print(f'LIBFFI_VERSION_INFO = {ctypes.LIBFFI_VERSION_INFO}', flush=True)
42+
print(f'libffi_version_info = {ctypes.libffi_version_info}', flush=True)
43+
self._test_libffi_version(ctypes.LIBFFI_VERSION_INFO, ctypes.LIBFFI_VERSION)
44+
self._test_libffi_version(ctypes.libffi_version_info, ctypes.libffi_version)
45+
self.assertEqual(ctypes.LIBFFI_VERSION_INFO[0], ctypes.libffi_version_info[0])
46+
47+
1448
class ValuesTestCase(unittest.TestCase):
1549

1650
def setUp(self):
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Add constants :const:`ctypes.LIBFFI_VERSION`, :const:`ctypes.libffi_version`,
2+
:const:`ctypes.LIBFFI_VERSION_INFO` and :const:`ctypes.libffi_version_info`,
3+
which provide information about the version of the libffi library in use.
4+
They are only available with libffi 3.5 or later.

Modules/_ctypes/_ctypes.c

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6386,6 +6386,89 @@ _ctypes_add_objects(PyObject *mod)
63866386
}
63876387

63886388

6389+
#ifdef FFI_VERSION_NUMBER
6390+
PyDoc_STRVAR(libffi_version_info__doc__,
6391+
"ctypes.libffi_version_info\n\
6392+
\n\
6393+
libffi version information as a named tuple.");
6394+
6395+
static PyStructSequence_Field libffi_version_info_fields[] = {
6396+
{"major", "Major release number"},
6397+
{"minor", "Minor release number"},
6398+
{"patch", "Patch release number"},
6399+
{0}
6400+
};
6401+
6402+
static PyStructSequence_Desc libffi_version_info_desc = {
6403+
"ctypes.libffi_version_info", /* name */
6404+
libffi_version_info__doc__, /* doc */
6405+
libffi_version_info_fields, /* fields */
6406+
3
6407+
};
6408+
6409+
static PyObject *
6410+
make_libffi_version_info(PyTypeObject *type, unsigned long number)
6411+
{
6412+
PyObject *version;
6413+
int pos = 0;
6414+
unsigned long major = number / 10000;
6415+
unsigned long minor = (number % 10000) / 100;
6416+
unsigned long patch = number % 100;
6417+
6418+
version = PyStructSequence_New(type);
6419+
if (version == NULL) {
6420+
return NULL;
6421+
}
6422+
6423+
#define SetItem(VALUE) \
6424+
PyStructSequence_SET_ITEM(version, pos++, VALUE); \
6425+
if (PyErr_Occurred()) { \
6426+
Py_DECREF(version); \
6427+
return NULL; \
6428+
}
6429+
6430+
SetItem(PyLong_FromUnsignedLong(major))
6431+
SetItem(PyLong_FromUnsignedLong(minor))
6432+
SetItem(PyLong_FromUnsignedLong(patch))
6433+
#undef SetItem
6434+
6435+
return version;
6436+
}
6437+
6438+
static int
6439+
_ctypes_add_version_constants(PyObject *mod)
6440+
{
6441+
if (PyModule_AddStringConstant(mod, "LIBFFI_VERSION",
6442+
FFI_VERSION_STRING) < 0) {
6443+
return -1;
6444+
}
6445+
if (PyModule_AddStringConstant(mod, "libffi_version",
6446+
ffi_get_version()) < 0) {
6447+
return -1;
6448+
}
6449+
PyTypeObject *version_type;
6450+
version_type = PyStructSequence_NewType(&libffi_version_info_desc);
6451+
if (version_type == NULL) {
6452+
return -1;
6453+
}
6454+
if (PyModule_Add(mod, "LIBFFI_VERSION_INFO",
6455+
make_libffi_version_info(version_type, FFI_VERSION_NUMBER)) < 0)
6456+
{
6457+
Py_DECREF(version_type);
6458+
return -1;
6459+
}
6460+
if (PyModule_Add(mod, "libffi_version_info",
6461+
make_libffi_version_info(version_type,
6462+
ffi_get_version_number())) < 0)
6463+
{
6464+
Py_DECREF(version_type);
6465+
return -1;
6466+
}
6467+
Py_DECREF(version_type);
6468+
return 0;
6469+
}
6470+
#endif
6471+
63896472
static int
63906473
_ctypes_mod_exec(PyObject *mod)
63916474
{
@@ -6440,6 +6523,11 @@ _ctypes_mod_exec(PyObject *mod)
64406523
if (_ctypes_add_objects(mod) < 0) {
64416524
return -1;
64426525
}
6526+
#ifdef FFI_VERSION_NUMBER
6527+
if (_ctypes_add_version_constants(mod) < 0) {
6528+
return -1;
6529+
}
6530+
#endif
64436531
return 0;
64446532
}
64456533

0 commit comments

Comments
 (0)