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