diff --git a/Doc/using/windows.rst b/Doc/using/windows.rst index af661969d04..f0f24e8150f 100644 --- a/Doc/using/windows.rst +++ b/Doc/using/windows.rst @@ -404,6 +404,16 @@ If you see the following error, you do not have the launcher installed: Per-user installations of Python do not add the launcher to :envvar:`PATH` unless the option was selected on installation. +Virtual environments +^^^^^^^^^^^^^^^^^^^^ + +If the launcher is run with no explicit Python version specification, and a +virtual environment (created with the standard library :mod:`venv` module or +the external ``virtualenv`` tool) active, the launcher will run the virtual +environment's interpreter rather than the global one. To run the global +interpreter, either deactivate the virtual environment, or explicitly specify +the global Python version. + From a script ^^^^^^^^^^^^^ @@ -478,6 +488,16 @@ be used by the launcher without modification. If you are writing a new script on Windows which you hope will be useful on Unix, you should use one of the shebang lines starting with ``/usr``. +Any of the above virtual commands can be suffixed with an explicit version +(either just the major version, or the major and minor version) - for example +``/usr/bin/python2.7`` - which will cause that specific version to be located +and used. + +The ``/usr/bin/env`` form of shebang line has one further special property. +Before looking for installed Python interpreters, this form will search the +executable :envvar:`PATH` for a Python executable. This corresponds to the +behaviour of the Unix ``env`` program, which performs a :envvar:`PATH` search. + Arguments in shebang lines -------------------------- diff --git a/Doc/whatsnew/3.5.rst b/Doc/whatsnew/3.5.rst index 7c4e530c247..2a777293964 100644 --- a/Doc/whatsnew/3.5.rst +++ b/Doc/whatsnew/3.5.rst @@ -123,6 +123,18 @@ manually, and should make it more robust against asynchronous signal reception. :pep:`475` -- Retry system calls failing with EINTR +PEP 486: Make the Python Launcher aware of virtual environments +--------------------------------------------------------------- + +:pep:`486` makes the Windows launcher (see :pep:`397`) aware of an active +virtual environment. When the default interpreter would be used and the +``VIRTUAL_ENV`` environment variable is set, the interpreter in the virtual +environment will be used. + +.. seealso:: + + :pep:`486` -- Make the Python Launcher aware of virtual environments + Other Language Changes ====================== diff --git a/Misc/NEWS b/Misc/NEWS index 796e9bf7e5f..17641ceeab8 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -77,6 +77,9 @@ Build Windows ------- +- Issue #23465: Implement PEP 486 - Make the Python Launcher aware of virtual + environments. Patch by Paul Moore. + - Issue #23437: Make user scripts directory versioned on Windows. Patch by Paul Moore. diff --git a/PC/launcher.c b/PC/launcher.c index d450d9f71e1..33dd5dab5d6 100644 --- a/PC/launcher.c +++ b/PC/launcher.c @@ -384,6 +384,31 @@ find_python_by_version(wchar_t const * wanted_ver) } +static wchar_t * +find_python_by_venv() +{ + static wchar_t venv_python[MAX_PATH]; + wchar_t *virtual_env = get_env(L"VIRTUAL_ENV"); + DWORD attrs; + + /* Check for VIRTUAL_ENV environment variable */ + if (virtual_env == NULL || virtual_env[0] == L'\0') { + return NULL; + } + + /* Check for a python executable in the venv */ + debug(L"Checking for Python executable in virtual env '%ls'\n", virtual_env); + _snwprintf_s(venv_python, MAX_PATH, _TRUNCATE, + L"%ls\\Scripts\\%ls", virtual_env, PYTHON_EXECUTABLE); + attrs = GetFileAttributesW(venv_python); + if (attrs == INVALID_FILE_ATTRIBUTES) { + debug(L"Python executable %ls missing from virtual env\n", venv_python); + return NULL; + } + + return venv_python; +} + static wchar_t appdata_ini_path[MAX_PATH]; static wchar_t launcher_ini_path[MAX_PATH]; @@ -1309,6 +1334,7 @@ process(int argc, wchar_t ** argv) { wchar_t * wp; wchar_t * command; + wchar_t * executable; wchar_t * p; int rc = 0; size_t plen; @@ -1453,6 +1479,7 @@ process(int argc, wchar_t ** argv) if (ip == NULL) error(RC_NO_PYTHON, L"Requested Python version (%ls) not \ installed", &p[1]); + executable = ip->executable; command += wcslen(p); command = skip_whitespace(command); } @@ -1470,9 +1497,16 @@ installed", &p[1]); #endif if (!valid) { - ip = locate_python(L""); - if (ip == NULL) - error(RC_NO_PYTHON, L"Can't find a default Python."); + /* Look for an active virtualenv */ + executable = find_python_by_venv(); + + /* If we didn't find one, look for the default Python */ + if (executable == NULL) { + ip = locate_python(L""); + if (ip == NULL) + error(RC_NO_PYTHON, L"Can't find a default Python."); + executable = ip->executable; + } if ((argc == 2) && (!_wcsicmp(p, L"-h") || !_wcsicmp(p, L"--help"))) { #if defined(_M_X64) BOOL canDo64bit = TRUE; @@ -1500,7 +1534,7 @@ Launcher arguments:\n\n\ fflush(stdout); } } - invoke_child(ip->executable, NULL, command); + invoke_child(executable, NULL, command); return rc; }