Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / native_client_sdk / src / build_tools / sdk_tools / sdk_update_main.py
blobf61a9d26fc8b551a36d968d63f4eaa75dabc9d64
1 #!/usr/bin/env python
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.
8 import argparse
9 import config
10 import cStringIO
11 import download
12 import logging
13 import os
14 import re
15 import sdk_update_common
16 from sdk_update_common import Error
17 import sys
18 import urllib2
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))
24 import manifest_util
27 # Import late so each command script can find our imports
28 import command.info
29 import command.list
30 import command.sources
31 import command.uninstall
32 import command.update
34 # This revision number is autogenerated from the Chrome revision.
35 REVISION = '{REVISION}'
37 GSTORE_URL = 'https://storage.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')
44 def usage(more):
45 def hook(fn):
46 fn.usage_more = more
47 return fn
48 return hook
51 def hide(fn):
52 fn.hide = True
53 return fn
56 def LoadConfig(raise_on_error=False):
57 path = os.path.join(USER_DATA_DIR, CONFIG_FILENAME)
58 cfg = config.Config()
59 if not os.path.exists(path):
60 return cfg
62 try:
63 try:
64 with open(path) as f:
65 file_data = f.read()
66 except IOError as e:
67 raise Error('Unable to read config from "%s".\n %s' % (path, e))
69 try:
70 cfg.LoadJson(file_data)
71 except Error as e:
72 raise Error('Parsing config file from "%s" failed.\n %s' % (path, e))
73 return cfg
74 except Error as e:
75 if raise_on_error:
76 raise
77 else:
78 logging.warn(str(e))
80 return cfg
83 def WriteConfig(cfg):
84 path = os.path.join(USER_DATA_DIR, CONFIG_FILENAME)
85 try:
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()
92 try:
93 with open(path, 'w') as f:
94 f.write(cfg_json)
95 except IOError as e:
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()
102 try:
103 try:
104 with open(path) as f:
105 manifest_string = f.read()
106 except IOError as e:
107 raise Error('Unable to read manifest from "%s".\n %s' % (path, e))
109 try:
110 manifest.LoadDataFromString(manifest_string)
111 except Exception as e:
112 raise Error('Parsing local manifest "%s" failed.\n %s' % (path, e))
113 except Error as e:
114 if raise_on_error:
115 raise
116 else:
117 logging.warn(str(e))
118 return manifest
121 def WriteLocalManifest(manifest):
122 path = os.path.join(USER_DATA_DIR, MANIFEST_FILENAME)
123 try:
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))
128 try:
129 manifest_json = manifest.GetDataAsString()
130 except Exception as e:
131 raise Error('Error encoding manifest "%s" to JSON.\n %s' % (path, e))
133 try:
134 with open(path, 'w') as f:
135 f.write(manifest_json)
136 except IOError as e:
137 raise Error('Unable to write manifest to "%s".\n %s' % (path, e))
140 def LoadRemoteManifest(url):
141 manifest = manifest_util.SDKManifest()
142 url_stream = None
143 try:
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' % (
149 url, e))
150 finally:
151 if url_stream:
152 url_stream.close()
154 try:
155 manifest.LoadDataFromString(manifest_stream.getvalue())
156 return manifest
157 except manifest_util.Error as e:
158 raise Error('Parsing remote manifest from URL "%s" failed.\n %s' % (
159 url, e,))
162 def LoadCombinedRemoteManifest(default_manifest_url, cfg):
163 manifest = LoadRemoteManifest(default_manifest_url)
164 for source in cfg.sources:
165 manifest.MergeManifest(LoadRemoteManifest(source))
166 return manifest
169 # Commands #####################################################################
172 @usage('<bundle names...>')
173 def CMDinfo(parser, args):
174 """display information about a bundle"""
175 parser.add_argument('bundles', nargs='+')
176 options = parser.parse_args(args)
177 cfg = LoadConfig()
178 remote_manifest = LoadCombinedRemoteManifest(options.manifest_url, cfg)
179 command.info.Info(remote_manifest, options.bundles)
180 return 0
183 def CMDlist(parser, args):
184 """list all available bundles"""
185 parser.add_argument('-r', '--revision', action='store_true',
186 help='display revision numbers')
187 options = parser.parse_args(args)
188 local_manifest = LoadLocalManifest()
189 cfg = LoadConfig()
190 remote_manifest = LoadCombinedRemoteManifest(options.manifest_url, cfg)
191 command.list.List(remote_manifest, local_manifest, options.revision)
192 return 0
195 @usage('<bundle names...>')
196 def CMDupdate(parser, args):
197 """update a bundle in the SDK to the latest version"""
198 parser.add_argument('-F', '--force', action='store_true',
199 help='Force updating bundles that already exist. The bundle will not be '
200 'updated if the local revision matches the remote revision.')
201 parser.add_argument('bundles', nargs='*',
202 help='bundles to update',
203 default=[command.update.RECOMMENDED])
204 options = parser.parse_args(args)
205 local_manifest = LoadLocalManifest()
206 cfg = LoadConfig()
207 remote_manifest = LoadCombinedRemoteManifest(options.manifest_url, cfg)
209 try:
210 delegate = command.update.RealUpdateDelegate(USER_DATA_DIR,
211 DEFAULT_SDK_ROOT, cfg)
212 command.update.Update(delegate, remote_manifest, local_manifest,
213 options.bundles, options.force)
214 finally:
215 # Always write out the local manifest, we may have successfully updated one
216 # or more bundles before failing.
217 try:
218 WriteLocalManifest(local_manifest)
219 except Error as e:
220 # Log the error writing to the manifest, but propagate the original
221 # exception.
222 logging.error(str(e))
224 return 0
227 def CMDinstall(parser, args):
228 """install a bundle in the SDK"""
229 # For now, forward to CMDupdate. We may want different behavior for this
230 # in the future, though...
231 return CMDupdate(parser, args)
234 @usage('<bundle names...>')
235 def CMDuninstall(parser, args):
236 """uninstall the given bundles"""
237 parser.add_argument('bundles', nargs='+', help='bundles to uninstall')
238 options = parser.parse_args(args)
239 local_manifest = LoadLocalManifest()
240 command.uninstall.Uninstall(DEFAULT_SDK_ROOT, local_manifest, options.bundles)
241 WriteLocalManifest(local_manifest)
242 return 0
245 @usage('<bundle names...>')
246 def CMDreinstall(parser, args):
247 """restore the given bundles to their original state
249 Note that if there is an update to a given bundle, reinstall will not
250 automatically update to the newest version.
252 parser.add_argument('bundles', nargs='+')
253 options = parser.parse_args(args)
254 local_manifest = LoadLocalManifest()
256 cfg = LoadConfig()
257 try:
258 delegate = command.update.RealUpdateDelegate(USER_DATA_DIR,
259 DEFAULT_SDK_ROOT, cfg)
260 command.update.Reinstall(delegate, local_manifest, options.bundles)
261 finally:
262 # Always write out the local manifest, we may have successfully updated one
263 # or more bundles before failing.
264 try:
265 WriteLocalManifest(local_manifest)
266 except Error as e:
267 # Log the error writing to the manifest, but propagate the original
268 # exception.
269 logging.error(str(e))
271 return 0
274 def CMDsources(parser, args):
275 """manage external package sources"""
276 parser.add_argument('-a', '--add', dest='url_to_add',
277 help='Add an additional package source')
278 parser.add_argument(
279 '-r', '--remove', dest='url_to_remove',
280 help='Remove package source (use \'all\' for all additional sources)')
281 parser.add_argument('-l', '--list', dest='do_list', action='store_true',
282 help='List additional package sources')
283 options = parser.parse_args(args)
285 cfg = LoadConfig(True)
286 write_config = False
287 if options.url_to_add:
288 command.sources.AddSource(cfg, options.url_to_add)
289 write_config = True
290 elif options.url_to_remove:
291 command.sources.RemoveSource(cfg, options.url_to_remove)
292 write_config = True
293 elif options.do_list:
294 command.sources.ListSources(cfg)
295 else:
296 parser.print_help()
298 if write_config:
299 WriteConfig(cfg)
301 return 0
304 def CMDversion(parser, args):
305 """display version information"""
306 parser.parse_args(args)
307 print "Native Client SDK Updater, version r%s" % REVISION
308 return 0
311 def CMDhelp(parser, args):
312 """print list of commands or help for a specific command"""
313 parser.add_argument('command', nargs='?', help=argparse.SUPPRESS)
314 options = parser.parse_args(args)
315 if options.command:
316 return main(options.command + ['--help'])
317 parser.print_help()
318 return 0
321 def Command(name):
322 return globals().get('CMD' + name, None)
325 def GenUsage(parser, cmd):
326 """Modify an OptParse object with the function's documentation."""
327 obj = Command(cmd)
328 more = getattr(obj, 'usage_more', '')
329 if cmd == 'help':
330 cmd = '<command>'
331 else:
332 # OptParser.description prefer nicely non-formatted strings.
333 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
334 parser.usage = '%%(prog)s %s [options] %s' % (cmd, more)
337 def UpdateSDKTools(options, args):
338 """update the sdk_tools bundle"""
340 local_manifest = LoadLocalManifest()
341 cfg = LoadConfig()
342 remote_manifest = LoadCombinedRemoteManifest(options.manifest_url, cfg)
344 try:
345 delegate = command.update.RealUpdateDelegate(USER_DATA_DIR,
346 DEFAULT_SDK_ROOT, cfg)
347 command.update.UpdateBundleIfNeeded(
348 delegate,
349 remote_manifest,
350 local_manifest,
351 command.update.SDK_TOOLS,
352 force=True)
353 finally:
354 # Always write out the local manifest, we may have successfully updated one
355 # or more bundles before failing.
356 WriteLocalManifest(local_manifest)
357 return 0
360 def main(argv):
361 # Get all commands...
362 cmds = [fn[3:] for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]
363 # Remove hidden commands...
364 cmds = filter(lambda fn: not getattr(Command(fn), 'hide', 0), cmds)
365 # Format for CMDhelp usage.
366 CMDhelp.usage_more = ('\n\nCommands are:\n' + '\n'.join([
367 ' %-10s %s' % (fn, Command(fn).__doc__.split('\n')[0].strip())
368 for fn in cmds]))
370 # Create the option parse and add --verbose support.
371 parser = argparse.ArgumentParser(description=__doc__)
372 parser.add_argument(
373 '-v', '--verbose', action='count', default=0,
374 help='Use 2 times for more debugging info')
375 parser.add_argument('-U', '--manifest-url', dest='manifest_url',
376 default=GSTORE_URL + '/nacl/nacl_sdk/' + MANIFEST_FILENAME,
377 metavar='URL', help='override the default URL for the NaCl manifest file')
378 parser.add_argument('--update-sdk-tools', action='store_true',
379 dest='update_sdk_tools', help=argparse.SUPPRESS)
381 old_parser_args = parser.parse_args
382 def Parse(args):
383 options = old_parser_args(args)
384 if options.verbose >= 2:
385 loglevel = logging.DEBUG
386 elif options.verbose:
387 loglevel = logging.INFO
388 else:
389 loglevel = logging.WARNING
391 logging.basicConfig(stream=sys.stdout, level=loglevel,
392 format='%(levelname)s:%(message)s')
394 # If --update-sdk-tools is passed, circumvent any other command running.
395 if options.update_sdk_tools:
396 UpdateSDKTools(options, args)
397 sys.exit(1)
399 return options
400 parser.parse_args = Parse
402 if argv:
403 cmd = Command(argv[0])
404 if cmd:
405 # "fix" the usage and the description now that we know the subcommand.
406 GenUsage(parser, argv[0])
407 return cmd(parser, argv[1:])
409 # Not a known command. Default to help.
410 GenUsage(parser, 'help')
411 return CMDhelp(parser, argv)
414 if __name__ == '__main__':
415 try:
416 sys.exit(main(sys.argv[1:]))
417 except Error as e:
418 logging.error(str(e))
419 sys.exit(1)
420 except KeyboardInterrupt:
421 sys.stderr.write('naclsdk: interrupted\n')
422 sys.exit(1)