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

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: More sosa feedback; put dummy sections in specs for anush; fixed unit tests. 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 '_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
64 def _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
84 def _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
99 def _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
112 def _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
169 def _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
285 def _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
306 def _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
347 def _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
399 def _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
439 def _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' to just 'chromite'.
489 cmd = (
490 './enter_chroot.sh --chroot="%s" %s --'
491 ' chromite/chromite --resume-state %s') % (
492 chroot_dir,
493 chroot_config.get('CHROOT', 'enter_chroot_flags'),
494 chroot_state_path)
495
496 # We'll put CWD as src/scripts when running the command. Since everyone
497 # running by hand has their cwd there, it is probably the safest.
498 cwd = os.path.join(_SRCROOT_PATH, 'src', 'scripts')
499
500 # Run it. We allow "error" so we don't print a confusing error message
501 # filled with out resume-state garbage on control-C.
502 cmd_result = RunCommand(cmd, shell=True, cwd=cwd, print_cmd=False,
503 exit_code=True, error_ok=True, ignore_sigint=True)
504
505 if cmd_result.returncode:
506 Die('Chroot exited with error code %d' % cmd_result.returncode)
507 finally:
508 # Make sure things get closed (and deleted), even upon exception.
509 state_file.close()
510
511
512 def _DoSetupBoard(build_config, clean_first):
513 """Setup the board, if needed.
514
515 This just runs the setup_board command with the proper args, if needed.
516
517 Args:
518 build_config: A SafeConfigParser representing the build config.
519 clean_first: Delete any old board config first.
520 """
521 # Skip this whole command if things already exist.
522 board_dir = _GetBoardDir(build_config)
523 if (not clean_first) and os.path.isdir(board_dir):
524 Info('%s already exists, skipping setup_board.' % board_dir)
525 return
526
527 Info('SETTING UP THE BOARD')
528
529 # Put together command.
530 cmd_list = [
531 './setup_board',
532 '--board="%s"' % build_config.get('BUILD', 'target'),
533 build_config.get('BUILD', 'setup_board_flags'),
534 ]
535 if clean_first:
536 cmd_list.insert(1, '--force')
537
538 # We're going convert to a string and force the shell to do all of the
539 # splitting of arguments, since we're throwing all of the flags from the
540 # config file in there.
541 cmd = ' '.join(cmd_list)
542
543 # We'll put CWD as src/scripts when running the command. Since everyone
544 # running by hand has their cwd there, it is probably the safest.
545 cwd = os.path.join(_SRCROOT_PATH, 'src', 'scripts')
546
547 # Run it. Exceptions will cause the program to exit.
548 RunCommand(cmd, shell=True, cwd=cwd, ignore_sigint=True)
549
550
551 def _DoBuildPackages(build_config):
552 """Build packages.
553
554 This just runs the build_packages command with the proper args.
555
556 Args:
557 build_config: A SafeConfigParser representing the build config.
558 """
559 Info('BUILDING PACKAGES')
560
561 # Put together command. We're going to force the shell to do all of the
562 # splitting of arguments, since we're throwing all of the flags from the
563 # config file in there.
564 cmd = './build_packages --board="%s" %s' % (
565 build_config.get('BUILD', 'target'),
566 build_config.get('BUILD', 'build_packages_flags')
567 )
568
569 # We'll put CWD as src/scripts when running the command. Since everyone
570 # running by hand has their cwd there, it is probably the safest.
571 cwd = os.path.join(_SRCROOT_PATH, 'src', 'scripts')
572
573 # Run it. Exceptions will cause the program to exit.
574 RunCommand(cmd, shell=True, cwd=cwd, ignore_sigint=True)
575
576
577 def _DoBuildImage(build_config):
578 """Build an image.
579
580 This just runs the build_image command with the proper args.
581
582 Args:
583 build_config: A SafeConfigParser representing the build config.
584 """
585 Info('BUILDING THE IMAGE')
586
587 # Put together command. We're going to force the shell to do all of the
588 # splitting of arguments, since we're throwing all of the flags from the
589 # config file in there.
590 cmd = './build_image --board="%s" %s' % (
591 build_config.get('BUILD', 'target'),
592 build_config.get('IMAGE', 'build_image_flags')
593 )
594
595 # We'll put CWD as src/scripts when running the command. Since everyone
596 # running by hand has their cwd there, it is probably the safest.
597 cwd = os.path.join(_SRCROOT_PATH, 'src', 'scripts')
598
599 # Run it. Exceptions will cause the program to exit.
600 RunCommand(cmd, shell=True, cwd=cwd, ignore_sigint=True)
601
602
603 def _DoClean(chroot_config, build_config, want_force_yes):
604 """Clean a target.
605
606 Args:
607 chroot_config: A SafeConfigParser representing the config for the chroot.
608 build_config: A SafeConfigParser representing the build config.
609 want_force_yes: If True, we won't ask any questions--we'll just assume
610 that the user really wants to kill the directory. If False, we'll
611 show UI asking the user to confirm.
612 """
613 # We'll need the directory so we can delete stuff; this is a chroot path.
614 board_dir = _GetBoardDir(build_config)
615
616 # If not in the chroot, convert board_dir into a non-chroot path...
617 if not IsInsideChroot():
618 chroot_dir = _GetChrootAbsDir(chroot_config)
619
620 # We'll need to make the board directory relative to the chroot.
621 assert board_dir.startswith('/'), 'Expected unix-style, absolute path.'
622 board_dir = board_dir.lstrip('/')
623 board_dir = os.path.join(chroot_dir, board_dir)
624
625 if not os.path.isdir(board_dir):
626 Die("Nothing to clean: the board directory doesn't exist.\n %s" %
627 board_dir)
628
629 if not want_force_yes:
630 sys.stderr.write('\n'
631 'Board dir is at: %s\n'
632 'Are you sure you want to delete it (YES/NO)? ' %
633 board_dir)
634 answer = raw_input()
635 if answer.lower() not in ('y', 'ye', 'yes'):
636 Die("You must answer 'yes' if you want to proceed.")
637
638 # Since we're about to do a sudo rm -rf, these are just extra precautions.
639 # This shouldn't be the only place testing these (assert fails are ugly and
640 # can be turned off), but better safe than sorry.
641 # Note that the restriction on '*' is a bit unnecessary, since no shell
642 # expansion should happen. ...but again, I'd rather be safe.
643 assert os.path.isabs(board_dir), 'Board dir better be absolute'
644 assert board_dir != '/', 'Board dir better not be /'
645 assert '*' not in board_dir, 'Board dir better not have any *s'
646 assert build_config.get('BUILD', 'target'), 'Target better not be blank'
647 assert build_config.get('BUILD', 'target') in board_dir, \
648 'Target name better be in board dir'
649
650 argv = ['sudo', '--', 'rm', '-rf', board_dir]
651 RunCommand(argv)
652 Info('Deleted: %s' % board_dir)
653
654
655 def _DoDistClean(chroot_config, want_force_yes):
656 """Remove the whole chroot.
657
658 Args:
659 chroot_config: A SafeConfigParser representing the config for the chroot.
660 want_force_yes: If True, we won't ask any questions--we'll just assume
661 that the user really wants to kill the directory. If False, we'll
662 show UI asking the user to confirm.
663 """
664 if IsInsideChroot():
665 Die('Please exit the chroot before trying to delete it.')
666
667 chroot_dir = _GetChrootAbsDir(chroot_config)
668 if not want_force_yes:
669 sys.stderr.write('\n'
670 'Chroot is at: %s\n'
671 'Are you sure you want to delete it (YES/NO)? ' %
672 chroot_dir)
673 answer = raw_input()
674 if answer.lower() not in ('y', 'ye', 'yes'):
675 Die("You must answer 'yes' if you want to proceed.")
676
677 # Can pass argv and not shell=True, since no user flags. :)
678 argv = ['./make_chroot', '--chroot=%s' % chroot_dir, '--delete']
679
680 # We'll put CWD as src/scripts when running the command. Since everyone
681 # running by hand has their cwd there, it is probably the safest.
682 cwd = os.path.join(_SRCROOT_PATH, 'src', 'scripts')
683
684 # Run it. Pass any failures upward.
685 RunCommand(argv, cwd=cwd)
686
687
688 def _DoWrappedChrootCommand(target_cmd, host_cmd, raw_argv, need_args=False,
689 chroot_config=None, argv=None, build_config=None):
690 """Helper function for any command that is simply wrapped by chromite.
691
692 These are commands where:
693 - We parse the command line only enough to figure out what board they
694 want. All othe command line parsing is handled by the wrapped command.
695 Because of this, the board name _needs_ to be specified first.
696 - Everything else (arg parsing, help, etc) is handled by the wrapped command.
697 The usage string will be a little messed up, but hopefully that's OK.
698
699 Args:
700 target_cmd: We'll put this at the start of argv when calling a target
701 command. We'll substiture %s with the target.
702 Like - ['my_command-%s'] or ['my_command', '--board=%s']
703 host_cmd: We'll put this at the start of argv when calling a host command.
704 Like - ['my_command']
705 raw_argv: Command line arguments, including this command's name, but not
706 the chromite command name or chromite options.
707 need_args: If True, we'll prompt for arguments if they weren't specified.
708 This makes the most sense when someone runs chromite with no arguments
709 and then walks through the menus. It's not ideal, but less sucky than
710 just quitting.
711 chroot_config: A SafeConfigParser for the chroot config; or None chromite
712 was called from within the chroot.
713 argv: None when called normally, but contains argv with board stripped off
714 if we call ourselves with _DoEnterChroot().
715 build_config: None when called normally, but contains the SafeConfigParser
716 for the build config if we call ourselves with _DoEnterChroot(). Note
717 that even when called through _DoEnterChroot(), could still be None
718 if user chose 'HOST' as the target.
719 """
720 # If we didn't get called through EnterChroot, we need to read the build
721 # config.
722 if argv is None:
723 # We look for the build config without calling OptionParser. This means
724 # that the board _needs_ to be first (if it's specified) and all options
725 # will be passed straight to our subcommand.
726 argv, build_config = _GetBuildConfigFromArgs(raw_argv[1:])
727
728 # Enter the chroot if needed...
729 if not IsInsideChroot():
730 _DoEnterChroot(chroot_config, _DoWrappedChrootCommand, target_cmd, host_cmd,
731 raw_argv, need_args=need_args, argv=argv,
732 build_config=build_config)
733 else:
734 # We'll put CWD as src/scripts when running the command. Since everyone
735 # running by hand has their cwd there, it is probably the safest.
736 cwd = os.path.join(_SRCROOT_PATH, 'src', 'scripts')
737
738 # Get command to call. If build_config is None, it means host.
739 if build_config is None:
740 argv_prefix = host_cmd
741 else:
742 # Make argv_prefix w/ target.
743 target_name = build_config.get('BUILD', 'target')
744 argv_prefix = [arg % target_name for arg in target_cmd]
745
746 # Not a great way to to specify arguments, but works for now... Wrapped
747 # commands are not wonderful interfaces anyway...
748 if need_args and not argv:
749 while True:
750 sys.stderr.write('arg %d (blank to exit): ' % (len(argv)+1))
751 arg = raw_input()
752 if not arg:
753 break
754 argv.append(arg)
755
756 # Add the prefix...
757 argv = argv_prefix + argv
758
759 # Run, ignoring errors since some commands (ahem, cros_workon) seem to
760 # return errors from things like --help.
761 RunCommand(argv, cwd=cwd, ignore_sigint=True, error_ok=True)
762
763
764 def _CmdBuild(raw_argv, chroot_config=None, loaded_config=False,
765 build_config=None):
766 """Build the chroot (if needed), the packages for a target, and the image.
767
768 Args:
769 raw_argv: Command line arguments, including this command's name, but not
770 the chromite command name or chromite options.
771 chroot_config: A SafeConfigParser for the chroot config; or None chromite
772 was called from within the chroot.
773 loaded_config: If True, we've already loaded the config.
774 build_config: None when called normally, but contains the SafeConfigParser
775 for the build config if we call ourselves with _DoEnterChroot(). Note
776 that even when called through _DoEnterChroot(), could still be None
777 if user chose 'HOST' as the target.
778 """
779 # Parse options for command...
780 usage_str = ('usage: %%prog [chromite_options] %s [options] [target]' %
781 raw_argv[0])
782 parser = optparse.OptionParser(usage=usage_str)
783 parser.add_option('--clean', default=False, action='store_true',
784 help='Clean before building.')
785 (options, argv) = parser.parse_args(raw_argv[1:])
786
787 # Load the build config if needed...
788 if not loaded_config:
789 argv, build_config = _GetBuildConfigFromArgs(argv)
790 if argv:
791 Die('Unknown arguments: %s' % ' '.join(argv))
792
793 if not IsInsideChroot():
794 # Note: we only want to clean the chroot if they do --clean and have the
795 # host target. If they do --clean and have a board target, it means
796 # that they just want to clean the board...
797 want_clean_chroot = options.clean and build_config is None
798
799 _DoMakeChroot(chroot_config, want_clean_chroot)
800
801 if build_config is not None:
802 _DoEnterChroot(chroot_config, _CmdBuild, raw_argv,
803 build_config=build_config, loaded_config=True)
804
805 Info('Done building.')
806 else:
807 if build_config is None:
808 Die("You can't build the host chroot from within the chroot.")
809
810 _DoSetupBoard(build_config, options.clean)
811 _DoBuildPackages(build_config)
812 _DoBuildImage(build_config)
813
814
815 def _CmdWorkon(raw_argv, *args, **kwargs):
816 """Run cros_workon.
817
818 This is just a wrapped command.
819
820 Args:
821 raw_argv: Command line arguments, including this command's name, but not
822 the chromite command name or chromite options.
823 args: The rest of the positional arguments. See _DoWrappedChrootCommand.
824 kwargs: The keyword arguments. See _DoWrappedChrootCommand.
825 """
826 # Slight optimization, just since I do this all the time...
827 if len(raw_argv) >= 2:
828 if raw_argv[1] in ('start', 'stop', 'list', 'list-all', 'iterate'):
829 Warn('OOPS, looks like you forgot a board name. Pick one.')
830 raw_argv = raw_argv[:1] + [''] + raw_argv[1:]
831
832 # Note that host version uses "./", since it's in src/scripts and not in the
833 # path...
834 _DoWrappedChrootCommand(['cros_workon-%s'], ['./cros_workon', '--host'],
835 raw_argv, need_args=True, *args, **kwargs)
836
837
838 def _CmdEbuild(raw_argv, *args, **kwargs):
839 """Run ebuild.
840
841 This is just a wrapped command.
842
843 Args:
844 raw_argv: Command line arguments, including this command's name, but not
845 the chromite command name or chromite options.
846 args: The rest of the positional arguments. See _DoWrappedChrootCommand.
847 kwargs: The keyword arguments. See _DoWrappedChrootCommand.
848 """
849 _DoWrappedChrootCommand(['ebuild-%s'], ['ebuild'],
850 raw_argv, need_args=True, *args, **kwargs)
851
852
853 def _CmdEmerge(raw_argv, *args, **kwargs):
854 """Run emerge.
855
856 This is just a wrapped command.
857
858 Args:
859 raw_argv: Command line arguments, including this command's name, but not
860 the chromite command name or chromite options.
861 args: The rest of the positional arguments. See _DoWrappedChrootCommand.
862 kwargs: The keyword arguments. See _DoWrappedChrootCommand.
863 """
864 _DoWrappedChrootCommand(['emerge-%s'], ['emerge'],
865 raw_argv, need_args=True, *args, **kwargs)
866
867
868 def _CmdEquery(raw_argv, *args, **kwargs):
869 """Run equery.
870
871 This is just a wrapped command.
872
873 Args:
874 raw_argv: Command line arguments, including this command's name, but not
875 the chromite command name or chromite options.
876 args: The rest of the positional arguments. See _DoWrappedChrootCommand.
877 kwargs: The keyword arguments. See _DoWrappedChrootCommand.
878 """
879 _DoWrappedChrootCommand(['equery-%s'], ['equery'],
880 raw_argv, need_args=True, *args, **kwargs)
881
882
883 def _CmdPortageq(raw_argv, *args, **kwargs):
884 """Run portageq.
885
886 This is just a wrapped command.
887
888 Args:
889 raw_argv: Command line arguments, including this command's name, but not
890 the chromite command name or chromite options.
891 args: The rest of the positional arguments. See _DoWrappedChrootCommand.
892 kwargs: The keyword arguments. See _DoWrappedChrootCommand.
893 """
894 _DoWrappedChrootCommand(['portageq-%s'], ['portageq'],
895 raw_argv, need_args=True, *args, **kwargs)
896
897
898 def _CmdShell(raw_argv, chroot_config=None):
899 """Start a shell in the chroot.
900
901 This can either just start a simple interactive shell, it can be used to
902 run an arbirtary command inside the chroot and then exit.
903
904 Args:
905 raw_argv: Command line arguments, including this command's name, but not
906 the chromite command name or chromite options.
907 chroot_config: A SafeConfigParser for the chroot config; or None chromite
908 was called from within the chroot.
909 """
910 # Parse options for command...
911 # ...note that OptionParser will eat the '--' if it's there, which is what
912 # we want..
913 usage_str = ('usage: %%prog [chromite_options] %s [options] [VAR=value] '
914 '[-- command [arg1] [arg2] ...]') % raw_argv[0]
915 parser = optparse.OptionParser(usage=usage_str)
916 (_, argv) = parser.parse_args(raw_argv[1:])
917
918 # Enter the chroot if needed...
919 if not IsInsideChroot():
920 _DoEnterChroot(chroot_config, _CmdShell, raw_argv)
921 else:
922 # We'll put CWD as src/scripts when running the command. Since everyone
923 # running by hand has their cwd there, it is probably the safest.
924 cwd = os.path.join(_SRCROOT_PATH, 'src', 'scripts')
925
926 # By default, no special environment...
927 env = None
928
929 if not argv:
930 # If no arguments, we'll just start bash.
931 argv = ['bash']
932 else:
933 # Parse the command line, looking at the beginning for VAR=value type
934 # statements. I couldn't figure out a way to get bash to do this for me.
935 user_env, argv = _SplitEnvFromArgs(argv)
936 if not argv:
937 Die('No command specified')
938
939 # If there was some environment, use it to override the standard
940 # environment.
941 if user_env:
942 env = dict(os.environ)
943 env.update(user_env)
944
945 # Don't show anything special for errors; we'll let the shell report them.
946 RunCommand(argv, cwd=cwd, env=env, error_ok=True, ignore_sigint=True)
947
948
949 def _CmdClean(raw_argv, chroot_config=None):
950 """Clean out built packages for a target; if target is host, deletes chroot.
951
952 Args:
953 raw_argv: Command line arguments, including this command's name, but not
954 the chromite command name or chromite options.
955 chroot_config: A SafeConfigParser for the chroot config; or None chromite
956 was called from within the chroot.
957 """
958 # Parse options for command...
959 usage_str = ('usage: %%prog [chromite_options] %s [options] [target]' %
960 raw_argv[0])
961 parser = optparse.OptionParser(usage=usage_str)
962 parser.add_option('-y', '--yes', default=False, action='store_true',
963 help='Answer "YES" to "are you sure?" questions.')
964 (options, argv) = parser.parse_args(raw_argv[1:])
965
966 # Make sure the chroot exists first, before possibly prompting for board...
967 # ...not really required, but nice for the user...
968 if not IsInsideChroot():
969 if not _DoesChrootExist(chroot_config):
970 Die("Nothing to clean: the chroot doesn't exist.\n %s" %
971 _GetChrootAbsDir(chroot_config))
972
973 # Load the build config...
974 argv, build_config = _GetBuildConfigFromArgs(argv)
975 if argv:
976 Die('Unknown arguments: %s' % ' '.join(argv))
977
978 # If they do clean host, we'll delete the whole chroot
979 if build_config is None:
980 _DoDistClean(chroot_config, options.yes)
981 else:
982 _DoClean(chroot_config, build_config, options.yes)
983
984
985 def main():
986 # TODO(dianders): Make help a little better. Specifically:
987 # 1. Add a command called 'help'
988 # 2. Make the help string below include command list and descriptions (like
989 # the menu, but without being interactive).
990 # 3. Make "help command" and "--help command" equivalent to "command --help".
991 help_str = (
992 """Usage: %(prog)s [chromite_options] [cmd [args]]\n"""
993 """\n"""
994 """The chromite script is a wrapper to make it easy to do various\n"""
995 """build tasks. For a list of commands, run without any arguments.\n"""
996 """\n"""
997 """Options:\n"""
998 """ -h, --help show this help message and exit\n"""
999 ) % {'prog': os.path.basename(sys.argv[0])}
1000 if not IsInsideChroot():
1001 help_str += (
1002 """ --chroot=CHROOT_NAME Chroot spec to use. Can be an absolute\n"""
1003 """ path to a spec file or a substring of a\n"""
1004 """ chroot spec name (without .spec suffix)\n"""
1005 )
1006
1007 # We don't use OptionParser here, since options for different subcommands are
1008 # so different. We just look for the chromite options here...
1009 if sys.argv[1:2] == ['--help']:
1010 print help_str
1011 sys.exit(0)
1012 elif sys.argv[1:2] == ['--resume-state']:
1013 # Internal mechanism (not documented to users) to resume in the chroot.
1014 # ...actual resume state file is passed in sys.argv[2] for simplicity...
1015 assert len(sys.argv) == 3, 'Resume State not passed properly.'
1016 fn, args, kwargs = cPickle.load(open(sys.argv[2], 'rb'))
1017 fn(*args, **kwargs)
1018 else:
1019 # Start by skipping argv[0]
1020 argv = sys.argv[1:]
1021
1022 # Look for special "--chroot" argument to allow for alternate chroots
1023 if not IsInsideChroot():
1024 # Default chroot name...
1025 chroot_name = 'chroot'
1026
1027 # Get chroot spec name if specified; trim argv down if needed...
1028 if argv:
1029 if argv[0].startswith('--chroot='):
1030 _, chroot_name = argv[0].split('=', 2)
1031 argv = argv[1:]
1032 elif argv[0] == '--chroot':
1033 if len(argv) < 2:
1034 Die('Chroot not specified.')
1035
1036 chroot_name = argv[1]
1037 argv = argv[2:]
1038
1039 chroot_spec_path = _FindSpec(chroot_name, spec_type=_CHROOT_SPEC_TYPE)
1040
1041 Info('Using chroot "%s"' % os.path.relpath(chroot_spec_path))
1042
1043 chroot_config = _ReadConfig(chroot_spec_path)
1044 else:
1045 # Already in the chroot; no need to get config...
1046 chroot_config = None
1047
1048 # Get command and arguments
1049 if argv:
1050 chromite_cmd = argv[0].lower()
1051 argv = argv[1:]
1052 else:
1053 chromite_cmd = ''
1054
1055 # Validate the chromite_cmd, popping a menu if needed.
1056 chromite_cmd = _FindCommand(chromite_cmd)
1057
1058 # Finally, call the function w/ standard argv.
1059 cmd_fn = eval(_COMMAND_HANDLERS[_COMMAND_STRS.index(chromite_cmd)])
1060 cmd_fn([chromite_cmd] + argv, chroot_config=chroot_config)
1061
1062
1063 if __name__ == '__main__':
1064 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