Update common.py.utils to be used in Skia repo
BUG=skia:2682
R=rmistry@google.com
Review URL: https://codereview.chromium.org/346743008
diff --git a/py/utils/find_depot_tools.py b/py/utils/find_depot_tools.py
new file mode 100644
index 0000000..3ea5dd7
--- /dev/null
+++ b/py/utils/find_depot_tools.py
@@ -0,0 +1,51 @@
+# Copyright (c) 2012 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.
+"""Small utility function to find depot_tools and add it to the python path.
+
+Will throw an ImportError exception if depot_tools can't be found since it
+imports breakpad.
+
+This file is directly copied from:
+https://chromium.googlesource.com/chromium/tools/build/+/master/scripts/common/find_depot_tools.py
+"""
+
+import os
+import sys
+
+
+def directory_really_is_depot_tools(directory):
+ return os.path.isfile(os.path.join(directory, 'breakpad.py'))
+
+
+def add_depot_tools_to_path():
+ """Search for depot_tools and add it to sys.path."""
+ # First look if depot_tools is already in PYTHONPATH.
+ for i in sys.path:
+ if i.rstrip(os.sep).endswith('depot_tools'):
+ if directory_really_is_depot_tools(i):
+ return i
+
+ # Then look if depot_tools is in PATH, common case.
+ for i in os.environ['PATH'].split(os.pathsep):
+ if i.rstrip(os.sep).endswith('depot_tools'):
+ if directory_really_is_depot_tools(i):
+ sys.path.insert(0, i.rstrip(os.sep))
+ return i
+ # Rare case, it's not even in PATH, look upward up to root.
+ root_dir = os.path.dirname(os.path.abspath(__file__))
+ previous_dir = os.path.abspath(__file__)
+ while root_dir and root_dir != previous_dir:
+ if directory_really_is_depot_tools(os.path.join(root_dir, 'depot_tools')):
+ i = os.path.join(root_dir, 'depot_tools')
+ sys.path.insert(0, i)
+ return i
+ previous_dir = root_dir
+ root_dir = os.path.dirname(root_dir)
+ print >> sys.stderr, 'Failed to find depot_tools'
+ return None
+
+add_depot_tools_to_path()
+
+# pylint: disable=W0611
+import breakpad
diff --git a/py/utils/git_utils.py b/py/utils/git_utils.py
index da7f097..5ac63ec 100644
--- a/py/utils/git_utils.py
+++ b/py/utils/git_utils.py
@@ -6,25 +6,54 @@
"""This module contains functions for using git."""
-import os
+import re
import shell_utils
-GIT = 'git.bat' if os.name == 'nt' else 'git'
+def _FindGit():
+ """Find the git executable.
+
+ Returns:
+ A string suitable for passing to subprocess functions, or None.
+ """
+ def test_git_executable(git):
+ """Test the git executable.
+
+ Args:
+ git: git executable path.
+ Returns:
+ True if test is successful.
+ """
+ try:
+ shell_utils.run([git, '--version'], echo=False)
+ return True
+ except (OSError,):
+ return False
+
+ for git in ('git', 'git.exe', 'git.bat'):
+ if test_git_executable(git):
+ return git
+ return None
+
+
+GIT = _FindGit()
def Add(addition):
"""Run 'git add <addition>'"""
shell_utils.run([GIT, 'add', addition])
+
def AIsAncestorOfB(a, b):
"""Return true if a is an ancestor of b."""
return shell_utils.run([GIT, 'merge-base', a, b]).rstrip() == FullHash(a)
+
def FullHash(commit):
"""Return full hash of specified commit."""
return shell_utils.run([GIT, 'rev-parse', '--verify', commit]).rstrip()
+
def IsMerge(commit):
"""Return True if the commit is a merge, False otherwise."""
rev_parse = shell_utils.run([GIT, 'rev-parse', commit, '--max-count=1',
@@ -33,14 +62,85 @@
# Get full hash since that is what was returned by rev-parse.
return FullHash(commit) != last_non_merge
+
def MergeAbort():
"""Abort in process merge."""
shell_utils.run([GIT, 'merge', '--abort'])
+
def ShortHash(commit):
"""Return short hash of the specified commit."""
return shell_utils.run([GIT, 'show', commit, '--format=%h', '-s']).rstrip()
+
+def Fetch(remote=None):
+ """Run "git fetch". """
+ cmd = [GIT, 'fetch']
+ if remote:
+ cmd.append(remote)
+ shell_utils.run(cmd)
+
+
def GetRemoteMasterHash(git_url):
return shell_utils.run([GIT, 'ls-remote', git_url, '--verify',
- 'refs/heads/master'])
+ 'refs/heads/master']).rstrip()
+
+
+def GetCurrentBranch():
+ return shell_utils.run([GIT, 'rev-parse', '--abbrev-ref', 'HEAD']).rstrip()
+
+
+class GitBranch(object):
+ """Class to manage git branches.
+
+ This class allows one to create a new branch in a repository to make changes,
+ then it commits the changes, switches to master branch, and deletes the
+ created temporary branch upon exit.
+ """
+ def __init__(self, branch_name, commit_msg, upload=True, commit_queue=False,
+ delete_when_finished=True):
+ self._branch_name = branch_name
+ self._commit_msg = commit_msg
+ self._upload = upload
+ self._commit_queue = commit_queue
+ self._patch_set = 0
+ self._delete_when_finished = delete_when_finished
+
+ def __enter__(self):
+ shell_utils.run([GIT, 'reset', '--hard', 'HEAD'])
+ shell_utils.run([GIT, 'checkout', 'master'])
+ if self._branch_name in shell_utils.run([GIT, 'branch']):
+ shell_utils.run([GIT, 'branch', '-D', self._branch_name])
+ shell_utils.run([GIT, 'checkout', '-b', self._branch_name,
+ '-t', 'origin/master'])
+ return self
+
+ def commit_and_upload(self, use_commit_queue=False):
+ """Commit all changes and upload a CL, returning the issue URL."""
+ try:
+ shell_utils.run([GIT, 'commit', '-a', '-m', self._commit_msg])
+ except shell_utils.CommandFailedException as e:
+ if not 'nothing to commit' in e.output:
+ raise
+ upload_cmd = [GIT, 'cl', 'upload', '-f', '--bypass-hooks',
+ '--bypass-watchlists']
+ self._patch_set += 1
+ if self._patch_set > 1:
+ upload_cmd.extend(['-t', 'Patch set %d' % self._patch_set])
+ if use_commit_queue:
+ upload_cmd.append('--use-commit-queue')
+ shell_utils.run(upload_cmd)
+ output = shell_utils.run([GIT, 'cl', 'issue']).rstrip()
+ return re.match('^Issue number: (?P<issue>\d+) \((?P<issue_url>.+)\)$',
+ output).group('issue_url')
+
+ def __exit__(self, exc_type, _value, _traceback):
+ if self._upload:
+ # Only upload if no error occurred.
+ try:
+ if exc_type is None:
+ self.commit_and_upload(use_commit_queue=self._commit_queue)
+ finally:
+ shell_utils.run([GIT, 'checkout', 'master'])
+ if self._delete_when_finished:
+ shell_utils.run([GIT, 'branch', '-D', self._branch_name])
diff --git a/py/utils/misc.py b/py/utils/misc.py
index a1bec6a..c3e4dc8 100644
--- a/py/utils/misc.py
+++ b/py/utils/misc.py
@@ -3,13 +3,10 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-""" This module contains miscellaneous tools used by the buildbot scripts. """
+""" This module contains miscellaneous tools. """
import os
-from git_utils import GIT
-import shell_utils
-
# Absolute path to the root of this Skia buildbot checkout.
BUILDBOT_PATH = os.path.realpath(os.path.join(
@@ -93,50 +90,3 @@
if self._verbose:
print 'chdir %s' % self._origin
os.chdir(self._origin)
-
-
-class GitBranch(object):
- """Class to manage git branches.
-
- This class allows one to create a new branch in a repository to make changes,
- then it commits the changes, switches to master branch, and deletes the
- created temporary branch upon exit.
- """
- def __init__(self, branch_name, commit_msg, upload=True, commit_queue=False):
- self._branch_name = branch_name
- self._commit_msg = commit_msg
- self._upload = upload
- self._commit_queue = commit_queue
- self._patch_set = 0
-
- def __enter__(self):
- shell_utils.run([GIT, 'reset', '--hard', 'HEAD'])
- shell_utils.run([GIT, 'checkout', 'master'])
- if self._branch_name in shell_utils.run([GIT, 'branch']):
- shell_utils.run([GIT, 'branch', '-D', self._branch_name])
- shell_utils.run([GIT, 'checkout', '-b', self._branch_name,
- '-t', 'origin/master'])
- return self
-
- def commit_and_upload(self, use_commit_queue=False):
- shell_utils.run([GIT, 'commit', '-a', '-m',
- self._commit_msg])
- upload_cmd = [GIT, 'cl', 'upload', '-f', '--bypass-hooks',
- '--bypass-watchlists']
- self._patch_set += 1
- if self._patch_set > 1:
- upload_cmd.extend(['-t', 'Patch set %d' % self._patch_set])
- if use_commit_queue:
- upload_cmd.append('--use-commit-queue')
- shell_utils.run(upload_cmd)
-
- def __exit__(self, exc_type, _value, _traceback):
- if self._upload:
- # Only upload if no error occurred.
- try:
- if exc_type is None:
- self.commit_and_upload(use_commit_queue=self._commit_queue)
- finally:
- shell_utils.run([GIT, 'checkout', 'master'])
- shell_utils.run([GIT, 'branch', '-D', self._branch_name])
-
diff --git a/py/utils/shell_utils.py b/py/utils/shell_utils.py
index a6b54ef..37d124b 100644
--- a/py/utils/shell_utils.py
+++ b/py/utils/shell_utils.py
@@ -20,6 +20,7 @@
DEFAULT_SECS_BETWEEN_ATTEMPTS = 10
POLL_MILLIS = 250
+VERBOSE = True
class CommandFailedException(Exception):
@@ -46,9 +47,11 @@
pass
-def run_async(cmd, echo=True, shell=False):
+def run_async(cmd, echo=None, shell=False):
""" Run 'cmd' in a subprocess, returning a Popen class instance referring to
that process. (Non-blocking) """
+ if echo is None:
+ echo = VERBOSE
if echo:
print cmd
if 'nt' in os.name:
@@ -99,7 +102,7 @@
self._stopped = True
-def log_process_in_real_time(proc, echo=True, timeout=None, log_file=None,
+def log_process_in_real_time(proc, echo=None, timeout=None, log_file=None,
halt_on_output=None, print_timestamps=True):
""" Log the output of proc in real time until it completes. Return a tuple
containing the exit code of proc and the contents of stdout.
@@ -114,6 +117,8 @@
print_timestamps: boolean indicating whether a formatted timestamp should be
prepended to each line of output.
"""
+ if echo is None:
+ echo = VERBOSE
stdout_queue = Queue.Queue()
log_thread = EnqueueThread(proc.stdout, stdout_queue)
log_thread.start()
@@ -153,7 +158,8 @@
return (code, ''.join(all_output))
-def log_process_after_completion(proc, echo=True, timeout=None, log_file=None):
+def log_process_after_completion(proc, echo=None, timeout=None,
+ log_file=None):
""" Wait for proc to complete and return a tuple containing the exit code of
proc and the contents of stdout. Unlike log_process_in_real_time, does not
attempt to read stdout from proc in real time.
@@ -164,6 +170,8 @@
TimeoutException if the run time exceeds the timeout.
log_file: an open file for writing outout
"""
+ if echo is None:
+ echo = VERBOSE
t_0 = time.time()
code = None
while code is None:
@@ -182,7 +190,7 @@
return (code, output)
-def run(cmd, echo=True, shell=False, timeout=None, print_timestamps=True,
+def run(cmd, echo=None, shell=False, timeout=None, print_timestamps=True,
log_in_real_time=True):
""" Run 'cmd' in a shell and return the combined contents of stdout and
stderr (Blocking). Throws an exception if the command exits non-zero.
@@ -202,6 +210,8 @@
subprocess in real time instead of when the process finishes. If echo is
False, we never log in real time, even if log_in_real_time is True.
"""
+ if echo is None:
+ echo = VERBOSE
proc = run_async(cmd, echo=echo, shell=shell)
# If we're not printing the output, we don't care if the output shows up in
# real time, so don't bother.
@@ -218,11 +228,13 @@
return output
-def run_retry(cmd, echo=True, shell=False, attempts=1,
+def run_retry(cmd, echo=None, shell=False, attempts=1,
secs_between_attempts=DEFAULT_SECS_BETWEEN_ATTEMPTS,
timeout=None, print_timestamps=True):
""" Wrapper for run() which makes multiple attempts until either the command
succeeds or the maximum number of attempts is reached. """
+ if echo is None:
+ echo = VERBOSE
attempt = 1
while True:
try: