Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pip/_internal/build_env/virtual.py: 34%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

53 statements  

1from __future__ import annotations 

2 

3import os 

4import site 

5import sys 

6import textwrap 

7from collections import OrderedDict 

8from collections.abc import Iterable 

9from types import TracebackType 

10from typing import TYPE_CHECKING 

11 

12from pip._internal.build_env.base import ( 

13 BuildEnvironment, 

14 BuildEnvironmentInstaller, 

15 Prefix, 

16) 

17from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds 

18 

19if TYPE_CHECKING: 

20 from pip._internal.req.req_install import InstallRequirement 

21 

22 

23def get_system_sitepackages() -> set[str]: 

24 """Get system site packages, as a normalized set of strings.""" 

25 system_sites = site.getsitepackages() 

26 return {os.path.normcase(path) for path in system_sites} 

27 

28 

29class VirtualBuildEnvironment(BuildEnvironment): 

30 """Legacy build environment implementation. 

31 

32 Patches sys.path and uses sitecustomize.py to isolate Python processes. It has 

33 known bugs and weird edge cases, but it exists as a fallback for platforms where 

34 the venv module is unavailable or dysfunctional. 

35 """ 

36 

37 def __init__(self, installer: BuildEnvironmentInstaller) -> None: 

38 self.python_executable = sys.executable 

39 self.installer = installer 

40 temp_dir = TempDirectory(kind=tempdir_kinds.BUILD_ENV, globally_managed=True) 

41 

42 self._prefixes = OrderedDict( 

43 (name, Prefix(os.path.join(temp_dir.path, name))) 

44 for name in ("normal", "overlay") 

45 ) 

46 

47 self._bin_dirs: list[str] = [] 

48 self.lib_dirs: list[str] = [] 

49 for prefix in reversed(list(self._prefixes.values())): 

50 self._bin_dirs.append(prefix.bin_dir) 

51 self.lib_dirs.extend(prefix.lib_dirs) 

52 

53 # Customize site to: 

54 # - ensure .pth files are honored 

55 # - prevent access to system site packages 

56 system_sites = get_system_sitepackages() 

57 

58 self._site_dir = os.path.join(temp_dir.path, "site") 

59 if not os.path.exists(self._site_dir): 

60 os.mkdir(self._site_dir) 

61 with open( 

62 os.path.join(self._site_dir, "sitecustomize.py"), "w", encoding="utf-8" 

63 ) as fp: 

64 fp.write(textwrap.dedent(""" 

65 import os, site, sys 

66 

67 # First, discover all system-sites related paths. 

68 original_sys_path = sys.path[:] 

69 # Clear sys.path so addsitedir() will add system site paths and paths 

70 # added by contained .pth files to sys.path reliably. This is necessary 

71 # since Python 3.15, which notably no longer re-executes .pth files for 

72 # known paths. 

73 sys.path = [] 

74 known_paths = set() 

75 for path in {system_sites!r}: 

76 site.addsitedir(path, known_paths=known_paths) 

77 system_paths = set(os.path.normcase(path) for path in sys.path) 

78 

79 # Drop discovered system-sites related paths. 

80 original_sys_path = [ 

81 path for path in original_sys_path 

82 if os.path.normcase(path) not in system_paths 

83 ] 

84 sys.path = original_sys_path 

85 

86 # Second, add lib directories. 

87 # ensuring .pth file are processed. 

88 for path in {lib_dirs!r}: 

89 assert not path in sys.path 

90 site.addsitedir(path) 

91 """).format(system_sites=system_sites, lib_dirs=self.lib_dirs)) 

92 

93 def __enter__(self) -> None: 

94 self._save_env = { 

95 name: os.environ.get(name, None) 

96 for name in ("PATH", "PYTHONNOUSERSITE", "PYTHONPATH") 

97 } 

98 

99 path = self._bin_dirs[:] 

100 old_path = self._save_env["PATH"] 

101 if old_path: 

102 path.extend(old_path.split(os.pathsep)) 

103 

104 pythonpath = [self._site_dir] 

105 

106 os.environ.update( 

107 { 

108 "PATH": os.pathsep.join(path), 

109 "PYTHONNOUSERSITE": "1", 

110 "PYTHONPATH": os.pathsep.join(pythonpath), 

111 } 

112 ) 

113 

114 def __exit__( 

115 self, 

116 exc_type: type[BaseException] | None, 

117 exc_val: BaseException | None, 

118 exc_tb: TracebackType | None, 

119 ) -> None: 

120 for varname, old_value in self._save_env.items(): 

121 if old_value is None: 

122 os.environ.pop(varname, None) 

123 else: 

124 os.environ[varname] = old_value 

125 

126 def install_requirements( 

127 self, 

128 requirements: Iterable[str], 

129 prefix_as_string: str, 

130 *, 

131 kind: str, 

132 for_req: InstallRequirement | None = None, 

133 ) -> None: 

134 prefix = self._prefixes[prefix_as_string] 

135 assert not prefix.setup 

136 prefix.setup = True 

137 if not requirements: 

138 return 

139 self.installer.install(requirements, prefix, kind=kind, for_req=for_req)