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