mirror of https://github.com/python/cpython
gh-83180: Made launcher treat shebang 'python' tags as low priority so that active virtual environments are preferred (GH-108101)
This commit is contained in:
parent
6139bf5e0c
commit
1b3bc610fd
|
@ -867,17 +867,18 @@ For example, if the first line of your script starts with
|
||||||
|
|
||||||
#! /usr/bin/python
|
#! /usr/bin/python
|
||||||
|
|
||||||
The default Python will be located and used. As many Python scripts written
|
The default Python or an active virtual environment will be located and used.
|
||||||
to work on Unix will already have this line, you should find these scripts can
|
As many Python scripts written to work on Unix will already have this line,
|
||||||
be used by the launcher without modification. If you are writing a new script
|
you should find these scripts can be used by the launcher without modification.
|
||||||
on Windows which you hope will be useful on Unix, you should use one of the
|
If you are writing a new script on Windows which you hope will be useful on
|
||||||
shebang lines starting with ``/usr``.
|
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
|
Any of the above virtual commands can be suffixed with an explicit version
|
||||||
(either just the major version, or the major and minor 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
|
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
|
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
|
.. 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
|
not provably i386/32-bit". To request a specific environment, use the new
|
||||||
:samp:`-V:{TAG}` argument with the complete tag.
|
:samp:`-V:{TAG}` 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.
|
The ``/usr/bin/env`` form of shebang line has one further special property.
|
||||||
Before looking for installed Python interpreters, this form will search the
|
Before looking for installed Python interpreters, this form will search the
|
||||||
executable :envvar:`PATH` for a Python executable matching the name provided
|
executable :envvar:`PATH` for a Python executable matching the name provided
|
||||||
|
|
|
@ -717,3 +717,25 @@ class TestLauncher(unittest.TestCase, RunPyMixin):
|
||||||
f"{expect} arg1 {script}",
|
f"{expect} arg1 {script}",
|
||||||
data["stdout"].strip(),
|
data["stdout"].strip(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_shebang_command_in_venv(self):
|
||||||
|
stem = "python-that-is-not-on-path"
|
||||||
|
|
||||||
|
# First ensure that our test name doesn't exist, and the launcher does
|
||||||
|
# not match any installed env
|
||||||
|
with self.script(f'#! /usr/bin/env {stem} arg1') as script:
|
||||||
|
data = self.run_py([script], expect_returncode=103)
|
||||||
|
|
||||||
|
with self.fake_venv() as (venv_exe, env):
|
||||||
|
# Put a real Python (ourselves) on PATH as a distraction.
|
||||||
|
# The active VIRTUAL_ENV should be preferred when the name isn't an
|
||||||
|
# exact match.
|
||||||
|
env["PATH"] = f"{Path(sys.executable).parent};{os.environ['PATH']}"
|
||||||
|
|
||||||
|
with self.script(f'#! /usr/bin/env {stem} arg1') as script:
|
||||||
|
data = self.run_py([script], env=env)
|
||||||
|
self.assertEqual(data["stdout"].strip(), f"{venv_exe} arg1 {script}")
|
||||||
|
|
||||||
|
with self.script(f'#! /usr/bin/env {Path(sys.executable).stem} arg1') as script:
|
||||||
|
data = self.run_py([script], env=env)
|
||||||
|
self.assertEqual(data["stdout"].strip(), f"{sys.executable} arg1 {script}")
|
||||||
|
|
|
@ -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.
|
|
@ -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
|
int
|
||||||
_compare(const wchar_t *x, int xLen, const wchar_t *y, int yLen)
|
_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
|
// if true, treats 'tag' as a non-PEP 514 filter
|
||||||
bool oldStyleTag;
|
bool oldStyleTag;
|
||||||
// if true, ignores 'tag' when a high priority environment is found
|
// 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
|
// gh-92817: This is currently set when a tag is read from configuration,
|
||||||
// the environment, rather than the command line or a shebang line, and the
|
// the environment, or a shebang, rather than the command line, and the
|
||||||
// only currently possible high priority environment is an active virtual
|
// only currently possible high priority environment is an active virtual
|
||||||
// environment
|
// environment
|
||||||
bool lowPriorityTag;
|
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];
|
wchar_t pathVariable[MAXLEN];
|
||||||
int n = GetEnvironmentVariableW(L"PATH", pathVariable, MAXLEN);
|
int n = GetEnvironmentVariableW(L"PATH", pathVariable, MAXLEN);
|
||||||
if (!n) {
|
if (!n) {
|
||||||
|
@ -1031,8 +1040,11 @@ checkShebang(SearchInfo *search)
|
||||||
debug(L"Shebang: %s\n", shebang);
|
debug(L"Shebang: %s\n", shebang);
|
||||||
|
|
||||||
// Handle shebangs that we should search PATH for
|
// Handle shebangs that we should search PATH for
|
||||||
|
int executablePathWasSetByUsrBinEnv = 0;
|
||||||
exitCode = searchPath(search, shebang, shebangLength);
|
exitCode = searchPath(search, shebang, shebangLength);
|
||||||
if (exitCode != RC_NO_SHEBANG) {
|
if (exitCode == 0) {
|
||||||
|
executablePathWasSetByUsrBinEnv = 1;
|
||||||
|
} else if (exitCode != RC_NO_SHEBANG) {
|
||||||
return exitCode;
|
return exitCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1067,7 +1079,7 @@ checkShebang(SearchInfo *search)
|
||||||
search->tagLength = commandLength;
|
search->tagLength = commandLength;
|
||||||
// If we had 'python3.12.exe' then we want to strip the suffix
|
// If we had 'python3.12.exe' then we want to strip the suffix
|
||||||
// off of the tag
|
// off of the tag
|
||||||
if (search->tagLength > 4) {
|
if (search->tagLength >= 4) {
|
||||||
const wchar_t *suffix = &search->tag[search->tagLength - 4];
|
const wchar_t *suffix = &search->tag[search->tagLength - 4];
|
||||||
if (0 == _comparePath(suffix, 4, L".exe", -1)) {
|
if (0 == _comparePath(suffix, 4, L".exe", -1)) {
|
||||||
search->tagLength -= 4;
|
search->tagLength -= 4;
|
||||||
|
@ -1075,13 +1087,14 @@ checkShebang(SearchInfo *search)
|
||||||
}
|
}
|
||||||
// If we had 'python3_d' then we want to strip the '_d' (any
|
// If we had 'python3_d' then we want to strip the '_d' (any
|
||||||
// '.exe' is already gone)
|
// '.exe' is already gone)
|
||||||
if (search->tagLength > 2) {
|
if (search->tagLength >= 2) {
|
||||||
const wchar_t *suffix = &search->tag[search->tagLength - 2];
|
const wchar_t *suffix = &search->tag[search->tagLength - 2];
|
||||||
if (0 == _comparePath(suffix, 2, L"_d", -1)) {
|
if (0 == _comparePath(suffix, 2, L"_d", -1)) {
|
||||||
search->tagLength -= 2;
|
search->tagLength -= 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
search->oldStyleTag = true;
|
search->oldStyleTag = true;
|
||||||
|
search->lowPriorityTag = true;
|
||||||
search->executableArgs = &command[commandLength];
|
search->executableArgs = &command[commandLength];
|
||||||
search->executableArgsLength = shebangLength - commandLength;
|
search->executableArgsLength = shebangLength - commandLength;
|
||||||
if (search->tag && search->tagLength) {
|
if (search->tag && search->tagLength) {
|
||||||
|
@ -1095,6 +1108,11 @@ checkShebang(SearchInfo *search)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Didn't match a template, but we found it on PATH
|
||||||
|
if (executablePathWasSetByUsrBinEnv) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Unrecognised executables are first tried as command aliases
|
// Unrecognised executables are first tried as command aliases
|
||||||
commandLength = 0;
|
commandLength = 0;
|
||||||
while (commandLength < shebangLength && !isspace(shebang[commandLength])) {
|
while (commandLength < shebangLength && !isspace(shebang[commandLength])) {
|
||||||
|
@ -1765,7 +1783,15 @@ virtualenvSearch(const SearchInfo *search, EnvironmentInfo **result)
|
||||||
return 0;
|
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);
|
debug(L"Python executable %s missing from virtual env\n", buffer);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue