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