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