From fd6ea21c84361c2a8e13d7cf5f301d60d7beeee9 Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Wed, 29 May 2024 18:51:13 +0100 Subject: [PATCH 1/3] gh-119070: Fix py.exe handling of /usr/bin/env commands missing extension (GH-119426) --- Lib/test/test_launcher.py | 33 +++++++++++++++++++ ...-05-22-19-43-29.gh-issue-119070._enton.rst | 3 ++ PC/launcher2.c | 2 +- 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Windows/2024-05-22-19-43-29.gh-issue-119070._enton.rst diff --git a/Lib/test/test_launcher.py b/Lib/test/test_launcher.py index 362b507d158288..86645035bede3a 100644 --- a/Lib/test/test_launcher.py +++ b/Lib/test/test_launcher.py @@ -717,3 +717,36 @@ def test_literal_shebang_invalid_template(self): f"{expect} arg1 {script}", 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 "normal" Python on PATH as a distraction. + # The active VIRTUAL_ENV should be preferred when the name isn't an + # exact match. + exe = Path(Path(venv_exe).name).absolute() + exe.touch() + self.addCleanup(exe.unlink) + env["PATH"] = f"{exe.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"{quote(venv_exe)} arg1 {quote(script)}") + + with self.script(f'#! /usr/bin/env {exe.stem} arg1') as script: + data = self.run_py([script], env=env) + self.assertEqual(data["stdout"].strip(), f"{quote(exe)} arg1 {quote(script)}") + + def test_shebang_executable_extension(self): + with self.script('#! /usr/bin/env python3.12') as script: + data = self.run_py([script]) + expect = "# Search PATH for python3.12.exe" + actual = [line.strip() for line in data["stderr"].splitlines() + if line.startswith("# Search PATH")] + self.assertEqual([expect], actual) diff --git a/Misc/NEWS.d/next/Windows/2024-05-22-19-43-29.gh-issue-119070._enton.rst b/Misc/NEWS.d/next/Windows/2024-05-22-19-43-29.gh-issue-119070._enton.rst new file mode 100644 index 00000000000000..aab26f57209864 --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2024-05-22-19-43-29.gh-issue-119070._enton.rst @@ -0,0 +1,3 @@ +Fixes ``py.exe`` handling of shebangs like ``/usr/bin/env python3.12``, +which were previously interpreted as ``python3.exe`` instead of +``python3.12.exe``. diff --git a/PC/launcher2.c b/PC/launcher2.c index c38cbc83a7ac0f..049571041e23c6 100644 --- a/PC/launcher2.c +++ b/PC/launcher2.c @@ -846,7 +846,7 @@ searchPath(SearchInfo *search, const wchar_t *shebang, int shebangLength) } wchar_t filename[MAXLEN]; - if (wcsncpy_s(filename, MAXLEN, command, lastDot)) { + if (wcsncpy_s(filename, MAXLEN, command, commandLength)) { return RC_BAD_VIRTUAL_PATH; } From 538f886c5900069ecb8788d903fa2e766ba0fe37 Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Wed, 29 May 2024 19:23:47 +0100 Subject: [PATCH 2/3] Add debug message used in test --- PC/launcher2.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/PC/launcher2.c b/PC/launcher2.c index 049571041e23c6..f331aab3f51e56 100644 --- a/PC/launcher2.c +++ b/PC/launcher2.c @@ -858,6 +858,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) { From 168f8456d74c2d63d98e52ac301c62b59d1d77e9 Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Wed, 29 May 2024 19:54:59 +0100 Subject: [PATCH 3/3] Remove backported test --- Lib/test/test_launcher.py | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/Lib/test/test_launcher.py b/Lib/test/test_launcher.py index 86645035bede3a..7dd0b67ea0b3dc 100644 --- a/Lib/test/test_launcher.py +++ b/Lib/test/test_launcher.py @@ -718,31 +718,6 @@ def test_literal_shebang_invalid_template(self): 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 "normal" Python on PATH as a distraction. - # The active VIRTUAL_ENV should be preferred when the name isn't an - # exact match. - exe = Path(Path(venv_exe).name).absolute() - exe.touch() - self.addCleanup(exe.unlink) - env["PATH"] = f"{exe.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"{quote(venv_exe)} arg1 {quote(script)}") - - with self.script(f'#! /usr/bin/env {exe.stem} arg1') as script: - data = self.run_py([script], env=env) - self.assertEqual(data["stdout"].strip(), f"{quote(exe)} arg1 {quote(script)}") - def test_shebang_executable_extension(self): with self.script('#! /usr/bin/env python3.12') as script: data = self.run_py([script])