]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-86179: Implement realpath() on Windows for getpath.py calculations (GH-113033)
authorSteve Dower <steve.dower@python.org>
Wed, 13 Dec 2023 23:41:43 +0000 (23:41 +0000)
committerGitHub <noreply@github.com>
Wed, 13 Dec 2023 23:41:43 +0000 (23:41 +0000)
Lib/sysconfig/__init__.py
Lib/test/test_venv.py
Misc/NEWS.d/next/Windows/2023-12-12-20-58-09.gh-issue-86179.YYSk_6.rst [new file with mode: 0644]
Modules/getpath.c

index c60c9f3440615becce1f1f86f8f54db5e84952a1..deb438c705f3a0368638041103857f6d2cabd948 100644 (file)
@@ -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:
index 617d14dcb9c5fe674a6595c97cb72b3d55d99723..8ecb23ff3843629717aa9a2b98aa5d99fb9edea1 100644 (file)
@@ -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 (file)
index 0000000..c1d9679
--- /dev/null
@@ -0,0 +1 @@
+Fixes path calculations when launching Python on Windows through a symlink.
index 6c1078b89145229e1c1b622d7c5677c962946422..422056b1fb6de402f9679370de13c8c7c86979be 100644 (file)
@@ -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);