blob: fa9ae0f1734536fd1d52fb3f9cf4c8e1e4843d94 [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
David Jamescbf31972011-05-17 11:46:30 -0700114# Whether process has been killed by a signal.
115KILLED = multiprocessing.Event()
116
David Jamesfcb70ef2011-02-02 16:02:30 -0800117
118class EmergeData(object):
119 """This simple struct holds various emerge variables.
120
121 This struct helps us easily pass emerge variables around as a unit.
122 These variables are used for calculating dependencies and installing
123 packages.
124 """
125
126 __slots__ = ["action", "cmdline_packages", "depgraph", "mtimedb", "opts",
127 "root_config", "scheduler_graph", "settings", "spinner",
128 "trees"]
129
130 def __init__(self):
131 # The action the user requested. If the user is installing packages, this
132 # is None. If the user is doing anything other than installing packages,
133 # this will contain the action name, which will map exactly to the
134 # long-form name of the associated emerge option.
135 #
136 # Example: If you call parallel_emerge --unmerge package, the action name
137 # will be "unmerge"
138 self.action = None
139
140 # The list of packages the user passed on the command-line.
141 self.cmdline_packages = None
142
143 # The emerge dependency graph. It'll contain all the packages involved in
144 # this merge, along with their versions.
145 self.depgraph = None
146
147 # A dict of the options passed to emerge. This dict has been cleaned up
148 # a bit by parse_opts, so that it's a bit easier for the emerge code to
149 # look at the options.
150 #
151 # Emerge takes a few shortcuts in its cleanup process to make parsing of
152 # the options dict easier. For example, if you pass in "--usepkg=n", the
153 # "--usepkg" flag is just left out of the dictionary altogether. Because
154 # --usepkg=n is the default, this makes parsing easier, because emerge
155 # can just assume that if "--usepkg" is in the dictionary, it's enabled.
156 #
157 # These cleanup processes aren't applied to all options. For example, the
158 # --with-bdeps flag is passed in as-is. For a full list of the cleanups
159 # applied by emerge, see the parse_opts function in the _emerge.main
160 # package.
161 self.opts = None
162
163 # A dictionary used by portage to maintain global state. This state is
164 # loaded from disk when portage starts up, and saved to disk whenever we
165 # call mtimedb.commit().
166 #
167 # This database contains information about global updates (i.e., what
168 # version of portage we have) and what we're currently doing. Portage
169 # saves what it is currently doing in this database so that it can be
170 # resumed when you call it with the --resume option.
171 #
172 # parallel_emerge does not save what it is currently doing in the mtimedb,
173 # so we do not support the --resume option.
174 self.mtimedb = None
175
176 # The portage configuration for our current root. This contains the portage
177 # settings (see below) and the three portage trees for our current root.
178 # (The three portage trees are explained below, in the documentation for
179 # the "trees" member.)
180 self.root_config = None
181
182 # The scheduler graph is used by emerge to calculate what packages to
183 # install. We don't actually install any deps, so this isn't really used,
184 # but we pass it in to the Scheduler object anyway.
185 self.scheduler_graph = None
186
187 # Portage settings for our current session. Most of these settings are set
188 # in make.conf inside our current install root.
189 self.settings = None
190
191 # The spinner, which spews stuff to stdout to indicate that portage is
192 # doing something. We maintain our own spinner, so we set the portage
193 # spinner to "silent" mode.
194 self.spinner = None
195
196 # The portage trees. There are separate portage trees for each root. To get
197 # the portage tree for the current root, you can look in self.trees[root],
198 # where root = self.settings["ROOT"].
199 #
200 # In each root, there are three trees: vartree, porttree, and bintree.
201 # - vartree: A database of the currently-installed packages.
202 # - porttree: A database of ebuilds, that can be used to build packages.
203 # - bintree: A database of binary packages.
204 self.trees = None
205
206
207class DepGraphGenerator(object):
208 """Grab dependency information about packages from portage.
209
210 Typical usage:
211 deps = DepGraphGenerator()
212 deps.Initialize(sys.argv[1:])
213 deps_tree, deps_info = deps.GenDependencyTree()
214 deps_graph = deps.GenDependencyGraph(deps_tree, deps_info)
215 deps.PrintTree(deps_tree)
216 PrintDepsMap(deps_graph)
217 """
218
David James386ccd12011-05-04 20:17:42 -0700219 __slots__ = ["board", "emerge", "package_db", "show_output"]
David Jamesfcb70ef2011-02-02 16:02:30 -0800220
221 def __init__(self):
222 self.board = None
223 self.emerge = EmergeData()
David Jamesfcb70ef2011-02-02 16:02:30 -0800224 self.package_db = {}
David Jamesfcb70ef2011-02-02 16:02:30 -0800225 self.show_output = False
David Jamesfcb70ef2011-02-02 16:02:30 -0800226
227 def ParseParallelEmergeArgs(self, argv):
228 """Read the parallel emerge arguments from the command-line.
229
230 We need to be compatible with emerge arg format. We scrape arguments that
231 are specific to parallel_emerge, and pass through the rest directly to
232 emerge.
233 Args:
234 argv: arguments list
235 Returns:
236 Arguments that don't belong to parallel_emerge
237 """
238 emerge_args = []
239 for arg in argv:
240 # Specifically match arguments that are specific to parallel_emerge, and
241 # pass through the rest.
242 if arg.startswith("--board="):
243 self.board = arg.replace("--board=", "")
244 elif arg.startswith("--workon="):
245 workon_str = arg.replace("--workon=", "")
David James386ccd12011-05-04 20:17:42 -0700246 if NEW_PORTAGE:
247 emerge_args.append("--reinstall-atoms=%s" % workon_str)
248 emerge_args.append("--usepkg-exclude=%s" % workon_str)
David Jamesfcb70ef2011-02-02 16:02:30 -0800249 elif arg.startswith("--force-remote-binary="):
250 force_remote_binary = arg.replace("--force-remote-binary=", "")
David James386ccd12011-05-04 20:17:42 -0700251 if NEW_PORTAGE:
252 emerge_args.append("--useoldpkg-atoms=%s" % force_remote_binary)
David Jamesfcb70ef2011-02-02 16:02:30 -0800253 elif arg == "--show-output":
254 self.show_output = True
David James386ccd12011-05-04 20:17:42 -0700255 elif arg == "--rebuild":
256 if NEW_PORTAGE:
257 emerge_args.append("--rebuild-if-unbuilt")
David Jamesfcb70ef2011-02-02 16:02:30 -0800258 else:
259 # Not one of our options, so pass through to emerge.
260 emerge_args.append(arg)
261
David James386ccd12011-05-04 20:17:42 -0700262 # These packages take a really long time to build, so, for expediency, we
263 # are blacklisting them from automatic rebuilds because one of their
264 # dependencies needs to be recompiled.
265 for pkg in ("chromeos-base/chromeos-chrome", "media-plugins/o3d",
266 "dev-java/icedtea"):
267 if NEW_PORTAGE:
268 emerge_args.append("--rebuild-exclude=%s" % pkg)
David Jamesfcb70ef2011-02-02 16:02:30 -0800269
270 return emerge_args
271
272 def Initialize(self, args):
273 """Initializer. Parses arguments and sets up portage state."""
274
275 # Parse and strip out args that are just intended for parallel_emerge.
276 emerge_args = self.ParseParallelEmergeArgs(args)
277
278 # Setup various environment variables based on our current board. These
279 # variables are normally setup inside emerge-${BOARD}, but since we don't
280 # call that script, we have to set it up here. These variables serve to
281 # point our tools at /build/BOARD and to setup cross compiles to the
282 # appropriate board as configured in toolchain.conf.
283 if self.board:
284 os.environ["PORTAGE_CONFIGROOT"] = "/build/" + self.board
285 os.environ["PORTAGE_SYSROOT"] = "/build/" + self.board
286 os.environ["SYSROOT"] = "/build/" + self.board
287 srcroot = "%s/../../src" % os.path.dirname(os.path.realpath(__file__))
288 # Strip the variant out of the board name to look for the toolchain. This
289 # is similar to what setup_board does.
290 board_no_variant = self.board.split('_')[0]
291 public_toolchain_path = ("%s/overlays/overlay-%s/toolchain.conf" %
292 (srcroot, board_no_variant))
293 private_toolchain_path = (
294 "%s/private-overlays/overlay-%s-private/toolchain.conf" %
295 (srcroot, board_no_variant))
296 if os.path.isfile(public_toolchain_path):
297 toolchain_path = public_toolchain_path
298 elif os.path.isfile(private_toolchain_path):
299 toolchain_path = private_toolchain_path
300 else:
301 print "Not able to locate toolchain.conf in board overlays"
302 sys.exit(1)
303
304 f = open(toolchain_path)
305 os.environ["CHOST"] = f.readline().strip()
306 f.close()
307
308 # Although CHROMEOS_ROOT isn't specific to boards, it's normally setup
309 # inside emerge-${BOARD}, so we set it up here for compatibility. It
310 # will be going away soon as we migrate to CROS_WORKON_SRCROOT.
311 os.environ.setdefault("CHROMEOS_ROOT", os.environ["HOME"] + "/trunk")
312
313 # Turn off interactive delays
314 os.environ["EBEEP_IGNORE"] = "1"
315 os.environ["EPAUSE_IGNORE"] = "1"
316 os.environ["UNMERGE_DELAY"] = "0"
317
318 # Parse the emerge options.
319 action, opts, cmdline_packages = parse_opts(emerge_args)
320
321 # If we're installing to the board, we want the --root-deps option so that
322 # portage will install the build dependencies to that location as well.
323 if self.board:
324 opts.setdefault("--root-deps", True)
325
326 # Set environment variables based on options. Portage normally sets these
327 # environment variables in emerge_main, but we can't use that function,
328 # because it also does a bunch of other stuff that we don't want.
329 # TODO(davidjames): Patch portage to move this logic into a function we can
330 # reuse here.
331 if "--debug" in opts:
332 os.environ["PORTAGE_DEBUG"] = "1"
333 if "--config-root" in opts:
334 os.environ["PORTAGE_CONFIGROOT"] = opts["--config-root"]
335 if "--root" in opts:
336 os.environ["ROOT"] = opts["--root"]
337 if "--accept-properties" in opts:
338 os.environ["ACCEPT_PROPERTIES"] = opts["--accept-properties"]
339
340 # Portage has two flags for doing collision protection: collision-protect
341 # and protect-owned. The protect-owned feature is enabled by default and
342 # is quite useful: it checks to make sure that we don't have multiple
343 # packages that own the same file. The collision-protect feature is more
344 # strict, and less useful: it fails if it finds a conflicting file, even
345 # if that file was created by an earlier ebuild that failed to install.
346 #
347 # We want to disable collision-protect here because we don't handle
348 # failures during the merge step very well. Sometimes we leave old files
349 # lying around and they cause problems, so for now we disable the flag.
350 # TODO(davidjames): Look for a better solution.
351 features = os.environ.get("FEATURES", "") + " -collision-protect"
352
David Jamesdeebd692011-05-09 17:02:52 -0700353 # Install packages in parallel.
354 features = features + " parallel-install"
355
David Jamesfcb70ef2011-02-02 16:02:30 -0800356 # If we're installing packages to the board, and we're not using the
357 # official flag, we can enable the following optimizations:
358 # 1) Don't lock during install step. This allows multiple packages to be
359 # installed at once. This is safe because our board packages do not
360 # muck with each other during the post-install step.
361 # 2) Don't update the environment until the end of the build. This is
362 # safe because board packages don't need to run during the build --
363 # they're cross-compiled, so our CPU architecture doesn't support them
364 # anyway.
365 if self.board and os.environ.get("CHROMEOS_OFFICIAL") != "1":
366 os.environ.setdefault("PORTAGE_LOCKS", "false")
David Jamesdeebd692011-05-09 17:02:52 -0700367 features = features + " -ebuild-locks no-env-update"
David Jamesfcb70ef2011-02-02 16:02:30 -0800368
369 os.environ["FEATURES"] = features
370
371 # Now that we've setup the necessary environment variables, we can load the
372 # emerge config from disk.
373 settings, trees, mtimedb = load_emerge_config()
374
375 # Check whether our portage tree is out of date. Typically, this happens
376 # when you're setting up a new portage tree, such as in setup_board and
377 # make_chroot. In that case, portage applies a bunch of global updates
378 # here. Once the updates are finished, we need to commit any changes
379 # that the global update made to our mtimedb, and reload the config.
380 #
381 # Portage normally handles this logic in emerge_main, but again, we can't
382 # use that function here.
383 if _global_updates(trees, mtimedb["updates"]):
384 mtimedb.commit()
385 settings, trees, mtimedb = load_emerge_config(trees=trees)
386
387 # Setup implied options. Portage normally handles this logic in
388 # emerge_main.
389 if "--buildpkgonly" in opts or "buildpkg" in settings.features:
390 opts.setdefault("--buildpkg", True)
391 if "--getbinpkgonly" in opts:
392 opts.setdefault("--usepkgonly", True)
393 opts.setdefault("--getbinpkg", True)
394 if "getbinpkg" in settings.features:
395 # Per emerge_main, FEATURES=getbinpkg overrides --getbinpkg=n
396 opts["--getbinpkg"] = True
397 if "--getbinpkg" in opts or "--usepkgonly" in opts:
398 opts.setdefault("--usepkg", True)
399 if "--fetch-all-uri" in opts:
400 opts.setdefault("--fetchonly", True)
401 if "--skipfirst" in opts:
402 opts.setdefault("--resume", True)
403 if "--buildpkgonly" in opts:
404 # --buildpkgonly will not merge anything, so it overrides all binary
405 # package options.
406 for opt in ("--getbinpkg", "--getbinpkgonly",
407 "--usepkg", "--usepkgonly"):
408 opts.pop(opt, None)
409 if (settings.get("PORTAGE_DEBUG", "") == "1" and
410 "python-trace" in settings.features):
411 portage.debug.set_trace(True)
412
413 # Complain about unsupported options
David James386ccd12011-05-04 20:17:42 -0700414 for opt in ("--ask", "--ask-enter-invalid", "--resume", "--skipfirst"):
David Jamesfcb70ef2011-02-02 16:02:30 -0800415 if opt in opts:
416 print "%s is not supported by parallel_emerge" % opt
417 sys.exit(1)
418
419 # Make emerge specific adjustments to the config (e.g. colors!)
420 adjust_configs(opts, trees)
421
422 # Save our configuration so far in the emerge object
423 emerge = self.emerge
424 emerge.action, emerge.opts = action, opts
425 emerge.settings, emerge.trees, emerge.mtimedb = settings, trees, mtimedb
426 emerge.cmdline_packages = cmdline_packages
427 root = settings["ROOT"]
428 emerge.root_config = trees[root]["root_config"]
429
David James386ccd12011-05-04 20:17:42 -0700430 if "--usepkg" in opts:
David Jamesfcb70ef2011-02-02 16:02:30 -0800431 emerge.trees[root]["bintree"].populate("--getbinpkg" in opts)
432
David Jamesfcb70ef2011-02-02 16:02:30 -0800433 def CreateDepgraph(self, emerge, packages):
434 """Create an emerge depgraph object."""
435 # Setup emerge options.
436 emerge_opts = emerge.opts.copy()
437
David James386ccd12011-05-04 20:17:42 -0700438 # Ask portage to build a dependency graph. with the options we specified
439 # above.
David Jamesfcb70ef2011-02-02 16:02:30 -0800440 params = create_depgraph_params(emerge_opts, emerge.action)
David James386ccd12011-05-04 20:17:42 -0700441 success, depgraph, _ = backtrack_depgraph(
442 emerge.settings, emerge.trees, emerge_opts, params, emerge.action,
443 packages, emerge.spinner)
444 emerge.depgraph = depgraph
David Jamesfcb70ef2011-02-02 16:02:30 -0800445
David James386ccd12011-05-04 20:17:42 -0700446 # Is it impossible to honor the user's request? Bail!
447 if not success:
448 depgraph.display_problems()
449 sys.exit(1)
David Jamesfcb70ef2011-02-02 16:02:30 -0800450
451 emerge.depgraph = depgraph
452
453 # Is it impossible to honor the user's request? Bail!
454 if not success:
455 depgraph.display_problems()
456 sys.exit(1)
457
David Jamesdeebd692011-05-09 17:02:52 -0700458 # Prime and flush emerge caches.
459 root = emerge.settings["ROOT"]
460 vardb = emerge.trees[root]["vartree"].dbapi
David James0bdc5de2011-05-12 16:22:26 -0700461 if "--pretend" not in emerge.opts:
462 vardb.counter_tick()
David Jamesdeebd692011-05-09 17:02:52 -0700463 vardb.flush_cache()
464
David James386ccd12011-05-04 20:17:42 -0700465 def GenDependencyTree(self):
David Jamesfcb70ef2011-02-02 16:02:30 -0800466 """Get dependency tree info from emerge.
467
David Jamesfcb70ef2011-02-02 16:02:30 -0800468 Returns:
469 Dependency tree
470 """
471 start = time.time()
472
473 emerge = self.emerge
474
475 # Create a list of packages to merge
476 packages = set(emerge.cmdline_packages[:])
David Jamesfcb70ef2011-02-02 16:02:30 -0800477
478 # Tell emerge to be quiet. We print plenty of info ourselves so we don't
479 # need any extra output from portage.
480 portage.util.noiselimit = -1
481
482 # My favorite feature: The silent spinner. It doesn't spin. Ever.
483 # I'd disable the colors by default too, but they look kind of cool.
484 emerge.spinner = stdout_spinner()
485 emerge.spinner.update = emerge.spinner.update_quiet
486
487 if "--quiet" not in emerge.opts:
488 print "Calculating deps..."
489
490 self.CreateDepgraph(emerge, packages)
491 depgraph = emerge.depgraph
492
493 # Build our own tree from the emerge digraph.
494 deps_tree = {}
495 digraph = depgraph._dynamic_config.digraph
496 for node, node_deps in digraph.nodes.items():
497 # Calculate dependency packages that need to be installed first. Each
498 # child on the digraph is a dependency. The "operation" field specifies
499 # what we're doing (e.g. merge, uninstall, etc.). The "priorities" array
500 # contains the type of dependency (e.g. build, runtime, runtime_post,
501 # etc.)
502 #
503 # Emerge itself actually treats some dependencies as "soft" dependencies
504 # and sometimes ignores them. We don't do that -- we honor all
505 # dependencies unless we're forced to prune them because they're cyclic.
506 #
507 # Portage refers to the identifiers for packages as a CPV. This acronym
508 # stands for Component/Path/Version.
509 #
510 # Here's an example CPV: chromeos-base/power_manager-0.0.1-r1
511 # Split up, this CPV would be:
512 # C -- Component: chromeos-base
513 # P -- Path: power_manager
514 # V -- Version: 0.0.1-r1
515 #
516 # We just refer to CPVs as packages here because it's easier.
517 deps = {}
518 for child, priorities in node_deps[0].items():
519 if isinstance(child, SetArg): continue
520 deps[str(child.cpv)] = dict(action=str(child.operation),
521 deptype=str(priorities[-1]),
522 deps={})
523
524 # We've built our list of deps, so we can add our package to the tree.
David James386ccd12011-05-04 20:17:42 -0700525 if isinstance(node, Package) and node.root == emerge.settings["ROOT"]:
David Jamesfcb70ef2011-02-02 16:02:30 -0800526 deps_tree[str(node.cpv)] = dict(action=str(node.operation),
527 deps=deps)
528
David Jamesfcb70ef2011-02-02 16:02:30 -0800529 # Ask portage for its install plan, so that we can only throw out
David James386ccd12011-05-04 20:17:42 -0700530 # dependencies that portage throws out.
David Jamesfcb70ef2011-02-02 16:02:30 -0800531 deps_info = {}
532 for pkg in depgraph.altlist():
533 if isinstance(pkg, Package):
David James386ccd12011-05-04 20:17:42 -0700534 assert pkg.root == emerge.settings["ROOT"]
David Jamesfcb70ef2011-02-02 16:02:30 -0800535 self.package_db[pkg.cpv] = pkg
536
David Jamesfcb70ef2011-02-02 16:02:30 -0800537 # Save off info about the package
David James386ccd12011-05-04 20:17:42 -0700538 deps_info[str(pkg.cpv)] = {"idx": len(deps_info)}
David Jamesfcb70ef2011-02-02 16:02:30 -0800539
540 seconds = time.time() - start
541 if "--quiet" not in emerge.opts:
542 print "Deps calculated in %dm%.1fs" % (seconds / 60, seconds % 60)
543
544 return deps_tree, deps_info
545
546 def PrintTree(self, deps, depth=""):
547 """Print the deps we have seen in the emerge output.
548
549 Args:
550 deps: Dependency tree structure.
551 depth: Allows printing the tree recursively, with indentation.
552 """
553 for entry in sorted(deps):
554 action = deps[entry]["action"]
555 print "%s %s (%s)" % (depth, entry, action)
556 self.PrintTree(deps[entry]["deps"], depth=depth + " ")
557
David James386ccd12011-05-04 20:17:42 -0700558 def GenDependencyGraph(self, deps_tree, deps_info):
David Jamesfcb70ef2011-02-02 16:02:30 -0800559 """Generate a doubly linked dependency graph.
560
561 Args:
562 deps_tree: Dependency tree structure.
563 deps_info: More details on the dependencies.
564 Returns:
565 Deps graph in the form of a dict of packages, with each package
566 specifying a "needs" list and "provides" list.
567 """
568 emerge = self.emerge
569 root = emerge.settings["ROOT"]
570
David Jamesfcb70ef2011-02-02 16:02:30 -0800571 # deps_map is the actual dependency graph.
572 #
573 # Each package specifies a "needs" list and a "provides" list. The "needs"
574 # list indicates which packages we depend on. The "provides" list
575 # indicates the reverse dependencies -- what packages need us.
576 #
577 # We also provide some other information in the dependency graph:
578 # - action: What we're planning on doing with this package. Generally,
579 # "merge", "nomerge", or "uninstall"
David Jamesfcb70ef2011-02-02 16:02:30 -0800580 deps_map = {}
581
582 def ReverseTree(packages):
583 """Convert tree to digraph.
584
585 Take the tree of package -> requirements and reverse it to a digraph of
586 buildable packages -> packages they unblock.
587 Args:
588 packages: Tree(s) of dependencies.
589 Returns:
590 Unsanitized digraph.
591 """
592 for pkg in packages:
593
594 # Create an entry for the package
595 action = packages[pkg]["action"]
David James386ccd12011-05-04 20:17:42 -0700596 default_pkg = {"needs": {}, "provides": set(), "action": action}
David Jamesfcb70ef2011-02-02 16:02:30 -0800597 this_pkg = deps_map.setdefault(pkg, default_pkg)
598
599 # Create entries for dependencies of this package first.
600 ReverseTree(packages[pkg]["deps"])
601
602 # Add dependencies to this package.
603 for dep, dep_item in packages[pkg]["deps"].iteritems():
604 dep_pkg = deps_map[dep]
605 dep_type = dep_item["deptype"]
606 if dep_type != "runtime_post":
607 dep_pkg["provides"].add(pkg)
608 this_pkg["needs"][dep] = dep_type
609
David Jamesfcb70ef2011-02-02 16:02:30 -0800610 def FindCycles():
611 """Find cycles in the dependency tree.
612
613 Returns:
614 A dict mapping cyclic packages to a dict of the deps that cause
615 cycles. For each dep that causes cycles, it returns an example
616 traversal of the graph that shows the cycle.
617 """
618
619 def FindCyclesAtNode(pkg, cycles, unresolved, resolved):
620 """Find cycles in cyclic dependencies starting at specified package.
621
622 Args:
623 pkg: Package identifier.
624 cycles: A dict mapping cyclic packages to a dict of the deps that
625 cause cycles. For each dep that causes cycles, it returns an
626 example traversal of the graph that shows the cycle.
627 unresolved: Nodes that have been visited but are not fully processed.
628 resolved: Nodes that have been visited and are fully processed.
629 """
630 pkg_cycles = cycles.get(pkg)
631 if pkg in resolved and not pkg_cycles:
632 # If we already looked at this package, and found no cyclic
633 # dependencies, we can stop now.
634 return
635 unresolved.append(pkg)
636 for dep in deps_map[pkg]["needs"]:
637 if dep in unresolved:
638 idx = unresolved.index(dep)
639 mycycle = unresolved[idx:] + [dep]
640 for i in range(len(mycycle) - 1):
641 pkg1, pkg2 = mycycle[i], mycycle[i+1]
642 cycles.setdefault(pkg1, {}).setdefault(pkg2, mycycle)
643 elif not pkg_cycles or dep not in pkg_cycles:
644 # Looks like we haven't seen this edge before.
645 FindCyclesAtNode(dep, cycles, unresolved, resolved)
646 unresolved.pop()
647 resolved.add(pkg)
648
649 cycles, unresolved, resolved = {}, [], set()
650 for pkg in deps_map:
651 FindCyclesAtNode(pkg, cycles, unresolved, resolved)
652 return cycles
653
David James386ccd12011-05-04 20:17:42 -0700654 def RemoveUnusedPackages():
David Jamesfcb70ef2011-02-02 16:02:30 -0800655 """Remove installed packages, propagating dependencies."""
David Jamesfcb70ef2011-02-02 16:02:30 -0800656 # Schedule packages that aren't on the install list for removal
657 rm_pkgs = set(deps_map.keys()) - set(deps_info.keys())
658
David Jamesfcb70ef2011-02-02 16:02:30 -0800659 # Remove the packages we don't want, simplifying the graph and making
660 # it easier for us to crack cycles.
661 for pkg in sorted(rm_pkgs):
662 this_pkg = deps_map[pkg]
663 needs = this_pkg["needs"]
664 provides = this_pkg["provides"]
665 for dep in needs:
666 dep_provides = deps_map[dep]["provides"]
667 dep_provides.update(provides)
668 dep_provides.discard(pkg)
669 dep_provides.discard(dep)
670 for target in provides:
671 target_needs = deps_map[target]["needs"]
672 target_needs.update(needs)
673 target_needs.pop(pkg, None)
674 target_needs.pop(target, None)
675 del deps_map[pkg]
676
677 def PrintCycleBreak(basedep, dep, mycycle):
678 """Print details about a cycle that we are planning on breaking.
679
680 We are breaking a cycle where dep needs basedep. mycycle is an
681 example cycle which contains dep -> basedep."""
682
683 # If it's an optional dependency, there's no need to spam the user with
684 # warning messages.
685 needs = deps_map[dep]["needs"]
686 depinfo = needs.get(basedep, "deleted")
687 if depinfo == "optional":
688 return
689
690 # Notify the user that we're breaking a cycle.
691 print "Breaking %s -> %s (%s)" % (dep, basedep, depinfo)
692
693 # Show cycle.
694 for i in range(len(mycycle) - 1):
695 pkg1, pkg2 = mycycle[i], mycycle[i+1]
696 needs = deps_map[pkg1]["needs"]
697 depinfo = needs.get(pkg2, "deleted")
698 if pkg1 == dep and pkg2 == basedep:
699 depinfo = depinfo + ", deleting"
700 print " %s -> %s (%s)" % (pkg1, pkg2, depinfo)
701
702 def SanitizeTree():
703 """Remove circular dependencies.
704
705 We prune all dependencies involved in cycles that go against the emerge
706 ordering. This has a nice property: we're guaranteed to merge
707 dependencies in the same order that portage does.
708
709 Because we don't treat any dependencies as "soft" unless they're killed
710 by a cycle, we pay attention to a larger number of dependencies when
711 merging. This hurts performance a bit, but helps reliability.
712 """
713 start = time.time()
714 cycles = FindCycles()
715 while cycles:
716 for dep, mycycles in cycles.iteritems():
717 for basedep, mycycle in mycycles.iteritems():
718 if deps_info[basedep]["idx"] >= deps_info[dep]["idx"]:
719 PrintCycleBreak(basedep, dep, mycycle)
720 del deps_map[dep]["needs"][basedep]
721 deps_map[basedep]["provides"].remove(dep)
722 cycles = FindCycles()
723 seconds = time.time() - start
724 if "--quiet" not in emerge.opts and seconds >= 0.1:
725 print "Tree sanitized in %dm%.1fs" % (seconds / 60, seconds % 60)
726
David Jamesa22906f2011-05-04 19:53:26 -0700727 ReverseTree(deps_tree)
David Jamesa22906f2011-05-04 19:53:26 -0700728
David James386ccd12011-05-04 20:17:42 -0700729 # We need to remove unused packages so that we can use the dependency
730 # ordering of the install process to show us what cycles to crack.
731 RemoveUnusedPackages()
David Jamesfcb70ef2011-02-02 16:02:30 -0800732 SanitizeTree()
David Jamesfcb70ef2011-02-02 16:02:30 -0800733 return deps_map
734
735 def PrintInstallPlan(self, deps_map):
736 """Print an emerge-style install plan.
737
738 The install plan lists what packages we're installing, in order.
739 It's useful for understanding what parallel_emerge is doing.
740
741 Args:
742 deps_map: The dependency graph.
743 """
744
745 def InstallPlanAtNode(target, deps_map):
746 nodes = []
747 nodes.append(target)
748 for dep in deps_map[target]["provides"]:
749 del deps_map[dep]["needs"][target]
750 if not deps_map[dep]["needs"]:
751 nodes.extend(InstallPlanAtNode(dep, deps_map))
752 return nodes
753
754 deps_map = copy.deepcopy(deps_map)
755 install_plan = []
756 plan = set()
757 for target, info in deps_map.iteritems():
758 if not info["needs"] and target not in plan:
759 for item in InstallPlanAtNode(target, deps_map):
760 plan.add(item)
761 install_plan.append(self.package_db[item])
762
763 for pkg in plan:
764 del deps_map[pkg]
765
766 if deps_map:
767 print "Cyclic dependencies:", " ".join(deps_map)
768 PrintDepsMap(deps_map)
769 sys.exit(1)
770
771 self.emerge.depgraph.display(install_plan)
772
773
774def PrintDepsMap(deps_map):
775 """Print dependency graph, for each package list it's prerequisites."""
776 for i in sorted(deps_map):
777 print "%s: (%s) needs" % (i, deps_map[i]["action"])
778 needs = deps_map[i]["needs"]
779 for j in sorted(needs):
780 print " %s" % (j)
781 if not needs:
782 print " no dependencies"
783
784
785class EmergeJobState(object):
786 __slots__ = ["done", "filename", "last_notify_timestamp", "last_output_seek",
787 "last_output_timestamp", "pkgname", "retcode", "start_timestamp",
788 "target"]
789
790 def __init__(self, target, pkgname, done, filename, start_timestamp,
791 retcode=None):
792
793 # The full name of the target we're building (e.g.
794 # chromeos-base/chromeos-0.0.1-r60)
795 self.target = target
796
797 # The short name of the target we're building (e.g. chromeos-0.0.1-r60)
798 self.pkgname = pkgname
799
800 # Whether the job is done. (True if the job is done; false otherwise.)
801 self.done = done
802
803 # The filename where output is currently stored.
804 self.filename = filename
805
806 # The timestamp of the last time we printed the name of the log file. We
807 # print this at the beginning of the job, so this starts at
808 # start_timestamp.
809 self.last_notify_timestamp = start_timestamp
810
811 # The location (in bytes) of the end of the last complete line we printed.
812 # This starts off at zero. We use this to jump to the right place when we
813 # print output from the same ebuild multiple times.
814 self.last_output_seek = 0
815
816 # The timestamp of the last time we printed output. Since we haven't
817 # printed output yet, this starts at zero.
818 self.last_output_timestamp = 0
819
820 # The return code of our job, if the job is actually finished.
821 self.retcode = retcode
822
823 # The timestamp when our job started.
824 self.start_timestamp = start_timestamp
825
826
David Jamescbf31972011-05-17 11:46:30 -0700827def KillHandler(signum, frame):
828 # Kill self and all subprocesses.
829 os.killpg(0, signal.SIGKILL)
830
David Jamesfcb70ef2011-02-02 16:02:30 -0800831def SetupWorkerSignals():
832 def ExitHandler(signum, frame):
David Jamescbf31972011-05-17 11:46:30 -0700833 # Set KILLED flag.
834 KILLED.set()
David Jamesfcb70ef2011-02-02 16:02:30 -0800835
David Jamescbf31972011-05-17 11:46:30 -0700836 # Remove our signal handlers so we don't get called recursively.
837 signal.signal(signal.SIGINT, KillHandler)
838 signal.signal(signal.SIGTERM, KillHandler)
David Jamesfcb70ef2011-02-02 16:02:30 -0800839
840 # Ensure that we exit quietly and cleanly, if possible, when we receive
841 # SIGTERM or SIGINT signals. By default, when the user hits CTRL-C, all
842 # of the child processes will print details about KeyboardInterrupt
843 # exceptions, which isn't very helpful.
844 signal.signal(signal.SIGINT, ExitHandler)
845 signal.signal(signal.SIGTERM, ExitHandler)
846
847
848def EmergeWorker(task_queue, job_queue, emerge, package_db):
849 """This worker emerges any packages given to it on the task_queue.
850
851 Args:
852 task_queue: The queue of tasks for this worker to do.
853 job_queue: The queue of results from the worker.
854 emerge: An EmergeData() object.
855 package_db: A dict, mapping package ids to portage Package objects.
856
857 It expects package identifiers to be passed to it via task_queue. When
858 a task is started, it pushes the (target, filename) to the started_queue.
859 The output is stored in filename. When a merge starts or finishes, we push
860 EmergeJobState objects to the job_queue.
861 """
862
863 SetupWorkerSignals()
864 settings, trees, mtimedb = emerge.settings, emerge.trees, emerge.mtimedb
David Jamesdeebd692011-05-09 17:02:52 -0700865
866 # Disable flushing of caches to save on I/O.
867 if 0 <= vercmp(portage.VERSION, "2.1.9.48"):
868 root = emerge.settings["ROOT"]
869 vardb = emerge.trees[root]["vartree"].dbapi
870 vardb._flush_cache_enabled = False
871
David Jamesfcb70ef2011-02-02 16:02:30 -0800872 opts, spinner = emerge.opts, emerge.spinner
873 opts["--nodeps"] = True
David James386ccd12011-05-04 20:17:42 -0700874 # When Portage launches new processes, it goes on a rampage and closes all
875 # open file descriptors. Ask Portage not to do that, as it breaks us.
876 portage.process.get_open_fds = lambda: []
David Jamesfcb70ef2011-02-02 16:02:30 -0800877 while True:
878 # Wait for a new item to show up on the queue. This is a blocking wait,
879 # so if there's nothing to do, we just sit here.
880 target = task_queue.get()
881 if not target:
882 # If target is None, this means that the main thread wants us to quit.
883 # The other workers need to exit too, so we'll push the message back on
884 # to the queue so they'll get it too.
885 task_queue.put(target)
886 return
David Jamescbf31972011-05-17 11:46:30 -0700887 if KILLED.is_set():
888 return
889
David Jamesfcb70ef2011-02-02 16:02:30 -0800890 db_pkg = package_db[target]
891 db_pkg.root_config = emerge.root_config
892 install_list = [db_pkg]
893 pkgname = db_pkg.pf
894 output = tempfile.NamedTemporaryFile(prefix=pkgname + "-", delete=False)
895 start_timestamp = time.time()
896 job = EmergeJobState(target, pkgname, False, output.name, start_timestamp)
897 job_queue.put(job)
898 if "--pretend" in opts:
899 retcode = 0
900 else:
901 save_stdout = sys.stdout
902 save_stderr = sys.stderr
903 try:
904 sys.stdout = output
905 sys.stderr = output
David James386ccd12011-05-04 20:17:42 -0700906 emerge.scheduler_graph.mergelist = install_list
907 scheduler = Scheduler(settings, trees, mtimedb, opts, spinner,
908 favorites=[], graph_config=emerge.scheduler_graph)
David Jamesfcb70ef2011-02-02 16:02:30 -0800909 retcode = scheduler.merge()
910 except Exception:
911 traceback.print_exc(file=output)
912 retcode = 1
913 finally:
914 sys.stdout = save_stdout
915 sys.stderr = save_stderr
916 output.close()
917 if retcode is None:
918 retcode = 0
919
David Jamescbf31972011-05-17 11:46:30 -0700920 if KILLED.is_set():
921 return
922
David Jamesfcb70ef2011-02-02 16:02:30 -0800923 job = EmergeJobState(target, pkgname, True, output.name, start_timestamp,
924 retcode)
925 job_queue.put(job)
926
927
928class LinePrinter(object):
929 """Helper object to print a single line."""
930
931 def __init__(self, line):
932 self.line = line
933
934 def Print(self, seek_locations):
935 print self.line
936
937
938class JobPrinter(object):
939 """Helper object to print output of a job."""
940
941 def __init__(self, job, unlink=False):
942 """Print output of job.
943
944 If unlink is True, unlink the job output file when done."""
945 self.current_time = time.time()
946 self.job = job
947 self.unlink = unlink
948
949 def Print(self, seek_locations):
950
951 job = self.job
952
953 # Calculate how long the job has been running.
954 seconds = self.current_time - job.start_timestamp
955
956 # Note that we've printed out the job so far.
957 job.last_output_timestamp = self.current_time
958
959 # Note that we're starting the job
960 info = "job %s (%dm%.1fs)" % (job.pkgname, seconds / 60, seconds % 60)
961 last_output_seek = seek_locations.get(job.filename, 0)
962 if last_output_seek:
963 print "=== Continue output for %s ===" % info
964 else:
965 print "=== Start output for %s ===" % info
966
967 # Print actual output from job
968 f = codecs.open(job.filename, encoding='utf-8', errors='replace')
969 f.seek(last_output_seek)
970 prefix = job.pkgname + ":"
971 for line in f:
972
973 # Save off our position in the file
974 if line and line[-1] == "\n":
975 last_output_seek = f.tell()
976 line = line[:-1]
977
978 # Print our line
979 print prefix, line.encode('utf-8', 'replace')
980 f.close()
981
982 # Save our last spot in the file so that we don't print out the same
983 # location twice.
984 seek_locations[job.filename] = last_output_seek
985
986 # Note end of output section
987 if job.done:
988 print "=== Complete: %s ===" % info
989 else:
990 print "=== Still running: %s ===" % info
991
992 if self.unlink:
993 os.unlink(job.filename)
994
995
996def PrintWorker(queue):
997 """A worker that prints stuff to the screen as requested."""
998
999 def ExitHandler(signum, frame):
David Jamescbf31972011-05-17 11:46:30 -07001000 # Set KILLED flag.
1001 KILLED.set()
1002
David Jamesfcb70ef2011-02-02 16:02:30 -08001003 # Switch to default signal handlers so that we'll die after two signals.
David Jamescbf31972011-05-17 11:46:30 -07001004 signal.signal(signal.SIGINT, KillHandler)
1005 signal.signal(signal.SIGTERM, KillHandler)
David Jamesfcb70ef2011-02-02 16:02:30 -08001006
1007 # Don't exit on the first SIGINT / SIGTERM, because the parent worker will
1008 # handle it and tell us when we need to exit.
1009 signal.signal(signal.SIGINT, ExitHandler)
1010 signal.signal(signal.SIGTERM, ExitHandler)
1011
1012 # seek_locations is a map indicating the position we are at in each file.
1013 # It starts off empty, but is set by the various Print jobs as we go along
1014 # to indicate where we left off in each file.
1015 seek_locations = {}
1016 while True:
1017 try:
1018 job = queue.get()
1019 if job:
1020 job.Print(seek_locations)
1021 else:
1022 break
1023 except IOError as ex:
1024 if ex.errno == errno.EINTR:
1025 # Looks like we received a signal. Keep printing.
1026 continue
1027 raise
1028
David Jamesfcb70ef2011-02-02 16:02:30 -08001029class EmergeQueue(object):
1030 """Class to schedule emerge jobs according to a dependency graph."""
1031
1032 def __init__(self, deps_map, emerge, package_db, show_output):
1033 # Store the dependency graph.
1034 self._deps_map = deps_map
1035 # Initialize the running queue to empty
1036 self._jobs = {}
1037 # List of total package installs represented in deps_map.
1038 install_jobs = [x for x in deps_map if deps_map[x]["action"] == "merge"]
1039 self._total_jobs = len(install_jobs)
1040 self._show_output = show_output
1041
1042 if "--pretend" in emerge.opts:
1043 print "Skipping merge because of --pretend mode."
1044 sys.exit(0)
1045
David Jamescbf31972011-05-17 11:46:30 -07001046 # Set a process group so we can easily terminate all children.
1047 os.setpgrp()
1048
David Jamesfcb70ef2011-02-02 16:02:30 -08001049 # Setup scheduler graph object. This is used by the child processes
1050 # to help schedule jobs.
1051 emerge.scheduler_graph = emerge.depgraph.schedulerGraph()
1052
1053 # Calculate how many jobs we can run in parallel. We don't want to pass
1054 # the --jobs flag over to emerge itself, because that'll tell emerge to
1055 # hide its output, and said output is quite useful for debugging hung
1056 # jobs.
1057 procs = min(self._total_jobs,
1058 emerge.opts.pop("--jobs", multiprocessing.cpu_count()))
1059 self._emerge_queue = multiprocessing.Queue()
1060 self._job_queue = multiprocessing.Queue()
1061 self._print_queue = multiprocessing.Queue()
1062 args = (self._emerge_queue, self._job_queue, emerge, package_db)
1063 self._pool = multiprocessing.Pool(procs, EmergeWorker, args)
1064 self._print_worker = multiprocessing.Process(target=PrintWorker,
1065 args=[self._print_queue])
1066 self._print_worker.start()
1067
1068 # Initialize the failed queue to empty.
1069 self._retry_queue = []
1070 self._failed = set()
1071
1072 # Print an update before we launch the merges.
1073 self._Status()
1074
1075 # Setup an exit handler so that we print nice messages if we are
1076 # terminated.
1077 self._SetupExitHandler()
1078
1079 # Schedule our jobs.
1080 for target, info in deps_map.items():
1081 if not info["needs"]:
1082 self._Schedule(target)
1083
1084 def _SetupExitHandler(self):
1085
1086 def ExitHandler(signum, frame):
David Jamescbf31972011-05-17 11:46:30 -07001087 # Set KILLED flag.
1088 KILLED.set()
David Jamesfcb70ef2011-02-02 16:02:30 -08001089
1090 # Kill our signal handlers so we don't get called recursively
David Jamescbf31972011-05-17 11:46:30 -07001091 signal.signal(signal.SIGINT, KillHandler)
1092 signal.signal(signal.SIGTERM, KillHandler)
David Jamesfcb70ef2011-02-02 16:02:30 -08001093
1094 # Print our current job status
1095 for target, job in self._jobs.iteritems():
1096 if job:
1097 self._print_queue.put(JobPrinter(job, unlink=True))
1098
1099 # Notify the user that we are exiting
1100 self._Print("Exiting on signal %s" % signum)
David Jamescbf31972011-05-17 11:46:30 -07001101 self._print_queue.put(None)
1102 self._print_worker.join()
David Jamesfcb70ef2011-02-02 16:02:30 -08001103
1104 # Kill child threads, then exit.
David Jamescbf31972011-05-17 11:46:30 -07001105 os.killpg(0, signal.SIGKILL)
David Jamesfcb70ef2011-02-02 16:02:30 -08001106 sys.exit(1)
1107
1108 # Print out job status when we are killed
1109 signal.signal(signal.SIGINT, ExitHandler)
1110 signal.signal(signal.SIGTERM, ExitHandler)
1111
1112 def _Schedule(self, target):
1113 # We maintain a tree of all deps, if this doesn't need
1114 # to be installed just free up it's children and continue.
1115 # It is possible to reinstall deps of deps, without reinstalling
1116 # first level deps, like so:
1117 # chromeos (merge) -> eselect (nomerge) -> python (merge)
David James386ccd12011-05-04 20:17:42 -07001118 if target not in self._deps_map:
1119 pass
1120 elif self._deps_map[target]["action"] == "nomerge":
David Jamesfcb70ef2011-02-02 16:02:30 -08001121 self._Finish(target)
David Jamesd20a6d92011-04-26 16:11:59 -07001122 elif target not in self._jobs:
David Jamesfcb70ef2011-02-02 16:02:30 -08001123 # Kick off the build if it's marked to be built.
1124 self._jobs[target] = None
1125 self._emerge_queue.put(target)
1126
1127 def _LoadAvg(self):
1128 loads = open("/proc/loadavg", "r").readline().split()[:3]
1129 return " ".join(loads)
1130
1131 def _Print(self, line):
1132 """Print a single line."""
1133 self._print_queue.put(LinePrinter(line))
1134
1135 def _Status(self):
1136 """Print status."""
1137 current_time = time.time()
1138 no_output = True
1139
1140 # Print interim output every minute if --show-output is used. Otherwise,
1141 # print notifications about running packages every 2 minutes, and print
1142 # full output for jobs that have been running for 60 minutes or more.
1143 if self._show_output:
1144 interval = 60
1145 notify_interval = 0
1146 else:
1147 interval = 60 * 60
1148 notify_interval = 60 * 2
1149 for target, job in self._jobs.iteritems():
1150 if job:
1151 last_timestamp = max(job.start_timestamp, job.last_output_timestamp)
1152 if last_timestamp + interval < current_time:
1153 self._print_queue.put(JobPrinter(job))
1154 job.last_output_timestamp = current_time
1155 no_output = False
1156 elif (notify_interval and
1157 job.last_notify_timestamp + notify_interval < current_time):
1158 job_seconds = current_time - job.start_timestamp
1159 args = (job.pkgname, job_seconds / 60, job_seconds % 60, job.filename)
1160 info = "Still building %s (%dm%.1fs). Logs in %s" % args
1161 job.last_notify_timestamp = current_time
1162 self._Print(info)
1163 no_output = False
1164
1165 # If we haven't printed any messages yet, print a general status message
1166 # here.
1167 if no_output:
1168 seconds = current_time - GLOBAL_START
1169 line = ("Pending %s, Ready %s, Running %s, Retrying %s, Total %s "
1170 "[Time %dm%.1fs Load %s]")
1171 qsize = self._emerge_queue.qsize()
1172 self._Print(line % (len(self._deps_map), qsize, len(self._jobs) - qsize,
1173 len(self._retry_queue), self._total_jobs,
1174 seconds / 60, seconds % 60, self._LoadAvg()))
1175
1176 def _Finish(self, target):
1177 """Mark a target as completed and unblock dependecies."""
1178 for dep in self._deps_map[target]["provides"]:
1179 del self._deps_map[dep]["needs"][target]
1180 if not self._deps_map[dep]["needs"]:
1181 self._Schedule(dep)
1182 self._deps_map.pop(target)
1183
1184 def _Retry(self):
1185 if self._retry_queue:
1186 target = self._retry_queue.pop(0)
1187 self._Schedule(target)
1188 self._Print("Retrying emerge of %s." % target)
1189
1190 def _Exit(self):
1191 # Tell emerge workers to exit. They all exit when 'None' is pushed
1192 # to the queue.
1193 self._emerge_queue.put(None)
1194 self._pool.close()
1195 self._pool.join()
1196
1197 # Now that our workers are finished, we can kill the print queue.
1198 self._print_queue.put(None)
1199 self._print_worker.join()
1200
1201 def Run(self):
1202 """Run through the scheduled ebuilds.
1203
1204 Keep running so long as we have uninstalled packages in the
1205 dependency graph to merge.
1206 """
1207 while self._deps_map:
1208 # Check here that we are actually waiting for something.
1209 if (self._emerge_queue.empty() and
1210 self._job_queue.empty() and
1211 not self._jobs and
1212 self._deps_map):
1213 # If we have failed on a package, retry it now.
1214 if self._retry_queue:
1215 self._Retry()
1216 else:
1217 # Tell child threads to exit.
1218 self._Exit()
1219
1220 # The dependency map is helpful for debugging failures.
1221 PrintDepsMap(self._deps_map)
1222
1223 # Tell the user why we're exiting.
1224 if self._failed:
1225 print "Packages failed: %s" % " ,".join(self._failed)
1226 else:
1227 print "Deadlock! Circular dependencies!"
1228 sys.exit(1)
1229
1230 try:
1231 job = self._job_queue.get(timeout=5)
1232 except Queue.Empty:
1233 # Print an update.
1234 self._Status()
1235 continue
1236
1237 target = job.target
1238
1239 if not job.done:
1240 self._jobs[target] = job
1241 self._Print("Started %s (logged in %s)" % (target, job.filename))
1242 continue
1243
1244 # Print output of job
1245 if self._show_output or job.retcode != 0:
1246 self._print_queue.put(JobPrinter(job, unlink=True))
1247 else:
1248 os.unlink(job.filename)
1249 del self._jobs[target]
1250
1251 seconds = time.time() - job.start_timestamp
1252 details = "%s (in %dm%.1fs)" % (target, seconds / 60, seconds % 60)
1253
1254 # Complain if necessary.
1255 if job.retcode != 0:
1256 # Handle job failure.
1257 if target in self._failed:
1258 # If this job has failed previously, give up.
1259 self._Print("Failed %s. Your build has failed." % details)
1260 else:
1261 # Queue up this build to try again after a long while.
1262 self._retry_queue.append(target)
1263 self._failed.add(target)
1264 self._Print("Failed %s, retrying later." % details)
1265 else:
1266 if target in self._failed and self._retry_queue:
1267 # If we have successfully retried a failed package, and there
1268 # are more failed packages, try the next one. We will only have
1269 # one retrying package actively running at a time.
1270 self._Retry()
1271
1272 self._Print("Completed %s" % details)
1273 # Mark as completed and unblock waiting ebuilds.
1274 self._Finish(target)
1275
1276 # Print an update.
1277 self._Status()
1278
1279 # Tell child threads to exit.
1280 self._Print("Merge complete")
1281 self._Exit()
1282
1283
1284def main():
1285
David James57437532011-05-06 15:51:21 -07001286 parallel_emerge_args = sys.argv[:]
David Jamesfcb70ef2011-02-02 16:02:30 -08001287 deps = DepGraphGenerator()
David James57437532011-05-06 15:51:21 -07001288 deps.Initialize(parallel_emerge_args[1:])
David Jamesfcb70ef2011-02-02 16:02:30 -08001289 emerge = deps.emerge
1290
1291 if emerge.action is not None:
1292 sys.argv = deps.ParseParallelEmergeArgs(sys.argv)
1293 sys.exit(emerge_main())
1294 elif not emerge.cmdline_packages:
1295 Usage()
1296 sys.exit(1)
1297
1298 # Unless we're in pretend mode, there's not much point running without
1299 # root access. We need to be able to install packages.
1300 #
1301 # NOTE: Even if you're running --pretend, it's a good idea to run
1302 # parallel_emerge with root access so that portage can write to the
1303 # dependency cache. This is important for performance.
1304 if "--pretend" not in emerge.opts and portage.secpass < 2:
1305 print "parallel_emerge: superuser access is required."
1306 sys.exit(1)
1307
1308 if "--quiet" not in emerge.opts:
1309 cmdline_packages = " ".join(emerge.cmdline_packages)
David Jamesfcb70ef2011-02-02 16:02:30 -08001310 print "Starting fast-emerge."
1311 print " Building package %s on %s" % (cmdline_packages,
1312 deps.board or "root")
David Jamesfcb70ef2011-02-02 16:02:30 -08001313
David James386ccd12011-05-04 20:17:42 -07001314 deps_tree, deps_info = deps.GenDependencyTree()
David Jamesfcb70ef2011-02-02 16:02:30 -08001315
1316 # You want me to be verbose? I'll give you two trees! Twice as much value.
1317 if "--tree" in emerge.opts and "--verbose" in emerge.opts:
1318 deps.PrintTree(deps_tree)
1319
David James386ccd12011-05-04 20:17:42 -07001320 deps_graph = deps.GenDependencyGraph(deps_tree, deps_info)
David Jamesfcb70ef2011-02-02 16:02:30 -08001321
1322 # OK, time to print out our progress so far.
1323 deps.PrintInstallPlan(deps_graph)
1324 if "--tree" in emerge.opts:
1325 PrintDepsMap(deps_graph)
1326
1327 # Are we upgrading portage? If so, and there are more packages to merge,
1328 # schedule a restart of parallel_emerge to merge the rest. This ensures that
1329 # we pick up all updates to portage settings before merging any more
1330 # packages.
1331 portage_upgrade = False
1332 root = emerge.settings["ROOT"]
1333 final_db = emerge.depgraph._dynamic_config.mydbapi[root]
1334 if root == "/":
1335 for db_pkg in final_db.match_pkgs("sys-apps/portage"):
1336 portage_pkg = deps_graph.get(db_pkg.cpv)
1337 if portage_pkg and len(deps_graph) > 1:
1338 portage_pkg["needs"].clear()
1339 portage_pkg["provides"].clear()
1340 deps_graph = { str(db_pkg.cpv): portage_pkg }
1341 portage_upgrade = True
1342 if "--quiet" not in emerge.opts:
1343 print "Upgrading portage first, then restarting..."
1344
1345 # Run the queued emerges.
1346 scheduler = EmergeQueue(deps_graph, emerge, deps.package_db, deps.show_output)
1347 scheduler.Run()
1348
1349 # Update world.
1350 if ("--oneshot" not in emerge.opts and
1351 "--pretend" not in emerge.opts):
1352 world_set = emerge.root_config.sets["selected"]
1353 new_world_pkgs = []
1354 for pkg in emerge.cmdline_packages:
1355 for db_pkg in final_db.match_pkgs(pkg):
1356 print "Adding %s to world" % db_pkg.cp
1357 new_world_pkgs.append(db_pkg.cp)
1358 if new_world_pkgs:
1359 world_set.update(new_world_pkgs)
1360
1361 # Update environment (library cache, symlinks, etc.)
1362 if deps.board and "--pretend" not in emerge.opts:
1363 portage.env_update()
1364
1365 # If we already upgraded portage, we don't need to do so again. But we do
1366 # need to upgrade the rest of the packages. So we'll go ahead and do that.
1367 if portage_upgrade:
David James57437532011-05-06 15:51:21 -07001368 args = parallel_emerge_args + ["--exclude=sys-apps/portage"]
David Jamesfcb70ef2011-02-02 16:02:30 -08001369 os.execvp(os.path.realpath(sys.argv[0]), args)
1370
1371 print "Done"
1372 sys.exit(0)
1373
1374if __name__ == "__main__":
1375 main()