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.
16 import sdk_update_common
17 from sdk_update_common
import Error
21 SCRIPT_DIR
= os
.path
.dirname(os
.path
.abspath(__file__
))
22 PARENT_DIR
= os
.path
.dirname(SCRIPT_DIR
)
24 sys
.path
.append(os
.path
.dirname(SCRIPT_DIR
))
28 # Import late so each command script can find our imports
31 import command
.sources
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
)
58 if not os
.path
.exists(path
):
59 return config
.Config()
64 return config
.Config(json
.loads(f
.read()))
66 raise Error('Unable to read config from "%s".\n %s' % (path
, e
))
67 except Exception as e
:
68 raise Error('Parsing config file from "%s" failed.\n %s' % (path
, e
))
74 return config
.Config()
78 path
= os
.path
.join(USER_DATA_DIR
, CONFIG_FILENAME
)
80 sdk_update_common
.MakeDirs(USER_DATA_DIR
)
81 except Exception as e
:
82 raise Error('Unable to create directory "%s".\n %s' % (USER_DATA_DIR
, e
))
85 cfg_json
= cfg
.ToJson()
86 except Exception as e
:
87 raise Error('Json encoding error writing config "%s".\n %s' % (path
, e
))
90 with
open(path
, 'w') as f
:
93 raise Error('Unable to write config to "%s".\n %s' % (path
, e
))
96 def LoadLocalManifest(raise_on_error
=False):
97 path
= os
.path
.join(USER_DATA_DIR
, MANIFEST_FILENAME
)
98 manifest
= manifest_util
.SDKManifest()
101 with
open(path
) as f
:
102 manifest_string
= f
.read()
104 raise Error('Unable to read manifest from "%s".\n %s' % (path
, e
))
107 manifest
.LoadDataFromString(manifest_string
)
108 except Exception as e
:
109 raise Error('Parsing local manifest "%s" failed.\n %s' % (path
, e
))
118 def WriteLocalManifest(manifest
):
119 path
= os
.path
.join(USER_DATA_DIR
, MANIFEST_FILENAME
)
121 sdk_update_common
.MakeDirs(USER_DATA_DIR
)
122 except Exception as e
:
123 raise Error('Unable to create directory "%s".\n %s' % (USER_DATA_DIR
, e
))
126 manifest_json
= manifest
.GetDataAsString()
127 except Exception as e
:
128 raise Error('Error encoding manifest "%s" to JSON.\n %s' % (path
, e
))
131 with
open(path
, 'w') as f
:
132 f
.write(manifest_json
)
134 raise Error('Unable to write manifest to "%s".\n %s' % (path
, e
))
137 def LoadRemoteManifest(url
):
138 manifest
= manifest_util
.SDKManifest()
141 manifest_stream
= cStringIO
.StringIO()
142 url_stream
= download
.UrlOpen(url
)
143 download
.DownloadAndComputeHash(url_stream
, manifest_stream
)
144 except urllib2
.URLError
as e
:
145 raise Error('Unable to read remote manifest from URL "%s".\n %s' % (
152 manifest
.LoadDataFromString(manifest_stream
.getvalue())
154 except manifest_util
.Error
as e
:
155 raise Error('Parsing remote manifest from URL "%s" failed.\n %s' % (
159 def LoadCombinedRemoteManifest(default_manifest_url
, cfg
):
160 manifest
= LoadRemoteManifest(default_manifest_url
)
161 for source
in cfg
.sources
:
162 manifest
.MergeManifest(LoadRemoteManifest(source
))
166 # Commands #####################################################################
169 @usage('<bundle names...>')
170 def CMDinfo(parser
, args
):
171 """display information about a bundle"""
172 options
, args
= parser
.parse_args(args
)
174 parser
.error('No bundles given')
177 remote_manifest
= LoadCombinedRemoteManifest(options
.manifest_url
, cfg
)
178 command
.info
.Info(remote_manifest
, args
)
182 def CMDlist(parser
, args
):
183 """list all available bundles"""
184 parser
.add_option('-r', '--revision', action
='store_true',
185 help='display revision numbers')
186 options
, args
= parser
.parse_args(args
)
188 parser
.error('Unsupported argument(s): %s' % ', '.join(args
))
189 local_manifest
= LoadLocalManifest()
191 remote_manifest
= LoadCombinedRemoteManifest(options
.manifest_url
, cfg
)
192 command
.list.List(remote_manifest
, local_manifest
, options
.revision
)
196 @usage('<bundle names...>')
197 def CMDupdate(parser
, args
):
198 """update a bundle in the SDK to the latest version"""
200 '-F', '--force', action
='store_true',
201 help='Force updating existing components that already exist')
202 options
, args
= parser
.parse_args(args
)
203 local_manifest
= LoadLocalManifest()
205 remote_manifest
= LoadCombinedRemoteManifest(options
.manifest_url
, cfg
)
208 args
= [command
.update
.RECOMMENDED
]
211 delegate
= command
.update
.RealUpdateDelegate(USER_DATA_DIR
,
213 command
.update
.Update(delegate
, remote_manifest
, local_manifest
, args
,
216 # Always write out the local manifest, we may have successfully updated one
217 # or more bundles before failing.
219 WriteLocalManifest(local_manifest
)
221 # Log the error writing to the manifest, but propagate the original
223 logging
.error(str(e
))
228 def CMDinstall(parser
, args
):
229 """install a bundle in the SDK"""
230 # For now, forward to CMDupdate. We may want different behavior for this
231 # in the future, though...
232 return CMDupdate(parser
, args
)
235 def CMDsources(parser
, args
):
236 """manage external package sources"""
237 parser
.add_option('-a', '--add', dest
='url_to_add',
238 help='Add an additional package source')
240 '-r', '--remove', dest
='url_to_remove',
241 help='Remove package source (use \'all\' for all additional sources)')
242 parser
.add_option('-l', '--list', dest
='do_list', action
='store_true',
243 help='List additional package sources')
244 options
, args
= parser
.parse_args(args
)
246 cfg
= LoadConfig(True)
248 if options
.url_to_add
:
249 command
.sources
.AddSource(cfg
, options
.url_to_add
)
251 elif options
.url_to_remove
:
252 command
.sources
.RemoveSource(cfg
, options
.url_to_remove
)
254 elif options
.do_list
:
255 command
.sources
.ListSources(cfg
)
265 def CMDversion(parser
, args
):
266 """display version information"""
267 _
, _
= parser
.parse_args(args
)
268 print "Native Client SDK Updater, version r%s" % REVISION
272 def CMDhelp(parser
, args
):
273 """print list of commands or help for a specific command"""
274 _
, args
= parser
.parse_args(args
)
276 return main(args
+ ['--help'])
282 return globals().get('CMD' + name
, None)
285 def GenUsage(parser
, cmd
):
286 """Modify an OptParse object with the function's documentation."""
288 more
= getattr(obj
, 'usage_more', '')
292 # OptParser.description prefer nicely non-formatted strings.
293 parser
.description
= re
.sub('[\r\n ]{2,}', ' ', obj
.__doc
__)
294 parser
.set_usage('usage: %%prog %s [options] %s' % (cmd
, more
))
297 def UpdateSDKTools(options
, args
):
298 """update the sdk_tools bundle"""
300 local_manifest
= LoadLocalManifest()
302 remote_manifest
= LoadCombinedRemoteManifest(options
.manifest_url
, cfg
)
305 delegate
= command
.update
.RealUpdateDelegate(USER_DATA_DIR
,
307 command
.update
.UpdateBundleIfNeeded(
311 command
.update
.SDK_TOOLS
,
314 # Always write out the local manifest, we may have successfully updated one
315 # or more bundles before failing.
316 WriteLocalManifest(local_manifest
)
321 # Get all commands...
322 cmds
= [fn
[3:] for fn
in dir(sys
.modules
[__name__
]) if fn
.startswith('CMD')]
323 # Remove hidden commands...
324 cmds
= filter(lambda fn
: not getattr(Command(fn
), 'hide', 0), cmds
)
325 # Format for CMDhelp usage.
326 CMDhelp
.usage_more
= ('\n\nCommands are:\n' + '\n'.join([
327 ' %-10s %s' % (fn
, Command(fn
).__doc
__.split('\n')[0].strip())
330 # Create the option parse and add --verbose support.
331 parser
= optparse
.OptionParser()
333 '-v', '--verbose', action
='count', default
=0,
334 help='Use 2 times for more debugging info')
335 parser
.add_option('-U', '--manifest-url', dest
='manifest_url',
336 default
=GSTORE_URL
+ '/nacl/nacl_sdk/' + MANIFEST_FILENAME
,
337 metavar
='URL', help='override the default URL for the NaCl manifest file')
338 parser
.add_option('--update-sdk-tools', action
='store_true',
339 dest
='update_sdk_tools', help=optparse
.SUPPRESS_HELP
)
341 old_parser_args
= parser
.parse_args
343 options
, args
= old_parser_args(args
)
344 if options
.verbose
>= 2:
345 loglevel
= logging
.DEBUG
346 elif options
.verbose
:
347 loglevel
= logging
.INFO
349 loglevel
= logging
.WARNING
351 fmt
= '%(levelname)s:%(message)s'
352 logging
.basicConfig(stream
=sys
.stdout
, level
=loglevel
, format
=fmt
)
354 # If --update-sdk-tools is passed, circumvent any other command running.
355 if options
.update_sdk_tools
:
356 UpdateSDKTools(options
, args
)
360 parser
.parse_args
= Parse
363 cmd
= Command(argv
[0])
365 # "fix" the usage and the description now that we know the subcommand.
366 GenUsage(parser
, argv
[0])
367 return cmd(parser
, argv
[1:])
369 # Not a known command. Default to help.
370 GenUsage(parser
, 'help')
371 return CMDhelp(parser
, argv
)
374 if __name__
== '__main__':
376 sys
.exit(main(sys
.argv
[1:]))
378 logging
.error(str(e
))