…sion
Python/getversion.c formats the compiler identification with \\%.80s\\, so the
long banner emitted by clang-cl builds is cut off before the \\MSC v.\\ marker
that _get_build_version() looks for. It then fell back to \\
eturn 6\\, i.e.
'assume MSVC 6', and find_msvcrt() handed out msvcrt.dll -- a CRT that does not
share its errno with the ucrt that _ctypes is linked against, so
ctypes.get_errno() silently returned stale values.
Report the build version as unknown instead, so find_msvcrt() and
find_library('c') return None exactly as they do on modern MSVC builds.
Also handle a second truncation case found while fixing this one: when the cut
lands inside the version number itself the marker is present but the digits are
not, and the unpacking assignment raised ValueError out of find_library('c').
Fixes #154199
Python/getversion.c formats the compiler identification with "%.80s". The banner
that PC/pyconfig.h emits for clang-cl builds is
"[Clang " __clang_version__ "] 64 bit (AMD64) with MSC v.<n> CRT]", and official LLVM
Windows binaries have an 86-character __clang_version__ (version + repo URL + full
commit hash), so the tail carrying the "MSC v." marker is always cut off.
ctypes.util._get_build_version() looks for that marker and, not finding it, fell back
to return 6 — "assume the compiler is MSVC 6". find_msvcrt() then maps version 6 to
msvcrt.dll and find_library('c') returns it. That CRT does not share its errno with
the ucrt that _ctypes is linked against, so ctypes.get_errno() silently returns stale
values after a failing call. No exception is raised — the caller just gets wrong data.
This reports the build version as unknown instead, so find_msvcrt() and
find_library('c') return None exactly as they already do on modern MSVC builds
(where _get_build_version() returns 14.x and the "CRT is no longer directly loadable"
branch from bpo-23606 applies). find_msvcrt() already had a version is None guard, so
this simply routes truncated banners into the existing safe path.
While fixing that I found a second manifestation of the same truncation: when the cut
lands inside the version digits, the marker is present but the number is not, and
s, rest = sys.version[i:].split(" ", 1) raises
ValueError: not enough values to unpack out of find_library('c') — worse than a wrong
return value. partition() handles both cases. Happy to split that into its own PR if
you would rather keep this one to the single change.
The documented contract is unchanged: Doc/library/ctypes.rst already says find_msvcrt()
returns None "if the name of the library cannot be determined", and that find_library("c")
fails on Windows. Only the undocumented MSVC-6 fallback is removed.
Testing
Added FindLibraryWindows to Lib/test/test_ctypes/test_find.py. It patches sys.version
with unittest.mock, so it exercises the fix on any Windows build — a clang-cl build is not
needed to run it. It covers a truncated clang banner, a truncation inside the marker, and a
real MSVC banner (asserting 14.4 / None, so the MSVC path is pinned against regression).
On a local clang-cl free-threaded build, python -m test test_ctypes:
test_open now skips with 'Unable to find C library', identical to an MSVC build. Three
tests move from run to skipped (test_errno.test_open, test_loading.test_find,
test_callbacks.test_issue_8959_a); all three already skip on MSVC builds, so this converges
clang-cl onto the reference platform rather than reducing coverage. Verified no MSVC
regression by mocking real banners: MSC v.1944 -> 14.4, MSC v.1600 -> msvcr100.dll,
MSC v.1200 -> msvcrt.dll (a genuine MSVC 6 build is unaffected).
The root cause is arguably the "%.80s" precision in Python/getversion.c — sys.version
also ends mid-hash without its closing bracket on these builds, and it broke
sysconfig.get_platform() the same way in gh-145410. I have not touched it here since that
would change sys.version content for every consumer, but it may be worth a separate issue.