Skip to content

_testcapi fails to link on single-config generators: cannot open file 'python3.lib' (LNK1104) #452

Open

Description

@hjmjohnson

Summary

On Windows, building with a single-config generator (Ninja, NMake Makefiles) fails while linking the _testcapi extension:

[721/737] Linking C shared library Lib\lib-dynload\_testcapi.pyd
FAILED: Lib/lib-dynload/_testcapi.pyd libs/_testcapi.lib
LINK : fatal error LNK1104: cannot open file 'python3.lib'

Multi-config generators (Visual Studio) are unaffected, which is why this has gone unnoticed.

Cause

_testcapi contains translation units compiled with Py_LIMITED_API (Modules/_testcapi/vectorcall_limited.c, heaptype_relative.c). PC/pyconfig.h then emits an auto-link directive for the stable-ABI import library:

/* PC/pyconfig.h */
#elif defined(Py_LIMITED_API)
#       pragma comment(lib,"python3.lib")

so python3.lib must be on the linker search path. CMakeLists.txt supplies that path by reconstructing it:

# CMakeLists.txt:653
set(libpython_output_dir "${PROJECT_BINARY_DIR}/CMakeBuild/libpython/$<CONFIG>")
target_link_directories(extension_testcapi PRIVATE ${libpython_output_dir})

$<CONFIG> expands on every generator, but only multi-config generators additionally append the configuration to a target's archive output directory. libpython3-shared sets LIBRARY_OUTPUT_DIRECTORY and RUNTIME_OUTPUT_DIRECTORY but no ARCHIVE_OUTPUT_DIRECTORY (cmake/libpython/CMakeLists.txt), so its import library falls back to the target's own binary directory — CMakeBuild/libpython, set by add_subdirectory(cmake/libpython CMakeBuild/libpython) at CMakeLists.txt:637.

Producer and consumer therefore disagree by exactly one directory level:

path
where python3.lib is actually written <build>/CMakeBuild/libpython/python3.lib
where target_link_directories points <build>/CMakeBuild/libpython/Release/ (does not exist)

Observed on disk in a failing build:

$ ls <build>/CMakeBuild/libpython/
CMakeFiles/  python3.lib          <-- the file
$ ls <build>/CMakeBuild/libpython/Release
ls: cannot access ...: No such file or directory

The offending line was introduced by 5d016e6 ("test_testcapi: Fix build ensuring python3.lib is found", 2025-05-27) and is still present on master (1b1862a).

Reproducer

Any WIN32 build with a single-config generator and extension_testcapi enabled:

cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DPYTHON_VERSION=3.12.10 <src>
cmake --build .

Encountered with Python 3.12.10, MSVC 19.38 (VS 2022 17.8), CMake 4.4.2, Ninja.

Suggested fix

Take the directory from the target rather than reconstructing it, which is correct for both generator classes:

target_link_directories(extension_testcapi PRIVATE $<TARGET_LINKER_FILE_DIR:libpython3-shared>)

libpython3-shared is already a hard dependency of extension_testcapi on the following line, so it is guaranteed to exist at that point.

PR follows.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions