You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
On a Windows CPython built with clang-cl (build.bat "/p:PlatformToolset=ClangCL", documented in PCbuild/readme.txt; gh-77532), ctypes.util.find_msvcrt() returns 'msvcrt.dll' instead of None. The cause is sys.version truncation: Python/getversion.c caps the compiler segment at 80 chars (%.80s), which cuts off the "MSC v." marker that ctypes.util._get_build_version() sniffs for, so it falls back to "assume MSVC 6". The practical impact: CDLL(find_library("c"), use_errno=True) loads the OS-private legacy msvcrt.dll, whose errno is a different variable from the ucrt errno that _ctypes saves/restores around foreign calls — ctypes.get_errno() returns stale values (typically 0) with no exception raised.
Observable symptom: test.test_ctypes.test_errno.Test.test_openfails on clang-cl builds, while on MSVC builds find_msvcrt() returns None and the same test silently skips (skipTest("Unable to find C library")), which is why this has gone unnoticed.
The same build additionally shows both CRTs loaded after the call (ctypes.util.dllist() lists ucrtbase.dllandmsvcrt.dll), which is the divergence in one line.
Root cause chain (permalinks sha-pinned to main @ 16d0c89, 2026-07-18; _get_build_version/find_msvcrt/find_library are textually unchanged on today's main, merely shifted a few lines by later unrelated ctypes commits):
But Python/getversion.c#L18-L20 composes sys.version with "%.80s (%.80s) %.80s" (free-threaded builds use the "%.80s free-threading build (%.80s) %.80s" variant — same %.80s cap), truncating the compiler segment at 80 chars. Official LLVM Windows binaries have a long __clang_version__ (22.1.8 (https://github.com/llvm/llvm-project <40-hex-sha>) — 86 chars by itself, 93 with the [Clang prefix), so the tail carrying both markers is always truncated away. The full Py_GetCompiler() string on my build is 130 chars:
[Clang 22.1.8 (https://github.com/llvm/llvm-project ca7933e47d3a3451d81e72ac174dcb5aa28b59d1)] 64 bit (AMD64) with MSC v.1951 CRT]
Lib/ctypes/util.py#L10-L33: _get_build_version() sniffs sys.version for "MSC v." and, when absent, returns 6 ("assume the compiler is MSVC 6", a distutils-era assumption).
msvcrt.dll maintains its own CRT state; the interpreter and _ctypes use the ucrt. The errno set by msvcrt.dll's _open is never seen by the use_errno machinery → get_errno() == 0.
Note that the 'msvcrt.dll' return is reachable only through this banner-sniff failure: when the marker survives (e.g. MSVC's [MSC v.1943 64 bit (AMD64)]), _get_build_version() yields 14.x and find_msvcrt() returns None — the code comment there cites issue23606, "CRT is no longer directly loadable" (gh-67794/bpo-23606). A clang-cl build links the same ucrt as the MSVC build, so 'msvcrt.dll' is simply the wrong answer for it. The MSVC-6 fallback would still be meaningful for a toolchain that genuinely links msvcrt.dll (classic MinGW), which is worth weighing when choosing between the options below.
Suggested fix directions (deferring to the maintainers):
Minimal: make _get_build_version() return None instead of 6 when "MSC v." is absent, so find_msvcrt()/find_library("c") behave exactly as on modern MSVC builds and test_errno skips consistently. This trades the VC6-era fallback away; if the MinGW case above is considered worth preserving, the next option handles both.
Alternative: make find_msvcrt() report the CRT the interpreter is actually linked against (ucrtbase.dll / the api-ms-win-crt-* stable ABI) independent of the compiler banner — correct for every toolchain, but it would reverse the bpo-23606 decision that modern MSVC returns None.
I'm happy to submit a PR for whichever direction a maintainer prefers, with a regression test that exercises _get_build_version() against a truncated clang-style banner.
Side observations I can file separately if useful: the %.80s cap leaves sys.version ending mid-commit-hash without its closing bracket on official-LLVM clang builds, and the clang _Py_COMPILER string literal has unbalanced brackets (one [, two ]).
Environment / build: CPython main @ f62050d (2026-07-16), built per PCbuild with build.bat -p x64 --disable-gil --pgo --tail-call-interp "/p:PlatformToolset=ClangCL", LLVM 22.1.8 official Windows release, Windows 11 x64. Also reproduced identically on an earlier (June 2026) clang-cl build, and I confirmed the contrasting skip behaviour on an MSVC build of the same source. My working branch carries only build-system tuning (PCbuild props/vcxproj plus a zlib-ng header) and build-regenerated files (hence the -dirty banner); the files involved here — Lib/ctypes/util.py, Lib/test/test_ctypes/test_errno.py, Python/getversion.c, PC/pyconfig.h — are unmodified from upstream. The failure is not related to free-threading — the mechanism only involves the compiler banner segment.
I'm aware clang-cl is PY_SUPPORT_TIER 0; filing since PCbuild documents and actively maintains the toolset (gh-77532, and it is still being kept working — e.g. gh-146210 in 2026, gh-131278/gh-130090/gh-131035 in 2025), the defect is in a stdlib banner-sniff rather than the toolchain, and the same latent defect affects any build whose banner lacks the marker.
Related: gh-77532 / GH-101352 (clang banner introduction), gh-67794 (bpo-23606, find_msvcrt → None for modern MSVC), gh-70914 (user report that find_msvcrt() returns None on post-VC6 builds; closed as a duplicate of bpo-23606), gh-145916 (soft-deprecation of ctypes.util.find_library — its linked PR touches only the docs, so it removes neither this defect nor the test failure), gh-145410 / GH-146145 (same truncation, sysconfig side). The same truncated banner also breaks the vendored get_platform() in Lib/test/test_importlib/test_windows.py; filed separately as gh-154200.
Bug description:
On a Windows CPython built with clang-cl (
build.bat "/p:PlatformToolset=ClangCL", documented in PCbuild/readme.txt; gh-77532),ctypes.util.find_msvcrt()returns'msvcrt.dll'instead ofNone. The cause issys.versiontruncation:Python/getversion.ccaps the compiler segment at 80 chars (%.80s), which cuts off the"MSC v."marker thatctypes.util._get_build_version()sniffs for, so it falls back to "assume MSVC 6". The practical impact:CDLL(find_library("c"), use_errno=True)loads the OS-private legacymsvcrt.dll, whoseerrnois a different variable from the ucrterrnothat_ctypessaves/restores around foreign calls —ctypes.get_errno()returns stale values (typically0) with no exception raised.Observable symptom:
test.test_ctypes.test_errno.Test.test_openfails on clang-cl builds, while on MSVC buildsfind_msvcrt()returnsNoneand the same test silently skips (skipTest("Unable to find C library")), which is why this has gone unnoticed.Observed on a clang-cl build of main:
The same build additionally shows both CRTs loaded after the call (
ctypes.util.dllist()listsucrtbase.dllandmsvcrt.dll), which is the divergence in one line.Root cause chain (permalinks sha-pinned to main @ 16d0c89, 2026-07-18;
_get_build_version/find_msvcrt/find_libraryare textually unchanged on today's main, merely shifted a few lines by later unrelated ctypes commits):PC/pyconfig.h#L161builds the clang banner as"[Clang " __clang_version__ "] 64 bit (AMD64) with MSC v." _Py_STRINGIZE(_MSC_VER) " CRT]"(added in gh-77532: Minor tweaks to allow compiling with PlatformToolset=ClangCL on Windows #101352) — so theMSC v.marker is intended to be present.Python/getversion.c#L18-L20composessys.versionwith"%.80s (%.80s) %.80s"(free-threaded builds use the"%.80s free-threading build (%.80s) %.80s"variant — same%.80scap), truncating the compiler segment at 80 chars. Official LLVM Windows binaries have a long__clang_version__(22.1.8 (https://github.com/llvm/llvm-project <40-hex-sha>)— 86 chars by itself, 93 with the[Clangprefix), so the tail carrying both markers is always truncated away. The fullPy_GetCompiler()string on my build is 130 chars:sys.versioncompiler segment ends at...ca7933e47d3a3451d81e72ac174d— 28 characters into the commit hash, withoutMSC v.,(AMD64), or the closing bracket. (An MSVC banner is ~27 chars and is never truncated, which is why this is clang-specific. The same truncation already brokesysconfig.get_platform()on clang-cl builds — sysconfig.get_platform() could return wrong value because sys.version is truncated #145410, fixed in March 2026 by moving the check into C: gh-145410: Add _sysconfig.get_platform() function #146145.)Lib/ctypes/util.py#L10-L33:_get_build_version()sniffssys.versionfor"MSC v."and, when absent, returns 6 ("assume the compiler is MSVC 6", a distutils-era assumption).Lib/ctypes/util.py#L35-L54:find_msvcrt()mapsversion <= 6to'msvcrt.dll', andfind_library("c")returns that.msvcrt.dllmaintains its own CRT state; the interpreter and_ctypesuse the ucrt. Theerrnoset bymsvcrt.dll's_openis never seen by theuse_errnomachinery →get_errno() == 0.Note that the
'msvcrt.dll'return is reachable only through this banner-sniff failure: when the marker survives (e.g. MSVC's[MSC v.1943 64 bit (AMD64)]),_get_build_version()yields 14.x andfind_msvcrt()returnsNone— the code comment there cites issue23606, "CRT is no longer directly loadable" (gh-67794/bpo-23606). A clang-cl build links the same ucrt as the MSVC build, so'msvcrt.dll'is simply the wrong answer for it. The MSVC-6 fallback would still be meaningful for a toolchain that genuinely linksmsvcrt.dll(classic MinGW), which is worth weighing when choosing between the options below.Suggested fix directions (deferring to the maintainers):
_get_build_version()returnNoneinstead of6when"MSC v."is absent, sofind_msvcrt()/find_library("c")behave exactly as on modern MSVC builds andtest_errnoskips consistently. This trades the VC6-era fallback away; if the MinGW case above is considered worth preserving, the next option handles both.find_msvcrt()report the CRT the interpreter is actually linked against (ucrtbase.dll/ theapi-ms-win-crt-*stable ABI) independent of the compiler banner — correct for every toolchain, but it would reverse the bpo-23606 decision that modern MSVC returnsNone.sys.versionstring-sniffing altogether; the truncation shows the banner is not a reliable data channel (sysconfig.get_platform() could return wrong value because sys.version is truncated #145410 already movedsysconfigoff it).I'm happy to submit a PR for whichever direction a maintainer prefers, with a regression test that exercises
_get_build_version()against a truncated clang-style banner.Side observations I can file separately if useful: the
%.80scap leavessys.versionending mid-commit-hash without its closing bracket on official-LLVM clang builds, and the clang_Py_COMPILERstring literal has unbalanced brackets (one[, two]).Environment / build: CPython main @ f62050d (2026-07-16), built per PCbuild with
build.bat -p x64 --disable-gil --pgo --tail-call-interp "/p:PlatformToolset=ClangCL", LLVM 22.1.8 official Windows release, Windows 11 x64. Also reproduced identically on an earlier (June 2026) clang-cl build, and I confirmed the contrasting skip behaviour on an MSVC build of the same source. My working branch carries only build-system tuning (PCbuild props/vcxproj plus a zlib-ng header) and build-regenerated files (hence the-dirtybanner); the files involved here —Lib/ctypes/util.py,Lib/test/test_ctypes/test_errno.py,Python/getversion.c,PC/pyconfig.h— are unmodified from upstream. The failure is not related to free-threading — the mechanism only involves the compiler banner segment.I'm aware clang-cl is
PY_SUPPORT_TIER 0; filing since PCbuild documents and actively maintains the toolset (gh-77532, and it is still being kept working — e.g. gh-146210 in 2026, gh-131278/gh-130090/gh-131035 in 2025), the defect is in a stdlib banner-sniff rather than the toolchain, and the same latent defect affects any build whose banner lacks the marker.Related: gh-77532 / GH-101352 (clang banner introduction), gh-67794 (bpo-23606,
find_msvcrt→Nonefor modern MSVC), gh-70914 (user report thatfind_msvcrt()returnsNoneon post-VC6 builds; closed as a duplicate of bpo-23606), gh-145916 (soft-deprecation ofctypes.util.find_library— its linked PR touches only the docs, so it removes neither this defect nor the test failure), gh-145410 / GH-146145 (same truncation,sysconfigside). The same truncated banner also breaks the vendoredget_platform()inLib/test/test_importlib/test_windows.py; filed separately as gh-154200.CPython versions tested on:
CPython main branch
Operating systems tested on:
Windows