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