summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDominic Davis-Foster <dominic@davis-foster.co.uk>2021-07-24 23:18:35 +0100
committerDominic Davis-Foster <dominic@davis-foster.co.uk>2021-09-30 15:34:35 +0100
commitae6cf66c79734924544c4d5312dc0ca6072f324b (patch)
tree23bda98066ab7cb23765d0452c194e2fd581fb86
parent0225b2a55ed60cddc22bd6a735dad09f8174499f (diff)
downloadpip-ae6cf66c79734924544c4d5312dc0ca6072f324b.tar.gz
Switch from appdirs to platformdirs
-rw-r--r--pyproject.toml2
-rw-r--r--src/pip/_internal/utils/appdirs.py10
-rw-r--r--src/pip/_vendor/__init__.py2
-rw-r--r--src/pip/_vendor/pkg_resources/__init__.py4
-rw-r--r--src/pip/_vendor/platformdirs.LICENSE.txt (renamed from src/pip/_vendor/appdirs.LICENSE.txt)0
-rw-r--r--src/pip/_vendor/platformdirs.py (renamed from src/pip/_vendor/appdirs.py)651
-rw-r--r--src/pip/_vendor/vendor.txt2
-rw-r--r--tests/unit/test_appdirs.py143
-rw-r--r--tools/vendoring/patches/appdirs.patch115
-rw-r--r--tools/vendoring/patches/pkg_resources.patch22
-rw-r--r--tools/vendoring/patches/platformdirs.patch20
11 files changed, 490 insertions, 481 deletions
diff --git a/pyproject.toml b/pyproject.toml
index a479b968d..4ec4d1421 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -48,8 +48,8 @@ drop = [
[tool.vendoring.typing-stubs]
six = ["six.__init__", "six.moves.__init__", "six.moves.configparser"]
-appdirs = []
distro = []
+platformdirs = []
[tool.vendoring.license.directories]
setuptools = "pkg_resources"
diff --git a/src/pip/_internal/utils/appdirs.py b/src/pip/_internal/utils/appdirs.py
index a8403b7de..eb7710449 100644
--- a/src/pip/_internal/utils/appdirs.py
+++ b/src/pip/_internal/utils/appdirs.py
@@ -9,7 +9,7 @@ and eventually drop this after all usages are changed.
import os
from typing import List
-from pip._vendor import appdirs as _appdirs
+from pip._vendor import platformdirs as _appdirs
def user_cache_dir(appname: str) -> str:
@@ -29,7 +29,11 @@ def user_config_dir(appname: str, roaming: bool = True) -> str:
# see <https://github.com/pypa/pip/issues/1733>
def site_config_dirs(appname: str) -> List[str]:
dirval = _appdirs.site_config_dir(appname, appauthor=False, multipath=True)
- if _appdirs.system not in ["win32", "darwin"]:
+ if _appdirs.system == "darwin":
+ # always look in /Library/Application Support/pip as well
+ return dirval.split(os.pathsep) + ["/Library/Application Support/pip"]
+ elif _appdirs.system == "win32":
+ return [dirval]
+ else:
# always look in /etc directly as well
return dirval.split(os.pathsep) + ["/etc"]
- return [dirval]
diff --git a/src/pip/_vendor/__init__.py b/src/pip/_vendor/__init__.py
index 57e32dab1..3843cb099 100644
--- a/src/pip/_vendor/__init__.py
+++ b/src/pip/_vendor/__init__.py
@@ -58,7 +58,6 @@ if DEBUNDLED:
sys.path[:] = glob.glob(os.path.join(WHEEL_DIR, "*.whl")) + sys.path
# Actually alias all of our vendored dependencies.
- vendored("appdirs")
vendored("cachecontrol")
vendored("certifi")
vendored("colorama")
@@ -74,6 +73,7 @@ if DEBUNDLED:
vendored("packaging.specifiers")
vendored("pep517")
vendored("pkg_resources")
+ vendored("platformdirs")
vendored("progress")
vendored("requests")
vendored("requests.exceptions")
diff --git a/src/pip/_vendor/pkg_resources/__init__.py b/src/pip/_vendor/pkg_resources/__init__.py
index a457ff27e..4cd562cf9 100644
--- a/src/pip/_vendor/pkg_resources/__init__.py
+++ b/src/pip/_vendor/pkg_resources/__init__.py
@@ -77,7 +77,7 @@ except ImportError:
importlib_machinery = None
from . import py31compat
-from pip._vendor import appdirs
+from pip._vendor import platformdirs
from pip._vendor import packaging
__import__('pip._vendor.packaging.version')
__import__('pip._vendor.packaging.specifiers')
@@ -1310,7 +1310,7 @@ def get_default_cache():
"""
return (
os.environ.get('PYTHON_EGG_CACHE')
- or appdirs.user_cache_dir(appname='Python-Eggs')
+ or platformdirs.user_cache_dir(appname='Python-Eggs')
)
diff --git a/src/pip/_vendor/appdirs.LICENSE.txt b/src/pip/_vendor/platformdirs.LICENSE.txt
index 107c61405..107c61405 100644
--- a/src/pip/_vendor/appdirs.LICENSE.txt
+++ b/src/pip/_vendor/platformdirs.LICENSE.txt
diff --git a/src/pip/_vendor/appdirs.py b/src/pip/_vendor/platformdirs.py
index 33a3b7741..3b8ef8ad8 100644
--- a/src/pip/_vendor/appdirs.py
+++ b/src/pip/_vendor/platformdirs.py
@@ -1,28 +1,27 @@
-#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2005-2010 ActiveState Software Inc.
# Copyright (c) 2013 Eddy Petrișor
"""Utilities for determining application-specific dirs.
-See <http://github.com/ActiveState/appdirs> for details and usage.
+See <https://github.com/platformdirs/platformdirs> for details and usage.
"""
# Dev Notes:
# - MSDN on where to store app data files:
# http://support.microsoft.com/default.aspx?scid=kb;en-us;310294#XSLTH3194121123120121120120
# - Mac OS X: http://developer.apple.com/documentation/MacOSX/Conceptual/BPFileSystem/index.html
-# - XDG spec for Un*x: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
+# - XDG spec for Un*x: https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
-__version__ = "1.4.4"
-__version_info__ = tuple(int(segment) for segment in __version__.split("."))
+__version__ = "2.0.2"
+__version_info__ = 2, 0, 2
import sys
import os
-PY3 = sys.version_info[0] == 3
+PY2 = sys.version_info[0] == 2
-if PY3:
+if not PY2:
unicode = str
if sys.platform.startswith('java'):
@@ -37,14 +36,348 @@ if sys.platform.startswith('java'):
# are actually checked for and the rest of the module expects
# *sys.platform* style strings.
system = 'linux2'
-elif sys.platform == 'cli' and os.name == 'nt':
- # Detect Windows in IronPython to match pip._internal.utils.compat.WINDOWS
- # Discussion: <https://github.com/pypa/pip/pull/7501>
- system = 'win32'
else:
system = sys.platform
+# https://docs.python.org/dev/library/sys.html#sys.platform
+if system == 'win32':
+ try:
+ from ctypes import windll
+ except ImportError:
+ try:
+ import com.sun.jna
+ except ImportError:
+ try:
+ if PY2:
+ import _winreg as winreg
+ else:
+ import winreg
+ except ImportError:
+ def _get_win_folder(csidl_name):
+ """Get folder from environment variables."""
+ if csidl_name == 'CSIDL_APPDATA':
+ env_var_name = 'APPDATA'
+ elif csidl_name == 'CSIDL_COMMON_APPDATA':
+ env_var_name = 'ALLUSERSPROFILE'
+ elif csidl_name == 'CSIDL_LOCAL_APPDATA':
+ env_var_name = 'LOCALAPPDATA'
+ else:
+ raise ValueError('Unknown CSIDL name: {}'.format(csidl_name))
+
+ if env_var_name in os.environ:
+ return os.environ[env_var_name]
+ else:
+ raise ValueError('Unset environment variable: {}'.format(env_var_name))
+ else:
+ def _get_win_folder(csidl_name):
+ """Get folder from the registry.
+
+ This is a fallback technique at best. I'm not sure if using the
+ registry for this guarantees us the correct answer for all CSIDL_*
+ names.
+ """
+ if csidl_name == 'CSIDL_APPDATA':
+ shell_folder_name = 'AppData'
+ elif csidl_name == 'CSIDL_COMMON_APPDATA':
+ shell_folder_name = 'Common AppData'
+ elif csidl_name == 'CSIDL_LOCAL_APPDATA':
+ shell_folder_name = 'Local AppData'
+ else:
+ raise ValueError('Unknown CSIDL name: {}'.format(csidl_name))
+
+ key = winreg.OpenKey(
+ winreg.HKEY_CURRENT_USER,
+ r'Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders'
+ )
+ directory, _ = winreg.QueryValueEx(key, shell_folder_name)
+ return directory
+ else:
+ def _get_win_folder_with_jna(csidl_name):
+ """Get folder with JNA."""
+ import array
+ from com.sun import jna
+ from com.sun.jna.platform import win32
+
+ buf_size = win32.WinDef.MAX_PATH * 2
+ buf = array.zeros('c', buf_size)
+ shell = win32.Shell32.INSTANCE
+ shell.SHGetFolderPath(
+ None, getattr(win32.ShlObj, csidl_name), None, win32.ShlObj.SHGFP_TYPE_CURRENT, buf
+ )
+ directory = jna.Native.toString(buf.tostring()).rstrip('\0')
+
+ # Downgrade to short path name if have highbit chars. See
+ # <http://bugs.activestate.com/show_bug.cgi?id=85099>.
+ has_high_char = False
+ for c in directory:
+ if ord(c) > 255:
+ has_high_char = True
+ break
+ if has_high_char:
+ buf = array.zeros('c', buf_size)
+ kernel = win32.Kernel32.INSTANCE
+ if kernel.GetShortPathName(directory, buf, buf_size):
+ directory = jna.Native.toString(buf.tostring()).rstrip('\0')
+
+ return directory
+ else:
+ def _get_win_folder(csidl_name):
+ """Get folder with ctypes."""
+ import ctypes
+
+ if csidl_name == 'CSIDL_APPDATA':
+ csidl_const = 26
+ elif csidl_name == 'CSIDL_COMMON_APPDATA':
+ csidl_const = 35
+ elif csidl_name == 'CSIDL_LOCAL_APPDATA':
+ csidl_const = 28
+ else:
+ raise ValueError('Unknown CSIDL name: {}'.format(csidl_name))
+
+ buf = ctypes.create_unicode_buffer(1024)
+ ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf)
+
+ # Downgrade to short path name if have highbit chars. See
+ # <http://bugs.activestate.com/show_bug.cgi?id=85099>.
+ has_high_char = False
+ for c in buf:
+ if ord(c) > 255:
+ has_high_char = True
+ break
+ if has_high_char:
+ buf2 = ctypes.create_unicode_buffer(1024)
+ if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024):
+ buf = buf2
+
+ return buf.value
+
+ def _user_data_dir_impl(appname=None, appauthor=None, version=None, roaming=False):
+ if appauthor is None:
+ appauthor = appname
+
+ const = 'CSIDL_APPDATA' if roaming else 'CSIDL_LOCAL_APPDATA'
+ path = os.path.normpath(_get_win_folder(const))
+ if appname:
+ if appauthor is not False:
+ path = os.path.join(path, appauthor, appname)
+ else:
+ path = os.path.join(path, appname)
+
+ if version:
+ path = os.path.join(path, version)
+
+ return path
+
+ def _site_data_dir_impl(appname=None, appauthor=None, version=None, multipath=False):
+ if appauthor is None:
+ appauthor = appname
+
+ path = os.path.normpath(_get_win_folder('CSIDL_COMMON_APPDATA'))
+ if appname:
+ if appauthor is not False:
+ path = os.path.join(path, appauthor, appname)
+ else:
+ path = os.path.join(path, appname)
+
+ if version:
+ path = os.path.join(path, version)
+
+ return path
+
+ def _user_config_dir_impl(appname=None, appauthor=None, version=None, roaming=False):
+ return _user_data_dir_impl(appname=appname, appauthor=appauthor, version=version, roaming=roaming)
+
+ def _site_config_dir_impl(appname=None, appauthor=None, version=None, multipath=False):
+ return _site_data_dir_impl(appname=appname, appauthor=appauthor, version=version)
+
+ def _user_cache_dir_impl(appname=None, appauthor=None, version=None, opinion=True):
+ if appauthor is None:
+ appauthor = appname
+
+ path = os.path.normpath(_get_win_folder('CSIDL_LOCAL_APPDATA'))
+ if appname:
+ if appauthor is not False:
+ path = os.path.join(path, appauthor, appname)
+ else:
+ path = os.path.join(path, appname)
+
+ if opinion:
+ path = os.path.join(path, 'Cache')
+
+ if version:
+ path = os.path.join(path, version)
+
+ return path
+
+ def _user_state_dir_impl(appname=None, appauthor=None, version=None, roaming=False):
+ return _user_data_dir_impl(appname=appname, appauthor=appauthor, version=version, roaming=roaming)
+
+ def _user_log_dir_impl(appname=None, appauthor=None, version=None, opinion=True):
+ path = _user_data_dir_impl(appname=appname, appauthor=appauthor, version=version)
+ if opinion:
+ path = os.path.join(path, 'Logs')
+
+ return path
+
+elif system == 'darwin':
+
+ def _user_data_dir_impl(appname=None, appauthor=None, version=None, roaming=False):
+ path = os.path.expanduser('~/Library/Application Support/')
+ if appname:
+ path = os.path.join(path, appname)
+ if version:
+ path = os.path.join(path, version)
+
+ return path
+
+ def _site_data_dir_impl(appname=None, appauthor=None, version=None, multipath=False):
+ path = '/Library/Application Support'
+ if appname:
+ path = os.path.join(path, appname)
+ if version:
+ path = os.path.join(path, version)
+
+ return path
+
+ def _user_config_dir_impl(appname=None, appauthor=None, version=None, roaming=False):
+ path = os.path.expanduser('~/Library/Preferences/')
+ if appname:
+ path = os.path.join(path, appname)
+ if version:
+ path = os.path.join(path, version)
+
+ return path
+
+ def _site_config_dir_impl(appname=None, appauthor=None, version=None, multipath=False):
+ path = '/Library/Preferences'
+ if appname:
+ path = os.path.join(path, appname)
+
+ return path
+
+ def _user_cache_dir_impl(appname=None, appauthor=None, version=None, opinion=True):
+ path = os.path.expanduser('~/Library/Caches')
+ if appname:
+ path = os.path.join(path, appname)
+ if version:
+ path = os.path.join(path, version)
+
+ return path
+
+ def _user_state_dir_impl(appname=None, appauthor=None, version=None, roaming=False):
+ return _user_data_dir_impl(appname=appname, appauthor=appauthor, version=version, roaming=roaming)
+
+ def _user_log_dir_impl(appname=None, appauthor=None, version=None, opinion=True):
+ path = os.path.expanduser('~/Library/Logs')
+ if appname:
+ path = os.path.join(path, appname)
+ if version:
+ path = os.path.join(path, version)
+
+ return path
+
+else:
+
+ def _user_data_dir_impl(appname=None, appauthor=None, version=None, roaming=False):
+ if 'XDG_DATA_HOME' in os.environ:
+ path = os.environ['XDG_DATA_HOME']
+ else:
+ path = os.path.expanduser('~/.local/share')
+
+ if appname:
+ path = os.path.join(path, appname)
+ if version:
+ path = os.path.join(path, version)
+
+ return path
+
+ def _site_data_dir_impl(appname=None, appauthor=None, version=None, multipath=False):
+ # XDG default for $XDG_DATA_DIRS
+ # only first, if multipath is False
+ if 'XDG_DATA_DIRS' in os.environ:
+ path = os.environ['XDG_DATA_DIRS']
+ else:
+ path = '/usr/local/share{}/usr/share'.format(os.pathsep)
+
+ pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)]
+ if appname:
+ if version:
+ appname = os.path.join(appname, version)
+ pathlist = [os.path.join(x, appname) for x in pathlist]
+
+ if multipath:
+ path = os.pathsep.join(pathlist)
+ else:
+ path = pathlist[0]
+
+ return path
+
+ def _user_config_dir_impl(appname=None, appauthor=None, version=None, roaming=False):
+ if 'XDG_CONFIG_HOME' in os.environ:
+ path = os.environ['XDG_CONFIG_HOME']
+ else:
+ path = os.path.expanduser('~/.config')
+
+ if appname:
+ path = os.path.join(path, appname)
+ if version:
+ path = os.path.join(path, version)
+
+ return path
+
+ def _site_config_dir_impl(appname=None, appauthor=None, version=None, multipath=False):
+ # XDG default for $XDG_CONFIG_DIRSS (missing or empty)
+ # see <https://github.com/pypa/pip/pull/7501#discussion_r360624829>
+ # only first, if multipath is False
+ path = os.getenv('XDG_CONFIG_DIRS') or '/etc/xdg'
+
+ pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)]
+ if appname:
+ if version:
+ appname = os.path.join(appname, version)
+ pathlist = [os.path.join(x, appname) for x in pathlist]
+
+ if multipath:
+ path = os.pathsep.join(pathlist)
+ else:
+ path = pathlist[0]
+
+ return path
+
+ def _user_cache_dir_impl(appname=None, appauthor=None, version=None, opinion=True):
+ if 'XDG_CACHE_HOME' in os.environ:
+ path = os.environ['XDG_CACHE_HOME']
+ else:
+ path = os.path.expanduser('~/.cache')
+
+ if appname:
+ path = os.path.join(path, appname)
+ if version:
+ path = os.path.join(path, version)
+
+ return path
+
+ def _user_state_dir_impl(appname=None, appauthor=None, version=None, roaming=False):
+ if 'XDG_STATE_HOME' in os.environ:
+ path = os.environ['XDG_STATE_HOME']
+ else:
+ path = os.path.expanduser('~/.local/state')
+
+ if appname:
+ path = os.path.join(path, appname)
+ if version:
+ path = os.path.join(path, version)
+
+ return path
+
+ def _user_log_dir_impl(appname=None, appauthor=None, version=None, opinion=True):
+ path = _user_cache_dir_impl(appname=appname, appauthor=appauthor, version=version)
+ if opinion:
+ path = os.path.join(path, 'log')
+
+ return path
+
def user_data_dir(appname=None, appauthor=None, version=None, roaming=False):
r"""Return full path to the user-specific data dir for this application.
@@ -68,7 +401,7 @@ def user_data_dir(appname=None, appauthor=None, version=None, roaming=False):
for a discussion of issues.
Typical user data directories are:
- Mac OS X: ~/Library/Application Support/<AppName> # or ~/.config/<AppName>, if the other does not exist
+ Mac OS X: ~/Library/Application Support/<AppName>
Unix: ~/.local/share/<AppName> # or in $XDG_DATA_HOME, if defined
Win XP (not roaming): C:\Documents and Settings\<username>\Application Data\<AppAuthor>\<AppName>
Win XP (roaming): C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>
@@ -78,27 +411,7 @@ def user_data_dir(appname=None, appauthor=None, version=None, roaming=False):
For Unix, we follow the XDG spec and support $XDG_DATA_HOME.
That means, by default "~/.local/share/<AppName>".
"""
- if system == "win32":
- if appauthor is None:
- appauthor = appname
- const = roaming and "CSIDL_APPDATA" or "CSIDL_LOCAL_APPDATA"
- path = os.path.normpath(_get_win_folder(const))
- if appname:
- if appauthor is not False:
- path = os.path.join(path, appauthor, appname)
- else:
- path = os.path.join(path, appname)
- elif system == 'darwin':
- path = os.path.expanduser('~/Library/Application Support/')
- if appname:
- path = os.path.join(path, appname)
- else:
- path = os.getenv('XDG_DATA_HOME', os.path.expanduser("~/.local/share"))
- if appname:
- path = os.path.join(path, appname)
- if appname and version:
- path = os.path.join(path, version)
- return path
+ return _user_data_dir_impl(appname=appname, appauthor=appauthor, version=version, roaming=roaming)
def site_data_dir(appname=None, appauthor=None, version=None, multipath=False):
@@ -132,39 +445,7 @@ def site_data_dir(appname=None, appauthor=None, version=None, multipath=False):
WARNING: Do not use this on Windows. See the Vista-Fail note above for why.
"""
- if system == "win32":
- if appauthor is None:
- appauthor = appname
- path = os.path.normpath(_get_win_folder("CSIDL_COMMON_APPDATA"))
- if appname:
- if appauthor is not False:
- path = os.path.join(path, appauthor, appname)
- else:
- path = os.path.join(path, appname)
- elif system == 'darwin':
- path = os.path.expanduser('/Library/Application Support')
- if appname:
- path = os.path.join(path, appname)
- else:
- # XDG default for $XDG_DATA_DIRS
- # only first, if multipath is False
- path = os.getenv('XDG_DATA_DIRS',
- os.pathsep.join(['/usr/local/share', '/usr/share']))
- pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)]
- if appname:
- if version:
- appname = os.path.join(appname, version)
- pathlist = [os.path.join(x, appname) for x in pathlist]
-
- if multipath:
- path = os.pathsep.join(pathlist)
- else:
- path = pathlist[0]
- return path
-
- if appname and version:
- path = os.path.join(path, version)
- return path
+ return _site_data_dir_impl(appname=appname, appauthor=appauthor, version=version, multipath=multipath)
def user_config_dir(appname=None, appauthor=None, version=None, roaming=False):
@@ -189,26 +470,16 @@ def user_config_dir(appname=None, appauthor=None, version=None, roaming=False):
for a discussion of issues.
Typical user config directories are:
- Mac OS X: same as user_data_dir
+ Mac OS X: ~/Library/Preferences/<AppName>
Unix: ~/.config/<AppName> # or in $XDG_CONFIG_HOME, if defined
Win *: same as user_data_dir
For Unix, we follow the XDG spec and support $XDG_CONFIG_HOME.
That means, by default "~/.config/<AppName>".
"""
- if system in ["win32", "darwin"]:
- path = user_data_dir(appname, appauthor, None, roaming)
- else:
- path = os.getenv('XDG_CONFIG_HOME', os.path.expanduser("~/.config"))
- if appname:
- path = os.path.join(path, appname)
- if appname and version:
- path = os.path.join(path, version)
- return path
+ return _user_config_dir_impl(appname=appname, appauthor=appauthor, version=version, roaming=roaming)
-# for the discussion regarding site_config_dir locations
-# see <https://github.com/pypa/pip/issues/1733>
def site_config_dir(appname=None, appauthor=None, version=None, multipath=False):
r"""Return full path to the user-shared data dir for this application.
@@ -239,26 +510,7 @@ def site_config_dir(appname=None, appauthor=None, version=None, multipath=False)
WARNING: Do not use this on Windows. See the Vista-Fail note above for why.
"""
- if system in ["win32", "darwin"]:
- path = site_data_dir(appname, appauthor)
- if appname and version:
- path = os.path.join(path, version)
- else:
- # XDG default for $XDG_CONFIG_DIRS (missing or empty)
- # see <https://github.com/pypa/pip/pull/7501#discussion_r360624829>
- # only first, if multipath is False
- path = os.getenv('XDG_CONFIG_DIRS') or '/etc/xdg'
- pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep) if x]
- if appname:
- if version:
- appname = os.path.join(appname, version)
- pathlist = [os.path.join(x, appname) for x in pathlist]
-
- if multipath:
- path = os.pathsep.join(pathlist)
- else:
- path = pathlist[0]
- return path
+ return _site_config_dir_impl(appname=appname, appauthor=appauthor, version=version, multipath=multipath)
def user_cache_dir(appname=None, appauthor=None, version=None, opinion=True):
@@ -294,32 +546,7 @@ def user_cache_dir(appname=None, appauthor=None, version=None, opinion=True):
OPINION: This function appends "Cache" to the `CSIDL_LOCAL_APPDATA` value.
This can be disabled with the `opinion=False` option.
"""
- if system == "win32":
- if appauthor is None:
- appauthor = appname
- path = os.path.normpath(_get_win_folder("CSIDL_LOCAL_APPDATA"))
- # When using Python 2, return paths as bytes on Windows like we do on
- # other operating systems. See helper function docs for more details.
- if not PY3 and isinstance(path, unicode):
- path = _win_path_to_bytes(path)
- if appname:
- if appauthor is not False:
- path = os.path.join(path, appauthor, appname)
- else:
- path = os.path.join(path, appname)
- if opinion:
- path = os.path.join(path, "Cache")
- elif system == 'darwin':
- path = os.path.expanduser('~/Library/Caches')
- if appname:
- path = os.path.join(path, appname)
- else:
- path = os.getenv('XDG_CACHE_HOME', os.path.expanduser('~/.cache'))
- if appname:
- path = os.path.join(path, appname)
- if appname and version:
- path = os.path.join(path, version)
- return path
+ return _user_cache_dir_impl(appname=appname, appauthor=appauthor, version=version, opinion=opinion)
def user_state_dir(appname=None, appauthor=None, version=None, roaming=False):
@@ -353,15 +580,7 @@ def user_state_dir(appname=None, appauthor=None, version=None, roaming=False):
That means, by default "~/.local/state/<AppName>".
"""
- if system in ["win32", "darwin"]:
- path = user_data_dir(appname, appauthor, None, roaming)
- else:
- path = os.getenv('XDG_STATE_HOME', os.path.expanduser("~/.local/state"))
- if appname:
- path = os.path.join(path, appname)
- if appname and version:
- path = os.path.join(path, version)
- return path
+ return _user_state_dir_impl(appname=appname, appauthor=appauthor, version=version, roaming=roaming)
def user_log_dir(appname=None, appauthor=None, version=None, opinion=True):
@@ -396,26 +615,10 @@ def user_log_dir(appname=None, appauthor=None, version=None, opinion=True):
value for Windows and appends "log" to the user cache dir for Unix.
This can be disabled with the `opinion=False` option.
"""
- if system == "darwin":
- path = os.path.join(
- os.path.expanduser('~/Library/Logs'),
- appname)
- elif system == "win32":
- path = user_data_dir(appname, appauthor, version)
- version = False
- if opinion:
- path = os.path.join(path, "Logs")
- else:
- path = user_cache_dir(appname, appauthor, version)
- version = False
- if opinion:
- path = os.path.join(path, "log")
- if appname and version:
- path = os.path.join(path, version)
- return path
+ return _user_log_dir_impl(appname=appname, appauthor=appauthor, version=version, opinion=opinion)
-class AppDirs(object):
+class PlatformDirs(object):
"""Convenience wrapper for getting application dirs."""
def __init__(self, appname=None, appauthor=None, version=None,
roaming=False, multipath=False):
@@ -461,144 +664,12 @@ class AppDirs(object):
version=self.version)
-#---- internal support stuff
-
-def _get_win_folder_from_registry(csidl_name):
- """This is a fallback technique at best. I'm not sure if using the
- registry for this guarantees us the correct answer for all CSIDL_*
- names.
- """
- if PY3:
- import winreg as _winreg
- else:
- import _winreg
-
- shell_folder_name = {
- "CSIDL_APPDATA": "AppData",
- "CSIDL_COMMON_APPDATA": "Common AppData",
- "CSIDL_LOCAL_APPDATA": "Local AppData",
- }[csidl_name]
-
- key = _winreg.OpenKey(
- _winreg.HKEY_CURRENT_USER,
- r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
- )
- dir, type = _winreg.QueryValueEx(key, shell_folder_name)
- return dir
-
-
-def _get_win_folder_with_pywin32(csidl_name):
- from win32com.shell import shellcon, shell
- dir = shell.SHGetFolderPath(0, getattr(shellcon, csidl_name), 0, 0)
- # Try to make this a unicode path because SHGetFolderPath does
- # not return unicode strings when there is unicode data in the
- # path.
- try:
- dir = unicode(dir)
-
- # Downgrade to short path name if have highbit chars. See
- # <http://bugs.activestate.com/show_bug.cgi?id=85099>.
- has_high_char = False
- for c in dir:
- if ord(c) > 255:
- has_high_char = True
- break
- if has_high_char:
- try:
- import win32api
- dir = win32api.GetShortPathName(dir)
- except ImportError:
- pass
- except UnicodeError:
- pass
- return dir
-
-
-def _get_win_folder_with_ctypes(csidl_name):
- import ctypes
-
- csidl_const = {
- "CSIDL_APPDATA": 26,
- "CSIDL_COMMON_APPDATA": 35,
- "CSIDL_LOCAL_APPDATA": 28,
- }[csidl_name]
-
- buf = ctypes.create_unicode_buffer(1024)
- ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf)
-
- # Downgrade to short path name if have highbit chars. See
- # <http://bugs.activestate.com/show_bug.cgi?id=85099>.
- has_high_char = False
- for c in buf:
- if ord(c) > 255:
- has_high_char = True
- break
- if has_high_char:
- buf2 = ctypes.create_unicode_buffer(1024)
- if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024):
- buf = buf2
-
- return buf.value
-
-def _get_win_folder_with_jna(csidl_name):
- import array
- from com.sun import jna
- from com.sun.jna.platform import win32
-
- buf_size = win32.WinDef.MAX_PATH * 2
- buf = array.zeros('c', buf_size)
- shell = win32.Shell32.INSTANCE
- shell.SHGetFolderPath(None, getattr(win32.ShlObj, csidl_name), None, win32.ShlObj.SHGFP_TYPE_CURRENT, buf)
- dir = jna.Native.toString(buf.tostring()).rstrip("\0")
-
- # Downgrade to short path name if have highbit chars. See
- # <http://bugs.activestate.com/show_bug.cgi?id=85099>.
- has_high_char = False
- for c in dir:
- if ord(c) > 255:
- has_high_char = True
- break
- if has_high_char:
- buf = array.zeros('c', buf_size)
- kernel = win32.Kernel32.INSTANCE
- if kernel.GetShortPathName(dir, buf, buf_size):
- dir = jna.Native.toString(buf.tostring()).rstrip("\0")
-
- return dir
-
-if system == "win32":
- try:
- from ctypes import windll
- _get_win_folder = _get_win_folder_with_ctypes
- except ImportError:
- try:
- import com.sun.jna
- _get_win_folder = _get_win_folder_with_jna
- except ImportError:
- _get_win_folder = _get_win_folder_from_registry
-
-
-def _win_path_to_bytes(path):
- """Encode Windows paths to bytes. Only used on Python 2.
-
- Motivation is to be consistent with other operating systems where paths
- are also returned as bytes. This avoids problems mixing bytes and Unicode
- elsewhere in the codebase. For more details and discussion see
- <https://github.com/pypa/pip/issues/3463>.
-
- If encoding using ASCII and MBCS fails, return the original Unicode path.
- """
- for encoding in ('ASCII', 'MBCS'):
- try:
- return path.encode(encoding)
- except (UnicodeEncodeError, LookupError):
- pass
- return path
-
+# Backwards compatibility with appdirs
+AppDirs = PlatformDirs
-#---- self test code
if __name__ == "__main__":
+ # ---- self test code
appname = "MyApp"
appauthor = "MyCompany"
@@ -613,21 +684,21 @@ if __name__ == "__main__":
print("-- app dirs %s --" % __version__)
print("-- app dirs (with optional 'version')")
- dirs = AppDirs(appname, appauthor, version="1.0")
+ dirs = PlatformDirs(appname, appauthor, version="1.0")
for prop in props:
print("%s: %s" % (prop, getattr(dirs, prop)))
print("\n-- app dirs (without optional 'version')")
- dirs = AppDirs(appname, appauthor)
+ dirs = PlatformDirs(appname, appauthor)
for prop in props:
print("%s: %s" % (prop, getattr(dirs, prop)))
print("\n-- app dirs (without optional 'appauthor')")
- dirs = AppDirs(appname)
+ dirs = PlatformDirs(appname)
for prop in props:
print("%s: %s" % (prop, getattr(dirs, prop)))
print("\n-- app dirs (with disabled 'appauthor')")
- dirs = AppDirs(appname, appauthor=False)
+ dirs = PlatformDirs(appname, appauthor=False)
for prop in props:
print("%s: %s" % (prop, getattr(dirs, prop)))
diff --git a/src/pip/_vendor/vendor.txt b/src/pip/_vendor/vendor.txt
index 991c3af8c..a0402fab8 100644
--- a/src/pip/_vendor/vendor.txt
+++ b/src/pip/_vendor/vendor.txt
@@ -1,4 +1,3 @@
-appdirs==1.4.4
CacheControl==0.12.6
colorama==0.4.4
distlib==0.3.3
@@ -7,6 +6,7 @@ html5lib==1.1
msgpack==1.0.2
packaging==21.0
pep517==0.11.0
+platformdirs==2.0.2
progress==1.5
pyparsing==2.4.7
requests==2.26.0
diff --git a/tests/unit/test_appdirs.py b/tests/unit/test_appdirs.py
index 70453c275..0cee99dd5 100644
--- a/tests/unit/test_appdirs.py
+++ b/tests/unit/test_appdirs.py
@@ -1,3 +1,4 @@
+import importlib
import ntpath
import os
import posixpath
@@ -5,23 +6,66 @@ import sys
from unittest import mock
import pytest
-from pip._vendor import appdirs as _appdirs
+from pip._vendor import platformdirs
from pip._internal.utils import appdirs
+@pytest.fixture()
+def platformdirs_win32(monkeypatch):
+ # Monkeypatch platformdirs to pretend we're running on Windows
+
+ with monkeypatch.context() as m:
+ m.setattr(sys, "platform", "win32")
+ m.setattr(os, "path", ntpath)
+ importlib.reload(platformdirs)
+ importlib.reload(appdirs)
+ yield
+
+ importlib.reload(platformdirs)
+ importlib.reload(appdirs)
+
+
+@pytest.fixture()
+def platformdirs_darwin(monkeypatch):
+ # Monkeypatch platformdirs to pretend we're running on macOS
+
+ with monkeypatch.context() as m:
+ m.setattr(sys, "platform", "darwin")
+ m.setattr(os, "path", posixpath)
+ importlib.reload(platformdirs)
+ importlib.reload(appdirs)
+ yield
+
+ importlib.reload(platformdirs)
+ importlib.reload(appdirs)
+
+
+@pytest.fixture()
+def platformdirs_linux(monkeypatch):
+ # Monkeypatch platformdirs to pretend we're running on Linux
+
+ with monkeypatch.context() as m:
+ m.setattr(sys, "platform", "linux")
+ m.setattr(os, "path", posixpath)
+ importlib.reload(platformdirs)
+ importlib.reload(appdirs)
+ yield
+
+ importlib.reload(platformdirs)
+ importlib.reload(appdirs)
+
+
class TestUserCacheDir:
- def test_user_cache_dir_win(self, monkeypatch: pytest.MonkeyPatch) -> None:
+ def test_user_cache_dir_win(self, monkeypatch: pytest.MonkeyPatch, platformdirs_win32) -> None:
_get_win_folder = mock.Mock(return_value="C:\\Users\\test\\AppData\\Local")
monkeypatch.setattr(
- _appdirs,
+ platformdirs,
"_get_win_folder",
_get_win_folder,
raising=False,
)
- monkeypatch.setattr(_appdirs, "system", "win32")
- monkeypatch.setattr(os, "path", ntpath)
assert (
appdirs.user_cache_dir("pip")
@@ -29,43 +73,31 @@ class TestUserCacheDir:
)
assert _get_win_folder.call_args_list == [mock.call("CSIDL_LOCAL_APPDATA")]
- def test_user_cache_dir_osx(self, monkeypatch: pytest.MonkeyPatch) -> None:
- monkeypatch.setattr(_appdirs, "system", "darwin")
- monkeypatch.setattr(os, "path", posixpath)
+ def test_user_cache_dir_osx(self, monkeypatch: pytest.MonkeyPatch, platformdirs_darwin) -> None:
monkeypatch.setenv("HOME", "/home/test")
- monkeypatch.setattr(sys, "platform", "darwin")
assert appdirs.user_cache_dir("pip") == "/home/test/Library/Caches/pip"
- def test_user_cache_dir_linux(self, monkeypatch: pytest.MonkeyPatch) -> None:
- monkeypatch.setattr(_appdirs, "system", "linux2")
- monkeypatch.setattr(os, "path", posixpath)
+ def test_user_cache_dir_linux(self, monkeypatch: pytest.MonkeyPatch, platformdirs_linux) -> None:
monkeypatch.delenv("XDG_CACHE_HOME", raising=False)
monkeypatch.setenv("HOME", "/home/test")
- monkeypatch.setattr(sys, "platform", "linux2")
assert appdirs.user_cache_dir("pip") == "/home/test/.cache/pip"
def test_user_cache_dir_linux_override(
- self, monkeypatch: pytest.MonkeyPatch
+ self, monkeypatch: pytest.MonkeyPatch, platformdirs_linux
) -> None:
- monkeypatch.setattr(_appdirs, "system", "linux2")
- monkeypatch.setattr(os, "path", posixpath)
monkeypatch.setenv("XDG_CACHE_HOME", "/home/test/.other-cache")
monkeypatch.setenv("HOME", "/home/test")
- monkeypatch.setattr(sys, "platform", "linux2")
assert appdirs.user_cache_dir("pip") == "/home/test/.other-cache/pip"
def test_user_cache_dir_linux_home_slash(
- self, monkeypatch: pytest.MonkeyPatch
+ self, monkeypatch: pytest.MonkeyPatch, platformdirs_linux
) -> None:
- monkeypatch.setattr(_appdirs, "system", "linux2")
- monkeypatch.setattr(os, "path", posixpath)
# Verify that we are not affected by https://bugs.python.org/issue14768
monkeypatch.delenv("XDG_CACHE_HOME", raising=False)
monkeypatch.setenv("HOME", "/")
- monkeypatch.setattr(sys, "platform", "linux2")
assert appdirs.user_cache_dir("pip") == "/.cache/pip"
@@ -76,7 +108,7 @@ class TestUserCacheDir:
def my_get_win_folder(csidl_name):
return "\u00DF\u00E4\u03B1\u20AC"
- monkeypatch.setattr(_appdirs, "_get_win_folder", my_get_win_folder)
+ monkeypatch.setattr(platformdirs, "_get_win_folder", my_get_win_folder)
# Do not use the isinstance expression directly in the
# assert statement, as the Unicode characters in the result
@@ -91,45 +123,38 @@ class TestUserCacheDir:
class TestSiteConfigDirs:
- def test_site_config_dirs_win(self, monkeypatch: pytest.MonkeyPatch) -> None:
+ def test_site_config_dirs_win(self, monkeypatch: pytest.MonkeyPatch, platformdirs_win32) -> None:
_get_win_folder = mock.Mock(return_value="C:\\ProgramData")
monkeypatch.setattr(
- _appdirs,
+ platformdirs,
"_get_win_folder",
_get_win_folder,
raising=False,
)
- monkeypatch.setattr(_appdirs, "system", "win32")
- monkeypatch.setattr(os, "path", ntpath)
assert appdirs.site_config_dirs("pip") == ["C:\\ProgramData\\pip"]
assert _get_win_folder.call_args_list == [mock.call("CSIDL_COMMON_APPDATA")]
- def test_site_config_dirs_osx(self, monkeypatch: pytest.MonkeyPatch) -> None:
- monkeypatch.setattr(_appdirs, "system", "darwin")
- monkeypatch.setattr(os, "path", posixpath)
+ def test_site_config_dirs_osx(self, monkeypatch: pytest.MonkeyPatch, platformdirs_darwin) -> None:
monkeypatch.setenv("HOME", "/home/test")
- monkeypatch.setattr(sys, "platform", "darwin")
- assert appdirs.site_config_dirs("pip") == ["/Library/Application Support/pip"]
+ assert appdirs.site_config_dirs("pip") == [
+ "/Library/Preferences/pip",
+ "/Library/Application Support/pip"
+ ]
- def test_site_config_dirs_linux(self, monkeypatch: pytest.MonkeyPatch) -> None:
- monkeypatch.setattr(_appdirs, "system", "linux2")
- monkeypatch.setattr(os, "path", posixpath)
+ def test_site_config_dirs_linux(self, monkeypatch: pytest.MonkeyPatch, platformdirs_linux) -> None:
monkeypatch.delenv("XDG_CONFIG_DIRS", raising=False)
- monkeypatch.setattr(sys, "platform", "linux2")
assert appdirs.site_config_dirs("pip") == ["/etc/xdg/pip", "/etc"]
def test_site_config_dirs_linux_override(
- self, monkeypatch: pytest.MonkeyPatch
+ self, monkeypatch: pytest.MonkeyPatch, platformdirs_linux
) -> None:
- monkeypatch.setattr(_appdirs, "system", "linux2")
monkeypatch.setattr(os, "path", posixpath)
- monkeypatch.setattr(os, "pathsep", ":")
+ monkeypatch.setattr(os, "pathsep", ':')
monkeypatch.setenv("XDG_CONFIG_DIRS", "/spam:/etc:/etc/xdg")
- monkeypatch.setattr(sys, "platform", "linux2")
assert appdirs.site_config_dirs("pip") == [
"/spam/pip",
@@ -139,30 +164,26 @@ class TestSiteConfigDirs:
]
def test_site_config_dirs_linux_empty(
- self, monkeypatch: pytest.MonkeyPatch
+ self, monkeypatch: pytest.MonkeyPatch, platformdirs_linux
) -> None:
- monkeypatch.setattr(_appdirs, "system", "linux2")
monkeypatch.setattr(os, "path", posixpath)
- monkeypatch.setattr(os, "pathsep", ":")
+ monkeypatch.setattr(os, "pathsep", ':')
monkeypatch.setenv("XDG_CONFIG_DIRS", "")
- monkeypatch.setattr(sys, "platform", "linux2")
- assert appdirs.site_config_dirs("pip") == ["/etc/xdg/pip", "/etc"]
+ assert appdirs.site_config_dirs("pip") == ['/etc/xdg/pip', '/etc']
class TestUserConfigDir:
def test_user_config_dir_win_no_roaming(
- self, monkeypatch: pytest.MonkeyPatch
+ self, monkeypatch: pytest.MonkeyPatch, platformdirs_win32
) -> None:
_get_win_folder = mock.Mock(return_value="C:\\Users\\test\\AppData\\Local")
monkeypatch.setattr(
- _appdirs,
+ platformdirs,
"_get_win_folder",
_get_win_folder,
raising=False,
)
- monkeypatch.setattr(_appdirs, "system", "win32")
- monkeypatch.setattr(os, "path", ntpath)
assert (
appdirs.user_config_dir("pip", roaming=False)
@@ -171,29 +192,24 @@ class TestUserConfigDir:
assert _get_win_folder.call_args_list == [mock.call("CSIDL_LOCAL_APPDATA")]
def test_user_config_dir_win_yes_roaming(
- self, monkeypatch: pytest.MonkeyPatch
+ self, monkeypatch: pytest.MonkeyPatch, platformdirs_win32
) -> None:
_get_win_folder = mock.Mock(return_value="C:\\Users\\test\\AppData\\Roaming")
monkeypatch.setattr(
- _appdirs,
+ platformdirs,
"_get_win_folder",
_get_win_folder,
raising=False,
)
- monkeypatch.setattr(_appdirs, "system", "win32")
- monkeypatch.setattr(os, "path", ntpath)
assert (
appdirs.user_config_dir("pip") == "C:\\Users\\test\\AppData\\Roaming\\pip"
)
assert _get_win_folder.call_args_list == [mock.call("CSIDL_APPDATA")]
- def test_user_config_dir_osx(self, monkeypatch: pytest.MonkeyPatch) -> None:
- monkeypatch.setattr(_appdirs, "system", "darwin")
- monkeypatch.setattr(os, "path", posixpath)
+ def test_user_config_dir_osx(self, monkeypatch: pytest.MonkeyPatch, platformdirs_darwin) -> None:
monkeypatch.setenv("HOME", "/home/test")
- monkeypatch.setattr(sys, "platform", "darwin")
if os.path.isdir("/home/test/Library/Application Support/"):
assert (
@@ -203,34 +219,25 @@ class TestUserConfigDir:
else:
assert appdirs.user_config_dir("pip") == "/home/test/.config/pip"
- def test_user_config_dir_linux(self, monkeypatch: pytest.MonkeyPatch) -> None:
- monkeypatch.setattr(_appdirs, "system", "linux2")
- monkeypatch.setattr(os, "path", posixpath)
+ def test_user_config_dir_linux(self, monkeypatch: pytest.MonkeyPatch, platformdirs_darwin) -> None:
monkeypatch.delenv("XDG_CONFIG_HOME", raising=False)
monkeypatch.setenv("HOME", "/home/test")
- monkeypatch.setattr(sys, "platform", "linux2")
assert appdirs.user_config_dir("pip") == "/home/test/.config/pip"
def test_user_config_dir_linux_override(
- self, monkeypatch: pytest.MonkeyPatch
+ self, monkeypatch: pytest.MonkeyPatch, platformdirs_linux
) -> None:
- monkeypatch.setattr(_appdirs, "system", "linux2")
- monkeypatch.setattr(os, "path", posixpath)
monkeypatch.setenv("XDG_CONFIG_HOME", "/home/test/.other-config")
monkeypatch.setenv("HOME", "/home/test")
- monkeypatch.setattr(sys, "platform", "linux2")
assert appdirs.user_config_dir("pip") == "/home/test/.other-config/pip"
def test_user_config_dir_linux_home_slash(
- self, monkeypatch: pytest.MonkeyPatch
+ self, monkeypatch: pytest.MonkeyPatch, platformdirs_linux
) -> None:
- monkeypatch.setattr(_appdirs, "system", "linux2")
- monkeypatch.setattr(os, "path", posixpath)
# Verify that we are not affected by https://bugs.python.org/issue14768
monkeypatch.delenv("XDG_CONFIG_HOME", raising=False)
monkeypatch.setenv("HOME", "/")
- monkeypatch.setattr(sys, "platform", "linux2")
assert appdirs.user_config_dir("pip") == "/.config/pip"
diff --git a/tools/vendoring/patches/appdirs.patch b/tools/vendoring/patches/appdirs.patch
deleted file mode 100644
index 69afd3e86..000000000
--- a/tools/vendoring/patches/appdirs.patch
+++ /dev/null
@@ -1,115 +0,0 @@
-diff --git a/src/pip/_vendor/appdirs.py b/src/pip/_vendor/appdirs.py
-index ae67001a..3a52b758 100644
---- a/src/pip/_vendor/appdirs.py
-+++ b/src/pip/_vendor/appdirs.py
-@@ -37,6 +37,10 @@ if sys.platform.startswith('java'):
- # are actually checked for and the rest of the module expects
- # *sys.platform* style strings.
- system = 'linux2'
-+elif sys.platform == 'cli' and os.name == 'nt':
-+ # Detect Windows in IronPython to match pip._internal.utils.compat.WINDOWS
-+ # Discussion: <https://github.com/pypa/pip/pull/7501>
-+ system = 'win32'
- else:
- system = sys.platform
-
-@@ -64,7 +68,7 @@ def user_data_dir(appname=None, appauthor=None, version=None, roaming=False):
- for a discussion of issues.
-
- Typical user data directories are:
-- Mac OS X: ~/Library/Application Support/<AppName>
-+ Mac OS X: ~/Library/Application Support/<AppName> # or ~/.config/<AppName>, if the other does not exist
- Unix: ~/.local/share/<AppName> # or in $XDG_DATA_HOME, if defined
- Win XP (not roaming): C:\Documents and Settings\<username>\Application Data\<AppAuthor>\<AppName>
- Win XP (roaming): C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>
-@@ -150,7 +154,7 @@ def site_data_dir(appname=None, appauthor=None, version=None, multipath=False):
- if appname:
- if version:
- appname = os.path.join(appname, version)
-- pathlist = [os.sep.join([x, appname]) for x in pathlist]
-+ pathlist = [os.path.join(x, appname) for x in pathlist]
-
- if multipath:
- path = os.pathsep.join(pathlist)
-@@ -203,6 +203,8 @@ def user_config_dir(appname=None, appauthor=None, version=None, roaming=False):
- return path
-
-
-+# for the discussion regarding site_config_dir locations
-+# see <https://github.com/pypa/pip/issues/1733>
- def site_config_dir(appname=None, appauthor=None, version=None, multipath=False):
- r"""Return full path to the user-shared data dir for this application.
-
-@@ -238,14 +244,15 @@ def site_config_dir(appname=None, appauthor=None, version=None, multipath=False)
- if appname and version:
- path = os.path.join(path, version)
- else:
-- # XDG default for $XDG_CONFIG_DIRS
-+ # XDG default for $XDG_CONFIG_DIRS (missing or empty)
-+ # see <https://github.com/pypa/pip/pull/7501#discussion_r360624829>
- # only first, if multipath is False
-- path = os.getenv('XDG_CONFIG_DIRS', '/etc/xdg')
-- pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)]
-+ path = os.getenv('XDG_CONFIG_DIRS') or '/etc/xdg'
-+ pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep) if x]
- if appname:
- if version:
- appname = os.path.join(appname, version)
-- pathlist = [os.sep.join([x, appname]) for x in pathlist]
-+ pathlist = [os.path.join(x, appname) for x in pathlist]
-
- if multipath:
- path = os.pathsep.join(pathlist)
-@@ -291,6 +300,10 @@ def user_cache_dir(appname=None, appauthor=None, version=None, opinion=True):
- if appauthor is None:
- appauthor = appname
- path = os.path.normpath(_get_win_folder("CSIDL_LOCAL_APPDATA"))
-+ # When using Python 2, return paths as bytes on Windows like we do on
-+ # other operating systems. See helper function docs for more details.
-+ if not PY3 and isinstance(path, unicode):
-+ path = _win_path_to_bytes(path)
- if appname:
- if appauthor is not False:
- path = os.path.join(path, appauthor, appname)
-@@ -557,18 +570,32 @@ def _get_win_folder_with_jna(csidl_name):
-
- if system == "win32":
- try:
-- import win32com.shell
-- _get_win_folder = _get_win_folder_with_pywin32
-+ from ctypes import windll
-+ _get_win_folder = _get_win_folder_with_ctypes
- except ImportError:
- try:
-- from ctypes import windll
-- _get_win_folder = _get_win_folder_with_ctypes
-+ import com.sun.jna
-+ _get_win_folder = _get_win_folder_with_jna
- except ImportError:
-- try:
-- import com.sun.jna
-- _get_win_folder = _get_win_folder_with_jna
-- except ImportError:
-- _get_win_folder = _get_win_folder_from_registry
-+ _get_win_folder = _get_win_folder_from_registry
-+
-+
-+def _win_path_to_bytes(path):
-+ """Encode Windows paths to bytes. Only used on Python 2.
-+
-+ Motivation is to be consistent with other operating systems where paths
-+ are also returned as bytes. This avoids problems mixing bytes and Unicode
-+ elsewhere in the codebase. For more details and discussion see
-+ <https://github.com/pypa/pip/issues/3463>.
-+
-+ If encoding using ASCII and MBCS fails, return the original Unicode path.
-+ """
-+ for encoding in ('ASCII', 'MBCS'):
-+ try:
-+ return path.encode(encoding)
-+ except (UnicodeEncodeError, LookupError):
-+ pass
-+ return path
-
-
- #---- self test code
diff --git a/tools/vendoring/patches/pkg_resources.patch b/tools/vendoring/patches/pkg_resources.patch
new file mode 100644
index 000000000..eea49e475
--- /dev/null
+++ b/tools/vendoring/patches/pkg_resources.patch
@@ -0,0 +1,22 @@
+diff --git a/src/pip/_vendor/pkg_resources/__init__.py b/src/pip/_vendor/pkg_resources/__init__.py
+index a457ff27e..4cd562cf9 100644
+--- a/src/pip/_vendor/pkg_resources/__init__.py
++++ b/src/pip/_vendor/pkg_resources/__init__.py
+@@ -77,7 +77,7 @@
+ importlib_machinery = None
+
+ from . import py31compat
+-from pip._vendor import appdirs
++from pip._vendor import platformdirs
+ from pip._vendor import packaging
+ __import__('pip._vendor.packaging.version')
+ __import__('pip._vendor.packaging.specifiers')
+@@ -1310,7 +1310,7 @@
+ """
+ return (
+ os.environ.get('PYTHON_EGG_CACHE')
+- or appdirs.user_cache_dir(appname='Python-Eggs')
++ or platformdirs.user_cache_dir(appname='Python-Eggs')
+ )
+
+
diff --git a/tools/vendoring/patches/platformdirs.patch b/tools/vendoring/patches/platformdirs.patch
new file mode 100644
index 000000000..008312441
--- /dev/null
+++ b/tools/vendoring/patches/platformdirs.patch
@@ -0,0 +1,20 @@
+diff --git a/src/pip/_vendor/platformdirs.py b/src/pip/_vendor/platformdirs.py
+index 23c6af8c7..3b8ef8ad8 100644
+--- a/src/pip/_vendor/platformdirs.py
++++ b/src/pip/_vendor/platformdirs.py
+@@ -327,12 +327,10 @@ else:
+ return path
+
+ def _site_config_dir_impl(appname=None, appauthor=None, version=None, multipath=False):
+- # XDG default for $XDG_CONFIG_DIRS
++ # XDG default for $XDG_CONFIG_DIRSS (missing or empty)
++ # see <https://github.com/pypa/pip/pull/7501#discussion_r360624829>
+ # only first, if multipath is False
+- if 'XDG_CONFIG_DIRS' in os.environ:
+- path = os.environ['XDG_CONFIG_DIRS']
+- else:
+- path = '/etc/xdg'
++ path = os.getenv('XDG_CONFIG_DIRS') or '/etc/xdg'
+
+ pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)]
+ if appname: