- add sources.
[platform/framework/web/crosswalk.git] / src / native_client_sdk / src / build_tools / sdk_tools / sdk_update_main.py
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.
5
6 # CMD code copied from git_cl.py in depot_tools.
7
8 import config
9 import cStringIO
10 import download
11 import logging
12 import optparse
13 import os
14 import re
15 import sdk_update_common
16 from sdk_update_common import Error
17 import sys
18 import urllib2
19
20 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
21 PARENT_DIR = os.path.dirname(SCRIPT_DIR)
22
23 sys.path.append(os.path.dirname(SCRIPT_DIR))
24 import manifest_util
25
26
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
33
34 # This revision number is autogenerated from the Chrome revision.
35 REVISION = '{REVISION}'
36
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')
42
43
44 def usage(more):
45   def hook(fn):
46     fn.usage_more = more
47     return fn
48   return hook
49
50
51 def hide(fn):
52   fn.hide = True
53   return fn
54
55
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
61
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))
68
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))
79
80   return cfg
81
82
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))
89
90   cfg_json = cfg.ToJson()
91
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))
97
98
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))
108
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
119
120
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))
127
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))
132
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))
138
139
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()
153
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,))
160
161
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
167
168
169 # Commands #####################################################################
170
171
172 @usage('<bundle names...>')
173 def CMDinfo(parser, args):
174   """display information about a bundle"""
175   options, args = parser.parse_args(args)
176   if not args:
177     parser.error('No bundles given')
178     return 0
179   cfg = LoadConfig()
180   remote_manifest = LoadCombinedRemoteManifest(options.manifest_url, cfg)
181   command.info.Info(remote_manifest, args)
182   return 0
183
184
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)
190   if args:
191     parser.error('Unsupported argument(s): %s' % ', '.join(args))
192   local_manifest = LoadLocalManifest()
193   cfg = LoadConfig()
194   remote_manifest = LoadCombinedRemoteManifest(options.manifest_url, cfg)
195   command.list.List(remote_manifest, local_manifest, options.revision)
196   return 0
197
198
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()
207   cfg = LoadConfig()
208   remote_manifest = LoadCombinedRemoteManifest(options.manifest_url, cfg)
209
210   if not args:
211     args = [command.update.RECOMMENDED]
212
213   try:
214     delegate = command.update.RealUpdateDelegate(USER_DATA_DIR,
215                                                  DEFAULT_SDK_ROOT, cfg)
216     command.update.Update(delegate, remote_manifest, local_manifest, args,
217                           options.force)
218   finally:
219     # Always write out the local manifest, we may have successfully updated one
220     # or more bundles before failing.
221     try:
222       WriteLocalManifest(local_manifest)
223     except Error as e:
224       # Log the error writing to the manifest, but propagate the original
225       # exception.
226       logging.error(str(e))
227
228   return 0
229
230
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)
236
237
238 @usage('<bundle names...>')
239 def CMDuninstall(parser, args):
240   """uninstall the given bundles"""
241   _, args = parser.parse_args(args)
242   if not args:
243     parser.error('No bundles given')
244     return 0
245   local_manifest = LoadLocalManifest()
246   command.uninstall.Uninstall(DEFAULT_SDK_ROOT, local_manifest, args)
247   WriteLocalManifest(local_manifest)
248   return 0
249
250
251 @usage('<bundle names...>')
252 def CMDreinstall(parser, args):
253   """restore the given bundles to their original state
254
255   Note that if there is an update to a given bundle, reinstall will not
256   automatically update to the newest version.
257   """
258   _, args = parser.parse_args(args)
259   local_manifest = LoadLocalManifest()
260
261   if not args:
262     parser.error('No bundles given')
263     return 0
264
265   cfg = LoadConfig()
266   try:
267     delegate = command.update.RealUpdateDelegate(USER_DATA_DIR,
268                                                  DEFAULT_SDK_ROOT, cfg)
269     command.update.Reinstall(delegate, local_manifest, args)
270   finally:
271     # Always write out the local manifest, we may have successfully updated one
272     # or more bundles before failing.
273     try:
274       WriteLocalManifest(local_manifest)
275     except Error as e:
276       # Log the error writing to the manifest, but propagate the original
277       # exception.
278       logging.error(str(e))
279
280   return 0
281
282
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')
287   parser.add_option(
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)
293
294   cfg = LoadConfig(True)
295   write_config = False
296   if options.url_to_add:
297     command.sources.AddSource(cfg, options.url_to_add)
298     write_config = True
299   elif options.url_to_remove:
300     command.sources.RemoveSource(cfg, options.url_to_remove)
301     write_config = True
302   elif options.do_list:
303     command.sources.ListSources(cfg)
304   else:
305     parser.print_help()
306
307   if write_config:
308     WriteConfig(cfg)
309
310   return 0
311
312
313 def CMDversion(parser, args):
314   """display version information"""
315   _, _ = parser.parse_args(args)
316   print "Native Client SDK Updater, version r%s" % REVISION
317   return 0
318
319
320 def CMDhelp(parser, args):
321   """print list of commands or help for a specific command"""
322   _, args = parser.parse_args(args)
323   if len(args) == 1:
324     return main(args + ['--help'])
325   parser.print_help()
326   return 0
327
328
329 def Command(name):
330   return globals().get('CMD' + name, None)
331
332
333 def GenUsage(parser, cmd):
334   """Modify an OptParse object with the function's documentation."""
335   obj = Command(cmd)
336   more = getattr(obj, 'usage_more', '')
337   if cmd == 'help':
338     cmd = '<command>'
339   else:
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))
343
344
345 def UpdateSDKTools(options, args):
346   """update the sdk_tools bundle"""
347
348   local_manifest = LoadLocalManifest()
349   cfg = LoadConfig()
350   remote_manifest = LoadCombinedRemoteManifest(options.manifest_url, cfg)
351
352   try:
353     delegate = command.update.RealUpdateDelegate(USER_DATA_DIR,
354                                                  DEFAULT_SDK_ROOT, cfg)
355     command.update.UpdateBundleIfNeeded(
356         delegate,
357         remote_manifest,
358         local_manifest,
359         command.update.SDK_TOOLS,
360         force=True)
361   finally:
362     # Always write out the local manifest, we may have successfully updated one
363     # or more bundles before failing.
364     WriteLocalManifest(local_manifest)
365   return 0
366
367
368 def main(argv):
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())
376       for fn in cmds]))
377
378   # Create the option parse and add --verbose support.
379   parser = optparse.OptionParser()
380   parser.add_option(
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)
388
389   old_parser_args = parser.parse_args
390   def 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
396     else:
397       loglevel = logging.WARNING
398
399     fmt = '%(levelname)s:%(message)s'
400     logging.basicConfig(stream=sys.stdout, level=loglevel, format=fmt)
401
402     # If --update-sdk-tools is passed, circumvent any other command running.
403     if options.update_sdk_tools:
404       UpdateSDKTools(options, args)
405       sys.exit(1)
406
407     return options, args
408   parser.parse_args = Parse
409
410   if argv:
411     cmd = Command(argv[0])
412     if cmd:
413       # "fix" the usage and the description now that we know the subcommand.
414       GenUsage(parser, argv[0])
415       return cmd(parser, argv[1:])
416
417   # Not a known command. Default to help.
418   GenUsage(parser, 'help')
419   return CMDhelp(parser, argv)
420
421
422 if __name__ == '__main__':
423   try:
424     sys.exit(main(sys.argv[1:]))
425   except Error as e:
426     logging.error(str(e))
427     sys.exit(1)
428   except KeyboardInterrupt:
429     sys.stderr.write('naclsdk: interrupted\n')
430     sys.exit(1)