Home | History | Annotate | Download | only in scripts
      1 #!/usr/bin/env python
      2 # Copyright (c) 2012 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 """Windows can't run .sh files, so this is a Python implementation of
      7 update.sh. This script should replace update.sh on all platforms eventually."""
      8 
      9 import os
     10 import re
     11 import shutil
     12 import subprocess
     13 import sys
     14 
     15 # Do NOT CHANGE this if you don't know what you're doing -- see
     16 # https://code.google.com/p/chromium/wiki/UpdatingClang
     17 # Reverting problematic clang rolls is safe, though.
     18 # Note: this revision is only used for Windows. Other platforms use update.sh.
     19 LLVM_WIN_REVISION = 'HEAD'
     20 
     21 # ASan on Windows is useful enough to use it even while the clang/win is still
     22 # in bringup. Use a pinned revision to make it slightly more stable.
     23 if (re.search(r'\b(asan)=1', os.environ.get('GYP_DEFINES', '')) and
     24     not 'LLVM_FORCE_HEAD_REVISION' in os.environ):
     25   LLVM_WIN_REVISION = '210586'
     26 
     27 # Path constants. (All of these should be absolute paths.)
     28 THIS_DIR = os.path.abspath(os.path.dirname(__file__))
     29 CHROMIUM_DIR = os.path.abspath(os.path.join(THIS_DIR, '..', '..', '..'))
     30 LLVM_DIR = os.path.join(CHROMIUM_DIR, 'third_party', 'llvm')
     31 LLVM_BUILD_DIR = os.path.join(CHROMIUM_DIR, 'third_party', 'llvm-build',
     32                               'Release+Asserts')
     33 COMPILER_RT_BUILD_DIR = os.path.join(LLVM_BUILD_DIR, '32bit-compiler-rt')
     34 CLANG_DIR = os.path.join(LLVM_DIR, 'tools', 'clang')
     35 COMPILER_RT_DIR = os.path.join(LLVM_DIR, 'projects', 'compiler-rt')
     36 STAMP_FILE = os.path.join(LLVM_BUILD_DIR, 'cr_build_revision')
     37 
     38 LLVM_REPO_URL='https://llvm.org/svn/llvm-project'
     39 if 'LLVM_REPO_URL' in os.environ:
     40   LLVM_REPO_URL = os.environ['LLVM_REPO_URL']
     41 
     42 
     43 def ReadStampFile():
     44   """Return the contents of the stamp file, or '' if it doesn't exist."""
     45   try:
     46     with open(STAMP_FILE, 'r') as f:
     47       return f.read();
     48   except IOError:
     49     return ''
     50 
     51 
     52 def WriteStampFile(s):
     53   """Write s to the stamp file."""
     54   if not os.path.exists(LLVM_BUILD_DIR):
     55     os.makedirs(LLVM_BUILD_DIR)
     56   with open(STAMP_FILE, 'w') as f:
     57     f.write(s)
     58 
     59 
     60 def DeleteFiles(dir, pattern):
     61   """Delete all files in dir matching pattern."""
     62   n = 0
     63   regex = re.compile(r'^' + pattern + r'$')
     64   for root, _, files in os.walk(dir):
     65     for f in files:
     66       if regex.match(f):
     67         os.remove(os.path.join(root, f))
     68         n += 1
     69   return n
     70 
     71 
     72 def ClobberChromiumBuildFiles():
     73   """Clobber Chomium build files."""
     74   print 'Clobbering Chromium build files...'
     75   out_dir = os.path.join(CHROMIUM_DIR, 'out')
     76   if os.path.isdir(out_dir):
     77     shutil.rmtree(out_dir)
     78     print 'Removed Chromium out dir: %s.' % (out_dir)
     79 
     80 
     81 def RunCommand(command, tries=1):
     82   """Run a command, possibly with multiple retries."""
     83   for i in range(0, tries):
     84     print 'Running %s (try #%d)' % (str(command), i + 1)
     85     if subprocess.call(command, shell=True) == 0:
     86       return
     87     print 'Failed.'
     88   sys.exit(1)
     89 
     90 def CopyFile(src, dst):
     91   """Copy a file from src to dst."""
     92   shutil.copy(src, dst)
     93   print "Copying %s to %s" % (src, dst)
     94 
     95 def Checkout(name, url, dir):
     96   """Checkout the SVN module at url into dir. Use name for the log message."""
     97   print "Checking out %s r%s into '%s'" % (name, LLVM_WIN_REVISION, dir)
     98   RunCommand(['svn', 'checkout', '--force',
     99               url + '@' + LLVM_WIN_REVISION, dir], tries=2)
    100 
    101 
    102 vs_version = None
    103 def GetVSVersion():
    104   global vs_version
    105   if not vs_version:
    106     # TODO(hans): Find a less hacky way to find the MSVS installation.
    107     sys.path.append(os.path.join(CHROMIUM_DIR, 'tools', 'gyp', 'pylib'))
    108     import gyp.MSVSVersion
    109     # We request VS 2013 because Clang won't build with 2010, and 2013 will be
    110     # the default for Chromium soon anyway.
    111     vs_version = gyp.MSVSVersion.SelectVisualStudioVersion('2013')
    112   return vs_version
    113 
    114 
    115 def UpdateClang():
    116   print 'Updating Clang to %s...' % (LLVM_WIN_REVISION)
    117   if LLVM_WIN_REVISION != 'HEAD' and ReadStampFile() == LLVM_WIN_REVISION:
    118     print 'Already up to date.'
    119     return 0
    120 
    121   ClobberChromiumBuildFiles()
    122 
    123   # Reset the stamp file in case the build is unsuccessful.
    124   WriteStampFile('')
    125 
    126   Checkout('LLVM', LLVM_REPO_URL + '/llvm/trunk', LLVM_DIR)
    127   Checkout('Clang', LLVM_REPO_URL + '/cfe/trunk', CLANG_DIR)
    128   Checkout('compiler-rt', LLVM_REPO_URL + '/compiler-rt/trunk', COMPILER_RT_DIR)
    129 
    130   if not os.path.exists(LLVM_BUILD_DIR):
    131     os.makedirs(LLVM_BUILD_DIR)
    132   os.chdir(LLVM_BUILD_DIR)
    133 
    134   if not re.search(r'cmake', os.environ['PATH'], flags=re.IGNORECASE):
    135     # If CMake is not on the path, try looking in a standard location.
    136     os.environ['PATH'] += os.pathsep + 'C:\\Program Files (x86)\\CMake 2.8\\bin'
    137 
    138   RunCommand(GetVSVersion().SetupScript('x64') +
    139              ['&&', 'cmake', '-GNinja', '-DCMAKE_BUILD_TYPE=Release',
    140               '-DLLVM_ENABLE_ASSERTIONS=ON', LLVM_DIR])
    141   RunCommand(GetVSVersion().SetupScript('x64') + ['&&', 'ninja', 'all'])
    142 
    143   # Do an x86 build of compiler-rt to get the 32-bit ASan run-time.
    144   # TODO(hans): Remove once the regular build above produces this.
    145   if not os.path.exists(COMPILER_RT_BUILD_DIR):
    146     os.makedirs(COMPILER_RT_BUILD_DIR)
    147   os.chdir(COMPILER_RT_BUILD_DIR)
    148   RunCommand(GetVSVersion().SetupScript('x86') +
    149              ['&&', 'cmake', '-GNinja', '-DCMAKE_BUILD_TYPE=Release',
    150               '-DLLVM_ENABLE_ASSERTIONS=ON', LLVM_DIR])
    151   RunCommand(GetVSVersion().SetupScript('x86') + ['&&', 'ninja', 'compiler-rt'])
    152 
    153   # TODO(hans): Make this (and the .gypi file) version number independent.
    154   asan_rt_lib_src_dir = os.path.join(COMPILER_RT_BUILD_DIR, 'lib', 'clang',
    155                                      '3.5.0', 'lib', 'windows')
    156   asan_rt_lib_dst_dir = os.path.join(LLVM_BUILD_DIR, 'lib', 'clang',
    157                                      '3.5.0', 'lib', 'windows')
    158 
    159   if not os.path.exists(asan_rt_lib_dst_dir):
    160     os.makedirs(asan_rt_lib_dst_dir)
    161   for root, _, files in os.walk(asan_rt_lib_src_dir):
    162     for f in files:
    163       if re.match(r'^.*-i386\.lib$', f):
    164         CopyFile(os.path.join(root, f), asan_rt_lib_dst_dir)
    165 
    166   CopyFile(os.path.join(asan_rt_lib_src_dir, '..', '..', 'asan_blacklist.txt'),
    167            os.path.join(asan_rt_lib_dst_dir, '..', '..'))
    168 
    169   # Make an extra copy of the sanitizer headers, to be put on the include path
    170   # of the fallback compiler.
    171   sanitizer_include_dir = os.path.join(LLVM_BUILD_DIR, 'lib', 'clang', '3.5.0',
    172                                        'include', 'sanitizer')
    173   aux_sanitizer_include_dir = os.path.join(LLVM_BUILD_DIR, 'lib', 'clang',
    174                                            '3.5.0', 'include_sanitizer',
    175                                            'sanitizer')
    176   if not os.path.exists(aux_sanitizer_include_dir):
    177     os.makedirs(aux_sanitizer_include_dir)
    178   for _, _, files in os.walk(sanitizer_include_dir):
    179     for f in files:
    180       CopyFile(os.path.join(sanitizer_include_dir, f),
    181                aux_sanitizer_include_dir)
    182 
    183   WriteStampFile(LLVM_WIN_REVISION)
    184   print 'Clang update was successful.'
    185   return 0
    186 
    187 
    188 def main():
    189   if not sys.platform in ['win32', 'cygwin']:
    190     # For non-Windows, fall back to update.sh.
    191     # TODO(hans): Make update.py replace update.sh completely.
    192 
    193     # This script is called by gclient. gclient opens its hooks subprocesses
    194     # with (stdout=subprocess.PIPE, stderr=subprocess.STDOUT) and then does
    195     # custom output processing that breaks printing '\r' characters for
    196     # single-line updating status messages as printed by curl and wget.
    197     # Work around this by setting stderr of the update.sh process to stdin (!):
    198     # gclient doesn't redirect stdin, and while stdin itself is read-only, a
    199     # dup()ed sys.stdin is writable, try
    200     #   fd2 = os.dup(sys.stdin.fileno()); os.write(fd2, 'hi')
    201     # TODO: Fix gclient instead, http://crbug.com/95350
    202     return subprocess.call(
    203         [os.path.join(os.path.dirname(__file__), 'update.sh')] +  sys.argv[1:],
    204         stderr=os.fdopen(os.dup(sys.stdin.fileno())))
    205 
    206   if not re.search(r'\b(clang|asan)=1', os.environ.get('GYP_DEFINES', '')):
    207     print 'Skipping Clang update (clang=1 was not set in GYP_DEFINES).'
    208     return 0
    209 
    210   if re.search(r'\b(make_clang_dir)=', os.environ.get('GYP_DEFINES', '')):
    211     print 'Skipping Clang update (make_clang_dir= was set in GYP_DEFINES).'
    212     return 0
    213 
    214   return UpdateClang()
    215 
    216 
    217 if __name__ == '__main__':
    218   sys.exit(main())
    219