blob: 5a2f60342a9e9610b0693bbea221af657a4a557e [file] [log] [blame]
Doug Andersonf0c73952011-01-18 13:46:07 -08001#!/usr/bin/python
Doug Andersone91900d2011-01-26 11:37:17 -08002#
3# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
Doug Andersonf0c73952011-01-18 13:46:07 -08004# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
Doug Andersone91900d2011-01-26 11:37:17 -08007"""Main file for the chromite shell."""
Doug Andersonf0c73952011-01-18 13:46:07 -08008
9# Python imports
Simon Glassf4616ab2011-03-30 13:19:16 -070010import optparse
Doug Andersonf0c73952011-01-18 13:46:07 -080011import os
12import sys
Doug Andersonf0c73952011-01-18 13:46:07 -080013
14
Doug Anderson4b6d3962011-01-21 09:41:50 -080015# Local imports
Doug Andersone91900d2011-01-26 11:37:17 -080016from chromite.lib import text_menu
Doug Andersona9b10902011-02-01 17:54:31 -080017import chromite.lib.cros_build_lib as cros_lib
18from chromite.shell import utils
19from chromite.shell.subcmds import build_cmd
20from chromite.shell.subcmds import clean_cmd
Doug Andersona9b10902011-02-01 17:54:31 -080021from chromite.shell.subcmds import shell_cmd
22from chromite.shell.subcmds import workon_cmd
Simon Glass21599602011-02-11 13:57:29 -080023from chromite.shell import chromite_env
24from chromite.lib import operation
Doug Andersonf0c73952011-01-18 13:46:07 -080025
26
Doug Andersona9b10902011-02-01 17:54:31 -080027# Define command handlers and command strings.
Doug Andersonf0c73952011-01-18 13:46:07 -080028#
29# ORDER MATTERS here when we show the menu.
Doug Andersonf0c73952011-01-18 13:46:07 -080030_COMMAND_HANDLERS = [
Doug Andersona9b10902011-02-01 17:54:31 -080031 build_cmd.BuildCmd,
32 clean_cmd.CleanCmd,
Doug Andersona9b10902011-02-01 17:54:31 -080033 shell_cmd.ShellCmd,
34 workon_cmd.WorkonCmd,
Doug Andersonf0c73952011-01-18 13:46:07 -080035]
Doug Andersona9b10902011-02-01 17:54:31 -080036_COMMAND_STRS = [cls.__name__[:-len('Cmd')].lower()
37 for cls in _COMMAND_HANDLERS]
Doug Andersonf0c73952011-01-18 13:46:07 -080038
39
40def _FindCommand(cmd_name):
41 """Find the command that matches the given command name.
42
43 This tries to be smart. See the cmd_name parameter for details.
44
45 Args:
46 cmd_name: Can be any of the following:
47 1. The full name of a command. This is checked first so that if one
48 command name is a substring of another, you can still specify
49 the shorter spec name and know you won't get a menu (the exact
50 match prevents the menu).
51 2. A _prefix_ that will be used to pare-down a menu of commands
52 Can be the empty string to show a menu of all commands
53
54 Returns:
55 The command name.
56 """
57 # Always make cmd_name lower. Commands are case-insensitive.
58 cmd_name = cmd_name.lower()
59
60 # If we're an exact match, we're done!
61 if cmd_name in _COMMAND_STRS:
62 return cmd_name
63
64 # Find ones that match and put them in a menu...
65 possible_cmds = []
66 possible_choices = []
67 for cmd_num, this_cmd in enumerate(_COMMAND_STRS):
68 if this_cmd.startswith(cmd_name):
Doug Andersona9b10902011-02-01 17:54:31 -080069 handler = _COMMAND_HANDLERS[cmd_num]
Doug Andersonf0c73952011-01-18 13:46:07 -080070 assert hasattr(handler, '__doc__'), \
71 ('All handlers must have docstrings: %s' % cmd_name)
72 desc = handler.__doc__.splitlines()[0]
73
74 possible_cmds.append(this_cmd)
75 possible_choices.append('%s - %s' % (this_cmd, desc))
76
77 if not possible_choices:
Doug Andersona9b10902011-02-01 17:54:31 -080078 cros_lib.Die('No commands matched: "%s". '
Doug Andersonf0c73952011-01-18 13:46:07 -080079 'Try running with no arguments for a menu.' %
80 cmd_name)
81
82 if cmd_name and len(possible_choices) == 1:
83 # Avoid showing the user a menu if the user's search string matched exactly
84 # one item.
85 choice = 0
Doug Andersonf0c73952011-01-18 13:46:07 -080086 else:
87 choice = text_menu.TextMenu(possible_choices, 'Which chromite command',
88 menu_width=0)
89
90 if choice is None:
Doug Andersona9b10902011-02-01 17:54:31 -080091 cros_lib.Die('OK, cancelling...')
Doug Andersonf0c73952011-01-18 13:46:07 -080092 else:
93 return possible_cmds[choice]
94
95
Simon Glassf5298ef2011-04-08 14:00:45 -070096def _ParseArguments(parser, argv):
97 '''Helper function to separate arguments for a main program and sub-command.
98
99 We split arguments into ones we understand, and ones to pass on to
100 sub-commands. For the former, we put them through the given optparse and
101 return the result options and sub-command name. For the latter, we just
102 return a list of options and arguments not intended for us.
103
104 We want to parse only the options that we understand at the top level of
105 Chromite. Our heuristic here is that anything after the first positional
106 parameter (which we assume is the command) is ignored at this level, and
107 is passed down to the command level to handle.
108
109 TODO(sjg): Revisit this to include tolerant option parser instead
110 http://codereview.chromium.org/6469035/
111
112 Args:
113 parser: Option parser.
114 argv: List of program arguments
115
116 Returns:
117 options: Top level options (returned from optparse).
118 cmd_str: Subcommand to run
119 cmd_args: Arguments intended for subcommands.
120 '''
121 our_args = []
122 cmd_args = list(argv)
123 cmd_str = ''
124 args = [] # Nothing until we call optparse
125 while not cmd_str:
126 if our_args:
127 (options, args) = parser.parse_args(our_args)
128 if len(args) > 1:
129 cmd_str = args[1].lower()
130 elif cmd_args:
131 # We don't have a command yet. Transfer a positional arg from from
132 # cmd_args to our_args to see if that does the trick. We move over any
133 # options we find also.
134 while cmd_args:
135 arg = cmd_args.pop(0)
136 our_args.append(arg)
137 if not arg.startswith( '-'):
138 break
139 else:
140 # No more cmd_args to consume.
141 break
142
143 # We must run the parser, even if it dies due to lack of arguments
144 if not args:
145 (options, args) = parser.parse_args(our_args)
146 return options, cmd_str, cmd_args
147
Doug Andersonf0c73952011-01-18 13:46:07 -0800148def main():
Doug Andersona9b10902011-02-01 17:54:31 -0800149 """Main function for the chromite shell."""
150
151 # Hack it so that argv[0] is 'chromite' so that it doesn't tell user to run
152 # 'main.py' in help commands...
153 # TODO(dianders): Remove this hack, since it is ugly. Shouldn't be needed
154 # once we change the way that the chromite wrapper works.
155 sys.argv[0] = 'chromite'
156
157 # Support EnterChroot().
Simon Glass5329b932011-03-14 16:49:04 -0700158 # This may raise a ChromiteError if the child dies, so we must handle this.
159 try:
160 did_resume = utils.ResumeEnterChrootIfNeeded(sys.argv)
161 if did_resume:
162 return
163 except chromite_env.ChromiteError:
164 # The error has been reported, but we must exit indicating failure
165 sys.exit(1)
Doug Andersona9b10902011-02-01 17:54:31 -0800166
Doug Andersonf0c73952011-01-18 13:46:07 -0800167 # TODO(dianders): Make help a little better. Specifically:
168 # 1. Add a command called 'help'
169 # 2. Make the help string below include command list and descriptions (like
170 # the menu, but without being interactive).
171 # 3. Make "help command" and "--help command" equivalent to "command --help".
172 help_str = (
Simon Glassf4616ab2011-03-30 13:19:16 -0700173 """%(prog)s [chromite_options] [cmd [args]]\n"""
Doug Andersonf0c73952011-01-18 13:46:07 -0800174 """\n"""
175 """The chromite script is a wrapper to make it easy to do various\n"""
Simon Glassf4616ab2011-03-30 13:19:16 -0700176 """build tasks. For a list of commands, run without any arguments."""
Doug Andersonf0c73952011-01-18 13:46:07 -0800177 ) % {'prog': os.path.basename(sys.argv[0])}
Simon Glassf4616ab2011-03-30 13:19:16 -0700178
179 parser = optparse.OptionParser()
180
181 # Verbose defaults to full for now, just to keep people acclimatized to
182 # vast amounts of comforting output.
Simon Glassf5298ef2011-04-08 14:00:45 -0700183 parser.add_option('-v', dest='verbose', default=3, type='int',
Simon Glassf4616ab2011-03-30 13:19:16 -0700184 help='Control verbosity: 0=silent, 1=progress, 3=full')
185 parser.add_option('-q', action='store_const', dest='verbose', const=0,
186 help='Be quieter (sets verbosity to 1)')
Doug Andersona9b10902011-02-01 17:54:31 -0800187 if not cros_lib.IsInsideChroot():
Simon Glassf4616ab2011-03-30 13:19:16 -0700188 parser.add_option('--chroot', action='store', type='string',
189 dest='chroot_name', default='chroot',
190 help="Chroot spec to use. Can be an absolute path to a spec file "
191 "or a substring of a chroot spec name (without .spec suffix)")
192 parser.usage = help_str
Simon Glassf5298ef2011-04-08 14:00:45 -0700193
194 # Parse the arguments and separate them into top-level and subcmd arguments.
195 options, cmd_str, cmd_args = _ParseArguments(parser, sys.argv)
Doug Andersonf0c73952011-01-18 13:46:07 -0800196
Simon Glasseea2a2b2011-03-30 13:36:15 -0700197 # Set up the cros system.
198 cros_env = chromite_env.ChromiteEnv()
199
200 # Configure the operation setup.
201 oper = cros_env.GetOperation()
202 oper.verbose = options.verbose >= 3
203 oper.progress = options.verbose >= 1
204
205 # Look for special "--chroot" argument to allow for alternate chroots
206 if not cros_lib.IsInsideChroot():
207 chroot_spec_path = utils.FindSpec(options.chroot_name,
208 spec_type=utils.CHROOT_SPEC_TYPE)
209
210 oper.Info('Using chroot "%s"' % os.path.relpath(chroot_spec_path))
211
212 chroot_config = utils.ReadConfig(chroot_spec_path)
Doug Andersonf0c73952011-01-18 13:46:07 -0800213 else:
Simon Glasseea2a2b2011-03-30 13:36:15 -0700214 # Already in the chroot; no need to get config...
215 chroot_config = None
Simon Glass5329b932011-03-14 16:49:04 -0700216
Simon Glasseea2a2b2011-03-30 13:36:15 -0700217 # Validate the subcmd, popping a menu if needed.
218 cmd_str = _FindCommand(cmd_str)
219 oper.Info("Running command '%s'." % cmd_str)
Doug Andersonf0c73952011-01-18 13:46:07 -0800220
Simon Glasseea2a2b2011-03-30 13:36:15 -0700221 # Finally, call the function w/ standard argv.
222 cmd_cls = _COMMAND_HANDLERS[_COMMAND_STRS.index(cmd_str)]
223 cmd_obj = cmd_cls()
224 cmd_obj.SetChromiteEnv(cros_env)
225 try:
Simon Glassf5298ef2011-04-08 14:00:45 -0700226 cmd_obj.Run([cmd_str] + cmd_args, chroot_config=chroot_config)
Doug Andersonf0c73952011-01-18 13:46:07 -0800227
Simon Glasseea2a2b2011-03-30 13:36:15 -0700228 # Handle an error in one of the scripts: print a message and exit.
229 except chromite_env.ChromiteError, msg:
230 sys.exit(1)
Doug Andersonf0c73952011-01-18 13:46:07 -0800231
232if __name__ == '__main__':
233 main()