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