11
11
from collections import OrderedDict
12
12
from collections .abc import Iterable
13
13
from types import TracebackType
14
- from typing import TYPE_CHECKING
14
+ from typing import TYPE_CHECKING , Protocol
15
15
16
16
from pip ._vendor .packaging .version import Version
17
17
26
26
27
27
if TYPE_CHECKING :
28
28
from pip ._internal .index .package_finder import PackageFinder
29
+ from pip ._internal .req .req_install import InstallRequirement
29
30
30
31
logger = logging .getLogger (__name__ )
31
32
@@ -79,10 +80,108 @@ def _get_system_sitepackages() -> set[str]:
79
80
return {os .path .normcase (path ) for path in system_sites }
80
81
81
82
83
+ class BuildEnvironmentInstaller (Protocol ):
84
+ """
85
+ Interface for installing build dependencies into an isolated build
86
+ environment.
87
+ """
88
+
89
+ def install (
90
+ self ,
91
+ requirements : Iterable [str ],
92
+ prefix : _Prefix ,
93
+ * ,
94
+ kind : str ,
95
+ for_req : InstallRequirement | None ,
96
+ ) -> None : ...
97
+
98
+
99
+ class SubprocessBuildEnvironmentInstaller :
100
+ """
101
+ Install build dependencies by calling pip in a subprocess.
102
+ """
103
+
104
+ def __init__ (self , finder : PackageFinder ) -> None :
105
+ self .finder = finder
106
+
107
+ def install (
108
+ self ,
109
+ requirements : Iterable [str ],
110
+ prefix : _Prefix ,
111
+ * ,
112
+ kind : str ,
113
+ for_req : InstallRequirement | None ,
114
+ ) -> None :
115
+ finder = self .finder
116
+ args : list [str ] = [
117
+ sys .executable ,
118
+ get_runnable_pip (),
119
+ "install" ,
120
+ "--ignore-installed" ,
121
+ "--no-user" ,
122
+ "--prefix" ,
123
+ prefix .path ,
124
+ "--no-warn-script-location" ,
125
+ "--disable-pip-version-check" ,
126
+ # As the build environment is ephemeral, it's wasteful to
127
+ # pre-compile everything, especially as not every Python
128
+ # module will be used/compiled in most cases.
129
+ "--no-compile" ,
130
+ # The prefix specified two lines above, thus
131
+ # target from config file or env var should be ignored
132
+ "--target" ,
133
+ "" ,
134
+ ]
135
+ if logger .getEffectiveLevel () <= logging .DEBUG :
136
+ args .append ("-vv" )
137
+ elif logger .getEffectiveLevel () <= VERBOSE :
138
+ args .append ("-v" )
139
+ for format_control in ("no_binary" , "only_binary" ):
140
+ formats = getattr (finder .format_control , format_control )
141
+ args .extend (
142
+ (
143
+ "--" + format_control .replace ("_" , "-" ),
144
+ "," .join (sorted (formats or {":none:" })),
145
+ )
146
+ )
147
+
148
+ index_urls = finder .index_urls
149
+ if index_urls :
150
+ args .extend (["-i" , index_urls [0 ]])
151
+ for extra_index in index_urls [1 :]:
152
+ args .extend (["--extra-index-url" , extra_index ])
153
+ else :
154
+ args .append ("--no-index" )
155
+ for link in finder .find_links :
156
+ args .extend (["--find-links" , link ])
157
+
158
+ if finder .proxy :
159
+ args .extend (["--proxy" , finder .proxy ])
160
+ for host in finder .trusted_hosts :
161
+ args .extend (["--trusted-host" , host ])
162
+ if finder .custom_cert :
163
+ args .extend (["--cert" , finder .custom_cert ])
164
+ if finder .client_cert :
165
+ args .extend (["--client-cert" , finder .client_cert ])
166
+ if finder .allow_all_prereleases :
167
+ args .append ("--pre" )
168
+ if finder .prefer_binary :
169
+ args .append ("--prefer-binary" )
170
+ args .append ("--" )
171
+ args .extend (requirements )
172
+ with open_spinner (f"Installing { kind } " ) as spinner :
173
+ call_subprocess (
174
+ args ,
175
+ command_desc = f"pip subprocess to install { kind } " ,
176
+ spinner = spinner ,
177
+ )
178
+
179
+
82
180
class BuildEnvironment :
83
181
"""Creates and manages an isolated environment to install build deps"""
84
182
85
- def __init__ (self ) -> None :
183
+ def __init__ (self , installer : BuildEnvironmentInstaller ) -> None :
184
+ self .installer = installer
86
185
temp_dir = TempDirectory (kind = tempdir_kinds .BUILD_ENV , globally_managed = True )
87
186
88
187
self ._prefixes = OrderedDict (
@@ -205,96 +304,18 @@ def check_requirements(
205
304
206
305
def install_requirements (
207
306
self ,
208
- finder : PackageFinder ,
209
307
requirements : Iterable [str ],
210
308
prefix_as_string : str ,
211
309
* ,
212
310
kind : str ,
311
+ for_req : InstallRequirement | None = None ,
213
312
) -> None :
214
313
prefix = self ._prefixes [prefix_as_string ]
215
314
assert not prefix .setup
216
315
prefix .setup = True
217
316
if not requirements :
218
317
return
219
- self ._install_requirements (
220
- get_runnable_pip (),
221
- finder ,
222
- requirements ,
223
- prefix ,
224
- kind = kind ,
225
- )
226
-
227
- @staticmethod
228
- def _install_requirements (
229
- pip_runnable : str ,
230
- finder : PackageFinder ,
231
- requirements : Iterable [str ],
232
- prefix : _Prefix ,
233
- * ,
234
- kind : str ,
235
- ) -> None :
236
- args : list [str ] = [
237
- sys .executable ,
238
- pip_runnable ,
239
- "install" ,
240
- "--ignore-installed" ,
241
- "--no-user" ,
242
- "--prefix" ,
243
- prefix .path ,
244
- "--no-warn-script-location" ,
245
- "--disable-pip-version-check" ,
246
- # As the build environment is ephemeral, it's wasteful to
247
- # pre-compile everything, especially as not every Python
248
- # module will be used/compiled in most cases.
249
- "--no-compile" ,
250
- # The prefix specified two lines above, thus
251
- # target from config file or env var should be ignored
252
- "--target" ,
253
- "" ,
254
- ]
255
- if logger .getEffectiveLevel () <= logging .DEBUG :
256
- args .append ("-vv" )
257
- elif logger .getEffectiveLevel () <= VERBOSE :
258
- args .append ("-v" )
259
- for format_control in ("no_binary" , "only_binary" ):
260
- formats = getattr (finder .format_control , format_control )
261
- args .extend (
262
- (
263
- "--" + format_control .replace ("_" , "-" ),
264
- "," .join (sorted (formats or {":none:" })),
265
- )
266
- )
267
-
268
- index_urls = finder .index_urls
269
- if index_urls :
270
- args .extend (["-i" , index_urls [0 ]])
271
- for extra_index in index_urls [1 :]:
272
- args .extend (["--extra-index-url" , extra_index ])
273
- else :
274
- args .append ("--no-index" )
275
- for link in finder .find_links :
276
- args .extend (["--find-links" , link ])
277
-
278
- if finder .proxy :
279
- args .extend (["--proxy" , finder .proxy ])
280
- for host in finder .trusted_hosts :
281
- args .extend (["--trusted-host" , host ])
282
- if finder .custom_cert :
283
- args .extend (["--cert" , finder .custom_cert ])
284
- if finder .client_cert :
285
- args .extend (["--client-cert" , finder .client_cert ])
286
- if finder .allow_all_prereleases :
287
- args .append ("--pre" )
288
- if finder .prefer_binary :
289
- args .append ("--prefer-binary" )
290
- args .append ("--" )
291
- args .extend (requirements )
292
- with open_spinner (f"Installing { kind } " ) as spinner :
293
- call_subprocess (
294
- args ,
295
- command_desc = f"pip subprocess to install { kind } " ,
296
- spinner = spinner ,
297
- )
318
+ self .installer .install (requirements , prefix , kind = kind , for_req = for_req )
298
319
299
320
300
321
class NoOpBuildEnvironment (BuildEnvironment ):
@@ -319,10 +340,10 @@ def cleanup(self) -> None:
319
340
320
341
def install_requirements (
321
342
self ,
322
- finder : PackageFinder ,
323
343
requirements : Iterable [str ],
324
344
prefix_as_string : str ,
325
345
* ,
326
346
kind : str ,
347
+ for_req : InstallRequirement | None = None ,
327
348
) -> None :
328
349
raise NotImplementedError ()
0 commit comments