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://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')
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 parser
.add_argument('bundles', nargs
='+')
176 options
= parser
.parse_args(args
)
178 remote_manifest
= LoadCombinedRemoteManifest(options
.manifest_url
, cfg
)
179 command
.info
.Info(remote_manifest
, options
.bundles
)
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()
190 remote_manifest
= LoadCombinedRemoteManifest(options
.manifest_url
, cfg
)
191 command
.list.List(remote_manifest
, local_manifest
, options
.revision
)
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()
207 remote_manifest
= LoadCombinedRemoteManifest(options
.manifest_url
, cfg
)
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
)
215 # Always write out the local manifest, we may have successfully updated one
216 # or more bundles before failing.
218 WriteLocalManifest(local_manifest
)
220 # Log the error writing to the manifest, but propagate the original
222 logging
.error(str(e
))
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
)
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()
258 delegate
= command
.update
.RealUpdateDelegate(USER_DATA_DIR
,
259 DEFAULT_SDK_ROOT
, cfg
)
260 command
.update
.Reinstall(delegate
, local_manifest
, options
.bundles
)
262 # Always write out the local manifest, we may have successfully updated one
263 # or more bundles before failing.
265 WriteLocalManifest(local_manifest
)
267 # Log the error writing to the manifest, but propagate the original
269 logging
.error(str(e
))
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')
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)
287 if options
.url_to_add
:
288 command
.sources
.AddSource(cfg
, options
.url_to_add
)
290 elif options
.url_to_remove
:
291 command
.sources
.RemoveSource(cfg
, options
.url_to_remove
)
293 elif options
.do_list
:
294 command
.sources
.ListSources(cfg
)
304 def CMDversion(parser
, args
):
305 """display version information"""
306 parser
.parse_args(args
)
307 print "Native Client SDK Updater, version r%s" % REVISION
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
)
316 return main(options
.command
+ ['--help'])
322 return globals().get('CMD' + name
, None)
325 def GenUsage(parser
, cmd
):
326 """Modify an OptParse object with the function's documentation."""
328 more
= getattr(obj
, 'usage_more', '')
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()
342 remote_manifest
= LoadCombinedRemoteManifest(options
.manifest_url
, cfg
)
345 delegate
= command
.update
.RealUpdateDelegate(USER_DATA_DIR
,
346 DEFAULT_SDK_ROOT
, cfg
)
347 command
.update
.UpdateBundleIfNeeded(
351 command
.update
.SDK_TOOLS
,
354 # Always write out the local manifest, we may have successfully updated one
355 # or more bundles before failing.
356 WriteLocalManifest(local_manifest
)
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())
370 # Create the option parse and add --verbose support.
371 parser
= argparse
.ArgumentParser(description
=__doc__
)
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
383 options
= old_parser_args(args
)
384 if options
.verbose
>= 2:
385 loglevel
= logging
.DEBUG
386 elif options
.verbose
:
387 loglevel
= logging
.INFO
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
)
400 parser
.parse_args
= Parse
403 cmd
= Command(argv
[0])
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__':
416 sys
.exit(main(sys
.argv
[1:]))
418 logging
.error(str(e
))
420 except KeyboardInterrupt:
421 sys
.stderr
.write('naclsdk: interrupted\n')