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

177 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:48 +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 # 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) 

343 and not WINDOWS 

344 and k in ("headers", "platlib", "purelib") 

345 and tuple(_fix_abiflags(old_v.parts)) == new_v.parts 

346 ) 

347 if skip_sysconfig_abiflag_bug: 

348 continue 

349 

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

354 ) 

355 if skip_msys2_mingw_bug: 

356 continue 

357 

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) 

364 and not WINDOWS 

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

366 ) 

367 if skip_cpython_build: 

368 continue 

369 

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

371 

372 if not warning_contexts: 

373 return old 

374 

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( 

379 dist_name, 

380 user, 

381 home, 

382 root, 

383 isolated, 

384 prefix, 

385 ignore_config_files=True, 

386 ) 

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

388 deprecated( 

389 reason=( 

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" 

394 ), 

395 replacement=None, 

396 gone_in=None, 

397 ) 

398 return old 

399 

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) 

404 

405 return old 

406 

407 

408def get_bin_prefix() -> str: 

409 new = _sysconfig.get_bin_prefix() 

410 if _USE_SYSCONFIG: 

411 return new 

412 

413 old = _distutils.get_bin_prefix() 

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

415 _log_context() 

416 return old 

417 

418 

419def get_bin_user() -> str: 

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

421 

422 

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

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

425 

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. 

432 """ 

433 if not _looks_like_debian_scheme(): 

434 return False 

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

436 return True 

437 return False 

438 

439 

440def get_purelib() -> str: 

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

442 new = _sysconfig.get_purelib() 

443 if _USE_SYSCONFIG: 

444 return new 

445 

446 old = _distutils.get_purelib() 

447 if _looks_like_deb_system_dist_packages(old): 

448 return old 

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

450 _log_context() 

451 return old 

452 

453 

454def get_platlib() -> str: 

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

456 new = _sysconfig.get_platlib() 

457 if _USE_SYSCONFIG: 

458 return new 

459 

460 from . import _distutils 

461 

462 old = _distutils.get_platlib() 

463 if _looks_like_deb_system_dist_packages(old): 

464 return old 

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

466 _log_context() 

467 return old