Home | History | Annotate | Download | only in bin
      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
      7 in the current version of the specified Chromium channel. If no channel is
      8 specified, we default to the 'stable' channel.
      9 
     10 This script assumes that git is installed and the computer meets all
     11 other prerequisites to build v8 normally (like having depot_tools installed and
     12 in $PATH).
     13 
     14 Example usage:
     15 $ tracing/bin/update_v8
     16 """
     17 
     18 import json
     19 import os
     20 import platform
     21 import re
     22 import shutil
     23 import subprocess
     24 import sys
     25 import tempfile
     26 import urllib2
     27 
     28 OMAHAPROXY_VERSION_MAP_URL = 'https://omahaproxy.appspot.com/all.json'
     29 
     30 V8_PATH = os.path.join(
     31     os.path.dirname(os.path.abspath(__file__)), os.path.pardir, 'third_party',
     32     'v8')
     33 V8_DST_PATH = os.path.join(V8_PATH, '{os}', '{arch}')
     34 V8_README_PATH = os.path.join(V8_PATH, 'README.chromium')
     35 
     36 V8_CHECKOUT_BINARY_PATH = os.path.join(
     37     '{v8_root}', 'v8', 'out', 'Release', 'd8')
     38 V8_GENERATE_GYP_CMD = (sys.executable + ' ' +
     39                        os.path.join('gypfiles', 'gyp_v8') +
     40                        ' -Dtarget_arch={arch}')
     41 V8_COMPILE_CMD = 'ninja -j 100 -C {0} d8'.format(os.path.join('out', 'Release'))
     42 V8_STRIP_CMD = 'strip -x {0}'.format(os.path.join('out', 'Release', 'd8'))
     43 
     44 VALID_CHANNEL_LIST = ['stable', 'canary', 'beta', 'dev']
     45 # Dict from the acceptable return values for Python's platform.system() to the
     46 # corresponding Chromium OS name.
     47 PYTHON_SYSTEM_TO_CHROME_OS = {
     48   'Linux': 'linux',
     49   'Windows': 'win',
     50   'Darwin': 'mac'
     51 }
     52 # Dict from the acceptable return values for Python's platform.machine() to the
     53 # corresponding ninja architecture name.
     54 PYTHON_MACHINE_TO_NINJA_ARCH = {
     55   'x86_64': 'x64',
     56   'AMD64': 'x64'
     57 }
     58 
     59 def Main(args):
     60   if len(args) > 1:
     61     print('Usage: update_v8 [TARGET_CHANNEL]')
     62     return 1
     63 
     64   target_channel = args[0] if len(args) == 1 else 'stable'
     65   target_arch = platform.machine()
     66 
     67   if target_channel not in VALID_CHANNEL_LIST:
     68     print 'Invalid target channel %s. Valid: %s' % (
     69         target_channel, VALID_CHANNEL_LIST)
     70     return 1
     71 
     72   if platform.system() not in PYTHON_SYSTEM_TO_CHROME_OS:
     73     print 'System not supported %s. Valid: %s' % (
     74         platform.system(), PYTHON_SYSTEM_TO_CHROME_OS)
     75     return 1
     76   target_os = PYTHON_SYSTEM_TO_CHROME_OS[platform.system()]
     77 
     78   if target_arch not in PYTHON_MACHINE_TO_NINJA_ARCH:
     79     print 'Invalid target architecture %s. Valid: %s' % (
     80         target_arch, PYTHON_MACHINE_TO_NINJA_ARCH)
     81     return 1
     82 
     83   v8_version = GetV8Version(target_os, target_channel)
     84   UpdateV8Binary(v8_version, target_os, target_arch)
     85   UpdateReadmeFile(v8_version, target_os)
     86 
     87   return 0
     88 
     89 def GetV8Version(target_os, target_channel):
     90   """Returns the v8 version that corresponds to the specified OS and channel."""
     91   # Fetch the current version map from omahaproxy.
     92   response = urllib2.urlopen(OMAHAPROXY_VERSION_MAP_URL)
     93   versions = json.loads(response.read())
     94 
     95   # Return the v8 version that corresponds to the target OS and channel in the
     96   # version map.
     97   v8_version = None
     98   for curr_os in versions:
     99     for curr_version in curr_os['versions']:
    100       if (curr_version['os'] == target_os and
    101           curr_version['channel'] == target_channel):
    102         return curr_version['v8_version']
    103 
    104 def _RunCommand(command):
    105   print 'Run: %s' % command
    106   subprocess.check_call(command, shell=True, stderr=sys.stderr,
    107                         stdout=sys.stdout)
    108 
    109 def UpdateV8Binary(v8_version, target_os, target_arch):
    110   """Updates the catapult V8 binary for the specified OS to be the specified V8
    111   version."""
    112   # Clone v8, checkout the version that corresponds to our target OS and target
    113   # channel, and build the d8 binary.
    114   with TempDir() as v8_checkout_path:
    115     with ChangeDirectory(v8_checkout_path):
    116       if 'DEPOT_TOOLS_WIN_TOOLCHAIN' not in os.environ:
    117         # If the user doesn't specify that they're using the Googler Windows
    118         # build toolchain, assume that they're not.
    119         os.environ['DEPOT_TOOLS_WIN_TOOLCHAIN'] = '0'
    120 
    121       _RunCommand('fetch v8')
    122       with ChangeDirectory('v8'):
    123         _RunCommand('git checkout {0}'.format(v8_version))
    124         _RunCommand('gclient sync')
    125         if not 'GYP_DEFINES' in os.environ:
    126           os.environ['GYP_DEFINES'] = ''
    127         os.environ['GYP_DEFINES'] += ' v8_use_external_startup_data=0'
    128         os.environ['GYP_GENERATORS'] = 'ninja'
    129         ninja_arch = PYTHON_MACHINE_TO_NINJA_ARCH[target_arch]
    130         _RunCommand(
    131             V8_GENERATE_GYP_CMD.format(arch=ninja_arch))
    132         _RunCommand(V8_COMPILE_CMD)
    133         if target_os in ['linux', 'mac']:
    134           _RunCommand(V8_STRIP_CMD)
    135 
    136     # Copy the d8 binary into place.
    137     d8_bin_src = V8_CHECKOUT_BINARY_PATH.format(v8_root=v8_checkout_path)
    138     d8_dst_dir = V8_DST_PATH.format(os=target_os, arch=target_arch)
    139 
    140     # Append .exe extension on win
    141     if target_os == 'win':
    142       d8_bin_src += '.exe'
    143     shutil.copy(d8_bin_src, d8_dst_dir)
    144     # Also copy dll files on win
    145     if target_os == 'win':
    146       d8_dir_src = os.path.dirname(d8_bin_src)
    147       for f in os.listdir(d8_dir_src):
    148         if f.endswith('.dll'):
    149           lib_path = os.path.join(d8_dir_src, f)
    150           shutil.copy(lib_path, d8_dst_dir)
    151 
    152 def UpdateReadmeFile(v8_version, target_os):
    153   """Updates the V8 version number in the V8 README.chromium file."""
    154   # Get the contents of the new README file with the replaced version number.
    155   new_readme_contents = ''
    156   with open(V8_README_PATH, 'r') as v8_readme:
    157     new_readme_contents = re.sub(r'[0-9\.]+ \({0}\)'.format(target_os),
    158                                  r'{0} ({1})'.format(v8_version, target_os),
    159                                  v8_readme.read())
    160 
    161   # Overwrite the old README file with the new one.
    162   with open(V8_README_PATH, 'w') as v8_readme:
    163     v8_readme.write(new_readme_contents)
    164 
    165 class ChangeDirectory:
    166   """A context manager that changes a directory while in scope."""
    167   def __init__(self, newPath):
    168     self.newPath = newPath
    169 
    170   def __enter__(self):
    171     self.oldPath = os.getcwd()
    172     os.chdir(self.newPath)
    173 
    174   def __exit__(self, etype, value, traceback):
    175     os.chdir(self.oldPath)
    176 
    177 class TempDir:
    178   """A context manager that creates a temporary directory while in scope."""
    179   def __enter__(self):
    180     self.path = tempfile.mkdtemp()
    181     print "creating {0}".format(self.path)
    182     return self.path
    183 
    184   def __exit__(self, etype, value, traceback):
    185     shutil.rmtree(self.path, ignore_errors=True)
    186     if os.path.isdir(self.path):
    187       print '%s still exists. You may want to delete it' % self.path
    188 
    189 if __name__ == '__main__':
    190   sys.exit(Main(sys.argv[1:]))
    191