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 errno | 10 import errno |
10 import exceptions | 11 import exceptions |
11 import hashlib | 12 import hashlib |
12 import json | 13 import json |
13 import optparse | 14 import optparse |
14 import os | 15 import os |
15 import shutil | 16 import shutil |
16 import subprocess | 17 import subprocess |
17 import sys | 18 import sys |
18 import tarfile | |
19 import tempfile | 19 import tempfile |
20 import time | 20 import time |
21 import urllib2 | 21 import urllib2 |
22 import urlparse | 22 import urlparse |
23 | 23 |
24 | 24 |
25 #------------------------------------------------------------------------------ | 25 #------------------------------------------------------------------------------ |
26 # Constants | 26 # Constants |
27 | 27 |
28 # Bump the MINOR_REV every time you check this file in. | 28 # Bump the MINOR_REV every time you check this file in. |
29 MAJOR_REV = 1 | 29 MAJOR_REV = 2 |
30 MINOR_REV = 14 | 30 MINOR_REV = 16 |
31 | 31 |
32 GLOBAL_HELP = '''Usage: naclsdk [options] command [command_options] | 32 GLOBAL_HELP = '''Usage: naclsdk [options] command [command_options] |
33 | 33 |
34 naclsdk is a simple utility that updates the Native Client (NaCl) | 34 naclsdk is a simple utility that updates the Native Client (NaCl) |
35 Software Developer's Kit (SDK). Each component is kept as a 'bundle' that | 35 Software Developer's Kit (SDK). Each component is kept as a 'bundle' that |
36 this utility can download as as subdirectory into the SDK. | 36 this utility can download as as subdirectory into the SDK. |
37 | 37 |
38 Commands: | 38 Commands: |
39 help [command] - Get either general or command-specific help | 39 help [command] - Get either general or command-specific help |
40 list - Lists the available bundles | 40 list - Lists the available bundles |
41 update/install - Updates/installs bundles in the SDK | 41 update/install - Updates/installs bundles in the SDK |
42 | 42 |
43 Example Usage: | 43 Example Usage: |
44 naclsdk list | 44 naclsdk list |
45 naclsdk update --force pepper_17 | 45 naclsdk update --force pepper_17 |
46 naclsdk install recommended | 46 naclsdk install recommended |
47 naclsdk help update''' | 47 naclsdk help update''' |
48 | 48 |
49 MANIFEST_FILENAME='naclsdk_manifest.json' | 49 MANIFEST_FILENAME='naclsdk_manifest2.json' |
50 SDK_TOOLS='sdk_tools' # the name for this tools directory | 50 SDK_TOOLS='sdk_tools' # the name for this tools directory |
51 USER_DATA_DIR='sdk_cache' | 51 USER_DATA_DIR='sdk_cache' |
52 | 52 |
53 HTTP_CONTENT_LENGTH = 'Content-Length' # HTTP Header field for content length | 53 HTTP_CONTENT_LENGTH = 'Content-Length' # HTTP Header field for content length |
54 | 54 |
55 # The following SSL certificates are used to validate the SSL connection | 55 # The following SSL certificates are used to validate the SSL connection |
56 # to https://commondatastorage.googleapis.com | 56 # to https://commondatastorage.googleapis.com |
57 # TODO(mball): Validate at least one of these certificates. | 57 # TODO(mball): Validate at least one of these certificates. |
58 # http://stackoverflow.com/questions/1087227/validate-ssl-certificates-with-pyth
on | 58 # http://stackoverflow.com/questions/1087227/validate-ssl-certificates-with-pyth
on |
59 | 59 |
(...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
132 STABILITY_LITERALS = [ | 132 STABILITY_LITERALS = [ |
133 'obsolete', 'post_stable', 'stable', 'beta', 'dev', 'canary'] | 133 'obsolete', 'post_stable', 'stable', 'beta', 'dev', 'canary'] |
134 # Valid values for the archive.host_os field | 134 # Valid values for the archive.host_os field |
135 HOST_OS_LITERALS = frozenset(['mac', 'win', 'linux', 'all']) | 135 HOST_OS_LITERALS = frozenset(['mac', 'win', 'linux', 'all']) |
136 # Valid values for bundle-recommended field. | 136 # Valid values for bundle-recommended field. |
137 YES_NO_LITERALS = ['yes', 'no'] | 137 YES_NO_LITERALS = ['yes', 'no'] |
138 # Valid keys for various sdk objects, used for validation. | 138 # Valid keys for various sdk objects, used for validation. |
139 VALID_ARCHIVE_KEYS = frozenset(['host_os', 'size', 'checksum', 'url']) | 139 VALID_ARCHIVE_KEYS = frozenset(['host_os', 'size', 'checksum', 'url']) |
140 VALID_BUNDLES_KEYS = frozenset([ | 140 VALID_BUNDLES_KEYS = frozenset([ |
141 ARCHIVES_KEY, NAME_KEY, VERSION_KEY, REVISION_KEY, | 141 ARCHIVES_KEY, NAME_KEY, VERSION_KEY, REVISION_KEY, |
142 'description', 'desc_url', 'stability', 'recommended', | 142 'description', 'desc_url', 'stability', 'recommended', 'repath', |
143 ]) | 143 ]) |
144 VALID_MANIFEST_KEYS = frozenset(['manifest_version', BUNDLES_KEY]) | 144 VALID_MANIFEST_KEYS = frozenset(['manifest_version', BUNDLES_KEY]) |
145 | 145 |
146 | 146 |
147 #------------------------------------------------------------------------------ | 147 #------------------------------------------------------------------------------ |
148 # General Utilities | 148 # General Utilities |
149 | 149 |
150 | 150 |
151 _debug_mode = False | 151 _debug_mode = False |
152 _quiet_mode = False | 152 _quiet_mode = False |
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
216 RemoveDir(outdir) | 216 RemoveDir(outdir) |
217 | 217 |
218 if os.path.splitext(installer)[1] == '.exe': | 218 if os.path.splitext(installer)[1] == '.exe': |
219 # If the installer has extension 'exe', assume it's a Windows NSIS-style | 219 # If the installer has extension 'exe', assume it's a Windows NSIS-style |
220 # installer that handles silent (/S) and relocated (/D) installs. | 220 # installer that handles silent (/S) and relocated (/D) installs. |
221 command = [installer, '/S', '/D=%s' % outdir] | 221 command = [installer, '/S', '/D=%s' % outdir] |
222 subprocess.check_call(command) | 222 subprocess.check_call(command) |
223 else: | 223 else: |
224 os.mkdir(outdir) | 224 os.mkdir(outdir) |
225 tar_file = None | 225 tar_file = None |
| 226 curpath = os.getcwd() |
226 try: | 227 try: |
227 tar_file = tarfile.open(installer) | 228 tar_file = cygtar.CygTar(installer, 'r', verbose=True) |
228 tar_file.extractall(path=outdir) | 229 if outdir: os.chdir(outdir) |
| 230 tar_file.Extract() |
229 finally: | 231 finally: |
230 if tar_file: | 232 if tar_file: |
231 tar_file.close() | 233 tar_file.Close() |
| 234 os.chdir(curpath) |
232 | 235 |
233 | 236 |
234 def RemoveDir(outdir): | 237 def RemoveDir(outdir): |
235 '''Removes the given directory | 238 '''Removes the given directory |
236 | 239 |
237 On Unix systems, this just runs shutil.rmtree, but on Windows, this doesn't | 240 On Unix systems, this just runs shutil.rmtree, but on Windows, this doesn't |
238 work when the directory contains junctions (as does our SDK installer). | 241 work when the directory contains junctions (as does our SDK installer). |
239 Therefore, on Windows, it runs rmdir /S /Q as a shell command. This always | 242 Therefore, on Windows, it runs rmdir /S /Q as a shell command. This always |
240 does the right thing on Windows. If the directory already didn't exist, | 243 does the right thing on Windows. If the directory already didn't exist, |
241 RemoveDir will return successfully without taking any action. | 244 RemoveDir will return successfully without taking any action. |
242 | 245 |
243 Args: | 246 Args: |
244 outdir: The directory to delete | 247 outdir: The directory to delete |
245 | 248 |
246 Raises: | 249 Raises: |
247 CalledProcessError - if the delete operation fails on Windows | 250 CalledProcessError - if the delete operation fails on Windows |
248 OSError - if the delete operation fails on Linux | 251 OSError - if the delete operation fails on Linux |
249 ''' | 252 ''' |
250 | 253 |
251 DebugPrint('Removing %s' % outdir) | 254 DebugPrint('Removing %s' % outdir) |
252 try: | 255 try: |
253 if sys.platform == 'win32': | 256 shutil.rmtree(outdir) |
254 subprocess.check_call(['rmdir /S /Q', outdir], shell=True) | |
255 else: | |
256 shutil.rmtree(outdir) | |
257 except: | 257 except: |
258 # If the directory is gone anyway, we probably failed because it was | |
259 # already gone. Treat that as success. | |
260 if not os.path.exists(outdir): | 258 if not os.path.exists(outdir): |
261 return | 259 return |
262 # Otherwise, re-raise. | 260 # On Windows this could be an issue with junctions, so try again with rmdir |
263 raise | 261 if sys.platform == 'win32': |
| 262 subprocess.check_call(['rmdir', '/S', '/Q', outdir], shell=True) |
264 | 263 |
265 | 264 |
266 def RenameDir(srcdir, destdir): | 265 def RenameDir(srcdir, destdir): |
267 '''Renames srcdir to destdir. Removes destdir before doing the | 266 '''Renames srcdir to destdir. Removes destdir before doing the |
268 rename if it already exists.''' | 267 rename if it already exists.''' |
269 | 268 |
270 max_tries = 100 | 269 max_tries = 5 |
271 | |
272 for num_tries in xrange(max_tries): | 270 for num_tries in xrange(max_tries): |
273 try: | 271 try: |
274 RemoveDir(destdir) | 272 RemoveDir(destdir) |
275 os.rename(srcdir, destdir) | 273 os.rename(srcdir, destdir) |
276 return | 274 return |
277 except OSError as err: | 275 except OSError as err: |
278 if err.errno != errno.EACCES: | 276 if err.errno != errno.EACCES: |
279 raise err | 277 raise err |
280 # If we are here, we didn't exit due to raised exception, so we are | 278 # If we are here, we didn't exit due to raised exception, so we are |
281 # handling a Windows flaky access error. Sleep one second and try | 279 # handling a Windows flaky access error. Sleep one second and try |
282 # again. | 280 # again. |
283 time.sleep(1) | 281 time.sleep(num_tries + 1) |
284 # end of while loop -- could not RenameDir | 282 # end of while loop -- could not RenameDir |
285 raise Error('Could not RenameDir %s => %s after %d tries.\n' % | 283 raise Error('Could not RenameDir %s => %s after %d tries.\n' % |
286 'Please check that no shells or applications ' | 284 'Please check that no shells or applications ' |
287 'are accessing files in %s.' | 285 'are accessing files in %s.' |
288 % (srcdir, destdir, num_tries, destdir)) | 286 % (srcdir, destdir, num_tries, destdir)) |
289 | 287 |
290 | 288 |
291 def ShowProgress(progress): | 289 def ShowProgress(progress): |
292 ''' A download-progress function used by class Archive. | 290 ''' A download-progress function used by class Archive. |
293 (See DownloadAndComputeHash).''' | 291 (See DownloadAndComputeHash).''' |
(...skipping 313 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
607 | 605 |
608 | 606 |
609 class SDKManifest(object): | 607 class SDKManifest(object): |
610 '''This class contains utilities for manipulation an SDK manifest string | 608 '''This class contains utilities for manipulation an SDK manifest string |
611 | 609 |
612 For ease of unit-testing, this class should not contain any file I/O. | 610 For ease of unit-testing, this class should not contain any file I/O. |
613 ''' | 611 ''' |
614 | 612 |
615 def __init__(self): | 613 def __init__(self): |
616 '''Create a new SDKManifest object with default contents''' | 614 '''Create a new SDKManifest object with default contents''' |
617 self.MANIFEST_VERSION = 1 | 615 self.MANIFEST_VERSION = MAJOR_REV |
618 self._manifest_data = { | 616 self._manifest_data = { |
619 "manifest_version": self.MANIFEST_VERSION, | 617 "manifest_version": self.MANIFEST_VERSION, |
620 "bundles": [], | 618 "bundles": [], |
621 } | 619 } |
622 | 620 |
623 def _ValidateManifest(self): | 621 def _ValidateManifest(self): |
624 '''Validate the Manifest file and raises an exception for problems''' | 622 '''Validate the Manifest file and raises an exception for problems''' |
625 # Validate the manifest top level | 623 # Validate the manifest top level |
626 if self._manifest_data["manifest_version"] > self.MANIFEST_VERSION: | 624 if self._manifest_data["manifest_version"] > self.MANIFEST_VERSION: |
627 raise Error("Manifest version too high: %s" % | 625 raise Error("Manifest version too high: %s" % |
(...skipping 286 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
914 if sha1 != archive['checksum']['sha1']: | 912 if sha1 != archive['checksum']['sha1']: |
915 raise Error("SHA1 checksum mismatch on '%s'. Expected %s but got %s" % | 913 raise Error("SHA1 checksum mismatch on '%s'. Expected %s but got %s" % |
916 (bundle_name, archive['checksum']['sha1'], sha1)) | 914 (bundle_name, archive['checksum']['sha1'], sha1)) |
917 if size != archive['size']: | 915 if size != archive['size']: |
918 raise Error("Size mismatch on Archive. Expected %s but got %s bytes" % | 916 raise Error("Size mismatch on Archive. Expected %s but got %s bytes" % |
919 (archive['size'], size)) | 917 (archive['size'], size)) |
920 InfoPrint('Updating bundle %s to version %s, revision %s' % ( | 918 InfoPrint('Updating bundle %s to version %s, revision %s' % ( |
921 (bundle_name, bundle[VERSION_KEY], bundle[REVISION_KEY]))) | 919 (bundle_name, bundle[VERSION_KEY], bundle[REVISION_KEY]))) |
922 ExtractInstaller(dest_filename, bundle_update_path) | 920 ExtractInstaller(dest_filename, bundle_update_path) |
923 if bundle_name != SDK_TOOLS: | 921 if bundle_name != SDK_TOOLS: |
924 RenameDir(bundle_update_path, bundle_path) | 922 repath = bundle.get('repath', None) |
| 923 if repath: |
| 924 bundle_move_path = os.path.join(bundle_update_path, repath) |
| 925 else: |
| 926 bundle_move_path = bundle_update_path |
| 927 RenameDir(bundle_move_path, bundle_path) |
| 928 if os.path.exists(bundle_update_path): |
| 929 RemoveDir(bundle_update_path) |
925 os.remove(dest_filename) | 930 os.remove(dest_filename) |
926 local_manifest.MergeBundle(bundle) | 931 local_manifest.MergeBundle(bundle) |
927 local_manifest.WriteFile() | 932 local_manifest.WriteFile() |
928 # Test revision numbers, update the bundle accordingly. | 933 # Test revision numbers, update the bundle accordingly. |
929 # TODO(dspringer): The local file should be refreshed from disk each | 934 # TODO(dspringer): The local file should be refreshed from disk each |
930 # iteration thought this loop so that multiple sdk_updates can run at the | 935 # iteration thought this loop so that multiple sdk_updates can run at the |
931 # same time. | 936 # same time. |
932 if local_manifest.BundleNeedsUpdate(bundle): | 937 if local_manifest.BundleNeedsUpdate(bundle): |
933 if (not update_options.force and os.path.exists(bundle_path) and | 938 if (not update_options.force and os.path.exists(bundle_path) and |
934 bundle_name != SDK_TOOLS): | 939 bundle_name != SDK_TOOLS): |
(...skipping 114 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1049 | 1054 |
1050 return 0 # Success | 1055 return 0 # Success |
1051 | 1056 |
1052 | 1057 |
1053 if __name__ == '__main__': | 1058 if __name__ == '__main__': |
1054 try: | 1059 try: |
1055 sys.exit(main(sys.argv[1:])) | 1060 sys.exit(main(sys.argv[1:])) |
1056 except Error as error: | 1061 except Error as error: |
1057 print "Error: %s" % error | 1062 print "Error: %s" % error |
1058 sys.exit(1) | 1063 sys.exit(1) |
OLD | NEW |