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