xref: /aosp_15_r20/external/skia/infra/bots/utils.py (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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