Skip to content

Commit

Permalink
pythongh-86179: Implement realpath() on Windows for getpath.py calcul…
Browse files Browse the repository at this point in the history
…ations (pythonGH-113033)
  • Loading branch information
zooba authored and aisk committed Feb 11, 2024
1 parent 495a879 commit 1af21d5
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 11 deletions.
11 changes: 1 addition & 10 deletions Lib/sysconfig/__init__.py
Expand Up @@ -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:
Expand Down
23 changes: 22 additions & 1 deletion Lib/test/test_venv.py
Expand Up @@ -46,7 +46,8 @@
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:
Expand Down Expand Up @@ -287,6 +288,16 @@ def test_sysconfig(self):
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')
Expand All @@ -309,6 +320,16 @@ def test_sysconfig_symlinks(self):
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 = (
Expand Down
@@ -0,0 +1 @@
Fixes path calculations when launching Python on Windows through a symlink.
39 changes: 39 additions & 0 deletions Modules/getpath.c
Expand Up @@ -502,6 +502,45 @@ getpath_realpath(PyObject *Py_UNUSED(self) , PyObject *args)
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);
Expand Down

0 comments on commit 1af21d5

Please sign in to comment.