• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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