Skip to content

feat!: drop support for Python 3.8, MSVC 2017#6110

Open
henryiii wants to merge 11 commits into
masterfrom
drop-python-3.8
Open

feat!: drop support for Python 3.8, MSVC 2017#6110
henryiii wants to merge 11 commits into
masterfrom
drop-python-3.8

Conversation

@henryiii

@henryiii henryiii commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator

Now require 3.9+ and matching MSVC 2019+.

I did check PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX before making the change in the PR, no obvious examples of this undocumented value being overridden.

🤖 AI text below 🤖

Description

Python 3.8 is at end of life. The deprecation note in the docs said that support
goes away in 3.1. This PR removes it. The minimum is now Python 3.9.

Configuration and tooling:

  • requires-python, the classifiers, and the mypy and pylint versions move to 3.9
  • CI matrix and packaging jobs move from 3.8 to 3.9. The AlmaLinux 8 job installs python39-devel
  • The AppVeyor job uses C:\Python39 in place of C:\Python38
  • The CMake tooling asks for Python 3.9
  • The #error in detail/common.h rejects Python earlier than 3.9
  • setup_helpers.py uses built-in generics in place of typing.List and typing.Tuple

Dead code paths that this removes:

  • the _PyObject_Vectorcall fallback in cast.h
  • the frame->f_code and frame->f_back fallbacks in pytypes.h
  • the PyFrame_FastToLocals path in get_type_override
  • the conditional Py_VISIT(Py_TYPE(self)) in tp_traverse
  • PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX, the pre-PyConfig interpreter init, and
    the widen_chars helpers in embed.h

The embed.h macro had a default of 0x03080000. Its legacy branch cannot run when
the minimum is 3.9, so the macro and the branch are gone. Four interpreter test cases
were behind that macro. They now always run.

I also renamed the Add program dir to path pre-PyConfig test case to
Add program dir to path without PyConfig, because no pre-PyConfig path is left.

Suggested changelog entry:

  • Support for Python 3.8 was removed. The minimum supported version is now Python 3.9.
  • Support for MSVC 2017 has been dropped (CPython 3.9.2+ requires 2019+).

📚 Documentation preview 📚: https://pybind11--6110.org.readthedocs.build/

henryiii added 3 commits July 24, 2026 23:46
The minimum supported version is now Python 3.9. pybind11 v3.0 was the last
release that supports Python 3.8. The deprecation note said that support goes
away in 3.1.

Remove the code paths that this makes dead:

- the `_PyObject_Vectorcall` fallback in `cast.h`
- the `frame->f_code` and `frame->f_back` fallbacks in `pytypes.h`
- the `PyFrame_FastToLocals` path in `get_type_override`
- the conditional `Py_VISIT(Py_TYPE(self))` in `tp_traverse`
- `PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX`, the pre-PyConfig interpreter
  init, and the `widen_chars` helpers in `embed.h`

Assisted-by: ClaudeCode:claude-opus-5
The AppVeyor job set `PYTHON: 38`, which makes the path `C:\Python38`.
The image gives Python 3.9.13 as `C:\Python39`.

Assisted-by: ClaudeCode:claude-opus-5
Python 3.9 is the new minimum, so MSVC 2017 is no longer needed. Raise the
compile-time floor to _MSC_VER 1920 and remove the workarounds that only
applied below it: std::launder, fold expressions, weak_from_this, aligned
new/delete, the C4100 warning helper, and the func_handle syntax error.

AppVeyor now builds with Visual Studio 2019.

Assisted-by: ClaudeCode:claude-opus-5
@henryiii henryiii changed the title feat!: drop support for Python 3.8 feat!: drop support for Python 3.8, MSVC 2017 Jul 25, 2026
henryiii added 6 commits July 25, 2026 00:44
CMake 4 has no FindPythonLibs, so pybind11 uses FindPython. FindPython reads
the registry before PATH and selected `C:\Python314-x64`, but the test packages
go into the Python on PATH. Pass `Python_ROOT_DIR` to name the correct one.

Also set `CMAKE_ARCH`. It was never set, so the architecture came from the
generator. Visual Studio 2017 defaults to Win32, but Visual Studio 2019
defaults to x64, which made this x86 job build 64-bit code.

Assisted-by: ClaudeCode:claude-opus-5
The build compiled but failed to link with LNK1104 on a bare `python39.lib`.
That name comes from the `#pragma comment(lib, ...)` in pyconfig.h, so the
linker needs the directory. CMake 4 has no FindPythonLibs, and FindPython does
not add it for this Debug x86 build. Put it on LIB instead.

The directory listing is temporary, to confirm the library is present.

Assisted-by: ClaudeCode:claude-opus-5
The image ships python39_d.lib next to python39.lib, so FindPython picks the
debug import library for a Debug build. pybind11 undefines _DEBUG around
Python.h, so pyconfig.h asks for python39.lib instead and the link failed with
LNK1104. Name the release library for the debug slot.

Setting LIB does not work, because MSBuild replaces it from the toolset, and it
would link both import libraries.

Assisted-by: ClaudeCode:claude-opus-5
Revert the two attempted fixes. Neither changed the failure: setting LIB does
not survive MSBuild, and naming the release library for Python_LIBRARY_DEBUG
had no effect.

Print the Python cache entries and the link settings of a generated project
file instead, to see what the linker really gets. Temporary.

Assisted-by: ClaudeCode:claude-opus-5
The generated project file linked C:\Python39\libs\python39_d.lib in the Debug
configuration, because the image ships debug binaries next to the release ones.
pybind11 undefines _DEBUG around Python.h, so pyconfig.h asks for python39.lib
in a #pragma comment(lib), which nothing on the link line satisfies and no
library directory holds. Map Debug to the release artifacts.

Assisted-by: ClaudeCode:claude-opus-5
Assisted-by: ClaudeCode:claude-opus-5
@henryiii

Copy link
Copy Markdown
Collaborator Author

🤖 AI text below 🤖

Why AppVeyor failed

The job compiled, then failed to link every module:

LINK : fatal error LNK1104: cannot open file 'python39.lib'

The AppVeyor image has python39_d.lib next to python39.lib. FindPython therefore used the debug import library for the Debug configuration:

Debug:   <AdditionalDependencies>C:\Python39\libs\python39_d.lib;kernel32.lib;...
Release: <AdditionalDependencies>C:\Python39\libs\python39.lib;kernel32.lib;...
         <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>

pybind11 undefines _DEBUG around Python.h, so pyconfig.h asks for the release library in a #pragma comment(lib, "python39.lib"). The linker satisfies such a request for free only if a library of that name is already on the link line. In Release it is. In Debug it is not, and there is no library directory to search.

This is new because the Visual Studio 2019 image has CMake 4.1, which has no FindPythonLibs. The build now uses FindPython. The Visual Studio 2017 job used the classic module, which took the release library. GitHub Actions does not show the problem, because setup-python installs no _d library.

The fix

One flag, so Debug uses the release artifacts of imported targets:

-DCMAKE_MAP_IMPORTED_CONFIG_DEBUG=Release

AppVeyor is green: Debug links python39.lib, 1256 tests pass, and the C++ tests give 25 cases with 11285 assertions.

Follow-up, not in this PR

The same mismatch can hit users. An MSVC Debug build against a Python installation that has the optional debug binaries links pythonXY_d.lib, but pybind11 builds for the release ABI. A fix in tools/pybind11NewTools.cmake needs care, because MAP_IMPORTED_CONFIG_DEBUG on the module target applies to all imported dependencies of that target, not only Python.

@henryiii
henryiii marked this pull request as ready for review July 26, 2026 03:04
The distro pytest in the Clang and GCC Docker jobs has no
PytestUnraisableExceptionWarning, so an unconditional filterwarnings
mark makes pytest fail with an INTERNALERROR after the tests pass.

Assisted-by: ClaudeCode:claude-opus-5

Claude-Session: https://claude.ai/code/session_01Aimf6HuSz1vLRwBnbxmCTc
@rwgk

rwgk commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

PR 6110 initial findings (codex gpt-5.6-sol)

Reviewed at commit 6e22547

Overall assessment

The implementation is fundamentally sound. I found no supported-version C++
correctness problem, ABI regression, or need for an internals-version bump. The
removed CPython and MSVC compatibility branches line up with the newly declared
minimum versions.

I recommend addressing the first three findings before merging.

Findings

1. Python_ADDITIONAL_VERSIONS is stale

tools/pybind11Tools.cmake currently sets:

set(Python_ADDITIONAL_VERSIONS
    "3.12;3.11;3.10;3.9"
    CACHE INTERNAL "")

The list is not an upper bound on supported Python versions. It is a manually
maintained compatibility hint for CMake's legacy FindPythonInterp module. Newer
Python versions are often found anyway when:

  • there is a generic python3 executable;
  • a virtual environment is active;
  • PYTHON_EXECUTABLE is specified explicitly; or
  • the installed CMake version already knows about the newer Python version.

Those common paths mask the stale list, but discovery is not reliable in all
supported configurations. I reproduced the problem with CMake 3.15 and a search
environment exposing only a version-suffixed python3.13: the branch defaults
failed with Could NOT find PythonInterp. Prepending 3.13 to
Python_ADDITIONAL_VERSIONS made the same configuration succeed.

Since the project currently declares and tests Python through 3.15, I recommend:

"3.15;3.14;3.13;3.12;3.11;3.10;3.9"

The cutoff at 3.12 appears to be historical lag rather than an intentional
policy decision.

2. The initialize_interpreter documentation describes the removed path

The documentation block in include/pybind11/embed.h still says that an
initialization failure terminates the program and links to Py_InitializeEx and
PySys_SetArgvEx.

The surviving implementation instead uses PyConfig_SetBytesArgv and
Py_InitializeFromConfig, and reports failures by throwing
std::runtime_error. Calling it while an interpreter is already running also
throws through pybind11_fail.

This documentation was already stale for the default modern path, but removing
the legacy initialization path makes it incorrect for every supported Python
version. I recommend updating the block to describe the PyConfig behavior,
exception semantics, and corresponding current CPython APIs.

3. The suggested changelog entry omits the MSVC support change

The PR title and opening sentence say that support for both Python 3.8 and MSVC
2017 is being removed, but the suggested changelog entry mentions only Python
3.8. Because that section is used to generate the project changelog, the
compiler-floor change would otherwise be absent from the release notes.

Suggested wording:

Support for Python 3.8 and MSVC 2017 was removed. The minimum supported
versions are now Python 3.9 and MSVC 2019.

Dropping MSVC 2017 is a separate breaking policy decision rather than a
technical consequence of dropping Python 3.8, so it is especially important to
state it explicitly.

4. Removing PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX is not strictly

dead-code removal

Commit 88699849264150f513dafb98d9ec1bb31a7fa904, which introduced the modern
embedding path, deliberately made
PYBIND11_PYCONFIG_SUPPORT_PY_VERSION_HEX externally overrideable from the
compiler command line. Setting a later threshold allowed a supported Python
version to use the pre-PyConfig initialization path as an escape hatch for an
unforeseen PyConfig problem.

I do not recommend retaining deprecated CPython initialization APIs merely to
preserve that hypothetical escape hatch. However, the PR description should
treat this as an intentional compatibility decision rather than claiming that
the legacy branch could no longer run. A diagnostic for users who still define
the old macro could also avoid a silent behavior change.

This is lower priority because the macro was specialized and not part of the
ordinary embedding interface, and the PR is already explicitly breaking.

5. A PyPy xfail reason remains tied to an unsupported version

tests/test_exceptions.py still has:

@pytest.mark.xfail(env.PYPY, reason="Failure on PyPy 3.8 (7.3.7)", strict=False)

This blanket-xfails the test on current supported PyPy releases while explaining
the exclusion in terms of an implementation version that is no longer
supported. It would be worthwhile to run the test without the marker on the
current PyPy job and either remove the marker or update its reason and linked
issue. I do not consider this a blocker for the support-floor change.

Review notes

  • PyObject_Vectorcall, PyFrame_GetCode, and PyFrame_GetBack are available
    at the new Python 3.9 minimum.
  • The unconditional Py_VISIT(Py_TYPE(self)) behavior is correct for Python
    3.9 and newer.
  • The removed MSVC workarounds all target compiler versions older than
    _MSC_VER == 1920.
  • The ordinary embedding path for supported Python versions remains the same
    PyConfig path; only the command-line escape hatch described above changes
    that conclusion.
  • The package metadata, runtime/header guards, CMake minimums, dependency
    markers, documentation support claims, and test matrices otherwise
    consistently move the Python floor to 3.9.
  • No layout in internals.h, platform ABI identifier, or supported-version
    exported interface is changed.

…fail

Extend Python_ADDITIONAL_VERSIONS through 3.15, describe the PyConfig
behavior of initialize_interpreter, and drop the stale Python 3.8 wording
from the PyPy xfail reason.

Assisted-by: ClaudeCode:claude-opus-5
@henryiii

henryiii commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator Author

I manually updated the description for items 3 and 4.

🤖 AI text below 🤖

Thanks for the review. Items 1, 2, and 5 are addressed in c10f26a.

1. Python_ADDITIONAL_VERSIONS

Agreed, the list is a hint for the legacy FindPythonInterp path and not a support statement. It now reads "3.15;3.14;3.13;3.12;3.11;3.10;3.9".

2. initialize_interpreter documentation

The block now describes the PyConfig path. It says that a failed start throws std::runtime_error, that a second call while an interpreter runs also throws, and it links to the init_config page in place of Py_InitializeEx and PySys_SetArgvEx.

5. The PyPy xfail

I checked the test logs for this PR. The test still fails on PyPy 3.11 (7.3.23) on all three jobs:

tests/test_exceptions.py::test_python_alreadyset_in_destructor XFAIL

Linux, macOS, and Windows all report XFAIL, not XPASS, so the marker is still necessary. Only the reason was stale, so it now says "Failure on PyPy (still fails with 7.3.23)". The link to the upstream PyPy issue stays.

For reference, the same logs show 15 XPASS results on PyPy, from test_call_policies.py and test_class_sh_disowning_mi.py. Those markers are about the PyPy garbage collector and are not related to the support floor, so I left them for a separate PR.

Items 3 and 4 are still open.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants