blob: eea7c4ad7aec191dc9983b3d5adfe1b35c84c78e [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
10import ConfigParser
11import cPickle
12import optparse
13import os
14import sys
15import tempfile
16
17
Doug Anderson4b6d3962011-01-21 09:41:50 -080018# Local imports
Doug Andersone91900d2011-01-26 11:37:17 -080019from chromite.lib import text_menu
20from chromite.lib.cros_build_lib import Die
21from chromite.lib.cros_build_lib import Info
22from chromite.lib.cros_build_lib import IsInsideChroot
23from chromite.lib.cros_build_lib import RunCommand
24from chromite.lib.cros_build_lib import Warning as Warn
25from chromite.shell import subcmd
Doug Andersonf0c73952011-01-18 13:46:07 -080026
27
Doug Anderson359535e2011-01-19 13:53:35 -080028# Find the Chromite root and Chromium OS root... Note: in the chroot we may
29# choose to install Chromite somewhere (/usr/lib/chromite?), so we use the
30# environment variable to get the right place if it exists.
Doug Andersone91900d2011-01-26 11:37:17 -080031_CHROMITE_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..')
Doug Anderson359535e2011-01-19 13:53:35 -080032_SRCROOT_PATH = os.environ.get('CROS_WORKON_SRCROOT',
33 os.path.realpath(os.path.join(_CHROMITE_PATH,
34 '..')))
Doug Andersonf0c73952011-01-18 13:46:07 -080035
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#
54# ORDER MATTERS here when we show the menu.
Doug Andersonf0c73952011-01-18 13:46:07 -080055_COMMAND_HANDLERS = [
Doug Anderson4b6d3962011-01-21 09:41:50 -080056 'BuildCmd',
57 'CleanCmd',
58 'EbuildCmd',
59 'EmergeCmd',
60 'EqueryCmd',
61 'PortageqCmd',
62 'ShellCmd',
63 'WorkonCmd',
Doug Andersonf0c73952011-01-18 13:46:07 -080064]
Doug Anderson4b6d3962011-01-21 09:41:50 -080065_COMMAND_STRS = [cls_str[:-len('Cmd')].lower() for cls_str in _COMMAND_HANDLERS]
Doug Andersonf0c73952011-01-18 13:46:07 -080066
67
68def _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
88def _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
103def _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
115
116def _FindCommand(cmd_name):
117 """Find the command that matches the given command name.
118
119 This tries to be smart. See the cmd_name parameter for details.
120
121 Args:
122 cmd_name: Can be any of the following:
123 1. The full name of a command. This is checked first so that if one
124 command name is a substring of another, you can still specify
125 the shorter spec name and know you won't get a menu (the exact
126 match prevents the menu).
127 2. A _prefix_ that will be used to pare-down a menu of commands
128 Can be the empty string to show a menu of all commands
129
130 Returns:
131 The command name.
132 """
133 # Always make cmd_name lower. Commands are case-insensitive.
134 cmd_name = cmd_name.lower()
135
136 # If we're an exact match, we're done!
137 if cmd_name in _COMMAND_STRS:
138 return cmd_name
139
140 # Find ones that match and put them in a menu...
141 possible_cmds = []
142 possible_choices = []
143 for cmd_num, this_cmd in enumerate(_COMMAND_STRS):
144 if this_cmd.startswith(cmd_name):
145 handler = eval(_COMMAND_HANDLERS[cmd_num])
146 assert hasattr(handler, '__doc__'), \
147 ('All handlers must have docstrings: %s' % cmd_name)
148 desc = handler.__doc__.splitlines()[0]
149
150 possible_cmds.append(this_cmd)
151 possible_choices.append('%s - %s' % (this_cmd, desc))
152
153 if not possible_choices:
154 Die('No commands matched: "%s". '
155 'Try running with no arguments for a menu.' %
156 cmd_name)
157
158 if cmd_name and len(possible_choices) == 1:
159 # Avoid showing the user a menu if the user's search string matched exactly
160 # one item.
161 choice = 0
162 Info("Running command '%s'." % possible_cmds[choice])
163 else:
164 choice = text_menu.TextMenu(possible_choices, 'Which chromite command',
165 menu_width=0)
166
167 if choice is None:
168 Die('OK, cancelling...')
169 else:
170 return possible_cmds[choice]
171
172
173def _FindSpec(spec_name, spec_type=_BUILD_SPEC_TYPE, can_show_ui=True):
174 """Find the spec with the given name.
175
176 This tries to be smart about helping the user to find the right spec. See
177 the spec_name parameter for details.
178
179 Args:
180 spec_name: Can be any of the following:
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
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.
Doug Andersone91900d2011-01-26 11:37:17 -0800213 # TODO(dianders): Should specs be part of the shell, or part of the main
214 # chromite?
Doug Andersonf0c73952011-01-18 13:46:07 -0800215 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
291def _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
312def _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
353def _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
405def _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
445def _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.
Doug Anderson4b6d3962011-01-21 09:41:50 -0800468 fn: Either: a) the function to call or b) A tuple of an object and the
469 name of the method to call.
Doug Andersonf0c73952011-01-18 13:46:07 -0800470 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.
Doug Andersone91900d2011-01-26 11:37:17 -0800494 # 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.
Doug Andersonf0c73952011-01-18 13:46:07 -0800498 cmd = (
499 './enter_chroot.sh --chroot="%s" %s --'
Doug Andersone91900d2011-01-26 11:37:17 -0800500 ' python ../../chromite/shell/main.py --resume-state %s') % (
Doug Andersonf0c73952011-01-18 13:46:07 -0800501 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
521def _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
560def _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
586def _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
612def _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
664def _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
Doug Andersone91900d2011-01-26 11:37:17 -0800697class WrappedChrootCmd(subcmd.ChromiteCmd):
Doug Anderson4b6d3962011-01-21 09:41:50 -0800698 """Superclass for any command that is simply wrapped by chromite.
Doug Andersonf0c73952011-01-18 13:46:07 -0800699
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.
Doug Andersonf0c73952011-01-18 13:46:07 -0800706 """
Doug Andersonf0c73952011-01-18 13:46:07 -0800707
Doug Anderson4b6d3962011-01-21 09:41:50 -0800708 def __init__(self, target_cmd, host_cmd, need_args=False):
709 """WrappedChrootCmd constructor.
Doug Andersonf0c73952011-01-18 13:46:07 -0800710
Doug Anderson4b6d3962011-01-21 09:41:50 -0800711 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)
Doug Andersonf0c73952011-01-18 13:46:07 -0800757 else:
Doug Anderson4b6d3962011-01-21 09:41:50 -0800758 # 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')
Doug Andersonf0c73952011-01-18 13:46:07 -0800761
Doug Anderson4b6d3962011-01-21 09:41:50 -0800762 # 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]
Doug Andersonf0c73952011-01-18 13:46:07 -0800769
Doug Anderson4b6d3962011-01-21 09:41:50 -0800770 # 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)
Doug Andersonf0c73952011-01-18 13:46:07 -0800779
Doug Anderson4b6d3962011-01-21 09:41:50 -0800780 # 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)
Doug Andersonf0c73952011-01-18 13:46:07 -0800786
787
Doug Andersone91900d2011-01-26 11:37:17 -0800788class BuildCmd(subcmd.ChromiteCmd):
Doug Anderson4b6d3962011-01-21 09:41:50 -0800789 """Build the chroot (if needed), the packages for a target, and the image."""
Doug Andersonf0c73952011-01-18 13:46:07 -0800790
Doug Anderson4b6d3962011-01-21 09:41:50 -0800791 def Run(self, raw_argv, chroot_config=None,
792 loaded_config=False, build_config=None):
793 """Run the command.
Doug Andersonf0c73952011-01-18 13:46:07 -0800794
Doug Anderson4b6d3962011-01-21 09:41:50 -0800795 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:])
Doug Andersonf0c73952011-01-18 13:46:07 -0800813
Doug Anderson4b6d3962011-01-21 09:41:50 -0800814 # 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))
Doug Andersonf0c73952011-01-18 13:46:07 -0800819
Doug Anderson4b6d3962011-01-21 09:41:50 -0800820 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
Doug Andersonf0c73952011-01-18 13:46:07 -0800825
Doug Anderson4b6d3962011-01-21 09:41:50 -0800826 _DoMakeChroot(chroot_config, want_clean_chroot)
Doug Andersonf0c73952011-01-18 13:46:07 -0800827
Doug Anderson4b6d3962011-01-21 09:41:50 -0800828 if build_config is not None:
829 _DoEnterChroot(chroot_config, (self, 'Run'), raw_argv,
830 build_config=build_config, loaded_config=True)
Doug Andersonf0c73952011-01-18 13:46:07 -0800831
Doug Anderson4b6d3962011-01-21 09:41:50 -0800832 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)
Doug Andersonf0c73952011-01-18 13:46:07 -0800840
841
Doug Anderson4b6d3962011-01-21 09:41:50 -0800842class WorkonCmd(WrappedChrootCmd):
843 """Run cros_workon."""
Doug Andersonf0c73952011-01-18 13:46:07 -0800844
Doug Anderson4b6d3962011-01-21 09:41:50 -0800845 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 )
Doug Andersonf0c73952011-01-18 13:46:07 -0800854
Doug Anderson4b6d3962011-01-21 09:41:50 -0800855 def Run(self, raw_argv, *args, **kwargs):
856 """Run the command.
Doug Andersonf0c73952011-01-18 13:46:07 -0800857
Doug Anderson4b6d3962011-01-21 09:41:50 -0800858 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)
Doug Andersonf0c73952011-01-18 13:46:07 -0800873
874
Doug Anderson4b6d3962011-01-21 09:41:50 -0800875class EbuildCmd(WrappedChrootCmd):
876 """Run ebuild."""
Doug Andersonf0c73952011-01-18 13:46:07 -0800877
Doug Anderson4b6d3962011-01-21 09:41:50 -0800878 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 )
Doug Andersonf0c73952011-01-18 13:46:07 -0800885
886
Doug Anderson4b6d3962011-01-21 09:41:50 -0800887class EmergeCmd(WrappedChrootCmd):
888 """Run emerge."""
Doug Andersonf0c73952011-01-18 13:46:07 -0800889
Doug Anderson4b6d3962011-01-21 09:41:50 -0800890 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 )
Doug Andersonf0c73952011-01-18 13:46:07 -0800897
898
Doug Anderson4b6d3962011-01-21 09:41:50 -0800899class EqueryCmd(WrappedChrootCmd):
900 """Run equery."""
Doug Andersonf0c73952011-01-18 13:46:07 -0800901
Doug Anderson4b6d3962011-01-21 09:41:50 -0800902 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 )
Doug Andersonf0c73952011-01-18 13:46:07 -0800909
910
Doug Anderson4b6d3962011-01-21 09:41:50 -0800911class PortageqCmd(WrappedChrootCmd):
912 """Run portageq."""
Doug Andersonf0c73952011-01-18 13:46:07 -0800913
Doug Anderson4b6d3962011-01-21 09:41:50 -0800914 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 )
Doug Andersonf0c73952011-01-18 13:46:07 -0800921
922
Doug Andersone91900d2011-01-26 11:37:17 -0800923class ShellCmd(subcmd.ChromiteCmd):
Doug Andersonf0c73952011-01-18 13:46:07 -0800924 """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.
Doug Andersonf0c73952011-01-18 13:46:07 -0800928 """
Doug Andersonf0c73952011-01-18 13:46:07 -0800929
Doug Anderson4b6d3962011-01-21 09:41:50 -0800930 def Run(self, raw_argv, chroot_config=None):
931 """Run the command.
Doug Andersonf0c73952011-01-18 13:46:07 -0800932
Doug Anderson4b6d3962011-01-21 09:41:50 -0800933 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:])
Doug Andersonf0c73952011-01-18 13:46:07 -0800946
Doug Anderson4b6d3962011-01-21 09:41:50 -0800947 # Enter the chroot if needed...
948 if not IsInsideChroot():
949 _DoEnterChroot(chroot_config, (self, 'Run'), raw_argv)
Doug Andersonf0c73952011-01-18 13:46:07 -0800950 else:
Doug Anderson4b6d3962011-01-21 09:41:50 -0800951 # 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
Doug Andersonf0c73952011-01-18 13:46:07 -0800958 if not argv:
Doug Anderson4b6d3962011-01-21 09:41:50 -0800959 # 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')
Doug Andersonf0c73952011-01-18 13:46:07 -0800968
Doug Anderson4b6d3962011-01-21 09:41:50 -0800969 # 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)
Doug Andersonf0c73952011-01-18 13:46:07 -0800974
Doug Anderson4b6d3962011-01-21 09:41:50 -0800975 # 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)
Doug Andersonf0c73952011-01-18 13:46:07 -0800977
978
Doug Anderson4b6d3962011-01-21 09:41:50 -0800979#def _CmdClean(raw_argv, chroot_config=None):
Doug Andersone91900d2011-01-26 11:37:17 -0800980class CleanCmd(subcmd.ChromiteCmd):
Doug Anderson4b6d3962011-01-21 09:41:50 -0800981 """Clean out built packages for a target; if target=host, deletes chroot."""
Doug Andersonf0c73952011-01-18 13:46:07 -0800982
Doug Anderson4b6d3962011-01-21 09:41:50 -0800983 def Run(self, raw_argv, chroot_config=None):
984 """Run the command.
Doug Andersonf0c73952011-01-18 13:46:07 -0800985
Doug Anderson4b6d3962011-01-21 09:41:50 -0800986 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:])
Doug Andersonf0c73952011-01-18 13:46:07 -0800999
Doug Anderson4b6d3962011-01-21 09:41:50 -08001000 # 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))
Doug Andersonf0c73952011-01-18 13:46:07 -08001006
Doug Anderson4b6d3962011-01-21 09:41:50 -08001007 # 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)
Doug Andersonf0c73952011-01-18 13:46:07 -08001017
1018
1019def main():
1020 # TODO(dianders): Make help a little better. Specifically:
1021 # 1. Add a command called 'help'
1022 # 2. Make the help string below include command list and descriptions (like
1023 # the menu, but without being interactive).
1024 # 3. Make "help command" and "--help command" equivalent to "command --help".
1025 help_str = (
1026 """Usage: %(prog)s [chromite_options] [cmd [args]]\n"""
1027 """\n"""
1028 """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"""
1030 """\n"""
1031 """Options:\n"""
1032 """ -h, --help show this help message and exit\n"""
1033 ) % {'prog': os.path.basename(sys.argv[0])}
1034 if not IsInsideChroot():
1035 help_str += (
1036 """ --chroot=CHROOT_NAME Chroot spec to use. Can be an absolute\n"""
1037 """ path to a spec file or a substring of a\n"""
1038 """ chroot spec name (without .spec suffix)\n"""
1039 )
1040
1041 # We don't use OptionParser here, since options for different subcommands are
1042 # so different. We just look for the chromite options here...
1043 if sys.argv[1:2] == ['--help']:
1044 print help_str
1045 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'))
Doug Anderson4b6d3962011-01-21 09:41:50 -08001051
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
Doug Andersonf0c73952011-01-18 13:46:07 -08001057 fn(*args, **kwargs)
1058 else:
1059 # Start by skipping argv[0]
1060 argv = sys.argv[1:]
1061
1062 # Look for special "--chroot" argument to allow for alternate chroots
1063 if not IsInsideChroot():
1064 # Default chroot name...
1065 chroot_name = 'chroot'
1066
1067 # Get chroot spec name if specified; trim argv down if needed...
1068 if argv:
1069 if argv[0].startswith('--chroot='):
1070 _, chroot_name = argv[0].split('=', 2)
1071 argv = argv[1:]
1072 elif argv[0] == '--chroot':
1073 if len(argv) < 2:
1074 Die('Chroot not specified.')
1075
1076 chroot_name = argv[1]
1077 argv = argv[2:]
1078
1079 chroot_spec_path = _FindSpec(chroot_name, spec_type=_CHROOT_SPEC_TYPE)
1080
1081 Info('Using chroot "%s"' % os.path.relpath(chroot_spec_path))
1082
1083 chroot_config = _ReadConfig(chroot_spec_path)
1084 else:
1085 # Already in the chroot; no need to get config...
1086 chroot_config = None
1087
1088 # Get command and arguments
1089 if argv:
Doug Anderson4b6d3962011-01-21 09:41:50 -08001090 cmd_str = argv[0].lower()
Doug Andersonf0c73952011-01-18 13:46:07 -08001091 argv = argv[1:]
1092 else:
Doug Anderson4b6d3962011-01-21 09:41:50 -08001093 cmd_str = ''
Doug Andersonf0c73952011-01-18 13:46:07 -08001094
Doug Andersone91900d2011-01-26 11:37:17 -08001095 # Validate the subcmd, popping a menu if needed.
Doug Anderson4b6d3962011-01-21 09:41:50 -08001096 cmd_str = _FindCommand(cmd_str)
Doug Andersonf0c73952011-01-18 13:46:07 -08001097
1098 # Finally, call the function w/ standard argv.
Doug Anderson4b6d3962011-01-21 09:41:50 -08001099 cmd_cls = eval(_COMMAND_HANDLERS[_COMMAND_STRS.index(cmd_str)])
1100 cmd_obj = cmd_cls()
1101 cmd_obj.Run([cmd_str] + argv, chroot_config=chroot_config)
Doug Andersonf0c73952011-01-18 13:46:07 -08001102
1103
1104if __name__ == '__main__':
1105 main()