11cb0ef41Sopenharmony_ci#!/usr/bin/env python3
21cb0ef41Sopenharmony_ci# Copyright 2013 the V8 project authors. All rights reserved.
31cb0ef41Sopenharmony_ci# Redistribution and use in source and binary forms, with or without
41cb0ef41Sopenharmony_ci# modification, are permitted provided that the following conditions are
51cb0ef41Sopenharmony_ci# met:
61cb0ef41Sopenharmony_ci#
71cb0ef41Sopenharmony_ci#     * Redistributions of source code must retain the above copyright
81cb0ef41Sopenharmony_ci#       notice, this list of conditions and the following disclaimer.
91cb0ef41Sopenharmony_ci#     * Redistributions in binary form must reproduce the above
101cb0ef41Sopenharmony_ci#       copyright notice, this list of conditions and the following
111cb0ef41Sopenharmony_ci#       disclaimer in the documentation and/or other materials provided
121cb0ef41Sopenharmony_ci#       with the distribution.
131cb0ef41Sopenharmony_ci#     * Neither the name of Google Inc. nor the names of its
141cb0ef41Sopenharmony_ci#       contributors may be used to endorse or promote products derived
151cb0ef41Sopenharmony_ci#       from this software without specific prior written permission.
161cb0ef41Sopenharmony_ci#
171cb0ef41Sopenharmony_ci# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
181cb0ef41Sopenharmony_ci# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
191cb0ef41Sopenharmony_ci# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
201cb0ef41Sopenharmony_ci# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
211cb0ef41Sopenharmony_ci# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
221cb0ef41Sopenharmony_ci# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
231cb0ef41Sopenharmony_ci# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
241cb0ef41Sopenharmony_ci# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
251cb0ef41Sopenharmony_ci# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
261cb0ef41Sopenharmony_ci# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
271cb0ef41Sopenharmony_ci# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
281cb0ef41Sopenharmony_ci
291cb0ef41Sopenharmony_ciimport argparse
301cb0ef41Sopenharmony_ciimport datetime
311cb0ef41Sopenharmony_cifrom distutils.version import LooseVersion
321cb0ef41Sopenharmony_ciimport glob
331cb0ef41Sopenharmony_ciimport imp
341cb0ef41Sopenharmony_ciimport json
351cb0ef41Sopenharmony_ciimport os
361cb0ef41Sopenharmony_ciimport re
371cb0ef41Sopenharmony_ciimport shutil
381cb0ef41Sopenharmony_ciimport subprocess
391cb0ef41Sopenharmony_ciimport sys
401cb0ef41Sopenharmony_ciimport textwrap
411cb0ef41Sopenharmony_ciimport time
421cb0ef41Sopenharmony_ciimport urllib
431cb0ef41Sopenharmony_ci
441cb0ef41Sopenharmony_cifrom git_recipes import GitRecipesMixin
451cb0ef41Sopenharmony_cifrom git_recipes import GitFailedException
461cb0ef41Sopenharmony_ci
471cb0ef41Sopenharmony_ciimport http.client as httplib
481cb0ef41Sopenharmony_ciimport urllib.request as urllib2
491cb0ef41Sopenharmony_ci
501cb0ef41Sopenharmony_ci
511cb0ef41Sopenharmony_ciDAY_IN_SECONDS = 24 * 60 * 60
521cb0ef41Sopenharmony_ciPUSH_MSG_GIT_RE = re.compile(r".* \(based on (?P<git_rev>[a-fA-F0-9]+)\)$")
531cb0ef41Sopenharmony_ciPUSH_MSG_NEW_RE = re.compile(r"^Version \d+\.\d+\.\d+$")
541cb0ef41Sopenharmony_ciVERSION_FILE = os.path.join("include", "v8-version.h")
551cb0ef41Sopenharmony_ciWATCHLISTS_FILE = "WATCHLISTS"
561cb0ef41Sopenharmony_ciRELEASE_WORKDIR = "/tmp/v8-release-scripts-work-dir/"
571cb0ef41Sopenharmony_ci
581cb0ef41Sopenharmony_ci# V8 base directory.
591cb0ef41Sopenharmony_ciV8_BASE = os.path.dirname(
601cb0ef41Sopenharmony_ci    os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
611cb0ef41Sopenharmony_ci
621cb0ef41Sopenharmony_ci# Add our copy of depot_tools to the PATH as many scripts use tools from there,
631cb0ef41Sopenharmony_ci# e.g. git-cl, fetch, git-new-branch etc, and we can not depend on depot_tools
641cb0ef41Sopenharmony_ci# being in the PATH on the LUCI bots.
651cb0ef41Sopenharmony_cipath_to_depot_tools = os.path.join(V8_BASE, 'third_party', 'depot_tools')
661cb0ef41Sopenharmony_cinew_path = path_to_depot_tools + os.pathsep + os.environ.get('PATH')
671cb0ef41Sopenharmony_cios.environ['PATH'] = new_path
681cb0ef41Sopenharmony_ci
691cb0ef41Sopenharmony_ci
701cb0ef41Sopenharmony_cidef TextToFile(text, file_name):
711cb0ef41Sopenharmony_ci  with open(file_name, "w") as f:
721cb0ef41Sopenharmony_ci    f.write(text)
731cb0ef41Sopenharmony_ci
741cb0ef41Sopenharmony_ci
751cb0ef41Sopenharmony_cidef AppendToFile(text, file_name):
761cb0ef41Sopenharmony_ci  with open(file_name, "a") as f:
771cb0ef41Sopenharmony_ci    f.write(text)
781cb0ef41Sopenharmony_ci
791cb0ef41Sopenharmony_ci
801cb0ef41Sopenharmony_cidef LinesInFile(file_name):
811cb0ef41Sopenharmony_ci  with open(file_name) as f:
821cb0ef41Sopenharmony_ci    for line in f:
831cb0ef41Sopenharmony_ci      yield line
841cb0ef41Sopenharmony_ci
851cb0ef41Sopenharmony_ci
861cb0ef41Sopenharmony_cidef FileToText(file_name):
871cb0ef41Sopenharmony_ci  with open(file_name) as f:
881cb0ef41Sopenharmony_ci    return f.read()
891cb0ef41Sopenharmony_ci
901cb0ef41Sopenharmony_ci
911cb0ef41Sopenharmony_cidef MSub(rexp, replacement, text):
921cb0ef41Sopenharmony_ci  return re.sub(rexp, replacement, text, flags=re.MULTILINE)
931cb0ef41Sopenharmony_ci
941cb0ef41Sopenharmony_ci
951cb0ef41Sopenharmony_ci# Some commands don't like the pipe, e.g. calling vi from within the script or
961cb0ef41Sopenharmony_ci# from subscripts like git cl upload.
971cb0ef41Sopenharmony_cidef Command(cmd, args="", prefix="", pipe=True, cwd=None):
981cb0ef41Sopenharmony_ci  cwd = cwd or os.getcwd()
991cb0ef41Sopenharmony_ci  # TODO(machenbach): Use timeout.
1001cb0ef41Sopenharmony_ci  cmd_line = "%s %s %s" % (prefix, cmd, args)
1011cb0ef41Sopenharmony_ci  print("Command: %s" % cmd_line)
1021cb0ef41Sopenharmony_ci  print("in %s" % cwd)
1031cb0ef41Sopenharmony_ci  sys.stdout.flush()
1041cb0ef41Sopenharmony_ci  try:
1051cb0ef41Sopenharmony_ci    if pipe:
1061cb0ef41Sopenharmony_ci      return subprocess.check_output(cmd_line, shell=True, cwd=cwd).decode('utf-8')
1071cb0ef41Sopenharmony_ci    else:
1081cb0ef41Sopenharmony_ci      return subprocess.check_call(cmd_line, shell=True, cwd=cwd)
1091cb0ef41Sopenharmony_ci  except subprocess.CalledProcessError:
1101cb0ef41Sopenharmony_ci    return None
1111cb0ef41Sopenharmony_ci  finally:
1121cb0ef41Sopenharmony_ci    sys.stdout.flush()
1131cb0ef41Sopenharmony_ci    sys.stderr.flush()
1141cb0ef41Sopenharmony_ci
1151cb0ef41Sopenharmony_ci
1161cb0ef41Sopenharmony_cidef SanitizeVersionTag(tag):
1171cb0ef41Sopenharmony_ci    version_without_prefix = re.compile(r"^\d+\.\d+\.\d+(?:\.\d+)?$")
1181cb0ef41Sopenharmony_ci    version_with_prefix = re.compile(r"^tags\/\d+\.\d+\.\d+(?:\.\d+)?$")
1191cb0ef41Sopenharmony_ci
1201cb0ef41Sopenharmony_ci    if version_without_prefix.match(tag):
1211cb0ef41Sopenharmony_ci      return tag
1221cb0ef41Sopenharmony_ci    elif version_with_prefix.match(tag):
1231cb0ef41Sopenharmony_ci        return tag[len("tags/"):]
1241cb0ef41Sopenharmony_ci    else:
1251cb0ef41Sopenharmony_ci      return None
1261cb0ef41Sopenharmony_ci
1271cb0ef41Sopenharmony_ci
1281cb0ef41Sopenharmony_cidef NormalizeVersionTags(version_tags):
1291cb0ef41Sopenharmony_ci  normalized_version_tags = []
1301cb0ef41Sopenharmony_ci
1311cb0ef41Sopenharmony_ci  # Remove tags/ prefix because of packed refs.
1321cb0ef41Sopenharmony_ci  for current_tag in version_tags:
1331cb0ef41Sopenharmony_ci    version_tag = SanitizeVersionTag(current_tag)
1341cb0ef41Sopenharmony_ci    if version_tag != None:
1351cb0ef41Sopenharmony_ci      normalized_version_tags.append(version_tag)
1361cb0ef41Sopenharmony_ci
1371cb0ef41Sopenharmony_ci  return normalized_version_tags
1381cb0ef41Sopenharmony_ci
1391cb0ef41Sopenharmony_ci
1401cb0ef41Sopenharmony_ci# Wrapper for side effects.
1411cb0ef41Sopenharmony_ciclass SideEffectHandler(object):  # pragma: no cover
1421cb0ef41Sopenharmony_ci  def Call(self, fun, *args, **kwargs):
1431cb0ef41Sopenharmony_ci    return fun(*args, **kwargs)
1441cb0ef41Sopenharmony_ci
1451cb0ef41Sopenharmony_ci  def Command(self, cmd, args="", prefix="", pipe=True, cwd=None):
1461cb0ef41Sopenharmony_ci    return Command(cmd, args, prefix, pipe, cwd=cwd)
1471cb0ef41Sopenharmony_ci
1481cb0ef41Sopenharmony_ci  def ReadLine(self):
1491cb0ef41Sopenharmony_ci    return sys.stdin.readline().strip()
1501cb0ef41Sopenharmony_ci
1511cb0ef41Sopenharmony_ci  def ReadURL(self, url, params=None):
1521cb0ef41Sopenharmony_ci    # pylint: disable=E1121
1531cb0ef41Sopenharmony_ci    url_fh = urllib2.urlopen(url, params, 60)
1541cb0ef41Sopenharmony_ci    try:
1551cb0ef41Sopenharmony_ci      return url_fh.read()
1561cb0ef41Sopenharmony_ci    finally:
1571cb0ef41Sopenharmony_ci      url_fh.close()
1581cb0ef41Sopenharmony_ci
1591cb0ef41Sopenharmony_ci  def ReadClusterFuzzAPI(self, api_key, **params):
1601cb0ef41Sopenharmony_ci    params["api_key"] = api_key.strip()
1611cb0ef41Sopenharmony_ci    params = urllib.urlencode(params)
1621cb0ef41Sopenharmony_ci
1631cb0ef41Sopenharmony_ci    headers = {"Content-type": "application/x-www-form-urlencoded"}
1641cb0ef41Sopenharmony_ci
1651cb0ef41Sopenharmony_ci    conn = httplib.HTTPSConnection("backend-dot-cluster-fuzz.appspot.com")
1661cb0ef41Sopenharmony_ci    conn.request("POST", "/_api/", params, headers)
1671cb0ef41Sopenharmony_ci
1681cb0ef41Sopenharmony_ci    response = conn.getresponse()
1691cb0ef41Sopenharmony_ci    data = response.read()
1701cb0ef41Sopenharmony_ci
1711cb0ef41Sopenharmony_ci    try:
1721cb0ef41Sopenharmony_ci      return json.loads(data)
1731cb0ef41Sopenharmony_ci    except:
1741cb0ef41Sopenharmony_ci      print(data)
1751cb0ef41Sopenharmony_ci      print("ERROR: Could not read response. Is your key valid?")
1761cb0ef41Sopenharmony_ci      raise
1771cb0ef41Sopenharmony_ci
1781cb0ef41Sopenharmony_ci  def Sleep(self, seconds):
1791cb0ef41Sopenharmony_ci    time.sleep(seconds)
1801cb0ef41Sopenharmony_ci
1811cb0ef41Sopenharmony_ci  def GetUTCStamp(self):
1821cb0ef41Sopenharmony_ci    return time.mktime(datetime.datetime.utcnow().timetuple())
1831cb0ef41Sopenharmony_ci
1841cb0ef41Sopenharmony_ciDEFAULT_SIDE_EFFECT_HANDLER = SideEffectHandler()
1851cb0ef41Sopenharmony_ci
1861cb0ef41Sopenharmony_ci
1871cb0ef41Sopenharmony_ciclass NoRetryException(Exception):
1881cb0ef41Sopenharmony_ci  pass
1891cb0ef41Sopenharmony_ci
1901cb0ef41Sopenharmony_ci
1911cb0ef41Sopenharmony_ciclass VCInterface(object):
1921cb0ef41Sopenharmony_ci  def InjectStep(self, step):
1931cb0ef41Sopenharmony_ci    self.step=step
1941cb0ef41Sopenharmony_ci
1951cb0ef41Sopenharmony_ci  def Pull(self):
1961cb0ef41Sopenharmony_ci    raise NotImplementedError()
1971cb0ef41Sopenharmony_ci
1981cb0ef41Sopenharmony_ci  def Fetch(self):
1991cb0ef41Sopenharmony_ci    raise NotImplementedError()
2001cb0ef41Sopenharmony_ci
2011cb0ef41Sopenharmony_ci  def GetTags(self):
2021cb0ef41Sopenharmony_ci    raise NotImplementedError()
2031cb0ef41Sopenharmony_ci
2041cb0ef41Sopenharmony_ci  def GetBranches(self):
2051cb0ef41Sopenharmony_ci    raise NotImplementedError()
2061cb0ef41Sopenharmony_ci
2071cb0ef41Sopenharmony_ci  def MainBranch(self):
2081cb0ef41Sopenharmony_ci    raise NotImplementedError()
2091cb0ef41Sopenharmony_ci
2101cb0ef41Sopenharmony_ci  def CandidateBranch(self):
2111cb0ef41Sopenharmony_ci    raise NotImplementedError()
2121cb0ef41Sopenharmony_ci
2131cb0ef41Sopenharmony_ci  def RemoteMainBranch(self):
2141cb0ef41Sopenharmony_ci    raise NotImplementedError()
2151cb0ef41Sopenharmony_ci
2161cb0ef41Sopenharmony_ci  def RemoteCandidateBranch(self):
2171cb0ef41Sopenharmony_ci    raise NotImplementedError()
2181cb0ef41Sopenharmony_ci
2191cb0ef41Sopenharmony_ci  def RemoteBranch(self, name):
2201cb0ef41Sopenharmony_ci    raise NotImplementedError()
2211cb0ef41Sopenharmony_ci
2221cb0ef41Sopenharmony_ci  def CLLand(self):
2231cb0ef41Sopenharmony_ci    raise NotImplementedError()
2241cb0ef41Sopenharmony_ci
2251cb0ef41Sopenharmony_ci  def Tag(self, tag, remote, message):
2261cb0ef41Sopenharmony_ci    """Sets a tag for the current commit.
2271cb0ef41Sopenharmony_ci
2281cb0ef41Sopenharmony_ci    Assumptions: The commit already landed and the commit message is unique.
2291cb0ef41Sopenharmony_ci    """
2301cb0ef41Sopenharmony_ci    raise NotImplementedError()
2311cb0ef41Sopenharmony_ci
2321cb0ef41Sopenharmony_ci
2331cb0ef41Sopenharmony_ciclass GitInterface(VCInterface):
2341cb0ef41Sopenharmony_ci  def Pull(self):
2351cb0ef41Sopenharmony_ci    self.step.GitPull()
2361cb0ef41Sopenharmony_ci
2371cb0ef41Sopenharmony_ci  def Fetch(self):
2381cb0ef41Sopenharmony_ci    self.step.Git("fetch")
2391cb0ef41Sopenharmony_ci
2401cb0ef41Sopenharmony_ci  def GetTags(self):
2411cb0ef41Sopenharmony_ci     return self.step.Git("tag").strip().splitlines()
2421cb0ef41Sopenharmony_ci
2431cb0ef41Sopenharmony_ci  def GetBranches(self):
2441cb0ef41Sopenharmony_ci    # Get relevant remote branches, e.g. "branch-heads/3.25".
2451cb0ef41Sopenharmony_ci    branches = filter(
2461cb0ef41Sopenharmony_ci        lambda s: re.match(r"^branch\-heads/\d+\.\d+$", s),
2471cb0ef41Sopenharmony_ci        self.step.GitRemotes())
2481cb0ef41Sopenharmony_ci    # Remove 'branch-heads/' prefix.
2491cb0ef41Sopenharmony_ci    return [b[13:] for b in branches]
2501cb0ef41Sopenharmony_ci
2511cb0ef41Sopenharmony_ci  def MainBranch(self):
2521cb0ef41Sopenharmony_ci    return "main"
2531cb0ef41Sopenharmony_ci
2541cb0ef41Sopenharmony_ci  def CandidateBranch(self):
2551cb0ef41Sopenharmony_ci    return "candidates"
2561cb0ef41Sopenharmony_ci
2571cb0ef41Sopenharmony_ci  def RemoteMainBranch(self):
2581cb0ef41Sopenharmony_ci    return "origin/main"
2591cb0ef41Sopenharmony_ci
2601cb0ef41Sopenharmony_ci  def RemoteCandidateBranch(self):
2611cb0ef41Sopenharmony_ci    return "origin/candidates"
2621cb0ef41Sopenharmony_ci
2631cb0ef41Sopenharmony_ci  def RemoteBranch(self, name):
2641cb0ef41Sopenharmony_ci    # Assume that if someone "fully qualified" the ref, they know what they
2651cb0ef41Sopenharmony_ci    # want.
2661cb0ef41Sopenharmony_ci    if name.startswith('refs/'):
2671cb0ef41Sopenharmony_ci      return name
2681cb0ef41Sopenharmony_ci    if name in ["candidates", "main"]:
2691cb0ef41Sopenharmony_ci      return "refs/remotes/origin/%s" % name
2701cb0ef41Sopenharmony_ci    try:
2711cb0ef41Sopenharmony_ci      # Check if branch is in heads.
2721cb0ef41Sopenharmony_ci      if self.step.Git("show-ref refs/remotes/origin/%s" % name).strip():
2731cb0ef41Sopenharmony_ci        return "refs/remotes/origin/%s" % name
2741cb0ef41Sopenharmony_ci    except GitFailedException:
2751cb0ef41Sopenharmony_ci      pass
2761cb0ef41Sopenharmony_ci    try:
2771cb0ef41Sopenharmony_ci      # Check if branch is in branch-heads.
2781cb0ef41Sopenharmony_ci      if self.step.Git("show-ref refs/remotes/branch-heads/%s" % name).strip():
2791cb0ef41Sopenharmony_ci        return "refs/remotes/branch-heads/%s" % name
2801cb0ef41Sopenharmony_ci    except GitFailedException:
2811cb0ef41Sopenharmony_ci      pass
2821cb0ef41Sopenharmony_ci    self.Die("Can't find remote of %s" % name)
2831cb0ef41Sopenharmony_ci
2841cb0ef41Sopenharmony_ci  def Tag(self, tag, remote, message):
2851cb0ef41Sopenharmony_ci    # Wait for the commit to appear. Assumes unique commit message titles (this
2861cb0ef41Sopenharmony_ci    # is the case for all automated merge and push commits - also no title is
2871cb0ef41Sopenharmony_ci    # the prefix of another title).
2881cb0ef41Sopenharmony_ci    commit = None
2891cb0ef41Sopenharmony_ci    for wait_interval in [10, 30, 60, 60, 60, 60, 60]:
2901cb0ef41Sopenharmony_ci      self.step.Git("fetch")
2911cb0ef41Sopenharmony_ci      commit = self.step.GitLog(n=1, format="%H", grep=message, branch=remote)
2921cb0ef41Sopenharmony_ci      if commit:
2931cb0ef41Sopenharmony_ci        break
2941cb0ef41Sopenharmony_ci      print("The commit has not replicated to git. Waiting for %s seconds." %
2951cb0ef41Sopenharmony_ci            wait_interval)
2961cb0ef41Sopenharmony_ci      self.step._side_effect_handler.Sleep(wait_interval)
2971cb0ef41Sopenharmony_ci    else:
2981cb0ef41Sopenharmony_ci      self.step.Die("Couldn't determine commit for setting the tag. Maybe the "
2991cb0ef41Sopenharmony_ci                    "git updater is lagging behind?")
3001cb0ef41Sopenharmony_ci
3011cb0ef41Sopenharmony_ci    self.step.Git("tag %s %s" % (tag, commit))
3021cb0ef41Sopenharmony_ci    self.step.Git("push origin refs/tags/%s:refs/tags/%s" % (tag, tag))
3031cb0ef41Sopenharmony_ci
3041cb0ef41Sopenharmony_ci  def CLLand(self):
3051cb0ef41Sopenharmony_ci    self.step.GitCLLand()
3061cb0ef41Sopenharmony_ci
3071cb0ef41Sopenharmony_ci
3081cb0ef41Sopenharmony_ciclass Step(GitRecipesMixin):
3091cb0ef41Sopenharmony_ci  def __init__(self, text, number, config, state, options, handler):
3101cb0ef41Sopenharmony_ci    self._text = text
3111cb0ef41Sopenharmony_ci    self._number = number
3121cb0ef41Sopenharmony_ci    self._config = config
3131cb0ef41Sopenharmony_ci    self._state = state
3141cb0ef41Sopenharmony_ci    self._options = options
3151cb0ef41Sopenharmony_ci    self._side_effect_handler = handler
3161cb0ef41Sopenharmony_ci    self.vc = GitInterface()
3171cb0ef41Sopenharmony_ci    self.vc.InjectStep(self)
3181cb0ef41Sopenharmony_ci
3191cb0ef41Sopenharmony_ci    # The testing configuration might set a different default cwd.
3201cb0ef41Sopenharmony_ci    self.default_cwd = (self._config.get("DEFAULT_CWD") or
3211cb0ef41Sopenharmony_ci                        os.path.join(self._options.work_dir, "v8"))
3221cb0ef41Sopenharmony_ci
3231cb0ef41Sopenharmony_ci    assert self._number >= 0
3241cb0ef41Sopenharmony_ci    assert self._config is not None
3251cb0ef41Sopenharmony_ci    assert self._state is not None
3261cb0ef41Sopenharmony_ci    assert self._side_effect_handler is not None
3271cb0ef41Sopenharmony_ci
3281cb0ef41Sopenharmony_ci  def __getitem__(self, key):
3291cb0ef41Sopenharmony_ci    # Convenience method to allow direct [] access on step classes for
3301cb0ef41Sopenharmony_ci    # manipulating the backed state dict.
3311cb0ef41Sopenharmony_ci    return self._state.get(key)
3321cb0ef41Sopenharmony_ci
3331cb0ef41Sopenharmony_ci  def __setitem__(self, key, value):
3341cb0ef41Sopenharmony_ci    # Convenience method to allow direct [] access on step classes for
3351cb0ef41Sopenharmony_ci    # manipulating the backed state dict.
3361cb0ef41Sopenharmony_ci    self._state[key] = value
3371cb0ef41Sopenharmony_ci
3381cb0ef41Sopenharmony_ci  def Config(self, key):
3391cb0ef41Sopenharmony_ci    return self._config[key]
3401cb0ef41Sopenharmony_ci
3411cb0ef41Sopenharmony_ci  def Run(self):
3421cb0ef41Sopenharmony_ci    # Restore state.
3431cb0ef41Sopenharmony_ci    state_file = "%s-state.json" % self._config["PERSISTFILE_BASENAME"]
3441cb0ef41Sopenharmony_ci    if not self._state and os.path.exists(state_file):
3451cb0ef41Sopenharmony_ci      self._state.update(json.loads(FileToText(state_file)))
3461cb0ef41Sopenharmony_ci
3471cb0ef41Sopenharmony_ci    print(">>> Step %d: %s" % (self._number, self._text))
3481cb0ef41Sopenharmony_ci    try:
3491cb0ef41Sopenharmony_ci      return self.RunStep()
3501cb0ef41Sopenharmony_ci    finally:
3511cb0ef41Sopenharmony_ci      # Persist state.
3521cb0ef41Sopenharmony_ci      TextToFile(json.dumps(self._state), state_file)
3531cb0ef41Sopenharmony_ci
3541cb0ef41Sopenharmony_ci  def RunStep(self):  # pragma: no cover
3551cb0ef41Sopenharmony_ci    raise NotImplementedError
3561cb0ef41Sopenharmony_ci
3571cb0ef41Sopenharmony_ci  def Retry(self, cb, retry_on=None, wait_plan=None):
3581cb0ef41Sopenharmony_ci    """ Retry a function.
3591cb0ef41Sopenharmony_ci    Params:
3601cb0ef41Sopenharmony_ci      cb: The function to retry.
3611cb0ef41Sopenharmony_ci      retry_on: A callback that takes the result of the function and returns
3621cb0ef41Sopenharmony_ci                True if the function should be retried. A function throwing an
3631cb0ef41Sopenharmony_ci                exception is always retried.
3641cb0ef41Sopenharmony_ci      wait_plan: A list of waiting delays between retries in seconds. The
3651cb0ef41Sopenharmony_ci                 maximum number of retries is len(wait_plan).
3661cb0ef41Sopenharmony_ci    """
3671cb0ef41Sopenharmony_ci    retry_on = retry_on or (lambda x: False)
3681cb0ef41Sopenharmony_ci    wait_plan = list(wait_plan or [])
3691cb0ef41Sopenharmony_ci    wait_plan.reverse()
3701cb0ef41Sopenharmony_ci    while True:
3711cb0ef41Sopenharmony_ci      got_exception = False
3721cb0ef41Sopenharmony_ci      try:
3731cb0ef41Sopenharmony_ci        result = cb()
3741cb0ef41Sopenharmony_ci      except NoRetryException as e:
3751cb0ef41Sopenharmony_ci        raise e
3761cb0ef41Sopenharmony_ci      except Exception as e:
3771cb0ef41Sopenharmony_ci        got_exception = e
3781cb0ef41Sopenharmony_ci      if got_exception or retry_on(result):
3791cb0ef41Sopenharmony_ci        if not wait_plan:  # pragma: no cover
3801cb0ef41Sopenharmony_ci          raise Exception("Retried too often. Giving up. Reason: %s" %
3811cb0ef41Sopenharmony_ci                          str(got_exception))
3821cb0ef41Sopenharmony_ci        wait_time = wait_plan.pop()
3831cb0ef41Sopenharmony_ci        print("Waiting for %f seconds." % wait_time)
3841cb0ef41Sopenharmony_ci        self._side_effect_handler.Sleep(wait_time)
3851cb0ef41Sopenharmony_ci        print("Retrying...")
3861cb0ef41Sopenharmony_ci      else:
3871cb0ef41Sopenharmony_ci        return result
3881cb0ef41Sopenharmony_ci
3891cb0ef41Sopenharmony_ci  def ReadLine(self, default=None):
3901cb0ef41Sopenharmony_ci    # Don't prompt in forced mode.
3911cb0ef41Sopenharmony_ci    if self._options.force_readline_defaults and default is not None:
3921cb0ef41Sopenharmony_ci      print("%s (forced)" % default)
3931cb0ef41Sopenharmony_ci      return default
3941cb0ef41Sopenharmony_ci    else:
3951cb0ef41Sopenharmony_ci      return self._side_effect_handler.ReadLine()
3961cb0ef41Sopenharmony_ci
3971cb0ef41Sopenharmony_ci  def Command(self, name, args, cwd=None):
3981cb0ef41Sopenharmony_ci    cmd = lambda: self._side_effect_handler.Command(
3991cb0ef41Sopenharmony_ci        name, args, "", True, cwd=cwd or self.default_cwd)
4001cb0ef41Sopenharmony_ci    return self.Retry(cmd, None, [5])
4011cb0ef41Sopenharmony_ci
4021cb0ef41Sopenharmony_ci  def Git(self, args="", prefix="", pipe=True, retry_on=None, cwd=None):
4031cb0ef41Sopenharmony_ci    cmd = lambda: self._side_effect_handler.Command(
4041cb0ef41Sopenharmony_ci        "git", args, prefix, pipe, cwd=cwd or self.default_cwd)
4051cb0ef41Sopenharmony_ci    result = self.Retry(cmd, retry_on, [5, 30])
4061cb0ef41Sopenharmony_ci    if result is None:
4071cb0ef41Sopenharmony_ci      raise GitFailedException("'git %s' failed." % args)
4081cb0ef41Sopenharmony_ci    return result
4091cb0ef41Sopenharmony_ci
4101cb0ef41Sopenharmony_ci  def Editor(self, args):
4111cb0ef41Sopenharmony_ci    if self._options.requires_editor:
4121cb0ef41Sopenharmony_ci      return self._side_effect_handler.Command(
4131cb0ef41Sopenharmony_ci          os.environ["EDITOR"],
4141cb0ef41Sopenharmony_ci          args,
4151cb0ef41Sopenharmony_ci          pipe=False,
4161cb0ef41Sopenharmony_ci          cwd=self.default_cwd)
4171cb0ef41Sopenharmony_ci
4181cb0ef41Sopenharmony_ci  def ReadURL(self, url, params=None, retry_on=None, wait_plan=None):
4191cb0ef41Sopenharmony_ci    wait_plan = wait_plan or [3, 60, 600]
4201cb0ef41Sopenharmony_ci    cmd = lambda: self._side_effect_handler.ReadURL(url, params)
4211cb0ef41Sopenharmony_ci    return self.Retry(cmd, retry_on, wait_plan)
4221cb0ef41Sopenharmony_ci
4231cb0ef41Sopenharmony_ci  def Die(self, msg=""):
4241cb0ef41Sopenharmony_ci    if msg != "":
4251cb0ef41Sopenharmony_ci      print("Error: %s" % msg)
4261cb0ef41Sopenharmony_ci    print("Exiting")
4271cb0ef41Sopenharmony_ci    raise Exception(msg)
4281cb0ef41Sopenharmony_ci
4291cb0ef41Sopenharmony_ci  def DieNoManualMode(self, msg=""):
4301cb0ef41Sopenharmony_ci    if not self._options.manual:  # pragma: no cover
4311cb0ef41Sopenharmony_ci      msg = msg or "Only available in manual mode."
4321cb0ef41Sopenharmony_ci      self.Die(msg)
4331cb0ef41Sopenharmony_ci
4341cb0ef41Sopenharmony_ci  def Confirm(self, msg):
4351cb0ef41Sopenharmony_ci    print("%s [Y/n] " % msg, end=' ')
4361cb0ef41Sopenharmony_ci    answer = self.ReadLine(default="Y")
4371cb0ef41Sopenharmony_ci    return answer == "" or answer == "Y" or answer == "y"
4381cb0ef41Sopenharmony_ci
4391cb0ef41Sopenharmony_ci  def DeleteBranch(self, name, cwd=None):
4401cb0ef41Sopenharmony_ci    for line in self.GitBranch(cwd=cwd).splitlines():
4411cb0ef41Sopenharmony_ci      if re.match(r"\*?\s*%s$" % re.escape(name), line):
4421cb0ef41Sopenharmony_ci        msg = "Branch %s exists, do you want to delete it?" % name
4431cb0ef41Sopenharmony_ci        if self.Confirm(msg):
4441cb0ef41Sopenharmony_ci          self.GitDeleteBranch(name, cwd=cwd)
4451cb0ef41Sopenharmony_ci          print("Branch %s deleted." % name)
4461cb0ef41Sopenharmony_ci        else:
4471cb0ef41Sopenharmony_ci          msg = "Can't continue. Please delete branch %s and try again." % name
4481cb0ef41Sopenharmony_ci          self.Die(msg)
4491cb0ef41Sopenharmony_ci
4501cb0ef41Sopenharmony_ci  def InitialEnvironmentChecks(self, cwd):
4511cb0ef41Sopenharmony_ci    # Cancel if this is not a git checkout.
4521cb0ef41Sopenharmony_ci    if not os.path.exists(os.path.join(cwd, ".git")):  # pragma: no cover
4531cb0ef41Sopenharmony_ci      self.Die("%s is not a git checkout. If you know what you're doing, try "
4541cb0ef41Sopenharmony_ci               "deleting it and rerunning this script." % cwd)
4551cb0ef41Sopenharmony_ci
4561cb0ef41Sopenharmony_ci    # Cancel if EDITOR is unset or not executable.
4571cb0ef41Sopenharmony_ci    if (self._options.requires_editor and (not os.environ.get("EDITOR") or
4581cb0ef41Sopenharmony_ci        self.Command(
4591cb0ef41Sopenharmony_ci            "which", os.environ["EDITOR"]) is None)):  # pragma: no cover
4601cb0ef41Sopenharmony_ci      self.Die("Please set your EDITOR environment variable, you'll need it.")
4611cb0ef41Sopenharmony_ci
4621cb0ef41Sopenharmony_ci  def CommonPrepare(self):
4631cb0ef41Sopenharmony_ci    # Check for a clean workdir.
4641cb0ef41Sopenharmony_ci    if not self.GitIsWorkdirClean():  # pragma: no cover
4651cb0ef41Sopenharmony_ci      self.Die("Workspace is not clean. Please commit or undo your changes.")
4661cb0ef41Sopenharmony_ci
4671cb0ef41Sopenharmony_ci    # Checkout main in case the script was left on a work branch.
4681cb0ef41Sopenharmony_ci    self.GitCheckout('origin/main')
4691cb0ef41Sopenharmony_ci
4701cb0ef41Sopenharmony_ci    # Fetch unfetched revisions.
4711cb0ef41Sopenharmony_ci    self.vc.Fetch()
4721cb0ef41Sopenharmony_ci
4731cb0ef41Sopenharmony_ci  def PrepareBranch(self):
4741cb0ef41Sopenharmony_ci    # Delete the branch that will be created later if it exists already.
4751cb0ef41Sopenharmony_ci    self.DeleteBranch(self._config["BRANCHNAME"])
4761cb0ef41Sopenharmony_ci
4771cb0ef41Sopenharmony_ci  def CommonCleanup(self):
4781cb0ef41Sopenharmony_ci    self.GitCheckout('origin/main')
4791cb0ef41Sopenharmony_ci    self.GitDeleteBranch(self._config["BRANCHNAME"])
4801cb0ef41Sopenharmony_ci
4811cb0ef41Sopenharmony_ci    # Clean up all temporary files.
4821cb0ef41Sopenharmony_ci    for f in glob.iglob("%s*" % self._config["PERSISTFILE_BASENAME"]):
4831cb0ef41Sopenharmony_ci      if os.path.isfile(f):
4841cb0ef41Sopenharmony_ci        os.remove(f)
4851cb0ef41Sopenharmony_ci      if os.path.isdir(f):
4861cb0ef41Sopenharmony_ci        shutil.rmtree(f)
4871cb0ef41Sopenharmony_ci
4881cb0ef41Sopenharmony_ci  def ReadAndPersistVersion(self, prefix=""):
4891cb0ef41Sopenharmony_ci    def ReadAndPersist(var_name, def_name):
4901cb0ef41Sopenharmony_ci      match = re.match(r"^#define %s\s+(\d*)" % def_name, line)
4911cb0ef41Sopenharmony_ci      if match:
4921cb0ef41Sopenharmony_ci        value = match.group(1)
4931cb0ef41Sopenharmony_ci        self["%s%s" % (prefix, var_name)] = value
4941cb0ef41Sopenharmony_ci    for line in LinesInFile(os.path.join(self.default_cwd, VERSION_FILE)):
4951cb0ef41Sopenharmony_ci      for (var_name, def_name) in [("major", "V8_MAJOR_VERSION"),
4961cb0ef41Sopenharmony_ci                                   ("minor", "V8_MINOR_VERSION"),
4971cb0ef41Sopenharmony_ci                                   ("build", "V8_BUILD_NUMBER"),
4981cb0ef41Sopenharmony_ci                                   ("patch", "V8_PATCH_LEVEL")]:
4991cb0ef41Sopenharmony_ci        ReadAndPersist(var_name, def_name)
5001cb0ef41Sopenharmony_ci
5011cb0ef41Sopenharmony_ci  def WaitForLGTM(self):
5021cb0ef41Sopenharmony_ci    print ("Please wait for an LGTM, then type \"LGTM<Return>\" to commit "
5031cb0ef41Sopenharmony_ci           "your change. (If you need to iterate on the patch or double check "
5041cb0ef41Sopenharmony_ci           "that it's sensible, do so in another shell, but remember to not "
5051cb0ef41Sopenharmony_ci           "change the headline of the uploaded CL.")
5061cb0ef41Sopenharmony_ci    answer = ""
5071cb0ef41Sopenharmony_ci    while answer != "LGTM":
5081cb0ef41Sopenharmony_ci      print("> ", end=' ')
5091cb0ef41Sopenharmony_ci      answer = self.ReadLine(None if self._options.wait_for_lgtm else "LGTM")
5101cb0ef41Sopenharmony_ci      if answer != "LGTM":
5111cb0ef41Sopenharmony_ci        print("That was not 'LGTM'.")
5121cb0ef41Sopenharmony_ci
5131cb0ef41Sopenharmony_ci  def WaitForResolvingConflicts(self, patch_file):
5141cb0ef41Sopenharmony_ci    print("Applying the patch \"%s\" failed. Either type \"ABORT<Return>\", "
5151cb0ef41Sopenharmony_ci          "or resolve the conflicts, stage *all* touched files with "
5161cb0ef41Sopenharmony_ci          "'git add', and type \"RESOLVED<Return>\"" % (patch_file))
5171cb0ef41Sopenharmony_ci    self.DieNoManualMode()
5181cb0ef41Sopenharmony_ci    answer = ""
5191cb0ef41Sopenharmony_ci    while answer != "RESOLVED":
5201cb0ef41Sopenharmony_ci      if answer == "ABORT":
5211cb0ef41Sopenharmony_ci        self.Die("Applying the patch failed.")
5221cb0ef41Sopenharmony_ci      if answer != "":
5231cb0ef41Sopenharmony_ci        print("That was not 'RESOLVED' or 'ABORT'.")
5241cb0ef41Sopenharmony_ci      print("> ", end=' ')
5251cb0ef41Sopenharmony_ci      answer = self.ReadLine()
5261cb0ef41Sopenharmony_ci
5271cb0ef41Sopenharmony_ci  # Takes a file containing the patch to apply as first argument.
5281cb0ef41Sopenharmony_ci  def ApplyPatch(self, patch_file, revert=False):
5291cb0ef41Sopenharmony_ci    try:
5301cb0ef41Sopenharmony_ci      self.GitApplyPatch(patch_file, revert)
5311cb0ef41Sopenharmony_ci    except GitFailedException:
5321cb0ef41Sopenharmony_ci      self.WaitForResolvingConflicts(patch_file)
5331cb0ef41Sopenharmony_ci
5341cb0ef41Sopenharmony_ci  def GetVersionTag(self, revision):
5351cb0ef41Sopenharmony_ci    tag = self.Git("describe --tags %s" % revision).strip()
5361cb0ef41Sopenharmony_ci    return SanitizeVersionTag(tag)
5371cb0ef41Sopenharmony_ci
5381cb0ef41Sopenharmony_ci  def GetRecentReleases(self, max_age):
5391cb0ef41Sopenharmony_ci    # Make sure tags are fetched.
5401cb0ef41Sopenharmony_ci    self.Git("fetch origin +refs/tags/*:refs/tags/*")
5411cb0ef41Sopenharmony_ci
5421cb0ef41Sopenharmony_ci    # Current timestamp.
5431cb0ef41Sopenharmony_ci    time_now = int(self._side_effect_handler.GetUTCStamp())
5441cb0ef41Sopenharmony_ci
5451cb0ef41Sopenharmony_ci    # List every tag from a given period.
5461cb0ef41Sopenharmony_ci    revisions = self.Git("rev-list --max-age=%d --tags" %
5471cb0ef41Sopenharmony_ci                         int(time_now - max_age)).strip()
5481cb0ef41Sopenharmony_ci
5491cb0ef41Sopenharmony_ci    # Filter out revisions who's tag is off by one or more commits.
5501cb0ef41Sopenharmony_ci    return list(filter(self.GetVersionTag, revisions.splitlines()))
5511cb0ef41Sopenharmony_ci
5521cb0ef41Sopenharmony_ci  def GetLatestVersion(self):
5531cb0ef41Sopenharmony_ci    # Use cached version if available.
5541cb0ef41Sopenharmony_ci    if self["latest_version"]:
5551cb0ef41Sopenharmony_ci      return self["latest_version"]
5561cb0ef41Sopenharmony_ci
5571cb0ef41Sopenharmony_ci    # Make sure tags are fetched.
5581cb0ef41Sopenharmony_ci    self.Git("fetch origin +refs/tags/*:refs/tags/*")
5591cb0ef41Sopenharmony_ci
5601cb0ef41Sopenharmony_ci    all_tags = self.vc.GetTags()
5611cb0ef41Sopenharmony_ci    only_version_tags = NormalizeVersionTags(all_tags)
5621cb0ef41Sopenharmony_ci
5631cb0ef41Sopenharmony_ci    version = sorted(only_version_tags,
5641cb0ef41Sopenharmony_ci                     key=LooseVersion, reverse=True)[0]
5651cb0ef41Sopenharmony_ci    self["latest_version"] = version
5661cb0ef41Sopenharmony_ci    return version
5671cb0ef41Sopenharmony_ci
5681cb0ef41Sopenharmony_ci  def GetLatestRelease(self):
5691cb0ef41Sopenharmony_ci    """The latest release is the git hash of the latest tagged version.
5701cb0ef41Sopenharmony_ci
5711cb0ef41Sopenharmony_ci    This revision should be rolled into chromium.
5721cb0ef41Sopenharmony_ci    """
5731cb0ef41Sopenharmony_ci    latest_version = self.GetLatestVersion()
5741cb0ef41Sopenharmony_ci
5751cb0ef41Sopenharmony_ci    # The latest release.
5761cb0ef41Sopenharmony_ci    latest_hash = self.GitLog(n=1, format="%H", branch=latest_version)
5771cb0ef41Sopenharmony_ci    assert latest_hash
5781cb0ef41Sopenharmony_ci    return latest_hash
5791cb0ef41Sopenharmony_ci
5801cb0ef41Sopenharmony_ci  def GetLatestReleaseBase(self, version=None):
5811cb0ef41Sopenharmony_ci    """The latest release base is the latest revision that is covered in the
5821cb0ef41Sopenharmony_ci    last change log file. It doesn't include cherry-picked patches.
5831cb0ef41Sopenharmony_ci    """
5841cb0ef41Sopenharmony_ci    latest_version = version or self.GetLatestVersion()
5851cb0ef41Sopenharmony_ci
5861cb0ef41Sopenharmony_ci    # Strip patch level if it exists.
5871cb0ef41Sopenharmony_ci    latest_version = ".".join(latest_version.split(".")[:3])
5881cb0ef41Sopenharmony_ci
5891cb0ef41Sopenharmony_ci    # The latest release base.
5901cb0ef41Sopenharmony_ci    latest_hash = self.GitLog(n=1, format="%H", branch=latest_version)
5911cb0ef41Sopenharmony_ci    assert latest_hash
5921cb0ef41Sopenharmony_ci
5931cb0ef41Sopenharmony_ci    title = self.GitLog(n=1, format="%s", git_hash=latest_hash)
5941cb0ef41Sopenharmony_ci    match = PUSH_MSG_GIT_RE.match(title)
5951cb0ef41Sopenharmony_ci    if match:
5961cb0ef41Sopenharmony_ci      # Legacy: In the old process there's one level of indirection. The
5971cb0ef41Sopenharmony_ci      # version is on the candidates branch and points to the real release
5981cb0ef41Sopenharmony_ci      # base on main through the commit message.
5991cb0ef41Sopenharmony_ci      return match.group("git_rev")
6001cb0ef41Sopenharmony_ci    match = PUSH_MSG_NEW_RE.match(title)
6011cb0ef41Sopenharmony_ci    if match:
6021cb0ef41Sopenharmony_ci      # This is a new-style v8 version branched from main. The commit
6031cb0ef41Sopenharmony_ci      # "latest_hash" is the version-file change. Its parent is the release
6041cb0ef41Sopenharmony_ci      # base on main.
6051cb0ef41Sopenharmony_ci      return self.GitLog(n=1, format="%H", git_hash="%s^" % latest_hash)
6061cb0ef41Sopenharmony_ci
6071cb0ef41Sopenharmony_ci    self.Die("Unknown latest release: %s" % latest_hash)
6081cb0ef41Sopenharmony_ci
6091cb0ef41Sopenharmony_ci  def ArrayToVersion(self, prefix):
6101cb0ef41Sopenharmony_ci    return ".".join([self[prefix + "major"],
6111cb0ef41Sopenharmony_ci                     self[prefix + "minor"],
6121cb0ef41Sopenharmony_ci                     self[prefix + "build"],
6131cb0ef41Sopenharmony_ci                     self[prefix + "patch"]])
6141cb0ef41Sopenharmony_ci
6151cb0ef41Sopenharmony_ci  def StoreVersion(self, version, prefix):
6161cb0ef41Sopenharmony_ci    version_parts = version.split(".")
6171cb0ef41Sopenharmony_ci    if len(version_parts) == 3:
6181cb0ef41Sopenharmony_ci      version_parts.append("0")
6191cb0ef41Sopenharmony_ci    major, minor, build, patch = version_parts
6201cb0ef41Sopenharmony_ci    self[prefix + "major"] = major
6211cb0ef41Sopenharmony_ci    self[prefix + "minor"] = minor
6221cb0ef41Sopenharmony_ci    self[prefix + "build"] = build
6231cb0ef41Sopenharmony_ci    self[prefix + "patch"] = patch
6241cb0ef41Sopenharmony_ci
6251cb0ef41Sopenharmony_ci  def SetVersion(self, version_file, prefix):
6261cb0ef41Sopenharmony_ci    output = ""
6271cb0ef41Sopenharmony_ci    for line in FileToText(version_file).splitlines():
6281cb0ef41Sopenharmony_ci      if line.startswith("#define V8_MAJOR_VERSION"):
6291cb0ef41Sopenharmony_ci        line = re.sub("\d+$", self[prefix + "major"], line)
6301cb0ef41Sopenharmony_ci      elif line.startswith("#define V8_MINOR_VERSION"):
6311cb0ef41Sopenharmony_ci        line = re.sub("\d+$", self[prefix + "minor"], line)
6321cb0ef41Sopenharmony_ci      elif line.startswith("#define V8_BUILD_NUMBER"):
6331cb0ef41Sopenharmony_ci        line = re.sub("\d+$", self[prefix + "build"], line)
6341cb0ef41Sopenharmony_ci      elif line.startswith("#define V8_PATCH_LEVEL"):
6351cb0ef41Sopenharmony_ci        line = re.sub("\d+$", self[prefix + "patch"], line)
6361cb0ef41Sopenharmony_ci      elif (self[prefix + "candidate"] and
6371cb0ef41Sopenharmony_ci            line.startswith("#define V8_IS_CANDIDATE_VERSION")):
6381cb0ef41Sopenharmony_ci        line = re.sub("\d+$", self[prefix + "candidate"], line)
6391cb0ef41Sopenharmony_ci      output += "%s\n" % line
6401cb0ef41Sopenharmony_ci    TextToFile(output, version_file)
6411cb0ef41Sopenharmony_ci
6421cb0ef41Sopenharmony_ci
6431cb0ef41Sopenharmony_ciclass BootstrapStep(Step):
6441cb0ef41Sopenharmony_ci  MESSAGE = "Bootstrapping checkout and state."
6451cb0ef41Sopenharmony_ci
6461cb0ef41Sopenharmony_ci  def RunStep(self):
6471cb0ef41Sopenharmony_ci    # Reserve state entry for json output.
6481cb0ef41Sopenharmony_ci    self['json_output'] = {}
6491cb0ef41Sopenharmony_ci
6501cb0ef41Sopenharmony_ci    if os.path.realpath(self.default_cwd) == os.path.realpath(V8_BASE):
6511cb0ef41Sopenharmony_ci      self.Die("Can't use v8 checkout with calling script as work checkout.")
6521cb0ef41Sopenharmony_ci    # Directory containing the working v8 checkout.
6531cb0ef41Sopenharmony_ci    if not os.path.exists(self._options.work_dir):
6541cb0ef41Sopenharmony_ci      os.makedirs(self._options.work_dir)
6551cb0ef41Sopenharmony_ci    if not os.path.exists(self.default_cwd):
6561cb0ef41Sopenharmony_ci      self.Command("fetch", "v8", cwd=self._options.work_dir)
6571cb0ef41Sopenharmony_ci
6581cb0ef41Sopenharmony_ci
6591cb0ef41Sopenharmony_ciclass UploadStep(Step):
6601cb0ef41Sopenharmony_ci  MESSAGE = "Upload for code review."
6611cb0ef41Sopenharmony_ci
6621cb0ef41Sopenharmony_ci  def RunStep(self):
6631cb0ef41Sopenharmony_ci    reviewer = None
6641cb0ef41Sopenharmony_ci    if self._options.reviewer:
6651cb0ef41Sopenharmony_ci      print("Using account %s for review." % self._options.reviewer)
6661cb0ef41Sopenharmony_ci      reviewer = self._options.reviewer
6671cb0ef41Sopenharmony_ci
6681cb0ef41Sopenharmony_ci    tbr_reviewer = None
6691cb0ef41Sopenharmony_ci    if self._options.tbr_reviewer:
6701cb0ef41Sopenharmony_ci      print("Using account %s for TBR review." % self._options.tbr_reviewer)
6711cb0ef41Sopenharmony_ci      tbr_reviewer = self._options.tbr_reviewer
6721cb0ef41Sopenharmony_ci
6731cb0ef41Sopenharmony_ci    if not reviewer and not tbr_reviewer:
6741cb0ef41Sopenharmony_ci      print(
6751cb0ef41Sopenharmony_ci        "Please enter the email address of a V8 reviewer for your patch: ",
6761cb0ef41Sopenharmony_ci        end=' ')
6771cb0ef41Sopenharmony_ci      self.DieNoManualMode("A reviewer must be specified in forced mode.")
6781cb0ef41Sopenharmony_ci      reviewer = self.ReadLine()
6791cb0ef41Sopenharmony_ci
6801cb0ef41Sopenharmony_ci    self.GitUpload(reviewer, self._options.force_upload,
6811cb0ef41Sopenharmony_ci                   bypass_hooks=self._options.bypass_upload_hooks,
6821cb0ef41Sopenharmony_ci                   tbr_reviewer=tbr_reviewer)
6831cb0ef41Sopenharmony_ci
6841cb0ef41Sopenharmony_ci
6851cb0ef41Sopenharmony_cidef MakeStep(step_class=Step, number=0, state=None, config=None,
6861cb0ef41Sopenharmony_ci             options=None, side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER):
6871cb0ef41Sopenharmony_ci    # Allow to pass in empty dictionaries.
6881cb0ef41Sopenharmony_ci    state = state if state is not None else {}
6891cb0ef41Sopenharmony_ci    config = config if config is not None else {}
6901cb0ef41Sopenharmony_ci
6911cb0ef41Sopenharmony_ci    try:
6921cb0ef41Sopenharmony_ci      message = step_class.MESSAGE
6931cb0ef41Sopenharmony_ci    except AttributeError:
6941cb0ef41Sopenharmony_ci      message = step_class.__name__
6951cb0ef41Sopenharmony_ci
6961cb0ef41Sopenharmony_ci    return step_class(message, number=number, config=config,
6971cb0ef41Sopenharmony_ci                      state=state, options=options,
6981cb0ef41Sopenharmony_ci                      handler=side_effect_handler)
6991cb0ef41Sopenharmony_ci
7001cb0ef41Sopenharmony_ci
7011cb0ef41Sopenharmony_ciclass ScriptsBase(object):
7021cb0ef41Sopenharmony_ci  def __init__(self,
7031cb0ef41Sopenharmony_ci               config=None,
7041cb0ef41Sopenharmony_ci               side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER,
7051cb0ef41Sopenharmony_ci               state=None):
7061cb0ef41Sopenharmony_ci    self._config = config or self._Config()
7071cb0ef41Sopenharmony_ci    self._side_effect_handler = side_effect_handler
7081cb0ef41Sopenharmony_ci    self._state = state if state is not None else {}
7091cb0ef41Sopenharmony_ci
7101cb0ef41Sopenharmony_ci  def _Description(self):
7111cb0ef41Sopenharmony_ci    return None
7121cb0ef41Sopenharmony_ci
7131cb0ef41Sopenharmony_ci  def _PrepareOptions(self, parser):
7141cb0ef41Sopenharmony_ci    pass
7151cb0ef41Sopenharmony_ci
7161cb0ef41Sopenharmony_ci  def _ProcessOptions(self, options):
7171cb0ef41Sopenharmony_ci    return True
7181cb0ef41Sopenharmony_ci
7191cb0ef41Sopenharmony_ci  def _Steps(self):  # pragma: no cover
7201cb0ef41Sopenharmony_ci    raise Exception("Not implemented.")
7211cb0ef41Sopenharmony_ci
7221cb0ef41Sopenharmony_ci  def _Config(self):
7231cb0ef41Sopenharmony_ci    return {}
7241cb0ef41Sopenharmony_ci
7251cb0ef41Sopenharmony_ci  def MakeOptions(self, args=None):
7261cb0ef41Sopenharmony_ci    parser = argparse.ArgumentParser(description=self._Description())
7271cb0ef41Sopenharmony_ci    parser.add_argument("-a", "--author", default="",
7281cb0ef41Sopenharmony_ci                        help="The author email used for code review.")
7291cb0ef41Sopenharmony_ci    parser.add_argument("--dry-run", default=False, action="store_true",
7301cb0ef41Sopenharmony_ci                        help="Perform only read-only actions.")
7311cb0ef41Sopenharmony_ci    parser.add_argument("--json-output",
7321cb0ef41Sopenharmony_ci                        help="File to write results summary to.")
7331cb0ef41Sopenharmony_ci    parser.add_argument("-r", "--reviewer", default="",
7341cb0ef41Sopenharmony_ci                        help="The account name to be used for reviews.")
7351cb0ef41Sopenharmony_ci    parser.add_argument("--tbr-reviewer", "--tbr", default="",
7361cb0ef41Sopenharmony_ci                        help="The account name to be used for TBR reviews.")
7371cb0ef41Sopenharmony_ci    parser.add_argument("-s", "--step",
7381cb0ef41Sopenharmony_ci        help="Specify the step where to start work. Default: 0.",
7391cb0ef41Sopenharmony_ci        default=0, type=int)
7401cb0ef41Sopenharmony_ci    parser.add_argument("--work-dir",
7411cb0ef41Sopenharmony_ci                        help=("Location where to bootstrap a working v8 "
7421cb0ef41Sopenharmony_ci                              "checkout."))
7431cb0ef41Sopenharmony_ci    self._PrepareOptions(parser)
7441cb0ef41Sopenharmony_ci
7451cb0ef41Sopenharmony_ci    if args is None:  # pragma: no cover
7461cb0ef41Sopenharmony_ci      options = parser.parse_args()
7471cb0ef41Sopenharmony_ci    else:
7481cb0ef41Sopenharmony_ci      options = parser.parse_args(args)
7491cb0ef41Sopenharmony_ci
7501cb0ef41Sopenharmony_ci    # Process common options.
7511cb0ef41Sopenharmony_ci    if options.step < 0:  # pragma: no cover
7521cb0ef41Sopenharmony_ci      print("Bad step number %d" % options.step)
7531cb0ef41Sopenharmony_ci      parser.print_help()
7541cb0ef41Sopenharmony_ci      return None
7551cb0ef41Sopenharmony_ci
7561cb0ef41Sopenharmony_ci    # Defaults for options, common to all scripts.
7571cb0ef41Sopenharmony_ci    options.manual = getattr(options, "manual", True)
7581cb0ef41Sopenharmony_ci    options.force = getattr(options, "force", False)
7591cb0ef41Sopenharmony_ci    options.bypass_upload_hooks = False
7601cb0ef41Sopenharmony_ci
7611cb0ef41Sopenharmony_ci    # Derived options.
7621cb0ef41Sopenharmony_ci    options.requires_editor = not options.force
7631cb0ef41Sopenharmony_ci    options.wait_for_lgtm = not options.force
7641cb0ef41Sopenharmony_ci    options.force_readline_defaults = not options.manual
7651cb0ef41Sopenharmony_ci    options.force_upload = not options.manual
7661cb0ef41Sopenharmony_ci
7671cb0ef41Sopenharmony_ci    # Process script specific options.
7681cb0ef41Sopenharmony_ci    if not self._ProcessOptions(options):
7691cb0ef41Sopenharmony_ci      parser.print_help()
7701cb0ef41Sopenharmony_ci      return None
7711cb0ef41Sopenharmony_ci
7721cb0ef41Sopenharmony_ci    if not options.work_dir:
7731cb0ef41Sopenharmony_ci      options.work_dir = "/tmp/v8-release-scripts-work-dir"
7741cb0ef41Sopenharmony_ci    return options
7751cb0ef41Sopenharmony_ci
7761cb0ef41Sopenharmony_ci  def RunSteps(self, step_classes, args=None):
7771cb0ef41Sopenharmony_ci    options = self.MakeOptions(args)
7781cb0ef41Sopenharmony_ci    if not options:
7791cb0ef41Sopenharmony_ci      return 1
7801cb0ef41Sopenharmony_ci
7811cb0ef41Sopenharmony_ci    # Ensure temp dir exists for state files.
7821cb0ef41Sopenharmony_ci    state_dir = os.path.dirname(self._config["PERSISTFILE_BASENAME"])
7831cb0ef41Sopenharmony_ci    if not os.path.exists(state_dir):
7841cb0ef41Sopenharmony_ci      os.makedirs(state_dir)
7851cb0ef41Sopenharmony_ci
7861cb0ef41Sopenharmony_ci    state_file = "%s-state.json" % self._config["PERSISTFILE_BASENAME"]
7871cb0ef41Sopenharmony_ci    if options.step == 0 and os.path.exists(state_file):
7881cb0ef41Sopenharmony_ci      os.remove(state_file)
7891cb0ef41Sopenharmony_ci
7901cb0ef41Sopenharmony_ci    steps = []
7911cb0ef41Sopenharmony_ci    for (number, step_class) in enumerate([BootstrapStep] + step_classes):
7921cb0ef41Sopenharmony_ci      steps.append(MakeStep(step_class, number, self._state, self._config,
7931cb0ef41Sopenharmony_ci                            options, self._side_effect_handler))
7941cb0ef41Sopenharmony_ci
7951cb0ef41Sopenharmony_ci    try:
7961cb0ef41Sopenharmony_ci      for step in steps[options.step:]:
7971cb0ef41Sopenharmony_ci        if step.Run():
7981cb0ef41Sopenharmony_ci          return 0
7991cb0ef41Sopenharmony_ci    finally:
8001cb0ef41Sopenharmony_ci      if options.json_output:
8011cb0ef41Sopenharmony_ci        with open(options.json_output, "w") as f:
8021cb0ef41Sopenharmony_ci          json.dump(self._state['json_output'], f)
8031cb0ef41Sopenharmony_ci
8041cb0ef41Sopenharmony_ci    return 0
8051cb0ef41Sopenharmony_ci
8061cb0ef41Sopenharmony_ci  def Run(self, args=None):
8071cb0ef41Sopenharmony_ci    return self.RunSteps(self._Steps(), args)
808