gclient: Preserve ANSI color codes when calling hooks.

If a hook prints error/warning output that's color-coded, gclient
will cause the coloring to be disabled since those hooks aren't
called directly from a terminal.

By emulating a terminal when launching subprocs from gclient, we can
convince them to keep the color escape codes.

LED builds with both //third_party/depot_tools rolled to this CL, as
well as depot_tools in the recipe bundle rolled to this CL:
linux: https://ci.chromium.org/swarming/task/4e40237985888310
mac: https://ci.chromium.org/swarming/task/4e4023ea0c829710
win: https://ci.chromium.org/swarming/task/4e4024612e03dc10

Bug: 1034063
Change-Id: I4150f66ef215ece06f4c32482dcd4ded14eb1bfe
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/2368435
Reviewed-by: Dirk Pranke <dpranke@google.com>
Commit-Queue: Ben Pastene <bpastene@chromium.org>
This commit is contained in:
Ben Pastene
2020-08-26 17:07:03 +00:00
committed by LUCI CQ
parent c6aa151180
commit d410c664e6
3 changed files with 42 additions and 13 deletions

View File

@@ -10,6 +10,7 @@ import codecs
import collections
import contextlib
import datetime
import errno
import functools
import io
import logging
@@ -585,9 +586,19 @@ def CheckCallAndFilter(args, print_stdout=False, filter_fn=None,
sleep_interval = RETRY_INITIAL_SLEEP
run_cwd = kwargs.get('cwd', os.getcwd())
for attempt in range(RETRY_MAX + 1):
# If our stdout is a terminal, then pass in a psuedo-tty pipe to our
# subprocess when filtering its output. This makes the subproc believe
# it was launched from a terminal, which will preserve ANSI color codes.
if sys.stdout.isatty():
pipe_reader, pipe_writer = os.openpty()
else:
pipe_reader, pipe_writer = os.pipe()
kid = subprocess2.Popen(
args, bufsize=0, stdout=subprocess2.PIPE, stderr=subprocess2.STDOUT,
args, bufsize=0, stdout=pipe_writer, stderr=subprocess2.STDOUT,
**kwargs)
# Close the write end of the pipe once we hand it off to the child proc.
os.close(pipe_writer)
GClientChildren.add(kid)
@@ -608,7 +619,14 @@ def CheckCallAndFilter(args, print_stdout=False, filter_fn=None,
try:
line_start = None
while True:
in_byte = kid.stdout.read(1)
try:
in_byte = os.read(pipe_reader, 1)
except (IOError, OSError) as e:
if e.errno == errno.EIO:
# An errno.EIO means EOF?
in_byte = None
else:
raise e
is_newline = in_byte in (b'\n', b'\r')
if not in_byte:
break
@@ -629,8 +647,8 @@ def CheckCallAndFilter(args, print_stdout=False, filter_fn=None,
if line_start is not None:
filter_line(command_output, line_start)
os.close(pipe_reader)
rv = kid.wait()
kid.stdout.close()
# Don't put this in a 'finally,' since the child may still run if we get
# an exception.