Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions Doc/library/ctypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3383,3 +3383,47 @@ Exceptions
.. availability:: Windows

.. versionadded:: 3.14


Library version
^^^^^^^^^^^^^^^

The following constants are only available if :mod:`!ctypes` was built with
libffi 3.5 or later, which is the first version providing this information.

.. data:: LIBFFI_VERSION

The version string of the libffi library that was used for building
the module, like ``'3.5.2'``.
This may be different from the libffi library actually used at runtime,
which is available as :const:`libffi_version`.

.. versionadded:: next

.. data:: libffi_version

The version string of the libffi library actually loaded by the interpreter.

.. versionadded:: next

.. data:: LIBFFI_VERSION_INFO

A named tuple containing the three components of the libffi library
version that was used for building the module:
*major*, *minor*, and *patch*.
All values are integers.
The components can also be accessed by name,
so ``ctypes.LIBFFI_VERSION_INFO[0]`` is equivalent to
``ctypes.LIBFFI_VERSION_INFO.major`` and so on.
This may be different from the libffi library actually used at runtime,
which is available as :const:`libffi_version_info`.

.. versionadded:: next

.. data:: libffi_version_info

A named tuple containing the version of the libffi library
actually loaded by the interpreter,
with the same fields as :const:`LIBFFI_VERSION_INFO`.

.. versionadded:: next
7 changes: 7 additions & 0 deletions Doc/whatsnew/3.16.rst
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,13 @@ ctypes
through a function signature.
(Contributed by Peter Bierma in :gh:`153903`.)

* Added constants :const:`~ctypes.LIBFFI_VERSION`,
:const:`~ctypes.libffi_version`, :const:`~ctypes.LIBFFI_VERSION_INFO`
and :const:`~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.
(Contributed by Serhiy Storchaka in :gh:`157487`.)


curses
------
Expand Down
6 changes: 6 additions & 0 deletions Lib/ctypes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@
from _ctypes import RTLD_LOCAL, RTLD_GLOBAL
from _ctypes import ArgumentError
from _ctypes import SIZEOF_TIME_T
try:
from _ctypes import (LIBFFI_VERSION, libffi_version,
LIBFFI_VERSION_INFO, libffi_version_info)
except ImportError:
# libffi < 3.5 does not provide version information.
pass
from _ctypes import CField

from struct import calcsize as _calcsize
Expand Down
11 changes: 11 additions & 0 deletions Lib/test/pythoninfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,16 @@ def collect_zstd(info_add):
copy_attributes(info_add, _zstd, 'zstd.%s', attributes)


def collect_ctypes(info_add):
try:
import _ctypes
except ImportError:
return

attributes = ('LIBFFI_VERSION', 'libffi_version')
copy_attributes(info_add, _ctypes, 'ctypes.%s', attributes)


def collect_expat(info_add):
try:
from xml.parsers import expat
Expand Down Expand Up @@ -1353,6 +1363,7 @@ def collect_info(info):
collect_curses,
collect_datetime,
collect_decimal,
collect_ctypes,
collect_expat,
collect_fips,
collect_gdb,
Expand Down
34 changes: 34 additions & 0 deletions Lib/test/test_ctypes/test_values.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,48 @@
"""

import _imp
import ctypes
import importlib.util
import sys
import unittest
from ctypes import (Structure, CDLL, POINTER, pythonapi,
c_ubyte, c_char_p, c_int)
from test import support
from test.support import import_helper, thread_unsafe


class LibffiVersionTest(unittest.TestCase):

def _test_libffi_version(self, v, string):
self.assertIsInstance(v[:], tuple)
self.assertEqual(len(v), 3)
self.assertIsInstance(v[0], int)
self.assertIsInstance(v[1], int)
self.assertIsInstance(v[2], int)
self.assertIsInstance(v.major, int)
self.assertIsInstance(v.minor, int)
self.assertIsInstance(v.patch, int)
self.assertEqual(v[0], v.major)
self.assertEqual(v[1], v.minor)
self.assertEqual(v[2], v.patch)
self.assertGreaterEqual(v.major, 3)
self.assertGreaterEqual(v.minor, 0)
self.assertGreaterEqual(v.patch, 0)
self.assertEqual(string, '%d.%d.%d' % v)

@unittest.skipUnless(hasattr(ctypes, 'LIBFFI_VERSION_INFO'),
'requires libffi >= 3.5')
def test_libffi_version(self):
if support.verbose:
print(f'LIBFFI_VERSION = {ctypes.LIBFFI_VERSION}', flush=True)
print(f'libffi_version = {ctypes.libffi_version}', flush=True)
print(f'LIBFFI_VERSION_INFO = {ctypes.LIBFFI_VERSION_INFO}', flush=True)
print(f'libffi_version_info = {ctypes.libffi_version_info}', flush=True)
self._test_libffi_version(ctypes.LIBFFI_VERSION_INFO, ctypes.LIBFFI_VERSION)
self._test_libffi_version(ctypes.libffi_version_info, ctypes.libffi_version)
self.assertEqual(ctypes.LIBFFI_VERSION_INFO[0], ctypes.libffi_version_info[0])


class ValuesTestCase(unittest.TestCase):

def setUp(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Add constants :const:`ctypes.LIBFFI_VERSION`, :const:`ctypes.libffi_version`,
:const:`ctypes.LIBFFI_VERSION_INFO` and :const:`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.
88 changes: 88 additions & 0 deletions Modules/_ctypes/_ctypes.c
Original file line number Diff line number Diff line change
Expand Up @@ -6386,6 +6386,89 @@ _ctypes_add_objects(PyObject *mod)
}


#ifdef FFI_VERSION_NUMBER
PyDoc_STRVAR(libffi_version_info__doc__,
"ctypes.libffi_version_info\n\
\n\
libffi version information as a named tuple.");

static PyStructSequence_Field libffi_version_info_fields[] = {
{"major", "Major release number"},
{"minor", "Minor release number"},
{"patch", "Patch release number"},
{0}
};

static PyStructSequence_Desc libffi_version_info_desc = {
"ctypes.libffi_version_info", /* name */
libffi_version_info__doc__, /* doc */
libffi_version_info_fields, /* fields */
3
};

static PyObject *
make_libffi_version_info(PyTypeObject *type, unsigned long number)
{
PyObject *version;
int pos = 0;
unsigned long major = number / 10000;
unsigned long minor = (number % 10000) / 100;
unsigned long patch = number % 100;

version = PyStructSequence_New(type);
if (version == NULL) {
return NULL;
}

#define SetItem(VALUE) \
PyStructSequence_SET_ITEM(version, pos++, VALUE); \
if (PyErr_Occurred()) { \
Py_DECREF(version); \
return NULL; \
}

SetItem(PyLong_FromUnsignedLong(major))
SetItem(PyLong_FromUnsignedLong(minor))
SetItem(PyLong_FromUnsignedLong(patch))
#undef SetItem

return version;
}

static int
_ctypes_add_version_constants(PyObject *mod)
{
if (PyModule_AddStringConstant(mod, "LIBFFI_VERSION",
FFI_VERSION_STRING) < 0) {
return -1;
}
if (PyModule_AddStringConstant(mod, "libffi_version",
ffi_get_version()) < 0) {
return -1;
}
PyTypeObject *version_type;
version_type = PyStructSequence_NewType(&libffi_version_info_desc);
if (version_type == NULL) {
return -1;
}
if (PyModule_Add(mod, "LIBFFI_VERSION_INFO",
make_libffi_version_info(version_type, FFI_VERSION_NUMBER)) < 0)
{
Py_DECREF(version_type);
return -1;
}
if (PyModule_Add(mod, "libffi_version_info",
make_libffi_version_info(version_type,
ffi_get_version_number())) < 0)
{
Py_DECREF(version_type);
return -1;
}
Py_DECREF(version_type);
return 0;
}
#endif

static int
_ctypes_mod_exec(PyObject *mod)
{
Expand Down Expand Up @@ -6440,6 +6523,11 @@ _ctypes_mod_exec(PyObject *mod)
if (_ctypes_add_objects(mod) < 0) {
return -1;
}
#ifdef FFI_VERSION_NUMBER
if (_ctypes_add_version_constants(mod) < 0) {
return -1;
}
#endif
return 0;
}

Expand Down
Loading