blob: fd0be556ecd5f403b5f4ca4b9d008fced964f523 [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
David James8c7e5e32011-06-28 11:26:03 -070044import heapq
David Jamesfcb70ef2011-02-02 16:02:30 -080045import multiprocessing
46import os
47import Queue
David Jamesfcb70ef2011-02-02 16:02:30 -080048import signal
49import sys
50import tempfile
51import time
52import traceback
David Jamesfcb70ef2011-02-02 16:02:30 -080053
54# If PORTAGE_USERNAME isn't specified, scrape it from the $HOME variable. On
55# Chromium OS, the default "portage" user doesn't have the necessary
56# permissions. It'd be easier if we could default to $USERNAME, but $USERNAME
57# is "root" here because we get called through sudo.
58#
59# We need to set this before importing any portage modules, because portage
60# looks up "PORTAGE_USERNAME" at import time.
61#
62# NOTE: .bashrc sets PORTAGE_USERNAME = $USERNAME, so most people won't
63# encounter this case unless they have an old chroot or blow away the
64# environment by running sudo without the -E specifier.
65if "PORTAGE_USERNAME" not in os.environ:
66 homedir = os.environ.get("HOME")
67 if homedir:
68 os.environ["PORTAGE_USERNAME"] = os.path.basename(homedir)
69
70# Portage doesn't expose dependency trees in its public API, so we have to
71# make use of some private APIs here. These modules are found under
72# /usr/lib/portage/pym/.
73#
74# TODO(davidjames): Update Portage to expose public APIs for these features.
75from _emerge.actions import adjust_configs
76from _emerge.actions import load_emerge_config
77from _emerge.create_depgraph_params import create_depgraph_params
David James386ccd12011-05-04 20:17:42 -070078from _emerge.depgraph import backtrack_depgraph
David Jamesfcb70ef2011-02-02 16:02:30 -080079from _emerge.main import emerge_main
80from _emerge.main import parse_opts
81from _emerge.Package import Package
82from _emerge.Scheduler import Scheduler
83from _emerge.SetArg import SetArg
84from _emerge.stdout_spinner import stdout_spinner
David James386ccd12011-05-04 20:17:42 -070085from portage._global_updates import _global_updates
86from portage.versions import vercmp
David Jamesfcb70ef2011-02-02 16:02:30 -080087import portage
88import portage.debug
David Jamesfcb70ef2011-02-02 16:02:30 -080089
David Jamesfcb70ef2011-02-02 16:02:30 -080090def Usage():
91 """Print usage."""
92 print "Usage:"
David James386ccd12011-05-04 20:17:42 -070093 print " ./parallel_emerge [--board=BOARD] [--workon=PKGS]"
David Jamesfcb70ef2011-02-02 16:02:30 -080094 print " [--rebuild] [emerge args] package"
95 print
96 print "Packages specified as workon packages are always built from source."
David Jamesfcb70ef2011-02-02 16:02:30 -080097 print
98 print "The --workon argument is mainly useful when you want to build and"
99 print "install packages that you are working on unconditionally, but do not"
100 print "to have to rev the package to indicate you want to build it from"
101 print "source. The build_packages script will automatically supply the"
102 print "workon argument to emerge, ensuring that packages selected using"
103 print "cros-workon are rebuilt."
104 print
105 print "The --rebuild option rebuilds packages whenever their dependencies"
106 print "are changed. This ensures that your build is correct."
107 sys.exit(1)
108
109
David Jamesfcb70ef2011-02-02 16:02:30 -0800110# Global start time
111GLOBAL_START = time.time()
112
David James7358d032011-05-19 10:40:03 -0700113# Whether process has been killed by a signal.
114KILLED = multiprocessing.Event()
115
David Jamesfcb70ef2011-02-02 16:02:30 -0800116
117class EmergeData(object):
118 """This simple struct holds various emerge variables.
119
120 This struct helps us easily pass emerge variables around as a unit.
121 These variables are used for calculating dependencies and installing
122 packages.
123 """
124
David Jamesbf1e3442011-05-28 07:44:20 -0700125 __slots__ = ["action", "cmdline_packages", "depgraph", "favorites",
126 "mtimedb", "opts", "root_config", "scheduler_graph",
127 "settings", "spinner", "trees"]
David Jamesfcb70ef2011-02-02 16:02:30 -0800128
129 def __init__(self):
130 # The action the user requested. If the user is installing packages, this
131 # is None. If the user is doing anything other than installing packages,
132 # this will contain the action name, which will map exactly to the
133 # long-form name of the associated emerge option.
134 #
135 # Example: If you call parallel_emerge --unmerge package, the action name
136 # will be "unmerge"
137 self.action = None
138
139 # The list of packages the user passed on the command-line.
140 self.cmdline_packages = None
141
142 # The emerge dependency graph. It'll contain all the packages involved in
143 # this merge, along with their versions.
144 self.depgraph = None
145
David Jamesbf1e3442011-05-28 07:44:20 -0700146 # The list of candidates to add to the world file.
147 self.favorites = None
148
David Jamesfcb70ef2011-02-02 16:02:30 -0800149 # A dict of the options passed to emerge. This dict has been cleaned up
150 # a bit by parse_opts, so that it's a bit easier for the emerge code to
151 # look at the options.
152 #
153 # Emerge takes a few shortcuts in its cleanup process to make parsing of
154 # the options dict easier. For example, if you pass in "--usepkg=n", the
155 # "--usepkg" flag is just left out of the dictionary altogether. Because
156 # --usepkg=n is the default, this makes parsing easier, because emerge
157 # can just assume that if "--usepkg" is in the dictionary, it's enabled.
158 #
159 # These cleanup processes aren't applied to all options. For example, the
160 # --with-bdeps flag is passed in as-is. For a full list of the cleanups
161 # applied by emerge, see the parse_opts function in the _emerge.main
162 # package.
163 self.opts = None
164
165 # A dictionary used by portage to maintain global state. This state is
166 # loaded from disk when portage starts up, and saved to disk whenever we
167 # call mtimedb.commit().
168 #
169 # This database contains information about global updates (i.e., what
170 # version of portage we have) and what we're currently doing. Portage
171 # saves what it is currently doing in this database so that it can be
172 # resumed when you call it with the --resume option.
173 #
174 # parallel_emerge does not save what it is currently doing in the mtimedb,
175 # so we do not support the --resume option.
176 self.mtimedb = None
177
178 # The portage configuration for our current root. This contains the portage
179 # settings (see below) and the three portage trees for our current root.
180 # (The three portage trees are explained below, in the documentation for
181 # the "trees" member.)
182 self.root_config = None
183
184 # The scheduler graph is used by emerge to calculate what packages to
185 # install. We don't actually install any deps, so this isn't really used,
186 # but we pass it in to the Scheduler object anyway.
187 self.scheduler_graph = None
188
189 # Portage settings for our current session. Most of these settings are set
190 # in make.conf inside our current install root.
191 self.settings = None
192
193 # The spinner, which spews stuff to stdout to indicate that portage is
194 # doing something. We maintain our own spinner, so we set the portage
195 # spinner to "silent" mode.
196 self.spinner = None
197
198 # The portage trees. There are separate portage trees for each root. To get
199 # the portage tree for the current root, you can look in self.trees[root],
200 # where root = self.settings["ROOT"].
201 #
202 # In each root, there are three trees: vartree, porttree, and bintree.
203 # - vartree: A database of the currently-installed packages.
204 # - porttree: A database of ebuilds, that can be used to build packages.
205 # - bintree: A database of binary packages.
206 self.trees = None
207
208
209class DepGraphGenerator(object):
210 """Grab dependency information about packages from portage.
211
212 Typical usage:
213 deps = DepGraphGenerator()
214 deps.Initialize(sys.argv[1:])
215 deps_tree, deps_info = deps.GenDependencyTree()
216 deps_graph = deps.GenDependencyGraph(deps_tree, deps_info)
217 deps.PrintTree(deps_tree)
218 PrintDepsMap(deps_graph)
219 """
220
David James386ccd12011-05-04 20:17:42 -0700221 __slots__ = ["board", "emerge", "package_db", "show_output"]
David Jamesfcb70ef2011-02-02 16:02:30 -0800222
223 def __init__(self):
224 self.board = None
225 self.emerge = EmergeData()
David Jamesfcb70ef2011-02-02 16:02:30 -0800226 self.package_db = {}
David Jamesfcb70ef2011-02-02 16:02:30 -0800227 self.show_output = False
David Jamesfcb70ef2011-02-02 16:02:30 -0800228
229 def ParseParallelEmergeArgs(self, argv):
230 """Read the parallel emerge arguments from the command-line.
231
232 We need to be compatible with emerge arg format. We scrape arguments that
233 are specific to parallel_emerge, and pass through the rest directly to
234 emerge.
235 Args:
236 argv: arguments list
237 Returns:
238 Arguments that don't belong to parallel_emerge
239 """
240 emerge_args = []
241 for arg in argv:
242 # Specifically match arguments that are specific to parallel_emerge, and
243 # pass through the rest.
244 if arg.startswith("--board="):
245 self.board = arg.replace("--board=", "")
246 elif arg.startswith("--workon="):
247 workon_str = arg.replace("--workon=", "")
David James7a1ea4b2011-10-13 15:06:41 -0700248 emerge_args.append("--reinstall-atoms=%s" % workon_str)
249 emerge_args.append("--usepkg-exclude=%s" % workon_str)
David Jamesfcb70ef2011-02-02 16:02:30 -0800250 elif arg.startswith("--force-remote-binary="):
251 force_remote_binary = arg.replace("--force-remote-binary=", "")
David James7a1ea4b2011-10-13 15:06:41 -0700252 emerge_args.append("--useoldpkg-atoms=%s" % force_remote_binary)
David Jamesfcb70ef2011-02-02 16:02:30 -0800253 elif arg == "--show-output":
254 self.show_output = True
David James386ccd12011-05-04 20:17:42 -0700255 elif arg == "--rebuild":
David James7a1ea4b2011-10-13 15:06:41 -0700256 emerge_args.append("--rebuild-if-unbuilt")
David Jamesfcb70ef2011-02-02 16:02:30 -0800257 else:
258 # Not one of our options, so pass through to emerge.
259 emerge_args.append(arg)
260
David James386ccd12011-05-04 20:17:42 -0700261 # These packages take a really long time to build, so, for expediency, we
262 # are blacklisting them from automatic rebuilds because one of their
263 # dependencies needs to be recompiled.
264 for pkg in ("chromeos-base/chromeos-chrome", "media-plugins/o3d",
265 "dev-java/icedtea"):
David James7a1ea4b2011-10-13 15:06:41 -0700266 emerge_args.append("--rebuild-exclude=%s" % pkg)
David Jamesfcb70ef2011-02-02 16:02:30 -0800267
268 return emerge_args
269
270 def Initialize(self, args):
271 """Initializer. Parses arguments and sets up portage state."""
272
273 # Parse and strip out args that are just intended for parallel_emerge.
274 emerge_args = self.ParseParallelEmergeArgs(args)
275
276 # Setup various environment variables based on our current board. These
277 # variables are normally setup inside emerge-${BOARD}, but since we don't
278 # call that script, we have to set it up here. These variables serve to
279 # point our tools at /build/BOARD and to setup cross compiles to the
280 # appropriate board as configured in toolchain.conf.
281 if self.board:
282 os.environ["PORTAGE_CONFIGROOT"] = "/build/" + self.board
283 os.environ["PORTAGE_SYSROOT"] = "/build/" + self.board
284 os.environ["SYSROOT"] = "/build/" + self.board
David Jamesfcb70ef2011-02-02 16:02:30 -0800285
286 # Although CHROMEOS_ROOT isn't specific to boards, it's normally setup
287 # inside emerge-${BOARD}, so we set it up here for compatibility. It
288 # will be going away soon as we migrate to CROS_WORKON_SRCROOT.
289 os.environ.setdefault("CHROMEOS_ROOT", os.environ["HOME"] + "/trunk")
290
291 # Turn off interactive delays
292 os.environ["EBEEP_IGNORE"] = "1"
293 os.environ["EPAUSE_IGNORE"] = "1"
294 os.environ["UNMERGE_DELAY"] = "0"
295
296 # Parse the emerge options.
David Jamesea3ca332011-05-26 11:48:29 -0700297 action, opts, cmdline_packages = parse_opts(emerge_args, silent=True)
David Jamesfcb70ef2011-02-02 16:02:30 -0800298
299 # Set environment variables based on options. Portage normally sets these
300 # environment variables in emerge_main, but we can't use that function,
301 # because it also does a bunch of other stuff that we don't want.
302 # TODO(davidjames): Patch portage to move this logic into a function we can
303 # reuse here.
304 if "--debug" in opts:
305 os.environ["PORTAGE_DEBUG"] = "1"
306 if "--config-root" in opts:
307 os.environ["PORTAGE_CONFIGROOT"] = opts["--config-root"]
308 if "--root" in opts:
309 os.environ["ROOT"] = opts["--root"]
310 if "--accept-properties" in opts:
311 os.environ["ACCEPT_PROPERTIES"] = opts["--accept-properties"]
312
313 # Portage has two flags for doing collision protection: collision-protect
314 # and protect-owned. The protect-owned feature is enabled by default and
315 # is quite useful: it checks to make sure that we don't have multiple
316 # packages that own the same file. The collision-protect feature is more
317 # strict, and less useful: it fails if it finds a conflicting file, even
318 # if that file was created by an earlier ebuild that failed to install.
319 #
320 # We want to disable collision-protect here because we don't handle
321 # failures during the merge step very well. Sometimes we leave old files
322 # lying around and they cause problems, so for now we disable the flag.
323 # TODO(davidjames): Look for a better solution.
324 features = os.environ.get("FEATURES", "") + " -collision-protect"
325
David Jamesdeebd692011-05-09 17:02:52 -0700326 # Install packages in parallel.
327 features = features + " parallel-install"
328
David Jamesfcb70ef2011-02-02 16:02:30 -0800329 # If we're installing packages to the board, and we're not using the
330 # official flag, we can enable the following optimizations:
331 # 1) Don't lock during install step. This allows multiple packages to be
332 # installed at once. This is safe because our board packages do not
333 # muck with each other during the post-install step.
334 # 2) Don't update the environment until the end of the build. This is
335 # safe because board packages don't need to run during the build --
336 # they're cross-compiled, so our CPU architecture doesn't support them
337 # anyway.
338 if self.board and os.environ.get("CHROMEOS_OFFICIAL") != "1":
339 os.environ.setdefault("PORTAGE_LOCKS", "false")
David Jamesdeebd692011-05-09 17:02:52 -0700340 features = features + " -ebuild-locks no-env-update"
David Jamesfcb70ef2011-02-02 16:02:30 -0800341
342 os.environ["FEATURES"] = features
343
344 # Now that we've setup the necessary environment variables, we can load the
345 # emerge config from disk.
346 settings, trees, mtimedb = load_emerge_config()
347
David Jamesea3ca332011-05-26 11:48:29 -0700348 # Add in EMERGE_DEFAULT_OPTS, if specified.
349 tmpcmdline = []
350 if "--ignore-default-opts" not in opts:
351 tmpcmdline.extend(settings["EMERGE_DEFAULT_OPTS"].split())
352 tmpcmdline.extend(emerge_args)
353 action, opts, cmdline_packages = parse_opts(tmpcmdline)
354
355 # If we're installing to the board, we want the --root-deps option so that
356 # portage will install the build dependencies to that location as well.
357 if self.board:
358 opts.setdefault("--root-deps", True)
359
David Jamesfcb70ef2011-02-02 16:02:30 -0800360 # Check whether our portage tree is out of date. Typically, this happens
361 # when you're setting up a new portage tree, such as in setup_board and
362 # make_chroot. In that case, portage applies a bunch of global updates
363 # here. Once the updates are finished, we need to commit any changes
364 # that the global update made to our mtimedb, and reload the config.
365 #
366 # Portage normally handles this logic in emerge_main, but again, we can't
367 # use that function here.
368 if _global_updates(trees, mtimedb["updates"]):
369 mtimedb.commit()
370 settings, trees, mtimedb = load_emerge_config(trees=trees)
371
372 # Setup implied options. Portage normally handles this logic in
373 # emerge_main.
374 if "--buildpkgonly" in opts or "buildpkg" in settings.features:
375 opts.setdefault("--buildpkg", True)
376 if "--getbinpkgonly" in opts:
377 opts.setdefault("--usepkgonly", True)
378 opts.setdefault("--getbinpkg", True)
379 if "getbinpkg" in settings.features:
380 # Per emerge_main, FEATURES=getbinpkg overrides --getbinpkg=n
381 opts["--getbinpkg"] = True
382 if "--getbinpkg" in opts or "--usepkgonly" in opts:
383 opts.setdefault("--usepkg", True)
384 if "--fetch-all-uri" in opts:
385 opts.setdefault("--fetchonly", True)
386 if "--skipfirst" in opts:
387 opts.setdefault("--resume", True)
388 if "--buildpkgonly" in opts:
389 # --buildpkgonly will not merge anything, so it overrides all binary
390 # package options.
391 for opt in ("--getbinpkg", "--getbinpkgonly",
392 "--usepkg", "--usepkgonly"):
393 opts.pop(opt, None)
394 if (settings.get("PORTAGE_DEBUG", "") == "1" and
395 "python-trace" in settings.features):
396 portage.debug.set_trace(True)
397
398 # Complain about unsupported options
David James386ccd12011-05-04 20:17:42 -0700399 for opt in ("--ask", "--ask-enter-invalid", "--resume", "--skipfirst"):
David Jamesfcb70ef2011-02-02 16:02:30 -0800400 if opt in opts:
401 print "%s is not supported by parallel_emerge" % opt
402 sys.exit(1)
403
404 # Make emerge specific adjustments to the config (e.g. colors!)
405 adjust_configs(opts, trees)
406
407 # Save our configuration so far in the emerge object
408 emerge = self.emerge
409 emerge.action, emerge.opts = action, opts
410 emerge.settings, emerge.trees, emerge.mtimedb = settings, trees, mtimedb
411 emerge.cmdline_packages = cmdline_packages
412 root = settings["ROOT"]
413 emerge.root_config = trees[root]["root_config"]
414
David James386ccd12011-05-04 20:17:42 -0700415 if "--usepkg" in opts:
David Jamesfcb70ef2011-02-02 16:02:30 -0800416 emerge.trees[root]["bintree"].populate("--getbinpkg" in opts)
417
David Jamesfcb70ef2011-02-02 16:02:30 -0800418 def CreateDepgraph(self, emerge, packages):
419 """Create an emerge depgraph object."""
420 # Setup emerge options.
421 emerge_opts = emerge.opts.copy()
422
David James386ccd12011-05-04 20:17:42 -0700423 # Ask portage to build a dependency graph. with the options we specified
424 # above.
David Jamesfcb70ef2011-02-02 16:02:30 -0800425 params = create_depgraph_params(emerge_opts, emerge.action)
David Jamesbf1e3442011-05-28 07:44:20 -0700426 success, depgraph, favorites = backtrack_depgraph(
David James386ccd12011-05-04 20:17:42 -0700427 emerge.settings, emerge.trees, emerge_opts, params, emerge.action,
428 packages, emerge.spinner)
429 emerge.depgraph = depgraph
David Jamesfcb70ef2011-02-02 16:02:30 -0800430
David James386ccd12011-05-04 20:17:42 -0700431 # Is it impossible to honor the user's request? Bail!
432 if not success:
433 depgraph.display_problems()
434 sys.exit(1)
David Jamesfcb70ef2011-02-02 16:02:30 -0800435
436 emerge.depgraph = depgraph
David Jamesbf1e3442011-05-28 07:44:20 -0700437 emerge.favorites = favorites
David Jamesfcb70ef2011-02-02 16:02:30 -0800438
David Jamesdeebd692011-05-09 17:02:52 -0700439 # Prime and flush emerge caches.
440 root = emerge.settings["ROOT"]
441 vardb = emerge.trees[root]["vartree"].dbapi
David James0bdc5de2011-05-12 16:22:26 -0700442 if "--pretend" not in emerge.opts:
443 vardb.counter_tick()
David Jamesdeebd692011-05-09 17:02:52 -0700444 vardb.flush_cache()
445
David James386ccd12011-05-04 20:17:42 -0700446 def GenDependencyTree(self):
David Jamesfcb70ef2011-02-02 16:02:30 -0800447 """Get dependency tree info from emerge.
448
David Jamesfcb70ef2011-02-02 16:02:30 -0800449 Returns:
450 Dependency tree
451 """
452 start = time.time()
453
454 emerge = self.emerge
455
456 # Create a list of packages to merge
457 packages = set(emerge.cmdline_packages[:])
David Jamesfcb70ef2011-02-02 16:02:30 -0800458
459 # Tell emerge to be quiet. We print plenty of info ourselves so we don't
460 # need any extra output from portage.
461 portage.util.noiselimit = -1
462
463 # My favorite feature: The silent spinner. It doesn't spin. Ever.
464 # I'd disable the colors by default too, but they look kind of cool.
465 emerge.spinner = stdout_spinner()
466 emerge.spinner.update = emerge.spinner.update_quiet
467
468 if "--quiet" not in emerge.opts:
469 print "Calculating deps..."
470
471 self.CreateDepgraph(emerge, packages)
472 depgraph = emerge.depgraph
473
474 # Build our own tree from the emerge digraph.
475 deps_tree = {}
476 digraph = depgraph._dynamic_config.digraph
David James3f778802011-08-25 19:31:45 -0700477 root = emerge.settings["ROOT"]
478 final_db = depgraph._dynamic_config.mydbapi[root]
David Jamesfcb70ef2011-02-02 16:02:30 -0800479 for node, node_deps in digraph.nodes.items():
480 # Calculate dependency packages that need to be installed first. Each
481 # child on the digraph is a dependency. The "operation" field specifies
482 # what we're doing (e.g. merge, uninstall, etc.). The "priorities" array
483 # contains the type of dependency (e.g. build, runtime, runtime_post,
484 # etc.)
485 #
David Jamesfcb70ef2011-02-02 16:02:30 -0800486 # Portage refers to the identifiers for packages as a CPV. This acronym
487 # stands for Component/Path/Version.
488 #
489 # Here's an example CPV: chromeos-base/power_manager-0.0.1-r1
490 # Split up, this CPV would be:
491 # C -- Component: chromeos-base
492 # P -- Path: power_manager
493 # V -- Version: 0.0.1-r1
494 #
495 # We just refer to CPVs as packages here because it's easier.
496 deps = {}
497 for child, priorities in node_deps[0].items():
David James3f778802011-08-25 19:31:45 -0700498 if isinstance(child, Package) and child.root == root:
499 cpv = str(child.cpv)
500 action = str(child.operation)
501
502 # If we're uninstalling a package, check whether Portage is
503 # installing a replacement. If so, just depend on the installation
504 # of the new package, because the old package will automatically
505 # be uninstalled at that time.
506 if action == "uninstall":
507 for pkg in final_db.match_pkgs(child.slot_atom):
508 cpv = str(pkg.cpv)
509 action = "merge"
510 break
511
512 deps[cpv] = dict(action=action,
513 deptypes=[str(x) for x in priorities],
514 deps={})
David Jamesfcb70ef2011-02-02 16:02:30 -0800515
516 # We've built our list of deps, so we can add our package to the tree.
David James3f778802011-08-25 19:31:45 -0700517 if isinstance(node, Package) and node.root == root:
David Jamesfcb70ef2011-02-02 16:02:30 -0800518 deps_tree[str(node.cpv)] = dict(action=str(node.operation),
519 deps=deps)
520
David Jamesfcb70ef2011-02-02 16:02:30 -0800521 # Ask portage for its install plan, so that we can only throw out
David James386ccd12011-05-04 20:17:42 -0700522 # dependencies that portage throws out.
David Jamesfcb70ef2011-02-02 16:02:30 -0800523 deps_info = {}
524 for pkg in depgraph.altlist():
525 if isinstance(pkg, Package):
David James3f778802011-08-25 19:31:45 -0700526 assert pkg.root == root
David Jamesfcb70ef2011-02-02 16:02:30 -0800527 self.package_db[pkg.cpv] = pkg
528
David Jamesfcb70ef2011-02-02 16:02:30 -0800529 # Save off info about the package
David James386ccd12011-05-04 20:17:42 -0700530 deps_info[str(pkg.cpv)] = {"idx": len(deps_info)}
David Jamesfcb70ef2011-02-02 16:02:30 -0800531
532 seconds = time.time() - start
533 if "--quiet" not in emerge.opts:
534 print "Deps calculated in %dm%.1fs" % (seconds / 60, seconds % 60)
535
536 return deps_tree, deps_info
537
538 def PrintTree(self, deps, depth=""):
539 """Print the deps we have seen in the emerge output.
540
541 Args:
542 deps: Dependency tree structure.
543 depth: Allows printing the tree recursively, with indentation.
544 """
545 for entry in sorted(deps):
546 action = deps[entry]["action"]
547 print "%s %s (%s)" % (depth, entry, action)
548 self.PrintTree(deps[entry]["deps"], depth=depth + " ")
549
David James386ccd12011-05-04 20:17:42 -0700550 def GenDependencyGraph(self, deps_tree, deps_info):
David Jamesfcb70ef2011-02-02 16:02:30 -0800551 """Generate a doubly linked dependency graph.
552
553 Args:
554 deps_tree: Dependency tree structure.
555 deps_info: More details on the dependencies.
556 Returns:
557 Deps graph in the form of a dict of packages, with each package
558 specifying a "needs" list and "provides" list.
559 """
560 emerge = self.emerge
561 root = emerge.settings["ROOT"]
562
David Jamesfcb70ef2011-02-02 16:02:30 -0800563 # deps_map is the actual dependency graph.
564 #
565 # Each package specifies a "needs" list and a "provides" list. The "needs"
566 # list indicates which packages we depend on. The "provides" list
567 # indicates the reverse dependencies -- what packages need us.
568 #
569 # We also provide some other information in the dependency graph:
570 # - action: What we're planning on doing with this package. Generally,
571 # "merge", "nomerge", or "uninstall"
David Jamesfcb70ef2011-02-02 16:02:30 -0800572 deps_map = {}
573
574 def ReverseTree(packages):
575 """Convert tree to digraph.
576
577 Take the tree of package -> requirements and reverse it to a digraph of
578 buildable packages -> packages they unblock.
579 Args:
580 packages: Tree(s) of dependencies.
581 Returns:
582 Unsanitized digraph.
583 """
David James8c7e5e32011-06-28 11:26:03 -0700584 binpkg_phases = set(["setup", "preinst", "postinst"])
David James3f778802011-08-25 19:31:45 -0700585 needed_dep_types = set(["blocker", "buildtime", "runtime"])
David Jamesfcb70ef2011-02-02 16:02:30 -0800586 for pkg in packages:
587
588 # Create an entry for the package
589 action = packages[pkg]["action"]
David James8c7e5e32011-06-28 11:26:03 -0700590 default_pkg = {"needs": {}, "provides": set(), "action": action,
591 "nodeps": False, "binary": False}
David Jamesfcb70ef2011-02-02 16:02:30 -0800592 this_pkg = deps_map.setdefault(pkg, default_pkg)
593
David James8c7e5e32011-06-28 11:26:03 -0700594 if pkg in deps_info:
595 this_pkg["idx"] = deps_info[pkg]["idx"]
596
597 # If a package doesn't have any defined phases that might use the
598 # dependent packages (i.e. pkg_setup, pkg_preinst, or pkg_postinst),
599 # we can install this package before its deps are ready.
600 emerge_pkg = self.package_db.get(pkg)
601 if emerge_pkg and emerge_pkg.type_name == "binary":
602 this_pkg["binary"] = True
603 defined_phases = emerge_pkg.metadata.defined_phases
604 defined_binpkg_phases = binpkg_phases.intersection(defined_phases)
605 if not defined_binpkg_phases:
606 this_pkg["nodeps"] = True
607
David Jamesfcb70ef2011-02-02 16:02:30 -0800608 # Create entries for dependencies of this package first.
609 ReverseTree(packages[pkg]["deps"])
610
611 # Add dependencies to this package.
612 for dep, dep_item in packages[pkg]["deps"].iteritems():
David James8c7e5e32011-06-28 11:26:03 -0700613 # We only need to enforce strict ordering of dependencies if the
David James3f778802011-08-25 19:31:45 -0700614 # dependency is a blocker, or is a buildtime or runtime dependency.
615 # (I.e., ignored, optional, and runtime_post dependencies don't
616 # depend on ordering.)
David James8c7e5e32011-06-28 11:26:03 -0700617 dep_types = dep_item["deptypes"]
618 if needed_dep_types.intersection(dep_types):
619 deps_map[dep]["provides"].add(pkg)
620 this_pkg["needs"][dep] = "/".join(dep_types)
David Jamesfcb70ef2011-02-02 16:02:30 -0800621
David James3f778802011-08-25 19:31:45 -0700622 # If there's a blocker, Portage may need to move files from one
623 # package to another, which requires editing the CONTENTS files of
624 # both packages. To avoid race conditions while editing this file,
625 # the two packages must not be installed in parallel, so we can't
626 # safely ignore dependencies. See http://crosbug.com/19328
627 if "blocker" in dep_types:
628 this_pkg["nodeps"] = False
629
David Jamesfcb70ef2011-02-02 16:02:30 -0800630 def FindCycles():
631 """Find cycles in the dependency tree.
632
633 Returns:
634 A dict mapping cyclic packages to a dict of the deps that cause
635 cycles. For each dep that causes cycles, it returns an example
636 traversal of the graph that shows the cycle.
637 """
638
639 def FindCyclesAtNode(pkg, cycles, unresolved, resolved):
640 """Find cycles in cyclic dependencies starting at specified package.
641
642 Args:
643 pkg: Package identifier.
644 cycles: A dict mapping cyclic packages to a dict of the deps that
645 cause cycles. For each dep that causes cycles, it returns an
646 example traversal of the graph that shows the cycle.
647 unresolved: Nodes that have been visited but are not fully processed.
648 resolved: Nodes that have been visited and are fully processed.
649 """
650 pkg_cycles = cycles.get(pkg)
651 if pkg in resolved and not pkg_cycles:
652 # If we already looked at this package, and found no cyclic
653 # dependencies, we can stop now.
654 return
655 unresolved.append(pkg)
656 for dep in deps_map[pkg]["needs"]:
657 if dep in unresolved:
658 idx = unresolved.index(dep)
659 mycycle = unresolved[idx:] + [dep]
660 for i in range(len(mycycle) - 1):
661 pkg1, pkg2 = mycycle[i], mycycle[i+1]
662 cycles.setdefault(pkg1, {}).setdefault(pkg2, mycycle)
663 elif not pkg_cycles or dep not in pkg_cycles:
664 # Looks like we haven't seen this edge before.
665 FindCyclesAtNode(dep, cycles, unresolved, resolved)
666 unresolved.pop()
667 resolved.add(pkg)
668
669 cycles, unresolved, resolved = {}, [], set()
670 for pkg in deps_map:
671 FindCyclesAtNode(pkg, cycles, unresolved, resolved)
672 return cycles
673
David James386ccd12011-05-04 20:17:42 -0700674 def RemoveUnusedPackages():
David Jamesfcb70ef2011-02-02 16:02:30 -0800675 """Remove installed packages, propagating dependencies."""
David Jamesfcb70ef2011-02-02 16:02:30 -0800676 # Schedule packages that aren't on the install list for removal
677 rm_pkgs = set(deps_map.keys()) - set(deps_info.keys())
678
David Jamesfcb70ef2011-02-02 16:02:30 -0800679 # Remove the packages we don't want, simplifying the graph and making
680 # it easier for us to crack cycles.
681 for pkg in sorted(rm_pkgs):
682 this_pkg = deps_map[pkg]
683 needs = this_pkg["needs"]
684 provides = this_pkg["provides"]
685 for dep in needs:
686 dep_provides = deps_map[dep]["provides"]
687 dep_provides.update(provides)
688 dep_provides.discard(pkg)
689 dep_provides.discard(dep)
690 for target in provides:
691 target_needs = deps_map[target]["needs"]
692 target_needs.update(needs)
693 target_needs.pop(pkg, None)
694 target_needs.pop(target, None)
695 del deps_map[pkg]
696
697 def PrintCycleBreak(basedep, dep, mycycle):
698 """Print details about a cycle that we are planning on breaking.
699
700 We are breaking a cycle where dep needs basedep. mycycle is an
701 example cycle which contains dep -> basedep."""
702
David Jamesfcb70ef2011-02-02 16:02:30 -0800703 needs = deps_map[dep]["needs"]
704 depinfo = needs.get(basedep, "deleted")
David Jamesfcb70ef2011-02-02 16:02:30 -0800705
David James3f778802011-08-25 19:31:45 -0700706 # It's OK to swap install order for blockers, as long as the two
707 # packages aren't installed in parallel. If there is a cycle, then
708 # we know the packages depend on each other already, so we can drop the
709 # blocker safely without printing a warning.
710 if depinfo == "blocker":
711 return
712
David Jamesfcb70ef2011-02-02 16:02:30 -0800713 # Notify the user that we're breaking a cycle.
714 print "Breaking %s -> %s (%s)" % (dep, basedep, depinfo)
715
716 # Show cycle.
717 for i in range(len(mycycle) - 1):
718 pkg1, pkg2 = mycycle[i], mycycle[i+1]
719 needs = deps_map[pkg1]["needs"]
720 depinfo = needs.get(pkg2, "deleted")
721 if pkg1 == dep and pkg2 == basedep:
722 depinfo = depinfo + ", deleting"
723 print " %s -> %s (%s)" % (pkg1, pkg2, depinfo)
724
725 def SanitizeTree():
726 """Remove circular dependencies.
727
728 We prune all dependencies involved in cycles that go against the emerge
729 ordering. This has a nice property: we're guaranteed to merge
730 dependencies in the same order that portage does.
731
732 Because we don't treat any dependencies as "soft" unless they're killed
733 by a cycle, we pay attention to a larger number of dependencies when
734 merging. This hurts performance a bit, but helps reliability.
735 """
736 start = time.time()
737 cycles = FindCycles()
738 while cycles:
739 for dep, mycycles in cycles.iteritems():
740 for basedep, mycycle in mycycles.iteritems():
741 if deps_info[basedep]["idx"] >= deps_info[dep]["idx"]:
Matt Tennant08797302011-10-17 16:18:45 -0700742 if "--quiet" not in emerge.opts:
743 PrintCycleBreak(basedep, dep, mycycle)
David Jamesfcb70ef2011-02-02 16:02:30 -0800744 del deps_map[dep]["needs"][basedep]
745 deps_map[basedep]["provides"].remove(dep)
746 cycles = FindCycles()
747 seconds = time.time() - start
748 if "--quiet" not in emerge.opts and seconds >= 0.1:
749 print "Tree sanitized in %dm%.1fs" % (seconds / 60, seconds % 60)
750
David James8c7e5e32011-06-28 11:26:03 -0700751 def FindRecursiveProvides(pkg, seen):
752 """Find all nodes that require a particular package.
753
754 Assumes that graph is acyclic.
755
756 Args:
757 pkg: Package identifier.
758 seen: Nodes that have been visited so far.
759 """
760 if pkg in seen:
761 return
762 seen.add(pkg)
763 info = deps_map[pkg]
764 info["tprovides"] = info["provides"].copy()
765 for dep in info["provides"]:
766 FindRecursiveProvides(dep, seen)
767 info["tprovides"].update(deps_map[dep]["tprovides"])
768
David Jamesa22906f2011-05-04 19:53:26 -0700769 ReverseTree(deps_tree)
David Jamesa22906f2011-05-04 19:53:26 -0700770
David James386ccd12011-05-04 20:17:42 -0700771 # We need to remove unused packages so that we can use the dependency
772 # ordering of the install process to show us what cycles to crack.
773 RemoveUnusedPackages()
David Jamesfcb70ef2011-02-02 16:02:30 -0800774 SanitizeTree()
David James8c7e5e32011-06-28 11:26:03 -0700775 seen = set()
776 for pkg in deps_map:
777 FindRecursiveProvides(pkg, seen)
David Jamesfcb70ef2011-02-02 16:02:30 -0800778 return deps_map
779
780 def PrintInstallPlan(self, deps_map):
781 """Print an emerge-style install plan.
782
783 The install plan lists what packages we're installing, in order.
784 It's useful for understanding what parallel_emerge is doing.
785
786 Args:
787 deps_map: The dependency graph.
788 """
789
790 def InstallPlanAtNode(target, deps_map):
791 nodes = []
792 nodes.append(target)
793 for dep in deps_map[target]["provides"]:
794 del deps_map[dep]["needs"][target]
795 if not deps_map[dep]["needs"]:
796 nodes.extend(InstallPlanAtNode(dep, deps_map))
797 return nodes
798
799 deps_map = copy.deepcopy(deps_map)
800 install_plan = []
801 plan = set()
802 for target, info in deps_map.iteritems():
803 if not info["needs"] and target not in plan:
804 for item in InstallPlanAtNode(target, deps_map):
805 plan.add(item)
806 install_plan.append(self.package_db[item])
807
808 for pkg in plan:
809 del deps_map[pkg]
810
811 if deps_map:
812 print "Cyclic dependencies:", " ".join(deps_map)
813 PrintDepsMap(deps_map)
814 sys.exit(1)
815
816 self.emerge.depgraph.display(install_plan)
817
818
819def PrintDepsMap(deps_map):
820 """Print dependency graph, for each package list it's prerequisites."""
821 for i in sorted(deps_map):
822 print "%s: (%s) needs" % (i, deps_map[i]["action"])
823 needs = deps_map[i]["needs"]
824 for j in sorted(needs):
825 print " %s" % (j)
826 if not needs:
827 print " no dependencies"
828
829
830class EmergeJobState(object):
831 __slots__ = ["done", "filename", "last_notify_timestamp", "last_output_seek",
832 "last_output_timestamp", "pkgname", "retcode", "start_timestamp",
Brian Harring0be85c62012-03-17 19:52:12 -0700833 "target", "fetch_only"]
David Jamesfcb70ef2011-02-02 16:02:30 -0800834
835 def __init__(self, target, pkgname, done, filename, start_timestamp,
Brian Harring0be85c62012-03-17 19:52:12 -0700836 retcode=None, fetch_only=False):
David Jamesfcb70ef2011-02-02 16:02:30 -0800837
838 # The full name of the target we're building (e.g.
839 # chromeos-base/chromeos-0.0.1-r60)
840 self.target = target
841
842 # The short name of the target we're building (e.g. chromeos-0.0.1-r60)
843 self.pkgname = pkgname
844
845 # Whether the job is done. (True if the job is done; false otherwise.)
846 self.done = done
847
848 # The filename where output is currently stored.
849 self.filename = filename
850
851 # The timestamp of the last time we printed the name of the log file. We
852 # print this at the beginning of the job, so this starts at
853 # start_timestamp.
854 self.last_notify_timestamp = start_timestamp
855
856 # The location (in bytes) of the end of the last complete line we printed.
857 # This starts off at zero. We use this to jump to the right place when we
858 # print output from the same ebuild multiple times.
859 self.last_output_seek = 0
860
861 # The timestamp of the last time we printed output. Since we haven't
862 # printed output yet, this starts at zero.
863 self.last_output_timestamp = 0
864
865 # The return code of our job, if the job is actually finished.
866 self.retcode = retcode
867
Brian Harring0be85c62012-03-17 19:52:12 -0700868 # Was this just a fetch job?
869 self.fetch_only = fetch_only
870
David Jamesfcb70ef2011-02-02 16:02:30 -0800871 # The timestamp when our job started.
872 self.start_timestamp = start_timestamp
873
874
David James7358d032011-05-19 10:40:03 -0700875def KillHandler(signum, frame):
876 # Kill self and all subprocesses.
877 os.killpg(0, signal.SIGKILL)
878
David Jamesfcb70ef2011-02-02 16:02:30 -0800879def SetupWorkerSignals():
880 def ExitHandler(signum, frame):
David James7358d032011-05-19 10:40:03 -0700881 # Set KILLED flag.
882 KILLED.set()
David James13cead42011-05-18 16:22:01 -0700883
David James7358d032011-05-19 10:40:03 -0700884 # Remove our signal handlers so we don't get called recursively.
885 signal.signal(signal.SIGINT, KillHandler)
886 signal.signal(signal.SIGTERM, KillHandler)
David Jamesfcb70ef2011-02-02 16:02:30 -0800887
888 # Ensure that we exit quietly and cleanly, if possible, when we receive
889 # SIGTERM or SIGINT signals. By default, when the user hits CTRL-C, all
890 # of the child processes will print details about KeyboardInterrupt
891 # exceptions, which isn't very helpful.
892 signal.signal(signal.SIGINT, ExitHandler)
893 signal.signal(signal.SIGTERM, ExitHandler)
894
David James1ed3e252011-10-05 20:26:15 -0700895def EmergeProcess(scheduler, output):
896 """Merge a package in a subprocess.
897
898 Args:
899 scheduler: Scheduler object.
900 output: Temporary file to write output.
901
902 Returns:
903 The exit code returned by the subprocess.
904 """
905 pid = os.fork()
906 if pid == 0:
907 try:
908 # Sanity checks.
909 if sys.stdout.fileno() != 1: raise Exception("sys.stdout.fileno() != 1")
910 if sys.stderr.fileno() != 2: raise Exception("sys.stderr.fileno() != 2")
911
912 # - Redirect 1 (stdout) and 2 (stderr) at our temporary file.
913 # - Redirect 0 to point at sys.stdin. In this case, sys.stdin
914 # points at a file reading os.devnull, because multiprocessing mucks
915 # with sys.stdin.
916 # - Leave the sys.stdin and output filehandles alone.
917 fd_pipes = {0: sys.stdin.fileno(),
918 1: output.fileno(),
919 2: output.fileno(),
920 sys.stdin.fileno(): sys.stdin.fileno(),
921 output.fileno(): output.fileno()}
922 portage.process._setup_pipes(fd_pipes)
923
924 # Portage doesn't like when sys.stdin.fileno() != 0, so point sys.stdin
925 # at the filehandle we just created in _setup_pipes.
926 if sys.stdin.fileno() != 0:
927 sys.stdin = os.fdopen(0, "r")
928
929 # Actually do the merge.
930 retval = scheduler.merge()
931
932 # We catch all exceptions here (including SystemExit, KeyboardInterrupt,
933 # etc) so as to ensure that we don't confuse the multiprocessing module,
934 # which expects that all forked children exit with os._exit().
935 except:
936 traceback.print_exc(file=output)
937 retval = 1
938 sys.stdout.flush()
939 sys.stderr.flush()
940 output.flush()
941 os._exit(retval)
942 else:
943 # Return the exit code of the subprocess.
944 return os.waitpid(pid, 0)[1]
David Jamesfcb70ef2011-02-02 16:02:30 -0800945
Brian Harring0be85c62012-03-17 19:52:12 -0700946def EmergeWorker(task_queue, job_queue, emerge, package_db, fetch_only=False):
David Jamesfcb70ef2011-02-02 16:02:30 -0800947 """This worker emerges any packages given to it on the task_queue.
948
949 Args:
950 task_queue: The queue of tasks for this worker to do.
951 job_queue: The queue of results from the worker.
952 emerge: An EmergeData() object.
953 package_db: A dict, mapping package ids to portage Package objects.
Brian Harring0be85c62012-03-17 19:52:12 -0700954 fetch_only: A bool, indicating if we should just fetch the target.
David Jamesfcb70ef2011-02-02 16:02:30 -0800955
956 It expects package identifiers to be passed to it via task_queue. When
957 a task is started, it pushes the (target, filename) to the started_queue.
958 The output is stored in filename. When a merge starts or finishes, we push
959 EmergeJobState objects to the job_queue.
960 """
961
962 SetupWorkerSignals()
963 settings, trees, mtimedb = emerge.settings, emerge.trees, emerge.mtimedb
David Jamesdeebd692011-05-09 17:02:52 -0700964
965 # Disable flushing of caches to save on I/O.
David James7a1ea4b2011-10-13 15:06:41 -0700966 root = emerge.settings["ROOT"]
967 vardb = emerge.trees[root]["vartree"].dbapi
968 vardb._flush_cache_enabled = False
Brian Harring0be85c62012-03-17 19:52:12 -0700969 bindb = emerge.trees[root]["bintree"].dbapi
970 # Might be a set, might be a list, might be None; no clue, just use shallow
971 # copy to ensure we can roll it back.
972 original_remotepkgs = copy.copy(bindb.bintree._remotepkgs)
David Jamesdeebd692011-05-09 17:02:52 -0700973
David Jamesfcb70ef2011-02-02 16:02:30 -0800974 opts, spinner = emerge.opts, emerge.spinner
975 opts["--nodeps"] = True
Brian Harring0be85c62012-03-17 19:52:12 -0700976 if fetch_only:
977 opts["--fetchonly"] = True
978
David Jamesfcb70ef2011-02-02 16:02:30 -0800979 while True:
980 # Wait for a new item to show up on the queue. This is a blocking wait,
981 # so if there's nothing to do, we just sit here.
Brian Harring0be85c62012-03-17 19:52:12 -0700982 pkg_state = task_queue.get()
983 if pkg_state is None:
David Jamesfcb70ef2011-02-02 16:02:30 -0800984 # If target is None, this means that the main thread wants us to quit.
985 # The other workers need to exit too, so we'll push the message back on
986 # to the queue so they'll get it too.
Brian Harring0be85c62012-03-17 19:52:12 -0700987 task_queue.put(None)
David Jamesfcb70ef2011-02-02 16:02:30 -0800988 return
David James7358d032011-05-19 10:40:03 -0700989 if KILLED.is_set():
990 return
991
Brian Harring0be85c62012-03-17 19:52:12 -0700992 target = pkg_state.target
993
David Jamesfcb70ef2011-02-02 16:02:30 -0800994 db_pkg = package_db[target]
Brian Harring0be85c62012-03-17 19:52:12 -0700995
996 if db_pkg.type_name == "binary":
997 if not fetch_only and pkg_state.fetched_successfully:
998 # Ensure portage doesn't think our pkg is remote- else it'll force
999 # a redownload of it (even if the on-disk file is fine). In-memory
1000 # caching basically, implemented dumbly.
1001 bindb.bintree._remotepkgs = None
1002 else:
1003 bindb.bintree_remotepkgs = original_remotepkgs
1004
David Jamesfcb70ef2011-02-02 16:02:30 -08001005 db_pkg.root_config = emerge.root_config
1006 install_list = [db_pkg]
1007 pkgname = db_pkg.pf
1008 output = tempfile.NamedTemporaryFile(prefix=pkgname + "-", delete=False)
1009 start_timestamp = time.time()
Brian Harring0be85c62012-03-17 19:52:12 -07001010 job = EmergeJobState(target, pkgname, False, output.name, start_timestamp,
1011 fetch_only=fetch_only)
David Jamesfcb70ef2011-02-02 16:02:30 -08001012 job_queue.put(job)
1013 if "--pretend" in opts:
1014 retcode = 0
1015 else:
David Jamesfcb70ef2011-02-02 16:02:30 -08001016 try:
David James386ccd12011-05-04 20:17:42 -07001017 emerge.scheduler_graph.mergelist = install_list
1018 scheduler = Scheduler(settings, trees, mtimedb, opts, spinner,
David Jamesbf1e3442011-05-28 07:44:20 -07001019 favorites=emerge.favorites, graph_config=emerge.scheduler_graph)
David Jamesace2e212011-07-13 11:47:39 -07001020
1021 # Enable blocker handling even though we're in --nodeps mode. This
1022 # allows us to unmerge the blocker after we've merged the replacement.
1023 scheduler._opts_ignore_blockers = frozenset()
1024
David James1ed3e252011-10-05 20:26:15 -07001025 retcode = EmergeProcess(scheduler, output)
David Jamesfcb70ef2011-02-02 16:02:30 -08001026 except Exception:
1027 traceback.print_exc(file=output)
1028 retcode = 1
David James1ed3e252011-10-05 20:26:15 -07001029 output.close()
David Jamesfcb70ef2011-02-02 16:02:30 -08001030
David James7358d032011-05-19 10:40:03 -07001031 if KILLED.is_set():
1032 return
1033
David Jamesfcb70ef2011-02-02 16:02:30 -08001034 job = EmergeJobState(target, pkgname, True, output.name, start_timestamp,
Brian Harring0be85c62012-03-17 19:52:12 -07001035 retcode, fetch_only=fetch_only)
David Jamesfcb70ef2011-02-02 16:02:30 -08001036 job_queue.put(job)
1037
1038
1039class LinePrinter(object):
1040 """Helper object to print a single line."""
1041
1042 def __init__(self, line):
1043 self.line = line
1044
1045 def Print(self, seek_locations):
1046 print self.line
1047
1048
1049class JobPrinter(object):
1050 """Helper object to print output of a job."""
1051
1052 def __init__(self, job, unlink=False):
1053 """Print output of job.
1054
1055 If unlink is True, unlink the job output file when done."""
1056 self.current_time = time.time()
1057 self.job = job
1058 self.unlink = unlink
1059
1060 def Print(self, seek_locations):
1061
1062 job = self.job
1063
1064 # Calculate how long the job has been running.
1065 seconds = self.current_time - job.start_timestamp
1066
1067 # Note that we've printed out the job so far.
1068 job.last_output_timestamp = self.current_time
1069
1070 # Note that we're starting the job
1071 info = "job %s (%dm%.1fs)" % (job.pkgname, seconds / 60, seconds % 60)
1072 last_output_seek = seek_locations.get(job.filename, 0)
1073 if last_output_seek:
1074 print "=== Continue output for %s ===" % info
1075 else:
1076 print "=== Start output for %s ===" % info
1077
1078 # Print actual output from job
1079 f = codecs.open(job.filename, encoding='utf-8', errors='replace')
1080 f.seek(last_output_seek)
1081 prefix = job.pkgname + ":"
1082 for line in f:
1083
1084 # Save off our position in the file
1085 if line and line[-1] == "\n":
1086 last_output_seek = f.tell()
1087 line = line[:-1]
1088
1089 # Print our line
1090 print prefix, line.encode('utf-8', 'replace')
1091 f.close()
1092
1093 # Save our last spot in the file so that we don't print out the same
1094 # location twice.
1095 seek_locations[job.filename] = last_output_seek
1096
1097 # Note end of output section
1098 if job.done:
1099 print "=== Complete: %s ===" % info
1100 else:
1101 print "=== Still running: %s ===" % info
1102
1103 if self.unlink:
1104 os.unlink(job.filename)
1105
1106
1107def PrintWorker(queue):
1108 """A worker that prints stuff to the screen as requested."""
1109
1110 def ExitHandler(signum, frame):
David James7358d032011-05-19 10:40:03 -07001111 # Set KILLED flag.
1112 KILLED.set()
1113
David Jamesfcb70ef2011-02-02 16:02:30 -08001114 # Switch to default signal handlers so that we'll die after two signals.
David James7358d032011-05-19 10:40:03 -07001115 signal.signal(signal.SIGINT, KillHandler)
1116 signal.signal(signal.SIGTERM, KillHandler)
David Jamesfcb70ef2011-02-02 16:02:30 -08001117
1118 # Don't exit on the first SIGINT / SIGTERM, because the parent worker will
1119 # handle it and tell us when we need to exit.
1120 signal.signal(signal.SIGINT, ExitHandler)
1121 signal.signal(signal.SIGTERM, ExitHandler)
1122
1123 # seek_locations is a map indicating the position we are at in each file.
1124 # It starts off empty, but is set by the various Print jobs as we go along
1125 # to indicate where we left off in each file.
1126 seek_locations = {}
1127 while True:
1128 try:
1129 job = queue.get()
1130 if job:
1131 job.Print(seek_locations)
David Jamesbccf8eb2011-07-27 14:06:06 -07001132 sys.stdout.flush()
David Jamesfcb70ef2011-02-02 16:02:30 -08001133 else:
1134 break
1135 except IOError as ex:
1136 if ex.errno == errno.EINTR:
1137 # Looks like we received a signal. Keep printing.
1138 continue
1139 raise
1140
Brian Harring867e2362012-03-17 04:05:17 -07001141
Brian Harring0be85c62012-03-17 19:52:12 -07001142class TargetState(object):
Brian Harring867e2362012-03-17 04:05:17 -07001143
Brian Harring0be85c62012-03-17 19:52:12 -07001144 __slots__ = ("target", "info", "score", "prefetched", "fetched_successfully")
Brian Harring867e2362012-03-17 04:05:17 -07001145
Brian Harring0be85c62012-03-17 19:52:12 -07001146 def __init__(self, target, info, fetched=False):
Brian Harring867e2362012-03-17 04:05:17 -07001147 self.target, self.info = target, info
Brian Harring0be85c62012-03-17 19:52:12 -07001148 self.fetched_successfully = False
1149 self.prefetched = False
Brian Harring867e2362012-03-17 04:05:17 -07001150 self.update_score()
1151
1152 def __cmp__(self, other):
1153 return cmp(self.score, other.score)
1154
1155 def update_score(self):
1156 self.score = (
1157 -len(self.info["tprovides"]),
Brian Harring0be85c62012-03-17 19:52:12 -07001158 len(self.info["needs"]),
Brian Harring11c5eeb2012-03-18 11:02:39 -07001159 not self.info["binary"],
Brian Harring867e2362012-03-17 04:05:17 -07001160 -len(self.info["provides"]),
1161 self.info["idx"],
1162 self.target,
1163 )
1164
1165
1166class ScoredHeap(object):
1167
Brian Harring0be85c62012-03-17 19:52:12 -07001168 __slots__ = ("heap", "_heap_set")
1169
Brian Harring867e2362012-03-17 04:05:17 -07001170 def __init__(self, initial=()):
Brian Harring0be85c62012-03-17 19:52:12 -07001171 self.heap = list()
1172 self._heap_set = set()
1173 if initial:
1174 self.multi_put(initial)
Brian Harring867e2362012-03-17 04:05:17 -07001175
1176 def get(self):
Brian Harring0be85c62012-03-17 19:52:12 -07001177 item = heapq.heappop(self.heap)
1178 self._heap_set.remove(item.target)
1179 return item
Brian Harring867e2362012-03-17 04:05:17 -07001180
Brian Harring0be85c62012-03-17 19:52:12 -07001181 def put(self, item):
1182 if not isinstance(item, TargetState):
1183 raise ValueError("Item %r isn't a TargetState" % (item,))
1184 heapq.heappush(self.heap, item)
1185 self._heap_set.add(item.target)
Brian Harring867e2362012-03-17 04:05:17 -07001186
Brian Harring0be85c62012-03-17 19:52:12 -07001187 def multi_put(self, sequence):
1188 sequence = list(sequence)
1189 self.heap.extend(sequence)
1190 self._heap_set.update(x.target for x in sequence)
Brian Harring867e2362012-03-17 04:05:17 -07001191 self.sort()
1192
David James5c9996d2012-03-24 10:50:46 -07001193 def sort(self):
1194 heapq.heapify(self.heap)
1195
Brian Harring0be85c62012-03-17 19:52:12 -07001196 def __contains__(self, target):
1197 return target in self._heap_set
1198
1199 def __nonzero__(self):
1200 return bool(self.heap)
1201
Brian Harring867e2362012-03-17 04:05:17 -07001202 def __len__(self):
1203 return len(self.heap)
1204
1205
David Jamesfcb70ef2011-02-02 16:02:30 -08001206class EmergeQueue(object):
1207 """Class to schedule emerge jobs according to a dependency graph."""
1208
1209 def __init__(self, deps_map, emerge, package_db, show_output):
1210 # Store the dependency graph.
1211 self._deps_map = deps_map
Brian Harring0be85c62012-03-17 19:52:12 -07001212 self._state_map = {}
David Jamesfcb70ef2011-02-02 16:02:30 -08001213 # Initialize the running queue to empty
Brian Harring0be85c62012-03-17 19:52:12 -07001214 self._build_jobs = {}
1215 self._build_ready = ScoredHeap()
1216 self._fetch_jobs = {}
1217 self._fetch_ready = ScoredHeap()
David Jamesfcb70ef2011-02-02 16:02:30 -08001218 # List of total package installs represented in deps_map.
1219 install_jobs = [x for x in deps_map if deps_map[x]["action"] == "merge"]
1220 self._total_jobs = len(install_jobs)
1221 self._show_output = show_output
1222
1223 if "--pretend" in emerge.opts:
1224 print "Skipping merge because of --pretend mode."
1225 sys.exit(0)
1226
David James7358d032011-05-19 10:40:03 -07001227 # Set a process group so we can easily terminate all children.
1228 os.setsid()
1229
David Jamesfcb70ef2011-02-02 16:02:30 -08001230 # Setup scheduler graph object. This is used by the child processes
1231 # to help schedule jobs.
1232 emerge.scheduler_graph = emerge.depgraph.schedulerGraph()
1233
1234 # Calculate how many jobs we can run in parallel. We don't want to pass
1235 # the --jobs flag over to emerge itself, because that'll tell emerge to
1236 # hide its output, and said output is quite useful for debugging hung
1237 # jobs.
1238 procs = min(self._total_jobs,
1239 emerge.opts.pop("--jobs", multiprocessing.cpu_count()))
Brian Harring0be85c62012-03-17 19:52:12 -07001240 self._build_procs = procs
1241 self._fetch_procs = procs
David James8c7e5e32011-06-28 11:26:03 -07001242 self._load_avg = emerge.opts.pop("--load-average", None)
David Jamesfcb70ef2011-02-02 16:02:30 -08001243 self._job_queue = multiprocessing.Queue()
1244 self._print_queue = multiprocessing.Queue()
Brian Harring0be85c62012-03-17 19:52:12 -07001245
1246 self._fetch_queue = multiprocessing.Queue()
1247 args = (self._fetch_queue, self._job_queue, emerge, package_db, True)
1248 self._fetch_pool = multiprocessing.Pool(self._fetch_procs, EmergeWorker,
1249 args)
1250
1251 self._build_queue = multiprocessing.Queue()
1252 args = (self._build_queue, self._job_queue, emerge, package_db)
1253 self._build_pool = multiprocessing.Pool(self._build_procs, EmergeWorker,
1254 args)
1255
David Jamesfcb70ef2011-02-02 16:02:30 -08001256 self._print_worker = multiprocessing.Process(target=PrintWorker,
1257 args=[self._print_queue])
1258 self._print_worker.start()
1259
1260 # Initialize the failed queue to empty.
1261 self._retry_queue = []
1262 self._failed = set()
1263
David Jamesfcb70ef2011-02-02 16:02:30 -08001264 # Setup an exit handler so that we print nice messages if we are
1265 # terminated.
1266 self._SetupExitHandler()
1267
1268 # Schedule our jobs.
Brian Harring0be85c62012-03-17 19:52:12 -07001269 self._state_map.update(
1270 (pkg, TargetState(pkg, data)) for pkg, data in deps_map.iteritems())
1271 self._fetch_ready.multi_put(self._state_map.itervalues())
David Jamesfcb70ef2011-02-02 16:02:30 -08001272
1273 def _SetupExitHandler(self):
1274
1275 def ExitHandler(signum, frame):
David James7358d032011-05-19 10:40:03 -07001276 # Set KILLED flag.
1277 KILLED.set()
David Jamesfcb70ef2011-02-02 16:02:30 -08001278
1279 # Kill our signal handlers so we don't get called recursively
David James7358d032011-05-19 10:40:03 -07001280 signal.signal(signal.SIGINT, KillHandler)
1281 signal.signal(signal.SIGTERM, KillHandler)
David Jamesfcb70ef2011-02-02 16:02:30 -08001282
1283 # Print our current job status
Brian Harring0be85c62012-03-17 19:52:12 -07001284 for job in self._build_jobs.itervalues():
David Jamesfcb70ef2011-02-02 16:02:30 -08001285 if job:
1286 self._print_queue.put(JobPrinter(job, unlink=True))
1287
1288 # Notify the user that we are exiting
1289 self._Print("Exiting on signal %s" % signum)
David James7358d032011-05-19 10:40:03 -07001290 self._print_queue.put(None)
1291 self._print_worker.join()
David Jamesfcb70ef2011-02-02 16:02:30 -08001292
1293 # Kill child threads, then exit.
David James7358d032011-05-19 10:40:03 -07001294 os.killpg(0, signal.SIGKILL)
David Jamesfcb70ef2011-02-02 16:02:30 -08001295 sys.exit(1)
1296
1297 # Print out job status when we are killed
1298 signal.signal(signal.SIGINT, ExitHandler)
1299 signal.signal(signal.SIGTERM, ExitHandler)
1300
Brian Harring0be85c62012-03-17 19:52:12 -07001301 def _Schedule(self, pkg_state):
David Jamesfcb70ef2011-02-02 16:02:30 -08001302 # We maintain a tree of all deps, if this doesn't need
David James8c7e5e32011-06-28 11:26:03 -07001303 # to be installed just free up its children and continue.
David Jamesfcb70ef2011-02-02 16:02:30 -08001304 # It is possible to reinstall deps of deps, without reinstalling
1305 # first level deps, like so:
1306 # chromeos (merge) -> eselect (nomerge) -> python (merge)
Brian Harring0be85c62012-03-17 19:52:12 -07001307 this_pkg = pkg_state.info
1308 target = pkg_state.target
1309 if pkg_state.info is not None:
1310 if this_pkg["action"] == "nomerge":
1311 self._Finish(target)
1312 elif target not in self._build_jobs:
1313 # Kick off the build if it's marked to be built.
1314 self._build_jobs[target] = None
1315 self._build_queue.put(pkg_state)
1316 return True
David Jamesfcb70ef2011-02-02 16:02:30 -08001317
David James8c7e5e32011-06-28 11:26:03 -07001318 def _ScheduleLoop(self):
1319 # If the current load exceeds our desired load average, don't schedule
1320 # more than one job.
1321 if self._load_avg and os.getloadavg()[0] > self._load_avg:
1322 needed_jobs = 1
1323 else:
Brian Harring0be85c62012-03-17 19:52:12 -07001324 needed_jobs = self._build_procs
David James8c7e5e32011-06-28 11:26:03 -07001325
1326 # Schedule more jobs.
Brian Harring0be85c62012-03-17 19:52:12 -07001327 while self._build_ready and len(self._build_jobs) < needed_jobs:
1328 state = self._build_ready.get()
1329 if state.target not in self._failed:
1330 self._Schedule(state)
David Jamesfcb70ef2011-02-02 16:02:30 -08001331
1332 def _Print(self, line):
1333 """Print a single line."""
1334 self._print_queue.put(LinePrinter(line))
1335
1336 def _Status(self):
1337 """Print status."""
1338 current_time = time.time()
1339 no_output = True
1340
1341 # Print interim output every minute if --show-output is used. Otherwise,
1342 # print notifications about running packages every 2 minutes, and print
1343 # full output for jobs that have been running for 60 minutes or more.
1344 if self._show_output:
1345 interval = 60
1346 notify_interval = 0
1347 else:
1348 interval = 60 * 60
1349 notify_interval = 60 * 2
Brian Harring0be85c62012-03-17 19:52:12 -07001350 for target, job in self._build_jobs.iteritems():
David Jamesfcb70ef2011-02-02 16:02:30 -08001351 if job:
1352 last_timestamp = max(job.start_timestamp, job.last_output_timestamp)
1353 if last_timestamp + interval < current_time:
1354 self._print_queue.put(JobPrinter(job))
1355 job.last_output_timestamp = current_time
1356 no_output = False
1357 elif (notify_interval and
1358 job.last_notify_timestamp + notify_interval < current_time):
1359 job_seconds = current_time - job.start_timestamp
1360 args = (job.pkgname, job_seconds / 60, job_seconds % 60, job.filename)
1361 info = "Still building %s (%dm%.1fs). Logs in %s" % args
1362 job.last_notify_timestamp = current_time
1363 self._Print(info)
1364 no_output = False
1365
1366 # If we haven't printed any messages yet, print a general status message
1367 # here.
1368 if no_output:
1369 seconds = current_time - GLOBAL_START
Brian Harring0be85c62012-03-17 19:52:12 -07001370 fjobs, fready = len(self._fetch_jobs), len(self._fetch_ready)
1371 bjobs, bready = len(self._build_jobs), len(self._build_ready)
1372 retries = len(self._retry_queue)
1373 pending = max(0, len(self._deps_map) - fjobs - bjobs)
1374 line = "Pending %s/%s, " % (pending, self._total_jobs)
1375 if fjobs or fready:
1376 line += "Fetching %s/%s, " % (fjobs, fready + fjobs)
1377 if bjobs or bready or retries:
1378 line += "Building %s/%s, " % (bjobs, bready + bjobs)
1379 if retries:
1380 line += "Retrying %s, " % (retries,)
David James8c7e5e32011-06-28 11:26:03 -07001381 load = " ".join(str(x) for x in os.getloadavg())
Brian Harring0be85c62012-03-17 19:52:12 -07001382 line += ("[Time %dm%.1fs Load %s]" % (seconds/60, seconds %60, load))
1383 self._Print(line)
David Jamesfcb70ef2011-02-02 16:02:30 -08001384
1385 def _Finish(self, target):
David James8c7e5e32011-06-28 11:26:03 -07001386 """Mark a target as completed and unblock dependencies."""
1387 this_pkg = self._deps_map[target]
1388 if this_pkg["needs"] and this_pkg["nodeps"]:
1389 # We got installed, but our deps have not been installed yet. Dependent
1390 # packages should only be installed when our needs have been fully met.
1391 this_pkg["action"] = "nomerge"
1392 else:
1393 finish = []
1394 for dep in this_pkg["provides"]:
1395 dep_pkg = self._deps_map[dep]
Brian Harring0be85c62012-03-17 19:52:12 -07001396 state = self._state_map[dep]
David James8c7e5e32011-06-28 11:26:03 -07001397 del dep_pkg["needs"][target]
Brian Harring0be85c62012-03-17 19:52:12 -07001398 state.update_score()
1399 if not state.prefetched:
1400 if dep in self._fetch_ready:
1401 # If it's not currently being fetched, update the prioritization
1402 self._fetch_ready.sort()
1403 elif not dep_pkg["needs"]:
David James8c7e5e32011-06-28 11:26:03 -07001404 if dep_pkg["nodeps"] and dep_pkg["action"] == "nomerge":
1405 self._Finish(dep)
1406 else:
Brian Harring0be85c62012-03-17 19:52:12 -07001407 self._build_ready.put(self._state_map[dep])
David James8c7e5e32011-06-28 11:26:03 -07001408 self._deps_map.pop(target)
David Jamesfcb70ef2011-02-02 16:02:30 -08001409
1410 def _Retry(self):
David James8c7e5e32011-06-28 11:26:03 -07001411 while self._retry_queue:
Brian Harring0be85c62012-03-17 19:52:12 -07001412 state = self._retry_queue.pop(0)
1413 if self._Schedule(state):
1414 self._Print("Retrying emerge of %s." % state.target)
David James8c7e5e32011-06-28 11:26:03 -07001415 break
David Jamesfcb70ef2011-02-02 16:02:30 -08001416
1417 def _Exit(self):
1418 # Tell emerge workers to exit. They all exit when 'None' is pushed
1419 # to the queue.
Brian Harring0be85c62012-03-17 19:52:12 -07001420 self._fetch_queue.put(None)
1421 self._build_queue.put(None)
1422
1423 self._fetch_pool.close()
1424 self._fetch_pool.join()
1425
1426 self._build_pool.close()
1427 self._build_pool.join()
1428
1429 self._build_queue.close()
1430 self._fetch_queue.close()
1431
1432 self._build_queue = self._fetch_queue = None
David Jamesfcb70ef2011-02-02 16:02:30 -08001433
1434 # Now that our workers are finished, we can kill the print queue.
1435 self._print_queue.put(None)
1436 self._print_worker.join()
David James97ce8902011-08-16 09:51:05 -07001437 self._print_queue.close()
1438 self._print_queue = None
1439 self._job_queue.close()
1440 self._job_queue = None
David Jamesfcb70ef2011-02-02 16:02:30 -08001441
1442 def Run(self):
1443 """Run through the scheduled ebuilds.
1444
1445 Keep running so long as we have uninstalled packages in the
1446 dependency graph to merge.
1447 """
Brian Harring0be85c62012-03-17 19:52:12 -07001448 # Start the fetchers.
1449 for _ in xrange(min(self._fetch_procs, len(self._fetch_ready))):
1450 state = self._fetch_ready.get()
1451 self._fetch_jobs[state.target] = None
1452 self._fetch_queue.put(state)
1453
1454 # Print an update, then get going.
1455 self._Status()
1456
David Jamese703d0f2012-01-12 16:27:45 -08001457 retried = set()
David Jamesfcb70ef2011-02-02 16:02:30 -08001458 while self._deps_map:
1459 # Check here that we are actually waiting for something.
Brian Harring0be85c62012-03-17 19:52:12 -07001460 if (self._build_queue.empty() and
David Jamesfcb70ef2011-02-02 16:02:30 -08001461 self._job_queue.empty() and
Brian Harring0be85c62012-03-17 19:52:12 -07001462 not self._fetch_jobs and
1463 not self._fetch_ready and
1464 not self._build_jobs and
1465 not self._build_ready and
David Jamesfcb70ef2011-02-02 16:02:30 -08001466 self._deps_map):
1467 # If we have failed on a package, retry it now.
1468 if self._retry_queue:
1469 self._Retry()
1470 else:
1471 # Tell child threads to exit.
1472 self._Exit()
1473
1474 # The dependency map is helpful for debugging failures.
1475 PrintDepsMap(self._deps_map)
1476
1477 # Tell the user why we're exiting.
1478 if self._failed:
1479 print "Packages failed: %s" % " ,".join(self._failed)
1480 else:
1481 print "Deadlock! Circular dependencies!"
1482 sys.exit(1)
1483
Brian Harring706747c2012-03-16 03:04:31 -07001484 for i in range(12):
David Jamesa74289a2011-08-12 10:41:24 -07001485 try:
1486 job = self._job_queue.get(timeout=5)
1487 break
1488 except Queue.Empty:
1489 # Check if any more jobs can be scheduled.
1490 self._ScheduleLoop()
1491 else:
Brian Harring706747c2012-03-16 03:04:31 -07001492 # Print an update every 60 seconds.
David Jamesfcb70ef2011-02-02 16:02:30 -08001493 self._Status()
1494 continue
1495
1496 target = job.target
1497
Brian Harring0be85c62012-03-17 19:52:12 -07001498 if job.fetch_only:
1499 if not job.done:
1500 self._fetch_jobs[job.target] = job
1501 else:
1502 state = self._state_map[job.target]
1503 state.prefetched = True
1504 state.fetched_successfully = (job.retcode == 0)
1505 del self._fetch_jobs[job.target]
1506 self._Print("Fetched %s in %2.2fs"
1507 % (target, time.time() - job.start_timestamp))
1508
1509 if self._show_output or job.retcode != 0:
1510 self._print_queue.put(JobPrinter(job, unlink=True))
1511 else:
1512 os.unlink(job.filename)
1513 # Failure or not, let build work with it next.
1514 if not self._deps_map[job.target]["needs"]:
1515 self._build_ready.put(state)
1516 self._ScheduleLoop()
1517
1518 if self._fetch_ready:
1519 state = self._fetch_ready.get()
1520 self._fetch_queue.put(state)
1521 self._fetch_jobs[state.target] = None
1522 else:
1523 # Minor optimization; shut down fetchers early since we know
1524 # the queue is empty.
1525 self._fetch_queue.put(None)
1526 continue
1527
David Jamesfcb70ef2011-02-02 16:02:30 -08001528 if not job.done:
Brian Harring0be85c62012-03-17 19:52:12 -07001529 self._build_jobs[target] = job
David Jamesfcb70ef2011-02-02 16:02:30 -08001530 self._Print("Started %s (logged in %s)" % (target, job.filename))
1531 continue
1532
1533 # Print output of job
1534 if self._show_output or job.retcode != 0:
1535 self._print_queue.put(JobPrinter(job, unlink=True))
1536 else:
1537 os.unlink(job.filename)
Brian Harring0be85c62012-03-17 19:52:12 -07001538 del self._build_jobs[target]
David Jamesfcb70ef2011-02-02 16:02:30 -08001539
1540 seconds = time.time() - job.start_timestamp
1541 details = "%s (in %dm%.1fs)" % (target, seconds / 60, seconds % 60)
David James32420cc2011-08-25 21:32:46 -07001542 previously_failed = target in self._failed
David Jamesfcb70ef2011-02-02 16:02:30 -08001543
1544 # Complain if necessary.
1545 if job.retcode != 0:
1546 # Handle job failure.
David James32420cc2011-08-25 21:32:46 -07001547 if previously_failed:
David Jamesfcb70ef2011-02-02 16:02:30 -08001548 # If this job has failed previously, give up.
1549 self._Print("Failed %s. Your build has failed." % details)
1550 else:
1551 # Queue up this build to try again after a long while.
David Jamese703d0f2012-01-12 16:27:45 -08001552 retried.add(target)
Brian Harring0be85c62012-03-17 19:52:12 -07001553 self._retry_queue.append(self._state_map[target])
David Jamesfcb70ef2011-02-02 16:02:30 -08001554 self._failed.add(target)
1555 self._Print("Failed %s, retrying later." % details)
1556 else:
David James32420cc2011-08-25 21:32:46 -07001557 if previously_failed:
1558 # Remove target from list of failed packages.
1559 self._failed.remove(target)
1560
1561 self._Print("Completed %s" % details)
1562
1563 # Mark as completed and unblock waiting ebuilds.
1564 self._Finish(target)
1565
1566 if previously_failed and self._retry_queue:
David Jamesfcb70ef2011-02-02 16:02:30 -08001567 # If we have successfully retried a failed package, and there
1568 # are more failed packages, try the next one. We will only have
1569 # one retrying package actively running at a time.
1570 self._Retry()
1571
David Jamesfcb70ef2011-02-02 16:02:30 -08001572
David James8c7e5e32011-06-28 11:26:03 -07001573 # Schedule pending jobs and print an update.
1574 self._ScheduleLoop()
1575 self._Status()
David Jamesfcb70ef2011-02-02 16:02:30 -08001576
David Jamese703d0f2012-01-12 16:27:45 -08001577 # If packages were retried, output a warning.
1578 if retried:
1579 self._Print("")
1580 self._Print("WARNING: The following packages failed the first time,")
1581 self._Print("but succeeded upon retry. This might indicate incorrect")
1582 self._Print("dependencies.")
1583 for pkg in retried:
1584 self._Print(" %s" % pkg)
1585 self._Print("@@@STEP_WARNINGS@@@")
1586 self._Print("")
1587
David Jamesfcb70ef2011-02-02 16:02:30 -08001588 # Tell child threads to exit.
1589 self._Print("Merge complete")
1590 self._Exit()
1591
1592
Brian Harring30675052012-02-29 12:18:22 -08001593def main(argv):
David Jamesfcb70ef2011-02-02 16:02:30 -08001594
Brian Harring30675052012-02-29 12:18:22 -08001595 parallel_emerge_args = argv[:]
David Jamesfcb70ef2011-02-02 16:02:30 -08001596 deps = DepGraphGenerator()
Brian Harring30675052012-02-29 12:18:22 -08001597 deps.Initialize(parallel_emerge_args)
David Jamesfcb70ef2011-02-02 16:02:30 -08001598 emerge = deps.emerge
1599
1600 if emerge.action is not None:
Brian Harring30675052012-02-29 12:18:22 -08001601 argv = deps.ParseParallelEmergeArgs(argv)
1602 sys.exit(emerge_main(argv))
David Jamesfcb70ef2011-02-02 16:02:30 -08001603 elif not emerge.cmdline_packages:
1604 Usage()
1605 sys.exit(1)
1606
1607 # Unless we're in pretend mode, there's not much point running without
1608 # root access. We need to be able to install packages.
1609 #
1610 # NOTE: Even if you're running --pretend, it's a good idea to run
1611 # parallel_emerge with root access so that portage can write to the
1612 # dependency cache. This is important for performance.
1613 if "--pretend" not in emerge.opts and portage.secpass < 2:
1614 print "parallel_emerge: superuser access is required."
1615 sys.exit(1)
1616
1617 if "--quiet" not in emerge.opts:
1618 cmdline_packages = " ".join(emerge.cmdline_packages)
David Jamesfcb70ef2011-02-02 16:02:30 -08001619 print "Starting fast-emerge."
1620 print " Building package %s on %s" % (cmdline_packages,
1621 deps.board or "root")
David Jamesfcb70ef2011-02-02 16:02:30 -08001622
David James386ccd12011-05-04 20:17:42 -07001623 deps_tree, deps_info = deps.GenDependencyTree()
David Jamesfcb70ef2011-02-02 16:02:30 -08001624
1625 # You want me to be verbose? I'll give you two trees! Twice as much value.
1626 if "--tree" in emerge.opts and "--verbose" in emerge.opts:
1627 deps.PrintTree(deps_tree)
1628
David James386ccd12011-05-04 20:17:42 -07001629 deps_graph = deps.GenDependencyGraph(deps_tree, deps_info)
David Jamesfcb70ef2011-02-02 16:02:30 -08001630
1631 # OK, time to print out our progress so far.
1632 deps.PrintInstallPlan(deps_graph)
1633 if "--tree" in emerge.opts:
1634 PrintDepsMap(deps_graph)
1635
1636 # Are we upgrading portage? If so, and there are more packages to merge,
1637 # schedule a restart of parallel_emerge to merge the rest. This ensures that
1638 # we pick up all updates to portage settings before merging any more
1639 # packages.
1640 portage_upgrade = False
1641 root = emerge.settings["ROOT"]
1642 final_db = emerge.depgraph._dynamic_config.mydbapi[root]
1643 if root == "/":
1644 for db_pkg in final_db.match_pkgs("sys-apps/portage"):
1645 portage_pkg = deps_graph.get(db_pkg.cpv)
1646 if portage_pkg and len(deps_graph) > 1:
1647 portage_pkg["needs"].clear()
1648 portage_pkg["provides"].clear()
1649 deps_graph = { str(db_pkg.cpv): portage_pkg }
1650 portage_upgrade = True
1651 if "--quiet" not in emerge.opts:
1652 print "Upgrading portage first, then restarting..."
1653
1654 # Run the queued emerges.
1655 scheduler = EmergeQueue(deps_graph, emerge, deps.package_db, deps.show_output)
1656 scheduler.Run()
David James97ce8902011-08-16 09:51:05 -07001657 scheduler = None
David Jamesfcb70ef2011-02-02 16:02:30 -08001658
David Jamesfcb70ef2011-02-02 16:02:30 -08001659 # Update environment (library cache, symlinks, etc.)
1660 if deps.board and "--pretend" not in emerge.opts:
Brian Harring1e3fefc2011-10-03 12:09:19 -07001661 # Turn off env-update suppression used above for disabling
1662 # env-update during merging.
1663 os.environ["FEATURES"] += " -no-env-update"
1664 # Also kick the existing settings should they be reused...
1665 if hasattr(portage, 'settings'):
1666 portage.settings.unlock()
1667 portage.settings.features.discard('no-env-update')
David Jamesfcb70ef2011-02-02 16:02:30 -08001668 portage.env_update()
1669
1670 # If we already upgraded portage, we don't need to do so again. But we do
1671 # need to upgrade the rest of the packages. So we'll go ahead and do that.
David Jamesebc3ae02011-05-21 20:46:10 -07001672 #
1673 # In order to grant the child permission to run setsid, we need to run sudo
1674 # again. We preserve SUDO_USER here in case an ebuild depends on it.
David Jamesfcb70ef2011-02-02 16:02:30 -08001675 if portage_upgrade:
Brian Harring30675052012-02-29 12:18:22 -08001676 args = ["sudo", "-E", "SUDO_USER=%s" % os.environ.get("SUDO_USER", "")]
Brian Harringef3e9832012-03-02 04:43:05 -08001677 args += [os.path.abspath(sys.argv[0])] + parallel_emerge_args
Brian Harring30675052012-02-29 12:18:22 -08001678 args += ["--exclude=sys-apps/portage"]
David Jamesebc3ae02011-05-21 20:46:10 -07001679 os.execvp("sudo", args)
David Jamesfcb70ef2011-02-02 16:02:30 -08001680
1681 print "Done"
1682 sys.exit(0)