28
28
BuildBackendException ,
29
29
BuildException ,
30
30
BuildSystemTableValidationError ,
31
+ CircularBuildDependencyError ,
31
32
FailedProcessError ,
33
+ ProjectTableValidationError ,
32
34
TypoWarning ,
33
35
)
34
- from ._util import check_dependency , parse_wheel_filename
35
-
36
+ from ._util import check_dependency , parse_wheel_filename , project_name_from_path
36
37
37
38
if sys .version_info >= (3 , 11 ):
38
39
import tomllib
@@ -126,6 +127,23 @@ def _parse_build_system_table(pyproject_toml: Mapping[str, Any]) -> Mapping[str,
126
127
return build_system_table
127
128
128
129
130
+ def _parse_project_name (pyproject_toml : Mapping [str , Any ]) -> str | None :
131
+ if 'project' not in pyproject_toml :
132
+ return None
133
+
134
+ project_table = dict (pyproject_toml ['project' ])
135
+
136
+ # If [project] is present, it must have a ``name`` field (per PEP 621)
137
+ if 'name' not in project_table :
138
+ raise ProjectTableValidationError ('`project` must have a `name` field' )
139
+
140
+ project_name = project_table ['name' ]
141
+ if not isinstance (project_name , str ):
142
+ raise ProjectTableValidationError ('`name` field in `project` must be a string' )
143
+
144
+ return project_name
145
+
146
+
129
147
def _wrap_subprocess_runner (runner : RunnerType , env : env .IsolatedEnv ) -> RunnerType :
130
148
def _invoke_wrapped_runner (cmd : Sequence [str ], cwd : str | None , extra_environ : Mapping [str , str ] | None ) -> None :
131
149
runner (cmd , cwd , {** (env .make_extra_environ () or {}), ** (extra_environ or {})})
@@ -170,8 +188,10 @@ def __init__(
170
188
pyproject_toml_path = os .path .join (source_dir , 'pyproject.toml' )
171
189
self ._build_system = _parse_build_system_table (_read_pyproject_toml (pyproject_toml_path ))
172
190
191
+ self .project_name : str | None = _parse_project_name (_read_pyproject_toml (pyproject_toml_path ))
173
192
self ._backend = self ._build_system ['build-backend' ]
174
193
194
+ self ._requires_for_build_cache : dict [str , set [str ] | None ] = {'wheel' : None , 'sdist' : None }
175
195
self ._hook = pyproject_hooks .BuildBackendHookCaller (
176
196
self ._source_dir ,
177
197
self ._backend ,
@@ -230,6 +250,33 @@ def get_requires_for_build(self, distribution: str, config_settings: ConfigSetti
230
250
with self ._handle_backend (hook_name ):
231
251
return set (get_requires (config_settings ))
232
252
253
+ def get_cache_requires_for_build (self , distribution : str , config_settings : ConfigSettingsType | None = None ) -> set [str ]:
254
+ """
255
+ Return the dependencies defined by the backend in addition to
256
+ :attr:`build_system_requires` for a given distribution.
257
+
258
+ :param distribution: Distribution to get the dependencies of
259
+ (``sdist`` or ``wheel``)
260
+ :param config_settings: Config settings for the build backend
261
+ """
262
+ requires_for_build : set [str ]
263
+ requires_for_build_cache : set [str ] | None = self ._requires_for_build_cache [distribution ]
264
+ if requires_for_build_cache is not None :
265
+ requires_for_build = requires_for_build_cache
266
+ else :
267
+ requires_for_build = self .get_requires_for_build (distribution , config_settings )
268
+ self ._requires_for_build_cache [distribution ] = requires_for_build
269
+ return requires_for_build
270
+
271
+ def check_build_system_dependencies (self ) -> set [tuple [str , ...]]:
272
+ """
273
+ Return the dependencies which are not satisfied from
274
+ :attr:`build_system_requires`
275
+
276
+ :returns: Set of variable-length unmet dependency tuples
277
+ """
278
+ return {u for d in self .build_system_requires for u in check_dependency (d , project_name = self .project_name )}
279
+
233
280
def check_dependencies (self , distribution : str , config_settings : ConfigSettingsType | None = None ) -> set [tuple [str , ...]]:
234
281
"""
235
282
Return the dependencies which are not satisfied from the combined set of
@@ -240,8 +287,20 @@ def check_dependencies(self, distribution: str, config_settings: ConfigSettingsT
240
287
:param config_settings: Config settings for the build backend
241
288
:returns: Set of variable-length unmet dependency tuples
242
289
"""
243
- dependencies = self .get_requires_for_build (distribution , config_settings ).union (self .build_system_requires )
244
- return {u for d in dependencies for u in check_dependency (d )}
290
+ build_system_dependencies = self .check_build_system_dependencies ()
291
+ requires_for_build : set [str ]
292
+ requires_for_build_cache : set [str ] | None = self ._requires_for_build_cache [distribution ]
293
+ if requires_for_build_cache is not None :
294
+ requires_for_build = requires_for_build_cache
295
+ else :
296
+ requires_for_build = self .get_requires_for_build (distribution , config_settings )
297
+ # cache if build system dependencies are fully satisfied
298
+ if len (build_system_dependencies ) == 0 :
299
+ self ._requires_for_build_cache [distribution ] = requires_for_build
300
+ dependencies = {
301
+ u for d in requires_for_build for u in check_dependency (d , project_name = self .project_name , backend = self ._backend )
302
+ }
303
+ return dependencies .union (build_system_dependencies )
245
304
246
305
def prepare (
247
306
self , distribution : str , output_directory : PathType , config_settings : ConfigSettingsType | None = None
@@ -286,7 +345,11 @@ def build(
286
345
"""
287
346
self .log (f'Building { distribution } ...' )
288
347
kwargs = {} if metadata_directory is None else {'metadata_directory' : metadata_directory }
289
- return self ._call_backend (f'build_{ distribution } ' , output_directory , config_settings , ** kwargs )
348
+ basename = self ._call_backend (f'build_{ distribution } ' , output_directory , config_settings , ** kwargs )
349
+ project_name = project_name_from_path (basename , distribution )
350
+ if project_name :
351
+ self .project_name = project_name
352
+ return basename
290
353
291
354
def metadata_path (self , output_directory : PathType ) -> str :
292
355
"""
@@ -301,13 +364,17 @@ def metadata_path(self, output_directory: PathType) -> str:
301
364
# prepare_metadata hook
302
365
metadata = self .prepare ('wheel' , output_directory )
303
366
if metadata is not None :
367
+ project_name = project_name_from_path (metadata , 'wheel' )
368
+ if project_name :
369
+ self .project_name = project_name
304
370
return metadata
305
371
306
372
# fallback to build_wheel hook
307
373
wheel = self .build ('wheel' , output_directory )
308
374
match = parse_wheel_filename (os .path .basename (wheel ))
309
375
if not match :
310
376
raise ValueError ('Invalid wheel' )
377
+ self .project_name = match ['distribution' ]
311
378
distinfo = f"{ match ['distribution' ]} -{ match ['version' ]} .dist-info"
312
379
member_prefix = f'{ distinfo } /'
313
380
with zipfile .ZipFile (wheel ) as w :
@@ -373,9 +440,11 @@ def log(message: str) -> None:
373
440
'BuildSystemTableValidationError' ,
374
441
'BuildBackendException' ,
375
442
'BuildException' ,
443
+ 'CircularBuildDependencyError' ,
376
444
'ConfigSettingsType' ,
377
445
'FailedProcessError' ,
378
446
'ProjectBuilder' ,
447
+ 'ProjectTableValidationError' ,
379
448
'RunnerType' ,
380
449
'TypoWarning' ,
381
450
'check_dependency' ,
0 commit comments