summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Lib/sysconfig/__init__.py11
-rw-r--r--Lib/test/test_venv.py23
-rw-r--r--Misc/NEWS.d/next/Windows/2023-12-12-20-58-09.gh-issue-86179.YYSk_6.rst1
-rw-r--r--Modules/getpath.c39
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);