2828 BuildBackendException ,
2929 BuildException ,
3030 BuildSystemTableValidationError ,
31+ CircularBuildDependencyError ,
3132 FailedProcessError ,
33+ ProjectTableValidationError ,
3234 TypoWarning ,
3335)
34- from ._util import check_dependency , parse_wheel_filename
35-
36+ from ._util import check_dependency , parse_wheel_filename , project_name_from_path
3637
3738if sys .version_info >= (3 , 11 ):
3839 import tomllib
@@ -126,6 +127,23 @@ def _parse_build_system_table(pyproject_toml: Mapping[str, Any]) -> Mapping[str,
126127 return build_system_table
127128
128129
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+
129147def _wrap_subprocess_runner (runner : RunnerType , env : env .IsolatedEnv ) -> RunnerType :
130148 def _invoke_wrapped_runner (cmd : Sequence [str ], cwd : str | None , extra_environ : Mapping [str , str ] | None ) -> None :
131149 runner (cmd , cwd , {** (env .make_extra_environ () or {}), ** (extra_environ or {})})
@@ -170,8 +188,10 @@ def __init__(
170188 pyproject_toml_path = os .path .join (source_dir , 'pyproject.toml' )
171189 self ._build_system = _parse_build_system_table (_read_pyproject_toml (pyproject_toml_path ))
172190
191+ self .project_name : str | None = _parse_project_name (_read_pyproject_toml (pyproject_toml_path ))
173192 self ._backend = self ._build_system ['build-backend' ]
174193
194+ self ._requires_for_build_cache : dict [str , set [str ] | None ] = {'wheel' : None , 'sdist' : None }
175195 self ._hook = pyproject_hooks .BuildBackendHookCaller (
176196 self ._source_dir ,
177197 self ._backend ,
@@ -230,6 +250,33 @@ def get_requires_for_build(self, distribution: str, config_settings: ConfigSetti
230250 with self ._handle_backend (hook_name ):
231251 return set (get_requires (config_settings ))
232252
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+
233280 def check_dependencies (self , distribution : str , config_settings : ConfigSettingsType | None = None ) -> set [tuple [str , ...]]:
234281 """
235282 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
240287 :param config_settings: Config settings for the build backend
241288 :returns: Set of variable-length unmet dependency tuples
242289 """
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 )
245304
246305 def prepare (
247306 self , distribution : str , output_directory : PathType , config_settings : ConfigSettingsType | None = None
@@ -286,7 +345,11 @@ def build(
286345 """
287346 self .log (f'Building { distribution } ...' )
288347 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
290353
291354 def metadata_path (self , output_directory : PathType ) -> str :
292355 """
@@ -301,13 +364,17 @@ def metadata_path(self, output_directory: PathType) -> str:
301364 # prepare_metadata hook
302365 metadata = self .prepare ('wheel' , output_directory )
303366 if metadata is not None :
367+ project_name = project_name_from_path (metadata , 'wheel' )
368+ if project_name :
369+ self .project_name = project_name
304370 return metadata
305371
306372 # fallback to build_wheel hook
307373 wheel = self .build ('wheel' , output_directory )
308374 match = parse_wheel_filename (os .path .basename (wheel ))
309375 if not match :
310376 raise ValueError ('Invalid wheel' )
377+ self .project_name = match ['distribution' ]
311378 distinfo = f"{ match ['distribution' ]} -{ match ['version' ]} .dist-info"
312379 member_prefix = f'{ distinfo } /'
313380 with zipfile .ZipFile (wheel ) as w :
@@ -373,9 +440,11 @@ def log(message: str) -> None:
373440 'BuildSystemTableValidationError' ,
374441 'BuildBackendException' ,
375442 'BuildException' ,
443+ 'CircularBuildDependencyError' ,
376444 'ConfigSettingsType' ,
377445 'FailedProcessError' ,
378446 'ProjectBuilder' ,
447+ 'ProjectTableValidationError' ,
379448 'RunnerType' ,
380449 'TypoWarning' ,
381450 'check_dependency' ,
0 commit comments