Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(211)

Side by Side Diff: shell/main.py

Issue 6250058: Split out the big chromite shell 'main.py' into lots of subfiles. (Closed) Base URL: ssh://git@gitrw.chromium.org:9222/chromite.git@master
Patch Set: Created 9 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | shell/main_unittest.py » ('j') | shell/subcmd.py » ('J')
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/python 1 #!/usr/bin/python
2 # 2 #
3 # Copyright (c) 2011 The Chromium OS Authors. All rights reserved. 3 # Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
4 # Use of this source code is governed by a BSD-style license that can be 4 # Use of this source code is governed by a BSD-style license that can be
5 # found in the LICENSE file. 5 # found in the LICENSE file.
6 6
7 """Main file for the chromite shell.""" 7 """Main file for the chromite shell."""
8 8
9 # Python imports 9 # Python imports
10 import ConfigParser
11 import cPickle
12 import optparse
13 import os 10 import os
14 import sys 11 import sys
15 import tempfile
16 12
17 13
18 # Local imports 14 # Local imports
19 from chromite.lib import text_menu 15 from chromite.lib import text_menu
20 from chromite.lib.cros_build_lib import Die 16 from chromite.lib import cros_build_lib as cros_lib
sosa 2011/02/02 01:16:19 might be cleaner if you did: import chromite.lib.
diandersAtChromium 2011/02/02 01:49:10 Done.
21 from chromite.lib.cros_build_lib import Info 17 from chromite.shell import utils
22 from chromite.lib.cros_build_lib import IsInsideChroot 18 from chromite.shell.subcmds import build_cmd
23 from chromite.lib.cros_build_lib import RunCommand 19 from chromite.shell.subcmds import clean_cmd
24 from chromite.lib.cros_build_lib import Warning as Warn 20 from chromite.shell.subcmds import portage_cmds
25 from chromite.shell import subcmd 21 from chromite.shell.subcmds import shell_cmd
22 from chromite.shell.subcmds import workon_cmd
26 23
27 24
28 # Find the Chromite root and Chromium OS root... Note: in the chroot we may 25 # Define command handlers and command strings.
29 # choose to install Chromite somewhere (/usr/lib/chromite?), so we use the
30 # environment variable to get the right place if it exists.
31 _CHROMITE_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..')
32 _SRCROOT_PATH = os.environ.get('CROS_WORKON_SRCROOT',
33 os.path.realpath(os.path.join(_CHROMITE_PATH,
34 '..')))
35
36
37 # Commands can take one of these two types of specs. Note that if a command
38 # takes a build spec, we will find the associated chroot spec. This should be
39 # a human-readable string. It is printed and also is the name of the spec
40 # directory.
41 _BUILD_SPEC_TYPE = 'build'
42 _CHROOT_SPEC_TYPE = 'chroot'
43
44
45 # This is a special target that indicates that you want to do something just
46 # to the host. This means different things to different commands.
47 # TODO(dianders): Good idea or bad idea?
48 _HOST_TARGET = 'HOST'
49
50
51 # Define command handlers and command strings. We define them in this way
52 # so that someone searching for what calls _CmdXyz can find it easy with a grep.
53 # 26 #
54 # ORDER MATTERS here when we show the menu. 27 # ORDER MATTERS here when we show the menu.
55 _COMMAND_HANDLERS = [ 28 _COMMAND_HANDLERS = [
56 'BuildCmd', 29 build_cmd.BuildCmd,
57 'CleanCmd', 30 clean_cmd.CleanCmd,
58 'EbuildCmd', 31 portage_cmds.EbuildCmd,
59 'EmergeCmd', 32 portage_cmds.EmergeCmd,
60 'EqueryCmd', 33 portage_cmds.EqueryCmd,
61 'PortageqCmd', 34 portage_cmds.PortageqCmd,
62 'ShellCmd', 35 shell_cmd.ShellCmd,
63 'WorkonCmd', 36 workon_cmd.WorkonCmd,
64 ] 37 ]
65 _COMMAND_STRS = [cls_str[:-len('Cmd')].lower() for cls_str in _COMMAND_HANDLERS] 38 _COMMAND_STRS = [cls.__name__[:-len('Cmd')].lower()
66 39 for cls in _COMMAND_HANDLERS ]
davidjames 2011/02/02 01:15:18 No need for extra spaces at the end here.
diandersAtChromium 2011/02/02 01:49:10 Done.
67
68 def _GetBoardDir(build_config):
69 """Returns the board directory (inside the chroot) given the name.
70
71 Args:
72 build_config: A SafeConfigParser representing the config that we're
73 building.
74
75 Returns:
76 The absolute path to the board.
77 """
78 target_name = build_config.get('BUILD', 'target')
79
80 # Extra checks on these, since we sometimes might do a rm -f on the board
81 # directory and these could cause some really bad behavior.
82 assert target_name, "Didn't expect blank target name."
83 assert len(target_name.split()) == 1, 'Target name should have no whitespace.'
84
85 return os.path.join('/', 'build', target_name)
86
87
88 def _GetChrootAbsDir(chroot_config):
89 """Returns the absolute chroot directory the chroot config.
90
91 Args:
92 chroot_config: A SafeConfigParser representing the config for the chroot.
93
94 Returns:
95 The chroot directory, always absolute.
96 """
97 chroot_dir = chroot_config.get('CHROOT', 'path')
98 chroot_dir = os.path.join(_SRCROOT_PATH, chroot_dir)
99
100 return chroot_dir
101
102
103 def _DoesChrootExist(chroot_config):
104 """Return True if the chroot folder exists.
105
106 Args:
107 chroot_config: A SafeConfigParser representing the config for the chroot.
108
109 Returns:
110 True if the chroot folder exists.
111 """
112 chroot_dir = _GetChrootAbsDir(chroot_config)
113 return os.path.isdir(chroot_dir)
114 40
115 41
116 def _FindCommand(cmd_name): 42 def _FindCommand(cmd_name):
117 """Find the command that matches the given command name. 43 """Find the command that matches the given command name.
118 44
119 This tries to be smart. See the cmd_name parameter for details. 45 This tries to be smart. See the cmd_name parameter for details.
120 46
121 Args: 47 Args:
122 cmd_name: Can be any of the following: 48 cmd_name: Can be any of the following:
123 1. The full name of a command. This is checked first so that if one 49 1. The full name of a command. This is checked first so that if one
(...skipping 11 matching lines...) Expand all
135 61
136 # If we're an exact match, we're done! 62 # If we're an exact match, we're done!
137 if cmd_name in _COMMAND_STRS: 63 if cmd_name in _COMMAND_STRS:
138 return cmd_name 64 return cmd_name
139 65
140 # Find ones that match and put them in a menu... 66 # Find ones that match and put them in a menu...
141 possible_cmds = [] 67 possible_cmds = []
142 possible_choices = [] 68 possible_choices = []
143 for cmd_num, this_cmd in enumerate(_COMMAND_STRS): 69 for cmd_num, this_cmd in enumerate(_COMMAND_STRS):
144 if this_cmd.startswith(cmd_name): 70 if this_cmd.startswith(cmd_name):
145 handler = eval(_COMMAND_HANDLERS[cmd_num]) 71 handler = _COMMAND_HANDLERS[cmd_num]
146 assert hasattr(handler, '__doc__'), \ 72 assert hasattr(handler, '__doc__'), \
147 ('All handlers must have docstrings: %s' % cmd_name) 73 ('All handlers must have docstrings: %s' % cmd_name)
148 desc = handler.__doc__.splitlines()[0] 74 desc = handler.__doc__.splitlines()[0]
149 75
150 possible_cmds.append(this_cmd) 76 possible_cmds.append(this_cmd)
151 possible_choices.append('%s - %s' % (this_cmd, desc)) 77 possible_choices.append('%s - %s' % (this_cmd, desc))
152 78
153 if not possible_choices: 79 if not possible_choices:
154 Die('No commands matched: "%s". ' 80 cros_lib.Die('No commands matched: "%s". '
155 'Try running with no arguments for a menu.' % 81 'Try running with no arguments for a menu.' %
156 cmd_name) 82 cmd_name)
157 83
158 if cmd_name and len(possible_choices) == 1: 84 if cmd_name and len(possible_choices) == 1:
159 # Avoid showing the user a menu if the user's search string matched exactly 85 # Avoid showing the user a menu if the user's search string matched exactly
160 # one item. 86 # one item.
161 choice = 0 87 choice = 0
162 Info("Running command '%s'." % possible_cmds[choice]) 88 cros_lib.Info("Running command '%s'." % possible_cmds[choice])
163 else: 89 else:
164 choice = text_menu.TextMenu(possible_choices, 'Which chromite command', 90 choice = text_menu.TextMenu(possible_choices, 'Which chromite command',
165 menu_width=0) 91 menu_width=0)
166 92
167 if choice is None: 93 if choice is None:
168 Die('OK, cancelling...') 94 cros_lib.Die('OK, cancelling...')
169 else: 95 else:
170 return possible_cmds[choice] 96 return possible_cmds[choice]
171 97
172 98
173 def _FindSpec(spec_name, spec_type=_BUILD_SPEC_TYPE, can_show_ui=True): 99 def main():
174 """Find the spec with the given name. 100 """Main function for the chromite shell."""
175 101
176 This tries to be smart about helping the user to find the right spec. See 102 # Hack it so that argv[0] is 'chromite' so that it doesn't tell user to run
177 the spec_name parameter for details. 103 # 'main.py' in help commands...
104 sys.argv[0] = 'chromite'
davidjames 2011/02/02 01:15:18 Can you add a TODO to remove this hack? We discuss
diandersAtChromium 2011/02/02 01:49:10 Done.
178 105
179 Args: 106 # Support EnterChroot()
180 spec_name: Can be any of the following: 107 utils.EnterChrootMainHook(sys.argv)
181 1. A full path to a spec file (including the .spec suffix). This is
182 checked first (i.e. if os.path.isfile(spec_name), we assume we've
183 got this case).
184 2. The full name of a spec file somewhere in the spec search path
185 (not including the .spec suffix). This is checked second. Putting
186 this check second means that if one spec name is a substring of
187 another, you can still specify the shorter spec name and know you
188 won't get a menu (the exact match prevents the menu).
189 3. A substring that will be used to pare-down a menu of spec files
190 found in the spec search path. Can be the empty string to show a
191 menu of all spec files in the spec path. NOTE: Only possible if
192 can_show_ui is True.
193 spec_type: The type of spec this is: 'chroot' or 'build'.
194 can_show_ui: If True, enables the spec name to be a substring since we can
195 then show a menu if the substring matches more than one thing.
196 108
197 Returns:
198 A path to the spec file.
199 """
200 spec_suffix = '.spec'
201
202 # Handle 'HOST' for spec name w/ no searching, so it counts as an exact match.
203 if spec_type == _BUILD_SPEC_TYPE and spec_name == _HOST_TARGET.lower():
204 return _HOST_TARGET
205
206 # If we have an exact path name, that's it. No searching.
207 if os.path.isfile(spec_name):
208 return spec_name
209
210 # Figure out what our search path should be.
211 # ...make these lists in anticipation of the need to support specs that live
212 # in private overlays.
213 # TODO(dianders): Should specs be part of the shell, or part of the main
214 # chromite?
215 search_path = [
216 os.path.join(_CHROMITE_PATH, 'specs', spec_type),
217 ]
218
219 # Look for an exact match of a spec name. An exact match will go through with
220 # no menu.
221 if spec_name:
222 for dir_path in search_path:
223 spec_path = os.path.join(dir_path, spec_name + spec_suffix)
224 if os.path.isfile(spec_path):
225 return spec_path
226
227 # Die right away if we can't show UI and didn't have an exact match.
228 if not can_show_ui:
229 Die("Couldn't find %s spec: %s" % (spec_type, spec_name))
230
231 # No full path and no exact match. Move onto a menu.
232 # First step is to construct the options. We'll store in a dict keyed by
233 # spec name.
234 options = {}
235 for dir_path in search_path:
236 for file_name in os.listdir(dir_path):
237 # Find any files that end with ".spec" in a case-insensitive manner.
238 if not file_name.lower().endswith(spec_suffix):
239 continue
240
241 this_spec_name, _ = os.path.splitext(file_name)
242
243 # Skip if this spec file doesn't contain our substring. We are _always_
244 # case insensitive here to be helpful to the user.
245 if spec_name.lower() not in this_spec_name.lower():
246 continue
247
248 # Disallow the spec to appear twice in the search path. This is the
249 # safest thing for now. We could revisit it later if we ever found a
250 # good reason (and if we ever have more than one directory in the
251 # search path).
252 if this_spec_name in options:
253 Die('Spec %s was found in two places in the search path' %
254 this_spec_name)
255
256 # Combine to get a full path.
257 full_path = os.path.join(dir_path, file_name)
258
259 # Ignore directories or anything else that isn't a file.
260 if not os.path.isfile(full_path):
261 continue
262
263 # OK, it's good. Store the path.
264 options[this_spec_name] = full_path
265
266 # Add 'HOST'. All caps so it sorts first.
267 if (spec_type == _BUILD_SPEC_TYPE and
268 spec_name.lower() in _HOST_TARGET.lower()):
269 options[_HOST_TARGET] = _HOST_TARGET
270
271 # If no match, die.
272 if not options:
273 Die("Couldn't find any matching %s specs for: %s" % (spec_type, spec_name))
274
275 # Avoid showing the user a menu if the user's search string matched exactly
276 # one item.
277 if spec_name and len(options) == 1:
278 _, spec_path = options.popitem()
279 return spec_path
280
281 # If more than one, show a menu...
282 option_keys = sorted(options.iterkeys())
283 choice = text_menu.TextMenu(option_keys, 'Choose a %s spec' % spec_type)
284
285 if choice is None:
286 Die('OK, cancelling...')
287 else:
288 return options[option_keys[choice]]
289
290
291 def _ReadConfig(spec_path):
292 """Read the a build config or chroot config from a spec file.
293
294 This includes adding thue proper _default stuff in.
295
296 Args:
297 spec_path: The path to the build or chroot spec.
298
299 Returns:
300 config: A SafeConfigParser representing the config.
301 """
302 spec_name, _ = os.path.splitext(os.path.basename(spec_path))
303 spec_dir = os.path.dirname(spec_path)
304
305 config = ConfigParser.SafeConfigParser({'name': spec_name})
306 config.read(os.path.join(spec_dir, '_defaults'))
307 config.read(spec_path)
308
309 return config
310
311
312 def _GetBuildConfigFromArgs(argv):
313 """Helper for commands that take a build config in the arg list.
314
315 This function can Die() in some instances.
316
317 Args:
318 argv: A list of command line arguments. If non-empty, [0] should be the
319 build spec. These will not be modified.
320
321 Returns:
322 argv: The argv with the build spec stripped off. This might be argv[1:] or
323 just argv. Not guaranteed to be new memory.
324 build_config: The SafeConfigParser for the build config; might be None if
325 this is a host config. TODO(dianders): Should there be a build spec for
326 the host?
327 """
328 # The spec name is optional. If no arguments, we'll show a menu...
329 # Note that if there are arguments, but the first argument is a flag, we'll
330 # assume that we got called before OptionParser. In that case, they might
331 # have specified options but still want the board menu.
332 if argv and not argv[0].startswith('-'):
333 spec_name = argv[0]
334 argv = argv[1:]
335 else:
336 spec_name = ''
337
338 spec_path = _FindSpec(spec_name)
339
340 if spec_path == _HOST_TARGET:
341 return argv, None
342
343 build_config = _ReadConfig(spec_path)
344
345 # TODO(dianders): Add a config checker class that makes sure that the
346 # target is not a blank string. Might also be good to make sure that the
347 # target has no whitespace (so we don't screw up a subcommand invoked
348 # through a shell).
349
350 return argv, build_config
351
352
353 def _SplitEnvFromArgs(argv):
354 """Split environment settings from arguments.
355
356 This function will just loop over the arguments, looking for ones with an '='
357 character in them. As long as it finds them, it takes them out of the arg
358 list adds them to a dictionary. As soon as it finds the first argument
359 without an '=', it stops looping.
360
361 NOTE: Some doctests below test for equality with ==, since dicts with more
362 than one item may be arbitrarily ordered.
363
364 >>> result = _SplitEnvFromArgs(['abc=1', 'def=two', 'three'])
365 >>> result == ({'abc': '1', 'def': 'two'}, ['three'])
366 True
367
368 >>> _SplitEnvFromArgs(['one', 'two', 'three'])
369 ({}, ['one', 'two', 'three'])
370
371 >>> _SplitEnvFromArgs(['abc=1', 'three', 'def=two'])
372 ({'abc': '1'}, ['three', 'def=two'])
373
374 >>> result = _SplitEnvFromArgs(['abc=1', 'ghi=4 4', 'def=two'])
375 >>> result == ({'abc': '1', 'ghi': '4 4', 'def': 'two'}, [])
376 True
377
378 >>> _SplitEnvFromArgs(['abc=1', 'abc=2', 'three'])
379 ({'abc': '2'}, ['three'])
380
381 >>> _SplitEnvFromArgs([])
382 ({}, [])
383
384 Args:
385 argv: The arguments to parse; this list is not modified. Should
386 not include "argv[0]"
387 Returns:
388 env: A dict containing key=value paris.
389 argv: A new list containing anything left after.
390 """
391 # Copy the list so we don't screw with caller...
392 argv = list(argv)
393
394 env = {}
395 while argv:
396 if '=' in argv[0]:
397 key, val = argv.pop(0).split('=', 2)
398 env[key] = val
399 else:
400 break
401
402 return env, argv
403
404
405 def _DoMakeChroot(chroot_config, clean_first):
406 """Build the chroot, if needed.
407
408 Args:
409 chroot_config: A SafeConfigParser representing the config for the chroot.
410 clean_first: Delete any old chroot first.
411 """
412 # Skip this whole command if things already exist.
413 # TODO(dianders): Theoretically, calling make_chroot a second time is OK
414 # and "fixes up" the chroot. ...but build_packages will do the fixups
415 # anyway (I think), so this isn't that important.
416 chroot_dir = _GetChrootAbsDir(chroot_config)
417 if (not clean_first) and _DoesChrootExist(chroot_config):
418 Info('%s already exists, skipping make_chroot.' % chroot_dir)
419 return
420
421 Info('MAKING THE CHROOT')
422
423 # Put together command.
424 cmd_list = [
425 './make_chroot',
426 '--chroot="%s"' % chroot_dir,
427 chroot_config.get('CHROOT', 'make_chroot_flags'),
428 ]
429 if clean_first:
430 cmd_list.insert(1, '--replace')
431
432 # We're going convert to a string and force the shell to do all of the
433 # splitting of arguments, since we're throwing all of the flags from the
434 # config file in there.
435 cmd = ' '.join(cmd_list)
436
437 # We'll put CWD as src/scripts when running the command. Since everyone
438 # running by hand has their cwd there, it is probably the safest.
439 cwd = os.path.join(_SRCROOT_PATH, 'src', 'scripts')
440
441 # Run it. Exceptions will cause the program to exit.
442 RunCommand(cmd, shell=True, cwd=cwd, ignore_sigint=True)
443
444
445 def _DoEnterChroot(chroot_config, fn, *args, **kwargs):
446 """Re-run the given function inside the chroot.
447
448 When the function is run, it will be run in a SEPARATE INSTANCE of chromite,
449 which will be run in the chroot. This is a little weird. Specifically:
450 - When the callee executes, it will be a separate python instance.
451 - Globals will be reset back to defaults.
452 - A different version of python (with different modules) may end up running
453 the script in the chroot.
454 - All arguments are pickled up into a temp file, whose path is passed on the
455 command line.
456 - That means that args must be pickleable.
457 - It also means that modifications to the parameters by the callee are not
458 visible to the caller.
459 - Even the function is "pickled". The way the pickle works, I belive, is it
460 just passes the name of the function. If this name somehow resolves
461 differently in the chroot, you may get weirdness.
462 - Since we're in the chroot, obviously files may have different paths. It's
463 up to you to convert parameters if you need to.
464 - The stdin, stdout, and stderr aren't touched.
465
466 Args:
467 chroot_config: A SafeConfigParser representing the config for the chroot.
468 fn: Either: a) the function to call or b) A tuple of an object and the
469 name of the method to call.
470 args: All other arguments will be passed to the function as is.
471 kwargs: All other arguments will be passed to the function as is.
472 """
473 # Make sure that the chroot exists...
474 chroot_dir = _GetChrootAbsDir(chroot_config)
475 if not _DoesChrootExist(chroot_config):
476 Die('Chroot dir does not exist; try the "build host" command.\n %s.' %
477 chroot_dir)
478
479 Info('ENTERING THE CHROOT')
480
481 # Save state to a temp file (inside the chroot!) using pickle.
482 tmp_dir = os.path.join(chroot_dir, 'tmp')
483 state_file = tempfile.NamedTemporaryFile(prefix='chromite', dir=tmp_dir)
484 try:
485 cPickle.dump((fn, args, kwargs), state_file, cPickle.HIGHEST_PROTOCOL)
486 state_file.flush()
487
488 # Translate temp file name into a chroot path...
489 chroot_state_path = os.path.join('/tmp', os.path.basename(state_file.name))
490
491 # Put together command. We're going to force the shell to do all of the
492 # splitting of arguments, since we're throwing all of the flags from the
493 # config file in there.
494 # TODO(dianders): It might be nice to run chromite as:
495 # python -m chromite.chromite_main
496 # ...but, at the moment, that fails if you're in src/scripts
497 # which already has a chromite folder.
498 cmd = (
499 './enter_chroot.sh --chroot="%s" %s --'
500 ' python ../../chromite/shell/main.py --resume-state %s') % (
501 chroot_dir,
502 chroot_config.get('CHROOT', 'enter_chroot_flags'),
503 chroot_state_path)
504
505 # We'll put CWD as src/scripts when running the command. Since everyone
506 # running by hand has their cwd there, it is probably the safest.
507 cwd = os.path.join(_SRCROOT_PATH, 'src', 'scripts')
508
509 # Run it. We allow "error" so we don't print a confusing error message
510 # filled with out resume-state garbage on control-C.
511 cmd_result = RunCommand(cmd, shell=True, cwd=cwd, print_cmd=False,
512 exit_code=True, error_ok=True, ignore_sigint=True)
513
514 if cmd_result.returncode:
515 Die('Chroot exited with error code %d' % cmd_result.returncode)
516 finally:
517 # Make sure things get closed (and deleted), even upon exception.
518 state_file.close()
519
520
521 def _DoSetupBoard(build_config, clean_first):
522 """Setup the board, if needed.
523
524 This just runs the setup_board command with the proper args, if needed.
525
526 Args:
527 build_config: A SafeConfigParser representing the build config.
528 clean_first: Delete any old board config first.
529 """
530 # Skip this whole command if things already exist.
531 board_dir = _GetBoardDir(build_config)
532 if (not clean_first) and os.path.isdir(board_dir):
533 Info('%s already exists, skipping setup_board.' % board_dir)
534 return
535
536 Info('SETTING UP THE BOARD')
537
538 # Put together command.
539 cmd_list = [
540 './setup_board',
541 '--board="%s"' % build_config.get('BUILD', 'target'),
542 build_config.get('BUILD', 'setup_board_flags'),
543 ]
544 if clean_first:
545 cmd_list.insert(1, '--force')
546
547 # We're going convert to a string and force the shell to do all of the
548 # splitting of arguments, since we're throwing all of the flags from the
549 # config file in there.
550 cmd = ' '.join(cmd_list)
551
552 # We'll put CWD as src/scripts when running the command. Since everyone
553 # running by hand has their cwd there, it is probably the safest.
554 cwd = os.path.join(_SRCROOT_PATH, 'src', 'scripts')
555
556 # Run it. Exceptions will cause the program to exit.
557 RunCommand(cmd, shell=True, cwd=cwd, ignore_sigint=True)
558
559
560 def _DoBuildPackages(build_config):
561 """Build packages.
562
563 This just runs the build_packages command with the proper args.
564
565 Args:
566 build_config: A SafeConfigParser representing the build config.
567 """
568 Info('BUILDING PACKAGES')
569
570 # Put together command. We're going to force the shell to do all of the
571 # splitting of arguments, since we're throwing all of the flags from the
572 # config file in there.
573 cmd = './build_packages --board="%s" %s' % (
574 build_config.get('BUILD', 'target'),
575 build_config.get('BUILD', 'build_packages_flags')
576 )
577
578 # We'll put CWD as src/scripts when running the command. Since everyone
579 # running by hand has their cwd there, it is probably the safest.
580 cwd = os.path.join(_SRCROOT_PATH, 'src', 'scripts')
581
582 # Run it. Exceptions will cause the program to exit.
583 RunCommand(cmd, shell=True, cwd=cwd, ignore_sigint=True)
584
585
586 def _DoBuildImage(build_config):
587 """Build an image.
588
589 This just runs the build_image command with the proper args.
590
591 Args:
592 build_config: A SafeConfigParser representing the build config.
593 """
594 Info('BUILDING THE IMAGE')
595
596 # Put together command. We're going to force the shell to do all of the
597 # splitting of arguments, since we're throwing all of the flags from the
598 # config file in there.
599 cmd = './build_image --board="%s" %s' % (
600 build_config.get('BUILD', 'target'),
601 build_config.get('IMAGE', 'build_image_flags')
602 )
603
604 # We'll put CWD as src/scripts when running the command. Since everyone
605 # running by hand has their cwd there, it is probably the safest.
606 cwd = os.path.join(_SRCROOT_PATH, 'src', 'scripts')
607
608 # Run it. Exceptions will cause the program to exit.
609 RunCommand(cmd, shell=True, cwd=cwd, ignore_sigint=True)
610
611
612 def _DoClean(chroot_config, build_config, want_force_yes):
613 """Clean a target.
614
615 Args:
616 chroot_config: A SafeConfigParser representing the config for the chroot.
617 build_config: A SafeConfigParser representing the build config.
618 want_force_yes: If True, we won't ask any questions--we'll just assume
619 that the user really wants to kill the directory. If False, we'll
620 show UI asking the user to confirm.
621 """
622 # We'll need the directory so we can delete stuff; this is a chroot path.
623 board_dir = _GetBoardDir(build_config)
624
625 # If not in the chroot, convert board_dir into a non-chroot path...
626 if not IsInsideChroot():
627 chroot_dir = _GetChrootAbsDir(chroot_config)
628
629 # We'll need to make the board directory relative to the chroot.
630 assert board_dir.startswith('/'), 'Expected unix-style, absolute path.'
631 board_dir = board_dir.lstrip('/')
632 board_dir = os.path.join(chroot_dir, board_dir)
633
634 if not os.path.isdir(board_dir):
635 Die("Nothing to clean: the board directory doesn't exist.\n %s" %
636 board_dir)
637
638 if not want_force_yes:
639 sys.stderr.write('\n'
640 'Board dir is at: %s\n'
641 'Are you sure you want to delete it (YES/NO)? ' %
642 board_dir)
643 answer = raw_input()
644 if answer.lower() not in ('y', 'ye', 'yes'):
645 Die("You must answer 'yes' if you want to proceed.")
646
647 # Since we're about to do a sudo rm -rf, these are just extra precautions.
648 # This shouldn't be the only place testing these (assert fails are ugly and
649 # can be turned off), but better safe than sorry.
650 # Note that the restriction on '*' is a bit unnecessary, since no shell
651 # expansion should happen. ...but again, I'd rather be safe.
652 assert os.path.isabs(board_dir), 'Board dir better be absolute'
653 assert board_dir != '/', 'Board dir better not be /'
654 assert '*' not in board_dir, 'Board dir better not have any *s'
655 assert build_config.get('BUILD', 'target'), 'Target better not be blank'
656 assert build_config.get('BUILD', 'target') in board_dir, \
657 'Target name better be in board dir'
658
659 argv = ['sudo', '--', 'rm', '-rf', board_dir]
660 RunCommand(argv)
661 Info('Deleted: %s' % board_dir)
662
663
664 def _DoDistClean(chroot_config, want_force_yes):
665 """Remove the whole chroot.
666
667 Args:
668 chroot_config: A SafeConfigParser representing the config for the chroot.
669 want_force_yes: If True, we won't ask any questions--we'll just assume
670 that the user really wants to kill the directory. If False, we'll
671 show UI asking the user to confirm.
672 """
673 if IsInsideChroot():
674 Die('Please exit the chroot before trying to delete it.')
675
676 chroot_dir = _GetChrootAbsDir(chroot_config)
677 if not want_force_yes:
678 sys.stderr.write('\n'
679 'Chroot is at: %s\n'
680 'Are you sure you want to delete it (YES/NO)? ' %
681 chroot_dir)
682 answer = raw_input()
683 if answer.lower() not in ('y', 'ye', 'yes'):
684 Die("You must answer 'yes' if you want to proceed.")
685
686 # Can pass argv and not shell=True, since no user flags. :)
687 argv = ['./make_chroot', '--chroot=%s' % chroot_dir, '--delete']
688
689 # We'll put CWD as src/scripts when running the command. Since everyone
690 # running by hand has their cwd there, it is probably the safest.
691 cwd = os.path.join(_SRCROOT_PATH, 'src', 'scripts')
692
693 # Run it. Pass any failures upward.
694 RunCommand(argv, cwd=cwd)
695
696
697 class WrappedChrootCmd(subcmd.ChromiteCmd):
698 """Superclass for any command that is simply wrapped by chromite.
699
700 These are commands where:
701 - We parse the command line only enough to figure out what board they
702 want. All othe command line parsing is handled by the wrapped command.
703 Because of this, the board name _needs_ to be specified first.
704 - Everything else (arg parsing, help, etc) is handled by the wrapped command.
705 The usage string will be a little messed up, but hopefully that's OK.
706 """
707
708 def __init__(self, target_cmd, host_cmd, need_args=False):
709 """WrappedChrootCmd constructor.
710
711 Args:
712 target_cmd: We'll put this at the start of argv when calling a target
713 command. We'll substiture %s with the target.
714 Like - ['my_command-%s'] or ['my_command', '--board=%s']
715 host_cmd: We'll put this at the start of argv when calling a host command.
716 Like - ['my_command'] or ['sudo', 'my_command']
717 need_args: If True, we'll prompt for arguments if they weren't specified.
718 This makes the most sense when someone runs chromite with no arguments
719 and then walks through the menus. It's not ideal, but less sucky than
720 just quitting.
721 """
722 # Call superclass constructor.
723 super(WrappedChrootCmd, self).__init__()
724
725 # Save away params for use later in Run().
726 self._target_cmd = target_cmd
727 self._host_cmd = host_cmd
728 self._need_args = need_args
729
730 def Run(self, raw_argv, chroot_config=None, argv=None, build_config=None):
731 """Run the command.
732
733 Args:
734 raw_argv: Command line arguments, including this command's name, but not
735 the chromite command name or chromite options.
736 chroot_config: A SafeConfigParser for the chroot config; or None chromite
737 was called from within the chroot.
738 argv: None when called normally, but contains argv with board stripped off
739 if we call ourselves with _DoEnterChroot().
740 build_config: None when called normally, but contains the SafeConfigParser
741 for the build config if we call ourselves with _DoEnterChroot(). Note
742 that even when called through _DoEnterChroot(), could still be None
743 if user chose 'HOST' as the target.
744 """
745 # If we didn't get called through EnterChroot, we need to read the build
746 # config.
747 if argv is None:
748 # We look for the build config without calling OptionParser. This means
749 # that the board _needs_ to be first (if it's specified) and all options
750 # will be passed straight to our subcommand.
751 argv, build_config = _GetBuildConfigFromArgs(raw_argv[1:])
752
753 # Enter the chroot if needed...
754 if not IsInsideChroot():
755 _DoEnterChroot(chroot_config, (self, 'Run'), raw_argv, argv=argv,
756 build_config=build_config)
757 else:
758 # We'll put CWD as src/scripts when running the command. Since everyone
759 # running by hand has their cwd there, it is probably the safest.
760 cwd = os.path.join(_SRCROOT_PATH, 'src', 'scripts')
761
762 # Get command to call. If build_config is None, it means host.
763 if build_config is None:
764 argv_prefix = self._host_cmd
765 else:
766 # Make argv_prefix w/ target.
767 target_name = build_config.get('BUILD', 'target')
768 argv_prefix = [arg % target_name for arg in self._target_cmd]
769
770 # Not a great way to to specify arguments, but works for now... Wrapped
771 # commands are not wonderful interfaces anyway...
772 if self._need_args and not argv:
773 while True:
774 sys.stderr.write('arg %d (blank to exit): ' % (len(argv)+1))
775 arg = raw_input()
776 if not arg:
777 break
778 argv.append(arg)
779
780 # Add the prefix...
781 argv = argv_prefix + argv
782
783 # Run, ignoring errors since some commands (ahem, cros_workon) seem to
784 # return errors from things like --help.
785 RunCommand(argv, cwd=cwd, ignore_sigint=True, error_ok=True)
786
787
788 class BuildCmd(subcmd.ChromiteCmd):
789 """Build the chroot (if needed), the packages for a target, and the image."""
790
791 def Run(self, raw_argv, chroot_config=None,
792 loaded_config=False, build_config=None):
793 """Run the command.
794
795 Args:
796 raw_argv: Command line arguments, including this command's name, but not
797 the chromite command name or chromite options.
798 chroot_config: A SafeConfigParser for the chroot config; or None chromite
799 was called from within the chroot.
800 loaded_config: If True, we've already loaded the config.
801 build_config: None when called normally, but contains the SafeConfigParser
802 for the build config if we call ourselves with _DoEnterChroot(). Note
803 that even when called through _DoEnterChroot(), could still be None
804 if user chose 'HOST' as the target.
805 """
806 # Parse options for command...
807 usage_str = ('usage: %%prog [chromite_options] %s [options] [target]' %
808 raw_argv[0])
809 parser = optparse.OptionParser(usage=usage_str)
810 parser.add_option('--clean', default=False, action='store_true',
811 help='Clean before building.')
812 (options, argv) = parser.parse_args(raw_argv[1:])
813
814 # Load the build config if needed...
815 if not loaded_config:
816 argv, build_config = _GetBuildConfigFromArgs(argv)
817 if argv:
818 Die('Unknown arguments: %s' % ' '.join(argv))
819
820 if not IsInsideChroot():
821 # Note: we only want to clean the chroot if they do --clean and have the
822 # host target. If they do --clean and have a board target, it means
823 # that they just want to clean the board...
824 want_clean_chroot = options.clean and build_config is None
825
826 _DoMakeChroot(chroot_config, want_clean_chroot)
827
828 if build_config is not None:
829 _DoEnterChroot(chroot_config, (self, 'Run'), raw_argv,
830 build_config=build_config, loaded_config=True)
831
832 Info('Done building.')
833 else:
834 if build_config is None:
835 Die("You can't build the host chroot from within the chroot.")
836
837 _DoSetupBoard(build_config, options.clean)
838 _DoBuildPackages(build_config)
839 _DoBuildImage(build_config)
840
841
842 class WorkonCmd(WrappedChrootCmd):
843 """Run cros_workon."""
844
845 def __init__(self):
846 """WorkonCmd constructor."""
847 # Just call the WrappedChrootCmd superclass, which does most of the work.
848 # Note that host version uses "./", since it's in src/scripts and not in the
849 # path...
850 super(WorkonCmd, self).__init__(
851 ['cros_workon-%s'], ['./cros_workon', '--host'],
852 need_args=True
853 )
854
855 def Run(self, raw_argv, *args, **kwargs):
856 """Run the command.
857
858 We do just a slight optimization to help users with a common typo.
859
860 Args:
861 raw_argv: Command line arguments, including this command's name, but not
862 the chromite command name or chromite options.
863 args: The rest of the positional arguments. See _DoWrappedChrootCommand.
864 kwargs: The keyword arguments. See _DoWrappedChrootCommand.
865 """
866 # Slight optimization, just since I do this all the time...
867 if len(raw_argv) >= 2:
868 if raw_argv[1] in ('start', 'stop', 'list', 'list-all', 'iterate'):
869 Warn('OOPS, looks like you forgot a board name. Pick one.')
870 raw_argv = raw_argv[:1] + [''] + raw_argv[1:]
871
872 super(WorkonCmd, self).Run(raw_argv, *args, **kwargs)
873
874
875 class EbuildCmd(WrappedChrootCmd):
876 """Run ebuild."""
877
878 def __init__(self):
879 """EbuildCmd constructor."""
880 # Just call the WrappedChrootCmd superclass, which does most of the work.
881 super(EbuildCmd, self).__init__(
882 ['ebuild-%s'], ['ebuild'],
883 need_args=True
884 )
885
886
887 class EmergeCmd(WrappedChrootCmd):
888 """Run emerge."""
889
890 def __init__(self):
891 """EmergeCmd constructor."""
892 # Just call the WrappedChrootCmd superclass, which does most of the work.
893 super(EmergeCmd, self).__init__(
894 ['emerge-%s'], ['sudo', 'emerge'],
895 need_args=True
896 )
897
898
899 class EqueryCmd(WrappedChrootCmd):
900 """Run equery."""
901
902 def __init__(self):
903 """EqueryCmd constructor."""
904 # Just call the WrappedChrootCmd superclass, which does most of the work.
905 super(EqueryCmd, self).__init__(
906 ['equery-%s'], ['equery'],
907 need_args=True
908 )
909
910
911 class PortageqCmd(WrappedChrootCmd):
912 """Run portageq."""
913
914 def __init__(self):
915 """PortageqCmd constructor."""
916 # Just call the WrappedChrootCmd superclass, which does most of the work.
917 super(PortageqCmd, self).__init__(
918 ['portageq-%s'], ['portageq'],
919 need_args=True
920 )
921
922
923 class ShellCmd(subcmd.ChromiteCmd):
924 """Start a shell in the chroot.
925
926 This can either just start a simple interactive shell, it can be used to
927 run an arbirtary command inside the chroot and then exit.
928 """
929
930 def Run(self, raw_argv, chroot_config=None):
931 """Run the command.
932
933 Args:
934 raw_argv: Command line arguments, including this command's name, but not
935 the chromite command name or chromite options.
936 chroot_config: A SafeConfigParser for the chroot config; or None chromite
937 was called from within the chroot.
938 """
939 # Parse options for command...
940 # ...note that OptionParser will eat the '--' if it's there, which is what
941 # we want..
942 usage_str = ('usage: %%prog [chromite_options] %s [options] [VAR=value] '
943 '[-- command [arg1] [arg2] ...]') % raw_argv[0]
944 parser = optparse.OptionParser(usage=usage_str)
945 (_, argv) = parser.parse_args(raw_argv[1:])
946
947 # Enter the chroot if needed...
948 if not IsInsideChroot():
949 _DoEnterChroot(chroot_config, (self, 'Run'), raw_argv)
950 else:
951 # We'll put CWD as src/scripts when running the command. Since everyone
952 # running by hand has their cwd there, it is probably the safest.
953 cwd = os.path.join(_SRCROOT_PATH, 'src', 'scripts')
954
955 # By default, no special environment...
956 env = None
957
958 if not argv:
959 # If no arguments, we'll just start bash.
960 argv = ['bash']
961 else:
962 # Parse the command line, looking at the beginning for VAR=value type
963 # statements. I couldn't figure out a way to get bash to do this for
964 # me.
965 user_env, argv = _SplitEnvFromArgs(argv)
966 if not argv:
967 Die('No command specified')
968
969 # If there was some environment, use it to override the standard
970 # environment.
971 if user_env:
972 env = dict(os.environ)
973 env.update(user_env)
974
975 # Don't show anything special for errors; we'll let the shell report them.
976 RunCommand(argv, cwd=cwd, env=env, error_ok=True, ignore_sigint=True)
977
978
979 #def _CmdClean(raw_argv, chroot_config=None):
980 class CleanCmd(subcmd.ChromiteCmd):
981 """Clean out built packages for a target; if target=host, deletes chroot."""
982
983 def Run(self, raw_argv, chroot_config=None):
984 """Run the command.
985
986 Args:
987 raw_argv: Command line arguments, including this command's name, but not
988 the chromite command name or chromite options.
989 chroot_config: A SafeConfigParser for the chroot config; or None chromite
990 was called from within the chroot.
991 """
992 # Parse options for command...
993 usage_str = ('usage: %%prog [chromite_options] %s [options] [target]' %
994 raw_argv[0])
995 parser = optparse.OptionParser(usage=usage_str)
996 parser.add_option('-y', '--yes', default=False, action='store_true',
997 help='Answer "YES" to "are you sure?" questions.')
998 (options, argv) = parser.parse_args(raw_argv[1:])
999
1000 # Make sure the chroot exists first, before possibly prompting for board...
1001 # ...not really required, but nice for the user...
1002 if not IsInsideChroot():
1003 if not _DoesChrootExist(chroot_config):
1004 Die("Nothing to clean: the chroot doesn't exist.\n %s" %
1005 _GetChrootAbsDir(chroot_config))
1006
1007 # Load the build config...
1008 argv, build_config = _GetBuildConfigFromArgs(argv)
1009 if argv:
1010 Die('Unknown arguments: %s' % ' '.join(argv))
1011
1012 # If they do clean host, we'll delete the whole chroot
1013 if build_config is None:
1014 _DoDistClean(chroot_config, options.yes)
1015 else:
1016 _DoClean(chroot_config, build_config, options.yes)
1017
1018
1019 def main():
1020 # TODO(dianders): Make help a little better. Specifically: 109 # TODO(dianders): Make help a little better. Specifically:
1021 # 1. Add a command called 'help' 110 # 1. Add a command called 'help'
1022 # 2. Make the help string below include command list and descriptions (like 111 # 2. Make the help string below include command list and descriptions (like
1023 # the menu, but without being interactive). 112 # the menu, but without being interactive).
1024 # 3. Make "help command" and "--help command" equivalent to "command --help". 113 # 3. Make "help command" and "--help command" equivalent to "command --help".
1025 help_str = ( 114 help_str = (
1026 """Usage: %(prog)s [chromite_options] [cmd [args]]\n""" 115 """Usage: %(prog)s [chromite_options] [cmd [args]]\n"""
1027 """\n""" 116 """\n"""
1028 """The chromite script is a wrapper to make it easy to do various\n""" 117 """The chromite script is a wrapper to make it easy to do various\n"""
1029 """build tasks. For a list of commands, run without any arguments.\n""" 118 """build tasks. For a list of commands, run without any arguments.\n"""
1030 """\n""" 119 """\n"""
1031 """Options:\n""" 120 """Options:\n"""
1032 """ -h, --help show this help message and exit\n""" 121 """ -h, --help show this help message and exit\n"""
1033 ) % {'prog': os.path.basename(sys.argv[0])} 122 ) % {'prog': os.path.basename(sys.argv[0])}
1034 if not IsInsideChroot(): 123 if not cros_lib.IsInsideChroot():
1035 help_str += ( 124 help_str += (
1036 """ --chroot=CHROOT_NAME Chroot spec to use. Can be an absolute\n""" 125 """ --chroot=CHROOT_NAME Chroot spec to use. Can be an absolute\n"""
1037 """ path to a spec file or a substring of a\n""" 126 """ path to a spec file or a substring of a\n"""
1038 """ chroot spec name (without .spec suffix)\n""" 127 """ chroot spec name (without .spec suffix)\n"""
1039 ) 128 )
1040 129
1041 # We don't use OptionParser here, since options for different subcommands are 130 # We don't use OptionParser here, since options for different subcommands are
1042 # so different. We just look for the chromite options here... 131 # so different. We just look for the chromite options here...
1043 if sys.argv[1:2] == ['--help']: 132 if sys.argv[1:2] == ['--help']:
1044 print help_str 133 print help_str
1045 sys.exit(0) 134 sys.exit(0)
1046 elif sys.argv[1:2] == ['--resume-state']:
1047 # Internal mechanism (not documented to users) to resume in the chroot.
1048 # ...actual resume state file is passed in sys.argv[2] for simplicity...
1049 assert len(sys.argv) == 3, 'Resume State not passed properly.'
1050 fn, args, kwargs = cPickle.load(open(sys.argv[2], 'rb'))
1051
1052 # Handle calling a method in a class; that can't be directly pickled.
1053 if isinstance(fn, tuple):
1054 obj, method = fn
1055 fn = getattr(obj, method)
1056
1057 fn(*args, **kwargs)
1058 else: 135 else:
1059 # Start by skipping argv[0] 136 # Start by skipping argv[0]
1060 argv = sys.argv[1:] 137 argv = sys.argv[1:]
1061 138
1062 # Look for special "--chroot" argument to allow for alternate chroots 139 # Look for special "--chroot" argument to allow for alternate chroots
1063 if not IsInsideChroot(): 140 if not cros_lib.IsInsideChroot():
1064 # Default chroot name... 141 # Default chroot name...
1065 chroot_name = 'chroot' 142 chroot_name = 'chroot'
1066 143
1067 # Get chroot spec name if specified; trim argv down if needed... 144 # Get chroot spec name if specified; trim argv down if needed...
1068 if argv: 145 if argv:
1069 if argv[0].startswith('--chroot='): 146 if argv[0].startswith('--chroot='):
1070 _, chroot_name = argv[0].split('=', 2) 147 _, chroot_name = argv[0].split('=', 2)
1071 argv = argv[1:] 148 argv = argv[1:]
1072 elif argv[0] == '--chroot': 149 elif argv[0] == '--chroot':
1073 if len(argv) < 2: 150 if len(argv) < 2:
1074 Die('Chroot not specified.') 151 cros_lib.Die('Chroot not specified.')
1075 152
1076 chroot_name = argv[1] 153 chroot_name = argv[1]
1077 argv = argv[2:] 154 argv = argv[2:]
1078 155
1079 chroot_spec_path = _FindSpec(chroot_name, spec_type=_CHROOT_SPEC_TYPE) 156 chroot_spec_path = utils.FindSpec(chroot_name,
157 spec_type=utils.CHROOT_SPEC_TYPE)
1080 158
1081 Info('Using chroot "%s"' % os.path.relpath(chroot_spec_path)) 159 cros_lib.Info('Using chroot "%s"' % os.path.relpath(chroot_spec_path))
1082 160
1083 chroot_config = _ReadConfig(chroot_spec_path) 161 chroot_config = utils.ReadConfig(chroot_spec_path)
1084 else: 162 else:
1085 # Already in the chroot; no need to get config... 163 # Already in the chroot; no need to get config...
1086 chroot_config = None 164 chroot_config = None
1087 165
1088 # Get command and arguments 166 # Get command and arguments
1089 if argv: 167 if argv:
1090 cmd_str = argv[0].lower() 168 cmd_str = argv[0].lower()
1091 argv = argv[1:] 169 argv = argv[1:]
1092 else: 170 else:
1093 cmd_str = '' 171 cmd_str = ''
1094 172
1095 # Validate the subcmd, popping a menu if needed. 173 # Validate the subcmd, popping a menu if needed.
1096 cmd_str = _FindCommand(cmd_str) 174 cmd_str = _FindCommand(cmd_str)
1097 175
1098 # Finally, call the function w/ standard argv. 176 # Finally, call the function w/ standard argv.
1099 cmd_cls = eval(_COMMAND_HANDLERS[_COMMAND_STRS.index(cmd_str)]) 177 cmd_cls = _COMMAND_HANDLERS[_COMMAND_STRS.index(cmd_str)]
davidjames 2011/02/02 01:15:18 Thank you! No eval please :)
diandersAtChromium 2011/02/02 01:49:10 Done.
1100 cmd_obj = cmd_cls() 178 cmd_obj = cmd_cls()
1101 cmd_obj.Run([cmd_str] + argv, chroot_config=chroot_config) 179 cmd_obj.Run([cmd_str] + argv, chroot_config=chroot_config)
1102 180
1103 181
1104 if __name__ == '__main__': 182 if __name__ == '__main__':
1105 main() 183 main()
OLDNEW
« no previous file with comments | « no previous file | shell/main_unittest.py » ('j') | shell/subcmd.py » ('J')

Powered by Google App Engine
This is Rietveld 408576698