| OLD | NEW |
| (Empty) |
| 1 #!/usr/bin/env python | |
| 2 # Copyright (c) 2011 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 import hashlib | |
| 7 import optparse | |
| 8 import os | |
| 9 import urllib2 | |
| 10 import sys | |
| 11 import time | |
| 12 | |
| 13 | |
| 14 # Print a dot every time this number of bytes is read. | |
| 15 PROGRESS_SPACING = 128 * 1024 | |
| 16 | |
| 17 | |
| 18 def ReadFile(filename): | |
| 19 fh = open(filename, 'r') | |
| 20 try: | |
| 21 return fh.read() | |
| 22 finally: | |
| 23 fh.close() | |
| 24 | |
| 25 | |
| 26 def WriteFile(filename, data): | |
| 27 fh = open(filename, 'w') | |
| 28 try: | |
| 29 fh.write(data) | |
| 30 finally: | |
| 31 fh.close() | |
| 32 | |
| 33 | |
| 34 def HashFile(filename): | |
| 35 hasher = hashlib.sha1() | |
| 36 fh = open(filename, 'rb') | |
| 37 try: | |
| 38 while True: | |
| 39 data = fh.read(4096) | |
| 40 if len(data) == 0: | |
| 41 break | |
| 42 hasher.update(data) | |
| 43 finally: | |
| 44 fh.close() | |
| 45 return hasher.hexdigest() | |
| 46 | |
| 47 | |
| 48 def CopyStream(input_stream, output_stream): | |
| 49 """Copies the contents of input_stream to output_stream. Prints | |
| 50 dots to indicate progress. | |
| 51 """ | |
| 52 bytes_read = 0 | |
| 53 dots_printed = 0 | |
| 54 while True: | |
| 55 data = input_stream.read(4096) | |
| 56 if len(data) == 0: | |
| 57 break | |
| 58 output_stream.write(data) | |
| 59 bytes_read += len(data) | |
| 60 if bytes_read / PROGRESS_SPACING > dots_printed: | |
| 61 sys.stdout.write('.') | |
| 62 sys.stdout.flush() | |
| 63 dots_printed += 1 | |
| 64 | |
| 65 | |
| 66 def RenameWithRetry(old_path, new_path): | |
| 67 # Renames of files that have recently been closed are known to be | |
| 68 # unreliable on Windows, because virus checkers like to keep the | |
| 69 # file open for a little while longer. This tends to happen more | |
| 70 # for files that look like Windows executables, which does not apply | |
| 71 # to our files, but we retry the rename here just in case. | |
| 72 if sys.platform in ('win32', 'cygwin'): | |
| 73 for i in range(5): | |
| 74 try: | |
| 75 if os.path.exists(new_path): | |
| 76 os.remove(new_path) | |
| 77 os.rename(old_path, new_path) | |
| 78 return | |
| 79 except Exception, exn: | |
| 80 sys.stdout.write('Rename failed with %r. Retrying...\n' % str(exn)) | |
| 81 sys.stdout.flush() | |
| 82 time.sleep(1) | |
| 83 raise Exception('Unabled to rename irt file') | |
| 84 else: | |
| 85 os.rename(old_path, new_path) | |
| 86 | |
| 87 | |
| 88 def DownloadFile(dest_path, url): | |
| 89 url_path = '%s.url' % dest_path | |
| 90 temp_path = '%s.temp' % dest_path | |
| 91 if os.path.exists(url_path) and ReadFile(url_path).strip() == url: | |
| 92 # The URL matches that of the file we previously downloaded, so | |
| 93 # there should be nothing to do. | |
| 94 return | |
| 95 sys.stdout.write('Downloading %r to %r\n' % (url, dest_path)) | |
| 96 output_fh = open(temp_path, 'wb') | |
| 97 stream = urllib2.urlopen(url) | |
| 98 CopyStream(stream, output_fh) | |
| 99 output_fh.close() | |
| 100 sys.stdout.write(' done\n') | |
| 101 if os.path.exists(url_path): | |
| 102 os.unlink(url_path) | |
| 103 RenameWithRetry(temp_path, dest_path) | |
| 104 WriteFile(url_path, url + '\n') | |
| 105 stream.close() | |
| 106 | |
| 107 | |
| 108 def DownloadFileWithRetry(dest_path, url): | |
| 109 for i in range(5): | |
| 110 try: | |
| 111 DownloadFile(dest_path, url) | |
| 112 break | |
| 113 except urllib2.HTTPError, exn: | |
| 114 if exn.getcode() == 404: | |
| 115 raise | |
| 116 sys.stdout.write('Download failed with error %r. Retrying...\n' | |
| 117 % str(exn)) | |
| 118 sys.stdout.flush() | |
| 119 time.sleep(1) | |
| 120 | |
| 121 | |
| 122 def EvalDepsFile(path): | |
| 123 scope = {'Var': lambda name: scope['vars'][name]} | |
| 124 execfile(path, {}, scope) | |
| 125 return scope | |
| 126 | |
| 127 | |
| 128 def Main(): | |
| 129 parser = optparse.OptionParser() | |
| 130 parser.add_option( | |
| 131 '--base_url', dest='base_url', | |
| 132 # For a view of this site that includes directory listings, see: | |
| 133 # http://gsdview.appspot.com/nativeclient-archive2/ | |
| 134 # (The trailing slash is required.) | |
| 135 default=('http://commondatastorage.googleapis.com/' | |
| 136 'nativeclient-archive2/irt'), | |
| 137 help='Base URL from which to download.') | |
| 138 parser.add_option( | |
| 139 '--nacl_revision', dest='nacl_revision', | |
| 140 help='Download an IRT binary that was built from this ' | |
| 141 'SVN revision of Native Client.') | |
| 142 parser.add_option( | |
| 143 '--file_hash', dest='file_hashes', action='append', nargs=2, default=[], | |
| 144 metavar='ARCH HASH', | |
| 145 help='ARCH gives the name of the architecture (e.g. "x86_32") for ' | |
| 146 'which to download an IRT binary. ' | |
| 147 'HASH gives the expected SHA1 hash of the file.') | |
| 148 options, args = parser.parse_args() | |
| 149 if len(args) != 0: | |
| 150 parser.error('Unexpected arguments: %r' % args) | |
| 151 | |
| 152 if options.nacl_revision is None and len(options.file_hashes) == 0: | |
| 153 # The script must have been invoked directly with no arguments, | |
| 154 # rather than being invoked by gclient. In this case, read the | |
| 155 # DEPS file ourselves rather than having gclient pass us values | |
| 156 # from DEPS. | |
| 157 deps_data = EvalDepsFile(os.path.join('src', 'DEPS')) | |
| 158 options.nacl_revision = deps_data['vars']['nacl_revision'] | |
| 159 options.file_hashes = [ | |
| 160 ('x86_32', deps_data['vars']['nacl_irt_hash_x86_32']), | |
| 161 ('x86_64', deps_data['vars']['nacl_irt_hash_x86_64']), | |
| 162 ] | |
| 163 | |
| 164 nacl_dir = os.path.join('src', 'native_client') | |
| 165 if not os.path.exists(nacl_dir): | |
| 166 # If "native_client" is not present, this might be because the | |
| 167 # developer has put '"src/native_client": None' in their | |
| 168 # '.gclient' file, because they don't want to build Chromium with | |
| 169 # Native Client support. So don't create 'src/native_client', | |
| 170 # because that would interfere with checking it out from SVN | |
| 171 # later. | |
| 172 sys.stdout.write( | |
| 173 'The directory %r does not exist: skipping downloading binaries ' | |
| 174 'for Native Client\'s IRT library\n' % nacl_dir) | |
| 175 return | |
| 176 if len(options.file_hashes) == 0: | |
| 177 sys.stdout.write('No --file_hash arguments given: nothing to update\n') | |
| 178 | |
| 179 new_deps = [] | |
| 180 for arch, expected_hash in options.file_hashes: | |
| 181 url = '%s/r%s/irt_%s.nexe' % (options.base_url, | |
| 182 options.nacl_revision, | |
| 183 arch) | |
| 184 dest_dir = os.path.join(nacl_dir, 'irt_binaries') | |
| 185 if not os.path.exists(dest_dir): | |
| 186 os.makedirs(dest_dir) | |
| 187 dest_path = os.path.join(dest_dir, 'nacl_irt_%s.nexe' % arch) | |
| 188 DownloadFileWithRetry(dest_path, url) | |
| 189 downloaded_hash = HashFile(dest_path) | |
| 190 if downloaded_hash != expected_hash: | |
| 191 sys.stdout.write( | |
| 192 'Hash mismatch: the file downloaded from URL %r had hash %r, ' | |
| 193 'but we expected %r\n' % (url, downloaded_hash, expected_hash)) | |
| 194 new_deps.append(' "nacl_irt_hash_%s": "%s",\n' | |
| 195 % (arch, downloaded_hash)) | |
| 196 | |
| 197 if len(new_deps) > 0: | |
| 198 sys.stdout.write('\nIf you have changed nacl_revision, the DEPS file ' | |
| 199 'probably needs to be updated with the following:\n%s\n' | |
| 200 % ''.join(new_deps)) | |
| 201 sys.exit(1) | |
| 202 | |
| 203 | |
| 204 if __name__ == '__main__': | |
| 205 Main() | |
| OLD | NEW |