blob: 568ae57fc0649d795b1022218db10a97f25f1c57 [file] [log] [blame]
David Jamesfcb70ef2011-02-02 16:02:30 -08001#!/usr/bin/python2.6
Mike Frysinger0a647fc2012-08-06 14:36:05 -04002# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
David Jamesfcb70ef2011-02-02 16:02:30 -08003# 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
David James78b6cd92012-04-02 21:36:12 -070012This script runs multiple emerge processes in parallel, using appropriate
13Portage APIs. It is faster than standard emerge because it has a
14multiprocess model instead of an asynchronous model.
David Jamesfcb70ef2011-02-02 16:02:30 -080015"""
16
17import codecs
18import copy
19import errno
Brian Harring8294d652012-05-23 02:20:52 -070020import gc
David James8c7e5e32011-06-28 11:26:03 -070021import heapq
David Jamesfcb70ef2011-02-02 16:02:30 -080022import multiprocessing
23import os
24import Queue
David Jamesfcb70ef2011-02-02 16:02:30 -080025import signal
26import sys
27import tempfile
Brian Harring8294d652012-05-23 02:20:52 -070028import threading
David Jamesfcb70ef2011-02-02 16:02:30 -080029import time
30import traceback
David Jamesfcb70ef2011-02-02 16:02:30 -080031
32# If PORTAGE_USERNAME isn't specified, scrape it from the $HOME variable. On
33# Chromium OS, the default "portage" user doesn't have the necessary
34# permissions. It'd be easier if we could default to $USERNAME, but $USERNAME
35# is "root" here because we get called through sudo.
36#
37# We need to set this before importing any portage modules, because portage
38# looks up "PORTAGE_USERNAME" at import time.
39#
40# NOTE: .bashrc sets PORTAGE_USERNAME = $USERNAME, so most people won't
41# encounter this case unless they have an old chroot or blow away the
42# environment by running sudo without the -E specifier.
43if "PORTAGE_USERNAME" not in os.environ:
44 homedir = os.environ.get("HOME")
45 if homedir:
46 os.environ["PORTAGE_USERNAME"] = os.path.basename(homedir)
47
48# Portage doesn't expose dependency trees in its public API, so we have to
49# make use of some private APIs here. These modules are found under
50# /usr/lib/portage/pym/.
51#
52# TODO(davidjames): Update Portage to expose public APIs for these features.
53from _emerge.actions import adjust_configs
54from _emerge.actions import load_emerge_config
55from _emerge.create_depgraph_params import create_depgraph_params
David James386ccd12011-05-04 20:17:42 -070056from _emerge.depgraph import backtrack_depgraph
Mike Frysinger901eaad2012-10-10 18:18:03 -040057try:
58 from _emerge.main import clean_logs
59except ImportError:
60 # Older portage versions did not provide clean_logs, so stub it.
61 # We need this if running in an older chroot that hasn't yet upgraded
62 # the portage version.
63 clean_logs = lambda x: None
David Jamesfcb70ef2011-02-02 16:02:30 -080064from _emerge.main import emerge_main
65from _emerge.main import parse_opts
66from _emerge.Package import Package
67from _emerge.Scheduler import Scheduler
David Jamesfcb70ef2011-02-02 16:02:30 -080068from _emerge.stdout_spinner import stdout_spinner
David James386ccd12011-05-04 20:17:42 -070069from portage._global_updates import _global_updates
David Jamesfcb70ef2011-02-02 16:02:30 -080070import portage
71import portage.debug
David Jamesfcb70ef2011-02-02 16:02:30 -080072
David Jamesfcb70ef2011-02-02 16:02:30 -080073def Usage():
74 """Print usage."""
75 print "Usage:"
David James386ccd12011-05-04 20:17:42 -070076 print " ./parallel_emerge [--board=BOARD] [--workon=PKGS]"
David Jamesfcb70ef2011-02-02 16:02:30 -080077 print " [--rebuild] [emerge args] package"
78 print
79 print "Packages specified as workon packages are always built from source."
David Jamesfcb70ef2011-02-02 16:02:30 -080080 print
81 print "The --workon argument is mainly useful when you want to build and"
82 print "install packages that you are working on unconditionally, but do not"
83 print "to have to rev the package to indicate you want to build it from"
84 print "source. The build_packages script will automatically supply the"
85 print "workon argument to emerge, ensuring that packages selected using"
86 print "cros-workon are rebuilt."
87 print
88 print "The --rebuild option rebuilds packages whenever their dependencies"
89 print "are changed. This ensures that your build is correct."
David Jamesfcb70ef2011-02-02 16:02:30 -080090
91
David Jamesfcb70ef2011-02-02 16:02:30 -080092# Global start time
93GLOBAL_START = time.time()
94
David James7358d032011-05-19 10:40:03 -070095# Whether process has been killed by a signal.
96KILLED = multiprocessing.Event()
97
David Jamesfcb70ef2011-02-02 16:02:30 -080098
99class EmergeData(object):
100 """This simple struct holds various emerge variables.
101
102 This struct helps us easily pass emerge variables around as a unit.
103 These variables are used for calculating dependencies and installing
104 packages.
105 """
106
David Jamesbf1e3442011-05-28 07:44:20 -0700107 __slots__ = ["action", "cmdline_packages", "depgraph", "favorites",
108 "mtimedb", "opts", "root_config", "scheduler_graph",
109 "settings", "spinner", "trees"]
David Jamesfcb70ef2011-02-02 16:02:30 -0800110
111 def __init__(self):
112 # The action the user requested. If the user is installing packages, this
113 # is None. If the user is doing anything other than installing packages,
114 # this will contain the action name, which will map exactly to the
115 # long-form name of the associated emerge option.
116 #
117 # Example: If you call parallel_emerge --unmerge package, the action name
118 # will be "unmerge"
119 self.action = None
120
121 # The list of packages the user passed on the command-line.
122 self.cmdline_packages = None
123
124 # The emerge dependency graph. It'll contain all the packages involved in
125 # this merge, along with their versions.
126 self.depgraph = None
127
David Jamesbf1e3442011-05-28 07:44:20 -0700128 # The list of candidates to add to the world file.
129 self.favorites = None
130
David Jamesfcb70ef2011-02-02 16:02:30 -0800131 # A dict of the options passed to emerge. This dict has been cleaned up
132 # a bit by parse_opts, so that it's a bit easier for the emerge code to
133 # look at the options.
134 #
135 # Emerge takes a few shortcuts in its cleanup process to make parsing of
136 # the options dict easier. For example, if you pass in "--usepkg=n", the
137 # "--usepkg" flag is just left out of the dictionary altogether. Because
138 # --usepkg=n is the default, this makes parsing easier, because emerge
139 # can just assume that if "--usepkg" is in the dictionary, it's enabled.
140 #
141 # These cleanup processes aren't applied to all options. For example, the
142 # --with-bdeps flag is passed in as-is. For a full list of the cleanups
143 # applied by emerge, see the parse_opts function in the _emerge.main
144 # package.
145 self.opts = None
146
147 # A dictionary used by portage to maintain global state. This state is
148 # loaded from disk when portage starts up, and saved to disk whenever we
149 # call mtimedb.commit().
150 #
151 # This database contains information about global updates (i.e., what
152 # version of portage we have) and what we're currently doing. Portage
153 # saves what it is currently doing in this database so that it can be
154 # resumed when you call it with the --resume option.
155 #
156 # parallel_emerge does not save what it is currently doing in the mtimedb,
157 # so we do not support the --resume option.
158 self.mtimedb = None
159
160 # The portage configuration for our current root. This contains the portage
161 # settings (see below) and the three portage trees for our current root.
162 # (The three portage trees are explained below, in the documentation for
163 # the "trees" member.)
164 self.root_config = None
165
166 # The scheduler graph is used by emerge to calculate what packages to
167 # install. We don't actually install any deps, so this isn't really used,
168 # but we pass it in to the Scheduler object anyway.
169 self.scheduler_graph = None
170
171 # Portage settings for our current session. Most of these settings are set
172 # in make.conf inside our current install root.
173 self.settings = None
174
175 # The spinner, which spews stuff to stdout to indicate that portage is
176 # doing something. We maintain our own spinner, so we set the portage
177 # spinner to "silent" mode.
178 self.spinner = None
179
180 # The portage trees. There are separate portage trees for each root. To get
181 # the portage tree for the current root, you can look in self.trees[root],
182 # where root = self.settings["ROOT"].
183 #
184 # In each root, there are three trees: vartree, porttree, and bintree.
185 # - vartree: A database of the currently-installed packages.
186 # - porttree: A database of ebuilds, that can be used to build packages.
187 # - bintree: A database of binary packages.
188 self.trees = None
189
190
191class DepGraphGenerator(object):
192 """Grab dependency information about packages from portage.
193
194 Typical usage:
195 deps = DepGraphGenerator()
196 deps.Initialize(sys.argv[1:])
197 deps_tree, deps_info = deps.GenDependencyTree()
198 deps_graph = deps.GenDependencyGraph(deps_tree, deps_info)
199 deps.PrintTree(deps_tree)
200 PrintDepsMap(deps_graph)
201 """
202
David James386ccd12011-05-04 20:17:42 -0700203 __slots__ = ["board", "emerge", "package_db", "show_output"]
David Jamesfcb70ef2011-02-02 16:02:30 -0800204
205 def __init__(self):
206 self.board = None
207 self.emerge = EmergeData()
David Jamesfcb70ef2011-02-02 16:02:30 -0800208 self.package_db = {}
David Jamesfcb70ef2011-02-02 16:02:30 -0800209 self.show_output = False
David Jamesfcb70ef2011-02-02 16:02:30 -0800210
211 def ParseParallelEmergeArgs(self, argv):
212 """Read the parallel emerge arguments from the command-line.
213
214 We need to be compatible with emerge arg format. We scrape arguments that
215 are specific to parallel_emerge, and pass through the rest directly to
216 emerge.
217 Args:
218 argv: arguments list
219 Returns:
220 Arguments that don't belong to parallel_emerge
221 """
222 emerge_args = []
223 for arg in argv:
224 # Specifically match arguments that are specific to parallel_emerge, and
225 # pass through the rest.
226 if arg.startswith("--board="):
227 self.board = arg.replace("--board=", "")
228 elif arg.startswith("--workon="):
229 workon_str = arg.replace("--workon=", "")
David James7a1ea4b2011-10-13 15:06:41 -0700230 emerge_args.append("--reinstall-atoms=%s" % workon_str)
231 emerge_args.append("--usepkg-exclude=%s" % workon_str)
David Jamesfcb70ef2011-02-02 16:02:30 -0800232 elif arg.startswith("--force-remote-binary="):
233 force_remote_binary = arg.replace("--force-remote-binary=", "")
David James7a1ea4b2011-10-13 15:06:41 -0700234 emerge_args.append("--useoldpkg-atoms=%s" % force_remote_binary)
David Jamesfcb70ef2011-02-02 16:02:30 -0800235 elif arg == "--show-output":
236 self.show_output = True
David James386ccd12011-05-04 20:17:42 -0700237 elif arg == "--rebuild":
David James7a1ea4b2011-10-13 15:06:41 -0700238 emerge_args.append("--rebuild-if-unbuilt")
David Jamesfcb70ef2011-02-02 16:02:30 -0800239 else:
240 # Not one of our options, so pass through to emerge.
241 emerge_args.append(arg)
242
David James386ccd12011-05-04 20:17:42 -0700243 # These packages take a really long time to build, so, for expediency, we
244 # are blacklisting them from automatic rebuilds because one of their
245 # dependencies needs to be recompiled.
246 for pkg in ("chromeos-base/chromeos-chrome", "media-plugins/o3d",
247 "dev-java/icedtea"):
David James7a1ea4b2011-10-13 15:06:41 -0700248 emerge_args.append("--rebuild-exclude=%s" % pkg)
David Jamesfcb70ef2011-02-02 16:02:30 -0800249
250 return emerge_args
251
252 def Initialize(self, args):
253 """Initializer. Parses arguments and sets up portage state."""
254
255 # Parse and strip out args that are just intended for parallel_emerge.
256 emerge_args = self.ParseParallelEmergeArgs(args)
257
258 # Setup various environment variables based on our current board. These
259 # variables are normally setup inside emerge-${BOARD}, but since we don't
260 # call that script, we have to set it up here. These variables serve to
261 # point our tools at /build/BOARD and to setup cross compiles to the
262 # appropriate board as configured in toolchain.conf.
263 if self.board:
264 os.environ["PORTAGE_CONFIGROOT"] = "/build/" + self.board
265 os.environ["PORTAGE_SYSROOT"] = "/build/" + self.board
266 os.environ["SYSROOT"] = "/build/" + self.board
David Jamesfcb70ef2011-02-02 16:02:30 -0800267
268 # Although CHROMEOS_ROOT isn't specific to boards, it's normally setup
269 # inside emerge-${BOARD}, so we set it up here for compatibility. It
270 # will be going away soon as we migrate to CROS_WORKON_SRCROOT.
271 os.environ.setdefault("CHROMEOS_ROOT", os.environ["HOME"] + "/trunk")
272
273 # Turn off interactive delays
274 os.environ["EBEEP_IGNORE"] = "1"
275 os.environ["EPAUSE_IGNORE"] = "1"
Mike Frysinger0a647fc2012-08-06 14:36:05 -0400276 os.environ["CLEAN_DELAY"] = "0"
David Jamesfcb70ef2011-02-02 16:02:30 -0800277
278 # Parse the emerge options.
David Jamesea3ca332011-05-26 11:48:29 -0700279 action, opts, cmdline_packages = parse_opts(emerge_args, silent=True)
David Jamesfcb70ef2011-02-02 16:02:30 -0800280
281 # Set environment variables based on options. Portage normally sets these
282 # environment variables in emerge_main, but we can't use that function,
283 # because it also does a bunch of other stuff that we don't want.
284 # TODO(davidjames): Patch portage to move this logic into a function we can
285 # reuse here.
286 if "--debug" in opts:
287 os.environ["PORTAGE_DEBUG"] = "1"
288 if "--config-root" in opts:
289 os.environ["PORTAGE_CONFIGROOT"] = opts["--config-root"]
290 if "--root" in opts:
291 os.environ["ROOT"] = opts["--root"]
292 if "--accept-properties" in opts:
293 os.environ["ACCEPT_PROPERTIES"] = opts["--accept-properties"]
294
David Jamesfcb70ef2011-02-02 16:02:30 -0800295 # If we're installing packages to the board, and we're not using the
David James927a56d2012-04-03 11:26:39 -0700296 # official flag, we can disable vardb locks. This is safe because we
297 # only run up to one instance of parallel_emerge in parallel.
David Jamesfcb70ef2011-02-02 16:02:30 -0800298 if self.board and os.environ.get("CHROMEOS_OFFICIAL") != "1":
299 os.environ.setdefault("PORTAGE_LOCKS", "false")
David Jamesfcb70ef2011-02-02 16:02:30 -0800300
301 # Now that we've setup the necessary environment variables, we can load the
302 # emerge config from disk.
303 settings, trees, mtimedb = load_emerge_config()
304
David Jamesea3ca332011-05-26 11:48:29 -0700305 # Add in EMERGE_DEFAULT_OPTS, if specified.
306 tmpcmdline = []
307 if "--ignore-default-opts" not in opts:
308 tmpcmdline.extend(settings["EMERGE_DEFAULT_OPTS"].split())
309 tmpcmdline.extend(emerge_args)
310 action, opts, cmdline_packages = parse_opts(tmpcmdline)
311
312 # If we're installing to the board, we want the --root-deps option so that
313 # portage will install the build dependencies to that location as well.
314 if self.board:
315 opts.setdefault("--root-deps", True)
316
David Jamesfcb70ef2011-02-02 16:02:30 -0800317 # Check whether our portage tree is out of date. Typically, this happens
318 # when you're setting up a new portage tree, such as in setup_board and
319 # make_chroot. In that case, portage applies a bunch of global updates
320 # here. Once the updates are finished, we need to commit any changes
321 # that the global update made to our mtimedb, and reload the config.
322 #
323 # Portage normally handles this logic in emerge_main, but again, we can't
324 # use that function here.
325 if _global_updates(trees, mtimedb["updates"]):
326 mtimedb.commit()
327 settings, trees, mtimedb = load_emerge_config(trees=trees)
328
329 # Setup implied options. Portage normally handles this logic in
330 # emerge_main.
331 if "--buildpkgonly" in opts or "buildpkg" in settings.features:
332 opts.setdefault("--buildpkg", True)
333 if "--getbinpkgonly" in opts:
334 opts.setdefault("--usepkgonly", True)
335 opts.setdefault("--getbinpkg", True)
336 if "getbinpkg" in settings.features:
337 # Per emerge_main, FEATURES=getbinpkg overrides --getbinpkg=n
338 opts["--getbinpkg"] = True
339 if "--getbinpkg" in opts or "--usepkgonly" in opts:
340 opts.setdefault("--usepkg", True)
341 if "--fetch-all-uri" in opts:
342 opts.setdefault("--fetchonly", True)
343 if "--skipfirst" in opts:
344 opts.setdefault("--resume", True)
345 if "--buildpkgonly" in opts:
346 # --buildpkgonly will not merge anything, so it overrides all binary
347 # package options.
348 for opt in ("--getbinpkg", "--getbinpkgonly",
349 "--usepkg", "--usepkgonly"):
350 opts.pop(opt, None)
351 if (settings.get("PORTAGE_DEBUG", "") == "1" and
352 "python-trace" in settings.features):
353 portage.debug.set_trace(True)
354
355 # Complain about unsupported options
David James386ccd12011-05-04 20:17:42 -0700356 for opt in ("--ask", "--ask-enter-invalid", "--resume", "--skipfirst"):
David Jamesfcb70ef2011-02-02 16:02:30 -0800357 if opt in opts:
358 print "%s is not supported by parallel_emerge" % opt
359 sys.exit(1)
360
361 # Make emerge specific adjustments to the config (e.g. colors!)
362 adjust_configs(opts, trees)
363
364 # Save our configuration so far in the emerge object
365 emerge = self.emerge
366 emerge.action, emerge.opts = action, opts
367 emerge.settings, emerge.trees, emerge.mtimedb = settings, trees, mtimedb
368 emerge.cmdline_packages = cmdline_packages
369 root = settings["ROOT"]
370 emerge.root_config = trees[root]["root_config"]
371
David James386ccd12011-05-04 20:17:42 -0700372 if "--usepkg" in opts:
David Jamesfcb70ef2011-02-02 16:02:30 -0800373 emerge.trees[root]["bintree"].populate("--getbinpkg" in opts)
374
David Jamesfcb70ef2011-02-02 16:02:30 -0800375 def CreateDepgraph(self, emerge, packages):
376 """Create an emerge depgraph object."""
377 # Setup emerge options.
378 emerge_opts = emerge.opts.copy()
379
David James386ccd12011-05-04 20:17:42 -0700380 # Ask portage to build a dependency graph. with the options we specified
381 # above.
David Jamesfcb70ef2011-02-02 16:02:30 -0800382 params = create_depgraph_params(emerge_opts, emerge.action)
David Jamesbf1e3442011-05-28 07:44:20 -0700383 success, depgraph, favorites = backtrack_depgraph(
David James386ccd12011-05-04 20:17:42 -0700384 emerge.settings, emerge.trees, emerge_opts, params, emerge.action,
385 packages, emerge.spinner)
386 emerge.depgraph = depgraph
David Jamesfcb70ef2011-02-02 16:02:30 -0800387
David James386ccd12011-05-04 20:17:42 -0700388 # Is it impossible to honor the user's request? Bail!
389 if not success:
390 depgraph.display_problems()
391 sys.exit(1)
David Jamesfcb70ef2011-02-02 16:02:30 -0800392
393 emerge.depgraph = depgraph
David Jamesbf1e3442011-05-28 07:44:20 -0700394 emerge.favorites = favorites
David Jamesfcb70ef2011-02-02 16:02:30 -0800395
David Jamesdeebd692011-05-09 17:02:52 -0700396 # Prime and flush emerge caches.
397 root = emerge.settings["ROOT"]
398 vardb = emerge.trees[root]["vartree"].dbapi
David James0bdc5de2011-05-12 16:22:26 -0700399 if "--pretend" not in emerge.opts:
400 vardb.counter_tick()
David Jamesdeebd692011-05-09 17:02:52 -0700401 vardb.flush_cache()
402
David James386ccd12011-05-04 20:17:42 -0700403 def GenDependencyTree(self):
David Jamesfcb70ef2011-02-02 16:02:30 -0800404 """Get dependency tree info from emerge.
405
David Jamesfcb70ef2011-02-02 16:02:30 -0800406 Returns:
407 Dependency tree
408 """
409 start = time.time()
410
411 emerge = self.emerge
412
413 # Create a list of packages to merge
414 packages = set(emerge.cmdline_packages[:])
David Jamesfcb70ef2011-02-02 16:02:30 -0800415
416 # Tell emerge to be quiet. We print plenty of info ourselves so we don't
417 # need any extra output from portage.
418 portage.util.noiselimit = -1
419
420 # My favorite feature: The silent spinner. It doesn't spin. Ever.
421 # I'd disable the colors by default too, but they look kind of cool.
422 emerge.spinner = stdout_spinner()
423 emerge.spinner.update = emerge.spinner.update_quiet
424
425 if "--quiet" not in emerge.opts:
426 print "Calculating deps..."
427
428 self.CreateDepgraph(emerge, packages)
429 depgraph = emerge.depgraph
430
431 # Build our own tree from the emerge digraph.
432 deps_tree = {}
433 digraph = depgraph._dynamic_config.digraph
David James3f778802011-08-25 19:31:45 -0700434 root = emerge.settings["ROOT"]
435 final_db = depgraph._dynamic_config.mydbapi[root]
David Jamesfcb70ef2011-02-02 16:02:30 -0800436 for node, node_deps in digraph.nodes.items():
437 # Calculate dependency packages that need to be installed first. Each
438 # child on the digraph is a dependency. The "operation" field specifies
439 # what we're doing (e.g. merge, uninstall, etc.). The "priorities" array
440 # contains the type of dependency (e.g. build, runtime, runtime_post,
441 # etc.)
442 #
David Jamesfcb70ef2011-02-02 16:02:30 -0800443 # Portage refers to the identifiers for packages as a CPV. This acronym
444 # stands for Component/Path/Version.
445 #
446 # Here's an example CPV: chromeos-base/power_manager-0.0.1-r1
447 # Split up, this CPV would be:
448 # C -- Component: chromeos-base
449 # P -- Path: power_manager
450 # V -- Version: 0.0.1-r1
451 #
452 # We just refer to CPVs as packages here because it's easier.
453 deps = {}
454 for child, priorities in node_deps[0].items():
David James3f778802011-08-25 19:31:45 -0700455 if isinstance(child, Package) and child.root == root:
456 cpv = str(child.cpv)
457 action = str(child.operation)
458
459 # If we're uninstalling a package, check whether Portage is
460 # installing a replacement. If so, just depend on the installation
461 # of the new package, because the old package will automatically
462 # be uninstalled at that time.
463 if action == "uninstall":
464 for pkg in final_db.match_pkgs(child.slot_atom):
465 cpv = str(pkg.cpv)
466 action = "merge"
467 break
468
469 deps[cpv] = dict(action=action,
470 deptypes=[str(x) for x in priorities],
471 deps={})
David Jamesfcb70ef2011-02-02 16:02:30 -0800472
473 # We've built our list of deps, so we can add our package to the tree.
David James3f778802011-08-25 19:31:45 -0700474 if isinstance(node, Package) and node.root == root:
David Jamesfcb70ef2011-02-02 16:02:30 -0800475 deps_tree[str(node.cpv)] = dict(action=str(node.operation),
476 deps=deps)
477
David Jamesfcb70ef2011-02-02 16:02:30 -0800478 # Ask portage for its install plan, so that we can only throw out
David James386ccd12011-05-04 20:17:42 -0700479 # dependencies that portage throws out.
David Jamesfcb70ef2011-02-02 16:02:30 -0800480 deps_info = {}
481 for pkg in depgraph.altlist():
482 if isinstance(pkg, Package):
David James3f778802011-08-25 19:31:45 -0700483 assert pkg.root == root
David Jamesfcb70ef2011-02-02 16:02:30 -0800484 self.package_db[pkg.cpv] = pkg
485
David Jamesfcb70ef2011-02-02 16:02:30 -0800486 # Save off info about the package
David James386ccd12011-05-04 20:17:42 -0700487 deps_info[str(pkg.cpv)] = {"idx": len(deps_info)}
David Jamesfcb70ef2011-02-02 16:02:30 -0800488
489 seconds = time.time() - start
490 if "--quiet" not in emerge.opts:
491 print "Deps calculated in %dm%.1fs" % (seconds / 60, seconds % 60)
492
493 return deps_tree, deps_info
494
495 def PrintTree(self, deps, depth=""):
496 """Print the deps we have seen in the emerge output.
497
498 Args:
499 deps: Dependency tree structure.
500 depth: Allows printing the tree recursively, with indentation.
501 """
502 for entry in sorted(deps):
503 action = deps[entry]["action"]
504 print "%s %s (%s)" % (depth, entry, action)
505 self.PrintTree(deps[entry]["deps"], depth=depth + " ")
506
David James386ccd12011-05-04 20:17:42 -0700507 def GenDependencyGraph(self, deps_tree, deps_info):
David Jamesfcb70ef2011-02-02 16:02:30 -0800508 """Generate a doubly linked dependency graph.
509
510 Args:
511 deps_tree: Dependency tree structure.
512 deps_info: More details on the dependencies.
513 Returns:
514 Deps graph in the form of a dict of packages, with each package
515 specifying a "needs" list and "provides" list.
516 """
517 emerge = self.emerge
518 root = emerge.settings["ROOT"]
519
David Jamesfcb70ef2011-02-02 16:02:30 -0800520 # deps_map is the actual dependency graph.
521 #
522 # Each package specifies a "needs" list and a "provides" list. The "needs"
523 # list indicates which packages we depend on. The "provides" list
524 # indicates the reverse dependencies -- what packages need us.
525 #
526 # We also provide some other information in the dependency graph:
527 # - action: What we're planning on doing with this package. Generally,
528 # "merge", "nomerge", or "uninstall"
David Jamesfcb70ef2011-02-02 16:02:30 -0800529 deps_map = {}
530
531 def ReverseTree(packages):
532 """Convert tree to digraph.
533
534 Take the tree of package -> requirements and reverse it to a digraph of
535 buildable packages -> packages they unblock.
536 Args:
537 packages: Tree(s) of dependencies.
538 Returns:
539 Unsanitized digraph.
540 """
David James8c7e5e32011-06-28 11:26:03 -0700541 binpkg_phases = set(["setup", "preinst", "postinst"])
David James3f778802011-08-25 19:31:45 -0700542 needed_dep_types = set(["blocker", "buildtime", "runtime"])
David Jamesfcb70ef2011-02-02 16:02:30 -0800543 for pkg in packages:
544
545 # Create an entry for the package
546 action = packages[pkg]["action"]
David James8c7e5e32011-06-28 11:26:03 -0700547 default_pkg = {"needs": {}, "provides": set(), "action": action,
548 "nodeps": False, "binary": False}
David Jamesfcb70ef2011-02-02 16:02:30 -0800549 this_pkg = deps_map.setdefault(pkg, default_pkg)
550
David James8c7e5e32011-06-28 11:26:03 -0700551 if pkg in deps_info:
552 this_pkg["idx"] = deps_info[pkg]["idx"]
553
554 # If a package doesn't have any defined phases that might use the
555 # dependent packages (i.e. pkg_setup, pkg_preinst, or pkg_postinst),
556 # we can install this package before its deps are ready.
557 emerge_pkg = self.package_db.get(pkg)
558 if emerge_pkg and emerge_pkg.type_name == "binary":
559 this_pkg["binary"] = True
560 defined_phases = emerge_pkg.metadata.defined_phases
561 defined_binpkg_phases = binpkg_phases.intersection(defined_phases)
562 if not defined_binpkg_phases:
563 this_pkg["nodeps"] = True
564
David Jamesfcb70ef2011-02-02 16:02:30 -0800565 # Create entries for dependencies of this package first.
566 ReverseTree(packages[pkg]["deps"])
567
568 # Add dependencies to this package.
569 for dep, dep_item in packages[pkg]["deps"].iteritems():
David James8c7e5e32011-06-28 11:26:03 -0700570 # We only need to enforce strict ordering of dependencies if the
David James3f778802011-08-25 19:31:45 -0700571 # dependency is a blocker, or is a buildtime or runtime dependency.
572 # (I.e., ignored, optional, and runtime_post dependencies don't
573 # depend on ordering.)
David James8c7e5e32011-06-28 11:26:03 -0700574 dep_types = dep_item["deptypes"]
575 if needed_dep_types.intersection(dep_types):
576 deps_map[dep]["provides"].add(pkg)
577 this_pkg["needs"][dep] = "/".join(dep_types)
David Jamesfcb70ef2011-02-02 16:02:30 -0800578
David James3f778802011-08-25 19:31:45 -0700579 # If there's a blocker, Portage may need to move files from one
580 # package to another, which requires editing the CONTENTS files of
581 # both packages. To avoid race conditions while editing this file,
582 # the two packages must not be installed in parallel, so we can't
583 # safely ignore dependencies. See http://crosbug.com/19328
584 if "blocker" in dep_types:
585 this_pkg["nodeps"] = False
586
David Jamesfcb70ef2011-02-02 16:02:30 -0800587 def FindCycles():
588 """Find cycles in the dependency tree.
589
590 Returns:
591 A dict mapping cyclic packages to a dict of the deps that cause
592 cycles. For each dep that causes cycles, it returns an example
593 traversal of the graph that shows the cycle.
594 """
595
596 def FindCyclesAtNode(pkg, cycles, unresolved, resolved):
597 """Find cycles in cyclic dependencies starting at specified package.
598
599 Args:
600 pkg: Package identifier.
601 cycles: A dict mapping cyclic packages to a dict of the deps that
602 cause cycles. For each dep that causes cycles, it returns an
603 example traversal of the graph that shows the cycle.
604 unresolved: Nodes that have been visited but are not fully processed.
605 resolved: Nodes that have been visited and are fully processed.
606 """
607 pkg_cycles = cycles.get(pkg)
608 if pkg in resolved and not pkg_cycles:
609 # If we already looked at this package, and found no cyclic
610 # dependencies, we can stop now.
611 return
612 unresolved.append(pkg)
613 for dep in deps_map[pkg]["needs"]:
614 if dep in unresolved:
615 idx = unresolved.index(dep)
616 mycycle = unresolved[idx:] + [dep]
617 for i in range(len(mycycle) - 1):
618 pkg1, pkg2 = mycycle[i], mycycle[i+1]
619 cycles.setdefault(pkg1, {}).setdefault(pkg2, mycycle)
620 elif not pkg_cycles or dep not in pkg_cycles:
621 # Looks like we haven't seen this edge before.
622 FindCyclesAtNode(dep, cycles, unresolved, resolved)
623 unresolved.pop()
624 resolved.add(pkg)
625
626 cycles, unresolved, resolved = {}, [], set()
627 for pkg in deps_map:
628 FindCyclesAtNode(pkg, cycles, unresolved, resolved)
629 return cycles
630
David James386ccd12011-05-04 20:17:42 -0700631 def RemoveUnusedPackages():
David Jamesfcb70ef2011-02-02 16:02:30 -0800632 """Remove installed packages, propagating dependencies."""
David Jamesfcb70ef2011-02-02 16:02:30 -0800633 # Schedule packages that aren't on the install list for removal
634 rm_pkgs = set(deps_map.keys()) - set(deps_info.keys())
635
David Jamesfcb70ef2011-02-02 16:02:30 -0800636 # Remove the packages we don't want, simplifying the graph and making
637 # it easier for us to crack cycles.
638 for pkg in sorted(rm_pkgs):
639 this_pkg = deps_map[pkg]
640 needs = this_pkg["needs"]
641 provides = this_pkg["provides"]
642 for dep in needs:
643 dep_provides = deps_map[dep]["provides"]
644 dep_provides.update(provides)
645 dep_provides.discard(pkg)
646 dep_provides.discard(dep)
647 for target in provides:
648 target_needs = deps_map[target]["needs"]
649 target_needs.update(needs)
650 target_needs.pop(pkg, None)
651 target_needs.pop(target, None)
652 del deps_map[pkg]
653
654 def PrintCycleBreak(basedep, dep, mycycle):
655 """Print details about a cycle that we are planning on breaking.
656
657 We are breaking a cycle where dep needs basedep. mycycle is an
658 example cycle which contains dep -> basedep."""
659
David Jamesfcb70ef2011-02-02 16:02:30 -0800660 needs = deps_map[dep]["needs"]
661 depinfo = needs.get(basedep, "deleted")
David Jamesfcb70ef2011-02-02 16:02:30 -0800662
David James3f778802011-08-25 19:31:45 -0700663 # It's OK to swap install order for blockers, as long as the two
664 # packages aren't installed in parallel. If there is a cycle, then
665 # we know the packages depend on each other already, so we can drop the
666 # blocker safely without printing a warning.
667 if depinfo == "blocker":
668 return
669
David Jamesfcb70ef2011-02-02 16:02:30 -0800670 # Notify the user that we're breaking a cycle.
671 print "Breaking %s -> %s (%s)" % (dep, basedep, depinfo)
672
673 # Show cycle.
674 for i in range(len(mycycle) - 1):
675 pkg1, pkg2 = mycycle[i], mycycle[i+1]
676 needs = deps_map[pkg1]["needs"]
677 depinfo = needs.get(pkg2, "deleted")
678 if pkg1 == dep and pkg2 == basedep:
679 depinfo = depinfo + ", deleting"
680 print " %s -> %s (%s)" % (pkg1, pkg2, depinfo)
681
682 def SanitizeTree():
683 """Remove circular dependencies.
684
685 We prune all dependencies involved in cycles that go against the emerge
686 ordering. This has a nice property: we're guaranteed to merge
687 dependencies in the same order that portage does.
688
689 Because we don't treat any dependencies as "soft" unless they're killed
690 by a cycle, we pay attention to a larger number of dependencies when
691 merging. This hurts performance a bit, but helps reliability.
692 """
693 start = time.time()
694 cycles = FindCycles()
695 while cycles:
696 for dep, mycycles in cycles.iteritems():
697 for basedep, mycycle in mycycles.iteritems():
698 if deps_info[basedep]["idx"] >= deps_info[dep]["idx"]:
Matt Tennant08797302011-10-17 16:18:45 -0700699 if "--quiet" not in emerge.opts:
700 PrintCycleBreak(basedep, dep, mycycle)
David Jamesfcb70ef2011-02-02 16:02:30 -0800701 del deps_map[dep]["needs"][basedep]
702 deps_map[basedep]["provides"].remove(dep)
703 cycles = FindCycles()
704 seconds = time.time() - start
705 if "--quiet" not in emerge.opts and seconds >= 0.1:
706 print "Tree sanitized in %dm%.1fs" % (seconds / 60, seconds % 60)
707
David James8c7e5e32011-06-28 11:26:03 -0700708 def FindRecursiveProvides(pkg, seen):
709 """Find all nodes that require a particular package.
710
711 Assumes that graph is acyclic.
712
713 Args:
714 pkg: Package identifier.
715 seen: Nodes that have been visited so far.
716 """
717 if pkg in seen:
718 return
719 seen.add(pkg)
720 info = deps_map[pkg]
721 info["tprovides"] = info["provides"].copy()
722 for dep in info["provides"]:
723 FindRecursiveProvides(dep, seen)
724 info["tprovides"].update(deps_map[dep]["tprovides"])
725
David Jamesa22906f2011-05-04 19:53:26 -0700726 ReverseTree(deps_tree)
David Jamesa22906f2011-05-04 19:53:26 -0700727
David James386ccd12011-05-04 20:17:42 -0700728 # We need to remove unused packages so that we can use the dependency
729 # ordering of the install process to show us what cycles to crack.
730 RemoveUnusedPackages()
David Jamesfcb70ef2011-02-02 16:02:30 -0800731 SanitizeTree()
David James8c7e5e32011-06-28 11:26:03 -0700732 seen = set()
733 for pkg in deps_map:
734 FindRecursiveProvides(pkg, seen)
David Jamesfcb70ef2011-02-02 16:02:30 -0800735 return deps_map
736
737 def PrintInstallPlan(self, deps_map):
738 """Print an emerge-style install plan.
739
740 The install plan lists what packages we're installing, in order.
741 It's useful for understanding what parallel_emerge is doing.
742
743 Args:
744 deps_map: The dependency graph.
745 """
746
747 def InstallPlanAtNode(target, deps_map):
748 nodes = []
749 nodes.append(target)
750 for dep in deps_map[target]["provides"]:
751 del deps_map[dep]["needs"][target]
752 if not deps_map[dep]["needs"]:
753 nodes.extend(InstallPlanAtNode(dep, deps_map))
754 return nodes
755
756 deps_map = copy.deepcopy(deps_map)
757 install_plan = []
758 plan = set()
759 for target, info in deps_map.iteritems():
760 if not info["needs"] and target not in plan:
761 for item in InstallPlanAtNode(target, deps_map):
762 plan.add(item)
763 install_plan.append(self.package_db[item])
764
765 for pkg in plan:
766 del deps_map[pkg]
767
768 if deps_map:
769 print "Cyclic dependencies:", " ".join(deps_map)
770 PrintDepsMap(deps_map)
771 sys.exit(1)
772
773 self.emerge.depgraph.display(install_plan)
774
775
776def PrintDepsMap(deps_map):
777 """Print dependency graph, for each package list it's prerequisites."""
778 for i in sorted(deps_map):
779 print "%s: (%s) needs" % (i, deps_map[i]["action"])
780 needs = deps_map[i]["needs"]
781 for j in sorted(needs):
782 print " %s" % (j)
783 if not needs:
784 print " no dependencies"
785
786
787class EmergeJobState(object):
788 __slots__ = ["done", "filename", "last_notify_timestamp", "last_output_seek",
789 "last_output_timestamp", "pkgname", "retcode", "start_timestamp",
Brian Harring0be85c62012-03-17 19:52:12 -0700790 "target", "fetch_only"]
David Jamesfcb70ef2011-02-02 16:02:30 -0800791
792 def __init__(self, target, pkgname, done, filename, start_timestamp,
Brian Harring0be85c62012-03-17 19:52:12 -0700793 retcode=None, fetch_only=False):
David Jamesfcb70ef2011-02-02 16:02:30 -0800794
795 # The full name of the target we're building (e.g.
796 # chromeos-base/chromeos-0.0.1-r60)
797 self.target = target
798
799 # The short name of the target we're building (e.g. chromeos-0.0.1-r60)
800 self.pkgname = pkgname
801
802 # Whether the job is done. (True if the job is done; false otherwise.)
803 self.done = done
804
805 # The filename where output is currently stored.
806 self.filename = filename
807
808 # The timestamp of the last time we printed the name of the log file. We
809 # print this at the beginning of the job, so this starts at
810 # start_timestamp.
811 self.last_notify_timestamp = start_timestamp
812
813 # The location (in bytes) of the end of the last complete line we printed.
814 # This starts off at zero. We use this to jump to the right place when we
815 # print output from the same ebuild multiple times.
816 self.last_output_seek = 0
817
818 # The timestamp of the last time we printed output. Since we haven't
819 # printed output yet, this starts at zero.
820 self.last_output_timestamp = 0
821
822 # The return code of our job, if the job is actually finished.
823 self.retcode = retcode
824
Brian Harring0be85c62012-03-17 19:52:12 -0700825 # Was this just a fetch job?
826 self.fetch_only = fetch_only
827
David Jamesfcb70ef2011-02-02 16:02:30 -0800828 # The timestamp when our job started.
829 self.start_timestamp = start_timestamp
830
831
David James7358d032011-05-19 10:40:03 -0700832def KillHandler(signum, frame):
833 # Kill self and all subprocesses.
834 os.killpg(0, signal.SIGKILL)
835
David Jamesfcb70ef2011-02-02 16:02:30 -0800836def SetupWorkerSignals():
837 def ExitHandler(signum, frame):
David James7358d032011-05-19 10:40:03 -0700838 # Set KILLED flag.
839 KILLED.set()
David James13cead42011-05-18 16:22:01 -0700840
David James7358d032011-05-19 10:40:03 -0700841 # Remove our signal handlers so we don't get called recursively.
842 signal.signal(signal.SIGINT, KillHandler)
843 signal.signal(signal.SIGTERM, KillHandler)
David Jamesfcb70ef2011-02-02 16:02:30 -0800844
845 # Ensure that we exit quietly and cleanly, if possible, when we receive
846 # SIGTERM or SIGINT signals. By default, when the user hits CTRL-C, all
847 # of the child processes will print details about KeyboardInterrupt
848 # exceptions, which isn't very helpful.
849 signal.signal(signal.SIGINT, ExitHandler)
850 signal.signal(signal.SIGTERM, ExitHandler)
851
David James1ed3e252011-10-05 20:26:15 -0700852def EmergeProcess(scheduler, output):
853 """Merge a package in a subprocess.
854
855 Args:
856 scheduler: Scheduler object.
857 output: Temporary file to write output.
858
859 Returns:
860 The exit code returned by the subprocess.
861 """
862 pid = os.fork()
863 if pid == 0:
864 try:
865 # Sanity checks.
866 if sys.stdout.fileno() != 1: raise Exception("sys.stdout.fileno() != 1")
867 if sys.stderr.fileno() != 2: raise Exception("sys.stderr.fileno() != 2")
868
869 # - Redirect 1 (stdout) and 2 (stderr) at our temporary file.
870 # - Redirect 0 to point at sys.stdin. In this case, sys.stdin
871 # points at a file reading os.devnull, because multiprocessing mucks
872 # with sys.stdin.
873 # - Leave the sys.stdin and output filehandles alone.
874 fd_pipes = {0: sys.stdin.fileno(),
875 1: output.fileno(),
876 2: output.fileno(),
877 sys.stdin.fileno(): sys.stdin.fileno(),
878 output.fileno(): output.fileno()}
879 portage.process._setup_pipes(fd_pipes)
880
881 # Portage doesn't like when sys.stdin.fileno() != 0, so point sys.stdin
882 # at the filehandle we just created in _setup_pipes.
883 if sys.stdin.fileno() != 0:
884 sys.stdin = os.fdopen(0, "r")
885
886 # Actually do the merge.
887 retval = scheduler.merge()
888
889 # We catch all exceptions here (including SystemExit, KeyboardInterrupt,
890 # etc) so as to ensure that we don't confuse the multiprocessing module,
891 # which expects that all forked children exit with os._exit().
892 except:
893 traceback.print_exc(file=output)
894 retval = 1
895 sys.stdout.flush()
896 sys.stderr.flush()
897 output.flush()
898 os._exit(retval)
899 else:
900 # Return the exit code of the subprocess.
901 return os.waitpid(pid, 0)[1]
David Jamesfcb70ef2011-02-02 16:02:30 -0800902
Brian Harring0be85c62012-03-17 19:52:12 -0700903def EmergeWorker(task_queue, job_queue, emerge, package_db, fetch_only=False):
David Jamesfcb70ef2011-02-02 16:02:30 -0800904 """This worker emerges any packages given to it on the task_queue.
905
906 Args:
907 task_queue: The queue of tasks for this worker to do.
908 job_queue: The queue of results from the worker.
909 emerge: An EmergeData() object.
910 package_db: A dict, mapping package ids to portage Package objects.
Brian Harring0be85c62012-03-17 19:52:12 -0700911 fetch_only: A bool, indicating if we should just fetch the target.
David Jamesfcb70ef2011-02-02 16:02:30 -0800912
913 It expects package identifiers to be passed to it via task_queue. When
914 a task is started, it pushes the (target, filename) to the started_queue.
915 The output is stored in filename. When a merge starts or finishes, we push
916 EmergeJobState objects to the job_queue.
917 """
918
919 SetupWorkerSignals()
920 settings, trees, mtimedb = emerge.settings, emerge.trees, emerge.mtimedb
David Jamesdeebd692011-05-09 17:02:52 -0700921
922 # Disable flushing of caches to save on I/O.
David James7a1ea4b2011-10-13 15:06:41 -0700923 root = emerge.settings["ROOT"]
924 vardb = emerge.trees[root]["vartree"].dbapi
925 vardb._flush_cache_enabled = False
Brian Harring0be85c62012-03-17 19:52:12 -0700926 bindb = emerge.trees[root]["bintree"].dbapi
927 # Might be a set, might be a list, might be None; no clue, just use shallow
928 # copy to ensure we can roll it back.
929 original_remotepkgs = copy.copy(bindb.bintree._remotepkgs)
David Jamesdeebd692011-05-09 17:02:52 -0700930
David Jamesfcb70ef2011-02-02 16:02:30 -0800931 opts, spinner = emerge.opts, emerge.spinner
932 opts["--nodeps"] = True
Brian Harring0be85c62012-03-17 19:52:12 -0700933 if fetch_only:
934 opts["--fetchonly"] = True
935
David Jamesfcb70ef2011-02-02 16:02:30 -0800936 while True:
937 # Wait for a new item to show up on the queue. This is a blocking wait,
938 # so if there's nothing to do, we just sit here.
Brian Harring0be85c62012-03-17 19:52:12 -0700939 pkg_state = task_queue.get()
940 if pkg_state is None:
David Jamesfcb70ef2011-02-02 16:02:30 -0800941 # If target is None, this means that the main thread wants us to quit.
942 # The other workers need to exit too, so we'll push the message back on
943 # to the queue so they'll get it too.
Brian Harring0be85c62012-03-17 19:52:12 -0700944 task_queue.put(None)
David Jamesfcb70ef2011-02-02 16:02:30 -0800945 return
David James7358d032011-05-19 10:40:03 -0700946 if KILLED.is_set():
947 return
948
Brian Harring0be85c62012-03-17 19:52:12 -0700949 target = pkg_state.target
950
David Jamesfcb70ef2011-02-02 16:02:30 -0800951 db_pkg = package_db[target]
Brian Harring0be85c62012-03-17 19:52:12 -0700952
953 if db_pkg.type_name == "binary":
954 if not fetch_only and pkg_state.fetched_successfully:
955 # Ensure portage doesn't think our pkg is remote- else it'll force
956 # a redownload of it (even if the on-disk file is fine). In-memory
957 # caching basically, implemented dumbly.
958 bindb.bintree._remotepkgs = None
959 else:
960 bindb.bintree_remotepkgs = original_remotepkgs
961
David Jamesfcb70ef2011-02-02 16:02:30 -0800962 db_pkg.root_config = emerge.root_config
963 install_list = [db_pkg]
964 pkgname = db_pkg.pf
965 output = tempfile.NamedTemporaryFile(prefix=pkgname + "-", delete=False)
David James01b1e0f2012-06-07 17:18:05 -0700966 os.chmod(output.name, 644)
David Jamesfcb70ef2011-02-02 16:02:30 -0800967 start_timestamp = time.time()
Brian Harring0be85c62012-03-17 19:52:12 -0700968 job = EmergeJobState(target, pkgname, False, output.name, start_timestamp,
969 fetch_only=fetch_only)
David Jamesfcb70ef2011-02-02 16:02:30 -0800970 job_queue.put(job)
971 if "--pretend" in opts:
972 retcode = 0
973 else:
David Jamesfcb70ef2011-02-02 16:02:30 -0800974 try:
David James386ccd12011-05-04 20:17:42 -0700975 emerge.scheduler_graph.mergelist = install_list
976 scheduler = Scheduler(settings, trees, mtimedb, opts, spinner,
David Jamesbf1e3442011-05-28 07:44:20 -0700977 favorites=emerge.favorites, graph_config=emerge.scheduler_graph)
David Jamesace2e212011-07-13 11:47:39 -0700978
979 # Enable blocker handling even though we're in --nodeps mode. This
980 # allows us to unmerge the blocker after we've merged the replacement.
981 scheduler._opts_ignore_blockers = frozenset()
982
David James1ed3e252011-10-05 20:26:15 -0700983 retcode = EmergeProcess(scheduler, output)
David Jamesfcb70ef2011-02-02 16:02:30 -0800984 except Exception:
985 traceback.print_exc(file=output)
986 retcode = 1
David James1ed3e252011-10-05 20:26:15 -0700987 output.close()
David Jamesfcb70ef2011-02-02 16:02:30 -0800988
David James7358d032011-05-19 10:40:03 -0700989 if KILLED.is_set():
990 return
991
David Jamesfcb70ef2011-02-02 16:02:30 -0800992 job = EmergeJobState(target, pkgname, True, output.name, start_timestamp,
Brian Harring0be85c62012-03-17 19:52:12 -0700993 retcode, fetch_only=fetch_only)
David Jamesfcb70ef2011-02-02 16:02:30 -0800994 job_queue.put(job)
995
996
997class LinePrinter(object):
998 """Helper object to print a single line."""
999
1000 def __init__(self, line):
1001 self.line = line
1002
1003 def Print(self, seek_locations):
1004 print self.line
1005
1006
1007class JobPrinter(object):
1008 """Helper object to print output of a job."""
1009
1010 def __init__(self, job, unlink=False):
1011 """Print output of job.
1012
1013 If unlink is True, unlink the job output file when done."""
1014 self.current_time = time.time()
1015 self.job = job
1016 self.unlink = unlink
1017
1018 def Print(self, seek_locations):
1019
1020 job = self.job
1021
1022 # Calculate how long the job has been running.
1023 seconds = self.current_time - job.start_timestamp
1024
1025 # Note that we've printed out the job so far.
1026 job.last_output_timestamp = self.current_time
1027
1028 # Note that we're starting the job
1029 info = "job %s (%dm%.1fs)" % (job.pkgname, seconds / 60, seconds % 60)
1030 last_output_seek = seek_locations.get(job.filename, 0)
1031 if last_output_seek:
1032 print "=== Continue output for %s ===" % info
1033 else:
1034 print "=== Start output for %s ===" % info
1035
1036 # Print actual output from job
1037 f = codecs.open(job.filename, encoding='utf-8', errors='replace')
1038 f.seek(last_output_seek)
1039 prefix = job.pkgname + ":"
1040 for line in f:
1041
1042 # Save off our position in the file
1043 if line and line[-1] == "\n":
1044 last_output_seek = f.tell()
1045 line = line[:-1]
1046
1047 # Print our line
1048 print prefix, line.encode('utf-8', 'replace')
1049 f.close()
1050
1051 # Save our last spot in the file so that we don't print out the same
1052 # location twice.
1053 seek_locations[job.filename] = last_output_seek
1054
1055 # Note end of output section
1056 if job.done:
1057 print "=== Complete: %s ===" % info
1058 else:
1059 print "=== Still running: %s ===" % info
1060
1061 if self.unlink:
1062 os.unlink(job.filename)
1063
1064
1065def PrintWorker(queue):
1066 """A worker that prints stuff to the screen as requested."""
1067
1068 def ExitHandler(signum, frame):
David James7358d032011-05-19 10:40:03 -07001069 # Set KILLED flag.
1070 KILLED.set()
1071
David Jamesfcb70ef2011-02-02 16:02:30 -08001072 # Switch to default signal handlers so that we'll die after two signals.
David James7358d032011-05-19 10:40:03 -07001073 signal.signal(signal.SIGINT, KillHandler)
1074 signal.signal(signal.SIGTERM, KillHandler)
David Jamesfcb70ef2011-02-02 16:02:30 -08001075
1076 # Don't exit on the first SIGINT / SIGTERM, because the parent worker will
1077 # handle it and tell us when we need to exit.
1078 signal.signal(signal.SIGINT, ExitHandler)
1079 signal.signal(signal.SIGTERM, ExitHandler)
1080
1081 # seek_locations is a map indicating the position we are at in each file.
1082 # It starts off empty, but is set by the various Print jobs as we go along
1083 # to indicate where we left off in each file.
1084 seek_locations = {}
1085 while True:
1086 try:
1087 job = queue.get()
1088 if job:
1089 job.Print(seek_locations)
David Jamesbccf8eb2011-07-27 14:06:06 -07001090 sys.stdout.flush()
David Jamesfcb70ef2011-02-02 16:02:30 -08001091 else:
1092 break
1093 except IOError as ex:
1094 if ex.errno == errno.EINTR:
1095 # Looks like we received a signal. Keep printing.
1096 continue
1097 raise
1098
Brian Harring867e2362012-03-17 04:05:17 -07001099
Brian Harring0be85c62012-03-17 19:52:12 -07001100class TargetState(object):
Brian Harring867e2362012-03-17 04:05:17 -07001101
Brian Harring0be85c62012-03-17 19:52:12 -07001102 __slots__ = ("target", "info", "score", "prefetched", "fetched_successfully")
Brian Harring867e2362012-03-17 04:05:17 -07001103
Brian Harring0be85c62012-03-17 19:52:12 -07001104 def __init__(self, target, info, fetched=False):
Brian Harring867e2362012-03-17 04:05:17 -07001105 self.target, self.info = target, info
Brian Harring0be85c62012-03-17 19:52:12 -07001106 self.fetched_successfully = False
1107 self.prefetched = False
Brian Harring867e2362012-03-17 04:05:17 -07001108 self.update_score()
1109
1110 def __cmp__(self, other):
1111 return cmp(self.score, other.score)
1112
1113 def update_score(self):
1114 self.score = (
1115 -len(self.info["tprovides"]),
Brian Harring0be85c62012-03-17 19:52:12 -07001116 len(self.info["needs"]),
Brian Harring11c5eeb2012-03-18 11:02:39 -07001117 not self.info["binary"],
Brian Harring867e2362012-03-17 04:05:17 -07001118 -len(self.info["provides"]),
1119 self.info["idx"],
1120 self.target,
1121 )
1122
1123
1124class ScoredHeap(object):
1125
Brian Harring0be85c62012-03-17 19:52:12 -07001126 __slots__ = ("heap", "_heap_set")
1127
Brian Harring867e2362012-03-17 04:05:17 -07001128 def __init__(self, initial=()):
Brian Harring0be85c62012-03-17 19:52:12 -07001129 self.heap = list()
1130 self._heap_set = set()
1131 if initial:
1132 self.multi_put(initial)
Brian Harring867e2362012-03-17 04:05:17 -07001133
1134 def get(self):
Brian Harring0be85c62012-03-17 19:52:12 -07001135 item = heapq.heappop(self.heap)
1136 self._heap_set.remove(item.target)
1137 return item
Brian Harring867e2362012-03-17 04:05:17 -07001138
Brian Harring0be85c62012-03-17 19:52:12 -07001139 def put(self, item):
1140 if not isinstance(item, TargetState):
1141 raise ValueError("Item %r isn't a TargetState" % (item,))
1142 heapq.heappush(self.heap, item)
1143 self._heap_set.add(item.target)
Brian Harring867e2362012-03-17 04:05:17 -07001144
Brian Harring0be85c62012-03-17 19:52:12 -07001145 def multi_put(self, sequence):
1146 sequence = list(sequence)
1147 self.heap.extend(sequence)
1148 self._heap_set.update(x.target for x in sequence)
Brian Harring867e2362012-03-17 04:05:17 -07001149 self.sort()
1150
David James5c9996d2012-03-24 10:50:46 -07001151 def sort(self):
1152 heapq.heapify(self.heap)
1153
Brian Harring0be85c62012-03-17 19:52:12 -07001154 def __contains__(self, target):
1155 return target in self._heap_set
1156
1157 def __nonzero__(self):
1158 return bool(self.heap)
1159
Brian Harring867e2362012-03-17 04:05:17 -07001160 def __len__(self):
1161 return len(self.heap)
1162
1163
David Jamesfcb70ef2011-02-02 16:02:30 -08001164class EmergeQueue(object):
1165 """Class to schedule emerge jobs according to a dependency graph."""
1166
1167 def __init__(self, deps_map, emerge, package_db, show_output):
1168 # Store the dependency graph.
1169 self._deps_map = deps_map
Brian Harring0be85c62012-03-17 19:52:12 -07001170 self._state_map = {}
David Jamesfcb70ef2011-02-02 16:02:30 -08001171 # Initialize the running queue to empty
Brian Harring0be85c62012-03-17 19:52:12 -07001172 self._build_jobs = {}
1173 self._build_ready = ScoredHeap()
1174 self._fetch_jobs = {}
1175 self._fetch_ready = ScoredHeap()
David Jamesfcb70ef2011-02-02 16:02:30 -08001176 # List of total package installs represented in deps_map.
1177 install_jobs = [x for x in deps_map if deps_map[x]["action"] == "merge"]
1178 self._total_jobs = len(install_jobs)
1179 self._show_output = show_output
1180
1181 if "--pretend" in emerge.opts:
1182 print "Skipping merge because of --pretend mode."
1183 sys.exit(0)
1184
David James7358d032011-05-19 10:40:03 -07001185 # Set a process group so we can easily terminate all children.
1186 os.setsid()
1187
David Jamesfcb70ef2011-02-02 16:02:30 -08001188 # Setup scheduler graph object. This is used by the child processes
1189 # to help schedule jobs.
1190 emerge.scheduler_graph = emerge.depgraph.schedulerGraph()
1191
1192 # Calculate how many jobs we can run in parallel. We don't want to pass
1193 # the --jobs flag over to emerge itself, because that'll tell emerge to
1194 # hide its output, and said output is quite useful for debugging hung
1195 # jobs.
1196 procs = min(self._total_jobs,
1197 emerge.opts.pop("--jobs", multiprocessing.cpu_count()))
Brian Harring0be85c62012-03-17 19:52:12 -07001198 self._build_procs = procs
1199 self._fetch_procs = procs
David James8c7e5e32011-06-28 11:26:03 -07001200 self._load_avg = emerge.opts.pop("--load-average", None)
David Jamesfcb70ef2011-02-02 16:02:30 -08001201 self._job_queue = multiprocessing.Queue()
1202 self._print_queue = multiprocessing.Queue()
Brian Harring0be85c62012-03-17 19:52:12 -07001203
1204 self._fetch_queue = multiprocessing.Queue()
1205 args = (self._fetch_queue, self._job_queue, emerge, package_db, True)
1206 self._fetch_pool = multiprocessing.Pool(self._fetch_procs, EmergeWorker,
1207 args)
1208
1209 self._build_queue = multiprocessing.Queue()
1210 args = (self._build_queue, self._job_queue, emerge, package_db)
1211 self._build_pool = multiprocessing.Pool(self._build_procs, EmergeWorker,
1212 args)
1213
David Jamesfcb70ef2011-02-02 16:02:30 -08001214 self._print_worker = multiprocessing.Process(target=PrintWorker,
1215 args=[self._print_queue])
1216 self._print_worker.start()
1217
1218 # Initialize the failed queue to empty.
1219 self._retry_queue = []
1220 self._failed = set()
1221
David Jamesfcb70ef2011-02-02 16:02:30 -08001222 # Setup an exit handler so that we print nice messages if we are
1223 # terminated.
1224 self._SetupExitHandler()
1225
1226 # Schedule our jobs.
Brian Harring0be85c62012-03-17 19:52:12 -07001227 self._state_map.update(
1228 (pkg, TargetState(pkg, data)) for pkg, data in deps_map.iteritems())
1229 self._fetch_ready.multi_put(self._state_map.itervalues())
David Jamesfcb70ef2011-02-02 16:02:30 -08001230
1231 def _SetupExitHandler(self):
1232
1233 def ExitHandler(signum, frame):
David James7358d032011-05-19 10:40:03 -07001234 # Set KILLED flag.
1235 KILLED.set()
David Jamesfcb70ef2011-02-02 16:02:30 -08001236
1237 # Kill our signal handlers so we don't get called recursively
David James7358d032011-05-19 10:40:03 -07001238 signal.signal(signal.SIGINT, KillHandler)
1239 signal.signal(signal.SIGTERM, KillHandler)
David Jamesfcb70ef2011-02-02 16:02:30 -08001240
1241 # Print our current job status
Brian Harring0be85c62012-03-17 19:52:12 -07001242 for job in self._build_jobs.itervalues():
David Jamesfcb70ef2011-02-02 16:02:30 -08001243 if job:
1244 self._print_queue.put(JobPrinter(job, unlink=True))
1245
1246 # Notify the user that we are exiting
1247 self._Print("Exiting on signal %s" % signum)
David James7358d032011-05-19 10:40:03 -07001248 self._print_queue.put(None)
1249 self._print_worker.join()
David Jamesfcb70ef2011-02-02 16:02:30 -08001250
1251 # Kill child threads, then exit.
David James7358d032011-05-19 10:40:03 -07001252 os.killpg(0, signal.SIGKILL)
David Jamesfcb70ef2011-02-02 16:02:30 -08001253 sys.exit(1)
1254
1255 # Print out job status when we are killed
1256 signal.signal(signal.SIGINT, ExitHandler)
1257 signal.signal(signal.SIGTERM, ExitHandler)
1258
Brian Harring0be85c62012-03-17 19:52:12 -07001259 def _Schedule(self, pkg_state):
David Jamesfcb70ef2011-02-02 16:02:30 -08001260 # We maintain a tree of all deps, if this doesn't need
David James8c7e5e32011-06-28 11:26:03 -07001261 # to be installed just free up its children and continue.
David Jamesfcb70ef2011-02-02 16:02:30 -08001262 # It is possible to reinstall deps of deps, without reinstalling
1263 # first level deps, like so:
1264 # chromeos (merge) -> eselect (nomerge) -> python (merge)
Brian Harring0be85c62012-03-17 19:52:12 -07001265 this_pkg = pkg_state.info
1266 target = pkg_state.target
1267 if pkg_state.info is not None:
1268 if this_pkg["action"] == "nomerge":
1269 self._Finish(target)
1270 elif target not in self._build_jobs:
1271 # Kick off the build if it's marked to be built.
1272 self._build_jobs[target] = None
1273 self._build_queue.put(pkg_state)
1274 return True
David Jamesfcb70ef2011-02-02 16:02:30 -08001275
David James8c7e5e32011-06-28 11:26:03 -07001276 def _ScheduleLoop(self):
1277 # If the current load exceeds our desired load average, don't schedule
1278 # more than one job.
1279 if self._load_avg and os.getloadavg()[0] > self._load_avg:
1280 needed_jobs = 1
1281 else:
Brian Harring0be85c62012-03-17 19:52:12 -07001282 needed_jobs = self._build_procs
David James8c7e5e32011-06-28 11:26:03 -07001283
1284 # Schedule more jobs.
Brian Harring0be85c62012-03-17 19:52:12 -07001285 while self._build_ready and len(self._build_jobs) < needed_jobs:
1286 state = self._build_ready.get()
1287 if state.target not in self._failed:
1288 self._Schedule(state)
David Jamesfcb70ef2011-02-02 16:02:30 -08001289
1290 def _Print(self, line):
1291 """Print a single line."""
1292 self._print_queue.put(LinePrinter(line))
1293
1294 def _Status(self):
1295 """Print status."""
1296 current_time = time.time()
1297 no_output = True
1298
1299 # Print interim output every minute if --show-output is used. Otherwise,
1300 # print notifications about running packages every 2 minutes, and print
1301 # full output for jobs that have been running for 60 minutes or more.
1302 if self._show_output:
1303 interval = 60
1304 notify_interval = 0
1305 else:
1306 interval = 60 * 60
1307 notify_interval = 60 * 2
Brian Harring0be85c62012-03-17 19:52:12 -07001308 for target, job in self._build_jobs.iteritems():
David Jamesfcb70ef2011-02-02 16:02:30 -08001309 if job:
1310 last_timestamp = max(job.start_timestamp, job.last_output_timestamp)
1311 if last_timestamp + interval < current_time:
1312 self._print_queue.put(JobPrinter(job))
1313 job.last_output_timestamp = current_time
1314 no_output = False
1315 elif (notify_interval and
1316 job.last_notify_timestamp + notify_interval < current_time):
1317 job_seconds = current_time - job.start_timestamp
1318 args = (job.pkgname, job_seconds / 60, job_seconds % 60, job.filename)
1319 info = "Still building %s (%dm%.1fs). Logs in %s" % args
1320 job.last_notify_timestamp = current_time
1321 self._Print(info)
1322 no_output = False
1323
1324 # If we haven't printed any messages yet, print a general status message
1325 # here.
1326 if no_output:
1327 seconds = current_time - GLOBAL_START
Brian Harring0be85c62012-03-17 19:52:12 -07001328 fjobs, fready = len(self._fetch_jobs), len(self._fetch_ready)
1329 bjobs, bready = len(self._build_jobs), len(self._build_ready)
1330 retries = len(self._retry_queue)
1331 pending = max(0, len(self._deps_map) - fjobs - bjobs)
1332 line = "Pending %s/%s, " % (pending, self._total_jobs)
1333 if fjobs or fready:
1334 line += "Fetching %s/%s, " % (fjobs, fready + fjobs)
1335 if bjobs or bready or retries:
1336 line += "Building %s/%s, " % (bjobs, bready + bjobs)
1337 if retries:
1338 line += "Retrying %s, " % (retries,)
David James8c7e5e32011-06-28 11:26:03 -07001339 load = " ".join(str(x) for x in os.getloadavg())
Brian Harring0be85c62012-03-17 19:52:12 -07001340 line += ("[Time %dm%.1fs Load %s]" % (seconds/60, seconds %60, load))
1341 self._Print(line)
David Jamesfcb70ef2011-02-02 16:02:30 -08001342
1343 def _Finish(self, target):
David James8c7e5e32011-06-28 11:26:03 -07001344 """Mark a target as completed and unblock dependencies."""
1345 this_pkg = self._deps_map[target]
1346 if this_pkg["needs"] and this_pkg["nodeps"]:
1347 # We got installed, but our deps have not been installed yet. Dependent
1348 # packages should only be installed when our needs have been fully met.
1349 this_pkg["action"] = "nomerge"
1350 else:
1351 finish = []
1352 for dep in this_pkg["provides"]:
1353 dep_pkg = self._deps_map[dep]
Brian Harring0be85c62012-03-17 19:52:12 -07001354 state = self._state_map[dep]
David James8c7e5e32011-06-28 11:26:03 -07001355 del dep_pkg["needs"][target]
Brian Harring0be85c62012-03-17 19:52:12 -07001356 state.update_score()
1357 if not state.prefetched:
1358 if dep in self._fetch_ready:
1359 # If it's not currently being fetched, update the prioritization
1360 self._fetch_ready.sort()
1361 elif not dep_pkg["needs"]:
David James8c7e5e32011-06-28 11:26:03 -07001362 if dep_pkg["nodeps"] and dep_pkg["action"] == "nomerge":
1363 self._Finish(dep)
1364 else:
Brian Harring0be85c62012-03-17 19:52:12 -07001365 self._build_ready.put(self._state_map[dep])
David James8c7e5e32011-06-28 11:26:03 -07001366 self._deps_map.pop(target)
David Jamesfcb70ef2011-02-02 16:02:30 -08001367
1368 def _Retry(self):
David James8c7e5e32011-06-28 11:26:03 -07001369 while self._retry_queue:
Brian Harring0be85c62012-03-17 19:52:12 -07001370 state = self._retry_queue.pop(0)
1371 if self._Schedule(state):
1372 self._Print("Retrying emerge of %s." % state.target)
David James8c7e5e32011-06-28 11:26:03 -07001373 break
David Jamesfcb70ef2011-02-02 16:02:30 -08001374
Brian Harringa43f5952012-04-12 01:19:34 -07001375 def _Shutdown(self):
David Jamesfcb70ef2011-02-02 16:02:30 -08001376 # Tell emerge workers to exit. They all exit when 'None' is pushed
1377 # to the queue.
Brian Harring0be85c62012-03-17 19:52:12 -07001378
Brian Harringa43f5952012-04-12 01:19:34 -07001379 # Shutdown the workers first; then jobs (which is how they feed things back)
1380 # then finally the print queue.
Brian Harring0be85c62012-03-17 19:52:12 -07001381
Brian Harringa43f5952012-04-12 01:19:34 -07001382 def _stop(queue, pool):
1383 if pool is None:
1384 return
1385 try:
1386 queue.put(None)
1387 pool.close()
1388 pool.join()
1389 finally:
1390 pool.terminate()
Brian Harring0be85c62012-03-17 19:52:12 -07001391
Brian Harringa43f5952012-04-12 01:19:34 -07001392 _stop(self._fetch_queue, self._fetch_pool)
1393 self._fetch_queue = self._fetch_pool = None
Brian Harring0be85c62012-03-17 19:52:12 -07001394
Brian Harringa43f5952012-04-12 01:19:34 -07001395 _stop(self._build_queue, self._build_pool)
1396 self._build_queue = self._build_pool = None
1397
1398 if self._job_queue is not None:
1399 self._job_queue.close()
1400 self._job_queue = None
David Jamesfcb70ef2011-02-02 16:02:30 -08001401
1402 # Now that our workers are finished, we can kill the print queue.
Brian Harringa43f5952012-04-12 01:19:34 -07001403 if self._print_worker is not None:
1404 try:
1405 self._print_queue.put(None)
1406 self._print_queue.close()
1407 self._print_worker.join()
1408 finally:
1409 self._print_worker.terminate()
1410 self._print_queue = self._print_worker = None
David Jamesfcb70ef2011-02-02 16:02:30 -08001411
1412 def Run(self):
1413 """Run through the scheduled ebuilds.
1414
1415 Keep running so long as we have uninstalled packages in the
1416 dependency graph to merge.
1417 """
Brian Harringa43f5952012-04-12 01:19:34 -07001418 if not self._deps_map:
1419 return
1420
Brian Harring0be85c62012-03-17 19:52:12 -07001421 # Start the fetchers.
1422 for _ in xrange(min(self._fetch_procs, len(self._fetch_ready))):
1423 state = self._fetch_ready.get()
1424 self._fetch_jobs[state.target] = None
1425 self._fetch_queue.put(state)
1426
1427 # Print an update, then get going.
1428 self._Status()
1429
David Jamese703d0f2012-01-12 16:27:45 -08001430 retried = set()
David Jamesfcb70ef2011-02-02 16:02:30 -08001431 while self._deps_map:
1432 # Check here that we are actually waiting for something.
Brian Harring0be85c62012-03-17 19:52:12 -07001433 if (self._build_queue.empty() and
David Jamesfcb70ef2011-02-02 16:02:30 -08001434 self._job_queue.empty() and
Brian Harring0be85c62012-03-17 19:52:12 -07001435 not self._fetch_jobs and
1436 not self._fetch_ready and
1437 not self._build_jobs and
1438 not self._build_ready and
David Jamesfcb70ef2011-02-02 16:02:30 -08001439 self._deps_map):
1440 # If we have failed on a package, retry it now.
1441 if self._retry_queue:
1442 self._Retry()
1443 else:
David Jamesfcb70ef2011-02-02 16:02:30 -08001444 # Tell the user why we're exiting.
1445 if self._failed:
1446 print "Packages failed: %s" % " ,".join(self._failed)
David James0eae23e2012-07-03 15:04:25 -07001447 status_file = os.environ.get("PARALLEL_EMERGE_STATUS_FILE")
1448 if status_file:
1449 failed_pkgs = set(portage.cpv_getkey(x) for x in self._failed)
1450 with open(status_file, "a") as f:
1451 f.write("%s\n" % " ".join(failed_pkgs))
David Jamesfcb70ef2011-02-02 16:02:30 -08001452 else:
1453 print "Deadlock! Circular dependencies!"
1454 sys.exit(1)
1455
Brian Harring706747c2012-03-16 03:04:31 -07001456 for i in range(12):
David Jamesa74289a2011-08-12 10:41:24 -07001457 try:
1458 job = self._job_queue.get(timeout=5)
1459 break
1460 except Queue.Empty:
1461 # Check if any more jobs can be scheduled.
1462 self._ScheduleLoop()
1463 else:
Brian Harring706747c2012-03-16 03:04:31 -07001464 # Print an update every 60 seconds.
David Jamesfcb70ef2011-02-02 16:02:30 -08001465 self._Status()
1466 continue
1467
1468 target = job.target
1469
Brian Harring0be85c62012-03-17 19:52:12 -07001470 if job.fetch_only:
1471 if not job.done:
1472 self._fetch_jobs[job.target] = job
1473 else:
1474 state = self._state_map[job.target]
1475 state.prefetched = True
1476 state.fetched_successfully = (job.retcode == 0)
1477 del self._fetch_jobs[job.target]
1478 self._Print("Fetched %s in %2.2fs"
1479 % (target, time.time() - job.start_timestamp))
1480
1481 if self._show_output or job.retcode != 0:
1482 self._print_queue.put(JobPrinter(job, unlink=True))
1483 else:
1484 os.unlink(job.filename)
1485 # Failure or not, let build work with it next.
1486 if not self._deps_map[job.target]["needs"]:
1487 self._build_ready.put(state)
1488 self._ScheduleLoop()
1489
1490 if self._fetch_ready:
1491 state = self._fetch_ready.get()
1492 self._fetch_queue.put(state)
1493 self._fetch_jobs[state.target] = None
1494 else:
1495 # Minor optimization; shut down fetchers early since we know
1496 # the queue is empty.
1497 self._fetch_queue.put(None)
1498 continue
1499
David Jamesfcb70ef2011-02-02 16:02:30 -08001500 if not job.done:
Brian Harring0be85c62012-03-17 19:52:12 -07001501 self._build_jobs[target] = job
David Jamesfcb70ef2011-02-02 16:02:30 -08001502 self._Print("Started %s (logged in %s)" % (target, job.filename))
1503 continue
1504
1505 # Print output of job
1506 if self._show_output or job.retcode != 0:
1507 self._print_queue.put(JobPrinter(job, unlink=True))
1508 else:
1509 os.unlink(job.filename)
Brian Harring0be85c62012-03-17 19:52:12 -07001510 del self._build_jobs[target]
David Jamesfcb70ef2011-02-02 16:02:30 -08001511
1512 seconds = time.time() - job.start_timestamp
1513 details = "%s (in %dm%.1fs)" % (target, seconds / 60, seconds % 60)
David James32420cc2011-08-25 21:32:46 -07001514 previously_failed = target in self._failed
David Jamesfcb70ef2011-02-02 16:02:30 -08001515
1516 # Complain if necessary.
1517 if job.retcode != 0:
1518 # Handle job failure.
David James32420cc2011-08-25 21:32:46 -07001519 if previously_failed:
David Jamesfcb70ef2011-02-02 16:02:30 -08001520 # If this job has failed previously, give up.
1521 self._Print("Failed %s. Your build has failed." % details)
1522 else:
1523 # Queue up this build to try again after a long while.
David Jamese703d0f2012-01-12 16:27:45 -08001524 retried.add(target)
Brian Harring0be85c62012-03-17 19:52:12 -07001525 self._retry_queue.append(self._state_map[target])
David Jamesfcb70ef2011-02-02 16:02:30 -08001526 self._failed.add(target)
1527 self._Print("Failed %s, retrying later." % details)
1528 else:
David James32420cc2011-08-25 21:32:46 -07001529 if previously_failed:
1530 # Remove target from list of failed packages.
1531 self._failed.remove(target)
1532
1533 self._Print("Completed %s" % details)
1534
1535 # Mark as completed and unblock waiting ebuilds.
1536 self._Finish(target)
1537
1538 if previously_failed and self._retry_queue:
David Jamesfcb70ef2011-02-02 16:02:30 -08001539 # If we have successfully retried a failed package, and there
1540 # are more failed packages, try the next one. We will only have
1541 # one retrying package actively running at a time.
1542 self._Retry()
1543
David Jamesfcb70ef2011-02-02 16:02:30 -08001544
David James8c7e5e32011-06-28 11:26:03 -07001545 # Schedule pending jobs and print an update.
1546 self._ScheduleLoop()
1547 self._Status()
David Jamesfcb70ef2011-02-02 16:02:30 -08001548
David Jamese703d0f2012-01-12 16:27:45 -08001549 # If packages were retried, output a warning.
1550 if retried:
1551 self._Print("")
1552 self._Print("WARNING: The following packages failed the first time,")
1553 self._Print("but succeeded upon retry. This might indicate incorrect")
1554 self._Print("dependencies.")
1555 for pkg in retried:
1556 self._Print(" %s" % pkg)
1557 self._Print("@@@STEP_WARNINGS@@@")
1558 self._Print("")
1559
David Jamesfcb70ef2011-02-02 16:02:30 -08001560 # Tell child threads to exit.
1561 self._Print("Merge complete")
David Jamesfcb70ef2011-02-02 16:02:30 -08001562
1563
Brian Harring30675052012-02-29 12:18:22 -08001564def main(argv):
Brian Harring8294d652012-05-23 02:20:52 -07001565 try:
1566 return real_main(argv)
1567 finally:
1568 # Work around multiprocessing sucking and not cleaning up after itself.
1569 # http://bugs.python.org/issue4106;
1570 # Step one; ensure GC is ran *prior* to the VM starting shutdown.
1571 gc.collect()
1572 # Step two; go looking for those threads and try to manually reap
1573 # them if we can.
1574 for x in threading.enumerate():
1575 # Filter on the name, and ident; if ident is None, the thread
1576 # wasn't started.
1577 if x.name == 'QueueFeederThread' and x.ident is not None:
1578 x.join(1)
David Jamesfcb70ef2011-02-02 16:02:30 -08001579
Brian Harring8294d652012-05-23 02:20:52 -07001580
1581def real_main(argv):
Brian Harring30675052012-02-29 12:18:22 -08001582 parallel_emerge_args = argv[:]
David Jamesfcb70ef2011-02-02 16:02:30 -08001583 deps = DepGraphGenerator()
Brian Harring30675052012-02-29 12:18:22 -08001584 deps.Initialize(parallel_emerge_args)
David Jamesfcb70ef2011-02-02 16:02:30 -08001585 emerge = deps.emerge
1586
1587 if emerge.action is not None:
Brian Harring30675052012-02-29 12:18:22 -08001588 argv = deps.ParseParallelEmergeArgs(argv)
Brian Harring8294d652012-05-23 02:20:52 -07001589 return emerge_main(argv)
David Jamesfcb70ef2011-02-02 16:02:30 -08001590 elif not emerge.cmdline_packages:
1591 Usage()
Brian Harring8294d652012-05-23 02:20:52 -07001592 return 1
David Jamesfcb70ef2011-02-02 16:02:30 -08001593
1594 # Unless we're in pretend mode, there's not much point running without
1595 # root access. We need to be able to install packages.
1596 #
1597 # NOTE: Even if you're running --pretend, it's a good idea to run
1598 # parallel_emerge with root access so that portage can write to the
1599 # dependency cache. This is important for performance.
1600 if "--pretend" not in emerge.opts and portage.secpass < 2:
1601 print "parallel_emerge: superuser access is required."
Brian Harring8294d652012-05-23 02:20:52 -07001602 return 1
David Jamesfcb70ef2011-02-02 16:02:30 -08001603
1604 if "--quiet" not in emerge.opts:
1605 cmdline_packages = " ".join(emerge.cmdline_packages)
David Jamesfcb70ef2011-02-02 16:02:30 -08001606 print "Starting fast-emerge."
1607 print " Building package %s on %s" % (cmdline_packages,
1608 deps.board or "root")
David Jamesfcb70ef2011-02-02 16:02:30 -08001609
David James386ccd12011-05-04 20:17:42 -07001610 deps_tree, deps_info = deps.GenDependencyTree()
David Jamesfcb70ef2011-02-02 16:02:30 -08001611
1612 # You want me to be verbose? I'll give you two trees! Twice as much value.
1613 if "--tree" in emerge.opts and "--verbose" in emerge.opts:
1614 deps.PrintTree(deps_tree)
1615
David James386ccd12011-05-04 20:17:42 -07001616 deps_graph = deps.GenDependencyGraph(deps_tree, deps_info)
David Jamesfcb70ef2011-02-02 16:02:30 -08001617
1618 # OK, time to print out our progress so far.
1619 deps.PrintInstallPlan(deps_graph)
1620 if "--tree" in emerge.opts:
1621 PrintDepsMap(deps_graph)
1622
1623 # Are we upgrading portage? If so, and there are more packages to merge,
1624 # schedule a restart of parallel_emerge to merge the rest. This ensures that
1625 # we pick up all updates to portage settings before merging any more
1626 # packages.
1627 portage_upgrade = False
1628 root = emerge.settings["ROOT"]
1629 final_db = emerge.depgraph._dynamic_config.mydbapi[root]
1630 if root == "/":
1631 for db_pkg in final_db.match_pkgs("sys-apps/portage"):
1632 portage_pkg = deps_graph.get(db_pkg.cpv)
1633 if portage_pkg and len(deps_graph) > 1:
1634 portage_pkg["needs"].clear()
1635 portage_pkg["provides"].clear()
1636 deps_graph = { str(db_pkg.cpv): portage_pkg }
1637 portage_upgrade = True
1638 if "--quiet" not in emerge.opts:
1639 print "Upgrading portage first, then restarting..."
1640
1641 # Run the queued emerges.
1642 scheduler = EmergeQueue(deps_graph, emerge, deps.package_db, deps.show_output)
Brian Harringa43f5952012-04-12 01:19:34 -07001643 try:
1644 scheduler.Run()
1645 finally:
1646 scheduler._Shutdown()
David James97ce8902011-08-16 09:51:05 -07001647 scheduler = None
David Jamesfcb70ef2011-02-02 16:02:30 -08001648
David Jamesfcb70ef2011-02-02 16:02:30 -08001649 # If we already upgraded portage, we don't need to do so again. But we do
1650 # need to upgrade the rest of the packages. So we'll go ahead and do that.
David Jamesebc3ae02011-05-21 20:46:10 -07001651 #
1652 # In order to grant the child permission to run setsid, we need to run sudo
1653 # again. We preserve SUDO_USER here in case an ebuild depends on it.
David Jamesfcb70ef2011-02-02 16:02:30 -08001654 if portage_upgrade:
Brian Harring30675052012-02-29 12:18:22 -08001655 args = ["sudo", "-E", "SUDO_USER=%s" % os.environ.get("SUDO_USER", "")]
Brian Harringef3e9832012-03-02 04:43:05 -08001656 args += [os.path.abspath(sys.argv[0])] + parallel_emerge_args
Brian Harring30675052012-02-29 12:18:22 -08001657 args += ["--exclude=sys-apps/portage"]
David Jamesebc3ae02011-05-21 20:46:10 -07001658 os.execvp("sudo", args)
David Jamesfcb70ef2011-02-02 16:02:30 -08001659
Mike Frysingerd20a6e22012-10-04 19:01:10 -04001660 clean_logs(emerge.settings)
1661
David Jamesfcb70ef2011-02-02 16:02:30 -08001662 print "Done"
Brian Harring8294d652012-05-23 02:20:52 -07001663 return 0