diff --git a/pyproject.toml b/pyproject.toml index a479b968d6d..4ec4d1421ab 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 a8403b7dee4..eb77104496b 100644 --- a/src/pip/_internal/utils/appdirs.py +++ b/src/pip/_internal/utils/appdirs.py @@ -9,7 +9,7 @@ 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 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 57e32dab105..3843cb09955 100644 --- a/src/pip/_vendor/__init__.py +++ b/src/pip/_vendor/__init__.py @@ -58,7 +58,6 @@ def vendored(modulename): 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 @@ def vendored(modulename): 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 a457ff27ef0..4cd562cf94c 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 @@ 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 similarity index 100% rename from src/pip/_vendor/appdirs.LICENSE.txt rename to src/pip/_vendor/platformdirs.LICENSE.txt diff --git a/src/pip/_vendor/appdirs.py b/src/pip/_vendor/platformdirs.py similarity index 61% rename from src/pip/_vendor/appdirs.py rename to src/pip/_vendor/platformdirs.py index 33a3b77410c..3b8ef8ad8cc 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 for details and usage. +See 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 @@ # 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: - 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 + # . + 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 + # . + 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 + # 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/ # or ~/.config/, if the other does not exist + Mac OS X: ~/Library/Application Support/ Unix: ~/.local/share/ # or in $XDG_DATA_HOME, if defined Win XP (not roaming): C:\Documents and Settings\\Application Data\\ Win XP (roaming): C:\Documents and Settings\\Local Settings\Application Data\\ @@ -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/". """ - 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/ Unix: ~/.config/ # 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/". """ - 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 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 - # 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/". """ - 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 @@ def user_log_dir(self): 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 - # . - 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 - # . - 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 - # . - 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 - . - - 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 @@ def _win_path_to_bytes(path): 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 ea0c213e72a..918d19344ad 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.2 @@ -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 6a3eb00e3b9..8535243b78a 100644 --- a/tests/unit/test_appdirs.py +++ b/tests/unit/test_appdirs.py @@ -1,26 +1,72 @@ +import importlib import ntpath import os import posixpath import sys from unittest import mock -from pip._vendor import appdirs as _appdirs +import pytest +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): + + def test_user_cache_dir_win(self, monkeypatch, platformdirs_win32): _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") @@ -28,39 +74,27 @@ def test_user_cache_dir_win(self, monkeypatch): ) assert _get_win_folder.call_args_list == [mock.call("CSIDL_LOCAL_APPDATA")] - def test_user_cache_dir_osx(self, monkeypatch): - monkeypatch.setattr(_appdirs, "system", "darwin") - monkeypatch.setattr(os, "path", posixpath) + def test_user_cache_dir_osx(self, monkeypatch, platformdirs_darwin): 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): - monkeypatch.setattr(_appdirs, "system", "linux2") - monkeypatch.setattr(os, "path", posixpath) + def test_user_cache_dir_linux(self, monkeypatch, platformdirs_linux): 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): - monkeypatch.setattr(_appdirs, "system", "linux2") - monkeypatch.setattr(os, "path", posixpath) + def test_user_cache_dir_linux_override(self, monkeypatch, platformdirs_linux): 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): - monkeypatch.setattr(_appdirs, "system", "linux2") - monkeypatch.setattr(os, "path", posixpath) + def test_user_cache_dir_linux_home_slash(self, monkeypatch, platformdirs_linux): # 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" @@ -71,7 +105,7 @@ def test_user_cache_dir_unicode(self, monkeypatch): 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 @@ -86,43 +120,39 @@ def my_get_win_folder(csidl_name): class TestSiteConfigDirs: - def test_site_config_dirs_win(self, monkeypatch): + + def test_site_config_dirs_win(self, monkeypatch, platformdirs_win32): + _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): - monkeypatch.setattr(_appdirs, "system", "darwin") - monkeypatch.setattr(os, "path", posixpath) + def test_site_config_dirs_osx(self, monkeypatch, platformdirs_darwin): 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): - monkeypatch.setattr(_appdirs, "system", "linux2") - monkeypatch.setattr(os, "path", posixpath) + + def test_site_config_dirs_linux(self, monkeypatch, platformdirs_linux): 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): - monkeypatch.setattr(_appdirs, "system", "linux2") + def test_site_config_dirs_linux_override(self, monkeypatch, platformdirs_linux): 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", @@ -131,27 +161,24 @@ def test_site_config_dirs_linux_override(self, monkeypatch): "/etc", ] - def test_site_config_dirs_linux_empty(self, monkeypatch): - monkeypatch.setattr(_appdirs, "system", "linux2") + def test_site_config_dirs_linux_empty(self, monkeypatch, platformdirs_linux): 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): + + def test_user_config_dir_win_no_roaming(self, monkeypatch, platformdirs_win32): _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) @@ -159,28 +186,24 @@ def test_user_config_dir_win_no_roaming(self, monkeypatch): ) assert _get_win_folder.call_args_list == [mock.call("CSIDL_LOCAL_APPDATA")] - def test_user_config_dir_win_yes_roaming(self, monkeypatch): + def test_user_config_dir_win_yes_roaming(self, monkeypatch, platformdirs_win32): + _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): - monkeypatch.setattr(_appdirs, "system", "darwin") - monkeypatch.setattr(os, "path", posixpath) + def test_user_config_dir_osx(self, monkeypatch, platformdirs_darwin): monkeypatch.setenv("HOME", "/home/test") - monkeypatch.setattr(sys, "platform", "darwin") if os.path.isdir("/home/test/Library/Application Support/"): assert ( @@ -190,30 +213,21 @@ def test_user_config_dir_osx(self, monkeypatch): else: assert appdirs.user_config_dir("pip") == "/home/test/.config/pip" - def test_user_config_dir_linux(self, monkeypatch): - monkeypatch.setattr(_appdirs, "system", "linux2") - monkeypatch.setattr(os, "path", posixpath) + def test_user_config_dir_linux(self, monkeypatch, platformdirs_darwin): 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): - monkeypatch.setattr(_appdirs, "system", "linux2") - monkeypatch.setattr(os, "path", posixpath) + def test_user_config_dir_linux_override(self, monkeypatch, platformdirs_linux): 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): - monkeypatch.setattr(_appdirs, "system", "linux2") - monkeypatch.setattr(os, "path", posixpath) + def test_user_config_dir_linux_home_slash(self, monkeypatch, platformdirs_linux): # 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 69afd3e8681..00000000000 --- 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: -+ 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/ -+ Mac OS X: ~/Library/Application Support/ # or ~/.config/, if the other does not exist - Unix: ~/.local/share/ # or in $XDG_DATA_HOME, if defined - Win XP (not roaming): C:\Documents and Settings\\Application Data\\ - Win XP (roaming): C:\Documents and Settings\\Local Settings\Application Data\\ -@@ -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 - 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 - # 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 -+ . -+ -+ 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 00000000000..eea49e475af --- /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 00000000000..00831244191 --- /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 + # 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: