Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit1a45659

Browse files
authored
gh-90329: Add _winapi.GetLongPathName and GetShortPathName and use in venv to reduce warnings (GH-117817)
1 parent667a574 commit1a45659

File tree

6 files changed

+333
-3
lines changed

6 files changed

+333
-3
lines changed

‎Lib/test/test_venv.py‎

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
requires_subprocess,is_emscripten,is_wasi,
2323
requires_venv_with_pip,TEST_HOME_DIR,
2424
requires_resource,copy_python_src_ignore)
25-
fromtest.support.os_helperimport (can_symlink,EnvironmentVarGuard,rmtree)
25+
fromtest.support.os_helperimport (can_symlink,EnvironmentVarGuard,rmtree,
26+
TESTFN)
2627
importunittest
2728
importvenv
2829
fromunittest.mockimportpatch,Mock
@@ -639,6 +640,36 @@ def test_activate_shell_script_has_no_dos_newlines(self):
639640
error_message=f"CR LF found in line{i}"
640641
self.assertFalse(line.endswith(b'\r\n'),error_message)
641642

643+
deftest_venv_same_path(self):
644+
same_path=venv.EnvBuilder._same_path
645+
ifsys.platform=='win32':
646+
# Case-insensitive, and handles short/long names
647+
tests= [
648+
(True,TESTFN,TESTFN),
649+
(True,TESTFN.lower(),TESTFN.upper()),
650+
]
651+
import_winapi
652+
# ProgramFiles is the most reliable path that will have short/long
653+
progfiles=os.getenv('ProgramFiles')
654+
ifprogfiles:
655+
tests= [
656+
*tests,
657+
(True,progfiles,progfiles),
658+
(True,_winapi.GetShortPathName(progfiles),_winapi.GetLongPathName(progfiles)),
659+
]
660+
else:
661+
# Just a simple case-sensitive comparison
662+
tests= [
663+
(True,TESTFN,TESTFN),
664+
(False,TESTFN.lower(),TESTFN.upper()),
665+
]
666+
forr,path1,path2intests:
667+
withself.subTest(f"{path1}-{path2}"):
668+
ifr:
669+
self.assertTrue(same_path(path1,path2))
670+
else:
671+
self.assertFalse(same_path(path1,path2))
672+
642673
@requireVenvCreate
643674
classEnsurePipTest(BaseTest):
644675
"""Test venv module installation of pip."""

‎Lib/test/test_winapi.py‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Test the Windows-only _winapi module
2+
3+
importos
4+
importpathlib
5+
importre
6+
importunittest
7+
fromtest.supportimportimport_helper
8+
9+
_winapi=import_helper.import_module('_winapi',required_on=['win'])
10+
11+
classWinAPITests(unittest.TestCase):
12+
deftest_getlongpathname(self):
13+
testfn=pathlib.Path(os.getenv("ProgramFiles")).parents[-1]/"PROGRA~1"
14+
ifnotos.path.isdir(testfn):
15+
raiseunittest.SkipTest("require x:\\PROGRA~1 to test")
16+
17+
# pathlib.Path will be rejected - only str is accepted
18+
withself.assertRaises(TypeError):
19+
_winapi.GetLongPathName(testfn)
20+
21+
actual=_winapi.GetLongPathName(os.fsdecode(testfn))
22+
23+
# Can't assume that PROGRA~1 expands to any particular variation, so
24+
# ensure it matches any one of them.
25+
candidates=set(testfn.parent.glob("Progra*"))
26+
self.assertIn(pathlib.Path(actual),candidates)
27+
28+
deftest_getshortpathname(self):
29+
testfn=pathlib.Path(os.getenv("ProgramFiles"))
30+
ifnotos.path.isdir(testfn):
31+
raiseunittest.SkipTest("require '%ProgramFiles%' to test")
32+
33+
# pathlib.Path will be rejected - only str is accepted
34+
withself.assertRaises(TypeError):
35+
_winapi.GetShortPathName(testfn)
36+
37+
actual=_winapi.GetShortPathName(os.fsdecode(testfn))
38+
39+
# Should contain "PROGRA~" but we can't predict the number
40+
self.assertIsNotNone(re.match(r".\:\\PROGRA~\d",actual.upper()),actual)

‎Lib/venv/__init__.py‎

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,33 @@ def _venv_path(self, env_dir, name):
102102
}
103103
returnsysconfig.get_path(name,scheme='venv',vars=vars)
104104

105+
@classmethod
106+
def_same_path(cls,path1,path2):
107+
"""Check whether two paths appear the same.
108+
109+
Whether they refer to the same file is irrelevant; we're testing for
110+
whether a human reader would look at the path string and easily tell
111+
that they're the same file.
112+
"""
113+
ifsys.platform=='win32':
114+
ifos.path.normcase(path1)==os.path.normcase(path2):
115+
returnTrue
116+
# gh-90329: Don't display a warning for short/long names
117+
import_winapi
118+
try:
119+
path1=_winapi.GetLongPathName(os.fsdecode(path1))
120+
exceptOSError:
121+
pass
122+
try:
123+
path2=_winapi.GetLongPathName(os.fsdecode(path2))
124+
exceptOSError:
125+
pass
126+
ifos.path.normcase(path1)==os.path.normcase(path2):
127+
returnTrue
128+
returnFalse
129+
else:
130+
returnpath1==path2
131+
105132
defensure_directories(self,env_dir):
106133
"""
107134
Create the directories for the environment.
@@ -162,7 +189,7 @@ def create_if_needed(d):
162189
# bpo-45337: Fix up env_exec_cmd to account for file system redirections.
163190
# Some redirects only apply to CreateFile and not CreateProcess
164191
real_env_exe=os.path.realpath(context.env_exe)
165-
ifos.path.normcase(real_env_exe)!=os.path.normcase(context.env_exe):
192+
ifnotself._same_path(real_env_exe,context.env_exe):
166193
logger.warning('Actual environment location may have moved due to '
167194
'redirects, links or junctions.\n'
168195
' Requested location: "%s"\n'
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Suppress the warning displayed on virtual environment creation when the
2+
requested and created paths differ only by a short (8.3 style) name.
3+
Warnings will continue to be shown if a junction or symlink in the path
4+
caused the venv to be created in a different location than originally
5+
requested.

‎Modules/_winapi.c‎

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1452,6 +1452,49 @@ _winapi_GetLastError_impl(PyObject *module)
14521452
returnGetLastError();
14531453
}
14541454

1455+
1456+
/*[clinic input]
1457+
_winapi.GetLongPathName
1458+
1459+
path: LPCWSTR
1460+
1461+
Return the long version of the provided path.
1462+
1463+
If the path is already in its long form, returns the same value.
1464+
1465+
The path must already be a 'str'. If the type is not known, use
1466+
os.fsdecode before calling this function.
1467+
[clinic start generated code]*/
1468+
1469+
staticPyObject*
1470+
_winapi_GetLongPathName_impl(PyObject*module,LPCWSTRpath)
1471+
/*[clinic end generated code: output=c4774b080275a2d0 input=9872e211e3a4a88f]*/
1472+
{
1473+
DWORDcchBuffer;
1474+
PyObject*result=NULL;
1475+
1476+
Py_BEGIN_ALLOW_THREADS
1477+
cchBuffer=GetLongPathNameW(path,NULL,0);
1478+
Py_END_ALLOW_THREADS
1479+
if (cchBuffer) {
1480+
WCHAR*buffer= (WCHAR*)PyMem_Malloc(cchBuffer*sizeof(WCHAR));
1481+
if (buffer) {
1482+
Py_BEGIN_ALLOW_THREADS
1483+
cchBuffer=GetLongPathNameW(path,buffer,cchBuffer);
1484+
Py_END_ALLOW_THREADS
1485+
if (cchBuffer) {
1486+
result=PyUnicode_FromWideChar(buffer,cchBuffer);
1487+
}else {
1488+
PyErr_SetFromWindowsErr(0);
1489+
}
1490+
PyMem_Free((void*)buffer);
1491+
}
1492+
}else {
1493+
PyErr_SetFromWindowsErr(0);
1494+
}
1495+
returnresult;
1496+
}
1497+
14551498
/*[clinic input]
14561499
_winapi.GetModuleFileName
14571500
@@ -1486,6 +1529,48 @@ _winapi_GetModuleFileName_impl(PyObject *module, HMODULE module_handle)
14861529
returnPyUnicode_FromWideChar(filename,wcslen(filename));
14871530
}
14881531

1532+
/*[clinic input]
1533+
_winapi.GetShortPathName
1534+
1535+
path: LPCWSTR
1536+
1537+
Return the short version of the provided path.
1538+
1539+
If the path is already in its short form, returns the same value.
1540+
1541+
The path must already be a 'str'. If the type is not known, use
1542+
os.fsdecode before calling this function.
1543+
[clinic start generated code]*/
1544+
1545+
staticPyObject*
1546+
_winapi_GetShortPathName_impl(PyObject*module,LPCWSTRpath)
1547+
/*[clinic end generated code: output=dab6ae494c621e81 input=43fa349aaf2ac718]*/
1548+
{
1549+
DWORDcchBuffer;
1550+
PyObject*result=NULL;
1551+
1552+
Py_BEGIN_ALLOW_THREADS
1553+
cchBuffer=GetShortPathNameW(path,NULL,0);
1554+
Py_END_ALLOW_THREADS
1555+
if (cchBuffer) {
1556+
WCHAR*buffer= (WCHAR*)PyMem_Malloc(cchBuffer*sizeof(WCHAR));
1557+
if (buffer) {
1558+
Py_BEGIN_ALLOW_THREADS
1559+
cchBuffer=GetShortPathNameW(path,buffer,cchBuffer);
1560+
Py_END_ALLOW_THREADS
1561+
if (cchBuffer) {
1562+
result=PyUnicode_FromWideChar(buffer,cchBuffer);
1563+
}else {
1564+
PyErr_SetFromWindowsErr(0);
1565+
}
1566+
PyMem_Free((void*)buffer);
1567+
}
1568+
}else {
1569+
PyErr_SetFromWindowsErr(0);
1570+
}
1571+
returnresult;
1572+
}
1573+
14891574
/*[clinic input]
14901575
_winapi.GetStdHandle -> HANDLE
14911576
@@ -2345,7 +2430,9 @@ static PyMethodDef winapi_functions[] = {
23452430
_WINAPI_GETCURRENTPROCESS_METHODDEF
23462431
_WINAPI_GETEXITCODEPROCESS_METHODDEF
23472432
_WINAPI_GETLASTERROR_METHODDEF
2433+
_WINAPI_GETLONGPATHNAME_METHODDEF
23482434
_WINAPI_GETMODULEFILENAME_METHODDEF
2435+
_WINAPI_GETSHORTPATHNAME_METHODDEF
23492436
_WINAPI_GETSTDHANDLE_METHODDEF
23502437
_WINAPI_GETVERSION_METHODDEF
23512438
_WINAPI_MAPVIEWOFFILE_METHODDEF

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp