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