xref: /aosp_15_r20/external/boringssl/src/util/bot/update_clang.py (revision 8fb009dc861624b67b6cdb62ea21f0f22d0c584b)
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