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
13 changes: 9 additions & 4 deletions Lib/ctypes/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,21 @@
def _get_build_version():
"""Return the version of MSVC that was used to build Python.

For Python 2.3 and up, the version number is included in
sys.version. For earlier versions, assume the compiler is MSVC 6.
The version number is included in sys.version. Return None if it
cannot be found there, for example because Python was not built
with MSVC or because sys.version has been truncated.
"""
# This function was copied from Lib/distutils/msvccompiler.py
prefix = "MSC v."
i = sys.version.find(prefix)
if i == -1:
return 6
# We don't know what version of the compiler this is.
return None
i = i + len(prefix)
s, rest = sys.version[i:].split(" ", 1)
s, sep, _ = sys.version[i:].partition(" ")
if not sep:
# sys.version was truncated inside the version number.
return None
majorVersion = int(s[:-2]) - 6
if majorVersion >= 13:
majorVersion += 1
Expand Down
52 changes: 52 additions & 0 deletions Lib/test/test_ctypes/test_find.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import ctypes.util
import os.path
import sys
import test.support
Expand Down Expand Up @@ -221,5 +222,56 @@ def test_find_nothing_with_wrong_ld_library_path(self):
self.assertIsNone(result)


@unittest.skipUnless(os.name == 'nt', 'Test only valid for Windows')
class FindLibraryWindows(unittest.TestCase):
# gh-154199: Python/getversion.c formats the compiler identification with
# "%.80s", so a long banner (as emitted by clang-cl builds) can be cut off
# before the "MSC v." marker. The build version must then be reported as
# unknown instead of defaulting to MSVC 6, which would make find_msvcrt()
# hand out msvcrt.dll -- a CRT that does not share its errno with the one
# the _ctypes extension module was linked against.
TRUNCATED_CLANG_VERSION = (
'3.16.0a0 free-threading build (heads/main:0123456789ab, '
'Jan 1 2026, 00:00:00) [Clang 22.1.8 '
'(https://github.com/llvm/llvm-project ca7933e47d3a3451d81e72ac174d'
)
# Truncated just after the marker, so the version digits are missing.
TRUNCATED_IN_MARKER_VERSION = (
'3.16.0a0 (main:0123456789ab, Jan 1 2026, 00:00:00) '
'[Clang 22.1.8 (https://example.invalid) 64 bit (AMD64) with MSC v.1944'
)
MSVC_VERSION = (
'3.16.0a0 (main:0123456789ab, Jan 1 2026, 00:00:00) '
'[MSC v.1944 64 bit (AMD64)]'
)

@thread_unsafe('patches sys.version')
def test_get_build_version_truncated(self):
with unittest.mock.patch.object(sys, 'version',
self.TRUNCATED_CLANG_VERSION):
self.assertIsNone(ctypes.util._get_build_version())

@thread_unsafe('patches sys.version')
def test_get_build_version_truncated_in_marker(self):
with unittest.mock.patch.object(sys, 'version',
self.TRUNCATED_IN_MARKER_VERSION):
self.assertIsNone(ctypes.util._get_build_version())

@thread_unsafe('patches sys.version')
def test_find_msvcrt_truncated(self):
with unittest.mock.patch.object(sys, 'version',
self.TRUNCATED_CLANG_VERSION):
self.assertIsNone(ctypes.util.find_msvcrt())
self.assertIsNone(find_library('c'))
self.assertIsNone(find_library('m'))

@thread_unsafe('patches sys.version')
def test_get_build_version_msvc(self):
with unittest.mock.patch.object(sys, 'version', self.MSVC_VERSION):
self.assertEqual(ctypes.util._get_build_version(), 14.4)
# Recent versions of the CRT are not directly loadable.
self.assertIsNone(ctypes.util.find_msvcrt())


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Fix :func:`ctypes.util.find_msvcrt` on Windows builds whose :data:`sys.version`
does not contain the ``MSC v.`` marker, which happens on ``clang-cl`` builds
because the compiler banner is truncated to 80 characters. Instead of assuming
MSVC 6 and returning ``msvcrt.dll`` -- a C runtime that does not share its
``errno`` with the one the :mod:`!_ctypes` extension module is linked against
-- the compiler version is now reported as unknown, so
:func:`!ctypes.util.find_msvcrt` and ``find_library('c')`` return ``None``,
matching builds made with modern versions of MSVC.
Loading