Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/pip/_internal/locations/__init__.py: 28%

174 statements  

« prev     ^ index     » next       coverage.py v7.4.3, created at 2024-02-26 06:33 +0000

1import functools 

2import logging 

3import os 

4import pathlib 

5import sys 

6import sysconfig 

7from typing import Any, Dict, Generator, Optional, Tuple 

8 

9from pip._internal.models.scheme import SCHEME_KEYS, Scheme 

10from pip._internal.utils.compat import WINDOWS 

11from pip._internal.utils.deprecation import deprecated 

12from pip._internal.utils.virtualenv import running_under_virtualenv 

13 

14from . import _sysconfig 

15from .base import ( 

16 USER_CACHE_DIR, 

17 get_major_minor_version, 

18 get_src_prefix, 

19 is_osx_framework, 

20 site_packages, 

21 user_site, 

22) 

23 

24__all__ = [ 

25 "USER_CACHE_DIR", 

26 "get_bin_prefix", 

27 "get_bin_user", 

28 "get_major_minor_version", 

29 "get_platlib", 

30 "get_purelib", 

31 "get_scheme", 

32 "get_src_prefix", 

33 "site_packages", 

34 "user_site", 

35] 

36 

37 

38logger = logging.getLogger(__name__) 

39 

40 

41_PLATLIBDIR: str = getattr(sys, "platlibdir", "lib") 

42 

43_USE_SYSCONFIG_DEFAULT = sys.version_info >= (3, 10) 

44 

45 

46def _should_use_sysconfig() -> bool: 

47 """This function determines the value of _USE_SYSCONFIG. 

48 

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 

53 

54 This is a function for testability, but should be constant during any one 

55 run. 

56 """ 

57 return bool(getattr(sysconfig, "_PIP_USE_SYSCONFIG", _USE_SYSCONFIG_DEFAULT)) 

58 

59 

60_USE_SYSCONFIG = _should_use_sysconfig() 

61 

62if 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 

65 # a pip reinstall. 

66 from . import _distutils 

67 

68# Be noisy about incompatibilities if this platforms "should" be using 

69# sysconfig, but is explicitly opting out and using distutils instead. 

70if _USE_SYSCONFIG_DEFAULT and not _USE_SYSCONFIG: 

71 _MISMATCH_LEVEL = logging.WARNING 

72else: 

73 _MISMATCH_LEVEL = logging.DEBUG 

74 

75 

76def _looks_like_bpo_44860() -> bool: 

77 """The resolution to bpo-44860 will change this incorrect platlib. 

78 

79 See <https://bugs.python.org/issue44860>. 

80 """ 

81 from distutils.command.install import INSTALL_SCHEMES 

82 

83 try: 

84 unix_user_platlib = INSTALL_SCHEMES["unix_user"]["platlib"] 

85 except KeyError: 

86 return False 

87 return unix_user_platlib == "$usersite" 

88 

89 

90def _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: 

95 return False 

96 unpatched = platlib.replace("/lib64/", "/lib/") 

97 return unpatched.replace("$platbase/", "$base/") == scheme["purelib"] 

98 

99 

100@functools.lru_cache(maxsize=None) 

101def _looks_like_red_hat_lib() -> bool: 

102 """Red Hat patches platlib in unix_prefix and unix_home, but not purelib. 

103 

104 This is the only way I can see to tell a Red Hat-patched Python. 

105 """ 

106 from distutils.command.install import INSTALL_SCHEMES 

107 

108 return all( 

109 k in INSTALL_SCHEMES 

110 and _looks_like_red_hat_patched_platlib_purelib(INSTALL_SCHEMES[k]) 

111 for k in ("unix_prefix", "unix_home") 

112 ) 

113 

114 

115@functools.lru_cache(maxsize=None) 

116def _looks_like_debian_scheme() -> bool: 

117 """Debian adds two additional schemes.""" 

118 from distutils.command.install import INSTALL_SCHEMES 

119 

120 return "deb_system" in INSTALL_SCHEMES and "unix_local" in INSTALL_SCHEMES 

121 

122 

123@functools.lru_cache(maxsize=None) 

124def _looks_like_red_hat_scheme() -> bool: 

125 """Red Hat patches ``sys.prefix`` and ``sys.exec_prefix``. 

126 

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. 

131 """ 

132 from distutils.command.install import install 

133 from distutils.dist import Distribution 

134 

135 cmd: Any = install(Distribution()) 

136 cmd.finalize_options() 

137 return ( 

138 cmd.exec_prefix == f"{os.path.normpath(sys.exec_prefix)}/local" 

139 and cmd.prefix == f"{os.path.normpath(sys.prefix)}/local" 

140 ) 

141 

142 

143@functools.lru_cache(maxsize=None) 

144def _looks_like_slackware_scheme() -> bool: 

145 """Slackware patches sysconfig but fails to patch distutils and site. 

146 

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. 

149 """ 

150 if user_site is None: # User-site not available. 

151 return False 

152 try: 

153 paths = sysconfig.get_paths(scheme="posix_user", expand=False) 

154 except KeyError: # User-site not available. 

155 return False 

156 return "/lib64/" in paths["purelib"] and "/lib64/" not in user_site 

157 

158 

159@functools.lru_cache(maxsize=None) 

160def _looks_like_msys2_mingw_scheme() -> bool: 

161 """MSYS2 patches distutils and sysconfig to use a UNIX-like scheme. 

162 

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. 

166 

167 MSYS2 MINGW's patch uses lowercase ``"lib"`` instead of the usual uppercase, 

168 and is missing the final ``"site-packages"``. 

169 """ 

170 paths = sysconfig.get_paths("nt", expand=False) 

171 return all( 

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")) 

174 ) 

175 

176 

177def _fix_abiflags(parts: Tuple[str]) -> Generator[str, None, None]: 

178 ldversion = sysconfig.get_config_var("LDVERSION") 

179 abiflags = getattr(sys, "abiflags", None) 

180 

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): 

183 yield from parts 

184 return 

185 

186 # Strip sys.abiflags from LDVERSION-based path components. 

187 for part in parts: 

188 if part.endswith(ldversion): 

189 part = part[: (0 - len(abiflags))] 

190 yield part 

191 

192 

193@functools.lru_cache(maxsize=None) 

194def _warn_mismatched(old: pathlib.Path, new: pathlib.Path, *, key: str) -> None: 

195 issue_url = "https://github.com/pypa/pip/issues/10151" 

196 message = ( 

197 "Value for %s does not match. Please report this to <%s>" 

198 "\ndistutils: %s" 

199 "\nsysconfig: %s" 

200 ) 

201 logger.log(_MISMATCH_LEVEL, message, key, issue_url, old, new) 

202 

203 

204def _warn_if_mismatch(old: pathlib.Path, new: pathlib.Path, *, key: str) -> bool: 

205 if old == new: 

206 return False 

207 _warn_mismatched(old, new, key=key) 

208 return True 

209 

210 

211@functools.lru_cache(maxsize=None) 

212def _log_context( 

213 *, 

214 user: bool = False, 

215 home: Optional[str] = None, 

216 root: Optional[str] = None, 

217 prefix: Optional[str] = None, 

218) -> None: 

219 parts = [ 

220 "Additional context:", 

221 "user = %r", 

222 "home = %r", 

223 "root = %r", 

224 "prefix = %r", 

225 ] 

226 

227 logger.log(_MISMATCH_LEVEL, "\n".join(parts), user, home, root, prefix) 

228 

229 

230def get_scheme( 

231 dist_name: str, 

232 user: bool = False, 

233 home: Optional[str] = None, 

234 root: Optional[str] = None, 

235 isolated: bool = False, 

236 prefix: Optional[str] = None, 

237) -> Scheme: 

238 new = _sysconfig.get_scheme( 

239 dist_name, 

240 user=user, 

241 home=home, 

242 root=root, 

243 isolated=isolated, 

244 prefix=prefix, 

245 ) 

246 if _USE_SYSCONFIG: 

247 return new 

248 

249 old = _distutils.get_scheme( 

250 dist_name, 

251 user=user, 

252 home=home, 

253 root=root, 

254 isolated=isolated, 

255 prefix=prefix, 

256 ) 

257 

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)) 

262 

263 if old_v == new_v: 

264 continue 

265 

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" 

272 and home is not None 

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") 

277 ) 

278 if skip_pypy_special_case: 

279 continue 

280 

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 = ( 

285 user 

286 and is_osx_framework() 

287 and k == "headers" 

288 and old_v.parent.parent == new_v.parent 

289 and old_v.parent.name.startswith("python") 

290 ) 

291 if skip_osx_framework_user_special_case: 

292 continue 

293 

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(): 

297 continue 

298 

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". 

303 skip_bpo_44860 = ( 

304 user 

305 and k == "platlib" 

306 and not WINDOWS 

307 and sys.version_info >= (3, 9) 

308 and _PLATLIBDIR != "lib" 

309 and _looks_like_bpo_44860() 

310 ) 

311 if skip_bpo_44860: 

312 continue 

313 

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 = ( 

317 user 

318 and k in ("platlib", "purelib") 

319 and not WINDOWS 

320 and _looks_like_slackware_scheme() 

321 ) 

322 if skip_slackware_user_scheme: 

323 continue 

324 

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()) 

335 ) 

336 if skip_linux_system_special_case: 

337 continue 

338 

339 # MSYS2 MINGW's sysconfig patch does not include the "site-packages" 

340 # part of the path. This is incorrect and will be fixed in MSYS. 

341 skip_msys2_mingw_bug = ( 

342 WINDOWS and k in ("platlib", "purelib") and _looks_like_msys2_mingw_scheme() 

343 ) 

344 if skip_msys2_mingw_bug: 

345 continue 

346 

347 # CPython's POSIX install script invokes pip (via ensurepip) against the 

348 # interpreter located in the source tree, not the install site. This 

349 # triggers special logic in sysconfig that's not present in distutils. 

350 # https://github.com/python/cpython/blob/8c21941ddaf/Lib/sysconfig.py#L178-L194 

351 skip_cpython_build = ( 

352 sysconfig.is_python_build(check_home=True) 

353 and not WINDOWS 

354 and k in ("headers", "include", "platinclude") 

355 ) 

356 if skip_cpython_build: 

357 continue 

358 

359 warning_contexts.append((old_v, new_v, f"scheme.{k}")) 

360 

361 if not warning_contexts: 

362 return old 

363 

364 # Check if this path mismatch is caused by distutils config files. Those 

365 # files will no longer work once we switch to sysconfig, so this raises a 

366 # deprecation message for them. 

367 default_old = _distutils.distutils_scheme( 

368 dist_name, 

369 user, 

370 home, 

371 root, 

372 isolated, 

373 prefix, 

374 ignore_config_files=True, 

375 ) 

376 if any(default_old[k] != getattr(old, k) for k in SCHEME_KEYS): 

377 deprecated( 

378 reason=( 

379 "Configuring installation scheme with distutils config files " 

380 "is deprecated and will no longer work in the near future. If you " 

381 "are using a Homebrew or Linuxbrew Python, please see discussion " 

382 "at https://github.com/Homebrew/homebrew-core/issues/76621" 

383 ), 

384 replacement=None, 

385 gone_in=None, 

386 ) 

387 return old 

388 

389 # Post warnings about this mismatch so user can report them back. 

390 for old_v, new_v, key in warning_contexts: 

391 _warn_mismatched(old_v, new_v, key=key) 

392 _log_context(user=user, home=home, root=root, prefix=prefix) 

393 

394 return old 

395 

396 

397def get_bin_prefix() -> str: 

398 new = _sysconfig.get_bin_prefix() 

399 if _USE_SYSCONFIG: 

400 return new 

401 

402 old = _distutils.get_bin_prefix() 

403 if _warn_if_mismatch(pathlib.Path(old), pathlib.Path(new), key="bin_prefix"): 

404 _log_context() 

405 return old 

406 

407 

408def get_bin_user() -> str: 

409 return _sysconfig.get_scheme("", user=True).scripts 

410 

411 

412def _looks_like_deb_system_dist_packages(value: str) -> bool: 

413 """Check if the value is Debian's APT-controlled dist-packages. 

414 

415 Debian's ``distutils.sysconfig.get_python_lib()`` implementation returns the 

416 default package path controlled by APT, but does not patch ``sysconfig`` to 

417 do the same. This is similar to the bug worked around in ``get_scheme()``, 

418 but here the default is ``deb_system`` instead of ``unix_local``. Ultimately 

419 we can't do anything about this Debian bug, and this detection allows us to 

420 skip the warning when needed. 

421 """ 

422 if not _looks_like_debian_scheme(): 

423 return False 

424 if value == "/usr/lib/python3/dist-packages": 

425 return True 

426 return False 

427 

428 

429def get_purelib() -> str: 

430 """Return the default pure-Python lib location.""" 

431 new = _sysconfig.get_purelib() 

432 if _USE_SYSCONFIG: 

433 return new 

434 

435 old = _distutils.get_purelib() 

436 if _looks_like_deb_system_dist_packages(old): 

437 return old 

438 if _warn_if_mismatch(pathlib.Path(old), pathlib.Path(new), key="purelib"): 

439 _log_context() 

440 return old 

441 

442 

443def get_platlib() -> str: 

444 """Return the default platform-shared lib location.""" 

445 new = _sysconfig.get_platlib() 

446 if _USE_SYSCONFIG: 

447 return new 

448 

449 from . import _distutils 

450 

451 old = _distutils.get_platlib() 

452 if _looks_like_deb_system_dist_packages(old): 

453 return old 

454 if _warn_if_mismatch(pathlib.Path(old), pathlib.Path(new), key="platlib"): 

455 _log_context() 

456 return old