diff options
-rw-r--r-- | Lib/sysconfig/__init__.py | 11 | ||||
-rw-r--r-- | Lib/test/test_venv.py | 23 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Windows/2023-12-12-20-58-09.gh-issue-86179.YYSk_6.rst | 1 | ||||
-rw-r--r-- | Modules/getpath.c | 39 |
4 files changed, 63 insertions, 11 deletions
diff --git a/Lib/sysconfig/__init__.py b/Lib/sysconfig/__init__.py index c60c9f3..deb438c 100644 --- a/Lib/sysconfig/__init__.py +++ b/Lib/sysconfig/__init__.py @@ -404,16 +404,7 @@ def get_config_h_filename(): """Return the path of pyconfig.h.""" if _PYTHON_BUILD: if os.name == "nt": - # This ought to be as simple as dirname(sys._base_executable), but - # if a venv uses symlinks to a build in the source tree, then this - # fails. So instead we guess the subdirectory name from sys.winver - if sys.winver.endswith('-32'): - arch = 'win32' - elif sys.winver.endswith('-arm64'): - arch = 'arm64' - else: - arch = 'amd64' - inc_dir = os.path.join(_PROJECT_BASE, 'PCbuild', arch) + inc_dir = os.path.dirname(sys._base_executable) else: inc_dir = _PROJECT_BASE else: diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py index 617d14d..8ecb23f 100644 --- a/Lib/test/test_venv.py +++ b/Lib/test/test_venv.py @@ -46,7 +46,8 @@ if is_emscripten or is_wasi: def check_output(cmd, encoding=None): p = subprocess.Popen(cmd, stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + stderr=subprocess.PIPE, + env={**os.environ, "PYTHONHOME": ""}) out, err = p.communicate() if p.returncode: if verbose and err: @@ -287,6 +288,16 @@ class BasicTest(BaseTest): cmd[2] = 'import sysconfig; print(sysconfig.%s)' % call out, err = check_output(cmd, encoding='utf-8') self.assertEqual(out.strip(), expected, err) + for attr, expected in ( + ('executable', self.envpy()), + # Usually compare to sys.executable, but if we're running in our own + # venv then we really need to compare to our base executable + ('_base_executable', sys._base_executable), + ): + with self.subTest(attr): + cmd[2] = f'import sys; print(sys.{attr})' + out, err = check_output(cmd, encoding='utf-8') + self.assertEqual(out.strip(), expected, err) @requireVenvCreate @unittest.skipUnless(can_symlink(), 'Needs symlinks') @@ -309,6 +320,16 @@ class BasicTest(BaseTest): cmd[2] = 'import sysconfig; print(sysconfig.%s)' % call out, err = check_output(cmd, encoding='utf-8') self.assertEqual(out.strip(), expected, err) + for attr, expected in ( + ('executable', self.envpy()), + # Usually compare to sys.executable, but if we're running in our own + # venv then we really need to compare to our base executable + ('_base_executable', sys._base_executable), + ): + with self.subTest(attr): + cmd[2] = f'import sys; print(sys.{attr})' + out, err = check_output(cmd, encoding='utf-8') + self.assertEqual(out.strip(), expected, err) if sys.platform == 'win32': ENV_SUBDIRS = ( diff --git a/Misc/NEWS.d/next/Windows/2023-12-12-20-58-09.gh-issue-86179.YYSk_6.rst b/Misc/NEWS.d/next/Windows/2023-12-12-20-58-09.gh-issue-86179.YYSk_6.rst new file mode 100644 index 0000000..c1d9679 --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2023-12-12-20-58-09.gh-issue-86179.YYSk_6.rst @@ -0,0 +1 @@ +Fixes path calculations when launching Python on Windows through a symlink. diff --git a/Modules/getpath.c b/Modules/getpath.c index 6c1078b..422056b 100644 --- a/Modules/getpath.c +++ b/Modules/getpath.c @@ -502,6 +502,45 @@ done: PyMem_Free((void *)path); PyMem_Free((void *)narrow); return r; +#elif defined(MS_WINDOWS) + HANDLE hFile; + wchar_t resolved[MAXPATHLEN+1]; + int len = 0, err; + PyObject *result; + + wchar_t *path = PyUnicode_AsWideCharString(pathobj, NULL); + if (!path) { + return NULL; + } + + Py_BEGIN_ALLOW_THREADS + hFile = CreateFileW(path, 0, 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + if (hFile != INVALID_HANDLE_VALUE) { + len = GetFinalPathNameByHandleW(hFile, resolved, MAXPATHLEN, VOLUME_NAME_DOS); + err = len ? 0 : GetLastError(); + CloseHandle(hFile); + } else { + err = GetLastError(); + } + Py_END_ALLOW_THREADS + + if (err) { + PyErr_SetFromWindowsErr(err); + result = NULL; + } else if (len <= MAXPATHLEN) { + const wchar_t *p = resolved; + if (0 == wcsncmp(p, L"\\\\?\\", 4)) { + if (GetFileAttributesW(&p[4]) != INVALID_FILE_ATTRIBUTES) { + p += 4; + len -= 4; + } + } + result = PyUnicode_FromWideChar(p, len); + } else { + result = Py_NewRef(pathobj); + } + PyMem_Free(path); + return result; #endif return Py_NewRef(pathobj); |