ソースを参照

Depot tools: use the clang-format binaries that are now included
as part of the Chromium checkout.

This follows the approach used by gn.

Changes include:
- in-the-PATH clang-format trampoline scripts
- clang_format.py, which finds clang-format binaries inside of Chrome
- Hook 'git cl format' to the new binaries and scripts
- Rearrange some code, for reuse between clang_format.py and gn.py

BUG=240309
TEST=presubmits (one failure on mac, but it fails on a clean checkout too)

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

git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@245074 0039d316-1c4b-4281-b951-d872f2087c98

nick@chromium.org 11 年 前
コミット
3ac1c4e320
7 ファイル変更168 行追加62 行削除
  1. 8 0
      clang-format
  2. 10 0
      clang-format.bat
  3. 90 0
      clang_format.py
  4. 22 3
      gclient_utils.py
  5. 22 17
      git_cl.py
  6. 6 34
      gn.py
  7. 10 8
      tests/gclient_utils_test.py

+ 8 - 0
clang-format

@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+# Copyright 2014 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.
+
+base_dir=$(dirname "$0")
+
+PYTHONDONTWRITEBYTECODE=1 exec python "$base_dir/clang_format.py" "$@"

+ 10 - 0
clang-format.bat

@@ -0,0 +1,10 @@
+@echo off
+:: Copyright 2014 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.
+
+:: This is required with cygwin only.
+PATH=%~dp0;%PATH%
+
+:: Defer control.
+%~dp0python "%~dp0\clang_format.py" %*

+ 90 - 0
clang_format.py

@@ -0,0 +1,90 @@
+#!/usr/bin/env python
+# Copyright 2014 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.
+
+"""Redirects to the version of clang-format checked into the Chrome tree.
+
+clang-format binaries are pulled down from Google Cloud Storage whenever you
+sync Chrome, to platform-specific locations. This script knows how to locate
+those tools, assuming the script is invoked from inside a Chromium checkout."""
+
+import gclient_utils
+import os
+import subprocess
+import sys
+
+
+class NotFoundError(Exception):
+  """A file could not be found."""
+  def __init__(self, e):
+    Exception.__init__(self,
+        'Problem while looking for clang-format in Chromium source tree:\n'
+        '  %s' % e)
+
+
+def _FindChromiumTree():
+  """Return the root of the current chromium checkout, or die trying."""
+  source_root = gclient_utils.FindFileUpwards('.gclient')
+  if not source_root:
+    raise NotFoundError(
+        '.gclient file not found in any parent of the current path.')
+  return source_root
+
+
+def FindClangFormatToolInChromiumTree():
+  """Return a path to the clang-format executable, or die trying."""
+  # The binaries in platform-specific subdirectories in src/tools/gn/bin.
+  tool_path = os.path.join(_FindChromiumTree(), 'src', 'third_party',
+                           'clang_format', 'bin',
+                           gclient_utils.GetMacWinOrLinux(),
+                           'clang-format' + gclient_utils.GetExeSuffix())
+  if not os.path.exists(tool_path):
+    # TODO(nick): After March 2014, eliminate the following advisory.
+    error_text = '''\n  GIT CL FORMAT - WINTER WEATHER ADVISORY
+
+    clang-format binaries now come with every Chrome checkout!
+
+    Unfortunately, your depot_tools scripts tried to find clang-format binaries
+    in your Chrome checkout, but failed. This is expected if you haven't synced
+    since the binaries were added.
+
+    'git cl format' will probably not work until you sync your Chrome tree.
+    Sorry about that.
+
+    Contact nick@chromium.org if you have any additional questions.\n\n'''
+
+    error_text += 'File does not exist: %s' % tool_path
+
+    raise NotFoundError(error_text)
+  return tool_path
+
+
+def FindClangFormatScriptInChromiumTree(script_name):
+  """Return a path to a clang-format helper script, or die trying."""
+  # The binaries in platform-specific subdirectories in src/tools/gn/bin.
+  script_path = os.path.join(_FindChromiumTree(), 'src', 'third_party',
+                             'clang_format', 'scripts', script_name)
+  if not os.path.exists(script_path):
+    raise NotFoundError('File does not exist: %s' % script_path)
+  return script_path
+
+
+def main(args):
+  try:
+    tool = FindClangFormatToolInChromiumTree()
+  except NotFoundError, e:
+    print >> sys.stderr, e
+    sys.exit(1)
+
+  # Add some visibility to --help showing where the tool lives, since this
+  # redirection can be a little opaque.
+  help_syntax = ('-h', '--help', '-help', '-help-list', '--help-list')
+  if any(match in args for match in help_syntax):
+    print '\nDepot tools redirects you to the clang-format at:\n    %s\n' % tool
+
+  return subprocess.call([tool] + sys.argv[1:])
+
+
+if __name__ == '__main__':
+  sys.exit(main(sys.argv))

+ 22 - 3
gclient_utils.py

@@ -33,6 +33,7 @@ class Error(Exception):
       msg = '\n'.join('%d> %s' % (index, l) for l in msg.splitlines())
     super(Error, self).__init__(msg, *args, **kwargs)
 
+
 def SplitUrlRevision(url):
   """Splits url and returns a two-tuple: url, rev"""
   if url.startswith('ssh:'):
@@ -103,10 +104,10 @@ def FileWrite(filename, content, mode='w'):
 def safe_rename(old, new):
   """Renames a file reliably.
 
-  Sometimes os.rename does not work because a dying git process keeps a handle 
-  on it for a few seconds. An exception is then thrown, which make the program 
+  Sometimes os.rename does not work because a dying git process keeps a handle
+  on it for a few seconds. An exception is then thrown, which make the program
   give up what it was doing and remove what was deleted.
-  The only solution is to catch the exception and try again until it works. 
+  The only solution is to catch the exception and try again until it works.
   """
   # roughly 10s
   retries = 100
@@ -560,6 +561,24 @@ def FindFileUpwards(filename, path=None):
     path = new_path
 
 
+def GetMacWinOrLinux():
+  """Returns 'mac', 'win', or 'linux', matching the current platform."""
+  if sys.platform.startswith(('cygwin', 'win')):
+    return 'win'
+  elif sys.platform.startswith('linux'):
+    return 'linux'
+  elif sys.platform == 'darwin':
+    return 'mac'
+  raise Error('Unknown platform: ' + sys.platform)
+
+
+def GetExeSuffix():
+  """Returns '' or '.exe' depending on how executables work on this platform."""
+  if sys.platform.startswith(('cygwin', 'win')):
+    return '.exe'
+  return ''
+
+
 def GetGClientRootAndEntries(path=None):
   """Returns the gclient root and the dict of entries."""
   config_file = '.gclient_entries'

+ 22 - 17
git_cl.py

@@ -32,6 +32,7 @@ except ImportError:
 from third_party import colorama
 from third_party import upload
 import breakpad  # pylint: disable=W0611
+import clang_format
 import fix_encoding
 import gclient_utils
 import presubmit_support
@@ -269,7 +270,7 @@ class Settings(object):
     if not self.updated:
       # The only value that actually changes the behavior is
       # autoupdate = "false". Everything else means "true".
-      autoupdate = RunGit(['config', 'rietveld.autoupdate'], 
+      autoupdate = RunGit(['config', 'rietveld.autoupdate'],
                           error_ok=True
                           ).strip().lower()
 
@@ -2342,7 +2343,14 @@ def CMDformat(parser, args):
   diff_cmd += ['*' + ext for ext in CLANG_EXTS]
   diff_output = RunGit(diff_cmd)
 
-  top_dir = RunGit(["rev-parse", "--show-toplevel"]).rstrip('\n')
+  top_dir = os.path.normpath(
+      RunGit(["rev-parse", "--show-toplevel"]).rstrip('\n'))
+
+  # Locate the clang-format binary in the checkout
+  try:
+    clang_format_tool = clang_format.FindClangFormatToolInChromiumTree()
+  except clang_format.NotFoundError, e:
+    DieWithError(e)
 
   if opts.full:
     # diff_output is a list of files to send to clang-format.
@@ -2350,24 +2358,21 @@ def CMDformat(parser, args):
     if not files:
       print "Nothing to format."
       return 0
-    RunCommand(['clang-format', '-i', '-style', 'Chromium'] + files,
+    RunCommand([clang_format_tool, '-i', '-style', 'Chromium'] + files,
                cwd=top_dir)
   else:
+    env = os.environ.copy()
+    env['PATH'] = os.path.dirname(clang_format_tool)
     # diff_output is a patch to send to clang-format-diff.py
-    cfd_path = os.path.join('/usr', 'lib', 'clang-format',
-                            'clang-format-diff.py')
-    if not os.path.exists(cfd_path):
-      DieWithError('Could not find clang-format-diff at %s.' % cfd_path)
-    cmd = [sys.executable, cfd_path, '-p0', '-style', 'Chromium']
-
-    # Newer versions of clang-format-diff.py require an explicit -i flag
-    # to apply the edits to files, otherwise it just displays a diff.
-    # Probe the usage string to verify if this is needed.
-    help_text = RunCommand([sys.executable, cfd_path, '-h'])
-    if '[-i]' in help_text:
-      cmd.append('-i')
-
-    RunCommand(cmd, stdin=diff_output, cwd=top_dir)
+    try:
+      script = clang_format.FindClangFormatScriptInChromiumTree(
+          'clang-format-diff.py')
+    except clang_format.NotFoundError, e:
+      DieWithError(e)
+
+    cmd = [sys.executable, script, '-p0', '-style', 'Chromium', '-i']
+
+    RunCommand(cmd, stdin=diff_output, cwd=top_dir, env=env)
 
   return 0
 

+ 6 - 34
gn.py

@@ -12,55 +12,27 @@ binary. It will also automatically try to find the gn binary when run inside
 the chrome source tree, so users can just type "gn" on the command line
 (normally depot_tools is on the path)."""
 
+import gclient_utils
 import os
 import subprocess
 import sys
 
 
-class PlatformUnknownError(IOError):
-  pass
-
-
-def HasDotfile(path):
-  """Returns True if the given path has a .gn file in it."""
-  return os.path.exists(path + '/.gn')
-
-
-def FindSourceRootOnPath():
-  """Searches upward from the current directory for the root of the source
-  tree and returns the found path. Returns None if no source root could
-  be found."""
-  cur = os.getcwd()
-  while True:
-    if HasDotfile(cur):
-      return cur
-    up_one = os.path.dirname(cur)
-    if up_one == cur:
-      return None  # Reached the top of the directory tree
-    cur = up_one
-
-
 def RunGN(sourceroot):
   # The binaries in platform-specific subdirectories in src/tools/gn/bin.
-  gnpath = sourceroot + '/tools/gn/bin/'
-  if sys.platform in ('cygwin', 'win32'):
-    gnpath += 'win/gn.exe'
-  elif sys.platform.startswith('linux'):
-    gnpath += 'linux/gn'
-  elif sys.platform == 'darwin':
-    gnpath += 'mac/gn'
-  else:
-    raise PlatformUnknownError('Unknown platform for GN: ' + sys.platform)
-
+  gnpath = os.path.join(sourceroot,
+                        'tools', 'gn', 'bin', gclient_utils.GetMacWinOrLinux(),
+                        'gn' + gclient_utils.GetExeSuffix())
   return subprocess.call([gnpath] + sys.argv[1:])
 
 
 def main(args):
-  sourceroot = FindSourceRootOnPath()
+  sourceroot = gclient_utils.FindFileUpwards('.gn')
   if not sourceroot:
     print >> sys.stderr, '.gn file not found in any parent of the current path.'
     sys.exit(1)
   return RunGN(sourceroot)
 
+
 if __name__ == '__main__':
   sys.exit(main(sys.argv))

+ 10 - 8
tests/gclient_utils_test.py

@@ -31,15 +31,17 @@ class GclientUtilsUnittest(GclientUtilBase):
         'Annotated', 'AutoFlush', 'CheckCallAndFilter', 'CommandToStr',
         'CheckCallAndFilterAndHeader', 'Error', 'ExecutionQueue', 'FileRead',
         'FileWrite', 'FindFileUpwards', 'FindGclientRoot',
-        'GetGClientRootAndEntries', 'GetEditor', 'IsDateRevision',
-        'MakeDateRevision', 'MakeFileAutoFlush', 'MakeFileAnnotated',
-        'PathDifference', 'ParseCodereviewSettingsContent', 'NumLocalCpus',
-        'PrintableObject', 'RETRY_INITIAL_SLEEP', 'RETRY_MAX', 'RunEditor',
-        'GCLIENT_CHILDREN', 'GCLIENT_CHILDREN_LOCK', 'GClientChildren',
-        'SplitUrlRevision', 'SyntaxErrorToError', 'UpgradeToHttps', 'Wrapper',
-        'WorkItem', 'codecs', 'lockedmethod', 'logging', 'os', 'pipes', 'Queue',
-        're', 'rmtree', 'safe_makedirs', 'safe_rename', 'stat', 'subprocess', 
+        'GetGClientRootAndEntries', 'GetEditor', 'GetExeSuffix',
+        'GetMacWinOrLinux', 'IsDateRevision', 'MakeDateRevision',
+        'MakeFileAutoFlush', 'MakeFileAnnotated', 'PathDifference',
+        'ParseCodereviewSettingsContent', 'NumLocalCpus', 'PrintableObject',
+        'RETRY_INITIAL_SLEEP', 'RETRY_MAX', 'RunEditor', 'GCLIENT_CHILDREN',
+        'GCLIENT_CHILDREN_LOCK', 'GClientChildren', 'SplitUrlRevision',
+        'SyntaxErrorToError', 'UpgradeToHttps', 'Wrapper', 'WorkItem',
+        'codecs', 'lockedmethod', 'logging', 'os', 'pipes', 'Queue', 're',
+        'rmtree', 'safe_makedirs', 'safe_rename', 'stat', 'subprocess',
         'subprocess2', 'sys', 'tempfile', 'threading', 'time', 'urlparse',
+
     ]
     # If this test fails, you should add the relevant test.
     self.compareMembers(gclient_utils, members)