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

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

Powered by Google App Engine
This is Rietveld 408576698