diff --git a/news/13346.bugfix.rst b/news/13346.bugfix.rst new file mode 100644 index 00000000000..b2b9413e7b4 --- /dev/null +++ b/news/13346.bugfix.rst @@ -0,0 +1 @@ +Provide a hint if a system error is raised involving long filenames or path segments on Windows. diff --git a/src/pip/_internal/commands/install.py b/src/pip/_internal/commands/install.py index 056eb8866a0..0cdbf4b043f 100644 --- a/src/pip/_internal/commands/install.py +++ b/src/pip/_internal/commands/install.py @@ -7,6 +7,7 @@ import shutil import site from optparse import SUPPRESS_HELP, Values +from pathlib import Path from pip._vendor.packaging.utils import canonicalize_name from pip._vendor.requests.exceptions import InvalidProxyURL @@ -774,19 +775,28 @@ def create_os_error_message( ) parts.append(".\n") - # Suggest the user to enable Long Paths if path length is - # more than 260 + # Provide a hint if the error may be caused by Windows path + # length limitations. This includes cases where any single + # path segment exceeds 255 characters or the total path + # exceeds 260 characters. + if ( WINDOWS - and error.errno == errno.ENOENT and error.filename - and len(error.filename) > 260 + and ( + error.errno in (errno.EINVAL, errno.ENOENT) + and ( + any(len(part) > 255 for part in Path(error.filename).parts) + or len(error.filename) > 260 + ) + ) ): parts.append( - "HINT: This error might have occurred since " - "this system does not have Windows Long Path " - "support enabled. You can find information on " - "how to enable this at " + "HINT: This error might be due to Windows path length " + "limitations.\n" + "Even if long paths are enabled, individual file or " + "folder names must not exceed 255 characters.\n" + "To enable long paths, see: " "https://pip.pypa.io/warnings/enable-long-paths\n" ) diff --git a/tests/unit/test_command_install.py b/tests/unit/test_command_install.py index e28edf6449c..b34d9289da9 100644 --- a/tests/unit/test_command_install.py +++ b/tests/unit/test_command_install.py @@ -1,4 +1,5 @@ import errno +import sys from unittest import mock import pytest @@ -120,6 +121,40 @@ def test_most_cases( "Consider checking your local proxy configuration" ' with "pip config debug".\n', ), + # Testing both long path error (ENOENT) + # and long file/folder name error (EINVAL) on Windows + pytest.param( + OSError(errno.ENOENT, "No such file or directory", f"C:/foo/{'/a/'*261}"), + False, + False, + "Could not install packages due to an OSError: " + f"[Errno 2] No such file or directory: 'C:/foo/{'/a/'*261}'\n" + "HINT: This error might be due to Windows path length " + "limitations.\n" + "Even if long paths are enabled, individual file or " + "folder names must not exceed 255 characters.\n" + "To enable long paths, see: " + "https://pip.pypa.io/warnings/enable-long-paths\n", + marks=pytest.mark.skipif( + sys.platform != "win32", reason="Windows-specific filename length test" + ), + ), + pytest.param( + OSError(errno.EINVAL, "No such file or directory", f"C:/foo/{'a'*256}"), + False, + False, + "Could not install packages due to an OSError: " + f"[Errno 22] No such file or directory: 'C:/foo/{'a'*256}'\n" + "HINT: This error might be due to Windows path length " + "limitations.\n" + "Even if long paths are enabled, individual file or " + "folder names must not exceed 255 characters.\n" + "To enable long paths, see: " + "https://pip.pypa.io/warnings/enable-long-paths\n", + marks=pytest.mark.skipif( + sys.platform != "win32", reason="Windows-specific filename length test" + ), + ), ], ) def test_create_os_error_message(