7 from typing
import Any
, Dict
, Generator
, Optional
, Tuple
9 from pip
._internal
.models
.scheme
import SCHEME_KEYS
, Scheme
10 from pip
._internal
.utils
.compat
import WINDOWS
11 from pip
._internal
.utils
.deprecation
import deprecated
12 from pip
._internal
.utils
.virtualenv
import running_under_virtualenv
14 from . import _sysconfig
17 get_major_minor_version
,
28 "get_major_minor_version",
38 logger
= logging
.getLogger(__name__
)
41 _PLATLIBDIR
: str = getattr(sys
, "platlibdir", "lib")
43 _USE_SYSCONFIG_DEFAULT
= sys
.version_info
>= (3, 10)
46 def _should_use_sysconfig() -> bool:
47 """This function determines the value of _USE_SYSCONFIG.
49 By default, pip uses sysconfig on Python 3.10+.
50 But Python distributors can override this decision by setting:
51 sysconfig._PIP_USE_SYSCONFIG = True / False
52 Rationale in https://github.com/pypa/pip/issues/10647
54 This is a function for testability, but should be constant during any one
57 return bool(getattr(sysconfig
, "_PIP_USE_SYSCONFIG", _USE_SYSCONFIG_DEFAULT
))
60 _USE_SYSCONFIG
= _should_use_sysconfig()
62 if not _USE_SYSCONFIG
:
63 # Import distutils lazily to avoid deprecation warnings,
64 # but import it soon enough that it is in memory and available during
66 from . import _distutils
68 # Be noisy about incompatibilities if this platforms "should" be using
69 # sysconfig, but is explicitly opting out and using distutils instead.
70 if _USE_SYSCONFIG_DEFAULT
and not _USE_SYSCONFIG
:
71 _MISMATCH_LEVEL
= logging
.WARNING
73 _MISMATCH_LEVEL
= logging
.DEBUG
76 def _looks_like_bpo_44860() -> bool:
77 """The resolution to bpo-44860 will change this incorrect platlib.
79 See <https://bugs.python.org/issue44860>.
81 from distutils
.command
.install
import INSTALL_SCHEMES
84 unix_user_platlib
= INSTALL_SCHEMES
["unix_user"]["platlib"]
87 return unix_user_platlib
== "$usersite"
90 def _looks_like_red_hat_patched_platlib_purelib(scheme
: Dict
[str, str]) -> bool:
91 platlib
= scheme
["platlib"]
92 if "/$platlibdir/" in platlib
:
93 platlib
= platlib
.replace("/$platlibdir/", f
"/{_PLATLIBDIR}/")
94 if "/lib64/" not in platlib
:
96 unpatched
= platlib
.replace("/lib64/", "/lib/")
97 return unpatched
.replace("$platbase/", "$base/") == scheme
["purelib"]
100 @functools.lru_cache(maxsize
=None)
101 def _looks_like_red_hat_lib() -> bool:
102 """Red Hat patches platlib in unix_prefix and unix_home, but not purelib.
104 This is the only way I can see to tell a Red Hat-patched Python.
106 from distutils
.command
.install
import INSTALL_SCHEMES
110 and _looks_like_red_hat_patched_platlib_purelib(INSTALL_SCHEMES
[k
])
111 for k
in ("unix_prefix", "unix_home")
115 @functools.lru_cache(maxsize
=None)
116 def _looks_like_debian_scheme() -> bool:
117 """Debian adds two additional schemes."""
118 from distutils
.command
.install
import INSTALL_SCHEMES
120 return "deb_system" in INSTALL_SCHEMES
and "unix_local" in INSTALL_SCHEMES
123 @functools.lru_cache(maxsize
=None)
124 def _looks_like_red_hat_scheme() -> bool:
125 """Red Hat patches ``sys.prefix`` and ``sys.exec_prefix``.
127 Red Hat's ``00251-change-user-install-location.patch`` changes the install
128 command's ``prefix`` and ``exec_prefix`` to append ``"/local"``. This is
129 (fortunately?) done quite unconditionally, so we create a default command
130 object without any configuration to detect this.
132 from distutils
.command
.install
import install
133 from distutils
.dist
import Distribution
135 cmd
: Any
= install(Distribution())
136 cmd
.finalize_options()
138 cmd
.exec_prefix
== f
"{os.path.normpath(sys.exec_prefix)}/local"
139 and cmd
.prefix
== f
"{os.path.normpath(sys.prefix)}/local"
143 @functools.lru_cache(maxsize
=None)
144 def _looks_like_slackware_scheme() -> bool:
145 """Slackware patches sysconfig but fails to patch distutils and site.
147 Slackware changes sysconfig's user scheme to use ``"lib64"`` for the lib
148 path, but does not do the same to the site module.
150 if user_site
is None: # User-site not available.
153 paths
= sysconfig
.get_paths(scheme
="posix_user", expand
=False)
154 except KeyError: # User-site not available.
156 return "/lib64/" in paths
["purelib"] and "/lib64/" not in user_site
159 @functools.lru_cache(maxsize
=None)
160 def _looks_like_msys2_mingw_scheme() -> bool:
161 """MSYS2 patches distutils and sysconfig to use a UNIX-like scheme.
163 However, MSYS2 incorrectly patches sysconfig ``nt`` scheme. The fix is
164 likely going to be included in their 3.10 release, so we ignore the warning.
165 See msys2/MINGW-packages#9319.
167 MSYS2 MINGW's patch uses lowercase ``"lib"`` instead of the usual uppercase,
168 and is missing the final ``"site-packages"``.
170 paths
= sysconfig
.get_paths("nt", expand
=False)
172 "Lib" not in p
and "lib" in p
and not p
.endswith("site-packages")
173 for p
in (paths
[key
] for key
in ("platlib", "purelib"))
177 def _fix_abiflags(parts
: Tuple
[str]) -> Generator
[str, None, None]:
178 ldversion
= sysconfig
.get_config_var("LDVERSION")
179 abiflags
= getattr(sys
, "abiflags", None)
181 # LDVERSION does not end with sys.abiflags. Just return the path unchanged.
182 if not ldversion
or not abiflags
or not ldversion
.endswith(abiflags
):
186 # Strip sys.abiflags from LDVERSION-based path components.
188 if part
.endswith(ldversion
):
189 part
= part
[: (0 - len(abiflags
))]
193 @functools.lru_cache(maxsize
=None)
194 def _warn_mismatched(old
: pathlib
.Path
, new
: pathlib
.Path
, *, key
: str) -> None:
195 issue_url
= "https://github.com/pypa/pip/issues/10151"
197 "Value for %s does not match. Please report this to <%s>"
201 logger
.log(_MISMATCH_LEVEL
, message
, key
, issue_url
, old
, new
)
204 def _warn_if_mismatch(old
: pathlib
.Path
, new
: pathlib
.Path
, *, key
: str) -> bool:
207 _warn_mismatched(old
, new
, key
=key
)
211 @functools.lru_cache(maxsize
=None)
215 home
: Optional
[str] = None,
216 root
: Optional
[str] = None,
217 prefix
: Optional
[str] = None,
220 "Additional context:",
227 logger
.log(_MISMATCH_LEVEL
, "\n".join(parts
), user
, home
, root
, prefix
)
233 home
: Optional
[str] = None,
234 root
: Optional
[str] = None,
235 isolated
: bool = False,
236 prefix
: Optional
[str] = None,
238 new
= _sysconfig
.get_scheme(
249 old
= _distutils
.get_scheme(
258 warning_contexts
= []
259 for k
in SCHEME_KEYS
:
260 old_v
= pathlib
.Path(getattr(old
, k
))
261 new_v
= pathlib
.Path(getattr(new
, k
))
266 # distutils incorrectly put PyPy packages under ``site-packages/python``
267 # in the ``posix_home`` scheme, but PyPy devs said they expect the
268 # directory name to be ``pypy`` instead. So we treat this as a bug fix
269 # and not warn about it. See bpo-43307 and python/cpython#24628.
270 skip_pypy_special_case
= (
271 sys
.implementation
.name
== "pypy"
273 and k
in ("platlib", "purelib")
274 and old_v
.parent
== new_v
.parent
275 and old_v
.name
.startswith("python")
276 and new_v
.name
.startswith("pypy")
278 if skip_pypy_special_case
:
281 # sysconfig's ``osx_framework_user`` does not include ``pythonX.Y`` in
282 # the ``include`` value, but distutils's ``headers`` does. We'll let
283 # CPython decide whether this is a bug or feature. See bpo-43948.
284 skip_osx_framework_user_special_case
= (
286 and is_osx_framework()
288 and old_v
.parent
.parent
== new_v
.parent
289 and old_v
.parent
.name
.startswith("python")
291 if skip_osx_framework_user_special_case
:
294 # On Red Hat and derived Linux distributions, distutils is patched to
295 # use "lib64" instead of "lib" for platlib.
296 if k
== "platlib" and _looks_like_red_hat_lib():
299 # On Python 3.9+, sysconfig's posix_user scheme sets platlib against
300 # sys.platlibdir, but distutils's unix_user incorrectly coninutes
301 # using the same $usersite for both platlib and purelib. This creates a
302 # mismatch when sys.platlibdir is not "lib".
307 and sys
.version_info
>= (3, 9)
308 and _PLATLIBDIR
!= "lib"
309 and _looks_like_bpo_44860()
314 # Slackware incorrectly patches posix_user to use lib64 instead of lib,
315 # but not usersite to match the location.
316 skip_slackware_user_scheme
= (
318 and k
in ("platlib", "purelib")
320 and _looks_like_slackware_scheme()
322 if skip_slackware_user_scheme
:
325 # Both Debian and Red Hat patch Python to place the system site under
326 # /usr/local instead of /usr. Debian also places lib in dist-packages
327 # instead of site-packages, but the /usr/local check should cover it.
328 skip_linux_system_special_case
= (
329 not (user
or home
or prefix
or running_under_virtualenv())
330 and old_v
.parts
[1:3] == ("usr", "local")
331 and len(new_v
.parts
) > 1
332 and new_v
.parts
[1] == "usr"
333 and (len(new_v
.parts
) < 3 or new_v
.parts
[2] != "local")
334 and (_looks_like_red_hat_scheme() or _looks_like_debian_scheme())
336 if skip_linux_system_special_case
:
339 # On Python 3.7 and earlier, sysconfig does not include sys.abiflags in
340 # the "pythonX.Y" part of the path, but distutils does.
341 skip_sysconfig_abiflag_bug
= (
342 sys
.version_info
< (3, 8)
344 and k
in ("headers", "platlib", "purelib")
345 and tuple(_fix_abiflags(old_v
.parts
)) == new_v
.parts
347 if skip_sysconfig_abiflag_bug
:
350 # MSYS2 MINGW's sysconfig patch does not include the "site-packages"
351 # part of the path. This is incorrect and will be fixed in MSYS.
352 skip_msys2_mingw_bug
= (
353 WINDOWS
and k
in ("platlib", "purelib") and _looks_like_msys2_mingw_scheme()
355 if skip_msys2_mingw_bug
:
358 # CPython's POSIX install script invokes pip (via ensurepip) against the
359 # interpreter located in the source tree, not the install site. This
360 # triggers special logic in sysconfig that's not present in distutils.
361 # https://github.com/python/cpython/blob/8c21941ddaf/Lib/sysconfig.py#L178-L194
362 skip_cpython_build
= (
363 sysconfig
.is_python_build(check_home
=True)
365 and k
in ("headers", "include", "platinclude")
367 if skip_cpython_build
:
370 warning_contexts
.append((old_v
, new_v
, f
"scheme.{k}"))
372 if not warning_contexts
:
375 # Check if this path mismatch is caused by distutils config files. Those
376 # files will no longer work once we switch to sysconfig, so this raises a
377 # deprecation message for them.
378 default_old
= _distutils
.distutils_scheme(
385 ignore_config_files
=True,
387 if any(default_old
[k
] != getattr(old
, k
) for k
in SCHEME_KEYS
):
390 "Configuring installation scheme with distutils config files "
391 "is deprecated and will no longer work in the near future. If you "
392 "are using a Homebrew or Linuxbrew Python, please see discussion "
393 "at https://github.com/Homebrew/homebrew-core/issues/76621"
400 # Post warnings about this mismatch so user can report them back.
401 for old_v
, new_v
, key
in warning_contexts
:
402 _warn_mismatched(old_v
, new_v
, key
=key
)
403 _log_context(user
=user
, home
=home
, root
=root
, prefix
=prefix
)
408 def get_bin_prefix() -> str:
409 new
= _sysconfig
.get_bin_prefix()
413 old
= _distutils
.get_bin_prefix()
414 if _warn_if_mismatch(pathlib
.Path(old
), pathlib
.Path(new
), key
="bin_prefix"):
419 def get_bin_user() -> str:
420 return _sysconfig
.get_scheme("", user
=True).scripts
423 def _looks_like_deb_system_dist_packages(value
: str) -> bool:
424 """Check if the value is Debian's APT-controlled dist-packages.
426 Debian's ``distutils.sysconfig.get_python_lib()`` implementation returns the
427 default package path controlled by APT, but does not patch ``sysconfig`` to
428 do the same. This is similar to the bug worked around in ``get_scheme()``,
429 but here the default is ``deb_system`` instead of ``unix_local``. Ultimately
430 we can't do anything about this Debian bug, and this detection allows us to
431 skip the warning when needed.
433 if not _looks_like_debian_scheme():
435 if value
== "/usr/lib/python3/dist-packages":
440 def get_purelib() -> str:
441 """Return the default pure-Python lib location."""
442 new
= _sysconfig
.get_purelib()
446 old
= _distutils
.get_purelib()
447 if _looks_like_deb_system_dist_packages(old
):
449 if _warn_if_mismatch(pathlib
.Path(old
), pathlib
.Path(new
), key
="purelib"):
454 def get_platlib() -> str:
455 """Return the default platform-shared lib location."""
456 new
= _sysconfig
.get_platlib()
460 from . import _distutils
462 old
= _distutils
.get_platlib()
463 if _looks_like_deb_system_dist_packages(old
):
465 if _warn_if_mismatch(pathlib
.Path(old
), pathlib
.Path(new
), key
="platlib"):