1#!/usr/bin/env python
2# Copyright 2015 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Updates the 64 bit d8 binary for the current OS to match the v8 version used
7in the current version of the specified Chromium channel. If no channel is
8specified, we default to the 'stable' channel.
9
10This script assumes that git is installed and the computer meets all
11other prerequisites to build v8 normally (like having depot_tools installed and
12in $PATH).
13
14Example usage:
15$ tracing/bin/update_v8
16"""
17
18import json
19import os
20import platform
21import re
22import shutil
23import subprocess
24import sys
25import tempfile
26import urllib2
27
28OMAHAPROXY_VERSION_MAP_URL = 'https://omahaproxy.appspot.com/all.json'
29
30V8_PATH = os.path.join(
31    os.path.dirname(os.path.abspath(__file__)), os.path.pardir, 'third_party',
32    'v8')
33V8_BINARY_PATH = os.path.join(V8_PATH, '{os}', '{arch}', 'd8')
34V8_README_PATH = os.path.join(V8_PATH, 'README.chromium')
35
36V8_CHECKOUT_BINARY_PATH = os.path.join(
37    '{v8_root}', 'v8', 'out', 'Release', 'd8')
38V8_GENERATE_GYP_CMD = os.path.join('build', 'gyp_v8') + ' -Dtarget_arch={arch}'
39V8_COMPILE_CMD = 'ninja -C {0} d8'.format(os.path.join('out', 'Release'))
40V8_STRIP_CMD = 'strip -x {0}'.format(os.path.join('out', 'Release', 'd8'))
41
42VALID_CHANNEL_LIST = ['stable', 'canary', 'beta', 'dev']
43# Dict from the acceptable return values for Python's platform.system() to the
44# corresponding Chromium OS name.
45PYTHON_SYSTEM_TO_CHROME_OS = {
46  'Linux': 'linux',
47  'Windows': 'win',
48  'Darwin': 'mac'
49}
50# Dict from the acceptable return values for Python's platform.machine() to the
51# corresponding ninja architecture name.
52PYTHON_MACHINE_TO_NINJA_ARCH = {
53  'x86_64': 'x64'
54}
55
56def Main(args):
57  if len(args) > 1:
58    print('Usage: update_v8 [TARGET_CHANNEL]')
59    return 1
60
61  target_channel = args[0] if len(args) == 1 else 'stable'
62  target_arch = platform.machine()
63
64  if target_channel not in VALID_CHANNEL_LIST:
65    print('Invalid target channel. Valid: {0}'.format(VALID_CHANNEL_LIST))
66    return 1
67
68  if platform.system() not in PYTHON_SYSTEM_TO_CHROME_OS:
69    print('System not supported. Valid: {0}'.format(
70        PYTHON_SYSTEM_TO_CHROME_OS.keys()))
71    return 1
72  target_os = PYTHON_SYSTEM_TO_CHROME_OS[platform.system()]
73
74  if target_arch not in PYTHON_MACHINE_TO_NINJA_ARCH:
75    print('Invalid target architecture. Valid: {0}'.format(
76        PYTHON_MACHINE_TO_NINJA_ARCH.keys()))
77    return 1
78
79  v8_version = GetV8Version(target_os, target_channel)
80  UpdateV8Binary(v8_version, target_os, target_arch)
81  UpdateReadmeFile(v8_version, target_os)
82
83  return 0
84
85def GetV8Version(target_os, target_channel):
86  """Returns the v8 version that corresponds to the specified OS and channel."""
87  # Fetch the current version map from omahaproxy.
88  response = urllib2.urlopen(OMAHAPROXY_VERSION_MAP_URL)
89  versions = json.loads(response.read())
90
91  # Return the v8 version that corresponds to the target OS and channel in the
92  # version map.
93  v8_version = None
94  for curr_os in versions:
95    for curr_version in curr_os['versions']:
96      if (curr_version['os'] == target_os and
97          curr_version['channel'] == target_channel):
98        return curr_version['v8_version']
99
100def UpdateV8Binary(v8_version, target_os, target_arch):
101  """Updates the catapult V8 binary for the specified OS to be the specified V8
102  version."""
103  # Clone v8, checkout the version that corresponds to our target OS and target
104  # channel, and build the d8 binary.
105  with TempDir() as v8_checkout_path:
106    with ChangeDirectory(v8_checkout_path):
107      subprocess.check_call('fetch v8', shell=True)
108      with ChangeDirectory('v8'):
109        subprocess.check_call('git checkout {0}'.format(v8_version), shell=True)
110
111        os.environ['GYP_DEFINES'] += ' v8_use_external_startup_data=0'
112        os.environ['GYP_GENERATORS'] = 'ninja'
113        ninja_arch = PYTHON_MACHINE_TO_NINJA_ARCH[target_arch]
114        subprocess.check_call(
115            V8_GENERATE_GYP_CMD.format(arch=ninja_arch), shell=True)
116        subprocess.check_call(V8_COMPILE_CMD, shell=True)
117        if target_os in ['linux', 'mac']:
118          subprocess.check_call(V8_STRIP_CMD, shell=True)
119
120    # Move the d8 binary into place.
121    d8_src = V8_CHECKOUT_BINARY_PATH.format(v8_root=v8_checkout_path)
122    d8_dst = V8_BINARY_PATH.format(os=target_os, arch=target_arch)
123
124    shutil.move(d8_src, d8_dst)
125
126def UpdateReadmeFile(v8_version, target_os):
127  """Updates the V8 version number in the V8 README.chromium file."""
128  # Get the contents of the new README file with the replaced version number.
129  new_readme_contents = ''
130  with open(V8_README_PATH, 'r') as v8_readme:
131    new_readme_contents = re.sub(r'[0-9\.]+ \({0}\)'.format(target_os),
132                                 r'{0} ({1})'.format(v8_version, target_os),
133                                 v8_readme.read())
134
135  # Overwrite the old README file with the new one.
136  with open(V8_README_PATH, 'w') as v8_readme:
137    v8_readme.write(new_readme_contents)
138
139class ChangeDirectory:
140  """A context manager that changes a directory while in scope."""
141  def __init__(self, newPath):
142    self.newPath = newPath
143
144  def __enter__(self):
145    self.oldPath = os.getcwd()
146    os.chdir(self.newPath)
147
148  def __exit__(self, etype, value, traceback):
149    os.chdir(self.oldPath)
150
151class TempDir:
152  """A context manager that creates a temporary directory while in scope."""
153  def __enter__(self):
154    self.path = tempfile.mkdtemp()
155    print "creating {0}".format(self.path)
156    return self.path
157
158  def __exit__(self, etype, value, traceback):
159    shutil.rmtree(self.path)
160
161if __name__ == '__main__':
162  sys.exit(Main(sys.argv[1:]))
163