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
4 # found in the LICENSE file.
6 # CMD code copied from git_cl.py in depot_tools.
15 import sdk_update_common
16 from sdk_update_common import Error
20 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
21 PARENT_DIR = os.path.dirname(SCRIPT_DIR)
23 sys.path.append(os.path.dirname(SCRIPT_DIR))
27 # Import late so each command script can find our imports
30 import command.sources
31 import command.uninstall
34 # This revision number is autogenerated from the Chrome revision.
35 REVISION = '{REVISION}'
37 GSTORE_URL = 'https://commondatastorage.googleapis.com/nativeclient-mirror'
38 CONFIG_FILENAME = 'naclsdk_config.json'
39 MANIFEST_FILENAME = 'naclsdk_manifest2.json'
40 DEFAULT_SDK_ROOT = os.path.abspath(PARENT_DIR)
41 USER_DATA_DIR = os.path.join(DEFAULT_SDK_ROOT, 'sdk_cache')
56 def LoadConfig(raise_on_error=False):
57 path = os.path.join(USER_DATA_DIR, CONFIG_FILENAME)
59 if not os.path.exists(path):
67 raise Error('Unable to read config from "%s".\n %s' % (path, e))
70 cfg.LoadJson(file_data)
72 raise Error('Parsing config file from "%s" failed.\n %s' % (path, e))
84 path = os.path.join(USER_DATA_DIR, CONFIG_FILENAME)
86 sdk_update_common.MakeDirs(USER_DATA_DIR)
87 except Exception as e:
88 raise Error('Unable to create directory "%s".\n %s' % (USER_DATA_DIR, e))
90 cfg_json = cfg.ToJson()
93 with open(path, 'w') as f:
96 raise Error('Unable to write config to "%s".\n %s' % (path, e))
99 def LoadLocalManifest(raise_on_error=False):
100 path = os.path.join(USER_DATA_DIR, MANIFEST_FILENAME)
101 manifest = manifest_util.SDKManifest()
104 with open(path) as f:
105 manifest_string = f.read()
107 raise Error('Unable to read manifest from "%s".\n %s' % (path, e))
110 manifest.LoadDataFromString(manifest_string)
111 except Exception as e:
112 raise Error('Parsing local manifest "%s" failed.\n %s' % (path, e))
121 def WriteLocalManifest(manifest):
122 path = os.path.join(USER_DATA_DIR, MANIFEST_FILENAME)
124 sdk_update_common.MakeDirs(USER_DATA_DIR)
125 except Exception as e:
126 raise Error('Unable to create directory "%s".\n %s' % (USER_DATA_DIR, e))
129 manifest_json = manifest.GetDataAsString()
130 except Exception as e:
131 raise Error('Error encoding manifest "%s" to JSON.\n %s' % (path, e))
134 with open(path, 'w') as f:
135 f.write(manifest_json)
137 raise Error('Unable to write manifest to "%s".\n %s' % (path, e))
140 def LoadRemoteManifest(url):
141 manifest = manifest_util.SDKManifest()
144 manifest_stream = cStringIO.StringIO()
145 url_stream = download.UrlOpen(url)
146 download.DownloadAndComputeHash(url_stream, manifest_stream)
147 except urllib2.URLError as e:
148 raise Error('Unable to read remote manifest from URL "%s".\n %s' % (
155 manifest.LoadDataFromString(manifest_stream.getvalue())
157 except manifest_util.Error as e:
158 raise Error('Parsing remote manifest from URL "%s" failed.\n %s' % (
162 def LoadCombinedRemoteManifest(default_manifest_url, cfg):
163 manifest = LoadRemoteManifest(default_manifest_url)
164 for source in cfg.sources:
165 manifest.MergeManifest(LoadRemoteManifest(source))
169 # Commands #####################################################################
172 @usage('<bundle names...>')
173 def CMDinfo(parser, args):
174 """display information about a bundle"""
175 options, args = parser.parse_args(args)
177 parser.error('No bundles given')
180 remote_manifest = LoadCombinedRemoteManifest(options.manifest_url, cfg)
181 command.info.Info(remote_manifest, args)
185 def CMDlist(parser, args):
186 """list all available bundles"""
187 parser.add_option('-r', '--revision', action='store_true',
188 help='display revision numbers')
189 options, args = parser.parse_args(args)
191 parser.error('Unsupported argument(s): %s' % ', '.join(args))
192 local_manifest = LoadLocalManifest()
194 remote_manifest = LoadCombinedRemoteManifest(options.manifest_url, cfg)
195 command.list.List(remote_manifest, local_manifest, options.revision)
199 @usage('<bundle names...>')
200 def CMDupdate(parser, args):
201 """update a bundle in the SDK to the latest version"""
202 parser.add_option('-F', '--force', action='store_true',
203 help='Force updating bundles that already exist. The bundle will not be '
204 'updated if the local revision matches the remote revision.')
205 options, args = parser.parse_args(args)
206 local_manifest = LoadLocalManifest()
208 remote_manifest = LoadCombinedRemoteManifest(options.manifest_url, cfg)
211 args = [command.update.RECOMMENDED]
214 delegate = command.update.RealUpdateDelegate(USER_DATA_DIR,
215 DEFAULT_SDK_ROOT, cfg)
216 command.update.Update(delegate, remote_manifest, local_manifest, args,
219 # Always write out the local manifest, we may have successfully updated one
220 # or more bundles before failing.
222 WriteLocalManifest(local_manifest)
224 # Log the error writing to the manifest, but propagate the original
226 logging.error(str(e))
231 def CMDinstall(parser, args):
232 """install a bundle in the SDK"""
233 # For now, forward to CMDupdate. We may want different behavior for this
234 # in the future, though...
235 return CMDupdate(parser, args)
238 @usage('<bundle names...>')
239 def CMDuninstall(parser, args):
240 """uninstall the given bundles"""
241 _, args = parser.parse_args(args)
243 parser.error('No bundles given')
245 local_manifest = LoadLocalManifest()
246 command.uninstall.Uninstall(DEFAULT_SDK_ROOT, local_manifest, args)
247 WriteLocalManifest(local_manifest)
251 @usage('<bundle names...>')
252 def CMDreinstall(parser, args):
253 """restore the given bundles to their original state
255 Note that if there is an update to a given bundle, reinstall will not
256 automatically update to the newest version.
258 _, args = parser.parse_args(args)
259 local_manifest = LoadLocalManifest()
262 parser.error('No bundles given')
267 delegate = command.update.RealUpdateDelegate(USER_DATA_DIR,
268 DEFAULT_SDK_ROOT, cfg)
269 command.update.Reinstall(delegate, local_manifest, args)
271 # Always write out the local manifest, we may have successfully updated one
272 # or more bundles before failing.
274 WriteLocalManifest(local_manifest)
276 # Log the error writing to the manifest, but propagate the original
278 logging.error(str(e))
283 def CMDsources(parser, args):
284 """manage external package sources"""
285 parser.add_option('-a', '--add', dest='url_to_add',
286 help='Add an additional package source')
288 '-r', '--remove', dest='url_to_remove',
289 help='Remove package source (use \'all\' for all additional sources)')
290 parser.add_option('-l', '--list', dest='do_list', action='store_true',
291 help='List additional package sources')
292 options, args = parser.parse_args(args)
294 cfg = LoadConfig(True)
296 if options.url_to_add:
297 command.sources.AddSource(cfg, options.url_to_add)
299 elif options.url_to_remove:
300 command.sources.RemoveSource(cfg, options.url_to_remove)
302 elif options.do_list:
303 command.sources.ListSources(cfg)
313 def CMDversion(parser, args):
314 """display version information"""
315 _, _ = parser.parse_args(args)
316 print "Native Client SDK Updater, version r%s" % REVISION
320 def CMDhelp(parser, args):
321 """print list of commands or help for a specific command"""
322 _, args = parser.parse_args(args)
324 return main(args + ['--help'])
330 return globals().get('CMD' + name, None)
333 def GenUsage(parser, cmd):
334 """Modify an OptParse object with the function's documentation."""
336 more = getattr(obj, 'usage_more', '')
340 # OptParser.description prefer nicely non-formatted strings.
341 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
342 parser.set_usage('usage: %%prog %s [options] %s' % (cmd, more))
345 def UpdateSDKTools(options, args):
346 """update the sdk_tools bundle"""
348 local_manifest = LoadLocalManifest()
350 remote_manifest = LoadCombinedRemoteManifest(options.manifest_url, cfg)
353 delegate = command.update.RealUpdateDelegate(USER_DATA_DIR,
354 DEFAULT_SDK_ROOT, cfg)
355 command.update.UpdateBundleIfNeeded(
359 command.update.SDK_TOOLS,
362 # Always write out the local manifest, we may have successfully updated one
363 # or more bundles before failing.
364 WriteLocalManifest(local_manifest)
369 # Get all commands...
370 cmds = [fn[3:] for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]
371 # Remove hidden commands...
372 cmds = filter(lambda fn: not getattr(Command(fn), 'hide', 0), cmds)
373 # Format for CMDhelp usage.
374 CMDhelp.usage_more = ('\n\nCommands are:\n' + '\n'.join([
375 ' %-10s %s' % (fn, Command(fn).__doc__.split('\n')[0].strip())
378 # Create the option parse and add --verbose support.
379 parser = optparse.OptionParser()
381 '-v', '--verbose', action='count', default=0,
382 help='Use 2 times for more debugging info')
383 parser.add_option('-U', '--manifest-url', dest='manifest_url',
384 default=GSTORE_URL + '/nacl/nacl_sdk/' + MANIFEST_FILENAME,
385 metavar='URL', help='override the default URL for the NaCl manifest file')
386 parser.add_option('--update-sdk-tools', action='store_true',
387 dest='update_sdk_tools', help=optparse.SUPPRESS_HELP)
389 old_parser_args = parser.parse_args
391 options, args = old_parser_args(args)
392 if options.verbose >= 2:
393 loglevel = logging.DEBUG
394 elif options.verbose:
395 loglevel = logging.INFO
397 loglevel = logging.WARNING
399 fmt = '%(levelname)s:%(message)s'
400 logging.basicConfig(stream=sys.stdout, level=loglevel, format=fmt)
402 # If --update-sdk-tools is passed, circumvent any other command running.
403 if options.update_sdk_tools:
404 UpdateSDKTools(options, args)
408 parser.parse_args = Parse
411 cmd = Command(argv[0])
413 # "fix" the usage and the description now that we know the subcommand.
414 GenUsage(parser, argv[0])
415 return cmd(parser, argv[1:])
417 # Not a known command. Default to help.
418 GenUsage(parser, 'help')
419 return CMDhelp(parser, argv)
422 if __name__ == '__main__':
424 sys.exit(main(sys.argv[1:]))
426 logging.error(str(e))
428 except KeyboardInterrupt:
429 sys.stderr.write('naclsdk: interrupted\n')