1919import psutil
2020
2121from briefcase .config import AppConfig
22- from briefcase .console import Console
22+ from briefcase .console import Console , LogLevel
2323from briefcase .exceptions import CommandOutputParseError , ParseError
2424from briefcase .integrations .base import Tool , ToolCache
2525
@@ -617,31 +617,33 @@ def _run_and_stream_output(
617617 return subprocess .CompletedProcess (args , return_code , stderr = stderr )
618618
619619 @ensure_console_is_safe
620- def check_output (self , args : SubprocessArgsT , quiet : bool = False , ** kwargs ) -> str :
620+ def check_output (self , args : SubprocessArgsT , quiet : int = 0 , ** kwargs ) -> str :
621621 """A wrapper for subprocess.check_output()
622622
623- The behavior of this method is identical to
624- subprocess.check_output(), except for:
625- - If the `env` is argument provided, the current system environment
626- will be copied, and the contents of env overwritten into that
627- environment.
628- - The `text` argument is defaulted to True so all output
629- is returned as strings instead of bytes.
630- - The `stderr` argument is defaulted to `stdout` so _all_ output is
631- returned and `stderr` isn't unexpectedly printed to the console.
623+ The behavior of this method is identical to subprocess.check_output(), except
624+ for:
625+ - If the `env` is argument provided, the current system environment will be
626+ copied, and the contents of env overwritten into that environment.
627+ - The `text` argument is defaulted to True so all output is returned as strings
628+ instead of bytes.
629+ - The `stderr` argument is defaulted to `stdout` so _all_ output is returned
630+ and `stderr` isn't unexpectedly printed to the console.
632631
633632 :param args: commands and its arguments to run via subprocess
634- :param quiet: Should the invocation of this command be silent, and
635- *not* appear in the logs? This should almost always be False;
636- however, for some calls (most notably, calls that are called
637- frequently to evaluate the status of another process), logging can
638- be turned off so that log output isn't corrupted by thousands of
639- polling calls.
633+ :param quiet: Should the invocation of this command be silent, and *not* appear
634+ in the logs? This should almost always be 0, indicating "not quiet"; any
635+ command output will be logged, and any errors will be logged *and* printed
636+ to the console. A value of 1 will log all command output, but errors will be
637+ logged, but *not* printed to the console. A value of 2 will *not* log any
638+ command output; errors will be logged, but *not* printed to the console. A
639+ value of 2 is mostly useful for calls that are called frequently to evaluate
640+ the status of another process (such as calls that poll an API) so that log
641+ output isn't corrupted by thousands of polling calls.
640642 """
641643 # if stderr isn't explicitly redirected, then send it to stdout.
642644 kwargs .setdefault ("stderr" , subprocess .STDOUT )
643645
644- if not quiet :
646+ if quiet < 2 :
645647 self ._log_command (args )
646648 self ._log_cwd (kwargs .get ("cwd" ))
647649 self ._log_environment (kwargs .get ("env" ))
@@ -651,12 +653,17 @@ def check_output(self, args: SubprocessArgsT, quiet: bool = False, **kwargs) ->
651653 [str (arg ) for arg in args ], ** self .final_kwargs (** kwargs )
652654 )
653655 except subprocess .CalledProcessError as e :
654- if not quiet :
656+ # Subprocess errors will be logged, and also printed to console if we're at
657+ # DEBUG+ log levels. However, if we're at quiet=0 *and* a non-debug log
658+ # level, we need to explicitly output to the console.
659+ if quiet == 0 and self .tools .console .verbosity < LogLevel .DEBUG :
660+ self .output_error (e )
661+ if quiet < 2 :
655662 self ._log_output (e .output , e .stderr )
656663 self ._log_return_code (e .returncode )
657664 raise
658665
659- if not quiet :
666+ if quiet < 2 :
660667 self ._log_output (cmd_output )
661668 self ._log_return_code (0 )
662669 return cmd_output
@@ -822,15 +829,22 @@ def cleanup(self, label: str, popen_process: subprocess.Popen):
822829 self .tools .console .warning (f"Forcibly killing { label } ..." )
823830 popen_process .kill ()
824831
832+ def _console (self , msg : str = "" ):
833+ """Funnel for subprocess console-only logging."""
834+ self .tools .console .to_console (msg , style = "dim" )
835+
825836 def _log (self , msg : str = "" ):
826837 """Funnel for all subprocess details logging."""
827838 self .tools .console .debug (msg , preface = ">>> " if msg else "" )
828839
829- def _log_command (self , args : SubprocessArgsT ):
840+ def _log_command (self , args : SubprocessArgsT , handler = None ):
830841 """Log the entire console command being executed."""
831- self ._log ()
832- self ._log ("Running Command:" )
833- self ._log (f" { ' ' .join (shlex .quote (str (arg )) for arg in args )} " )
842+ if handler is None :
843+ handler = self ._log
844+
845+ handler ()
846+ handler ("Running Command:" )
847+ handler (f" { ' ' .join (shlex .quote (str (arg )) for arg in args )} " )
834848
835849 def _log_cwd (self , cwd : str | Path | None ):
836850 """Log the working directory for the command being executed."""
@@ -849,18 +863,36 @@ def _log_environment(self, overrides: dict[str, str] | None):
849863 for env_var , value in overrides .items ():
850864 self ._log (f" { env_var } ={ value } " )
851865
852- def _log_output (self , output : str , stderr : str | None = None ):
866+ def _log_output (self , output : str , stderr : str | None = None , handler = None ):
853867 """Log the output of the executed command."""
868+ if handler is None :
869+ handler = self ._log
870+
854871 if output :
855- self . _log ("Command Output:" )
872+ handler ("Command Output:" )
856873 for line in ensure_str (output ).splitlines ():
857- self . _log (f" { line } " )
874+ handler (f" { line } " )
858875
859876 if stderr :
860- self . _log ("Command Error Output (stderr):" )
877+ handler ("Command Error Output (stderr):" )
861878 for line in ensure_str (stderr ).splitlines ():
862- self . _log (f" { line } " )
879+ handler (f" { line } " )
863880
864- def _log_return_code (self , return_code : int | str ):
881+ def _log_return_code (self , return_code : int | str , handler = None ):
865882 """Log the output value of the executed command."""
866- self ._log (f"Return code: { return_code } " )
883+ if handler is None :
884+ handler = self ._log
885+ handler (f"Return code: { return_code } " )
886+ handler ()
887+
888+ def output_error (self , exception : subprocess .CalledProcessException ):
889+ """Print error from a subprocess to the console.
890+
891+ This will output the command, output and return code to the console,
892+ but *not* to the log.
893+
894+ :param exception: The raw exception raised by a subprocess
895+ """
896+ self ._log_command (exception .cmd , handler = self ._console )
897+ self ._log_output (exception .output , exception .stderr , handler = self ._console )
898+ self ._log_return_code (exception .returncode , handler = self ._console )
0 commit comments