blob: 8fa401fed06b8ed924c1b644463ba89ba95faae3 [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 James386ccd12011-05-04 20:17:42 -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 James386ccd12011-05-04 20:17:42 -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 James386ccd12011-05-04 20:17:42 -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 James386ccd12011-05-04 20:17:42 -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 James386ccd12011-05-04 20:17:42 -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 James386ccd12011-05-04 20:17:42 -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 James386ccd12011-05-04 20:17:42 -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 James386ccd12011-05-04 20:17:42 -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 James386ccd12011-05-04 20:17:42 -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 James386ccd12011-05-04 20:17:42 -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
David Jamesdeebd692011-05-09 17:02:52 -0700350 # Install packages in parallel.
351 features = features + " parallel-install"
352
David Jamesfcb70ef2011-02-02 16:02:30 -0800353 # If we're installing packages to the board, and we're not using the
354 # official flag, we can enable the following optimizations:
355 # 1) Don't lock during install step. This allows multiple packages to be
356 # installed at once. This is safe because our board packages do not
357 # muck with each other during the post-install step.
358 # 2) Don't update the environment until the end of the build. This is
359 # safe because board packages don't need to run during the build --
360 # they're cross-compiled, so our CPU architecture doesn't support them
361 # anyway.
362 if self.board and os.environ.get("CHROMEOS_OFFICIAL") != "1":
363 os.environ.setdefault("PORTAGE_LOCKS", "false")
David Jamesdeebd692011-05-09 17:02:52 -0700364 features = features + " -ebuild-locks no-env-update"
David Jamesfcb70ef2011-02-02 16:02:30 -0800365
366 os.environ["FEATURES"] = features
367
368 # Now that we've setup the necessary environment variables, we can load the
369 # emerge config from disk.
370 settings, trees, mtimedb = load_emerge_config()
371
372 # Check whether our portage tree is out of date. Typically, this happens
373 # when you're setting up a new portage tree, such as in setup_board and
374 # make_chroot. In that case, portage applies a bunch of global updates
375 # here. Once the updates are finished, we need to commit any changes
376 # that the global update made to our mtimedb, and reload the config.
377 #
378 # Portage normally handles this logic in emerge_main, but again, we can't
379 # use that function here.
380 if _global_updates(trees, mtimedb["updates"]):
381 mtimedb.commit()
382 settings, trees, mtimedb = load_emerge_config(trees=trees)
383
384 # Setup implied options. Portage normally handles this logic in
385 # emerge_main.
386 if "--buildpkgonly" in opts or "buildpkg" in settings.features:
387 opts.setdefault("--buildpkg", True)
388 if "--getbinpkgonly" in opts:
389 opts.setdefault("--usepkgonly", True)
390 opts.setdefault("--getbinpkg", True)
391 if "getbinpkg" in settings.features:
392 # Per emerge_main, FEATURES=getbinpkg overrides --getbinpkg=n
393 opts["--getbinpkg"] = True
394 if "--getbinpkg" in opts or "--usepkgonly" in opts:
395 opts.setdefault("--usepkg", True)
396 if "--fetch-all-uri" in opts:
397 opts.setdefault("--fetchonly", True)
398 if "--skipfirst" in opts:
399 opts.setdefault("--resume", True)
400 if "--buildpkgonly" in opts:
401 # --buildpkgonly will not merge anything, so it overrides all binary
402 # package options.
403 for opt in ("--getbinpkg", "--getbinpkgonly",
404 "--usepkg", "--usepkgonly"):
405 opts.pop(opt, None)
406 if (settings.get("PORTAGE_DEBUG", "") == "1" and
407 "python-trace" in settings.features):
408 portage.debug.set_trace(True)
409
410 # Complain about unsupported options
David James386ccd12011-05-04 20:17:42 -0700411 for opt in ("--ask", "--ask-enter-invalid", "--resume", "--skipfirst"):
David Jamesfcb70ef2011-02-02 16:02:30 -0800412 if opt in opts:
413 print "%s is not supported by parallel_emerge" % opt
414 sys.exit(1)
415
416 # Make emerge specific adjustments to the config (e.g. colors!)
417 adjust_configs(opts, trees)
418
419 # Save our configuration so far in the emerge object
420 emerge = self.emerge
421 emerge.action, emerge.opts = action, opts
422 emerge.settings, emerge.trees, emerge.mtimedb = settings, trees, mtimedb
423 emerge.cmdline_packages = cmdline_packages
424 root = settings["ROOT"]
425 emerge.root_config = trees[root]["root_config"]
426
David James386ccd12011-05-04 20:17:42 -0700427 if "--usepkg" in opts:
David Jamesfcb70ef2011-02-02 16:02:30 -0800428 emerge.trees[root]["bintree"].populate("--getbinpkg" in opts)
429
David Jamesfcb70ef2011-02-02 16:02:30 -0800430 def CreateDepgraph(self, emerge, packages):
431 """Create an emerge depgraph object."""
432 # Setup emerge options.
433 emerge_opts = emerge.opts.copy()
434
David James386ccd12011-05-04 20:17:42 -0700435 # Ask portage to build a dependency graph. with the options we specified
436 # above.
David Jamesfcb70ef2011-02-02 16:02:30 -0800437 params = create_depgraph_params(emerge_opts, emerge.action)
David James386ccd12011-05-04 20:17:42 -0700438 success, depgraph, _ = backtrack_depgraph(
439 emerge.settings, emerge.trees, emerge_opts, params, emerge.action,
440 packages, emerge.spinner)
441 emerge.depgraph = depgraph
David Jamesfcb70ef2011-02-02 16:02:30 -0800442
David James386ccd12011-05-04 20:17:42 -0700443 # Is it impossible to honor the user's request? Bail!
444 if not success:
445 depgraph.display_problems()
446 sys.exit(1)
David Jamesfcb70ef2011-02-02 16:02:30 -0800447
448 emerge.depgraph = depgraph
449
450 # Is it impossible to honor the user's request? Bail!
451 if not success:
452 depgraph.display_problems()
453 sys.exit(1)
454
David Jamesdeebd692011-05-09 17:02:52 -0700455 # Prime and flush emerge caches.
456 root = emerge.settings["ROOT"]
457 vardb = emerge.trees[root]["vartree"].dbapi
458 vardb.counter_tick()
459 vardb.flush_cache()
460
David James386ccd12011-05-04 20:17:42 -0700461 def GenDependencyTree(self):
David Jamesfcb70ef2011-02-02 16:02:30 -0800462 """Get dependency tree info from emerge.
463
David Jamesfcb70ef2011-02-02 16:02:30 -0800464 Returns:
465 Dependency tree
466 """
467 start = time.time()
468
469 emerge = self.emerge
470
471 # Create a list of packages to merge
472 packages = set(emerge.cmdline_packages[:])
David Jamesfcb70ef2011-02-02 16:02:30 -0800473
474 # Tell emerge to be quiet. We print plenty of info ourselves so we don't
475 # need any extra output from portage.
476 portage.util.noiselimit = -1
477
478 # My favorite feature: The silent spinner. It doesn't spin. Ever.
479 # I'd disable the colors by default too, but they look kind of cool.
480 emerge.spinner = stdout_spinner()
481 emerge.spinner.update = emerge.spinner.update_quiet
482
483 if "--quiet" not in emerge.opts:
484 print "Calculating deps..."
485
486 self.CreateDepgraph(emerge, packages)
487 depgraph = emerge.depgraph
488
489 # Build our own tree from the emerge digraph.
490 deps_tree = {}
491 digraph = depgraph._dynamic_config.digraph
492 for node, node_deps in digraph.nodes.items():
493 # Calculate dependency packages that need to be installed first. Each
494 # child on the digraph is a dependency. The "operation" field specifies
495 # what we're doing (e.g. merge, uninstall, etc.). The "priorities" array
496 # contains the type of dependency (e.g. build, runtime, runtime_post,
497 # etc.)
498 #
499 # Emerge itself actually treats some dependencies as "soft" dependencies
500 # and sometimes ignores them. We don't do that -- we honor all
501 # dependencies unless we're forced to prune them because they're cyclic.
502 #
503 # Portage refers to the identifiers for packages as a CPV. This acronym
504 # stands for Component/Path/Version.
505 #
506 # Here's an example CPV: chromeos-base/power_manager-0.0.1-r1
507 # Split up, this CPV would be:
508 # C -- Component: chromeos-base
509 # P -- Path: power_manager
510 # V -- Version: 0.0.1-r1
511 #
512 # We just refer to CPVs as packages here because it's easier.
513 deps = {}
514 for child, priorities in node_deps[0].items():
515 if isinstance(child, SetArg): continue
516 deps[str(child.cpv)] = dict(action=str(child.operation),
517 deptype=str(priorities[-1]),
518 deps={})
519
520 # We've built our list of deps, so we can add our package to the tree.
David James386ccd12011-05-04 20:17:42 -0700521 if isinstance(node, Package) and node.root == emerge.settings["ROOT"]:
David Jamesfcb70ef2011-02-02 16:02:30 -0800522 deps_tree[str(node.cpv)] = dict(action=str(node.operation),
523 deps=deps)
524
David Jamesfcb70ef2011-02-02 16:02:30 -0800525 # Ask portage for its install plan, so that we can only throw out
David James386ccd12011-05-04 20:17:42 -0700526 # dependencies that portage throws out.
David Jamesfcb70ef2011-02-02 16:02:30 -0800527 deps_info = {}
528 for pkg in depgraph.altlist():
529 if isinstance(pkg, Package):
David James386ccd12011-05-04 20:17:42 -0700530 assert pkg.root == emerge.settings["ROOT"]
David Jamesfcb70ef2011-02-02 16:02:30 -0800531 self.package_db[pkg.cpv] = pkg
532
David Jamesfcb70ef2011-02-02 16:02:30 -0800533 # Save off info about the package
David James386ccd12011-05-04 20:17:42 -0700534 deps_info[str(pkg.cpv)] = {"idx": len(deps_info)}
David Jamesfcb70ef2011-02-02 16:02:30 -0800535
536 seconds = time.time() - start
537 if "--quiet" not in emerge.opts:
538 print "Deps calculated in %dm%.1fs" % (seconds / 60, seconds % 60)
539
540 return deps_tree, deps_info
541
542 def PrintTree(self, deps, depth=""):
543 """Print the deps we have seen in the emerge output.
544
545 Args:
546 deps: Dependency tree structure.
547 depth: Allows printing the tree recursively, with indentation.
548 """
549 for entry in sorted(deps):
550 action = deps[entry]["action"]
551 print "%s %s (%s)" % (depth, entry, action)
552 self.PrintTree(deps[entry]["deps"], depth=depth + " ")
553
David James386ccd12011-05-04 20:17:42 -0700554 def GenDependencyGraph(self, deps_tree, deps_info):
David Jamesfcb70ef2011-02-02 16:02:30 -0800555 """Generate a doubly linked dependency graph.
556
557 Args:
558 deps_tree: Dependency tree structure.
559 deps_info: More details on the dependencies.
560 Returns:
561 Deps graph in the form of a dict of packages, with each package
562 specifying a "needs" list and "provides" list.
563 """
564 emerge = self.emerge
565 root = emerge.settings["ROOT"]
566
David Jamesfcb70ef2011-02-02 16:02:30 -0800567 # deps_map is the actual dependency graph.
568 #
569 # Each package specifies a "needs" list and a "provides" list. The "needs"
570 # list indicates which packages we depend on. The "provides" list
571 # indicates the reverse dependencies -- what packages need us.
572 #
573 # We also provide some other information in the dependency graph:
574 # - action: What we're planning on doing with this package. Generally,
575 # "merge", "nomerge", or "uninstall"
David Jamesfcb70ef2011-02-02 16:02:30 -0800576 deps_map = {}
577
578 def ReverseTree(packages):
579 """Convert tree to digraph.
580
581 Take the tree of package -> requirements and reverse it to a digraph of
582 buildable packages -> packages they unblock.
583 Args:
584 packages: Tree(s) of dependencies.
585 Returns:
586 Unsanitized digraph.
587 """
588 for pkg in packages:
589
590 # Create an entry for the package
591 action = packages[pkg]["action"]
David James386ccd12011-05-04 20:17:42 -0700592 default_pkg = {"needs": {}, "provides": set(), "action": action}
David Jamesfcb70ef2011-02-02 16:02:30 -0800593 this_pkg = deps_map.setdefault(pkg, default_pkg)
594
595 # Create entries for dependencies of this package first.
596 ReverseTree(packages[pkg]["deps"])
597
598 # Add dependencies to this package.
599 for dep, dep_item in packages[pkg]["deps"].iteritems():
600 dep_pkg = deps_map[dep]
601 dep_type = dep_item["deptype"]
602 if dep_type != "runtime_post":
603 dep_pkg["provides"].add(pkg)
604 this_pkg["needs"][dep] = dep_type
605
David Jamesfcb70ef2011-02-02 16:02:30 -0800606 def FindCycles():
607 """Find cycles in the dependency tree.
608
609 Returns:
610 A dict mapping cyclic packages to a dict of the deps that cause
611 cycles. For each dep that causes cycles, it returns an example
612 traversal of the graph that shows the cycle.
613 """
614
615 def FindCyclesAtNode(pkg, cycles, unresolved, resolved):
616 """Find cycles in cyclic dependencies starting at specified package.
617
618 Args:
619 pkg: Package identifier.
620 cycles: A dict mapping cyclic packages to a dict of the deps that
621 cause cycles. For each dep that causes cycles, it returns an
622 example traversal of the graph that shows the cycle.
623 unresolved: Nodes that have been visited but are not fully processed.
624 resolved: Nodes that have been visited and are fully processed.
625 """
626 pkg_cycles = cycles.get(pkg)
627 if pkg in resolved and not pkg_cycles:
628 # If we already looked at this package, and found no cyclic
629 # dependencies, we can stop now.
630 return
631 unresolved.append(pkg)
632 for dep in deps_map[pkg]["needs"]:
633 if dep in unresolved:
634 idx = unresolved.index(dep)
635 mycycle = unresolved[idx:] + [dep]
636 for i in range(len(mycycle) - 1):
637 pkg1, pkg2 = mycycle[i], mycycle[i+1]
638 cycles.setdefault(pkg1, {}).setdefault(pkg2, mycycle)
639 elif not pkg_cycles or dep not in pkg_cycles:
640 # Looks like we haven't seen this edge before.
641 FindCyclesAtNode(dep, cycles, unresolved, resolved)
642 unresolved.pop()
643 resolved.add(pkg)
644
645 cycles, unresolved, resolved = {}, [], set()
646 for pkg in deps_map:
647 FindCyclesAtNode(pkg, cycles, unresolved, resolved)
648 return cycles
649
David James386ccd12011-05-04 20:17:42 -0700650 def RemoveUnusedPackages():
David Jamesfcb70ef2011-02-02 16:02:30 -0800651 """Remove installed packages, propagating dependencies."""
David Jamesfcb70ef2011-02-02 16:02:30 -0800652 # Schedule packages that aren't on the install list for removal
653 rm_pkgs = set(deps_map.keys()) - set(deps_info.keys())
654
David Jamesfcb70ef2011-02-02 16:02:30 -0800655 # Remove the packages we don't want, simplifying the graph and making
656 # it easier for us to crack cycles.
657 for pkg in sorted(rm_pkgs):
658 this_pkg = deps_map[pkg]
659 needs = this_pkg["needs"]
660 provides = this_pkg["provides"]
661 for dep in needs:
662 dep_provides = deps_map[dep]["provides"]
663 dep_provides.update(provides)
664 dep_provides.discard(pkg)
665 dep_provides.discard(dep)
666 for target in provides:
667 target_needs = deps_map[target]["needs"]
668 target_needs.update(needs)
669 target_needs.pop(pkg, None)
670 target_needs.pop(target, None)
671 del deps_map[pkg]
672
673 def PrintCycleBreak(basedep, dep, mycycle):
674 """Print details about a cycle that we are planning on breaking.
675
676 We are breaking a cycle where dep needs basedep. mycycle is an
677 example cycle which contains dep -> basedep."""
678
679 # If it's an optional dependency, there's no need to spam the user with
680 # warning messages.
681 needs = deps_map[dep]["needs"]
682 depinfo = needs.get(basedep, "deleted")
683 if depinfo == "optional":
684 return
685
686 # Notify the user that we're breaking a cycle.
687 print "Breaking %s -> %s (%s)" % (dep, basedep, depinfo)
688
689 # Show cycle.
690 for i in range(len(mycycle) - 1):
691 pkg1, pkg2 = mycycle[i], mycycle[i+1]
692 needs = deps_map[pkg1]["needs"]
693 depinfo = needs.get(pkg2, "deleted")
694 if pkg1 == dep and pkg2 == basedep:
695 depinfo = depinfo + ", deleting"
696 print " %s -> %s (%s)" % (pkg1, pkg2, depinfo)
697
698 def SanitizeTree():
699 """Remove circular dependencies.
700
701 We prune all dependencies involved in cycles that go against the emerge
702 ordering. This has a nice property: we're guaranteed to merge
703 dependencies in the same order that portage does.
704
705 Because we don't treat any dependencies as "soft" unless they're killed
706 by a cycle, we pay attention to a larger number of dependencies when
707 merging. This hurts performance a bit, but helps reliability.
708 """
709 start = time.time()
710 cycles = FindCycles()
711 while cycles:
712 for dep, mycycles in cycles.iteritems():
713 for basedep, mycycle in mycycles.iteritems():
714 if deps_info[basedep]["idx"] >= deps_info[dep]["idx"]:
715 PrintCycleBreak(basedep, dep, mycycle)
716 del deps_map[dep]["needs"][basedep]
717 deps_map[basedep]["provides"].remove(dep)
718 cycles = FindCycles()
719 seconds = time.time() - start
720 if "--quiet" not in emerge.opts and seconds >= 0.1:
721 print "Tree sanitized in %dm%.1fs" % (seconds / 60, seconds % 60)
722
David Jamesa22906f2011-05-04 19:53:26 -0700723 ReverseTree(deps_tree)
David Jamesa22906f2011-05-04 19:53:26 -0700724
David James386ccd12011-05-04 20:17:42 -0700725 # We need to remove unused packages so that we can use the dependency
726 # ordering of the install process to show us what cycles to crack.
727 RemoveUnusedPackages()
David Jamesfcb70ef2011-02-02 16:02:30 -0800728 SanitizeTree()
David Jamesfcb70ef2011-02-02 16:02:30 -0800729 return deps_map
730
731 def PrintInstallPlan(self, deps_map):
732 """Print an emerge-style install plan.
733
734 The install plan lists what packages we're installing, in order.
735 It's useful for understanding what parallel_emerge is doing.
736
737 Args:
738 deps_map: The dependency graph.
739 """
740
741 def InstallPlanAtNode(target, deps_map):
742 nodes = []
743 nodes.append(target)
744 for dep in deps_map[target]["provides"]:
745 del deps_map[dep]["needs"][target]
746 if not deps_map[dep]["needs"]:
747 nodes.extend(InstallPlanAtNode(dep, deps_map))
748 return nodes
749
750 deps_map = copy.deepcopy(deps_map)
751 install_plan = []
752 plan = set()
753 for target, info in deps_map.iteritems():
754 if not info["needs"] and target not in plan:
755 for item in InstallPlanAtNode(target, deps_map):
756 plan.add(item)
757 install_plan.append(self.package_db[item])
758
759 for pkg in plan:
760 del deps_map[pkg]
761
762 if deps_map:
763 print "Cyclic dependencies:", " ".join(deps_map)
764 PrintDepsMap(deps_map)
765 sys.exit(1)
766
767 self.emerge.depgraph.display(install_plan)
768
769
770def PrintDepsMap(deps_map):
771 """Print dependency graph, for each package list it's prerequisites."""
772 for i in sorted(deps_map):
773 print "%s: (%s) needs" % (i, deps_map[i]["action"])
774 needs = deps_map[i]["needs"]
775 for j in sorted(needs):
776 print " %s" % (j)
777 if not needs:
778 print " no dependencies"
779
780
781class EmergeJobState(object):
782 __slots__ = ["done", "filename", "last_notify_timestamp", "last_output_seek",
783 "last_output_timestamp", "pkgname", "retcode", "start_timestamp",
784 "target"]
785
786 def __init__(self, target, pkgname, done, filename, start_timestamp,
787 retcode=None):
788
789 # The full name of the target we're building (e.g.
790 # chromeos-base/chromeos-0.0.1-r60)
791 self.target = target
792
793 # The short name of the target we're building (e.g. chromeos-0.0.1-r60)
794 self.pkgname = pkgname
795
796 # Whether the job is done. (True if the job is done; false otherwise.)
797 self.done = done
798
799 # The filename where output is currently stored.
800 self.filename = filename
801
802 # The timestamp of the last time we printed the name of the log file. We
803 # print this at the beginning of the job, so this starts at
804 # start_timestamp.
805 self.last_notify_timestamp = start_timestamp
806
807 # The location (in bytes) of the end of the last complete line we printed.
808 # This starts off at zero. We use this to jump to the right place when we
809 # print output from the same ebuild multiple times.
810 self.last_output_seek = 0
811
812 # The timestamp of the last time we printed output. Since we haven't
813 # printed output yet, this starts at zero.
814 self.last_output_timestamp = 0
815
816 # The return code of our job, if the job is actually finished.
817 self.retcode = retcode
818
819 # The timestamp when our job started.
820 self.start_timestamp = start_timestamp
821
822
823def SetupWorkerSignals():
824 def ExitHandler(signum, frame):
825 # Remove our signal handlers so we don't get called recursively.
826 signal.signal(signal.SIGINT, signal.SIG_DFL)
827 signal.signal(signal.SIGTERM, signal.SIG_DFL)
828
829 # Try to exit cleanly
830 sys.exit(1)
831
832 # Ensure that we exit quietly and cleanly, if possible, when we receive
833 # SIGTERM or SIGINT signals. By default, when the user hits CTRL-C, all
834 # of the child processes will print details about KeyboardInterrupt
835 # exceptions, which isn't very helpful.
836 signal.signal(signal.SIGINT, ExitHandler)
837 signal.signal(signal.SIGTERM, ExitHandler)
838
839
840def EmergeWorker(task_queue, job_queue, emerge, package_db):
841 """This worker emerges any packages given to it on the task_queue.
842
843 Args:
844 task_queue: The queue of tasks for this worker to do.
845 job_queue: The queue of results from the worker.
846 emerge: An EmergeData() object.
847 package_db: A dict, mapping package ids to portage Package objects.
848
849 It expects package identifiers to be passed to it via task_queue. When
850 a task is started, it pushes the (target, filename) to the started_queue.
851 The output is stored in filename. When a merge starts or finishes, we push
852 EmergeJobState objects to the job_queue.
853 """
854
855 SetupWorkerSignals()
856 settings, trees, mtimedb = emerge.settings, emerge.trees, emerge.mtimedb
David Jamesdeebd692011-05-09 17:02:52 -0700857
858 # Disable flushing of caches to save on I/O.
859 if 0 <= vercmp(portage.VERSION, "2.1.9.48"):
860 root = emerge.settings["ROOT"]
861 vardb = emerge.trees[root]["vartree"].dbapi
862 vardb._flush_cache_enabled = False
863
David Jamesfcb70ef2011-02-02 16:02:30 -0800864 opts, spinner = emerge.opts, emerge.spinner
865 opts["--nodeps"] = True
David James386ccd12011-05-04 20:17:42 -0700866 # When Portage launches new processes, it goes on a rampage and closes all
867 # open file descriptors. Ask Portage not to do that, as it breaks us.
868 portage.process.get_open_fds = lambda: []
David Jamesfcb70ef2011-02-02 16:02:30 -0800869 while True:
870 # Wait for a new item to show up on the queue. This is a blocking wait,
871 # so if there's nothing to do, we just sit here.
872 target = task_queue.get()
873 if not target:
874 # If target is None, this means that the main thread wants us to quit.
875 # The other workers need to exit too, so we'll push the message back on
876 # to the queue so they'll get it too.
877 task_queue.put(target)
878 return
879 db_pkg = package_db[target]
880 db_pkg.root_config = emerge.root_config
881 install_list = [db_pkg]
882 pkgname = db_pkg.pf
883 output = tempfile.NamedTemporaryFile(prefix=pkgname + "-", delete=False)
884 start_timestamp = time.time()
885 job = EmergeJobState(target, pkgname, False, output.name, start_timestamp)
886 job_queue.put(job)
887 if "--pretend" in opts:
888 retcode = 0
889 else:
890 save_stdout = sys.stdout
891 save_stderr = sys.stderr
892 try:
893 sys.stdout = output
894 sys.stderr = output
David James386ccd12011-05-04 20:17:42 -0700895 emerge.scheduler_graph.mergelist = install_list
896 scheduler = Scheduler(settings, trees, mtimedb, opts, spinner,
897 favorites=[], graph_config=emerge.scheduler_graph)
David Jamesfcb70ef2011-02-02 16:02:30 -0800898 retcode = scheduler.merge()
899 except Exception:
900 traceback.print_exc(file=output)
901 retcode = 1
902 finally:
903 sys.stdout = save_stdout
904 sys.stderr = save_stderr
905 output.close()
906 if retcode is None:
907 retcode = 0
908
909 job = EmergeJobState(target, pkgname, True, output.name, start_timestamp,
910 retcode)
911 job_queue.put(job)
912
913
914class LinePrinter(object):
915 """Helper object to print a single line."""
916
917 def __init__(self, line):
918 self.line = line
919
920 def Print(self, seek_locations):
921 print self.line
922
923
924class JobPrinter(object):
925 """Helper object to print output of a job."""
926
927 def __init__(self, job, unlink=False):
928 """Print output of job.
929
930 If unlink is True, unlink the job output file when done."""
931 self.current_time = time.time()
932 self.job = job
933 self.unlink = unlink
934
935 def Print(self, seek_locations):
936
937 job = self.job
938
939 # Calculate how long the job has been running.
940 seconds = self.current_time - job.start_timestamp
941
942 # Note that we've printed out the job so far.
943 job.last_output_timestamp = self.current_time
944
945 # Note that we're starting the job
946 info = "job %s (%dm%.1fs)" % (job.pkgname, seconds / 60, seconds % 60)
947 last_output_seek = seek_locations.get(job.filename, 0)
948 if last_output_seek:
949 print "=== Continue output for %s ===" % info
950 else:
951 print "=== Start output for %s ===" % info
952
953 # Print actual output from job
954 f = codecs.open(job.filename, encoding='utf-8', errors='replace')
955 f.seek(last_output_seek)
956 prefix = job.pkgname + ":"
957 for line in f:
958
959 # Save off our position in the file
960 if line and line[-1] == "\n":
961 last_output_seek = f.tell()
962 line = line[:-1]
963
964 # Print our line
965 print prefix, line.encode('utf-8', 'replace')
966 f.close()
967
968 # Save our last spot in the file so that we don't print out the same
969 # location twice.
970 seek_locations[job.filename] = last_output_seek
971
972 # Note end of output section
973 if job.done:
974 print "=== Complete: %s ===" % info
975 else:
976 print "=== Still running: %s ===" % info
977
978 if self.unlink:
979 os.unlink(job.filename)
980
981
982def PrintWorker(queue):
983 """A worker that prints stuff to the screen as requested."""
984
985 def ExitHandler(signum, frame):
986 # Switch to default signal handlers so that we'll die after two signals.
987 signal.signal(signal.SIGINT, signal.SIG_DFL)
988 signal.signal(signal.SIGTERM, signal.SIG_DFL)
989
990 # Don't exit on the first SIGINT / SIGTERM, because the parent worker will
991 # handle it and tell us when we need to exit.
992 signal.signal(signal.SIGINT, ExitHandler)
993 signal.signal(signal.SIGTERM, ExitHandler)
994
995 # seek_locations is a map indicating the position we are at in each file.
996 # It starts off empty, but is set by the various Print jobs as we go along
997 # to indicate where we left off in each file.
998 seek_locations = {}
999 while True:
1000 try:
1001 job = queue.get()
1002 if job:
1003 job.Print(seek_locations)
1004 else:
1005 break
1006 except IOError as ex:
1007 if ex.errno == errno.EINTR:
1008 # Looks like we received a signal. Keep printing.
1009 continue
1010 raise
1011
1012
1013class EmergeQueue(object):
1014 """Class to schedule emerge jobs according to a dependency graph."""
1015
1016 def __init__(self, deps_map, emerge, package_db, show_output):
1017 # Store the dependency graph.
1018 self._deps_map = deps_map
1019 # Initialize the running queue to empty
1020 self._jobs = {}
1021 # List of total package installs represented in deps_map.
1022 install_jobs = [x for x in deps_map if deps_map[x]["action"] == "merge"]
1023 self._total_jobs = len(install_jobs)
1024 self._show_output = show_output
1025
1026 if "--pretend" in emerge.opts:
1027 print "Skipping merge because of --pretend mode."
1028 sys.exit(0)
1029
1030 # Setup scheduler graph object. This is used by the child processes
1031 # to help schedule jobs.
1032 emerge.scheduler_graph = emerge.depgraph.schedulerGraph()
1033
1034 # Calculate how many jobs we can run in parallel. We don't want to pass
1035 # the --jobs flag over to emerge itself, because that'll tell emerge to
1036 # hide its output, and said output is quite useful for debugging hung
1037 # jobs.
1038 procs = min(self._total_jobs,
1039 emerge.opts.pop("--jobs", multiprocessing.cpu_count()))
1040 self._emerge_queue = multiprocessing.Queue()
1041 self._job_queue = multiprocessing.Queue()
1042 self._print_queue = multiprocessing.Queue()
1043 args = (self._emerge_queue, self._job_queue, emerge, package_db)
1044 self._pool = multiprocessing.Pool(procs, EmergeWorker, args)
1045 self._print_worker = multiprocessing.Process(target=PrintWorker,
1046 args=[self._print_queue])
1047 self._print_worker.start()
1048
1049 # Initialize the failed queue to empty.
1050 self._retry_queue = []
1051 self._failed = set()
1052
1053 # Print an update before we launch the merges.
1054 self._Status()
1055
1056 # Setup an exit handler so that we print nice messages if we are
1057 # terminated.
1058 self._SetupExitHandler()
1059
1060 # Schedule our jobs.
1061 for target, info in deps_map.items():
1062 if not info["needs"]:
1063 self._Schedule(target)
1064
1065 def _SetupExitHandler(self):
1066
1067 def ExitHandler(signum, frame):
1068
1069 # Kill our signal handlers so we don't get called recursively
1070 signal.signal(signal.SIGINT, signal.SIG_DFL)
1071 signal.signal(signal.SIGTERM, signal.SIG_DFL)
1072
1073 # Print our current job status
1074 for target, job in self._jobs.iteritems():
1075 if job:
1076 self._print_queue.put(JobPrinter(job, unlink=True))
1077
1078 # Notify the user that we are exiting
1079 self._Print("Exiting on signal %s" % signum)
1080
1081 # Kill child threads, then exit.
1082 self._Exit()
1083 sys.exit(1)
1084
1085 # Print out job status when we are killed
1086 signal.signal(signal.SIGINT, ExitHandler)
1087 signal.signal(signal.SIGTERM, ExitHandler)
1088
1089 def _Schedule(self, target):
1090 # We maintain a tree of all deps, if this doesn't need
1091 # to be installed just free up it's children and continue.
1092 # It is possible to reinstall deps of deps, without reinstalling
1093 # first level deps, like so:
1094 # chromeos (merge) -> eselect (nomerge) -> python (merge)
David James386ccd12011-05-04 20:17:42 -07001095 if target not in self._deps_map:
1096 pass
1097 elif self._deps_map[target]["action"] == "nomerge":
David Jamesfcb70ef2011-02-02 16:02:30 -08001098 self._Finish(target)
David Jamesd20a6d92011-04-26 16:11:59 -07001099 elif target not in self._jobs:
David Jamesfcb70ef2011-02-02 16:02:30 -08001100 # Kick off the build if it's marked to be built.
1101 self._jobs[target] = None
1102 self._emerge_queue.put(target)
1103
1104 def _LoadAvg(self):
1105 loads = open("/proc/loadavg", "r").readline().split()[:3]
1106 return " ".join(loads)
1107
1108 def _Print(self, line):
1109 """Print a single line."""
1110 self._print_queue.put(LinePrinter(line))
1111
1112 def _Status(self):
1113 """Print status."""
1114 current_time = time.time()
1115 no_output = True
1116
1117 # Print interim output every minute if --show-output is used. Otherwise,
1118 # print notifications about running packages every 2 minutes, and print
1119 # full output for jobs that have been running for 60 minutes or more.
1120 if self._show_output:
1121 interval = 60
1122 notify_interval = 0
1123 else:
1124 interval = 60 * 60
1125 notify_interval = 60 * 2
1126 for target, job in self._jobs.iteritems():
1127 if job:
1128 last_timestamp = max(job.start_timestamp, job.last_output_timestamp)
1129 if last_timestamp + interval < current_time:
1130 self._print_queue.put(JobPrinter(job))
1131 job.last_output_timestamp = current_time
1132 no_output = False
1133 elif (notify_interval and
1134 job.last_notify_timestamp + notify_interval < current_time):
1135 job_seconds = current_time - job.start_timestamp
1136 args = (job.pkgname, job_seconds / 60, job_seconds % 60, job.filename)
1137 info = "Still building %s (%dm%.1fs). Logs in %s" % args
1138 job.last_notify_timestamp = current_time
1139 self._Print(info)
1140 no_output = False
1141
1142 # If we haven't printed any messages yet, print a general status message
1143 # here.
1144 if no_output:
1145 seconds = current_time - GLOBAL_START
1146 line = ("Pending %s, Ready %s, Running %s, Retrying %s, Total %s "
1147 "[Time %dm%.1fs Load %s]")
1148 qsize = self._emerge_queue.qsize()
1149 self._Print(line % (len(self._deps_map), qsize, len(self._jobs) - qsize,
1150 len(self._retry_queue), self._total_jobs,
1151 seconds / 60, seconds % 60, self._LoadAvg()))
1152
1153 def _Finish(self, target):
1154 """Mark a target as completed and unblock dependecies."""
1155 for dep in self._deps_map[target]["provides"]:
1156 del self._deps_map[dep]["needs"][target]
1157 if not self._deps_map[dep]["needs"]:
1158 self._Schedule(dep)
1159 self._deps_map.pop(target)
1160
1161 def _Retry(self):
1162 if self._retry_queue:
1163 target = self._retry_queue.pop(0)
1164 self._Schedule(target)
1165 self._Print("Retrying emerge of %s." % target)
1166
1167 def _Exit(self):
1168 # Tell emerge workers to exit. They all exit when 'None' is pushed
1169 # to the queue.
1170 self._emerge_queue.put(None)
1171 self._pool.close()
1172 self._pool.join()
1173
1174 # Now that our workers are finished, we can kill the print queue.
1175 self._print_queue.put(None)
1176 self._print_worker.join()
1177
1178 def Run(self):
1179 """Run through the scheduled ebuilds.
1180
1181 Keep running so long as we have uninstalled packages in the
1182 dependency graph to merge.
1183 """
1184 while self._deps_map:
1185 # Check here that we are actually waiting for something.
1186 if (self._emerge_queue.empty() and
1187 self._job_queue.empty() and
1188 not self._jobs and
1189 self._deps_map):
1190 # If we have failed on a package, retry it now.
1191 if self._retry_queue:
1192 self._Retry()
1193 else:
1194 # Tell child threads to exit.
1195 self._Exit()
1196
1197 # The dependency map is helpful for debugging failures.
1198 PrintDepsMap(self._deps_map)
1199
1200 # Tell the user why we're exiting.
1201 if self._failed:
1202 print "Packages failed: %s" % " ,".join(self._failed)
1203 else:
1204 print "Deadlock! Circular dependencies!"
1205 sys.exit(1)
1206
1207 try:
1208 job = self._job_queue.get(timeout=5)
1209 except Queue.Empty:
1210 # Print an update.
1211 self._Status()
1212 continue
1213
1214 target = job.target
1215
1216 if not job.done:
1217 self._jobs[target] = job
1218 self._Print("Started %s (logged in %s)" % (target, job.filename))
1219 continue
1220
1221 # Print output of job
1222 if self._show_output or job.retcode != 0:
1223 self._print_queue.put(JobPrinter(job, unlink=True))
1224 else:
1225 os.unlink(job.filename)
1226 del self._jobs[target]
1227
1228 seconds = time.time() - job.start_timestamp
1229 details = "%s (in %dm%.1fs)" % (target, seconds / 60, seconds % 60)
1230
1231 # Complain if necessary.
1232 if job.retcode != 0:
1233 # Handle job failure.
1234 if target in self._failed:
1235 # If this job has failed previously, give up.
1236 self._Print("Failed %s. Your build has failed." % details)
1237 else:
1238 # Queue up this build to try again after a long while.
1239 self._retry_queue.append(target)
1240 self._failed.add(target)
1241 self._Print("Failed %s, retrying later." % details)
1242 else:
1243 if target in self._failed and self._retry_queue:
1244 # If we have successfully retried a failed package, and there
1245 # are more failed packages, try the next one. We will only have
1246 # one retrying package actively running at a time.
1247 self._Retry()
1248
1249 self._Print("Completed %s" % details)
1250 # Mark as completed and unblock waiting ebuilds.
1251 self._Finish(target)
1252
1253 # Print an update.
1254 self._Status()
1255
1256 # Tell child threads to exit.
1257 self._Print("Merge complete")
1258 self._Exit()
1259
1260
1261def main():
1262
David James57437532011-05-06 15:51:21 -07001263 parallel_emerge_args = sys.argv[:]
David Jamesfcb70ef2011-02-02 16:02:30 -08001264 deps = DepGraphGenerator()
David James57437532011-05-06 15:51:21 -07001265 deps.Initialize(parallel_emerge_args[1:])
David Jamesfcb70ef2011-02-02 16:02:30 -08001266 emerge = deps.emerge
1267
1268 if emerge.action is not None:
1269 sys.argv = deps.ParseParallelEmergeArgs(sys.argv)
1270 sys.exit(emerge_main())
1271 elif not emerge.cmdline_packages:
1272 Usage()
1273 sys.exit(1)
1274
1275 # Unless we're in pretend mode, there's not much point running without
1276 # root access. We need to be able to install packages.
1277 #
1278 # NOTE: Even if you're running --pretend, it's a good idea to run
1279 # parallel_emerge with root access so that portage can write to the
1280 # dependency cache. This is important for performance.
1281 if "--pretend" not in emerge.opts and portage.secpass < 2:
1282 print "parallel_emerge: superuser access is required."
1283 sys.exit(1)
1284
1285 if "--quiet" not in emerge.opts:
1286 cmdline_packages = " ".join(emerge.cmdline_packages)
David Jamesfcb70ef2011-02-02 16:02:30 -08001287 print "Starting fast-emerge."
1288 print " Building package %s on %s" % (cmdline_packages,
1289 deps.board or "root")
David Jamesfcb70ef2011-02-02 16:02:30 -08001290
David James386ccd12011-05-04 20:17:42 -07001291 deps_tree, deps_info = deps.GenDependencyTree()
David Jamesfcb70ef2011-02-02 16:02:30 -08001292
1293 # You want me to be verbose? I'll give you two trees! Twice as much value.
1294 if "--tree" in emerge.opts and "--verbose" in emerge.opts:
1295 deps.PrintTree(deps_tree)
1296
David James386ccd12011-05-04 20:17:42 -07001297 deps_graph = deps.GenDependencyGraph(deps_tree, deps_info)
David Jamesfcb70ef2011-02-02 16:02:30 -08001298
1299 # OK, time to print out our progress so far.
1300 deps.PrintInstallPlan(deps_graph)
1301 if "--tree" in emerge.opts:
1302 PrintDepsMap(deps_graph)
1303
1304 # Are we upgrading portage? If so, and there are more packages to merge,
1305 # schedule a restart of parallel_emerge to merge the rest. This ensures that
1306 # we pick up all updates to portage settings before merging any more
1307 # packages.
1308 portage_upgrade = False
1309 root = emerge.settings["ROOT"]
1310 final_db = emerge.depgraph._dynamic_config.mydbapi[root]
1311 if root == "/":
1312 for db_pkg in final_db.match_pkgs("sys-apps/portage"):
1313 portage_pkg = deps_graph.get(db_pkg.cpv)
1314 if portage_pkg and len(deps_graph) > 1:
1315 portage_pkg["needs"].clear()
1316 portage_pkg["provides"].clear()
1317 deps_graph = { str(db_pkg.cpv): portage_pkg }
1318 portage_upgrade = True
1319 if "--quiet" not in emerge.opts:
1320 print "Upgrading portage first, then restarting..."
1321
1322 # Run the queued emerges.
1323 scheduler = EmergeQueue(deps_graph, emerge, deps.package_db, deps.show_output)
1324 scheduler.Run()
1325
1326 # Update world.
1327 if ("--oneshot" not in emerge.opts and
1328 "--pretend" not in emerge.opts):
1329 world_set = emerge.root_config.sets["selected"]
1330 new_world_pkgs = []
1331 for pkg in emerge.cmdline_packages:
1332 for db_pkg in final_db.match_pkgs(pkg):
1333 print "Adding %s to world" % db_pkg.cp
1334 new_world_pkgs.append(db_pkg.cp)
1335 if new_world_pkgs:
1336 world_set.update(new_world_pkgs)
1337
1338 # Update environment (library cache, symlinks, etc.)
1339 if deps.board and "--pretend" not in emerge.opts:
1340 portage.env_update()
1341
1342 # If we already upgraded portage, we don't need to do so again. But we do
1343 # need to upgrade the rest of the packages. So we'll go ahead and do that.
1344 if portage_upgrade:
David James57437532011-05-06 15:51:21 -07001345 args = parallel_emerge_args + ["--exclude=sys-apps/portage"]
David Jamesfcb70ef2011-02-02 16:02:30 -08001346 os.execvp(os.path.realpath(sys.argv[0]), args)
1347
1348 print "Done"
1349 sys.exit(0)
1350
1351if __name__ == "__main__":
1352 main()