From 572694adc951836c60a92fe38c8612ad06a09da3 Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Mon, 18 Jul 2022 04:48:31 +0800 Subject: [PATCH] Avoid importing distutils globally MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The distutils module is deprecated in 3.11 so we should avoid importing it unless absolutely necessary to avoid emitting superfulous warnings. Rebased from https://github.com/pypa/pip/commit/0e06452530 Also includes: Import distutils only if needed, but sooner Rebased from: https://github.com/pypa/pip/commit/db47515958 Co-Authored-By: Stéphane Bidoul --- src/pip/_internal/locations/__init__.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/pip/_internal/locations/__init__.py b/src/pip/_internal/locations/__init__.py index dba182d..4cb45dc 100644 --- a/src/pip/_internal/locations/__init__.py +++ b/src/pip/_internal/locations/__init__.py @@ -11,7 +11,7 @@ from pip._internal.utils.compat import WINDOWS from pip._internal.utils.deprecation import deprecated from pip._internal.utils.virtualenv import running_under_virtualenv -from . import _distutils, _sysconfig +from . import _sysconfig from .base import ( USER_CACHE_DIR, get_major_minor_version, @@ -47,6 +47,12 @@ _PLATLIBDIR: str = getattr(sys, "platlibdir", "lib") _USE_SYSCONFIG = sys.version_info >= (3, 10) +if not _USE_SYSCONFIG: + # Import distutils lazily to avoid deprecation warnings, + # but import it soon enough that it is in memory and available during + # a pip reinstall. + from . import _distutils + def _looks_like_bpo_44860() -> bool: """The resolution to bpo-44860 will change this incorrect platlib. -- 2.38.1 From 942f43f0bab6f8245f2597a4fd376702bffb88e9 Mon Sep 17 00:00:00 2001 From: Pradyun Gedam Date: Fri, 17 Jun 2022 13:50:25 +0100 Subject: [PATCH] Replace usage of `distutils.utils.change_root` with copied-over logic This eliminates an import from distutils, by pulling in the relevant code into pip itself. Rebased from https://github.com/pypa/pip/commit/c520ccf75d --- src/pip/_internal/locations/_sysconfig.py | 5 ++-- src/pip/_internal/locations/base.py | 28 +++++++++++++++++++ .../_internal/operations/install/legacy.py | 2 +- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/pip/_internal/locations/_sysconfig.py b/src/pip/_internal/locations/_sysconfig.py index 5e141aa..0bbc928 100644 --- a/src/pip/_internal/locations/_sysconfig.py +++ b/src/pip/_internal/locations/_sysconfig.py @@ -1,4 +1,3 @@ -import distutils.util # FIXME: For change_root. import logging import os import sys @@ -9,7 +8,7 @@ from pip._internal.exceptions import InvalidSchemeCombination, UserInstallationI from pip._internal.models.scheme import SCHEME_KEYS, Scheme from pip._internal.utils.virtualenv import running_under_virtualenv -from .base import get_major_minor_version, is_osx_framework +from .base import change_root, get_major_minor_version, is_osx_framework logger = logging.getLogger(__name__) @@ -194,7 +193,7 @@ def get_scheme( ) if root is not None: for key in SCHEME_KEYS: - value = distutils.util.change_root(root, getattr(scheme, key)) + value = change_root(root, getattr(scheme, key)) setattr(scheme, key, value) return scheme diff --git a/src/pip/_internal/locations/base.py b/src/pip/_internal/locations/base.py index 86dad4a..d73676d 100644 --- a/src/pip/_internal/locations/base.py +++ b/src/pip/_internal/locations/base.py @@ -5,6 +5,7 @@ import sys import sysconfig import typing +from pip._internal.exceptions import InstallationError from pip._internal.utils import appdirs from pip._internal.utils.virtualenv import running_under_virtualenv @@ -22,6 +23,33 @@ def get_major_minor_version() -> str: """ return "{}.{}".format(*sys.version_info) +def change_root(new_root: str, pathname: str) -> str: + """Return 'pathname' with 'new_root' prepended. + + If 'pathname' is relative, this is equivalent to os.path.join(new_root, pathname). + Otherwise, it requires making 'pathname' relative and then joining the + two, which is tricky on DOS/Windows and Mac OS. + + This is borrowed from Python's standard library's distutils module. + """ + if os.name == "posix": + if not os.path.isabs(pathname): + return os.path.join(new_root, pathname) + else: + return os.path.join(new_root, pathname[1:]) + + elif os.name == "nt": + (drive, path) = os.path.splitdrive(pathname) + if path[0] == "\\": + path = path[1:] + return os.path.join(new_root, path) + + else: + raise InstallationError( + f"Unknown platform: {os.name}\n" + "Can not change root path prefix on unknown platform." + ) + def get_src_prefix() -> str: if running_under_virtualenv(): diff --git a/src/pip/_internal/operations/install/legacy.py b/src/pip/_internal/operations/install/legacy.py index 2206c93..4281cb0 100644 --- a/src/pip/_internal/operations/install/legacy.py +++ b/src/pip/_internal/operations/install/legacy.py @@ -3,11 +3,11 @@ import logging import os -from distutils.util import change_root from typing import List, Optional, Sequence from pip._internal.build_env import BuildEnvironment from pip._internal.exceptions import InstallationError +from pip._internal.locations.base import change_root from pip._internal.models.scheme import Scheme from pip._internal.utils.logging import indent_log from pip._internal.utils.misc import ensure_dir -- 2.38.1 From 4ef5c7eac10bb1d88568af53373491fe6febfa63 Mon Sep 17 00:00:00 2001 From: Pradyun Gedam Date: Fri, 17 Jun 2022 15:30:50 +0100 Subject: [PATCH] Replace `distutils.fancy_getopt` with `getopt` This eliminates one location where distutils may be imported on Python 3.12+, by replacing the logic with mostly equivalent logic. Rebased from: https://github.com/pypa/pip/commit/8cbb89b6cc --- src/pip/_internal/utils/distutils_args.py | 51 ++++++++++++----------- tests/unit/test_utils_distutils_args.py | 2 +- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src/pip/_internal/utils/distutils_args.py b/src/pip/_internal/utils/distutils_args.py index e4aa5b8..2fd1862 100644 --- a/src/pip/_internal/utils/distutils_args.py +++ b/src/pip/_internal/utils/distutils_args.py @@ -1,42 +1,43 @@ -from distutils.errors import DistutilsArgError -from distutils.fancy_getopt import FancyGetopt +from getopt import GetoptError, getopt from typing import Dict, List _options = [ - ("exec-prefix=", None, ""), - ("home=", None, ""), - ("install-base=", None, ""), - ("install-data=", None, ""), - ("install-headers=", None, ""), - ("install-lib=", None, ""), - ("install-platlib=", None, ""), - ("install-purelib=", None, ""), - ("install-scripts=", None, ""), - ("prefix=", None, ""), - ("root=", None, ""), - ("user", None, ""), + "exec-prefix=", + "home=", + "install-base=", + "install-data=", + "install-headers=", + "install-lib=", + "install-platlib=", + "install-purelib=", + "install-scripts=", + "prefix=", + "root=", + "user", ] -# typeshed doesn't permit Tuple[str, None, str], see python/typeshed#3469. -_distutils_getopt = FancyGetopt(_options) # type: ignore - - def parse_distutils_args(args: List[str]) -> Dict[str, str]: - """Parse provided arguments, returning an object that has the - matched arguments. + """Parse provided arguments, returning an object that has the matched arguments. Any unknown arguments are ignored. """ result = {} for arg in args: try: - _, match = _distutils_getopt.getopt(args=[arg]) - except DistutilsArgError: + parsed_opt, _ = getopt(args=[arg], shortopts="", longopts=_options) + except GetoptError: # We don't care about any other options, which here may be # considered unrecognized since our option list is not # exhaustive. - pass - else: - result.update(match.__dict__) + continue + + if not parsed_opt: + continue + + option = parsed_opt[0] + name_from_parsed = option[0][2:].replace("-", "_") + value_from_parsed = option[1] or "true" + result[name_from_parsed] = value_from_parsed + return result diff --git a/tests/unit/test_utils_distutils_args.py b/tests/unit/test_utils_distutils_args.py index e63c565..21f31e9 100644 --- a/tests/unit/test_utils_distutils_args.py +++ b/tests/unit/test_utils_distutils_args.py @@ -60,4 +60,4 @@ def test_all_value_options_work(name: str, value: str) -> None: def test_user_option_works() -> None: result = parse_distutils_args(["--user"]) - assert result["user"] == 1 + assert result["user"] -- 2.38.1