@@ -59,6 +59,17 @@ class RequiresBuildWheelResult(NamedTuple):
5959 err : str
6060
6161
62+ class RequiresBuildEditableResult (NamedTuple ):
63+ """Information collected while acquiring the wheel build dependencies"""
64+
65+ #: editable wheel build dependencies
66+ requires : tuple [Requirement , ...]
67+ #: backend standard output while acquiring the editable wheel build dependencies
68+ out : str
69+ #: backend standard error while acquiring the editable wheel build dependencies
70+ err : str
71+
72+
6273class MetadataForBuildWheelResult (NamedTuple ):
6374 """Information collected while acquiring the wheel metadata"""
6475
@@ -70,6 +81,17 @@ class MetadataForBuildWheelResult(NamedTuple):
7081 err : str
7182
7283
84+ class MetadataForBuildEditableResult (NamedTuple ):
85+ """Information collected while acquiring the editable metadata"""
86+
87+ #: path to the wheel metadata
88+ metadata : Path
89+ #: backend standard output while generating the editable wheel metadata
90+ out : str
91+ #: backend standard output while generating the editable wheel metadata
92+ err : str
93+
94+
7395class SdistResult (NamedTuple ):
7496 """Information collected while building a source distribution"""
7597
@@ -92,6 +114,17 @@ class WheelResult(NamedTuple):
92114 err : str
93115
94116
117+ class EditableResult (NamedTuple ):
118+ """Information collected while building an editable wheel"""
119+
120+ #: path to the built wheel artifact
121+ wheel : Path
122+ #: backend standard output while building the wheel
123+ out : str
124+ #: backend standard error while building the wheel
125+ err : str
126+
127+
95128class BackendFailed (RuntimeError ):
96129 """An error of the build backend."""
97130
@@ -240,6 +273,23 @@ def get_requires_for_build_wheel(self, config_settings: ConfigSettings | None =
240273 self ._unexpected_response ("get_requires_for_build_wheel" , result , "list of string" , out , err )
241274 return RequiresBuildWheelResult (tuple (Requirement (r ) for r in cast (List [str ], result )), out , err )
242275
276+ def get_requires_for_build_editable (
277+ self , config_settings : ConfigSettings | None = None
278+ ) -> RequiresBuildEditableResult :
279+ """
280+ Get build requirements for an editable wheel build (per PEP-660).
281+
282+ :param config_settings: run arguments
283+ :return: outcome
284+ """
285+ try :
286+ result , out , err = self ._send (cmd = "get_requires_for_build_editable" , config_settings = config_settings )
287+ except BackendFailed as exc :
288+ result , out , err = [], exc .out , exc .err
289+ if not isinstance (result , list ) or not all (isinstance (i , str ) for i in result ):
290+ self ._unexpected_response ("get_requires_for_build_editable" , result , "list of string" , out , err )
291+ return RequiresBuildEditableResult (tuple (Requirement (r ) for r in cast (List [str ], result )), out , err )
292+
243293 def prepare_metadata_for_build_wheel (
244294 self , metadata_directory : Path , config_settings : ConfigSettings | None = None
245295 ) -> MetadataForBuildWheelResult :
@@ -250,24 +300,52 @@ def prepare_metadata_for_build_wheel(
250300 :param config_settings: build arguments
251301 :return: metadata generation result
252302 """
303+ self ._check_metadata_dir (metadata_directory )
304+ try :
305+ basename , out , err = self ._send (
306+ cmd = "prepare_metadata_for_build_wheel" ,
307+ metadata_directory = metadata_directory ,
308+ config_settings = config_settings ,
309+ )
310+ except BackendFailed :
311+ # if backend does not provide it acquire it from the wheel
312+ basename , err , out = self ._metadata_from_built_wheel (config_settings , metadata_directory , "build_wheel" )
313+ if not isinstance (basename , str ):
314+ self ._unexpected_response ("prepare_metadata_for_build_wheel" , basename , str , out , err )
315+ result = metadata_directory / basename
316+ return MetadataForBuildWheelResult (result , out , err )
317+
318+ def _check_metadata_dir (self , metadata_directory : Path ) -> None :
253319 if metadata_directory == self ._root :
254320 raise RuntimeError (f"the project root and the metadata directory can't be the same { self ._root } " )
255321 if metadata_directory .exists (): # start with fresh
256322 ensure_empty_dir (metadata_directory )
257323 metadata_directory .mkdir (parents = True , exist_ok = True )
324+
325+ def prepare_metadata_for_build_editable (
326+ self , metadata_directory : Path , config_settings : ConfigSettings | None = None
327+ ) -> MetadataForBuildEditableResult :
328+ """
329+ Build editable wheel metadata (per PEP-660).
330+
331+ :param metadata_directory: where to generate the metadata
332+ :param config_settings: build arguments
333+ :return: metadata generation result
334+ """
335+ self ._check_metadata_dir (metadata_directory )
258336 try :
259337 basename , out , err = self ._send (
260- cmd = "prepare_metadata_for_build_wheel " ,
338+ cmd = "prepare_metadata_for_build_editable " ,
261339 metadata_directory = metadata_directory ,
262340 config_settings = config_settings ,
263341 )
264342 except BackendFailed :
265343 # if backend does not provide it acquire it from the wheel
266- basename , err , out = self ._metadata_from_built_wheel (config_settings , metadata_directory )
344+ basename , err , out = self ._metadata_from_built_wheel (config_settings , metadata_directory , "build_editable" )
267345 if not isinstance (basename , str ):
268346 self ._unexpected_response ("prepare_metadata_for_build_wheel" , basename , str , out , err )
269347 result = metadata_directory / basename
270- return MetadataForBuildWheelResult (result , out , err )
348+ return MetadataForBuildEditableResult (result , out , err )
271349
272350 def build_sdist (self , sdist_directory : Path , config_settings : ConfigSettings | None = None ) -> SdistResult :
273351 """
@@ -294,7 +372,7 @@ def build_wheel(
294372 metadata_directory : Path | None = None ,
295373 ) -> WheelResult :
296374 """
297- Build a source distribution (per PEP-517).
375+ Build a wheel file (per PEP-517).
298376
299377 :param wheel_directory: the folder where to build the wheel
300378 :param config_settings: build arguments
@@ -312,15 +390,40 @@ def build_wheel(
312390 self ._unexpected_response ("build_wheel" , basename , str , out , err )
313391 return WheelResult (wheel_directory / basename , out , err )
314392
393+ def build_editable (
394+ self ,
395+ wheel_directory : Path ,
396+ config_settings : ConfigSettings | None = None ,
397+ metadata_directory : Path | None = None ,
398+ ) -> EditableResult :
399+ """
400+ Build an editable wheel file (per PEP-660).
401+
402+ :param wheel_directory: the folder where to build the editable wheel
403+ :param config_settings: build arguments
404+ :param metadata_directory: wheel metadata folder
405+ :return: wheel build result
406+ """
407+ wheel_directory .mkdir (parents = True , exist_ok = True )
408+ basename , out , err = self ._send (
409+ cmd = "build_editable" ,
410+ wheel_directory = wheel_directory ,
411+ config_settings = config_settings ,
412+ metadata_directory = metadata_directory ,
413+ )
414+ if not isinstance (basename , str ):
415+ self ._unexpected_response ("build_editable" , basename , str , out , err )
416+ return EditableResult (wheel_directory / basename , out , err )
417+
315418 def _unexpected_response (self , cmd : str , got : Any , expected_type : Any , out : str , err : str ) -> NoReturn :
316419 msg = f"{ cmd !r} on { self .backend !r} returned { got !r} but expected type { expected_type !r} "
317420 raise BackendFailed ({"code" : None , "exc_type" : TypeError .__name__ , "exc_msg" : msg }, out , err )
318421
319422 def _metadata_from_built_wheel (
320- self , config_settings : ConfigSettings | None , metadata_directory : Path | None
423+ self , config_settings : ConfigSettings | None , metadata_directory : Path | None , cmd : str
321424 ) -> tuple [str , str , str ]:
322425 with self ._wheel_directory () as wheel_directory :
323- wheel_result = self . build_wheel (
426+ wheel_result = getattr ( self , cmd ) (
324427 wheel_directory = wheel_directory ,
325428 config_settings = config_settings ,
326429 metadata_directory = metadata_directory ,
0 commit comments