OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 '''A simple tool to update the Native Client SDK to the latest version''' | 6 '''A simple tool to update the Native Client SDK to the latest version''' |
7 | 7 |
8 import cStringIO | 8 import cStringIO |
9 import cygtar | 9 import cygtar |
10 import errno | 10 import errno |
(...skipping 85 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
96 mgs: A string to send to stderr.''' | 96 mgs: A string to send to stderr.''' |
97 sys.stderr.write("WARNING: %s\n" % msg) | 97 sys.stderr.write("WARNING: %s\n" % msg) |
98 sys.stderr.flush() | 98 sys.stderr.flush() |
99 | 99 |
100 | 100 |
101 class Error(Exception): | 101 class Error(Exception): |
102 '''Generic error/exception for sdk_update module''' | 102 '''Generic error/exception for sdk_update module''' |
103 pass | 103 pass |
104 | 104 |
105 | 105 |
106 def GetHostOS(): | |
107 '''Returns the host_os value that corresponds to the current host OS''' | |
108 return { | |
109 'linux2': 'linux', | |
110 'darwin': 'mac', | |
111 'cygwin': 'win', | |
112 'win32': 'win' | |
113 }[sys.platform] | |
114 | |
115 def UrlOpen(url): | 106 def UrlOpen(url): |
116 request = fancy_urllib.FancyRequest(url) | 107 request = fancy_urllib.FancyRequest(url) |
117 ca_certs = os.path.join(os.path.dirname(os.path.abspath(__file__)), | 108 ca_certs = os.path.join(os.path.dirname(os.path.abspath(__file__)), |
118 'cacerts.txt') | 109 'cacerts.txt') |
119 request.set_ssl_info(ca_certs=ca_certs) | 110 request.set_ssl_info(ca_certs=ca_certs) |
120 url_opener = urllib2.build_opener( | 111 url_opener = urllib2.build_opener( |
121 fancy_urllib.FancyProxyHandler(), | 112 fancy_urllib.FancyProxyHandler(), |
122 fancy_urllib.FancyRedirectHandler(), | 113 fancy_urllib.FancyRedirectHandler(), |
123 fancy_urllib.FancyHTTPSHandler()) | 114 fancy_urllib.FancyHTTPSHandler()) |
124 return url_opener.open(request) | 115 return url_opener.open(request) |
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
200 # handling a Windows flaky access error. Sleep one second and try | 191 # handling a Windows flaky access error. Sleep one second and try |
201 # again. | 192 # again. |
202 time.sleep(num_tries + 1) | 193 time.sleep(num_tries + 1) |
203 # end of while loop -- could not RenameDir | 194 # end of while loop -- could not RenameDir |
204 raise Error('Could not RenameDir %s => %s after %d tries.\n' % | 195 raise Error('Could not RenameDir %s => %s after %d tries.\n' % |
205 'Please check that no shells or applications ' | 196 'Please check that no shells or applications ' |
206 'are accessing files in %s.' | 197 'are accessing files in %s.' |
207 % (srcdir, destdir, num_tries, destdir)) | 198 % (srcdir, destdir, num_tries, destdir)) |
208 | 199 |
209 | 200 |
210 def ShowProgress(progress): | |
211 ''' A download-progress function used by class Archive. | |
212 (See DownloadAndComputeHash).''' | |
213 global count # A divider, so we don't emit dots too often. | |
214 | |
215 if progress == 0: | |
216 count = 0 | |
217 elif progress == 100: | |
218 sys.stdout.write('\n') | |
219 else: | |
220 count = count + 1 | |
221 if count > 10: | |
222 sys.stdout.write('.') | |
223 sys.stdout.flush() | |
224 count = 0 | |
225 | |
226 | |
227 class ProgressFunction(object): | 201 class ProgressFunction(object): |
228 '''Create a progress function for a file with a given size''' | 202 '''Create a progress function for a file with a given size''' |
229 | 203 |
230 def __init__(self, file_size=0): | 204 def __init__(self, file_size=0): |
231 '''Constructor | 205 '''Constructor |
232 | 206 |
233 Args: | 207 Args: |
234 file_size: number of bytes in file. 0 indicates unknown''' | 208 file_size: number of bytes in file. 0 indicates unknown''' |
235 self.dots = 0 | 209 self.dots = 0 |
236 self.file_size = int(file_size) | 210 self.file_size = int(file_size) |
237 | 211 |
238 def GetProgressFunction(self): | 212 def GetProgressFunction(self): |
239 '''Returns a progress function based on a known file size''' | 213 '''Returns a progress function based on a known file size''' |
240 def ShowKnownProgress(progress): | 214 def ShowKnownProgress(progress): |
241 if progress == 0: | 215 if progress == 0: |
242 sys.stdout.write('|%s|\n' % ('=' * 48)) | 216 sys.stdout.write('|%s|\n' % ('=' * 48)) |
243 else: | 217 else: |
244 new_dots = progress * 50 / self.file_size - self.dots | 218 new_dots = progress * 50 / self.file_size - self.dots |
245 sys.stdout.write('.' * new_dots) | 219 sys.stdout.write('.' * new_dots) |
246 self.dots += new_dots | 220 self.dots += new_dots |
247 if progress == self.file_size: | 221 if progress == self.file_size: |
248 sys.stdout.write('\n') | 222 sys.stdout.write('\n') |
249 sys.stdout.flush() | 223 sys.stdout.flush() |
250 | 224 |
251 return ShowKnownProgress | 225 return ShowKnownProgress |
252 | 226 |
253 | 227 |
254 def DownloadAndComputeHash(from_stream, to_stream=None, progress_func=None): | 228 def DownloadArchiveToFile(archive, dest_path): |
255 ''' Download the archive data from from-stream and generate sha1 and | 229 '''Download the archive's data to a file at dest_path. |
256 size info. | 230 |
| 231 As a side effect, computes the sha1 hash and data size, both returned as a |
| 232 tuple. Raises an Error if the url can't be opened, or an IOError exception if |
| 233 dest_path can't be opened. |
257 | 234 |
258 Args: | 235 Args: |
259 from_stream: An input stream that supports read. | 236 dest_path: Path for the file that will receive the data. |
260 to_stream: [optional] the data is written to to_stream if it is | 237 Return: |
261 provided. | 238 A tuple (sha1, size) with the sha1 hash and data size respectively.''' |
262 progress_func: [optional] A function used to report download progress. If | 239 sha1 = None |
263 provided, progress_func is called with progress=0 at the | |
264 beginning of the download, periodically with progress=1 | |
265 during the download, and progress=100 at the end. | |
266 | |
267 Return | |
268 A tuple (sha1, size) where sha1 is a sha1-hash for the archive data and | |
269 size is the size of the archive data in bytes.''' | |
270 # Use a no-op progress function if none is specified. | |
271 def progress_no_op(progress): | |
272 pass | |
273 if not progress_func: | |
274 progress_func = progress_no_op | |
275 | |
276 sha1_hash = hashlib.sha1() | |
277 size = 0 | 240 size = 0 |
278 progress_func(progress=0) | 241 with open(dest_path, 'wb') as to_stream: |
279 while(1): | 242 from_stream = None |
280 data = from_stream.read(32768) | 243 try: |
281 if not data: | 244 from_stream = UrlOpen(archive.url) |
282 break | 245 except urllib2.URLError: |
283 sha1_hash.update(data) | 246 raise Error('Cannot open "%s" for archive %s' % |
284 size += len(data) | 247 (archive.url, archive.host_os)) |
285 if to_stream: | 248 try: |
286 to_stream.write(data) | 249 content_length = int(from_stream.info()[HTTP_CONTENT_LENGTH]) |
287 progress_func(size) | 250 progress_function = ProgressFunction(content_length).GetProgressFunction() |
288 | 251 InfoPrint('Downloading %s' % archive.url) |
289 progress_func(progress=100) | 252 sha1, size = manifest_util.DownloadAndComputeHash( |
290 return sha1_hash.hexdigest(), size | 253 from_stream, |
| 254 to_stream=to_stream, |
| 255 progress_func=progress_function) |
| 256 if size != content_length: |
| 257 raise Error('Download size mismatch for %s.\n' |
| 258 'Expected %s bytes but got %s' % |
| 259 (archive.url, content_length, size)) |
| 260 finally: |
| 261 if from_stream: from_stream.close() |
| 262 return sha1, size |
291 | 263 |
292 | 264 |
293 def LoadManifestFromFile(path): | 265 def LoadManifestFromFile(path): |
294 '''Returns a manifest loaded from the JSON file at |path|. | 266 '''Returns a manifest loaded from the JSON file at |path|. |
295 | 267 |
296 If the path does not exist or is invalid, returns an empty manifest.''' | 268 If the path does not exist or is invalid, returns an empty manifest.''' |
297 if not os.path.exists(path): | 269 if not os.path.exists(path): |
298 return manifest_util.SDKManifest() | 270 return manifest_util.SDKManifest() |
299 | 271 |
300 with open(path, 'r') as f: | 272 with open(path, 'r') as f: |
301 json_string = f.read() | 273 json_string = f.read() |
302 if not json_string: | 274 if not json_string: |
303 return manifest_util.SDKManifest() | 275 return manifest_util.SDKManifest() |
304 | 276 |
305 manifest = manifest_util.SDKManifest() | 277 manifest = manifest_util.SDKManifest() |
306 manifest.LoadManifestString(json_string) | 278 manifest.LoadManifestString(json_string) |
307 return manifest | 279 return manifest |
308 | 280 |
309 | 281 |
310 def LoadManifestFromURL(url): | 282 def LoadManifestFromURL(url): |
311 '''Returns a manifest loaded from |url|.''' | 283 '''Returns a manifest loaded from |url|.''' |
312 try: | 284 try: |
313 url_stream = UrlOpen(url) | 285 url_stream = UrlOpen(url) |
314 except urllib2.URLError as e: | 286 except urllib2.URLError as e: |
315 raise Error('Unable to open %s. [%s]' % (url, e)) | 287 raise Error('Unable to open %s. [%s]' % (url, e)) |
316 | 288 |
317 manifest_stream = cStringIO.StringIO() | 289 manifest_stream = cStringIO.StringIO() |
318 sha1, size = DownloadAndComputeHash( | 290 sha1, size = manifest_util.DownloadAndComputeHash(url_stream, manifest_stream) |
319 url_stream, manifest_stream) | |
320 manifest = manifest_util.SDKManifest() | 291 manifest = manifest_util.SDKManifest() |
321 manifest.LoadManifestString(manifest_stream.getvalue()) | 292 manifest.LoadManifestString(manifest_stream.getvalue()) |
322 | 293 |
323 def BundleFilter(bundle): | 294 def BundleFilter(bundle): |
324 # Only add this bundle if it's supported on this platform. | 295 # Only add this bundle if it's supported on this platform. |
325 return bundle.GetArchive(GetHostOS()) | 296 return bundle.GetHostOSArchive() |
326 | 297 |
327 manifest.FilterBundles(BundleFilter) | 298 manifest.FilterBundles(BundleFilter) |
328 return manifest | 299 return manifest |
329 | 300 |
330 | 301 |
331 def WriteManifestToFile(manifest, path): | 302 def WriteManifestToFile(manifest, path): |
332 '''Write |manifest| to a JSON file at |path|.''' | 303 '''Write |manifest| to a JSON file at |path|.''' |
333 json_string = manifest.GetManifestString() | 304 json_string = manifest.GetManifestString() |
334 | 305 |
335 # Write the JSON data to a temp file. | 306 # Write the JSON data to a temp file. |
336 temp_file_name = None | 307 temp_file_name = None |
337 # TODO(dspringer): Use file locks here so that multiple sdk_updates can | 308 # TODO(dspringer): Use file locks here so that multiple sdk_updates can |
338 # run at the same time. | 309 # run at the same time. |
339 with tempfile.NamedTemporaryFile(mode='w', delete=False) as f: | 310 with tempfile.NamedTemporaryFile(mode='w', delete=False) as f: |
340 f.write(json_string) | 311 f.write(json_string) |
341 temp_file_name = f.name | 312 temp_file_name = f.name |
342 # Move the temp file to the actual file. | 313 # Move the temp file to the actual file. |
343 if os.path.exists(path): | 314 if os.path.exists(path): |
344 os.remove(path) | 315 os.remove(path) |
345 shutil.move(temp_file_name, path) | 316 shutil.move(temp_file_name, path) |
346 | 317 |
347 | 318 |
348 def DownloadArchiveToFile(archive, dest_path): | |
349 '''Download the archive's data to a file at dest_path. | |
350 | |
351 As a side effect, computes the sha1 hash and data size, both returned as a | |
352 tuple. Raises an Error if the url can't be opened, or an IOError exception if | |
353 dest_path can't be opened. | |
354 | |
355 Args: | |
356 dest_path: Path for the file that will receive the data. | |
357 Return: | |
358 A tuple (sha1, size) with the sha1 hash and data size respectively.''' | |
359 sha1 = None | |
360 size = 0 | |
361 with open(dest_path, 'wb') as to_stream: | |
362 from_stream = None | |
363 try: | |
364 from_stream = UrlOpen(archive.url) | |
365 except urllib2.URLError: | |
366 raise Error('Cannot open "%s" for archive %s' % | |
367 (archive.url, archive.host_os)) | |
368 try: | |
369 content_length = int(from_stream.info()[HTTP_CONTENT_LENGTH]) | |
370 progress_function = ProgressFunction(content_length).GetProgressFunction() | |
371 InfoPrint('Downloading %s' % archive.url) | |
372 sha1, size = DownloadAndComputeHash( | |
373 from_stream, | |
374 to_stream=to_stream, | |
375 progress_func=progress_function) | |
376 if size != content_length: | |
377 raise Error('Download size mismatch for %s.\n' | |
378 'Expected %s bytes but got %s' % | |
379 (archive.url, content_length, size)) | |
380 finally: | |
381 if from_stream: from_stream.close() | |
382 return sha1, size | |
383 | |
384 | |
385 #------------------------------------------------------------------------------ | 319 #------------------------------------------------------------------------------ |
386 # Commands | 320 # Commands |
387 | 321 |
388 | 322 |
389 def List(options, argv): | 323 def List(options, argv): |
390 '''Usage: %prog [options] list | 324 '''Usage: %prog [options] list |
391 | 325 |
392 Lists the available SDK bundles that are available for download.''' | 326 Lists the available SDK bundles that are available for download.''' |
393 def PrintBundles(bundles): | 327 def PrintBundles(bundles): |
394 for bundle in bundles: | 328 for bundle in bundles: |
(...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
464 | 398 |
465 for bundle in bundles: | 399 for bundle in bundles: |
466 bundle_path = os.path.join(options.sdk_root_dir, bundle.name) | 400 bundle_path = os.path.join(options.sdk_root_dir, bundle.name) |
467 bundle_update_path = '%s_update' % bundle_path | 401 bundle_update_path = '%s_update' % bundle_path |
468 if not (bundle.name in args or | 402 if not (bundle.name in args or |
469 ALL in args or (RECOMMENDED in args and | 403 ALL in args or (RECOMMENDED in args and |
470 bundle[RECOMMENDED] == 'yes')): | 404 bundle[RECOMMENDED] == 'yes')): |
471 continue | 405 continue |
472 def UpdateBundle(): | 406 def UpdateBundle(): |
473 '''Helper to install a bundle''' | 407 '''Helper to install a bundle''' |
474 archive = bundle.GetArchive(GetHostOS()) | 408 archive = bundle.GetHostOSArchive() |
475 (scheme, host, path, _, _, _) = urlparse.urlparse(archive['url']) | 409 (scheme, host, path, _, _, _) = urlparse.urlparse(archive['url']) |
476 dest_filename = os.path.join(options.user_data_dir, path.split('/')[-1]) | 410 dest_filename = os.path.join(options.user_data_dir, path.split('/')[-1]) |
477 sha1, size = DownloadArchiveToFile(archive, dest_filename) | 411 sha1, size = DownloadArchiveToFile(archive, dest_filename) |
478 if sha1 != archive.GetChecksum(): | 412 if sha1 != archive.GetChecksum(): |
479 raise Error("SHA1 checksum mismatch on '%s'. Expected %s but got %s" % | 413 raise Error("SHA1 checksum mismatch on '%s'. Expected %s but got %s" % |
480 (bundle.name, archive.GetChecksum(), sha1)) | 414 (bundle.name, archive.GetChecksum(), sha1)) |
481 if size != archive.size: | 415 if size != archive.size: |
482 raise Error("Size mismatch on Archive. Expected %s but got %s bytes" % | 416 raise Error("Size mismatch on Archive. Expected %s but got %s bytes" % |
483 (archive.size, size)) | 417 (archive.size, size)) |
484 InfoPrint('Updating bundle %s to version %s, revision %s' % ( | 418 InfoPrint('Updating bundle %s to version %s, revision %s' % ( |
(...skipping 126 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
611 | 545 |
612 return 0 # Success | 546 return 0 # Success |
613 | 547 |
614 | 548 |
615 if __name__ == '__main__': | 549 if __name__ == '__main__': |
616 try: | 550 try: |
617 sys.exit(main(sys.argv[1:])) | 551 sys.exit(main(sys.argv[1:])) |
618 except Error as error: | 552 except Error as error: |
619 print "Error: %s" % error | 553 print "Error: %s" % error |
620 sys.exit(1) | 554 sys.exit(1) |
OLD | NEW |