mirror of https://github.com/python/cpython
gh-90329: Add _winapi.GetLongPathName and GetShortPathName and use in venv to reduce warnings (GH-117817)
This commit is contained in:
parent
64cd6fc9a6
commit
185999bb3a
|
@ -23,7 +23,8 @@ from test.support import (captured_stdout, captured_stderr,
|
||||||
is_emscripten, is_wasi,
|
is_emscripten, is_wasi,
|
||||||
requires_venv_with_pip, TEST_HOME_DIR,
|
requires_venv_with_pip, TEST_HOME_DIR,
|
||||||
requires_resource, copy_python_src_ignore)
|
requires_resource, copy_python_src_ignore)
|
||||||
from test.support.os_helper import (can_symlink, EnvironmentVarGuard, rmtree)
|
from test.support.os_helper import (can_symlink, EnvironmentVarGuard, rmtree,
|
||||||
|
TESTFN)
|
||||||
import unittest
|
import unittest
|
||||||
import venv
|
import venv
|
||||||
from unittest.mock import patch, Mock
|
from unittest.mock import patch, Mock
|
||||||
|
@ -744,6 +745,36 @@ class BasicTest(BaseTest):
|
||||||
with self.assertRaises(FileNotFoundError):
|
with self.assertRaises(FileNotFoundError):
|
||||||
self.get_text_file_contents('.gitignore')
|
self.get_text_file_contents('.gitignore')
|
||||||
|
|
||||||
|
def test_venv_same_path(self):
|
||||||
|
same_path = venv.EnvBuilder._same_path
|
||||||
|
if sys.platform == 'win32':
|
||||||
|
# Case-insensitive, and handles short/long names
|
||||||
|
tests = [
|
||||||
|
(True, TESTFN, TESTFN),
|
||||||
|
(True, TESTFN.lower(), TESTFN.upper()),
|
||||||
|
]
|
||||||
|
import _winapi
|
||||||
|
# ProgramFiles is the most reliable path that will have short/long
|
||||||
|
progfiles = os.getenv('ProgramFiles')
|
||||||
|
if progfiles:
|
||||||
|
tests = [
|
||||||
|
*tests,
|
||||||
|
(True, progfiles, progfiles),
|
||||||
|
(True, _winapi.GetShortPathName(progfiles), _winapi.GetLongPathName(progfiles)),
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
# Just a simple case-sensitive comparison
|
||||||
|
tests = [
|
||||||
|
(True, TESTFN, TESTFN),
|
||||||
|
(False, TESTFN.lower(), TESTFN.upper()),
|
||||||
|
]
|
||||||
|
for r, path1, path2 in tests:
|
||||||
|
with self.subTest(f"{path1}-{path2}"):
|
||||||
|
if r:
|
||||||
|
self.assertTrue(same_path(path1, path2))
|
||||||
|
else:
|
||||||
|
self.assertFalse(same_path(path1, path2))
|
||||||
|
|
||||||
@requireVenvCreate
|
@requireVenvCreate
|
||||||
class EnsurePipTest(BaseTest):
|
class EnsurePipTest(BaseTest):
|
||||||
"""Test venv module installation of pip."""
|
"""Test venv module installation of pip."""
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
# Test the Windows-only _winapi module
|
# Test the Windows-only _winapi module
|
||||||
|
|
||||||
|
import os
|
||||||
|
import pathlib
|
||||||
import random
|
import random
|
||||||
|
import re
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import unittest
|
import unittest
|
||||||
|
@ -92,3 +95,35 @@ class WinAPIBatchedWaitForMultipleObjectsTests(unittest.TestCase):
|
||||||
|
|
||||||
def test_max_events_waitany(self):
|
def test_max_events_waitany(self):
|
||||||
self._events_waitany_test(MAXIMUM_BATCHED_WAIT_OBJECTS)
|
self._events_waitany_test(MAXIMUM_BATCHED_WAIT_OBJECTS)
|
||||||
|
|
||||||
|
|
||||||
|
class WinAPITests(unittest.TestCase):
|
||||||
|
def test_getlongpathname(self):
|
||||||
|
testfn = pathlib.Path(os.getenv("ProgramFiles")).parents[-1] / "PROGRA~1"
|
||||||
|
if not os.path.isdir(testfn):
|
||||||
|
raise unittest.SkipTest("require x:\\PROGRA~1 to test")
|
||||||
|
|
||||||
|
# pathlib.Path will be rejected - only str is accepted
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
_winapi.GetLongPathName(testfn)
|
||||||
|
|
||||||
|
actual = _winapi.GetLongPathName(os.fsdecode(testfn))
|
||||||
|
|
||||||
|
# Can't assume that PROGRA~1 expands to any particular variation, so
|
||||||
|
# ensure it matches any one of them.
|
||||||
|
candidates = set(testfn.parent.glob("Progra*"))
|
||||||
|
self.assertIn(pathlib.Path(actual), candidates)
|
||||||
|
|
||||||
|
def test_getshortpathname(self):
|
||||||
|
testfn = pathlib.Path(os.getenv("ProgramFiles"))
|
||||||
|
if not os.path.isdir(testfn):
|
||||||
|
raise unittest.SkipTest("require '%ProgramFiles%' to test")
|
||||||
|
|
||||||
|
# pathlib.Path will be rejected - only str is accepted
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
_winapi.GetShortPathName(testfn)
|
||||||
|
|
||||||
|
actual = _winapi.GetShortPathName(os.fsdecode(testfn))
|
||||||
|
|
||||||
|
# Should contain "PROGRA~" but we can't predict the number
|
||||||
|
self.assertIsNotNone(re.match(r".\:\\PROGRA~\d", actual.upper()), actual)
|
||||||
|
|
|
@ -107,6 +107,33 @@ class EnvBuilder:
|
||||||
}
|
}
|
||||||
return sysconfig.get_path(name, scheme='venv', vars=vars)
|
return sysconfig.get_path(name, scheme='venv', vars=vars)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _same_path(cls, path1, path2):
|
||||||
|
"""Check whether two paths appear the same.
|
||||||
|
|
||||||
|
Whether they refer to the same file is irrelevant; we're testing for
|
||||||
|
whether a human reader would look at the path string and easily tell
|
||||||
|
that they're the same file.
|
||||||
|
"""
|
||||||
|
if sys.platform == 'win32':
|
||||||
|
if os.path.normcase(path1) == os.path.normcase(path2):
|
||||||
|
return True
|
||||||
|
# gh-90329: Don't display a warning for short/long names
|
||||||
|
import _winapi
|
||||||
|
try:
|
||||||
|
path1 = _winapi.GetLongPathName(os.fsdecode(path1))
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
path2 = _winapi.GetLongPathName(os.fsdecode(path2))
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
if os.path.normcase(path1) == os.path.normcase(path2):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return path1 == path2
|
||||||
|
|
||||||
def ensure_directories(self, env_dir):
|
def ensure_directories(self, env_dir):
|
||||||
"""
|
"""
|
||||||
Create the directories for the environment.
|
Create the directories for the environment.
|
||||||
|
@ -171,7 +198,7 @@ class EnvBuilder:
|
||||||
# bpo-45337: Fix up env_exec_cmd to account for file system redirections.
|
# bpo-45337: Fix up env_exec_cmd to account for file system redirections.
|
||||||
# Some redirects only apply to CreateFile and not CreateProcess
|
# Some redirects only apply to CreateFile and not CreateProcess
|
||||||
real_env_exe = os.path.realpath(context.env_exe)
|
real_env_exe = os.path.realpath(context.env_exe)
|
||||||
if os.path.normcase(real_env_exe) != os.path.normcase(context.env_exe):
|
if not self._same_path(real_env_exe, context.env_exe):
|
||||||
logger.warning('Actual environment location may have moved due to '
|
logger.warning('Actual environment location may have moved due to '
|
||||||
'redirects, links or junctions.\n'
|
'redirects, links or junctions.\n'
|
||||||
' Requested location: "%s"\n'
|
' Requested location: "%s"\n'
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
Suppress the warning displayed on virtual environment creation when the
|
||||||
|
requested and created paths differ only by a short (8.3 style) name.
|
||||||
|
Warnings will continue to be shown if a junction or symlink in the path
|
||||||
|
caused the venv to be created in a different location than originally
|
||||||
|
requested.
|
|
@ -1517,6 +1517,49 @@ _winapi_GetLastError_impl(PyObject *module)
|
||||||
return GetLastError();
|
return GetLastError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*[clinic input]
|
||||||
|
_winapi.GetLongPathName
|
||||||
|
|
||||||
|
path: LPCWSTR
|
||||||
|
|
||||||
|
Return the long version of the provided path.
|
||||||
|
|
||||||
|
If the path is already in its long form, returns the same value.
|
||||||
|
|
||||||
|
The path must already be a 'str'. If the type is not known, use
|
||||||
|
os.fsdecode before calling this function.
|
||||||
|
[clinic start generated code]*/
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
_winapi_GetLongPathName_impl(PyObject *module, LPCWSTR path)
|
||||||
|
/*[clinic end generated code: output=c4774b080275a2d0 input=9872e211e3a4a88f]*/
|
||||||
|
{
|
||||||
|
DWORD cchBuffer;
|
||||||
|
PyObject *result = NULL;
|
||||||
|
|
||||||
|
Py_BEGIN_ALLOW_THREADS
|
||||||
|
cchBuffer = GetLongPathNameW(path, NULL, 0);
|
||||||
|
Py_END_ALLOW_THREADS
|
||||||
|
if (cchBuffer) {
|
||||||
|
WCHAR *buffer = (WCHAR *)PyMem_Malloc(cchBuffer * sizeof(WCHAR));
|
||||||
|
if (buffer) {
|
||||||
|
Py_BEGIN_ALLOW_THREADS
|
||||||
|
cchBuffer = GetLongPathNameW(path, buffer, cchBuffer);
|
||||||
|
Py_END_ALLOW_THREADS
|
||||||
|
if (cchBuffer) {
|
||||||
|
result = PyUnicode_FromWideChar(buffer, cchBuffer);
|
||||||
|
} else {
|
||||||
|
PyErr_SetFromWindowsErr(0);
|
||||||
|
}
|
||||||
|
PyMem_Free((void *)buffer);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
PyErr_SetFromWindowsErr(0);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/*[clinic input]
|
/*[clinic input]
|
||||||
_winapi.GetModuleFileName
|
_winapi.GetModuleFileName
|
||||||
|
|
||||||
|
@ -1551,6 +1594,48 @@ _winapi_GetModuleFileName_impl(PyObject *module, HMODULE module_handle)
|
||||||
return PyUnicode_FromWideChar(filename, wcslen(filename));
|
return PyUnicode_FromWideChar(filename, wcslen(filename));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*[clinic input]
|
||||||
|
_winapi.GetShortPathName
|
||||||
|
|
||||||
|
path: LPCWSTR
|
||||||
|
|
||||||
|
Return the short version of the provided path.
|
||||||
|
|
||||||
|
If the path is already in its short form, returns the same value.
|
||||||
|
|
||||||
|
The path must already be a 'str'. If the type is not known, use
|
||||||
|
os.fsdecode before calling this function.
|
||||||
|
[clinic start generated code]*/
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
_winapi_GetShortPathName_impl(PyObject *module, LPCWSTR path)
|
||||||
|
/*[clinic end generated code: output=dab6ae494c621e81 input=43fa349aaf2ac718]*/
|
||||||
|
{
|
||||||
|
DWORD cchBuffer;
|
||||||
|
PyObject *result = NULL;
|
||||||
|
|
||||||
|
Py_BEGIN_ALLOW_THREADS
|
||||||
|
cchBuffer = GetShortPathNameW(path, NULL, 0);
|
||||||
|
Py_END_ALLOW_THREADS
|
||||||
|
if (cchBuffer) {
|
||||||
|
WCHAR *buffer = (WCHAR *)PyMem_Malloc(cchBuffer * sizeof(WCHAR));
|
||||||
|
if (buffer) {
|
||||||
|
Py_BEGIN_ALLOW_THREADS
|
||||||
|
cchBuffer = GetShortPathNameW(path, buffer, cchBuffer);
|
||||||
|
Py_END_ALLOW_THREADS
|
||||||
|
if (cchBuffer) {
|
||||||
|
result = PyUnicode_FromWideChar(buffer, cchBuffer);
|
||||||
|
} else {
|
||||||
|
PyErr_SetFromWindowsErr(0);
|
||||||
|
}
|
||||||
|
PyMem_Free((void *)buffer);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
PyErr_SetFromWindowsErr(0);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/*[clinic input]
|
/*[clinic input]
|
||||||
_winapi.GetStdHandle -> HANDLE
|
_winapi.GetStdHandle -> HANDLE
|
||||||
|
|
||||||
|
@ -2846,7 +2931,9 @@ static PyMethodDef winapi_functions[] = {
|
||||||
_WINAPI_GETCURRENTPROCESS_METHODDEF
|
_WINAPI_GETCURRENTPROCESS_METHODDEF
|
||||||
_WINAPI_GETEXITCODEPROCESS_METHODDEF
|
_WINAPI_GETEXITCODEPROCESS_METHODDEF
|
||||||
_WINAPI_GETLASTERROR_METHODDEF
|
_WINAPI_GETLASTERROR_METHODDEF
|
||||||
|
_WINAPI_GETLONGPATHNAME_METHODDEF
|
||||||
_WINAPI_GETMODULEFILENAME_METHODDEF
|
_WINAPI_GETMODULEFILENAME_METHODDEF
|
||||||
|
_WINAPI_GETSHORTPATHNAME_METHODDEF
|
||||||
_WINAPI_GETSTDHANDLE_METHODDEF
|
_WINAPI_GETSTDHANDLE_METHODDEF
|
||||||
_WINAPI_GETVERSION_METHODDEF
|
_WINAPI_GETVERSION_METHODDEF
|
||||||
_WINAPI_MAPVIEWOFFILE_METHODDEF
|
_WINAPI_MAPVIEWOFFILE_METHODDEF
|
||||||
|
|
|
@ -741,6 +741,76 @@ exit:
|
||||||
return return_value;
|
return return_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyDoc_STRVAR(_winapi_GetLongPathName__doc__,
|
||||||
|
"GetLongPathName($module, /, path)\n"
|
||||||
|
"--\n"
|
||||||
|
"\n"
|
||||||
|
"Return the long version of the provided path.\n"
|
||||||
|
"\n"
|
||||||
|
"If the path is already in its long form, returns the same value.\n"
|
||||||
|
"\n"
|
||||||
|
"The path must already be a \'str\'. If the type is not known, use\n"
|
||||||
|
"os.fsdecode before calling this function.");
|
||||||
|
|
||||||
|
#define _WINAPI_GETLONGPATHNAME_METHODDEF \
|
||||||
|
{"GetLongPathName", _PyCFunction_CAST(_winapi_GetLongPathName), METH_FASTCALL|METH_KEYWORDS, _winapi_GetLongPathName__doc__},
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
_winapi_GetLongPathName_impl(PyObject *module, LPCWSTR path);
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
_winapi_GetLongPathName(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
|
||||||
|
{
|
||||||
|
PyObject *return_value = NULL;
|
||||||
|
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
|
||||||
|
|
||||||
|
#define NUM_KEYWORDS 1
|
||||||
|
static struct {
|
||||||
|
PyGC_Head _this_is_not_used;
|
||||||
|
PyObject_VAR_HEAD
|
||||||
|
PyObject *ob_item[NUM_KEYWORDS];
|
||||||
|
} _kwtuple = {
|
||||||
|
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
|
||||||
|
.ob_item = { &_Py_ID(path), },
|
||||||
|
};
|
||||||
|
#undef NUM_KEYWORDS
|
||||||
|
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
|
||||||
|
|
||||||
|
#else // !Py_BUILD_CORE
|
||||||
|
# define KWTUPLE NULL
|
||||||
|
#endif // !Py_BUILD_CORE
|
||||||
|
|
||||||
|
static const char * const _keywords[] = {"path", NULL};
|
||||||
|
static _PyArg_Parser _parser = {
|
||||||
|
.keywords = _keywords,
|
||||||
|
.fname = "GetLongPathName",
|
||||||
|
.kwtuple = KWTUPLE,
|
||||||
|
};
|
||||||
|
#undef KWTUPLE
|
||||||
|
PyObject *argsbuf[1];
|
||||||
|
LPCWSTR path = NULL;
|
||||||
|
|
||||||
|
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf);
|
||||||
|
if (!args) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
if (!PyUnicode_Check(args[0])) {
|
||||||
|
_PyArg_BadArgument("GetLongPathName", "argument 'path'", "str", args[0]);
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
path = PyUnicode_AsWideCharString(args[0], NULL);
|
||||||
|
if (path == NULL) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
return_value = _winapi_GetLongPathName_impl(module, path);
|
||||||
|
|
||||||
|
exit:
|
||||||
|
/* Cleanup for path */
|
||||||
|
PyMem_Free((void *)path);
|
||||||
|
|
||||||
|
return return_value;
|
||||||
|
}
|
||||||
|
|
||||||
PyDoc_STRVAR(_winapi_GetModuleFileName__doc__,
|
PyDoc_STRVAR(_winapi_GetModuleFileName__doc__,
|
||||||
"GetModuleFileName($module, module_handle, /)\n"
|
"GetModuleFileName($module, module_handle, /)\n"
|
||||||
"--\n"
|
"--\n"
|
||||||
|
@ -775,6 +845,76 @@ exit:
|
||||||
return return_value;
|
return return_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyDoc_STRVAR(_winapi_GetShortPathName__doc__,
|
||||||
|
"GetShortPathName($module, /, path)\n"
|
||||||
|
"--\n"
|
||||||
|
"\n"
|
||||||
|
"Return the short version of the provided path.\n"
|
||||||
|
"\n"
|
||||||
|
"If the path is already in its short form, returns the same value.\n"
|
||||||
|
"\n"
|
||||||
|
"The path must already be a \'str\'. If the type is not known, use\n"
|
||||||
|
"os.fsdecode before calling this function.");
|
||||||
|
|
||||||
|
#define _WINAPI_GETSHORTPATHNAME_METHODDEF \
|
||||||
|
{"GetShortPathName", _PyCFunction_CAST(_winapi_GetShortPathName), METH_FASTCALL|METH_KEYWORDS, _winapi_GetShortPathName__doc__},
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
_winapi_GetShortPathName_impl(PyObject *module, LPCWSTR path);
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
_winapi_GetShortPathName(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
|
||||||
|
{
|
||||||
|
PyObject *return_value = NULL;
|
||||||
|
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
|
||||||
|
|
||||||
|
#define NUM_KEYWORDS 1
|
||||||
|
static struct {
|
||||||
|
PyGC_Head _this_is_not_used;
|
||||||
|
PyObject_VAR_HEAD
|
||||||
|
PyObject *ob_item[NUM_KEYWORDS];
|
||||||
|
} _kwtuple = {
|
||||||
|
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
|
||||||
|
.ob_item = { &_Py_ID(path), },
|
||||||
|
};
|
||||||
|
#undef NUM_KEYWORDS
|
||||||
|
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
|
||||||
|
|
||||||
|
#else // !Py_BUILD_CORE
|
||||||
|
# define KWTUPLE NULL
|
||||||
|
#endif // !Py_BUILD_CORE
|
||||||
|
|
||||||
|
static const char * const _keywords[] = {"path", NULL};
|
||||||
|
static _PyArg_Parser _parser = {
|
||||||
|
.keywords = _keywords,
|
||||||
|
.fname = "GetShortPathName",
|
||||||
|
.kwtuple = KWTUPLE,
|
||||||
|
};
|
||||||
|
#undef KWTUPLE
|
||||||
|
PyObject *argsbuf[1];
|
||||||
|
LPCWSTR path = NULL;
|
||||||
|
|
||||||
|
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf);
|
||||||
|
if (!args) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
if (!PyUnicode_Check(args[0])) {
|
||||||
|
_PyArg_BadArgument("GetShortPathName", "argument 'path'", "str", args[0]);
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
path = PyUnicode_AsWideCharString(args[0], NULL);
|
||||||
|
if (path == NULL) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
return_value = _winapi_GetShortPathName_impl(module, path);
|
||||||
|
|
||||||
|
exit:
|
||||||
|
/* Cleanup for path */
|
||||||
|
PyMem_Free((void *)path);
|
||||||
|
|
||||||
|
return return_value;
|
||||||
|
}
|
||||||
|
|
||||||
PyDoc_STRVAR(_winapi_GetStdHandle__doc__,
|
PyDoc_STRVAR(_winapi_GetStdHandle__doc__,
|
||||||
"GetStdHandle($module, std_handle, /)\n"
|
"GetStdHandle($module, std_handle, /)\n"
|
||||||
"--\n"
|
"--\n"
|
||||||
|
@ -1978,4 +2118,4 @@ exit:
|
||||||
|
|
||||||
return return_value;
|
return return_value;
|
||||||
}
|
}
|
||||||
/*[clinic end generated code: output=1f5bbcfa8d1847c5 input=a9049054013a1b77]*/
|
/*[clinic end generated code: output=ed94a2482ede3744 input=a9049054013a1b77]*/
|
||||||
|
|
Loading…
Reference in New Issue