update_node.py 6.78 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
#!/usr/bin/env python
# Copyright 2017 the V8 project authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""
Use this script to update V8 in a Node.js checkout.

Requirements:
  - Node.js checkout in which V8 should be updated.
  - V8 checkout at the commit to which Node.js should be updated.

Usage:
  $ update_node.py <path_to_v8> <path_to_node>

  This will synchronize the content of <path_to_node>/deps/v8 with <path_to_v8>,
  and a few V8 dependencies require in Node.js. It will also update .gitignore
  appropriately.

Optional flags:
  --gclient     Run `gclient sync` on the V8 checkout before updating.
  --commit      Create commit with the updated V8 in the Node.js checkout.
  --with-patch  Also include currently staged files in the V8 checkout.
"""

import argparse
import os
import shutil
import subprocess
import sys
import stat
import node_common

TARGET_SUBDIR = os.path.join("deps", "v8")

SUB_REPOSITORIES = [ ["base", "trace_event", "common"],
37
                     ["third_party", "googletest", "src"],
38 39 40 41
                     ["third_party", "jinja2"],
                     ["third_party", "markupsafe"] ]

DELETE_FROM_GITIGNORE = [ "/base",
42
                          "/third_party/googletest/src",
43 44 45 46 47
                          "/third_party/jinja2",
                          "/third_party/markupsafe" ]

# Node.js requires only a single header file from gtest to build V8.
# Both jinja2 and markupsafe are required to generate part of the inspector.
48
ADD_TO_GITIGNORE = [ "/third_party/googletest/*",
49
                     "!/third_party/googletest/BUILD.gn",
50 51 52 53 54 55 56 57 58
                     "!/third_party/googletest/src",
                     "/third_party/googletest/src/*",
                     "!/third_party/googletest/src/googletest",
                     "/third_party/googletest/src/googletest/*",
                     "!/third_party/googletest/src/googletest/include",
                     "/third_party/googletest/src/googletest/include/*",
                     "!/third_party/googletest/src/googletest/include/gtest",
                     "/third_party/googletest/src/googletest/include/gtest/*",
                     "!/third_party/googletest/src/googletest/include/gtest/gtest_prod.h",
59 60 61
                     "!/third_party/jinja2",
                     "!/third_party/markupsafe" ]

Yang Guo's avatar
Yang Guo committed
62 63 64
# Node.js owns deps/v8/gypfiles in their downstream repository.
FILES_TO_KEEP = [ "gypfiles" ]

65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
def RunGclient(path):
  assert os.path.isdir(path)
  print ">> Running gclient sync"
  subprocess.check_call(["gclient", "sync", "--nohooks"], cwd=path)

def CommitPatch(options):
  """Makes a dummy commit for the changes in the index.

  On trybots, bot_updated applies the patch to the index. We commit it to make
  the fake git clone fetch it into node.js. We can leave the commit, as
  bot_update will ensure a clean state on each run.
  """
  print ">> Committing patch"
  subprocess.check_call(
      ["git", "-c", "user.name=fake", "-c", "user.email=fake@chromium.org",
       "commit", "--allow-empty", "-m", "placeholder-commit"],
      cwd=options.v8_path,
  )

Yang Guo's avatar
Yang Guo committed
84
def UpdateTarget(repository, options, files_to_keep):
85 86 87 88 89 90 91 92 93
  source = os.path.join(options.v8_path, *repository)
  target = os.path.join(options.node_path, TARGET_SUBDIR, *repository)
  print ">> Updating target directory %s" % target
  print ">>     from active branch at %s" % source
  if not os.path.exists(target):
    os.makedirs(target)
  # Remove possible remnants of previous incomplete runs.
  node_common.UninitGit(target)

Yang Guo's avatar
Yang Guo committed
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
  git_args = []
  git_args.append(["init"])                       # initialize target repo

  if files_to_keep:
    git_args.append(["add"] + files_to_keep)            # add and commit
    git_args.append(["commit", "-m", "keep files"])     # files we want to keep

  git_args.append(["remote", "add", "source", source])  # point to source repo
  git_args.append(["fetch", "source", "HEAD"])          # sync to current branch
  git_args.append(["checkout", "-f", "FETCH_HEAD"])     # switch to that branch
  git_args.append(["clean", "-fd"])                     # delete removed files

  if files_to_keep:
    git_args.append(["cherry-pick", "master"])    # restore kept files

109
  try:
Yang Guo's avatar
Yang Guo committed
110 111
    for args in git_args:
      subprocess.check_call(["git"] + args, cwd=target)
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
  except:
    raise
  finally:
    node_common.UninitGit(target)

def UpdateGitIgnore(options):
  file_name = os.path.join(options.node_path, TARGET_SUBDIR, ".gitignore")
  assert os.path.isfile(file_name)
  print ">> Updating .gitignore with lines"
  with open(file_name) as gitignore:
    content = gitignore.readlines()
  content = [x.strip() for x in content]
  for x in DELETE_FROM_GITIGNORE:
    if x in content:
      print "- %s" % x
      content.remove(x)
  for x in ADD_TO_GITIGNORE:
    if x not in content:
      print "+ %s" % x
      content.append(x)
  content.sort(key=lambda x: x[1:] if x.startswith("!") else x)
  with open(file_name, "w") as gitignore:
    for x in content:
      gitignore.write("%s\n" % x)

def CreateCommit(options):
  print ">> Creating commit."
  # Find git hash from source.
  githash = subprocess.check_output(["git", "rev-parse", "--short", "HEAD"],
                                    cwd=options.v8_path).strip()
  # Create commit at target.
  git_commands = [
    ["git", "checkout", "-b", "update_v8_to_%s" % githash],  # new branch
    ["git", "add", "."],                                     # add files
    ["git", "commit", "-m", "Update V8 to %s" % githash]     # new commit
  ]
  for command in git_commands:
    subprocess.check_call(command, cwd=options.node_path)

def ParseOptions(args):
  parser = argparse.ArgumentParser(description="Update V8 in Node.js")
  parser.add_argument("v8_path", help="Path to V8 checkout")
  parser.add_argument("node_path", help="Path to Node.js checkout")
  parser.add_argument("--gclient", action="store_true", help="Run gclient sync")
  parser.add_argument("--commit", action="store_true", help="Create commit")
  parser.add_argument("--with-patch", action="store_true",
                      help="Apply also staged files")
  options = parser.parse_args(args)
  assert os.path.isdir(options.v8_path)
  options.v8_path = os.path.abspath(options.v8_path)
  assert os.path.isdir(options.node_path)
  options.node_path = os.path.abspath(options.node_path)
  return options

def Main(args):
  options = ParseOptions(args)
  if options.gclient:
    RunGclient(options.v8_path)
  # Commit patch on trybots to main V8 repository.
  if options.with_patch:
    CommitPatch(options)
  # Update main V8 repository.
Yang Guo's avatar
Yang Guo committed
174
  UpdateTarget([""], options, FILES_TO_KEEP)
175 176 177
  # Patch .gitignore before updating sub-repositories.
  UpdateGitIgnore(options)
  for repo in SUB_REPOSITORIES:
Yang Guo's avatar
Yang Guo committed
178
    UpdateTarget(repo, options, None)
179 180 181 182 183
  if options.commit:
    CreateCommit(options)

if __name__ == "__main__":
  Main(sys.argv[1:])