From 0020f69de91f41b2a546d389467131d001d8878d Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Fri, 18 Aug 2023 00:09:11 +0100 Subject: [PATCH] gh-83180: Made launcher treat shebang 'python' tags as low priority so that active virtual environments are preferred --- Doc/using/windows.rst | 20 +++++++---- ...3-08-18-00-01-21.gh-issue-83180.DdLffv.rst | 3 ++ PC/launcher2.c | 36 ++++++++++++++++--- 3 files changed, 49 insertions(+), 10 deletions(-) create mode 100644 Misc/NEWS.d/next/Windows/2023-08-18-00-01-21.gh-issue-83180.DdLffv.rst diff --git a/Doc/using/windows.rst b/Doc/using/windows.rst index ca79c9d3a9d3a8..9e98e377f65b63 100644 --- a/Doc/using/windows.rst +++ b/Doc/using/windows.rst @@ -867,17 +867,18 @@ For example, if the first line of your script starts with #! /usr/bin/python -The default Python will be located and used. As many Python scripts written -to work on Unix will already have this line, you should find these scripts can -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``. +The default Python or an active virtual environment will be located and used. +As many Python scripts written to work on Unix will already have this line, +you should find these scripts can 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). Furthermore the 32-bit version can be requested by adding "-32" after the minor version. I.e. ``/usr/bin/python3.7-32`` will request usage of the -32-bit python 3.7. +32-bit Python 3.7. If a virtual environment is active, the version will be +ignored and the environment will be used. .. versionadded:: 3.7 @@ -891,6 +892,13 @@ minor version. I.e. ``/usr/bin/python3.7-32`` will request usage of the not provably i386/32-bit". To request a specific environment, use the new ``-V:`` argument with the complete tag. +.. versionchanged:: 3.13 + + Virtual commands referencing ``python`` now prefer an active virtual + environment rather than searching :envvar:`PATH`. This handles cases where + the shebang specifies ``/usr/bin/env python3`` but :file:`python3.exe` is + not present in the active environment. + 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 matching the name provided diff --git a/Misc/NEWS.d/next/Windows/2023-08-18-00-01-21.gh-issue-83180.DdLffv.rst b/Misc/NEWS.d/next/Windows/2023-08-18-00-01-21.gh-issue-83180.DdLffv.rst new file mode 100644 index 00000000000000..1e59765a7674b1 --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2023-08-18-00-01-21.gh-issue-83180.DdLffv.rst @@ -0,0 +1,3 @@ +Changes the :ref:`launcher` to prefer an active virtual environment when the +launched script has a shebang line using a Unix-like virtual command, even +if the command requests a specific version of Python. diff --git a/PC/launcher2.c b/PC/launcher2.c index bb500d4b6bfb07..a34578c08e078a 100644 --- a/PC/launcher2.c +++ b/PC/launcher2.c @@ -195,6 +195,13 @@ join(wchar_t *buffer, size_t bufferLength, const wchar_t *fragment) } +bool +split_parent(wchar_t *buffer, size_t bufferLength) +{ + return SUCCEEDED(PathCchRemoveFileSpec(buffer, bufferLength)); +} + + int _compare(const wchar_t *x, int xLen, const wchar_t *y, int yLen) { @@ -414,8 +421,8 @@ typedef struct { // if true, treats 'tag' as a non-PEP 514 filter bool oldStyleTag; // if true, ignores 'tag' when a high priority environment is found - // gh-92817: This is currently set when a tag is read from configuration or - // the environment, rather than the command line or a shebang line, and the + // gh-92817: This is currently set when a tag is read from configuration, + // the environment, or a shebang, rather than the command line, and the // only currently possible high priority environment is an active virtual // environment bool lowPriorityTag; @@ -794,6 +801,8 @@ searchPath(SearchInfo *search, const wchar_t *shebang, int shebangLength) } } + debug(L"# Search PATH for %s\n", filename); + wchar_t pathVariable[MAXLEN]; int n = GetEnvironmentVariableW(L"PATH", pathVariable, MAXLEN); if (!n) { @@ -1031,8 +1040,12 @@ checkShebang(SearchInfo *search) debug(L"Shebang: %s\n", shebang); // Handle shebangs that we should search PATH for + int executablePathWasSetByUsrBinEnv = 0; exitCode = searchPath(search, shebang, shebangLength); - if (exitCode != RC_NO_SHEBANG) { + if (exitCode == 0) { + // We might need to clear executable path later if we match a template + executablePathWasSetByUsrBinEnv = 1; + } else if (exitCode != RC_NO_SHEBANG) { return exitCode; } @@ -1082,6 +1095,13 @@ checkShebang(SearchInfo *search) } } search->oldStyleTag = true; + search->lowPriorityTag = true; + if (executablePathWasSetByUsrBinEnv) { + // If it was allocated, it's in a free list, so just clear it + debug(L"# Shebang template made us forget executablePath %s\n", + search->executablePath); + search->executablePath = NULL; + } search->executableArgs = &command[commandLength]; search->executableArgsLength = shebangLength - commandLength; if (search->tag && search->tagLength) { @@ -1765,7 +1785,15 @@ virtualenvSearch(const SearchInfo *search, EnvironmentInfo **result) return 0; } - if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW(buffer)) { + DWORD attr = GetFileAttributesW(buffer); + if (INVALID_FILE_ATTRIBUTES == attr && search->lowPriorityTag) { + if (!split_parent(buffer, MAXLEN) || !join(buffer, MAXLEN, L"python.exe")) { + return 0; + } + attr = GetFileAttributesW(buffer); + } + + if (INVALID_FILE_ATTRIBUTES == attr) { debug(L"Python executable %s missing from virtual env\n", buffer); return 0; }