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

Side by Side Diff: shell/utils.py

Issue 6250058: Split out the big chromite shell 'main.py' into lots of subfiles. (Closed) Base URL: ssh://git@gitrw.chromium.org:9222/chromite.git@master
Patch Set: Incorporated davidjames and sosa feedback. Created 9 years, 10 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 | « shell/subcmds/workon_cmd.py ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 # Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4
5 """Utility functions shared between files in the chromite shell."""
6
7
8 # Python imports
9 import ConfigParser
10 import cPickle
11 import os
12 import sys
13 import tempfile
14
15
16 # Local imports
17 import chromite.lib.cros_build_lib as cros_lib
18 from chromite.lib import text_menu
19
20
21 # Find the Chromite root and Chromium OS root... Note: in the chroot we may
22 # choose to install Chromite somewhere (/usr/lib/chromite?), so we use the
23 # environment variable to get the right place if it exists.
24 CHROMITE_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..')
25 SRCROOT_PATH = os.environ.get('CROS_WORKON_SRCROOT',
26 os.path.realpath(os.path.join(CHROMITE_PATH,
27 '..')))
28
29
30 # Commands can take one of these two types of specs. Note that if a command
31 # takes a build spec, we will find the associated chroot spec. This should be
32 # a human-readable string. It is printed and also is the name of the spec
33 # directory.
34 BUILD_SPEC_TYPE = 'build'
35 CHROOT_SPEC_TYPE = 'chroot'
36
37
38 # This is a special target that indicates that you want to do something just
39 # to the host. This means different things to different commands.
40 # TODO(dianders): Good idea or bad idea?
41 _HOST_TARGET = 'HOST'
42
43
44 def GetBoardDir(build_config):
45 """Returns the board directory (inside the chroot) given the name.
46
47 Args:
48 build_config: A SafeConfigParser representing the config that we're
49 building.
50
51 Returns:
52 The absolute path to the board.
53 """
54 target_name = build_config.get('BUILD', 'target')
55
56 # Extra checks on these, since we sometimes might do a rm -f on the board
57 # directory and these could cause some really bad behavior.
58 assert target_name, "Didn't expect blank target name."
59 assert len(target_name.split()) == 1, 'Target name should have no whitespace.'
60
61 return os.path.join('/', 'build', target_name)
62
63
64 def GetChrootAbsDir(chroot_config):
65 """Returns the absolute chroot directory the chroot config.
66
67 Args:
68 chroot_config: A SafeConfigParser representing the config for the chroot.
69
70 Returns:
71 The chroot directory, always absolute.
72 """
73 chroot_dir = chroot_config.get('CHROOT', 'path')
74 chroot_dir = os.path.join(SRCROOT_PATH, chroot_dir)
75
76 return chroot_dir
77
78
79 def DoesChrootExist(chroot_config):
80 """Return True if the chroot folder exists.
81
82 Args:
83 chroot_config: A SafeConfigParser representing the config for the chroot.
84
85 Returns:
86 True if the chroot folder exists.
87 """
88 chroot_dir = GetChrootAbsDir(chroot_config)
89 return os.path.isdir(chroot_dir)
90
91
92 def FindSpec(spec_name, spec_type=BUILD_SPEC_TYPE, can_show_ui=True):
93 """Find the spec with the given name.
94
95 This tries to be smart about helping the user to find the right spec. See
96 the spec_name parameter for details.
97
98 Args:
99 spec_name: Can be any of the following:
100 1. A full path to a spec file (including the .spec suffix). This is
101 checked first (i.e. if os.path.isfile(spec_name), we assume we've
102 got this case).
103 2. The full name of a spec file somewhere in the spec search path
104 (not including the .spec suffix). This is checked second. Putting
105 this check second means that if one spec name is a substring of
106 another, you can still specify the shorter spec name and know you
107 won't get a menu (the exact match prevents the menu).
108 3. A substring that will be used to pare-down a menu of spec files
109 found in the spec search path. Can be the empty string to show a
110 menu of all spec files in the spec path. NOTE: Only possible if
111 can_show_ui is True.
112 spec_type: The type of spec this is: 'chroot' or 'build'.
113 can_show_ui: If True, enables the spec name to be a substring since we can
114 then show a menu if the substring matches more than one thing.
115
116 Returns:
117 A path to the spec file.
118 """
119 spec_suffix = '.spec'
120
121 # Handle 'HOST' for spec name w/ no searching, so it counts as an exact match.
122 if spec_type == BUILD_SPEC_TYPE and spec_name == _HOST_TARGET.lower():
123 return _HOST_TARGET
124
125 # If we have an exact path name, that's it. No searching.
126 if os.path.isfile(spec_name):
127 return spec_name
128
129 # Figure out what our search path should be.
130 # ...make these lists in anticipation of the need to support specs that live
131 # in private overlays.
132 # TODO(dianders): Should specs be part of the shell, or part of the main
133 # chromite?
134 search_path = [
135 os.path.join(CHROMITE_PATH, 'specs', spec_type),
136 ]
137
138 # Look for an exact match of a spec name. An exact match will go through with
139 # no menu.
140 if spec_name:
141 for dir_path in search_path:
142 spec_path = os.path.join(dir_path, spec_name + spec_suffix)
143 if os.path.isfile(spec_path):
144 return spec_path
145
146 # cros_lib.Die right away if we can't show UI and didn't have an exact match.
147 if not can_show_ui:
148 cros_lib.Die("Couldn't find %s spec: %s" % (spec_type, spec_name))
149
150 # No full path and no exact match. Move onto a menu.
151 # First step is to construct the options. We'll store in a dict keyed by
152 # spec name.
153 options = {}
154 for dir_path in search_path:
155 for file_name in os.listdir(dir_path):
156 # Find any files that end with ".spec" in a case-insensitive manner.
157 if not file_name.lower().endswith(spec_suffix):
158 continue
159
160 this_spec_name, _ = os.path.splitext(file_name)
161
162 # Skip if this spec file doesn't contain our substring. We are _always_
163 # case insensitive here to be helpful to the user.
164 if spec_name.lower() not in this_spec_name.lower():
165 continue
166
167 # Disallow the spec to appear twice in the search path. This is the
168 # safest thing for now. We could revisit it later if we ever found a
169 # good reason (and if we ever have more than one directory in the
170 # search path).
171 if this_spec_name in options:
172 cros_lib.Die('Spec %s was found in two places in the search path' %
173 this_spec_name)
174
175 # Combine to get a full path.
176 full_path = os.path.join(dir_path, file_name)
177
178 # Ignore directories or anything else that isn't a file.
179 if not os.path.isfile(full_path):
180 continue
181
182 # OK, it's good. Store the path.
183 options[this_spec_name] = full_path
184
185 # Add 'HOST'. All caps so it sorts first.
186 if (spec_type == BUILD_SPEC_TYPE and
187 spec_name.lower() in _HOST_TARGET.lower()):
188 options[_HOST_TARGET] = _HOST_TARGET
189
190 # If no match, die.
191 if not options:
192 cros_lib.Die("Couldn't find any matching %s specs for: %s" % (spec_type,
193 spec_name))
194
195 # Avoid showing the user a menu if the user's search string matched exactly
196 # one item.
197 if spec_name and len(options) == 1:
198 _, spec_path = options.popitem()
199 return spec_path
200
201 # If more than one, show a menu...
202 option_keys = sorted(options.iterkeys())
203 choice = text_menu.TextMenu(option_keys, 'Choose a %s spec' % spec_type)
204
205 if choice is None:
206 cros_lib.Die('OK, cancelling...')
207 else:
208 return options[option_keys[choice]]
209
210
211 def ReadConfig(spec_path):
212 """Read the a build config or chroot config from a spec file.
213
214 This includes adding thue proper _default stuff in.
215
216 Args:
217 spec_path: The path to the build or chroot spec.
218
219 Returns:
220 config: A SafeConfigParser representing the config.
221 """
222 spec_name, _ = os.path.splitext(os.path.basename(spec_path))
223 spec_dir = os.path.dirname(spec_path)
224
225 config = ConfigParser.SafeConfigParser({'name': spec_name})
226 config.read(os.path.join(spec_dir, '_defaults'))
227 config.read(spec_path)
228
229 return config
230
231
232 def GetBuildConfigFromArgs(argv):
233 """Helper for commands that take a build config in the arg list.
234
235 This function can cros_lib.Die() in some instances.
236
237 Args:
238 argv: A list of command line arguments. If non-empty, [0] should be the
239 build spec. These will not be modified.
240
241 Returns:
242 argv: The argv with the build spec stripped off. This might be argv[1:] or
243 just argv. Not guaranteed to be new memory.
244 build_config: The SafeConfigParser for the build config; might be None if
245 this is a host config. TODO(dianders): Should there be a build spec for
246 the host?
247 """
248 # The spec name is optional. If no arguments, we'll show a menu...
249 # Note that if there are arguments, but the first argument is a flag, we'll
250 # assume that we got called before OptionParser. In that case, they might
251 # have specified options but still want the board menu.
252 if argv and not argv[0].startswith('-'):
253 spec_name = argv[0]
254 argv = argv[1:]
255 else:
256 spec_name = ''
257
258 spec_path = FindSpec(spec_name)
259
260 if spec_path == _HOST_TARGET:
261 return argv, None
262
263 build_config = ReadConfig(spec_path)
264
265 # TODO(dianders): Add a config checker class that makes sure that the
266 # target is not a blank string. Might also be good to make sure that the
267 # target has no whitespace (so we don't screw up a subcommand invoked
268 # through a shell).
269
270 return argv, build_config
271
272
273 def EnterChroot(chroot_config, fn, *args, **kwargs):
274 """Re-run the given function inside the chroot.
275
276 When the function is run, it will be run in a SEPARATE INSTANCE of chromite,
277 which will be run in the chroot. This is a little weird. Specifically:
278 - When the callee executes, it will be a separate python instance.
279 - Globals will be reset back to defaults.
280 - A different version of python (with different modules) may end up running
281 the script in the chroot.
282 - All arguments are pickled up into a temp file, whose path is passed on the
283 command line.
284 - That means that args must be pickleable.
285 - It also means that modifications to the parameters by the callee are not
286 visible to the caller.
287 - Even the function is "pickled". The way the pickle works, I belive, is it
288 just passes the name of the function. If this name somehow resolves
289 differently in the chroot, you may get weirdness.
290 - Since we're in the chroot, obviously files may have different paths. It's
291 up to you to convert parameters if you need to.
292 - The stdin, stdout, and stderr aren't touched.
293
294 Args:
295 chroot_config: A SafeConfigParser representing the config for the chroot.
296 fn: Either: a) the function to call or b) A tuple of an object and the
297 name of the method to call.
298 args: All other arguments will be passed to the function as is.
299 kwargs: All other arguments will be passed to the function as is.
300 """
301 # Make sure that the chroot exists...
302 chroot_dir = GetChrootAbsDir(chroot_config)
303 if not DoesChrootExist(chroot_config):
304 cros_lib.Die(
305 'Chroot dir does not exist; try the "build host" command.\n %s.' %
306 chroot_dir)
307
308 cros_lib.Info('ENTERING THE CHROOT')
309
310 # Save state to a temp file (inside the chroot!) using pickle.
311 tmp_dir = os.path.join(chroot_dir, 'tmp')
312 state_file = tempfile.NamedTemporaryFile(prefix='chromite', dir=tmp_dir)
313 try:
314 cPickle.dump((fn, args, kwargs), state_file, cPickle.HIGHEST_PROTOCOL)
315 state_file.flush()
316
317 # Translate temp file name into a chroot path...
318 chroot_state_path = os.path.join('/tmp', os.path.basename(state_file.name))
319
320 # Put together command. We're going to force the shell to do all of the
321 # splitting of arguments, since we're throwing all of the flags from the
322 # config file in there.
323 # TODO(dianders): It might be nice to run chromite as:
324 # python -m chromite.chromite_main
325 # ...but, at the moment, that fails if you're in src/scripts
326 # which already has a chromite folder.
327 cmd = (
328 './enter_chroot.sh --chroot="%s" %s --'
329 ' python ../../chromite/shell/main.py --resume-state %s') % (
330 chroot_dir,
331 chroot_config.get('CHROOT', 'enter_chroot_flags'),
332 chroot_state_path)
333
334 # We'll put CWD as src/scripts when running the command. Since everyone
335 # running by hand has their cwd there, it is probably the safest.
336 cwd = os.path.join(SRCROOT_PATH, 'src', 'scripts')
337
338 # Run it. We allow "error" so we don't print a confusing error message
339 # filled with out resume-state garbage on control-C.
340 cmd_result = cros_lib.RunCommand(cmd, shell=True, cwd=cwd, print_cmd=False,
341 exit_code=True, error_ok=True, ignore_sigint=True)
342
343 if cmd_result.returncode:
344 cros_lib.Die('Chroot exited with error code %d' % cmd_result.returncode)
345 finally:
346 # Make sure things get closed (and deleted), even upon exception.
347 state_file.close()
348
349
350 def ResumeEnterChrootIfNeeded(argv):
351 """Should be called as the first line in main() to support EnterChroot().
352
353 We'll check whether the --resume-state argument is there. If the argument is
354 there, we'll use it and call the proper function (now that we're in the
355 chroot). We'll then return True to indicate to main() that it should exit
356 immediately.
357
358 If the --resume-state argument is not there, this function will return False
359 without doing anything else.
360
361 Args:
362 argv: The value of sys.argv
363
364 Returns:
365 True if we resumed; indicates that main should return without doing any
366 further work.
367 """
368 if argv[1:2] == ['--resume-state']:
369 # Internal mechanism (not documented to users) to resume in the chroot.
370 # ...actual resume state file is passed in argv[2] for simplicity...
371 assert len(argv) == 3, 'Resume State not passed properly.'
372 fn, args, kwargs = cPickle.load(open(argv[2], 'rb'))
373
374 # Handle calling a method in a class; that can't be directly pickled.
375 if isinstance(fn, tuple):
376 obj, method = fn
377 fn = getattr(obj, method)
378
379 fn(*args, **kwargs)
380
381 # Return True to tell main() that it should exit.
382 return True
383 else:
384 # Return False to tell main() that we didn't find the --resume-state
385 # argument and that it should do normal arugment parsing.
386 return False
OLDNEW
« no previous file with comments | « shell/subcmds/workon_cmd.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698