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 = '321529'
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# Bump after VC updates.
37DIA_DLL = {
38  '2013': 'msdia120.dll',
39  '2015': 'msdia140.dll',
40  '2017': 'msdia140.dll',
41}
42
43
44def DownloadUrl(url, output_file):
45  """Download url into output_file."""
46  CHUNK_SIZE = 4096
47  TOTAL_DOTS = 10
48  num_retries = 3
49  retry_wait_s = 5  # Doubled at each retry.
50
51  while True:
52    try:
53      sys.stdout.write('Downloading %s ' % url)
54      sys.stdout.flush()
55      response = urllib2.urlopen(url)
56      total_size = int(response.info().getheader('Content-Length').strip())
57      bytes_done = 0
58      dots_printed = 0
59      while True:
60        chunk = response.read(CHUNK_SIZE)
61        if not chunk:
62          break
63        output_file.write(chunk)
64        bytes_done += len(chunk)
65        num_dots = TOTAL_DOTS * bytes_done / total_size
66        sys.stdout.write('.' * (num_dots - dots_printed))
67        sys.stdout.flush()
68        dots_printed = num_dots
69      if bytes_done != total_size:
70        raise urllib2.URLError("only got %d of %d bytes" %
71                               (bytes_done, total_size))
72      print ' Done.'
73      return
74    except urllib2.URLError as e:
75      sys.stdout.write('\n')
76      print e
77      if num_retries == 0 or isinstance(e, urllib2.HTTPError) and e.code == 404:
78        raise e
79      num_retries -= 1
80      print 'Retrying in %d s ...' % retry_wait_s
81      time.sleep(retry_wait_s)
82      retry_wait_s *= 2
83
84
85def EnsureDirExists(path):
86  if not os.path.exists(path):
87    print "Creating directory %s" % path
88    os.makedirs(path)
89
90
91def DownloadAndUnpack(url, output_dir):
92  with tempfile.TemporaryFile() as f:
93    DownloadUrl(url, f)
94    f.seek(0)
95    EnsureDirExists(output_dir)
96    tarfile.open(mode='r:gz', fileobj=f).extractall(path=output_dir)
97
98
99def ReadStampFile(path=STAMP_FILE):
100  """Return the contents of the stamp file, or '' if it doesn't exist."""
101  try:
102    with open(path, 'r') as f:
103      return f.read().rstrip()
104  except IOError:
105    return ''
106
107
108def WriteStampFile(s, path=STAMP_FILE):
109  """Write s to the stamp file."""
110  EnsureDirExists(os.path.dirname(path))
111  with open(path, 'w') as f:
112    f.write(s)
113    f.write('\n')
114
115
116def RmTree(dir):
117  """Delete dir."""
118  def ChmodAndRetry(func, path, _):
119    # Subversion can leave read-only files around.
120    if not os.access(path, os.W_OK):
121      os.chmod(path, stat.S_IWUSR)
122      return func(path)
123    raise
124
125  shutil.rmtree(dir, onerror=ChmodAndRetry)
126
127
128def CopyFile(src, dst):
129  """Copy a file from src to dst."""
130  print "Copying %s to %s" % (src, dst)
131  shutil.copy(src, dst)
132
133
134vs_version = None
135def GetVSVersion():
136  global vs_version
137  if vs_version:
138    return vs_version
139
140  # Try using the toolchain in depot_tools.
141  # This sets environment variables used by SelectVisualStudioVersion below.
142  sys.path.append(THIS_DIR)
143  import vs_toolchain
144  vs_toolchain.SetEnvironmentAndGetRuntimeDllDirs()
145
146  # Use gyp to find the MSVS installation, either in depot_tools as per above,
147  # or a system-wide installation otherwise.
148  sys.path.append(os.path.join(THIS_DIR, 'gyp', 'pylib'))
149  import gyp.MSVSVersion
150  vs_version = gyp.MSVSVersion.SelectVisualStudioVersion(
151      vs_toolchain.GetVisualStudioVersion())
152  return vs_version
153
154
155def CopyDiaDllTo(target_dir):
156  # This script always wants to use the 64-bit msdia*.dll.
157  dia_path = os.path.join(GetVSVersion().Path(), 'DIA SDK', 'bin', 'amd64')
158  dia_dll = os.path.join(dia_path, DIA_DLL[GetVSVersion().ShortName()])
159  CopyFile(dia_dll, target_dir)
160
161
162def UpdateClang():
163  cds_file = "clang-%s.tgz" %  PACKAGE_VERSION
164  if sys.platform == 'win32' or sys.platform == 'cygwin':
165    cds_full_url = CDS_URL + '/Win/' + cds_file
166  elif sys.platform.startswith('linux'):
167    cds_full_url = CDS_URL + '/Linux_x64/' + cds_file
168  else:
169    return 0
170
171  print 'Updating Clang to %s...' % PACKAGE_VERSION
172
173  if ReadStampFile() == PACKAGE_VERSION:
174    print 'Clang is already up to date.'
175    return 0
176
177  # Reset the stamp file in case the build is unsuccessful.
178  WriteStampFile('')
179
180  print 'Downloading prebuilt clang'
181  if os.path.exists(LLVM_BUILD_DIR):
182    RmTree(LLVM_BUILD_DIR)
183  try:
184    DownloadAndUnpack(cds_full_url, LLVM_BUILD_DIR)
185    print 'clang %s unpacked' % PACKAGE_VERSION
186    if sys.platform == 'win32':
187      CopyDiaDllTo(os.path.join(LLVM_BUILD_DIR, 'bin'))
188    WriteStampFile(PACKAGE_VERSION)
189    return 0
190  except urllib2.URLError:
191    print 'Failed to download prebuilt clang %s' % cds_file
192    print 'Exiting.'
193    return 1
194
195
196def main():
197  # Don't buffer stdout, so that print statements are immediately flushed.
198  sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
199  return UpdateClang()
200
201
202if __name__ == '__main__':
203  sys.exit(main())
204