| #!/usr/bin/env python |
| # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| # CMD code copied from git_cl.py in depot_tools. |
| |
| import config |
| import cStringIO |
| import download |
| import logging |
| import optparse |
| import os |
| import re |
| import sdk_update_common |
| from sdk_update_common import Error |
| import sys |
| import urllib2 |
| |
| SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) |
| PARENT_DIR = os.path.dirname(SCRIPT_DIR) |
| |
| sys.path.append(os.path.dirname(SCRIPT_DIR)) |
| import manifest_util |
| |
| |
| # Import late so each command script can find our imports |
| import command.info |
| import command.list |
| import command.sources |
| import command.uninstall |
| import command.update |
| |
| # This revision number is autogenerated from the Chrome revision. |
| REVISION = '306188' |
| |
| GSTORE_URL = 'https://storage.googleapis.com/nativeclient-mirror' |
| CONFIG_FILENAME = 'naclsdk_config.json' |
| MANIFEST_FILENAME = 'naclsdk_manifest2.json' |
| DEFAULT_SDK_ROOT = os.path.abspath(PARENT_DIR) |
| USER_DATA_DIR = os.path.join(DEFAULT_SDK_ROOT, 'sdk_cache') |
| |
| |
| def usage(more): |
| def hook(fn): |
| fn.usage_more = more |
| return fn |
| return hook |
| |
| |
| def hide(fn): |
| fn.hide = True |
| return fn |
| |
| |
| def LoadConfig(raise_on_error=False): |
| path = os.path.join(USER_DATA_DIR, CONFIG_FILENAME) |
| cfg = config.Config() |
| if not os.path.exists(path): |
| return cfg |
| |
| try: |
| try: |
| with open(path) as f: |
| file_data = f.read() |
| except IOError as e: |
| raise Error('Unable to read config from "%s".\n %s' % (path, e)) |
| |
| try: |
| cfg.LoadJson(file_data) |
| except Error as e: |
| raise Error('Parsing config file from "%s" failed.\n %s' % (path, e)) |
| return cfg |
| except Error as e: |
| if raise_on_error: |
| raise |
| else: |
| logging.warn(str(e)) |
| |
| return cfg |
| |
| |
| 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)) |
| |
| cfg_json = cfg.ToJson() |
| |
| 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 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)) |
| |
| 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 |
| |
| |
| 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)) |
| |
| try: |
| manifest_json = manifest.GetDataAsString() |
| except Exception as e: |
| raise Error('Error encoding manifest "%s" to JSON.\n %s' % (path, e)) |
| |
| 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 LoadRemoteManifest(url): |
| manifest = manifest_util.SDKManifest() |
| url_stream = None |
| try: |
| manifest_stream = cStringIO.StringIO() |
| 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() |
| |
| 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 LoadCombinedRemoteManifest(default_manifest_url, cfg): |
| manifest = LoadRemoteManifest(default_manifest_url) |
| for source in cfg.sources: |
| manifest.MergeManifest(LoadRemoteManifest(source)) |
| return manifest |
| |
| |
| # Commands ##################################################################### |
| |
| |
| @usage('<bundle names...>') |
| def CMDinfo(parser, args): |
| """display information about a bundle""" |
| options, args = parser.parse_args(args) |
| if not args: |
| parser.error('No bundles given') |
| return 0 |
| cfg = LoadConfig() |
| remote_manifest = LoadCombinedRemoteManifest(options.manifest_url, cfg) |
| command.info.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) |
| command.list.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 bundles that already exist. The bundle will not be ' |
| 'updated if the local revision matches the remote revision.') |
| options, args = parser.parse_args(args) |
| local_manifest = LoadLocalManifest() |
| cfg = LoadConfig() |
| remote_manifest = LoadCombinedRemoteManifest(options.manifest_url, cfg) |
| |
| if not args: |
| args = [command.update.RECOMMENDED] |
| |
| try: |
| delegate = command.update.RealUpdateDelegate(USER_DATA_DIR, |
| DEFAULT_SDK_ROOT, cfg) |
| command.update.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)) |
| |
| return 0 |
| |
| |
| 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) |
| |
| |
| @usage('<bundle names...>') |
| def CMDuninstall(parser, args): |
| """uninstall the given bundles""" |
| _, args = parser.parse_args(args) |
| if not args: |
| parser.error('No bundles given') |
| return 0 |
| local_manifest = LoadLocalManifest() |
| command.uninstall.Uninstall(DEFAULT_SDK_ROOT, local_manifest, args) |
| WriteLocalManifest(local_manifest) |
| return 0 |
| |
| |
| @usage('<bundle names...>') |
| def CMDreinstall(parser, args): |
| """restore the given bundles to their original state |
| |
| Note that if there is an update to a given bundle, reinstall will not |
| automatically update to the newest version. |
| """ |
| _, args = parser.parse_args(args) |
| local_manifest = LoadLocalManifest() |
| |
| if not args: |
| parser.error('No bundles given') |
| return 0 |
| |
| cfg = LoadConfig() |
| try: |
| delegate = command.update.RealUpdateDelegate(USER_DATA_DIR, |
| DEFAULT_SDK_ROOT, cfg) |
| command.update.Reinstall(delegate, local_manifest, args) |
| 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)) |
| |
| return 0 |
| |
| |
| 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) |
| |
| cfg = LoadConfig(True) |
| write_config = False |
| if options.url_to_add: |
| command.sources.AddSource(cfg, options.url_to_add) |
| write_config = True |
| elif options.url_to_remove: |
| command.sources.RemoveSource(cfg, options.url_to_remove) |
| write_config = True |
| elif options.do_list: |
| command.sources.ListSources(cfg) |
| else: |
| parser.print_help() |
| |
| if write_config: |
| WriteConfig(cfg) |
| |
| return 0 |
| |
| |
| def CMDversion(parser, args): |
| """display version information""" |
| _, _ = parser.parse_args(args) |
| print "Native Client SDK Updater, version r%s" % REVISION |
| return 0 |
| |
| |
| 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 |
| |
| |
| def Command(name): |
| return globals().get('CMD' + name, None) |
| |
| |
| def GenUsage(parser, cmd): |
| """Modify an OptParse object with the function's documentation.""" |
| obj = Command(cmd) |
| more = getattr(obj, 'usage_more', '') |
| if cmd == 'help': |
| cmd = '<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' % (cmd, more)) |
| |
| |
| def UpdateSDKTools(options, args): |
| """update the sdk_tools bundle""" |
| |
| local_manifest = LoadLocalManifest() |
| cfg = LoadConfig() |
| remote_manifest = LoadCombinedRemoteManifest(options.manifest_url, cfg) |
| |
| try: |
| delegate = command.update.RealUpdateDelegate(USER_DATA_DIR, |
| DEFAULT_SDK_ROOT, cfg) |
| command.update.UpdateBundleIfNeeded( |
| delegate, |
| remote_manifest, |
| local_manifest, |
| command.update.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): |
| # 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( |
| '-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 |
| |
| fmt = '%(levelname)s:%(message)s' |
| logging.basicConfig(stream=sys.stdout, level=loglevel, format=fmt) |
| |
| # 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 argv: |
| cmd = Command(argv[0]) |
| if cmd: |
| # "fix" the usage and the description now that we know the subcommand. |
| GenUsage(parser, argv[0]) |
| return cmd(parser, argv[1:]) |
| |
| # 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 e: |
| logging.error(str(e)) |
| sys.exit(1) |
| except KeyboardInterrupt: |
| sys.stderr.write('naclsdk: interrupted\n') |
| sys.exit(1) |