diff --git a/docs/changelog/3151.bugfix.rst b/docs/changelog/3151.bugfix.rst new file mode 100644 index 000000000..8d6ca5b53 --- /dev/null +++ b/docs/changelog/3151.bugfix.rst @@ -0,0 +1,3 @@ +Fix Windows debug build ``venvlauncher_d.exe`` substitution never triggering because ``executables()`` compared the +source executable name instead of the target name, and fix ``AttributeError`` on ``debug_build`` attribute for +interpreter info objects missing the field - by :user:`gaborbernat`. diff --git a/docs/explanation.rst b/docs/explanation.rst index a9017a17f..35beca9d7 100644 --- a/docs/explanation.rst +++ b/docs/explanation.rst @@ -97,27 +97,23 @@ it can discover on the system, provided a matching creator exists. .. list-table:: :header-rows: 1 - :widths: 20 15 15 50 + :widths: 20 15 65 - - Implementation - Platforms - - Free-threaded - Notes - - CPython - Linux, macOS, Windows - - 3.13+ - - Full support including macOS framework builds, Homebrew, Microsoft Store, and Windows debug builds. + - 3.8+. Free-threaded builds supported on 3.13+. Includes macOS framework, Homebrew, Microsoft Store, and Windows + debug build support. - - PyPy - Linux, macOS, Windows - - No - - PyPy 3.8+. + - 3.8+. - - GraalPy - Linux, macOS, Windows - - No - - GraalPy 24.1+. Minimal test coverage, marked experimental. + - 24.1+. Minimal test coverage, marked experimental. - - RustPython - Linux, macOS, Windows - - No - Minimal test coverage, marked experimental. Seed packages (``pip``, ``setuptools``) are bundled for CPython 3.8 through 3.16. Target interpreters outside this range diff --git a/src/virtualenv/create/via_global_ref/builtin/cpython/cpython3.py b/src/virtualenv/create/via_global_ref/builtin/cpython/cpython3.py index ca1efc8f1..ccb7f8d27 100644 --- a/src/virtualenv/create/via_global_ref/builtin/cpython/cpython3.py +++ b/src/virtualenv/create/via_global_ref/builtin/cpython/cpython3.py @@ -106,18 +106,19 @@ def sources(cls, interpreter: PythonInfo) -> Generator[PathRef]: # ty: ignore[i @classmethod def _debug_suffix(cls, interpreter: PythonInfo) -> str: - return "_d" if interpreter.debug_build else "" + return "_d" if getattr(interpreter, "debug_build", False) else "" @classmethod def executables(cls, interpreter: PythonInfo) -> list[PathRef] | Generator[PathRef]: sources = super().sources(interpreter) if interpreter.version_info >= (3, 13): + host = cls.host_python(interpreter) t_suffix = "t" if interpreter.free_threaded else "" d_suffix = cls._debug_suffix(interpreter) updated_sources: list[PathRef] = [] for ref in sources: - if ref.src.name == "python.exe": - launcher_path = ref.src.with_name(f"venvlauncher{t_suffix}{d_suffix}.exe") + if ref.base == "python.exe": + launcher_path = host.with_name(f"venvlauncher{t_suffix}{d_suffix}.exe") if launcher_path.exists(): new_ref = ExePathRefToDest( launcher_path, dest=ref.dest, targets=[ref.base, *ref.aliases], must=ref.must, when=ref.when diff --git a/tests/unit/create/via_global_ref/builtin/cpython/cpython3_win_debug.json b/tests/unit/create/via_global_ref/builtin/cpython/cpython3_win_debug.json new file mode 100644 index 000000000..7143b21f5 --- /dev/null +++ b/tests/unit/create/via_global_ref/builtin/cpython/cpython3_win_debug.json @@ -0,0 +1,63 @@ +{ + "platform": "win32", + "implementation": "CPython", + "version_info": { + "major": 3, + "minor": 13, + "micro": 0, + "releaselevel": "final", + "serial": 0 + }, + "architecture": 64, + "version_nodot": "313", + "version": "3.13.0 (main, Oct 07 2024, 00:00:00) [MSC v.1941 64 bit (AMD64)]", + "os": "nt", + "prefix": "c:\\path\\to\\python", + "base_prefix": "c:\\path\\to\\python", + "real_prefix": null, + "base_exec_prefix": "c:\\path\\to\\python", + "exec_prefix": "c:\\path\\to\\python", + "executable": "c:\\path\\to\\python\\python_d.exe", + "original_executable": "c:\\path\\to\\python\\python_d.exe", + "system_executable": "c:\\path\\to\\python\\python_d.exe", + "has_venv": false, + "path": [ + "c:\\path\\to\\python\\Scripts\\virtualenv.exe", + "c:\\path\\to\\python\\python313.zip", + "c:\\path\\to\\python", + "c:\\path\\to\\python\\Lib\\site-packages" + ], + "file_system_encoding": "utf-8", + "stdout_encoding": "utf-8", + "sysconfig_scheme": null, + "sysconfig_paths": { + "stdlib": "{installed_base}/Lib", + "platstdlib": "{base}/Lib", + "purelib": "{base}/Lib/site-packages", + "platlib": "{base}/Lib/site-packages", + "include": "{installed_base}/Include", + "scripts": "{base}/Scripts", + "data": "{base}" + }, + "distutils_install": { + "purelib": "Lib\\site-packages", + "platlib": "Lib\\site-packages", + "headers": "Include\\UNKNOWN", + "scripts": "Scripts", + "data": "" + }, + "sysconfig": { + "makefile_filename": "c:\\path\\to\\python\\Lib\\config\\Makefile" + }, + "sysconfig_vars": { + "PYTHONFRAMEWORK": "", + "installed_base": "c:\\path\\to\\python", + "base": "c:\\path\\to\\python" + }, + "system_stdlib": "c:\\path\\to\\python\\Lib", + "system_stdlib_platform": "c:\\path\\to\\python\\Lib", + "max_size": 9223372036854775807, + "_creators": null, + "free_threaded": false, + "debug_build": true +} diff --git a/tests/unit/create/via_global_ref/builtin/cpython/cpython3_win_embed.json b/tests/unit/create/via_global_ref/builtin/cpython/cpython3_win_embed.json index c75c6f4fc..515f7b7a0 100644 --- a/tests/unit/create/via_global_ref/builtin/cpython/cpython3_win_embed.json +++ b/tests/unit/create/via_global_ref/builtin/cpython/cpython3_win_embed.json @@ -58,5 +58,6 @@ "system_stdlib_platform": "c:\\path\\to\\python\\Lib", "max_size": 9223372036854775807, "_creators": null, - "free_threaded": false + "free_threaded": false, + "debug_build": false } diff --git a/tests/unit/create/via_global_ref/builtin/cpython/cpython3_win_free_threaded.json b/tests/unit/create/via_global_ref/builtin/cpython/cpython3_win_free_threaded.json index f39a6059c..44130a075 100644 --- a/tests/unit/create/via_global_ref/builtin/cpython/cpython3_win_free_threaded.json +++ b/tests/unit/create/via_global_ref/builtin/cpython/cpython3_win_free_threaded.json @@ -58,5 +58,6 @@ "system_stdlib_platform": "c:\\path\\to\\python\\Lib", "max_size": 9223372036854775807, "_creators": null, - "free_threaded": true + "free_threaded": true, + "debug_build": false } diff --git a/tests/unit/create/via_global_ref/builtin/cpython/test_cpython3_win.py b/tests/unit/create/via_global_ref/builtin/cpython/test_cpython3_win.py index 94e02bfb9..f8b4c091f 100644 --- a/tests/unit/create/via_global_ref/builtin/cpython/test_cpython3_win.py +++ b/tests/unit/create/via_global_ref/builtin/cpython/test_cpython3_win.py @@ -129,6 +129,25 @@ def test_free_threaded_exe_naming(py_info, mock_files) -> None: assert "pythonw3.13t.exe" in pythonw_refs[0].aliases +@pytest.mark.parametrize("py_info_name", ["cpython3_win_debug"]) +def test_debug_build_shim(py_info, mock_files) -> None: + shim = path(py_info.system_stdlib, "venv\\scripts\\nt\\venvlauncher_d.exe") + mock_files(CPYTHON3_PATH, [shim]) + assert CPython3Windows.has_shim(interpreter=py_info) + sources = tuple(CPython3Windows.sources(interpreter=py_info)) + assert contains_exe(sources, shim) + + +@pytest.mark.parametrize("py_info_name", ["cpython3_win_debug"]) +def test_debug_build_uses_venvlauncher(py_info, mock_files) -> None: + launcher = path(py_info.prefix, "venvlauncher_d.exe") + w_launcher = path(py_info.prefix, "venvwlauncher_d.exe") + mock_files(CPYTHON3_PATH, [py_info.system_executable, launcher, w_launcher]) + sources = tuple(CPython3Windows.sources(interpreter=py_info)) + assert contains_exe(sources, launcher, "python.exe") + assert contains_exe(sources, w_launcher, "pythonw.exe") + + @pytest.mark.parametrize("py_info_name", ["cpython3_win_embed"]) def test_pywin32_dll_exclusion(py_info, mock_files) -> None: """Test that pywin32 DLLs are excluded from virtualenv creation."""