Преглед на файлове

Fix coloring madness in depot_tools.

'setup_color' now contains logic to correctly detect:
  * cmd
  * cmd pipe
  * msys bash
  * msys pipe
  * cmd running inside msys bash (git-command)
  * cmd pipe running inside msys bash (git-command > outfile)

R=brucedawson@chromium.org, dnj@chromium.org
BUG=600049

Review URL: https://codereview.chromium.org/1851283002

git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@299682 0039d316-1c4b-4281-b951-d872f2087c98
iannucci@chromium.org преди 9 години
родител
ревизия
596cd5c95d
променени са 6 файла, в които са добавени 112 реда и са изтрити 16 реда
  1. 2 3
      depot-tools-auth.py
  2. 3 3
      gclient.py
  3. 4 3
      git_cl.py
  4. 3 2
      git_common.py
  5. 6 5
      git_map_branches.py
  6. 94 0
      setup_color.py

+ 2 - 3
depot-tools-auth.py

@@ -16,9 +16,8 @@ import optparse
 import sys
 import os
 
-from third_party import colorama
-
 import auth
+import setup_color
 import subcommand
 
 __version__ = '1.0'
@@ -95,7 +94,7 @@ def main(argv):
 
 
 if __name__ == '__main__':
-  colorama.init(wrap="TERM" not in os.environ)
+  setup_color.init()
   try:
     sys.exit(main(sys.argv[1:]))
   except KeyboardInterrupt:

+ 3 - 3
gclient.py

@@ -102,7 +102,7 @@ import git_cache
 from third_party.repo.progress import Progress
 import subcommand
 import subprocess2
-from third_party import colorama
+import setup_color
 
 CHROMIUM_SRC_URL = 'https://chromium.googlesource.com/chromium/src.git'
 
@@ -1511,7 +1511,7 @@ been automagically updated.  The previous version is available at %s.old.
       revision_overrides = self._EnforceRevisions()
     pm = None
     # Disable progress for non-tty stdout.
-    if (sys.stdout.isatty() and not self._options.verbose and progress):
+    if (setup_color.IS_TTY and not self._options.verbose and progress):
       if command in ('update', 'revert'):
         pm = Progress('Syncing projects', 1)
       elif command == 'recurse':
@@ -2303,7 +2303,7 @@ def main(argv):
     return 2
   fix_encoding.fix_encoding()
   disable_buffering()
-  colorama.init(wrap="TERM" not in os.environ)
+  setup_color.init()
   dispatcher = subcommand.CommandDispatcher(__name__)
   try:
     return dispatcher.execute(OptionParser(), argv)

+ 4 - 3
git_cl.py

@@ -45,6 +45,7 @@ from luci_hacks import trigger_luci_job as luci_trigger
 import clang_format
 import commit_queue
 import dart_format
+import setup_color
 import fix_encoding
 import gclient_utils
 import gerrit_util
@@ -2481,7 +2482,7 @@ def CMDstatus(parser, args):
     issue_url, status = branch_statuses.pop(branch)
     color = color_for_status(status)
     reset = Fore.RESET
-    if not sys.stdout.isatty():
+    if not setup_color.IS_TTY:
       color = ''
       reset = ''
     status_str = '(%s)' % status if status else ''
@@ -4181,7 +4182,7 @@ def CMDtry_results(parser, args):
   group.add_option(
       "--print-master", action='store_true', help="print master name as well.")
   group.add_option(
-      "--color", action='store_true', default=sys.stdout.isatty(),
+      "--color", action='store_true', default=setup_color.IS_TTY,
       help="force color output, useful when piping output.")
   group.add_option(
       "--buildbucket-host", default='cr-buildbucket.appspot.com',
@@ -4615,7 +4616,7 @@ if __name__ == '__main__':
   # These affect sys.stdout so do it outside of main() to simplify mocks in
   # unit testing.
   fix_encoding.fix_encoding()
-  colorama.init(wrap="TERM" not in os.environ)
+  setup_color.init()
   try:
     sys.exit(main(sys.argv[1:]))
   except KeyboardInterrupt:

+ 3 - 2
git_common.py

@@ -22,6 +22,7 @@ import functools
 import logging
 import os
 import re
+import setup_color
 import shutil
 import signal
 import sys
@@ -281,7 +282,7 @@ def once(function):
 ## Git functions
 
 
-def blame(filename, revision=None, porcelain=False, *args):
+def blame(filename, revision=None, porcelain=False, *_args):
   command = ['blame']
   if porcelain:
     command.append('-p')
@@ -576,7 +577,7 @@ def less():  # pragma: no cover
   Automatically checks if sys.stdout is a non-TTY stream. If so, it avoids
   running less and just yields sys.stdout.
   """
-  if not sys.stdout.isatty():
+  if not setup_color.IS_TTY:
     yield sys.stdout
     return
 

+ 6 - 5
git_map_branches.py

@@ -28,16 +28,17 @@ Branches are colorized as follows:
 import argparse
 import collections
 import os
-import sys
 import subprocess2
-
-from third_party import colorama
-from third_party.colorama import Fore, Style
+import sys
 
 from git_common import current_branch, upstream, tags, get_branches_info
 from git_common import get_git_version, MIN_UPSTREAM_TRACK_GIT_VERSION, hash_one
 from git_common import run
 
+import setup_color
+
+from third_party.colorama import Fore, Style
+
 DEFAULT_SEPARATOR = ' ' * 4
 
 
@@ -272,7 +273,7 @@ class BranchMapper(object):
 
 
 def main(argv):
-  colorama.init(wrap="TERM" not in os.environ)
+  setup_color.init()
   if get_git_version() < MIN_UPSTREAM_TRACK_GIT_VERSION:
     print >> sys.stderr, (
         'This tool will not show all tracking information for git version '

+ 94 - 0
setup_color.py

@@ -0,0 +1,94 @@
+#!/usr/bin/env python
+# Copyright (c) 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import os
+import sys
+from third_party import colorama
+
+IS_TTY = None
+OUT_TYPE = 'unknown'
+
+def init():
+  # should_wrap instructs colorama to wrap stdout/stderr with an ASNI colorcode
+  # interpreter that converts them to SetConsoleTextAttribute calls. This only
+  # should be True in cases where we're connected to cmd.exe's console. Setting
+  # this to True on non-windows systems has no effect.
+  should_wrap = False
+  global IS_TTY, OUT_TYPE
+  IS_TTY = sys.stdout.isatty()
+  if IS_TTY:
+    # Yay! We detected a console in the normal way. It doesn't really matter
+    # if it's windows or not, we win.
+    OUT_TYPE = 'console'
+    should_wrap = True
+  elif sys.platform.startswith('win'):
+    # assume this is some sort of file
+    OUT_TYPE = 'file (win)'
+
+    import msvcrt
+    import ctypes
+    h = msvcrt.get_osfhandle(sys.stdout.fileno())
+    # h is the win32 HANDLE for stdout.
+    ftype = ctypes.windll.kernel32.GetFileType(h)
+    if ftype == 2: # FILE_TYPE_CHAR
+      # This is a normal cmd console, but we'll only get here if we're running
+      # inside a `git command` which is actually git->bash->command. Not sure
+      # why isatty doesn't detect this case.
+      OUT_TYPE = 'console (cmd via msys)'
+      IS_TTY = True
+      should_wrap = True
+    elif ftype == 3: # FILE_TYPE_PIPE
+      OUT_TYPE = 'pipe (win)'
+      # This is some kind of pipe on windows. This could either be a real pipe
+      # or this could be msys using a pipe to emulate a pty. We use the same
+      # algorithm that msys-git uses to determine if it's connected to a pty or
+      # not.
+
+      # This function and the structures are defined in the MSDN documentation
+      # using the same names.
+      def NT_SUCCESS(status):
+        # The first two bits of status are the severity. The success
+        # severities are 0 and 1, and the !success severities are 2 and 3.
+        # Therefore since ctypes interprets the default restype of the call
+        # to be an 'C int' (which is guaranteed to be signed 32 bits), All
+        # success codes are positive, and all !success codes are negative.
+        return status >= 0
+
+      class UNICODE_STRING(ctypes.Structure):
+        _fields_ = [('Length', ctypes.c_ushort),
+                    ('MaximumLength', ctypes.c_ushort),
+                    ('Buffer', ctypes.c_wchar_p)]
+
+      class OBJECT_NAME_INFORMATION(ctypes.Structure):
+        _fields_ = [('Name', UNICODE_STRING),
+                    ('NameBuffer', ctypes.c_wchar_p)]
+
+      buf = ctypes.create_string_buffer('\0', 1024)
+      # Ask NT what the name of the object our stdout HANDLE is. It would be
+      # possible to use GetFileInformationByHandleEx, but it's only available
+      # on Vista+. If you're reading this in 2017 or later, feel free to
+      # refactor this out.
+      #
+      # The '1' here is ObjectNameInformation
+      if NT_SUCCESS(ctypes.windll.ntdll.NtQueryObject(h, 1, buf, len(buf)-2,
+                    None)):
+        out = OBJECT_NAME_INFORMATION.from_buffer(buf)
+        name = out.Name.Buffer.split('\\')[-1]
+        IS_TTY = name.startswith('msys-') and '-pty' in name
+        if IS_TTY:
+          OUT_TYPE = 'bash (msys)'
+    else:
+      # A normal file, or an unknown file type.
+      pass
+  else:
+    # This is non-windows, so we trust isatty.
+    OUT_TYPE = 'pipe or file'
+
+  colorama.init(wrap=should_wrap)
+
+if __name__ == '__main__':
+  init()
+  print 'IS_TTY:', IS_TTY
+  print 'OUT_TYPE:', OUT_TYPE