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