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"""This script is used to download prebuilt clang binaries.""" 7 8import os 9import shutil 10import subprocess 11import stat 12import sys 13import tarfile 14import tempfile 15import time 16import urllib2 17 18 19# CLANG_REVISION and CLANG_SUB_REVISION determine the build of clang 20# to use. These should be synced with tools/clang/scripts/update.py in 21# Chromium. 22CLANG_REVISION = 'llvmorg-12-init-5627-gf086e85e' 23CLANG_SUB_REVISION = 2 24 25PACKAGE_VERSION = '%s-%s' % (CLANG_REVISION, CLANG_SUB_REVISION) 26 27# Path constants. (All of these should be absolute paths.) 28THIS_DIR = os.path.abspath(os.path.dirname(__file__)) 29LLVM_BUILD_DIR = os.path.join(THIS_DIR, 'llvm-build') 30STAMP_FILE = os.path.join(LLVM_BUILD_DIR, 'cr_build_revision') 31 32# URL for pre-built binaries. 33CDS_URL = os.environ.get('CDS_CLANG_BUCKET_OVERRIDE', 34 'https://commondatastorage.googleapis.com/chromium-browser-clang') 35 36 37def DownloadUrl(url, output_file): 38 """Download url into output_file.""" 39 CHUNK_SIZE = 4096 40 TOTAL_DOTS = 10 41 num_retries = 3 42 retry_wait_s = 5 # Doubled at each retry. 43 44 while True: 45 try: 46 sys.stdout.write('Downloading %s ' % url) 47 sys.stdout.flush() 48 response = urllib2.urlopen(url) 49 total_size = int(response.info().getheader('Content-Length').strip()) 50 bytes_done = 0 51 dots_printed = 0 52 while True: 53 chunk = response.read(CHUNK_SIZE) 54 if not chunk: 55 break 56 output_file.write(chunk) 57 bytes_done += len(chunk) 58 num_dots = TOTAL_DOTS * bytes_done / total_size 59 sys.stdout.write('.' * (num_dots - dots_printed)) 60 sys.stdout.flush() 61 dots_printed = num_dots 62 if bytes_done != total_size: 63 raise urllib2.URLError("only got %d of %d bytes" % 64 (bytes_done, total_size)) 65 print ' Done.' 66 return 67 except urllib2.URLError as e: 68 sys.stdout.write('\n') 69 print e 70 if num_retries == 0 or isinstance(e, urllib2.HTTPError) and e.code == 404: 71 raise e 72 num_retries -= 1 73 print 'Retrying in %d s ...' % retry_wait_s 74 time.sleep(retry_wait_s) 75 retry_wait_s *= 2 76 77 78def EnsureDirExists(path): 79 if not os.path.exists(path): 80 print "Creating directory %s" % path 81 os.makedirs(path) 82 83 84def DownloadAndUnpack(url, output_dir): 85 with tempfile.TemporaryFile() as f: 86 DownloadUrl(url, f) 87 f.seek(0) 88 EnsureDirExists(output_dir) 89 tarfile.open(mode='r:gz', fileobj=f).extractall(path=output_dir) 90 91 92def ReadStampFile(path=STAMP_FILE): 93 """Return the contents of the stamp file, or '' if it doesn't exist.""" 94 try: 95 with open(path, 'r') as f: 96 return f.read().rstrip() 97 except IOError: 98 return '' 99 100 101def WriteStampFile(s, path=STAMP_FILE): 102 """Write s to the stamp file.""" 103 EnsureDirExists(os.path.dirname(path)) 104 with open(path, 'w') as f: 105 f.write(s) 106 f.write('\n') 107 108 109def RmTree(dir): 110 """Delete dir.""" 111 def ChmodAndRetry(func, path, _): 112 # Subversion can leave read-only files around. 113 if not os.access(path, os.W_OK): 114 os.chmod(path, stat.S_IWUSR) 115 return func(path) 116 raise 117 118 shutil.rmtree(dir, onerror=ChmodAndRetry) 119 120 121def CopyFile(src, dst): 122 """Copy a file from src to dst.""" 123 print "Copying %s to %s" % (src, dst) 124 shutil.copy(src, dst) 125 126 127def UpdateClang(): 128 cds_file = "clang-%s.tgz" % PACKAGE_VERSION 129 if sys.platform == 'win32' or sys.platform == 'cygwin': 130 cds_full_url = CDS_URL + '/Win/' + cds_file 131 elif sys.platform.startswith('linux'): 132 cds_full_url = CDS_URL + '/Linux_x64/' + cds_file 133 else: 134 return 0 135 136 print 'Updating Clang to %s...' % PACKAGE_VERSION 137 138 if ReadStampFile() == PACKAGE_VERSION: 139 print 'Clang is already up to date.' 140 return 0 141 142 # Reset the stamp file in case the build is unsuccessful. 143 WriteStampFile('') 144 145 print 'Downloading prebuilt clang' 146 if os.path.exists(LLVM_BUILD_DIR): 147 RmTree(LLVM_BUILD_DIR) 148 try: 149 DownloadAndUnpack(cds_full_url, LLVM_BUILD_DIR) 150 print 'clang %s unpacked' % PACKAGE_VERSION 151 WriteStampFile(PACKAGE_VERSION) 152 return 0 153 except urllib2.URLError: 154 print 'Failed to download prebuilt clang %s' % cds_file 155 print 'Exiting.' 156 return 1 157 158 159def main(): 160 # Don't buffer stdout, so that print statements are immediately flushed. 161 sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0) 162 return UpdateClang() 163 164 165if __name__ == '__main__': 166 sys.exit(main()) 167