Blob Blame History Raw
From 572694adc951836c60a92fe38c8612ad06a09da3 Mon Sep 17 00:00:00 2001
From: Tzu-ping Chung <uranusjr@gmail.com>
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 <stephane.bidoul@gmail.com>
---
 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 <pgedam@bloomberg.net>
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 <pgedam@bloomberg.net>
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