88from typing import Optional , Dict , Any , List , Callable
99
1010from .global_json import GlobalJson
11+ from .msbuild import MSBuildFile , MSBuildTarget
1112
1213
1314class Host :
@@ -144,9 +145,10 @@ def get_argparser(self) -> argparse.ArgumentParser:
144145 usage = self .argparser_usage .replace ('command' , 'bite' ) + ' [target]' ,
145146 )
146147 bite_parser .add_argument ('target' , nargs = '?' , default = 'help' , help = 'bite.core target to run, default is "help"' )
148+ bite_parser .add_argument ('--list' , '-l' , action = 'store_true' , help = 'List available targets' )
147149 self .register_handler ('bite' , self ._handle_bite )
148150
149- dotnet_parser = subparsers .add_parser (
151+ subparsers .add_parser (
150152 'dotnet' ,
151153 help = 'Run a dotnet command' ,
152154 epilog = self .argparser_epilog + ' (Dotnet CLI)' ,
@@ -232,43 +234,79 @@ def _handle_bite(self, args: argparse.Namespace, extras: List[str]) -> None:
232234 Handle the 'bite' command, running a custom msbuild target.
233235 Passes any extra arguments to msbuild.
234236 """
237+ list_targets = getattr (args , 'list' , False )
238+ if list_targets :
239+ dependant_targets : List [MSBuildTarget ] = []
240+ targets = self ._get_bite_core_targets ()
241+ print ("Available independent targets:" )
242+ for target in targets :
243+ if getattr (target , 'AfterTargets' , None ) is None and getattr (target , 'BeforeTargets' , None ) is None :
244+ print (f" { target .Name } " )
245+ else :
246+ dependant_targets .append (target )
247+ if dependant_targets :
248+ print ("\n Available automated targets:" )
249+ for target in dependant_targets :
250+ print (f" { target .Name } " , end = ' ' )
251+ if getattr (target , 'AfterTargets' , None ):
252+ print (f"(after '{ target .AfterTargets } ')" , end = ' ' )
253+ if getattr (target , 'BeforeTargets' , None ):
254+ print (f"(before '{ target .BeforeTargets } ')" , end = ' ' )
255+ print ()
256+ return
235257 target = getattr (args , 'target' , 'help' )
236258 self .run_bite (target , * extras )
237259
238260 # --- Dotnet/MSBuild Execution ---
239261
240- def run (self , command : str , * args : str ) -> None :
262+ def run (self , command : str , * args : str , capture_output : bool = False ) -> Optional [ subprocess . CompletedProcess ] :
241263 """
242264 Run a dotnet command.
243265
244266 Args:
245267 command: The dotnet CLI command to run.
246268 *args: Additional arguments to pass to the command.
269+ capture_output: If True, capture and return the output.
270+
271+ Returns:
272+ subprocess.CompletedProcess if capture_output is True, otherwise None.
247273 """
248274 cmd = ['dotnet' , command ] + list (args )
249- subprocess .call (cmd )
275+ if capture_output :
276+ return subprocess .run (cmd , capture_output = True , text = True )
277+ else :
278+ subprocess .call (cmd )
279+ return None
250280
251- def run_builtin (self , command : str , * args : str ) -> None :
281+ def run_builtin (self , command : str , * args : str , capture_output : bool = False ) -> Optional [ subprocess . CompletedProcess ] :
252282 """
253283 Run a built-in dotnet command with the solution file and default arguments.
254284
255285 Args:
256286 command: The dotnet command to run (e.g., 'build', 'restore').
257287 *args: Additional arguments to pass to the dotnet cli.
288+ capture_output: If True, capture and return the output.
289+
290+ Returns:
291+ subprocess.CompletedProcess if capture_output is True, otherwise None.
258292 """
259293 cmd = [self .solution ] + self .DEFAULT_ARGS + list (args )
260- self .run (command , * cmd )
294+ return self .run (command , * cmd , capture_output = capture_output )
261295
262- def run_bite (self , target : str , * args : str ) -> None :
296+ def run_bite (self , target : str , * args : str , capture_output : bool = False ) -> Optional [ subprocess . CompletedProcess ] :
263297 """
264298 Run bite.core with the specified target and default arguments.
265299
266300 Args:
267301 target: The bite.core target to run.
268302 *args: Additional arguments to pass to msbuild.
303+ capture_output: If True, capture and return the output.
304+
305+ Returns:
306+ subprocess.CompletedProcess if capture_output is True, otherwise None.
269307 """
270308 cmd = self .DEFAULT_ARGS + [f'-t:{ target } ' , self .BITE_PROJ_PATH ] + list (args )
271- self .run ('msbuild' , * cmd )
309+ return self .run ('msbuild' , * cmd , capture_output = capture_output )
272310
273311 # --- SDK Installation ---
274312
@@ -384,7 +422,12 @@ def _resolve_requested_sdk(self) -> Optional[str]:
384422 def msbuild_path (path : str ) -> str :
385423 """
386424 Convert a Python path string to an MSBuild-acceptable path for directory properties.
387- Ensures absolute path, uses backslashes, and ends with a backslash.
425+
426+ Args:
427+ path (str): The path to convert.
428+
429+ Returns:
430+ str: The MSBuild-compatible absolute path, quoted if it contains spaces, and ending with a backslash.
388431 """
389432 abs_path = os .path .abspath (path )
390433 msbuild_path = abs_path
@@ -394,6 +437,36 @@ def msbuild_path(path: str) -> str:
394437 msbuild_path = f'"{ msbuild_path } "'
395438 return msbuild_path
396439
440+ def _get_bite_core_targets (self ) -> List [MSBuildTarget ]:
441+ """
442+ Retrieve all available MSBuild targets from bite.core or .bite.targets files.
443+
444+ Returns:
445+ List[MSBuildTarget]: List of discovered MSBuildTarget objects.
446+ """
447+ targets : List [MSBuildTarget ] = []
448+
449+ # Try to get targets from bite.core msbuild output
450+ output = self .run_bite ('help' , '-pp' , capture_output = True )
451+ if output and output .stdout :
452+ try :
453+ # Parse the XML output directly if possible
454+ targets = MSBuildFile (xml_string = output .stdout ).get_targets ()
455+ return targets
456+ except Exception :
457+ pass
458+
459+ # Fallback: scan all .bite.targets files in the modules directory
460+ pattern = os .path .join (self .MODULES_DIR , '**' , '*.bite.targets' )
461+ for path in glob .glob (pattern , recursive = True ):
462+ try :
463+ obj = MSBuildFile (path )
464+ targets .extend (obj .get_targets ())
465+ except Exception :
466+ continue
467+
468+ return targets
469+
397470 def load_modules (self ) -> Dict [str , Any ]:
398471 """
399472 Load all .bite.py modules from the modules directory.
0 commit comments