• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2#
3# Copyright 2016 Google Inc.
4#
5# Use of this source code is governed by a BSD-style license that can be
6# found in the LICENSE file.
7
8
9from __future__ import print_function
10import datetime
11import errno
12import os
13import shutil
14import sys
15import subprocess
16import tempfile
17import time
18import uuid
19
20
21SKIA_REPO = 'https://skia.googlesource.com/skia.git'
22
23GCLIENT = 'gclient.bat' if sys.platform == 'win32' else 'gclient'
24WHICH = 'where' if sys.platform == 'win32' else 'which'
25GIT = subprocess.check_output([WHICH, 'git']).splitlines()[0]
26
27class print_timings(object):
28  def __init__(self):
29    self._start = None
30
31  def __enter__(self):
32    self._start = datetime.datetime.utcnow()
33    print('Task started at %s GMT' % str(self._start))
34
35  def __exit__(self, t, v, tb):
36    finish = datetime.datetime.utcnow()
37    duration = (finish-self._start).total_seconds()
38    print('Task finished at %s GMT (%f seconds)' % (str(finish), duration))
39
40
41class tmp_dir(object):
42  """Helper class used for creating a temporary directory and working in it."""
43  def __init__(self):
44    self._orig_dir = None
45    self._tmp_dir = None
46
47  def __enter__(self):
48    self._orig_dir = os.getcwd()
49    self._tmp_dir = tempfile.mkdtemp()
50    os.chdir(self._tmp_dir)
51    return self
52
53  def __exit__(self, t, v, tb):
54    os.chdir(self._orig_dir)
55    RemoveDirectory(self._tmp_dir)
56
57  @property
58  def name(self):
59    return self._tmp_dir
60
61
62class chdir(object):
63  """Helper class used for changing into and out of a directory."""
64  def __init__(self, d):
65    self._dir = d
66    self._orig_dir = None
67
68  def __enter__(self):
69    self._orig_dir = os.getcwd()
70    os.chdir(self._dir)
71    return self
72
73  def __exit__(self, t, v, tb):
74    os.chdir(self._orig_dir)
75
76
77def git_clone(repo_url, dest_dir):
78  """Clone the given repo into the given destination directory."""
79  subprocess.check_call([GIT, 'clone', repo_url, dest_dir])
80
81
82class git_branch(object):
83  """Check out a temporary git branch.
84
85  On exit, deletes the branch and attempts to restore the original state.
86  """
87  def __init__(self):
88    self._branch = None
89    self._orig_branch = None
90    self._stashed = False
91
92  def __enter__(self):
93    output = subprocess.check_output([GIT, 'stash'])
94    self._stashed = 'No local changes' not in output
95
96    # Get the original branch name or commit hash.
97    self._orig_branch = subprocess.check_output([
98        GIT, 'rev-parse', '--abbrev-ref', 'HEAD']).rstrip()
99    if self._orig_branch == 'HEAD':
100      self._orig_branch = subprocess.check_output([
101          GIT, 'rev-parse', 'HEAD']).rstrip()
102
103    # Check out a new branch, based at updated origin/main.
104    subprocess.check_call([GIT, 'fetch', 'origin'])
105    self._branch = '_tmp_%s' % uuid.uuid4()
106    subprocess.check_call([GIT, 'checkout', '-b', self._branch,
107                           '-t', 'origin/main'])
108    return self
109
110  def __exit__(self, exc_type, _value, _traceback):
111    subprocess.check_call([GIT, 'reset', '--hard', 'HEAD'])
112    subprocess.check_call([GIT, 'checkout', self._orig_branch])
113    if self._stashed:
114      subprocess.check_call([GIT, 'stash', 'pop'])
115    subprocess.check_call([GIT, 'branch', '-D', self._branch])
116
117
118def RemoveDirectory(*path):
119  """Recursively removes a directory, even if it's marked read-only.
120
121  This was copied from:
122  https://chromium.googlesource.com/chromium/tools/build/+/f3e7ff03613cd59a463b2ccc49773c3813e77404/scripts/common/chromium_utils.py#491
123
124  Remove the directory located at *path, if it exists.
125
126  shutil.rmtree() doesn't work on Windows if any of the files or directories
127  are read-only, which svn repositories and some .svn files are.  We need to
128  be able to force the files to be writable (i.e., deletable) as we traverse
129  the tree.
130
131  Even with all this, Windows still sometimes fails to delete a file, citing
132  a permission error (maybe something to do with antivirus scans or disk
133  indexing).  The best suggestion any of the user forums had was to wait a
134  bit and try again, so we do that too.  It's hand-waving, but sometimes it
135  works. :/
136  """
137  file_path = os.path.join(*path)
138  if not os.path.exists(file_path):
139    return
140
141  if sys.platform == 'win32':
142    # Give up and use cmd.exe's rd command.
143    file_path = os.path.normcase(file_path)
144    for _ in range(3):
145      print('RemoveDirectory running %s' % (' '.join(
146          ['cmd.exe', '/c', 'rd', '/q', '/s', file_path])))
147      if not subprocess.call(['cmd.exe', '/c', 'rd', '/q', '/s', file_path]):
148        break
149      print('  Failed')
150      time.sleep(3)
151    return
152
153  def RemoveWithRetry_non_win(rmfunc, path):
154    if os.path.islink(path):
155      return os.remove(path)
156    else:
157      return rmfunc(path)
158
159  remove_with_retry = RemoveWithRetry_non_win
160
161  def RmTreeOnError(function, path, excinfo):
162    r"""This works around a problem whereby python 2.x on Windows has no ability
163    to check for symbolic links.  os.path.islink always returns False.  But
164    shutil.rmtree will fail if invoked on a symbolic link whose target was
165    deleted before the link.  E.g., reproduce like this:
166    > mkdir test
167    > mkdir test\1
168    > mklink /D test\current test\1
169    > python -c "import chromium_utils; chromium_utils.RemoveDirectory('test')"
170    To avoid this issue, we pass this error-handling function to rmtree.  If
171    we see the exact sort of failure, we ignore it.  All other failures we re-
172    raise.
173    """
174
175    exception_type = excinfo[0]
176    exception_value = excinfo[1]
177    # If shutil.rmtree encounters a symbolic link on Windows, os.listdir will
178    # fail with a WindowsError exception with an ENOENT errno (i.e., file not
179    # found).  We'll ignore that error.  Note that WindowsError is not defined
180    # for non-Windows platforms, so we use OSError (of which it is a subclass)
181    # to avoid lint complaints about an undefined global on non-Windows
182    # platforms.
183    if (function is os.listdir) and issubclass(exception_type, OSError):
184      if exception_value.errno == errno.ENOENT:
185        # File does not exist, and we're trying to delete, so we can ignore the
186        # failure.
187        print('WARNING:  Failed to list %s during rmtree.  Ignoring.\n' % path)
188      else:
189        raise
190    else:
191      raise
192
193  for root, dirs, files in os.walk(file_path, topdown=False):
194    # For POSIX:  making the directory writable guarantees removability.
195    # Windows will ignore the non-read-only bits in the chmod value.
196    os.chmod(root, 0o770)
197    for name in files:
198      remove_with_retry(os.remove, os.path.join(root, name))
199    for name in dirs:
200      remove_with_retry(lambda p: shutil.rmtree(p, onerror=RmTreeOnError),
201                        os.path.join(root, name))
202
203  remove_with_retry(os.rmdir, file_path)
204