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