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