blob: 781d43e45a5656be8c304522414a59089a2b4907 [file] [log] [blame]
David Jamesfcb70ef2011-02-02 16:02:30 -08001#!/usr/bin/python2.6
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"""Program to run emerge in parallel, for significant speedup.
7
8Usage:
David Jamesf9744012011-05-04 13:19:31 -07009 ./parallel_emerge [--board=BOARD] [--workon=PKGS]
David Jamesfcb70ef2011-02-02 16:02:30 -080010 [--force-remote-binary=PKGS] [emerge args] package
11
12Basic operation:
13 Runs 'emerge -p --debug' to display dependencies, and stores a
14 dependency graph. All non-blocked packages are launched in parallel,
15 as 'emerge --nodeps package' with any blocked packages being emerged
16 immediately upon deps being met.
17
18 For this to work effectively, /usr/lib/portage/pym/portage/locks.py
19 must be stubbed out, preventing portage from slowing itself with
20 unneccesary locking, as this script ensures that emerge is run in such
21 a way that common resources are never in conflict. This is controlled
22 by an environment variable PORTAGE_LOCKS set in parallel emerge
23 subprocesses.
24
25 Parallel Emerge unlocks two things during operation, here's what you
26 must do to keep this safe:
27 * Storage dir containing binary packages. - Don't emerge new
28 packages while installing the existing ones.
29 * Portage database - You must not examine deps while modifying the
30 database. Therefore you may only parallelize "-p" read only access,
31 or "--nodeps" write only access.
32 Caveats:
33 * Some ebuild packages have incorrectly specified deps, and running
34 them in parallel is more likely to bring out these failures.
35 * Some ebuilds (especially the build part) have complex dependencies
36 that are not captured well by this script (it may be necessary to
37 install an old package to build, but then install a newer version
38 of the same package for a runtime dep).
39"""
40
41import codecs
42import copy
43import errno
44import multiprocessing
45import os
46import Queue
David Jamesfcb70ef2011-02-02 16:02:30 -080047import signal
48import sys
49import tempfile
50import time
51import traceback
David Jamesfcb70ef2011-02-02 16:02:30 -080052
53# If PORTAGE_USERNAME isn't specified, scrape it from the $HOME variable. On
54# Chromium OS, the default "portage" user doesn't have the necessary
55# permissions. It'd be easier if we could default to $USERNAME, but $USERNAME
56# is "root" here because we get called through sudo.
57#
58# We need to set this before importing any portage modules, because portage
59# looks up "PORTAGE_USERNAME" at import time.
60#
61# NOTE: .bashrc sets PORTAGE_USERNAME = $USERNAME, so most people won't
62# encounter this case unless they have an old chroot or blow away the
63# environment by running sudo without the -E specifier.
64if "PORTAGE_USERNAME" not in os.environ:
65 homedir = os.environ.get("HOME")
66 if homedir:
67 os.environ["PORTAGE_USERNAME"] = os.path.basename(homedir)
68
69# Portage doesn't expose dependency trees in its public API, so we have to
70# make use of some private APIs here. These modules are found under
71# /usr/lib/portage/pym/.
72#
73# TODO(davidjames): Update Portage to expose public APIs for these features.
74from _emerge.actions import adjust_configs
75from _emerge.actions import load_emerge_config
76from _emerge.create_depgraph_params import create_depgraph_params
David Jamesf9744012011-05-04 13:19:31 -070077from _emerge.depgraph import backtrack_depgraph
David Jamesfcb70ef2011-02-02 16:02:30 -080078from _emerge.main import emerge_main
79from _emerge.main import parse_opts
80from _emerge.Package import Package
81from _emerge.Scheduler import Scheduler
82from _emerge.SetArg import SetArg
83from _emerge.stdout_spinner import stdout_spinner
David Jamesf9744012011-05-04 13:19:31 -070084from portage._global_updates import _global_updates
85from portage.versions import vercmp
David Jamesfcb70ef2011-02-02 16:02:30 -080086import portage
87import portage.debug
David Jamesfcb70ef2011-02-02 16:02:30 -080088
David Jamesf9744012011-05-04 13:19:31 -070089NEW_PORTAGE = (0 <= vercmp(portage.VERSION, "2.1.9.46-r2"))
David Jamesfcb70ef2011-02-02 16:02:30 -080090
91def Usage():
92 """Print usage."""
93 print "Usage:"
David Jamesf9744012011-05-04 13:19:31 -070094 print " ./parallel_emerge [--board=BOARD] [--workon=PKGS]"
David Jamesfcb70ef2011-02-02 16:02:30 -080095 print " [--rebuild] [emerge args] package"
96 print
97 print "Packages specified as workon packages are always built from source."
David Jamesfcb70ef2011-02-02 16:02:30 -080098 print
99 print "The --workon argument is mainly useful when you want to build and"
100 print "install packages that you are working on unconditionally, but do not"
101 print "to have to rev the package to indicate you want to build it from"
102 print "source. The build_packages script will automatically supply the"
103 print "workon argument to emerge, ensuring that packages selected using"
104 print "cros-workon are rebuilt."
105 print
106 print "The --rebuild option rebuilds packages whenever their dependencies"
107 print "are changed. This ensures that your build is correct."
108 sys.exit(1)
109
110
David Jamesfcb70ef2011-02-02 16:02:30 -0800111# Global start time
112GLOBAL_START = time.time()
113
114
115class EmergeData(object):
116 """This simple struct holds various emerge variables.
117
118 This struct helps us easily pass emerge variables around as a unit.
119 These variables are used for calculating dependencies and installing
120 packages.
121 """
122
123 __slots__ = ["action", "cmdline_packages", "depgraph", "mtimedb", "opts",
124 "root_config", "scheduler_graph", "settings", "spinner",
125 "trees"]
126
127 def __init__(self):
128 # The action the user requested. If the user is installing packages, this
129 # is None. If the user is doing anything other than installing packages,
130 # this will contain the action name, which will map exactly to the
131 # long-form name of the associated emerge option.
132 #
133 # Example: If you call parallel_emerge --unmerge package, the action name
134 # will be "unmerge"
135 self.action = None
136
137 # The list of packages the user passed on the command-line.
138 self.cmdline_packages = None
139
140 # The emerge dependency graph. It'll contain all the packages involved in
141 # this merge, along with their versions.
142 self.depgraph = None
143
144 # A dict of the options passed to emerge. This dict has been cleaned up
145 # a bit by parse_opts, so that it's a bit easier for the emerge code to
146 # look at the options.
147 #
148 # Emerge takes a few shortcuts in its cleanup process to make parsing of
149 # the options dict easier. For example, if you pass in "--usepkg=n", the
150 # "--usepkg" flag is just left out of the dictionary altogether. Because
151 # --usepkg=n is the default, this makes parsing easier, because emerge
152 # can just assume that if "--usepkg" is in the dictionary, it's enabled.
153 #
154 # These cleanup processes aren't applied to all options. For example, the
155 # --with-bdeps flag is passed in as-is. For a full list of the cleanups
156 # applied by emerge, see the parse_opts function in the _emerge.main
157 # package.
158 self.opts = None
159
160 # A dictionary used by portage to maintain global state. This state is
161 # loaded from disk when portage starts up, and saved to disk whenever we
162 # call mtimedb.commit().
163 #
164 # This database contains information about global updates (i.e., what
165 # version of portage we have) and what we're currently doing. Portage
166 # saves what it is currently doing in this database so that it can be
167 # resumed when you call it with the --resume option.
168 #
169 # parallel_emerge does not save what it is currently doing in the mtimedb,
170 # so we do not support the --resume option.
171 self.mtimedb = None
172
173 # The portage configuration for our current root. This contains the portage
174 # settings (see below) and the three portage trees for our current root.
175 # (The three portage trees are explained below, in the documentation for
176 # the "trees" member.)
177 self.root_config = None
178
179 # The scheduler graph is used by emerge to calculate what packages to
180 # install. We don't actually install any deps, so this isn't really used,
181 # but we pass it in to the Scheduler object anyway.
182 self.scheduler_graph = None
183
184 # Portage settings for our current session. Most of these settings are set
185 # in make.conf inside our current install root.
186 self.settings = None
187
188 # The spinner, which spews stuff to stdout to indicate that portage is
189 # doing something. We maintain our own spinner, so we set the portage
190 # spinner to "silent" mode.
191 self.spinner = None
192
193 # The portage trees. There are separate portage trees for each root. To get
194 # the portage tree for the current root, you can look in self.trees[root],
195 # where root = self.settings["ROOT"].
196 #
197 # In each root, there are three trees: vartree, porttree, and bintree.
198 # - vartree: A database of the currently-installed packages.
199 # - porttree: A database of ebuilds, that can be used to build packages.
200 # - bintree: A database of binary packages.
201 self.trees = None
202
203
204class DepGraphGenerator(object):
205 """Grab dependency information about packages from portage.
206
207 Typical usage:
208 deps = DepGraphGenerator()
209 deps.Initialize(sys.argv[1:])
210 deps_tree, deps_info = deps.GenDependencyTree()
211 deps_graph = deps.GenDependencyGraph(deps_tree, deps_info)
212 deps.PrintTree(deps_tree)
213 PrintDepsMap(deps_graph)
214 """
215
David Jamesf9744012011-05-04 13:19:31 -0700216 __slots__ = ["board", "emerge", "package_db", "show_output"]
David Jamesfcb70ef2011-02-02 16:02:30 -0800217
218 def __init__(self):
219 self.board = None
220 self.emerge = EmergeData()
David Jamesfcb70ef2011-02-02 16:02:30 -0800221 self.package_db = {}
David Jamesfcb70ef2011-02-02 16:02:30 -0800222 self.show_output = False
David Jamesfcb70ef2011-02-02 16:02:30 -0800223
224 def ParseParallelEmergeArgs(self, argv):
225 """Read the parallel emerge arguments from the command-line.
226
227 We need to be compatible with emerge arg format. We scrape arguments that
228 are specific to parallel_emerge, and pass through the rest directly to
229 emerge.
230 Args:
231 argv: arguments list
232 Returns:
233 Arguments that don't belong to parallel_emerge
234 """
235 emerge_args = []
236 for arg in argv:
237 # Specifically match arguments that are specific to parallel_emerge, and
238 # pass through the rest.
239 if arg.startswith("--board="):
240 self.board = arg.replace("--board=", "")
241 elif arg.startswith("--workon="):
242 workon_str = arg.replace("--workon=", "")
David Jamesf9744012011-05-04 13:19:31 -0700243 if NEW_PORTAGE:
244 emerge_args.append("--reinstall-atoms=%s" % workon_str)
245 emerge_args.append("--usepkg-exclude=%s" % workon_str)
David Jamesfcb70ef2011-02-02 16:02:30 -0800246 elif arg.startswith("--force-remote-binary="):
247 force_remote_binary = arg.replace("--force-remote-binary=", "")
David Jamesf9744012011-05-04 13:19:31 -0700248 if NEW_PORTAGE:
249 emerge_args.append("--useoldpkg-atoms=%s" % force_remote_binary)
David Jamesfcb70ef2011-02-02 16:02:30 -0800250 elif arg == "--show-output":
251 self.show_output = True
David Jamesf9744012011-05-04 13:19:31 -0700252 elif arg == "--rebuild":
253 if NEW_PORTAGE:
254 emerge_args.append("--rebuild-if-unbuilt")
David Jamesfcb70ef2011-02-02 16:02:30 -0800255 else:
256 # Not one of our options, so pass through to emerge.
257 emerge_args.append(arg)
258
David Jamesf9744012011-05-04 13:19:31 -0700259 # These packages take a really long time to build, so, for expediency, we
260 # are blacklisting them from automatic rebuilds because one of their
261 # dependencies needs to be recompiled.
262 for pkg in ("chromeos-base/chromeos-chrome", "media-plugins/o3d",
263 "dev-java/icedtea"):
264 if NEW_PORTAGE:
265 emerge_args.append("--rebuild-exclude=%s" % pkg)
David Jamesfcb70ef2011-02-02 16:02:30 -0800266
267 return emerge_args
268
269 def Initialize(self, args):
270 """Initializer. Parses arguments and sets up portage state."""
271
272 # Parse and strip out args that are just intended for parallel_emerge.
273 emerge_args = self.ParseParallelEmergeArgs(args)
274
275 # Setup various environment variables based on our current board. These
276 # variables are normally setup inside emerge-${BOARD}, but since we don't
277 # call that script, we have to set it up here. These variables serve to
278 # point our tools at /build/BOARD and to setup cross compiles to the
279 # appropriate board as configured in toolchain.conf.
280 if self.board:
281 os.environ["PORTAGE_CONFIGROOT"] = "/build/" + self.board
282 os.environ["PORTAGE_SYSROOT"] = "/build/" + self.board
283 os.environ["SYSROOT"] = "/build/" + self.board
284 srcroot = "%s/../../src" % os.path.dirname(os.path.realpath(__file__))
285 # Strip the variant out of the board name to look for the toolchain. This
286 # is similar to what setup_board does.
287 board_no_variant = self.board.split('_')[0]
288 public_toolchain_path = ("%s/overlays/overlay-%s/toolchain.conf" %
289 (srcroot, board_no_variant))
290 private_toolchain_path = (
291 "%s/private-overlays/overlay-%s-private/toolchain.conf" %
292 (srcroot, board_no_variant))
293 if os.path.isfile(public_toolchain_path):
294 toolchain_path = public_toolchain_path
295 elif os.path.isfile(private_toolchain_path):
296 toolchain_path = private_toolchain_path
297 else:
298 print "Not able to locate toolchain.conf in board overlays"
299 sys.exit(1)
300
301 f = open(toolchain_path)
302 os.environ["CHOST"] = f.readline().strip()
303 f.close()
304
305 # Although CHROMEOS_ROOT isn't specific to boards, it's normally setup
306 # inside emerge-${BOARD}, so we set it up here for compatibility. It
307 # will be going away soon as we migrate to CROS_WORKON_SRCROOT.
308 os.environ.setdefault("CHROMEOS_ROOT", os.environ["HOME"] + "/trunk")
309
310 # Turn off interactive delays
311 os.environ["EBEEP_IGNORE"] = "1"
312 os.environ["EPAUSE_IGNORE"] = "1"
313 os.environ["UNMERGE_DELAY"] = "0"
314
315 # Parse the emerge options.
316 action, opts, cmdline_packages = parse_opts(emerge_args)
317
318 # If we're installing to the board, we want the --root-deps option so that
319 # portage will install the build dependencies to that location as well.
320 if self.board:
321 opts.setdefault("--root-deps", True)
322
323 # Set environment variables based on options. Portage normally sets these
324 # environment variables in emerge_main, but we can't use that function,
325 # because it also does a bunch of other stuff that we don't want.
326 # TODO(davidjames): Patch portage to move this logic into a function we can
327 # reuse here.
328 if "--debug" in opts:
329 os.environ["PORTAGE_DEBUG"] = "1"
330 if "--config-root" in opts:
331 os.environ["PORTAGE_CONFIGROOT"] = opts["--config-root"]
332 if "--root" in opts:
333 os.environ["ROOT"] = opts["--root"]
334 if "--accept-properties" in opts:
335 os.environ["ACCEPT_PROPERTIES"] = opts["--accept-properties"]
336
337 # Portage has two flags for doing collision protection: collision-protect
338 # and protect-owned. The protect-owned feature is enabled by default and
339 # is quite useful: it checks to make sure that we don't have multiple
340 # packages that own the same file. The collision-protect feature is more
341 # strict, and less useful: it fails if it finds a conflicting file, even
342 # if that file was created by an earlier ebuild that failed to install.
343 #
344 # We want to disable collision-protect here because we don't handle
345 # failures during the merge step very well. Sometimes we leave old files
346 # lying around and they cause problems, so for now we disable the flag.
347 # TODO(davidjames): Look for a better solution.
348 features = os.environ.get("FEATURES", "") + " -collision-protect"
349
350 # If we're installing packages to the board, and we're not using the
351 # official flag, we can enable the following optimizations:
352 # 1) Don't lock during install step. This allows multiple packages to be
353 # installed at once. This is safe because our board packages do not
354 # muck with each other during the post-install step.
355 # 2) Don't update the environment until the end of the build. This is
356 # safe because board packages don't need to run during the build --
357 # they're cross-compiled, so our CPU architecture doesn't support them
358 # anyway.
359 if self.board and os.environ.get("CHROMEOS_OFFICIAL") != "1":
360 os.environ.setdefault("PORTAGE_LOCKS", "false")
361 features = features + " no-env-update"
362
363 os.environ["FEATURES"] = features
364
365 # Now that we've setup the necessary environment variables, we can load the
366 # emerge config from disk.
367 settings, trees, mtimedb = load_emerge_config()
368
369 # Check whether our portage tree is out of date. Typically, this happens
370 # when you're setting up a new portage tree, such as in setup_board and
371 # make_chroot. In that case, portage applies a bunch of global updates
372 # here. Once the updates are finished, we need to commit any changes
373 # that the global update made to our mtimedb, and reload the config.
374 #
375 # Portage normally handles this logic in emerge_main, but again, we can't
376 # use that function here.
377 if _global_updates(trees, mtimedb["updates"]):
378 mtimedb.commit()
379 settings, trees, mtimedb = load_emerge_config(trees=trees)
380
381 # Setup implied options. Portage normally handles this logic in
382 # emerge_main.
383 if "--buildpkgonly" in opts or "buildpkg" in settings.features:
384 opts.setdefault("--buildpkg", True)
385 if "--getbinpkgonly" in opts:
386 opts.setdefault("--usepkgonly", True)
387 opts.setdefault("--getbinpkg", True)
388 if "getbinpkg" in settings.features:
389 # Per emerge_main, FEATURES=getbinpkg overrides --getbinpkg=n
390 opts["--getbinpkg"] = True
391 if "--getbinpkg" in opts or "--usepkgonly" in opts:
392 opts.setdefault("--usepkg", True)
393 if "--fetch-all-uri" in opts:
394 opts.setdefault("--fetchonly", True)
395 if "--skipfirst" in opts:
396 opts.setdefault("--resume", True)
397 if "--buildpkgonly" in opts:
398 # --buildpkgonly will not merge anything, so it overrides all binary
399 # package options.
400 for opt in ("--getbinpkg", "--getbinpkgonly",
401 "--usepkg", "--usepkgonly"):
402 opts.pop(opt, None)
403 if (settings.get("PORTAGE_DEBUG", "") == "1" and
404 "python-trace" in settings.features):
405 portage.debug.set_trace(True)
406
407 # Complain about unsupported options
David Jamesf9744012011-05-04 13:19:31 -0700408 for opt in ("--ask", "--ask-enter-invalid", "--resume", "--skipfirst"):
David Jamesfcb70ef2011-02-02 16:02:30 -0800409 if opt in opts:
410 print "%s is not supported by parallel_emerge" % opt
411 sys.exit(1)
412
413 # Make emerge specific adjustments to the config (e.g. colors!)
414 adjust_configs(opts, trees)
415
416 # Save our configuration so far in the emerge object
417 emerge = self.emerge
418 emerge.action, emerge.opts = action, opts
419 emerge.settings, emerge.trees, emerge.mtimedb = settings, trees, mtimedb
420 emerge.cmdline_packages = cmdline_packages
421 root = settings["ROOT"]
422 emerge.root_config = trees[root]["root_config"]
423
David Jamesf9744012011-05-04 13:19:31 -0700424 if "--usepkg" in opts:
David Jamesfcb70ef2011-02-02 16:02:30 -0800425 emerge.trees[root]["bintree"].populate("--getbinpkg" in opts)
426
David Jamesfcb70ef2011-02-02 16:02:30 -0800427 def CreateDepgraph(self, emerge, packages):
428 """Create an emerge depgraph object."""
429 # Setup emerge options.
430 emerge_opts = emerge.opts.copy()
431
David Jamesf9744012011-05-04 13:19:31 -0700432 # Ask portage to build a dependency graph. with the options we specified
433 # above.
David Jamesfcb70ef2011-02-02 16:02:30 -0800434 params = create_depgraph_params(emerge_opts, emerge.action)
David Jamesf9744012011-05-04 13:19:31 -0700435 success, depgraph, _ = backtrack_depgraph(
436 emerge.settings, emerge.trees, emerge_opts, params, emerge.action,
437 packages, emerge.spinner)
438 emerge.depgraph = depgraph
David Jamesfcb70ef2011-02-02 16:02:30 -0800439
David Jamesf9744012011-05-04 13:19:31 -0700440 # Is it impossible to honor the user's request? Bail!
441 if not success:
442 depgraph.display_problems()
443 sys.exit(1)
David Jamesfcb70ef2011-02-02 16:02:30 -0800444
445 emerge.depgraph = depgraph
446
447 # Is it impossible to honor the user's request? Bail!
448 if not success:
449 depgraph.display_problems()
450 sys.exit(1)
451
David Jamesf9744012011-05-04 13:19:31 -0700452 def GenDependencyTree(self):
David Jamesfcb70ef2011-02-02 16:02:30 -0800453 """Get dependency tree info from emerge.
454
David Jamesfcb70ef2011-02-02 16:02:30 -0800455 Returns:
456 Dependency tree
457 """
458 start = time.time()
459
460 emerge = self.emerge
461
462 # Create a list of packages to merge
463 packages = set(emerge.cmdline_packages[:])
David Jamesfcb70ef2011-02-02 16:02:30 -0800464
465 # Tell emerge to be quiet. We print plenty of info ourselves so we don't
466 # need any extra output from portage.
467 portage.util.noiselimit = -1
468
469 # My favorite feature: The silent spinner. It doesn't spin. Ever.
470 # I'd disable the colors by default too, but they look kind of cool.
471 emerge.spinner = stdout_spinner()
472 emerge.spinner.update = emerge.spinner.update_quiet
473
474 if "--quiet" not in emerge.opts:
475 print "Calculating deps..."
476
477 self.CreateDepgraph(emerge, packages)
478 depgraph = emerge.depgraph
479
480 # Build our own tree from the emerge digraph.
481 deps_tree = {}
482 digraph = depgraph._dynamic_config.digraph
483 for node, node_deps in digraph.nodes.items():
484 # Calculate dependency packages that need to be installed first. Each
485 # child on the digraph is a dependency. The "operation" field specifies
486 # what we're doing (e.g. merge, uninstall, etc.). The "priorities" array
487 # contains the type of dependency (e.g. build, runtime, runtime_post,
488 # etc.)
489 #
490 # Emerge itself actually treats some dependencies as "soft" dependencies
491 # and sometimes ignores them. We don't do that -- we honor all
492 # dependencies unless we're forced to prune them because they're cyclic.
493 #
494 # Portage refers to the identifiers for packages as a CPV. This acronym
495 # stands for Component/Path/Version.
496 #
497 # Here's an example CPV: chromeos-base/power_manager-0.0.1-r1
498 # Split up, this CPV would be:
499 # C -- Component: chromeos-base
500 # P -- Path: power_manager
501 # V -- Version: 0.0.1-r1
502 #
503 # We just refer to CPVs as packages here because it's easier.
504 deps = {}
505 for child, priorities in node_deps[0].items():
506 if isinstance(child, SetArg): continue
507 deps[str(child.cpv)] = dict(action=str(child.operation),
508 deptype=str(priorities[-1]),
509 deps={})
510
511 # We've built our list of deps, so we can add our package to the tree.
512 if isinstance(node, Package):
513 deps_tree[str(node.cpv)] = dict(action=str(node.operation),
514 deps=deps)
515
David Jamesfcb70ef2011-02-02 16:02:30 -0800516 # Ask portage for its install plan, so that we can only throw out
David Jamesf9744012011-05-04 13:19:31 -0700517 # dependencies that portage throws out.
David Jamesfcb70ef2011-02-02 16:02:30 -0800518 deps_info = {}
519 for pkg in depgraph.altlist():
520 if isinstance(pkg, Package):
David Jamesfcb70ef2011-02-02 16:02:30 -0800521 self.package_db[pkg.cpv] = pkg
522
David Jamesfcb70ef2011-02-02 16:02:30 -0800523 # Save off info about the package
David Jamesf9744012011-05-04 13:19:31 -0700524 deps_info[str(pkg.cpv)] = {"idx": len(deps_info)}
David Jamesfcb70ef2011-02-02 16:02:30 -0800525
526 seconds = time.time() - start
527 if "--quiet" not in emerge.opts:
528 print "Deps calculated in %dm%.1fs" % (seconds / 60, seconds % 60)
529
530 return deps_tree, deps_info
531
532 def PrintTree(self, deps, depth=""):
533 """Print the deps we have seen in the emerge output.
534
535 Args:
536 deps: Dependency tree structure.
537 depth: Allows printing the tree recursively, with indentation.
538 """
539 for entry in sorted(deps):
540 action = deps[entry]["action"]
541 print "%s %s (%s)" % (depth, entry, action)
542 self.PrintTree(deps[entry]["deps"], depth=depth + " ")
543
David Jamesf9744012011-05-04 13:19:31 -0700544 def GenDependencyGraph(self, deps_tree, deps_info):
David Jamesfcb70ef2011-02-02 16:02:30 -0800545 """Generate a doubly linked dependency graph.
546
547 Args:
548 deps_tree: Dependency tree structure.
549 deps_info: More details on the dependencies.
550 Returns:
551 Deps graph in the form of a dict of packages, with each package
552 specifying a "needs" list and "provides" list.
553 """
554 emerge = self.emerge
555 root = emerge.settings["ROOT"]
556
David Jamesfcb70ef2011-02-02 16:02:30 -0800557 # deps_map is the actual dependency graph.
558 #
559 # Each package specifies a "needs" list and a "provides" list. The "needs"
560 # list indicates which packages we depend on. The "provides" list
561 # indicates the reverse dependencies -- what packages need us.
562 #
563 # We also provide some other information in the dependency graph:
564 # - action: What we're planning on doing with this package. Generally,
565 # "merge", "nomerge", or "uninstall"
David Jamesfcb70ef2011-02-02 16:02:30 -0800566 deps_map = {}
567
568 def ReverseTree(packages):
569 """Convert tree to digraph.
570
571 Take the tree of package -> requirements and reverse it to a digraph of
572 buildable packages -> packages they unblock.
573 Args:
574 packages: Tree(s) of dependencies.
575 Returns:
576 Unsanitized digraph.
577 """
578 for pkg in packages:
579
580 # Create an entry for the package
581 action = packages[pkg]["action"]
David Jamesf9744012011-05-04 13:19:31 -0700582 default_pkg = {"needs": {}, "provides": set(), "action": action}
David Jamesfcb70ef2011-02-02 16:02:30 -0800583 this_pkg = deps_map.setdefault(pkg, default_pkg)
584
585 # Create entries for dependencies of this package first.
586 ReverseTree(packages[pkg]["deps"])
587
588 # Add dependencies to this package.
589 for dep, dep_item in packages[pkg]["deps"].iteritems():
590 dep_pkg = deps_map[dep]
591 dep_type = dep_item["deptype"]
592 if dep_type != "runtime_post":
593 dep_pkg["provides"].add(pkg)
594 this_pkg["needs"][dep] = dep_type
595
David Jamesfcb70ef2011-02-02 16:02:30 -0800596 def FindCycles():
597 """Find cycles in the dependency tree.
598
599 Returns:
600 A dict mapping cyclic packages to a dict of the deps that cause
601 cycles. For each dep that causes cycles, it returns an example
602 traversal of the graph that shows the cycle.
603 """
604
605 def FindCyclesAtNode(pkg, cycles, unresolved, resolved):
606 """Find cycles in cyclic dependencies starting at specified package.
607
608 Args:
609 pkg: Package identifier.
610 cycles: A dict mapping cyclic packages to a dict of the deps that
611 cause cycles. For each dep that causes cycles, it returns an
612 example traversal of the graph that shows the cycle.
613 unresolved: Nodes that have been visited but are not fully processed.
614 resolved: Nodes that have been visited and are fully processed.
615 """
616 pkg_cycles = cycles.get(pkg)
617 if pkg in resolved and not pkg_cycles:
618 # If we already looked at this package, and found no cyclic
619 # dependencies, we can stop now.
620 return
621 unresolved.append(pkg)
622 for dep in deps_map[pkg]["needs"]:
623 if dep in unresolved:
624 idx = unresolved.index(dep)
625 mycycle = unresolved[idx:] + [dep]
626 for i in range(len(mycycle) - 1):
627 pkg1, pkg2 = mycycle[i], mycycle[i+1]
628 cycles.setdefault(pkg1, {}).setdefault(pkg2, mycycle)
629 elif not pkg_cycles or dep not in pkg_cycles:
630 # Looks like we haven't seen this edge before.
631 FindCyclesAtNode(dep, cycles, unresolved, resolved)
632 unresolved.pop()
633 resolved.add(pkg)
634
635 cycles, unresolved, resolved = {}, [], set()
636 for pkg in deps_map:
637 FindCyclesAtNode(pkg, cycles, unresolved, resolved)
638 return cycles
639
David Jamesf9744012011-05-04 13:19:31 -0700640 def RemoveUnusedPackages():
David Jamesfcb70ef2011-02-02 16:02:30 -0800641 """Remove installed packages, propagating dependencies."""
David Jamesfcb70ef2011-02-02 16:02:30 -0800642 # Schedule packages that aren't on the install list for removal
643 rm_pkgs = set(deps_map.keys()) - set(deps_info.keys())
644
David Jamesfcb70ef2011-02-02 16:02:30 -0800645 # Remove the packages we don't want, simplifying the graph and making
646 # it easier for us to crack cycles.
647 for pkg in sorted(rm_pkgs):
648 this_pkg = deps_map[pkg]
649 needs = this_pkg["needs"]
650 provides = this_pkg["provides"]
651 for dep in needs:
652 dep_provides = deps_map[dep]["provides"]
653 dep_provides.update(provides)
654 dep_provides.discard(pkg)
655 dep_provides.discard(dep)
656 for target in provides:
657 target_needs = deps_map[target]["needs"]
658 target_needs.update(needs)
659 target_needs.pop(pkg, None)
660 target_needs.pop(target, None)
661 del deps_map[pkg]
662
663 def PrintCycleBreak(basedep, dep, mycycle):
664 """Print details about a cycle that we are planning on breaking.
665
666 We are breaking a cycle where dep needs basedep. mycycle is an
667 example cycle which contains dep -> basedep."""
668
669 # If it's an optional dependency, there's no need to spam the user with
670 # warning messages.
671 needs = deps_map[dep]["needs"]
672 depinfo = needs.get(basedep, "deleted")
673 if depinfo == "optional":
674 return
675
676 # Notify the user that we're breaking a cycle.
677 print "Breaking %s -> %s (%s)" % (dep, basedep, depinfo)
678
679 # Show cycle.
680 for i in range(len(mycycle) - 1):
681 pkg1, pkg2 = mycycle[i], mycycle[i+1]
682 needs = deps_map[pkg1]["needs"]
683 depinfo = needs.get(pkg2, "deleted")
684 if pkg1 == dep and pkg2 == basedep:
685 depinfo = depinfo + ", deleting"
686 print " %s -> %s (%s)" % (pkg1, pkg2, depinfo)
687
688 def SanitizeTree():
689 """Remove circular dependencies.
690
691 We prune all dependencies involved in cycles that go against the emerge
692 ordering. This has a nice property: we're guaranteed to merge
693 dependencies in the same order that portage does.
694
695 Because we don't treat any dependencies as "soft" unless they're killed
696 by a cycle, we pay attention to a larger number of dependencies when
697 merging. This hurts performance a bit, but helps reliability.
698 """
699 start = time.time()
700 cycles = FindCycles()
701 while cycles:
702 for dep, mycycles in cycles.iteritems():
703 for basedep, mycycle in mycycles.iteritems():
704 if deps_info[basedep]["idx"] >= deps_info[dep]["idx"]:
705 PrintCycleBreak(basedep, dep, mycycle)
706 del deps_map[dep]["needs"][basedep]
707 deps_map[basedep]["provides"].remove(dep)
708 cycles = FindCycles()
709 seconds = time.time() - start
710 if "--quiet" not in emerge.opts and seconds >= 0.1:
711 print "Tree sanitized in %dm%.1fs" % (seconds / 60, seconds % 60)
712
David Jamesfcb70ef2011-02-02 16:02:30 -0800713 ReverseTree(deps_tree)
David Jamesfcb70ef2011-02-02 16:02:30 -0800714
David Jamesf9744012011-05-04 13:19:31 -0700715 # We need to remove unused packages so that we can use the dependency
716 # ordering of the install process to show us what cycles to crack.
717 RemoveUnusedPackages()
David Jamesfcb70ef2011-02-02 16:02:30 -0800718 SanitizeTree()
David Jamesfcb70ef2011-02-02 16:02:30 -0800719 return deps_map
720
721 def PrintInstallPlan(self, deps_map):
722 """Print an emerge-style install plan.
723
724 The install plan lists what packages we're installing, in order.
725 It's useful for understanding what parallel_emerge is doing.
726
727 Args:
728 deps_map: The dependency graph.
729 """
730
731 def InstallPlanAtNode(target, deps_map):
732 nodes = []
733 nodes.append(target)
734 for dep in deps_map[target]["provides"]:
735 del deps_map[dep]["needs"][target]
736 if not deps_map[dep]["needs"]:
737 nodes.extend(InstallPlanAtNode(dep, deps_map))
738 return nodes
739
740 deps_map = copy.deepcopy(deps_map)
741 install_plan = []
742 plan = set()
743 for target, info in deps_map.iteritems():
744 if not info["needs"] and target not in plan:
745 for item in InstallPlanAtNode(target, deps_map):
746 plan.add(item)
747 install_plan.append(self.package_db[item])
748
749 for pkg in plan:
750 del deps_map[pkg]
751
752 if deps_map:
753 print "Cyclic dependencies:", " ".join(deps_map)
754 PrintDepsMap(deps_map)
755 sys.exit(1)
756
757 self.emerge.depgraph.display(install_plan)
758
759
760def PrintDepsMap(deps_map):
761 """Print dependency graph, for each package list it's prerequisites."""
762 for i in sorted(deps_map):
763 print "%s: (%s) needs" % (i, deps_map[i]["action"])
764 needs = deps_map[i]["needs"]
765 for j in sorted(needs):
766 print " %s" % (j)
767 if not needs:
768 print " no dependencies"
769
770
771class EmergeJobState(object):
772 __slots__ = ["done", "filename", "last_notify_timestamp", "last_output_seek",
773 "last_output_timestamp", "pkgname", "retcode", "start_timestamp",
774 "target"]
775
776 def __init__(self, target, pkgname, done, filename, start_timestamp,
777 retcode=None):
778
779 # The full name of the target we're building (e.g.
780 # chromeos-base/chromeos-0.0.1-r60)
781 self.target = target
782
783 # The short name of the target we're building (e.g. chromeos-0.0.1-r60)
784 self.pkgname = pkgname
785
786 # Whether the job is done. (True if the job is done; false otherwise.)
787 self.done = done
788
789 # The filename where output is currently stored.
790 self.filename = filename
791
792 # The timestamp of the last time we printed the name of the log file. We
793 # print this at the beginning of the job, so this starts at
794 # start_timestamp.
795 self.last_notify_timestamp = start_timestamp
796
797 # The location (in bytes) of the end of the last complete line we printed.
798 # This starts off at zero. We use this to jump to the right place when we
799 # print output from the same ebuild multiple times.
800 self.last_output_seek = 0
801
802 # The timestamp of the last time we printed output. Since we haven't
803 # printed output yet, this starts at zero.
804 self.last_output_timestamp = 0
805
806 # The return code of our job, if the job is actually finished.
807 self.retcode = retcode
808
809 # The timestamp when our job started.
810 self.start_timestamp = start_timestamp
811
812
813def SetupWorkerSignals():
814 def ExitHandler(signum, frame):
815 # Remove our signal handlers so we don't get called recursively.
816 signal.signal(signal.SIGINT, signal.SIG_DFL)
817 signal.signal(signal.SIGTERM, signal.SIG_DFL)
818
819 # Try to exit cleanly
820 sys.exit(1)
821
822 # Ensure that we exit quietly and cleanly, if possible, when we receive
823 # SIGTERM or SIGINT signals. By default, when the user hits CTRL-C, all
824 # of the child processes will print details about KeyboardInterrupt
825 # exceptions, which isn't very helpful.
826 signal.signal(signal.SIGINT, ExitHandler)
827 signal.signal(signal.SIGTERM, ExitHandler)
828
829
830def EmergeWorker(task_queue, job_queue, emerge, package_db):
831 """This worker emerges any packages given to it on the task_queue.
832
833 Args:
834 task_queue: The queue of tasks for this worker to do.
835 job_queue: The queue of results from the worker.
836 emerge: An EmergeData() object.
837 package_db: A dict, mapping package ids to portage Package objects.
838
839 It expects package identifiers to be passed to it via task_queue. When
840 a task is started, it pushes the (target, filename) to the started_queue.
841 The output is stored in filename. When a merge starts or finishes, we push
842 EmergeJobState objects to the job_queue.
843 """
844
845 SetupWorkerSignals()
846 settings, trees, mtimedb = emerge.settings, emerge.trees, emerge.mtimedb
847 opts, spinner = emerge.opts, emerge.spinner
848 opts["--nodeps"] = True
David Jamesf9744012011-05-04 13:19:31 -0700849 # When Portage launches new processes, it goes on a rampage and closes all
850 # open file descriptors. Ask Portage not to do that, as it breaks us.
851 portage.process.get_open_fds = lambda: []
David Jamesfcb70ef2011-02-02 16:02:30 -0800852 while True:
853 # Wait for a new item to show up on the queue. This is a blocking wait,
854 # so if there's nothing to do, we just sit here.
855 target = task_queue.get()
856 if not target:
857 # If target is None, this means that the main thread wants us to quit.
858 # The other workers need to exit too, so we'll push the message back on
859 # to the queue so they'll get it too.
860 task_queue.put(target)
861 return
862 db_pkg = package_db[target]
863 db_pkg.root_config = emerge.root_config
864 install_list = [db_pkg]
865 pkgname = db_pkg.pf
866 output = tempfile.NamedTemporaryFile(prefix=pkgname + "-", delete=False)
867 start_timestamp = time.time()
868 job = EmergeJobState(target, pkgname, False, output.name, start_timestamp)
869 job_queue.put(job)
870 if "--pretend" in opts:
871 retcode = 0
872 else:
873 save_stdout = sys.stdout
874 save_stderr = sys.stderr
875 try:
876 sys.stdout = output
877 sys.stderr = output
David Jamesf9744012011-05-04 13:19:31 -0700878 emerge.scheduler_graph.mergelist = install_list
879 scheduler = Scheduler(settings, trees, mtimedb, opts, spinner,
880 favorites=[], graph_config=emerge.scheduler_graph)
David Jamesfcb70ef2011-02-02 16:02:30 -0800881 retcode = scheduler.merge()
882 except Exception:
883 traceback.print_exc(file=output)
884 retcode = 1
885 finally:
886 sys.stdout = save_stdout
887 sys.stderr = save_stderr
888 output.close()
889 if retcode is None:
890 retcode = 0
891
892 job = EmergeJobState(target, pkgname, True, output.name, start_timestamp,
893 retcode)
894 job_queue.put(job)
895
896
897class LinePrinter(object):
898 """Helper object to print a single line."""
899
900 def __init__(self, line):
901 self.line = line
902
903 def Print(self, seek_locations):
904 print self.line
905
906
907class JobPrinter(object):
908 """Helper object to print output of a job."""
909
910 def __init__(self, job, unlink=False):
911 """Print output of job.
912
913 If unlink is True, unlink the job output file when done."""
914 self.current_time = time.time()
915 self.job = job
916 self.unlink = unlink
917
918 def Print(self, seek_locations):
919
920 job = self.job
921
922 # Calculate how long the job has been running.
923 seconds = self.current_time - job.start_timestamp
924
925 # Note that we've printed out the job so far.
926 job.last_output_timestamp = self.current_time
927
928 # Note that we're starting the job
929 info = "job %s (%dm%.1fs)" % (job.pkgname, seconds / 60, seconds % 60)
930 last_output_seek = seek_locations.get(job.filename, 0)
931 if last_output_seek:
932 print "=== Continue output for %s ===" % info
933 else:
934 print "=== Start output for %s ===" % info
935
936 # Print actual output from job
937 f = codecs.open(job.filename, encoding='utf-8', errors='replace')
938 f.seek(last_output_seek)
939 prefix = job.pkgname + ":"
940 for line in f:
941
942 # Save off our position in the file
943 if line and line[-1] == "\n":
944 last_output_seek = f.tell()
945 line = line[:-1]
946
947 # Print our line
948 print prefix, line.encode('utf-8', 'replace')
949 f.close()
950
951 # Save our last spot in the file so that we don't print out the same
952 # location twice.
953 seek_locations[job.filename] = last_output_seek
954
955 # Note end of output section
956 if job.done:
957 print "=== Complete: %s ===" % info
958 else:
959 print "=== Still running: %s ===" % info
960
961 if self.unlink:
962 os.unlink(job.filename)
963
964
965def PrintWorker(queue):
966 """A worker that prints stuff to the screen as requested."""
967
968 def ExitHandler(signum, frame):
969 # Switch to default signal handlers so that we'll die after two signals.
970 signal.signal(signal.SIGINT, signal.SIG_DFL)
971 signal.signal(signal.SIGTERM, signal.SIG_DFL)
972
973 # Don't exit on the first SIGINT / SIGTERM, because the parent worker will
974 # handle it and tell us when we need to exit.
975 signal.signal(signal.SIGINT, ExitHandler)
976 signal.signal(signal.SIGTERM, ExitHandler)
977
978 # seek_locations is a map indicating the position we are at in each file.
979 # It starts off empty, but is set by the various Print jobs as we go along
980 # to indicate where we left off in each file.
981 seek_locations = {}
982 while True:
983 try:
984 job = queue.get()
985 if job:
986 job.Print(seek_locations)
987 else:
988 break
989 except IOError as ex:
990 if ex.errno == errno.EINTR:
991 # Looks like we received a signal. Keep printing.
992 continue
993 raise
994
995
996class EmergeQueue(object):
997 """Class to schedule emerge jobs according to a dependency graph."""
998
999 def __init__(self, deps_map, emerge, package_db, show_output):
1000 # Store the dependency graph.
1001 self._deps_map = deps_map
1002 # Initialize the running queue to empty
1003 self._jobs = {}
1004 # List of total package installs represented in deps_map.
1005 install_jobs = [x for x in deps_map if deps_map[x]["action"] == "merge"]
1006 self._total_jobs = len(install_jobs)
1007 self._show_output = show_output
1008
1009 if "--pretend" in emerge.opts:
1010 print "Skipping merge because of --pretend mode."
1011 sys.exit(0)
1012
1013 # Setup scheduler graph object. This is used by the child processes
1014 # to help schedule jobs.
1015 emerge.scheduler_graph = emerge.depgraph.schedulerGraph()
1016
1017 # Calculate how many jobs we can run in parallel. We don't want to pass
1018 # the --jobs flag over to emerge itself, because that'll tell emerge to
1019 # hide its output, and said output is quite useful for debugging hung
1020 # jobs.
1021 procs = min(self._total_jobs,
1022 emerge.opts.pop("--jobs", multiprocessing.cpu_count()))
1023 self._emerge_queue = multiprocessing.Queue()
1024 self._job_queue = multiprocessing.Queue()
1025 self._print_queue = multiprocessing.Queue()
1026 args = (self._emerge_queue, self._job_queue, emerge, package_db)
1027 self._pool = multiprocessing.Pool(procs, EmergeWorker, args)
1028 self._print_worker = multiprocessing.Process(target=PrintWorker,
1029 args=[self._print_queue])
1030 self._print_worker.start()
1031
1032 # Initialize the failed queue to empty.
1033 self._retry_queue = []
1034 self._failed = set()
1035
1036 # Print an update before we launch the merges.
1037 self._Status()
1038
1039 # Setup an exit handler so that we print nice messages if we are
1040 # terminated.
1041 self._SetupExitHandler()
1042
1043 # Schedule our jobs.
1044 for target, info in deps_map.items():
1045 if not info["needs"]:
1046 self._Schedule(target)
1047
1048 def _SetupExitHandler(self):
1049
1050 def ExitHandler(signum, frame):
1051
1052 # Kill our signal handlers so we don't get called recursively
1053 signal.signal(signal.SIGINT, signal.SIG_DFL)
1054 signal.signal(signal.SIGTERM, signal.SIG_DFL)
1055
1056 # Print our current job status
1057 for target, job in self._jobs.iteritems():
1058 if job:
1059 self._print_queue.put(JobPrinter(job, unlink=True))
1060
1061 # Notify the user that we are exiting
1062 self._Print("Exiting on signal %s" % signum)
1063
1064 # Kill child threads, then exit.
1065 self._Exit()
1066 sys.exit(1)
1067
1068 # Print out job status when we are killed
1069 signal.signal(signal.SIGINT, ExitHandler)
1070 signal.signal(signal.SIGTERM, ExitHandler)
1071
1072 def _Schedule(self, target):
1073 # We maintain a tree of all deps, if this doesn't need
1074 # to be installed just free up it's children and continue.
1075 # It is possible to reinstall deps of deps, without reinstalling
1076 # first level deps, like so:
1077 # chromeos (merge) -> eselect (nomerge) -> python (merge)
David Jamesf9744012011-05-04 13:19:31 -07001078 if target not in self._deps_map:
1079 pass
1080 elif self._deps_map[target]["action"] == "nomerge":
David Jamesfcb70ef2011-02-02 16:02:30 -08001081 self._Finish(target)
David Jamesd20a6d92011-04-26 16:11:59 -07001082 elif target not in self._jobs:
David Jamesfcb70ef2011-02-02 16:02:30 -08001083 # Kick off the build if it's marked to be built.
1084 self._jobs[target] = None
1085 self._emerge_queue.put(target)
1086
1087 def _LoadAvg(self):
1088 loads = open("/proc/loadavg", "r").readline().split()[:3]
1089 return " ".join(loads)
1090
1091 def _Print(self, line):
1092 """Print a single line."""
1093 self._print_queue.put(LinePrinter(line))
1094
1095 def _Status(self):
1096 """Print status."""
1097 current_time = time.time()
1098 no_output = True
1099
1100 # Print interim output every minute if --show-output is used. Otherwise,
1101 # print notifications about running packages every 2 minutes, and print
1102 # full output for jobs that have been running for 60 minutes or more.
1103 if self._show_output:
1104 interval = 60
1105 notify_interval = 0
1106 else:
1107 interval = 60 * 60
1108 notify_interval = 60 * 2
1109 for target, job in self._jobs.iteritems():
1110 if job:
1111 last_timestamp = max(job.start_timestamp, job.last_output_timestamp)
1112 if last_timestamp + interval < current_time:
1113 self._print_queue.put(JobPrinter(job))
1114 job.last_output_timestamp = current_time
1115 no_output = False
1116 elif (notify_interval and
1117 job.last_notify_timestamp + notify_interval < current_time):
1118 job_seconds = current_time - job.start_timestamp
1119 args = (job.pkgname, job_seconds / 60, job_seconds % 60, job.filename)
1120 info = "Still building %s (%dm%.1fs). Logs in %s" % args
1121 job.last_notify_timestamp = current_time
1122 self._Print(info)
1123 no_output = False
1124
1125 # If we haven't printed any messages yet, print a general status message
1126 # here.
1127 if no_output:
1128 seconds = current_time - GLOBAL_START
1129 line = ("Pending %s, Ready %s, Running %s, Retrying %s, Total %s "
1130 "[Time %dm%.1fs Load %s]")
1131 qsize = self._emerge_queue.qsize()
1132 self._Print(line % (len(self._deps_map), qsize, len(self._jobs) - qsize,
1133 len(self._retry_queue), self._total_jobs,
1134 seconds / 60, seconds % 60, self._LoadAvg()))
1135
1136 def _Finish(self, target):
1137 """Mark a target as completed and unblock dependecies."""
1138 for dep in self._deps_map[target]["provides"]:
1139 del self._deps_map[dep]["needs"][target]
1140 if not self._deps_map[dep]["needs"]:
1141 self._Schedule(dep)
1142 self._deps_map.pop(target)
1143
1144 def _Retry(self):
1145 if self._retry_queue:
1146 target = self._retry_queue.pop(0)
1147 self._Schedule(target)
1148 self._Print("Retrying emerge of %s." % target)
1149
1150 def _Exit(self):
1151 # Tell emerge workers to exit. They all exit when 'None' is pushed
1152 # to the queue.
1153 self._emerge_queue.put(None)
1154 self._pool.close()
1155 self._pool.join()
1156
1157 # Now that our workers are finished, we can kill the print queue.
1158 self._print_queue.put(None)
1159 self._print_worker.join()
1160
1161 def Run(self):
1162 """Run through the scheduled ebuilds.
1163
1164 Keep running so long as we have uninstalled packages in the
1165 dependency graph to merge.
1166 """
1167 while self._deps_map:
1168 # Check here that we are actually waiting for something.
1169 if (self._emerge_queue.empty() and
1170 self._job_queue.empty() and
1171 not self._jobs and
1172 self._deps_map):
1173 # If we have failed on a package, retry it now.
1174 if self._retry_queue:
1175 self._Retry()
1176 else:
1177 # Tell child threads to exit.
1178 self._Exit()
1179
1180 # The dependency map is helpful for debugging failures.
1181 PrintDepsMap(self._deps_map)
1182
1183 # Tell the user why we're exiting.
1184 if self._failed:
1185 print "Packages failed: %s" % " ,".join(self._failed)
1186 else:
1187 print "Deadlock! Circular dependencies!"
1188 sys.exit(1)
1189
1190 try:
1191 job = self._job_queue.get(timeout=5)
1192 except Queue.Empty:
1193 # Print an update.
1194 self._Status()
1195 continue
1196
1197 target = job.target
1198
1199 if not job.done:
1200 self._jobs[target] = job
1201 self._Print("Started %s (logged in %s)" % (target, job.filename))
1202 continue
1203
1204 # Print output of job
1205 if self._show_output or job.retcode != 0:
1206 self._print_queue.put(JobPrinter(job, unlink=True))
1207 else:
1208 os.unlink(job.filename)
1209 del self._jobs[target]
1210
1211 seconds = time.time() - job.start_timestamp
1212 details = "%s (in %dm%.1fs)" % (target, seconds / 60, seconds % 60)
1213
1214 # Complain if necessary.
1215 if job.retcode != 0:
1216 # Handle job failure.
1217 if target in self._failed:
1218 # If this job has failed previously, give up.
1219 self._Print("Failed %s. Your build has failed." % details)
1220 else:
1221 # Queue up this build to try again after a long while.
1222 self._retry_queue.append(target)
1223 self._failed.add(target)
1224 self._Print("Failed %s, retrying later." % details)
1225 else:
1226 if target in self._failed and self._retry_queue:
1227 # If we have successfully retried a failed package, and there
1228 # are more failed packages, try the next one. We will only have
1229 # one retrying package actively running at a time.
1230 self._Retry()
1231
1232 self._Print("Completed %s" % details)
1233 # Mark as completed and unblock waiting ebuilds.
1234 self._Finish(target)
1235
1236 # Print an update.
1237 self._Status()
1238
1239 # Tell child threads to exit.
1240 self._Print("Merge complete")
1241 self._Exit()
1242
1243
1244def main():
1245
1246 deps = DepGraphGenerator()
1247 deps.Initialize(sys.argv[1:])
1248 emerge = deps.emerge
1249
1250 if emerge.action is not None:
1251 sys.argv = deps.ParseParallelEmergeArgs(sys.argv)
1252 sys.exit(emerge_main())
1253 elif not emerge.cmdline_packages:
1254 Usage()
1255 sys.exit(1)
1256
1257 # Unless we're in pretend mode, there's not much point running without
1258 # root access. We need to be able to install packages.
1259 #
1260 # NOTE: Even if you're running --pretend, it's a good idea to run
1261 # parallel_emerge with root access so that portage can write to the
1262 # dependency cache. This is important for performance.
1263 if "--pretend" not in emerge.opts and portage.secpass < 2:
1264 print "parallel_emerge: superuser access is required."
1265 sys.exit(1)
1266
1267 if "--quiet" not in emerge.opts:
1268 cmdline_packages = " ".join(emerge.cmdline_packages)
David Jamesfcb70ef2011-02-02 16:02:30 -08001269 print "Starting fast-emerge."
1270 print " Building package %s on %s" % (cmdline_packages,
1271 deps.board or "root")
David Jamesfcb70ef2011-02-02 16:02:30 -08001272
David Jamesf9744012011-05-04 13:19:31 -07001273 deps_tree, deps_info = deps.GenDependencyTree()
David Jamesfcb70ef2011-02-02 16:02:30 -08001274
1275 # You want me to be verbose? I'll give you two trees! Twice as much value.
1276 if "--tree" in emerge.opts and "--verbose" in emerge.opts:
1277 deps.PrintTree(deps_tree)
1278
David Jamesf9744012011-05-04 13:19:31 -07001279 deps_graph = deps.GenDependencyGraph(deps_tree, deps_info)
David Jamesfcb70ef2011-02-02 16:02:30 -08001280
1281 # OK, time to print out our progress so far.
1282 deps.PrintInstallPlan(deps_graph)
1283 if "--tree" in emerge.opts:
1284 PrintDepsMap(deps_graph)
1285
1286 # Are we upgrading portage? If so, and there are more packages to merge,
1287 # schedule a restart of parallel_emerge to merge the rest. This ensures that
1288 # we pick up all updates to portage settings before merging any more
1289 # packages.
1290 portage_upgrade = False
1291 root = emerge.settings["ROOT"]
1292 final_db = emerge.depgraph._dynamic_config.mydbapi[root]
1293 if root == "/":
1294 for db_pkg in final_db.match_pkgs("sys-apps/portage"):
1295 portage_pkg = deps_graph.get(db_pkg.cpv)
1296 if portage_pkg and len(deps_graph) > 1:
1297 portage_pkg["needs"].clear()
1298 portage_pkg["provides"].clear()
1299 deps_graph = { str(db_pkg.cpv): portage_pkg }
1300 portage_upgrade = True
1301 if "--quiet" not in emerge.opts:
1302 print "Upgrading portage first, then restarting..."
1303
1304 # Run the queued emerges.
1305 scheduler = EmergeQueue(deps_graph, emerge, deps.package_db, deps.show_output)
1306 scheduler.Run()
1307
1308 # Update world.
1309 if ("--oneshot" not in emerge.opts and
1310 "--pretend" not in emerge.opts):
1311 world_set = emerge.root_config.sets["selected"]
1312 new_world_pkgs = []
1313 for pkg in emerge.cmdline_packages:
1314 for db_pkg in final_db.match_pkgs(pkg):
1315 print "Adding %s to world" % db_pkg.cp
1316 new_world_pkgs.append(db_pkg.cp)
1317 if new_world_pkgs:
1318 world_set.update(new_world_pkgs)
1319
1320 # Update environment (library cache, symlinks, etc.)
1321 if deps.board and "--pretend" not in emerge.opts:
1322 portage.env_update()
1323
1324 # If we already upgraded portage, we don't need to do so again. But we do
1325 # need to upgrade the rest of the packages. So we'll go ahead and do that.
1326 if portage_upgrade:
David Jamesf9744012011-05-04 13:19:31 -07001327 args = sys.argv[1:] + ["--exclude=sys-apps/portage"]
David Jamesfcb70ef2011-02-02 16:02:30 -08001328 os.execvp(os.path.realpath(sys.argv[0]), args)
1329
1330 print "Done"
1331 sys.exit(0)
1332
1333if __name__ == "__main__":
1334 main()