Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(26)

Unified Diff: native_client_sdk/src/build_tools/sdk_tools/sdk_update_main.py

Issue 11228013: [NaCl SDK] Refactor sdk_update*. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: windows fixes Created 8 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: native_client_sdk/src/build_tools/sdk_tools/sdk_update_main.py
diff --git a/native_client_sdk/src/build_tools/sdk_tools/sdk_update_main.py b/native_client_sdk/src/build_tools/sdk_tools/sdk_update_main.py
index c16af4a7ba5e8fe41563492e77ec427b42eb5d20..b098b6255a773a3f20db62120bb494bbca9fa2a3 100755
--- a/native_client_sdk/src/build_tools/sdk_tools/sdk_update_main.py
+++ b/native_client_sdk/src/build_tools/sdk_tools/sdk_update_main.py
@@ -3,760 +3,373 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-'''A simple tool to update the Native Client SDK to the latest version'''
+# CMD code copied from git_cl.py in depot_tools.
+import config
import cStringIO
-import cygtar
+import download
import json
-import manifest_util
+import logging
import optparse
import os
-from sdk_update_common import RenameDir, RemoveDir, Error
-import shutil
-import subprocess
+import re
+import sdk_update_common
+from sdk_update_common import Error
import sys
-import tempfile
-# when pylint runs the third_party module is the one from depot_tools
-# pylint: disable=E0611
-from third_party import fancy_urllib
import urllib2
-import urlparse
-
-# pylint: disable=C0301
-#------------------------------------------------------------------------------
-# Constants
+SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
+PARENT_DIR = os.path.dirname(SCRIPT_DIR)
-# This revision number is autogenerated from the Chrome revision.
-REVISION = '{REVISION}'
+sys.path.append(os.path.dirname(SCRIPT_DIR))
+import manifest_util
-GLOBAL_HELP = '''Usage: naclsdk [options] command [command_options]
-naclsdk is a simple utility that updates the Native Client (NaCl)
-Software Developer's Kit (SDK). Each component is kept as a 'bundle' that
-this utility can download as as subdirectory into the SDK.
+# Import late so each command script can find our imports
+# pylint thinks this is commands from the python library.
+import commands
-Commands:
- help [command] - Get either general or command-specific help
- info - Displays information about a bundle
- list - Lists the available bundles
- update/install - Updates/installs bundles in the SDK
- sources - Manage external package sources
+# Module commands has no member... (this is because pylint thinks commands is
+# from the python standard library).
+# pylint: disable=E1101
-Example Usage:
- naclsdk info pepper_canary
- naclsdk list
- naclsdk update --force pepper_17
- naclsdk install recommended
- naclsdk help update
- naclsdk sources --list'''
+# This revision number is autogenerated from the Chrome revision.
+REVISION = '{REVISION}'
+GSTORE_URL = 'https://commondatastorage.googleapis.com/nativeclient-mirror'
CONFIG_FILENAME = 'naclsdk_config.json'
MANIFEST_FILENAME = 'naclsdk_manifest2.json'
-SDK_TOOLS = 'sdk_tools' # the name for this tools directory
-USER_DATA_DIR = 'sdk_cache'
-
-HTTP_CONTENT_LENGTH = 'Content-Length' # HTTP Header field for content length
-
-
-#------------------------------------------------------------------------------
-# General Utilities
-
-
-_debug_mode = False
-_quiet_mode = False
-
+DEFAULT_SDK_ROOT = os.path.abspath(PARENT_DIR)
+USER_DATA_DIR = os.path.join(DEFAULT_SDK_ROOT, 'sdk_cache')
-def DebugPrint(msg):
- '''Display a message to stderr if debug printing is enabled
- Note: This function appends a newline to the end of the string
+def usage(more):
+ def hook(fn):
+ fn.usage_more = more
+ return fn
+ return hook
- Args:
- msg: A string to send to stderr in debug mode'''
- if _debug_mode:
- sys.stderr.write("%s\n" % msg)
- sys.stderr.flush()
+def hide(fn):
+ fn.hide = True
+ return fn
-def InfoPrint(msg):
- '''Display an informational message to stdout if not in quiet mode
-
- Note: This function appends a newline to the end of the string
-
- Args:
- mgs: A string to send to stdio when not in quiet mode'''
- if not _quiet_mode:
- sys.stdout.write("%s\n" % msg)
- sys.stdout.flush()
+def LoadConfig(raise_on_error=False):
+ path = os.path.join(USER_DATA_DIR, CONFIG_FILENAME)
+ if not os.path.exists(path):
+ return config.Config()
-def WarningPrint(msg):
- '''Display an informational message to stderr.
+ try:
+ try:
+ with open(path) as f:
+ return config.Config(json.loads(f.read()))
+ except IOError as e:
+ raise Error('Unable to read config from "%s".\n %s' % (path, e))
+ except Exception as e:
+ raise Error('Parsing config file from "%s" failed.\n %s' % (path, e))
+ except Error as e:
+ if raise_on_error:
+ raise
+ else:
+ logging.warn(str(e))
+ return config.Config()
- Note: This function appends a newline to the end of the string
- Args:
- mgs: A string to send to stderr.'''
- sys.stderr.write("WARNING: %s\n" % msg)
- sys.stderr.flush()
+def WriteConfig(cfg):
+ path = os.path.join(USER_DATA_DIR, CONFIG_FILENAME)
+ try:
+ sdk_update_common.MakeDirs(USER_DATA_DIR)
+ except Exception as e:
+ raise Error('Unable to create directory "%s".\n %s' % (USER_DATA_DIR, e))
+ try:
+ cfg_json = cfg.ToJson()
+ except Exception as e:
+ raise Error('Json encoding error writing config "%s".\n %s' % (path, e))
-def UrlOpen(url):
- request = fancy_urllib.FancyRequest(url)
- ca_certs = os.path.join(os.path.dirname(os.path.abspath(__file__)),
- 'cacerts.txt')
- request.set_ssl_info(ca_certs=ca_certs)
- url_opener = urllib2.build_opener(
- fancy_urllib.FancyProxyHandler(),
- fancy_urllib.FancyRedirectHandler(),
- fancy_urllib.FancyHTTPSHandler())
- return url_opener.open(request)
+ try:
+ with open(path, 'w') as f:
+ f.write(cfg_json)
+ except IOError as e:
+ raise Error('Unable to write config to "%s".\n %s' % (path, e))
-def ExtractInstaller(installer, outdir):
- '''Extract the SDK installer into a given directory
- If the outdir already exists, then this function deletes it
+def LoadLocalManifest(raise_on_error=False):
+ path = os.path.join(USER_DATA_DIR, MANIFEST_FILENAME)
+ manifest = manifest_util.SDKManifest()
+ try:
+ try:
+ with open(path) as f:
+ manifest_string = f.read()
+ except IOError as e:
+ raise Error('Unable to read manifest from "%s".\n %s' % (path, e))
- Args:
- installer: full path of the SDK installer
- outdir: output directory where to extract the installer
+ try:
+ manifest.LoadDataFromString(manifest_string)
+ except Exception as e:
+ raise Error('Parsing local manifest "%s" failed.\n %s' % (path, e))
+ except Error as e:
+ if raise_on_error:
+ raise
+ else:
+ logging.warn(str(e))
+ return manifest
- Raises:
- CalledProcessError - if the extract operation fails'''
- RemoveDir(outdir)
- if os.path.splitext(installer)[1] == '.exe':
- # If the installer has extension 'exe', assume it's a Windows NSIS-style
- # installer that handles silent (/S) and relocated (/D) installs.
- command = [installer, '/S', '/D=%s' % outdir]
- subprocess.check_call(command)
- else:
- os.mkdir(outdir)
- tar_file = None
- curpath = os.getcwd()
- try:
- tar_file = cygtar.CygTar(installer, 'r', verbose=True)
- if outdir:
- os.chdir(outdir)
- tar_file.Extract()
- finally:
- if tar_file:
- tar_file.Close()
- os.chdir(curpath)
-
-
-class ProgressFunction(object):
- '''Create a progress function for a file with a given size'''
-
- def __init__(self, file_size=0):
- '''Constructor
-
- Args:
- file_size: number of bytes in file. 0 indicates unknown'''
- self.dots = 0
- self.file_size = int(file_size)
-
- def GetProgressFunction(self):
- '''Returns a progress function based on a known file size'''
- def ShowKnownProgress(progress):
- if progress == 0:
- sys.stdout.write('|%s|\n' % ('=' * 48))
- else:
- new_dots = progress * 50 / self.file_size - self.dots
- sys.stdout.write('.' * new_dots)
- self.dots += new_dots
- if progress == self.file_size:
- sys.stdout.write('\n')
- sys.stdout.flush()
-
- return ShowKnownProgress
-
-
-def DownloadArchiveToFile(archive, dest_path):
- '''Download the archive's data to a file at dest_path.
-
- As a side effect, computes the sha1 hash and data size, both returned as a
- tuple. Raises an Error if the url can't be opened, or an IOError exception if
- dest_path can't be opened.
-
- Args:
- dest_path: Path for the file that will receive the data.
- Return:
- A tuple (sha1, size) with the sha1 hash and data size respectively.'''
- sha1 = None
- size = 0
- with open(dest_path, 'wb') as to_stream:
- from_stream = None
- try:
- from_stream = UrlOpen(archive.url)
- except urllib2.URLError:
- raise Error('Cannot open "%s" for archive %s' %
- (archive.url, archive.host_os))
- try:
- content_length = int(from_stream.info()[HTTP_CONTENT_LENGTH])
- progress_function = ProgressFunction(content_length).GetProgressFunction()
- InfoPrint('Downloading %s' % archive.url)
- sha1, size = manifest_util.DownloadAndComputeHash(
- from_stream,
- to_stream=to_stream,
- progress_func=progress_function)
- if size != content_length:
- raise Error('Download size mismatch for %s.\n'
- 'Expected %s bytes but got %s' %
- (archive.url, content_length, size))
- finally:
- if from_stream:
- from_stream.close()
- return sha1, size
-
-
-def LoadFromFile(path, obj):
- '''Returns a manifest loaded from the JSON file at |path|.
-
- If the path does not exist or is invalid, returns unmodified object.'''
- methodlist = [m for m in dir(obj) if callable(getattr(obj, m))]
- if 'LoadDataFromString' not in methodlist:
- return obj
- if not os.path.exists(path):
- return obj
+def WriteLocalManifest(manifest):
+ path = os.path.join(USER_DATA_DIR, MANIFEST_FILENAME)
+ try:
+ sdk_update_common.MakeDirs(USER_DATA_DIR)
+ except Exception as e:
+ raise Error('Unable to create directory "%s".\n %s' % (USER_DATA_DIR, e))
- with open(path, 'r') as f:
- json_string = f.read()
- if not json_string:
- return obj
+ try:
+ manifest_json = manifest.GetDataAsString()
+ except Exception as e:
+ raise Error('Error encoding manifest "%s" to JSON.\n %s' % (path, e))
- obj.LoadDataFromString(json_string)
- return obj
+ try:
+ with open(path, 'w') as f:
+ f.write(manifest_json)
+ except IOError as e:
+ raise Error('Unable to write manifest to "%s".\n %s' % (path, e))
-def LoadManifestFromURLs(urls):
- '''Returns a manifest loaded from |urls|, merged into one manifest.'''
+def LoadRemoteManifest(url):
manifest = manifest_util.SDKManifest()
- for url in urls:
- try:
- url_stream = UrlOpen(url)
- except urllib2.URLError as e:
- raise Error('Unable to open %s. [%s]' % (url, e))
-
+ url_stream = None
+ try:
manifest_stream = cStringIO.StringIO()
- manifest_util.DownloadAndComputeHash(url_stream, manifest_stream)
- temp_manifest = manifest_util.SDKManifest()
- temp_manifest.LoadDataFromString(manifest_stream.getvalue())
+ url_stream = download.UrlOpen(url)
+ download.DownloadAndComputeHash(url_stream, manifest_stream)
+ except urllib2.URLError as e:
+ raise Error('Unable to read remote manifest from URL "%s".\n %s' % (
+ url, e))
+ finally:
+ if url_stream:
+ url_stream.close()
- manifest.MergeManifest(temp_manifest)
+ try:
+ manifest.LoadDataFromString(manifest_stream.getvalue())
+ return manifest
+ except manifest_util.Error as e:
+ raise Error('Parsing remote manifest from URL "%s" failed.\n %s' % (
+ url, e,))
- def BundleFilter(bundle):
- # Only add this bundle if it's supported on this platform.
- return bundle.GetHostOSArchive()
- manifest.FilterBundles(BundleFilter)
+def LoadCombinedRemoteManifest(default_manifest_url, cfg):
+ manifest = LoadRemoteManifest(default_manifest_url)
+ for source in cfg.sources:
+ manifest.MergeManifest(LoadRemoteManifest(source))
return manifest
-def WriteToFile(path, obj):
- '''Write |manifest| to a JSON file at |path|.'''
- methodlist = [m for m in dir(obj) if callable(getattr(obj, m))]
- if 'GetDataAsString' not in methodlist:
- raise Error('Unable to write object to file')
- json_string = obj.GetDataAsString()
-
- # Write the JSON data to a temp file.
- temp_file_name = None
- # TODO(dspringer): Use file locks here so that multiple sdk_updates can
- # run at the same time.
- with tempfile.NamedTemporaryFile(mode='w', delete=False) as f:
- f.write(json_string)
- temp_file_name = f.name
- # Move the temp file to the actual file.
- if os.path.exists(path):
- os.remove(path)
- shutil.move(temp_file_name, path)
-
-
-class SDKConfig(object):
- '''This class contains utilities for manipulating an SDK config
- '''
-
- def __init__(self):
- '''Create a new SDKConfig object with default contents'''
- self._data = {
- 'sources': [],
- }
-
- def AddSource(self, string):
- '''Add a source file to load packages from.
-
- Args:
- string: a URL to an external package manifest file.'''
- # For now whitelist only the following location for external sources:
- # https://commondatastorage.googleapis.com/nativeclient-mirror/nacl/nacl_sdk
- (scheme, host, path, _, _, _) = urlparse.urlparse(string)
- if (host != 'commondatastorage.googleapis.com' or
- scheme != 'https' or
- not path.startswith('/nativeclient-mirror/nacl/nacl_sdk')):
- WarningPrint('Only whitelisted sources from '
- '\'https://commondatastorage.googleapis.com/nativeclient-'
- 'mirror/nacl/nacl_sdk\' are currently allowed.')
- return
- if string in self._data['sources']:
- WarningPrint('source \''+string+'\' already exists in config.')
- return
- try:
- UrlOpen(string)
- except urllib2.URLError:
- WarningPrint('Unable to fetch manifest URL \'%s\'. Exiting...' % string)
- return
-
- self._data['sources'].append(string)
- InfoPrint('source \''+string+'\' added to config.')
-
- def RemoveSource(self, string):
- '''Remove a source file to load packages from.
+# Commands #####################################################################
- Args:
- string: a URL to an external SDK manifest file.'''
- if string not in self._data['sources']:
- WarningPrint('source \''+string+'\' doesn\'t exist in config.')
- else:
- self._data['sources'].remove(string)
- InfoPrint('source \''+string+'\' removed from config.')
-
- def RemoveAllSources(self):
- if len(self.GetSources()) == 0:
- InfoPrint('There are no external sources to remove.')
- # Copy the list because RemoveSource modifies the underlying list
- sources = list(self.GetSources())
- for source in sources:
- self.RemoveSource(source)
-
-
- def ListSources(self):
- '''List all external sources in config.'''
- if len(self._data['sources']):
- InfoPrint('Installed sources:')
- for s in self._data['sources']:
- InfoPrint(' '+s)
- else:
- InfoPrint('No external sources installed')
-
- def GetSources(self):
- '''Return a list of external sources'''
- return self._data['sources']
- def LoadDataFromString(self, string):
- ''' Load a JSON config string. Raises an exception if string
- is not well-formed JSON.
+@usage('<bundle names...>')
+def CMDinfo(parser, args):
+ """display information about a bundle"""
+ options, args = parser.parse_args(args)
+ if len(args) == 0:
+ parser.error('No bundles given')
+ return 0
+ cfg = LoadConfig()
+ remote_manifest = LoadCombinedRemoteManifest(options.manifest_url, cfg)
+ commands.Info(remote_manifest, args)
+ return 0
+
+
+def CMDlist(parser, args):
+ """list all available bundles"""
+ parser.add_option('-r', '--revision', action='store_true',
+ help='display revision numbers')
+ options, args = parser.parse_args(args)
+ if args:
+ parser.error('Unsupported argument(s): %s' % ', '.join(args))
+ local_manifest = LoadLocalManifest()
+ cfg = LoadConfig()
+ remote_manifest = LoadCombinedRemoteManifest(options.manifest_url, cfg)
+ commands.List(remote_manifest, local_manifest, options.revision)
+ return 0
+
+
+@usage('<bundle names...>')
+def CMDupdate(parser, args):
+ """update a bundle in the SDK to the latest version"""
+ parser.add_option(
+ '-F', '--force', action='store_true',
+ help='Force updating existing components that already exist')
+ options, args = parser.parse_args(args)
+ local_manifest = LoadLocalManifest()
+ cfg = LoadConfig()
+ remote_manifest = LoadCombinedRemoteManifest(options.manifest_url, cfg)
- Args:
- string: a JSON-formatted string containing the previous config'''
- self._data = json.loads(string)
+ if not args:
+ args = [commands.RECOMMENDED]
+ try:
+ delegate = commands.RealUpdateDelegate(USER_DATA_DIR, DEFAULT_SDK_ROOT)
+ commands.Update(delegate, remote_manifest, local_manifest, args,
+ options.force)
+ finally:
+ # Always write out the local manifest, we may have successfully updated one
+ # or more bundles before failing.
+ try:
+ WriteLocalManifest(local_manifest)
+ except Error as e:
+ # Log the error writing to the manifest, but propagate the original
+ # exception.
+ logging.error(str(e))
- def GetDataAsString(self):
- '''Returns the current JSON manifest object, pretty-printed'''
- pretty_string = json.dumps(self._data, sort_keys=False, indent=2)
- # json.dumps sometimes returns trailing whitespace and does not put
- # a newline at the end. This code fixes these problems.
- pretty_lines = pretty_string.split('\n')
- return '\n'.join([line.rstrip() for line in pretty_lines]) + '\n'
+ return 0
-#------------------------------------------------------------------------------
-# Commands
+def CMDinstall(parser, args):
+ """install a bundle in the SDK"""
+ # For now, forward to CMDupdate. We may want different behavior for this
+ # in the future, though...
+ return CMDupdate(parser, args)
-def Info(options, argv, config):
- '''Usage: %prof [global_options] info [options] bundle_names...
+def CMDsources(parser, args):
+ """manage external package sources"""
+ parser.add_option('-a', '--add', dest='url_to_add',
+ help='Add an additional package source')
+ parser.add_option(
+ '-r', '--remove', dest='url_to_remove',
+ help='Remove package source (use \'all\' for all additional sources)')
+ parser.add_option('-l', '--list', dest='do_list', action='store_true',
+ help='List additional package sources')
+ options, args = parser.parse_args(args)
- Displays information about a SDK bundle.'''
+ cfg = LoadConfig(True)
+ write_config = False
+ if options.url_to_add:
+ commands.AddSource(cfg, options.url_to_add)
+ write_config = True
+ elif options.url_to_remove:
+ commands.RemoveSource(cfg, options.url_to_remove)
+ write_config = True
+ elif options.do_list:
+ commands.ListSources(cfg)
+ else:
+ parser.print_help()
- DebugPrint("Running List command with: %s, %s" %(options, argv))
+ if write_config:
+ WriteConfig(cfg)
- parser = optparse.OptionParser(usage=Info.__doc__)
- (_, args) = parser.parse_args(argv)
+ return 0
- if not args:
- parser.print_help()
- return
-
- manifest = LoadManifestFromURLs([options.manifest_url] + config.GetSources())
- valid_bundles = [bundle.name for bundle in manifest.GetBundles()]
- valid_args = set(args) & set(valid_bundles)
- invalid_args = set(args) - valid_args
- if invalid_args:
- InfoPrint('Unknown bundle(s): %s\n' % (', '.join(invalid_args)))
-
- for bundle_name in args:
- if bundle_name not in valid_args:
- continue
-
- bundle = manifest.GetBundle(bundle_name)
-
- InfoPrint('%s' % bundle.name)
- for key, value in bundle.iteritems():
- if key == manifest_util.ARCHIVES_KEY:
- archive = bundle.GetHostOSArchive()
- InfoPrint(' Archive:')
- for archive_key, archive_value in archive.iteritems():
- InfoPrint(' %s: %s' % (archive_key, archive_value))
- elif key not in (manifest_util.ARCHIVES_KEY, manifest_util.NAME_KEY):
- InfoPrint(' %s: %s' % (key, value))
- InfoPrint('')
-
-
-def List(options, argv, config):
- '''Usage: %prog [global_options] list [options]
-
- Lists the available SDK bundles that are available for download.'''
-
- def PrintBundle(local_bundle, bundle, needs_update, display_revisions):
- installed = local_bundle is not None
- # If bundle is None, there is no longer a remote bundle with this name.
- if bundle is None:
- bundle = local_bundle
-
- if display_revisions:
- if needs_update:
- revision = ' (r%s -> r%s)' % (local_bundle.revision, bundle.revision)
- else:
- revision = ' (r%s)' % (bundle.revision,)
- else:
- revision = ''
- InfoPrint(' %s%s %s (%s)%s' % (
- 'I' if installed else ' ',
- '*' if needs_update else ' ',
- bundle.name,
- bundle.stability,
- revision))
+def CMDversion(parser, args):
+ """display version information"""
+ _, _ = parser.parse_args(args)
+ print "Native Client SDK Updater, version r%s" % REVISION
+ return 0
- DebugPrint("Running List command with: %s, %s" %(options, argv))
+def CMDhelp(parser, args):
+ """print list of commands or help for a specific command"""
+ _, args = parser.parse_args(args)
+ if len(args) == 1:
+ return main(args + ['--help'])
+ parser.print_help()
+ return 0
- parser = optparse.OptionParser(usage=List.__doc__)
- parser.add_option(
- '-r', '--revision', dest='revision',
- default=False, action='store_true',
- help='display revision numbers')
- (list_options, _) = parser.parse_args(argv)
-
- manifest = LoadManifestFromURLs([options.manifest_url] + config.GetSources())
- manifest_path = os.path.join(options.user_data_dir, options.manifest_filename)
- local_manifest = LoadFromFile(manifest_path, manifest_util.SDKManifest())
-
- any_bundles_need_update = False
- InfoPrint('Bundles:')
- InfoPrint(' I: installed\n *: update available\n')
- for bundle in manifest.GetBundles():
- local_bundle = local_manifest.GetBundle(bundle.name)
- needs_update = local_bundle and local_manifest.BundleNeedsUpdate(bundle)
- if needs_update:
- any_bundles_need_update = True
-
- PrintBundle(local_bundle, bundle, needs_update, list_options.revision)
-
- if not any_bundles_need_update:
- InfoPrint('\nAll installed bundles are up-to-date.')
-
- local_only_bundles = set([b.name for b in local_manifest.GetBundles()])
- local_only_bundles -= set([b.name for b in manifest.GetBundles()])
- if local_only_bundles:
- InfoPrint('\nBundles installed locally that are not available remotely:')
- for bundle_name in local_only_bundles:
- local_bundle = local_manifest.GetBundle(bundle_name)
- PrintBundle(local_bundle, None, False, list_options.revision)
-
-
-def Update(options, argv, config):
- '''Usage: %prog [global_options] update [options] [target]
-
- Updates the Native Client SDK to a specified version. By default, this
- command updates all the recommended components. The update process works
- like this:
- 1. Fetch the manifest from the mirror.
- 2. Load manifest from USER_DATA_DIR - if there is no local manifest file,
- make an empty manifest object.
- 3. Update each the bundle:
- for bundle in bundles:
- # Compare bundle versions & revisions.
- # Test if local version.revision < mirror OR local doesn't exist.
- if local_manifest < mirror_manifest:
- update(bundle)
- update local_manifest with mirror_manifest for bundle
- write manifest to disk. Use locks.
- else:
- InfoPrint('bundle is up-to-date')
-
- Targets:
- recommended: (default) Install/Update all recommended components
- all: Install/Update all available components
- bundle_name: Install/Update only the given bundle
- '''
- DebugPrint("Running Update command with: %s, %s" % (options, argv))
- ALL = 'all' # Update all bundles
- RECOMMENDED = 'recommended' # Only update the bundles with recommended=yes
-
- parser = optparse.OptionParser(usage=Update.__doc__)
- parser.add_option(
- '-F', '--force', dest='force',
- default=False, action='store_true',
- help='Force updating existing components that already exist')
- (update_options, args) = parser.parse_args(argv)
- if len(args) == 0:
- args = [RECOMMENDED]
-
- manifest = LoadManifestFromURLs([options.manifest_url] + config.GetSources())
- bundles = manifest.GetBundles()
- local_manifest_path = os.path.join(options.user_data_dir,
- options.manifest_filename)
- local_manifest = LoadFromFile(local_manifest_path,
- manifest_util.SDKManifest())
-
- # Validate the arg list against the available bundle names. Raises an
- # error if any invalid bundle names or args are detected.
- valid_args = set([ALL, RECOMMENDED] + [bundle.name for bundle in bundles])
- bad_args = set(args) - valid_args
- if len(bad_args) > 0:
- raise Error("Unrecognized bundle name or argument: '%s'" %
- ', '.join(bad_args))
-
- if SDK_TOOLS in args and not options.update_sdk_tools:
- # We only want sdk_tools to be updated by sdk_update.py. If the user
- # tries to update directly, we just ignore the request.
- InfoPrint('Updating sdk_tools happens automatically.\n'
- 'Ignoring manual update request.')
- args.remove(SDK_TOOLS)
-
- for bundle in bundles:
- bundle_path = os.path.join(options.sdk_root_dir, bundle.name)
- bundle_update_path = '%s_update' % bundle_path
- if not (bundle.name in args or
- ALL in args or (RECOMMENDED in args and
- bundle[RECOMMENDED] == 'yes')):
- continue
-
- if bundle.name == SDK_TOOLS and not options.update_sdk_tools:
- continue
-
- def UpdateBundle():
- '''Helper to install a bundle'''
- archive = bundle.GetHostOSArchive()
- (_, _, path, _, _, _) = urlparse.urlparse(archive['url'])
- dest_filename = os.path.join(options.user_data_dir, path.split('/')[-1])
- sha1, size = DownloadArchiveToFile(archive, dest_filename)
- if sha1 != archive.GetChecksum():
- raise Error("SHA1 checksum mismatch on '%s'. Expected %s but got %s" %
- (bundle.name, archive.GetChecksum(), sha1))
- if size != archive.size:
- raise Error("Size mismatch on Archive. Expected %s but got %s bytes" %
- (archive.size, size))
- InfoPrint('Updating bundle %s to version %s, revision %s' % (
- (bundle.name, bundle.version, bundle.revision)))
- ExtractInstaller(dest_filename, bundle_update_path)
- if bundle.name != SDK_TOOLS:
- repath = bundle.get('repath', None)
- if repath:
- bundle_move_path = os.path.join(bundle_update_path, repath)
- else:
- bundle_move_path = bundle_update_path
- RenameDir(bundle_move_path, bundle_path)
- if os.path.exists(bundle_update_path):
- RemoveDir(bundle_update_path)
- os.remove(dest_filename)
- local_manifest.MergeBundle(bundle)
- WriteToFile(local_manifest_path, local_manifest)
- # Test revision numbers, update the bundle accordingly.
- # TODO(dspringer): The local file should be refreshed from disk each
- # iteration thought this loop so that multiple sdk_updates can run at the
- # same time.
- if local_manifest.BundleNeedsUpdate(bundle):
- if (not update_options.force and os.path.exists(bundle_path) and
- bundle.name != SDK_TOOLS):
- WarningPrint('%s already exists, but has an update available.\n'
- 'Run update with the --force option to overwrite the '
- 'existing directory.\nWarning: This will overwrite any '
- 'modifications you have made within this directory.'
- % bundle.name)
- else:
- UpdateBundle()
- else:
- InfoPrint('%s is already up-to-date.' % bundle.name)
+def Command(name):
+ return globals().get('CMD' + name, None)
-def Sources(options, argv, config):
- '''Usage: %prog [global_options] sources [options] [--list,--add URL,--remove URL]
- Manage additional package sources. URL should point to a valid package
- manifest file for download.
- '''
- DebugPrint("Running Sources command with: %s, %s" % (options, argv))
+def GenUsage(parser, command):
+ """Modify an OptParse object with the function's documentation."""
+ obj = Command(command)
+ more = getattr(obj, 'usage_more', '')
+ if command == 'help':
+ command = '<command>'
+ else:
+ # OptParser.description prefer nicely non-formatted strings.
+ parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
+ parser.set_usage('usage: %%prog %s [options] %s' % (command, more))
- parser = optparse.OptionParser(usage=Sources.__doc__)
- parser.add_option(
- '-a', '--add', dest='url_to_add',
- default=None,
- help='Add additional package source')
- parser.add_option(
- '-r', '--remove', dest='url_to_remove',
- default=None,
- help='Remove package source (use \'all\' for all additional sources)')
- parser.add_option(
- '-l', '--list', dest='do_list',
- default=False, action='store_true',
- help='List additional package sources')
- source_options, _ = parser.parse_args(argv)
- write_config = False
- if source_options.url_to_add:
- config.AddSource(source_options.url_to_add)
- write_config = True
- elif source_options.url_to_remove:
- if source_options.url_to_remove == 'all':
- config.RemoveAllSources()
- else:
- config.RemoveSource(source_options.url_to_remove)
- write_config = True
- elif source_options.do_list:
- config.ListSources()
- else:
- parser.print_help()
+def UpdateSDKTools(options, args):
+ """update the sdk_tools bundle"""
- if write_config:
- WriteToFile(os.path.join(options.user_data_dir, options.config_filename),
- config)
+ local_manifest = LoadLocalManifest()
+ cfg = LoadConfig()
+ remote_manifest = LoadCombinedRemoteManifest(options.manifest_url, cfg)
-#------------------------------------------------------------------------------
-# Command-line interface
+ try:
+ delegate = commands.RealUpdateDelegate(USER_DATA_DIR, DEFAULT_SDK_ROOT)
+ commands.UpdateBundleIfNeeded(delegate, remote_manifest, local_manifest,
+ commands.SDK_TOOLS, force=True)
+ finally:
+ # Always write out the local manifest, we may have successfully updated one
+ # or more bundles before failing.
+ WriteLocalManifest(local_manifest)
+ return 0
def main(argv):
- '''Main entry for the sdk_update utility'''
- parser = optparse.OptionParser(usage=GLOBAL_HELP, add_help_option=False)
- DEFAULT_SDK_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
-
- # Manually add help options so we can ignore it when auto-updating.
- parser.add_option(
- '-h', '--help', dest='help', action='store_true',
- help='show this help message and exit')
- parser.add_option(
- '-U', '--manifest-url', dest='manifest_url',
- default='https://commondatastorage.googleapis.com/nativeclient-mirror/'
- 'nacl/nacl_sdk/%s' % MANIFEST_FILENAME,
- help='override the default URL for the NaCl manifest file')
+ # Get all commands...
+ cmds = [fn[3:] for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]
+ # Remove hidden commands...
+ cmds = filter(lambda fn: not getattr(Command(fn), 'hide', 0), cmds)
+ # Format for CMDhelp usage.
+ CMDhelp.usage_more = ('\n\nCommands are:\n' + '\n'.join([
+ ' %-10s %s' % (fn, Command(fn).__doc__.split('\n')[0].strip())
+ for fn in cmds]))
+
+ # Create the option parse and add --verbose support.
+ parser = optparse.OptionParser()
parser.add_option(
- '-d', '--debug', dest='debug',
- default=False, action='store_true',
- help='enable displaying debug information to stderr')
- parser.add_option(
- '-q', '--quiet', dest='quiet',
- default=False, action='store_true',
- help='suppress displaying informational prints to stdout')
- parser.add_option(
- '-u', '--user-data-dir', dest='user_data_dir',
- # TODO(mball): the default should probably be in something like
- # ~/.naclsdk (linux), or ~/Library/Application Support/NaClSDK (mac),
- # or %HOMEPATH%\Application Data\NaClSDK (i.e., %APPDATA% on windows)
- default=os.path.join(DEFAULT_SDK_ROOT, USER_DATA_DIR),
- help="specify location of NaCl SDK's data directory")
- parser.add_option(
- '-s', '--sdk-root-dir', dest='sdk_root_dir',
- default=DEFAULT_SDK_ROOT,
- help="location where the SDK bundles are installed")
- parser.add_option(
- '-v', '--version', dest='show_version',
- action='store_true',
- help='show version information and exit')
- parser.add_option(
- '-m', '--manifest', dest='manifest_filename',
- default=MANIFEST_FILENAME,
- help="name of local manifest file relative to user-data-dir")
- parser.add_option(
- '-c', '--config', dest='config_filename',
- default=CONFIG_FILENAME,
- help="name of the local config file relative to user-data-dir")
- parser.add_option(
- '--update-sdk-tools', dest='update_sdk_tools',
- default=False, action='store_true')
-
-
- COMMANDS = {
- 'info': Info,
- 'list': List,
- 'update': Update,
- 'install': Update,
- 'sources': Sources,
- }
-
- # Separate global options from command-specific options
- global_argv = argv
- command_argv = []
- for index, arg in enumerate(argv):
- if arg in COMMANDS:
- global_argv = argv[:index]
- command_argv = argv[index:]
- break
-
- (options, args) = parser.parse_args(global_argv)
- args += command_argv
-
- global _debug_mode, _quiet_mode
- _debug_mode = options.debug
- _quiet_mode = options.quiet
-
- def PrintHelpAndExit(unused_options=None, unused_args=None):
- parser.print_help()
- exit(1)
+ '-v', '--verbose', action='count', default=0,
+ help='Use 2 times for more debugging info')
+ parser.add_option('-U', '--manifest-url', dest='manifest_url',
+ default=GSTORE_URL + '/nacl/nacl_sdk/' + MANIFEST_FILENAME,
+ metavar='URL', help='override the default URL for the NaCl manifest file')
+ parser.add_option('--update-sdk-tools', action='store_true',
+ dest='update_sdk_tools', help=optparse.SUPPRESS_HELP)
+
+ old_parser_args = parser.parse_args
+ def Parse(args):
+ options, args = old_parser_args(args)
+ if options.verbose >= 2:
+ loglevel = logging.DEBUG
+ elif options.verbose:
+ loglevel = logging.INFO
+ else:
+ loglevel = logging.WARNING
- if options.update_sdk_tools:
- # Ignore all other commands, and just update the sdk tools.
- args = ['update', 'sdk_tools']
- # Leave the rest of the options alone -- they may be needed to update
- # correctly.
- options.show_version = False
- options.sdk_root_dir = DEFAULT_SDK_ROOT
+ fmt = '%(levelname)s:%(message)s'
+ logging.basicConfig(stream=sys.stdout, level=loglevel, format=fmt)
- if options.show_version:
- print "Native Client SDK Updater, version r%s" % (REVISION,)
- exit(0)
+ # If --update-sdk-tools is passed, circumvent any other command running.
+ if options.update_sdk_tools:
+ UpdateSDKTools(options, args)
+ sys.exit(1)
+ return options, args
+ parser.parse_args = Parse
- if not args:
- print "Need to supply a command"
- PrintHelpAndExit()
-
- if options.help:
- PrintHelpAndExit()
-
- def DefaultHandler(unused_options=None, unused_args=None, unused_config=None):
- print "Unknown Command: %s" % args[0]
- PrintHelpAndExit()
-
- def InvokeCommand(args):
- command = COMMANDS.get(args[0], DefaultHandler)
- # Load the config file before running commands
- config = LoadFromFile(os.path.join(options.user_data_dir,
- options.config_filename),
- SDKConfig())
- command(options, args[1:], config)
-
- if args[0] == 'help':
- if len(args) == 1:
- PrintHelpAndExit()
- else:
- InvokeCommand([args[1], '-h'])
- else:
- # Make sure the user_data_dir exists.
- if not os.path.exists(options.user_data_dir):
- os.makedirs(options.user_data_dir)
- InvokeCommand(args)
+ if argv:
+ command = Command(argv[0])
+ if command:
+ # "fix" the usage and the description now that we know the subcommand.
+ GenUsage(parser, argv[0])
+ return command(parser, argv[1:])
- return 0 # Success
+ # Not a known command. Default to help.
+ GenUsage(parser, 'help')
+ return CMDhelp(parser, argv)
if __name__ == '__main__':
try:
sys.exit(main(sys.argv[1:]))
- except Error as error:
- print "Error: %s" % error
+ except Error as e:
+ logging.error(str(e))
sys.exit(1)

Powered by Google App Engine
This is Rietveld 408576698