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

Side by Side Diff: chromite/chromite.py

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

Powered by Google App Engine
This is Rietveld 408576698