Hide output of pw_exec programs by default

This change updates the pw_exec GN template and wrapper script to hide
the output of the program unless it exits unsuccessfully.

Sample output:

20200103 13:40:06 ERR
20200103 13:40:06 ERR Command failed with exit code 2 in GN build.
20200103 13:40:06 ERR
20200103 13:40:06 ERR Build target:
20200103 13:40:06 ERR
20200103 13:40:06 ERR   pw_target_runner_client_pw_go_get
20200103 13:40:06 ERR
20200103 13:40:06 ERR Full command:
20200103 13:40:06 ERR
20200103 13:40:06 ERR   go got github.com/golang/protobuf/proto google.golang.org/grpc
20200103 13:40:06 ERR
20200103 13:40:06 ERR Process output:

go got: unknown command
Run 'go help' for usage.

20200103 13:40:06 ERR

Bug: 34
Change-Id: I7e7b2a6d359ecf1eeff6918d56defb909141cbfd
diff --git a/pw_build/exec.gni b/pw_build/exec.gni
index 7c8765d..3e51d57 100644
--- a/pw_build/exec.gni
+++ b/pw_build/exec.gni
@@ -45,6 +45,9 @@
 #    running the program if the file is empty. Used to avoid running commands
 #    which error when called without arguments.
 #
+#  capture_output: If true, output from the program is hidden unless the program
+#    exits with an error. Defaults to true.
+#
 # Example:
 #
 #   pw_exec("hello_world") {
@@ -61,7 +64,10 @@
 template("pw_exec") {
   assert(defined(invoker.program), "pw_exec requires a program to run")
 
-  _script_args = []
+  _script_args = [
+    "--target",
+    target_name,
+  ]
 
   if (defined(invoker.env_file)) {
     _script_args += [
@@ -90,6 +96,10 @@
     }
   }
 
+  if (!defined(invoker.capture_output) || invoker.capture_output) {
+    _script_args += [ "--capture-output" ]
+  }
+
   _script_args += [
     "--",
     invoker.program,
diff --git a/pw_build/py/exec.py b/pw_build/py/exec.py
index 823e275..e05330e 100644
--- a/pw_build/py/exec.py
+++ b/pw_build/py/exec.py
@@ -14,12 +14,18 @@
 """Python wrapper that runs a program. For use in GN."""
 
 import argparse
+import logging
 import os
 import re
+import shlex
 import subprocess
 import sys
 from typing import Dict, Optional
 
+import pw_cli.log
+
+_LOG = logging.getLogger(__name__)
+
 
 def argument_parser(
     parser: Optional[argparse.ArgumentParser] = None
@@ -35,6 +41,11 @@
         help='File containing extra positional arguments to the program',
     )
     parser.add_argument(
+        '--capture-output',
+        action='store_true',
+        help='Hide output from the program unless it fails',
+    )
+    parser.add_argument(
         '-e',
         '--env',
         action='append',
@@ -52,6 +63,10 @@
         help='Don\'t run the program if --args-file is empty',
     )
     parser.add_argument(
+        '--target',
+        help='GN build target that runs the program',
+    )
+    parser.add_argument(
         'command',
         nargs=argparse.REMAINDER,
         help='Program to run with arguments',
@@ -85,6 +100,7 @@
 
 
 def main() -> int:
+    """Runs a program specified by command-line arguments."""
     args = argument_parser().parse_args()
     if not args.command or args.command[0] != '--':
         return 1
@@ -111,8 +127,35 @@
     for string in args.env:
         apply_env_var(string, env)
 
-    return subprocess.call(command, env=env)
+    if args.capture_output:
+        output_args = {'stdout': subprocess.PIPE, 'stderr': subprocess.STDOUT}
+    else:
+        output_args = {}
+
+    process = subprocess.run(command, env=env, **output_args)
+
+    if process.returncode != 0 and args.capture_output:
+        _LOG.error('')
+        _LOG.error('Command failed with exit code %d in GN build.',
+                   process.returncode)
+        _LOG.error('')
+        _LOG.error('Build target:')
+        _LOG.error('')
+        _LOG.error('  %s', args.target)
+        _LOG.error('')
+        _LOG.error('Full command:')
+        _LOG.error('')
+        _LOG.error('  %s', shlex.join(command))
+        _LOG.error('')
+        _LOG.error('Process output:')
+        print(flush=True)
+        sys.stdout.buffer.write(process.stdout)
+        print(flush=True)
+        _LOG.error('')
+
+    return process.returncode
 
 
 if __name__ == '__main__':
+    pw_cli.log.install()
     sys.exit(main())