blob: 5e6f6eaf0fb1c5725e01f078a9d81808adec9ccd [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.
David James321490a2012-12-17 12:05:56 -080053# pylint: disable=W0212
David Jamesfcb70ef2011-02-02 16:02:30 -080054from _emerge.actions import adjust_configs
55from _emerge.actions import load_emerge_config
56from _emerge.create_depgraph_params import create_depgraph_params
David James386ccd12011-05-04 20:17:42 -070057from _emerge.depgraph import backtrack_depgraph
Mike Frysinger901eaad2012-10-10 18:18:03 -040058try:
59 from _emerge.main import clean_logs
60except ImportError:
61 # Older portage versions did not provide clean_logs, so stub it.
62 # We need this if running in an older chroot that hasn't yet upgraded
63 # the portage version.
64 clean_logs = lambda x: None
David Jamesfcb70ef2011-02-02 16:02:30 -080065from _emerge.main import emerge_main
66from _emerge.main import parse_opts
67from _emerge.Package import Package
68from _emerge.Scheduler import Scheduler
David Jamesfcb70ef2011-02-02 16:02:30 -080069from _emerge.stdout_spinner import stdout_spinner
David James386ccd12011-05-04 20:17:42 -070070from portage._global_updates import _global_updates
David Jamesfcb70ef2011-02-02 16:02:30 -080071import portage
72import portage.debug
David Jamesfcb70ef2011-02-02 16:02:30 -080073
David Jamesfcb70ef2011-02-02 16:02:30 -080074def Usage():
75 """Print usage."""
76 print "Usage:"
David James386ccd12011-05-04 20:17:42 -070077 print " ./parallel_emerge [--board=BOARD] [--workon=PKGS]"
David Jamesfcb70ef2011-02-02 16:02:30 -080078 print " [--rebuild] [emerge args] package"
79 print
80 print "Packages specified as workon packages are always built from source."
David Jamesfcb70ef2011-02-02 16:02:30 -080081 print
82 print "The --workon argument is mainly useful when you want to build and"
83 print "install packages that you are working on unconditionally, but do not"
84 print "to have to rev the package to indicate you want to build it from"
85 print "source. The build_packages script will automatically supply the"
86 print "workon argument to emerge, ensuring that packages selected using"
87 print "cros-workon are rebuilt."
88 print
89 print "The --rebuild option rebuilds packages whenever their dependencies"
90 print "are changed. This ensures that your build is correct."
David Jamesfcb70ef2011-02-02 16:02:30 -080091
92
David Jamesfcb70ef2011-02-02 16:02:30 -080093# Global start time
94GLOBAL_START = time.time()
95
David James7358d032011-05-19 10:40:03 -070096# Whether process has been killed by a signal.
97KILLED = multiprocessing.Event()
98
David Jamesfcb70ef2011-02-02 16:02:30 -080099
100class EmergeData(object):
101 """This simple struct holds various emerge variables.
102
103 This struct helps us easily pass emerge variables around as a unit.
104 These variables are used for calculating dependencies and installing
105 packages.
106 """
107
David Jamesbf1e3442011-05-28 07:44:20 -0700108 __slots__ = ["action", "cmdline_packages", "depgraph", "favorites",
109 "mtimedb", "opts", "root_config", "scheduler_graph",
110 "settings", "spinner", "trees"]
David Jamesfcb70ef2011-02-02 16:02:30 -0800111
112 def __init__(self):
113 # The action the user requested. If the user is installing packages, this
114 # is None. If the user is doing anything other than installing packages,
115 # this will contain the action name, which will map exactly to the
116 # long-form name of the associated emerge option.
117 #
118 # Example: If you call parallel_emerge --unmerge package, the action name
119 # will be "unmerge"
120 self.action = None
121
122 # The list of packages the user passed on the command-line.
123 self.cmdline_packages = None
124
125 # The emerge dependency graph. It'll contain all the packages involved in
126 # this merge, along with their versions.
127 self.depgraph = None
128
David Jamesbf1e3442011-05-28 07:44:20 -0700129 # The list of candidates to add to the world file.
130 self.favorites = None
131
David Jamesfcb70ef2011-02-02 16:02:30 -0800132 # A dict of the options passed to emerge. This dict has been cleaned up
133 # a bit by parse_opts, so that it's a bit easier for the emerge code to
134 # look at the options.
135 #
136 # Emerge takes a few shortcuts in its cleanup process to make parsing of
137 # the options dict easier. For example, if you pass in "--usepkg=n", the
138 # "--usepkg" flag is just left out of the dictionary altogether. Because
139 # --usepkg=n is the default, this makes parsing easier, because emerge
140 # can just assume that if "--usepkg" is in the dictionary, it's enabled.
141 #
142 # These cleanup processes aren't applied to all options. For example, the
143 # --with-bdeps flag is passed in as-is. For a full list of the cleanups
144 # applied by emerge, see the parse_opts function in the _emerge.main
145 # package.
146 self.opts = None
147
148 # A dictionary used by portage to maintain global state. This state is
149 # loaded from disk when portage starts up, and saved to disk whenever we
150 # call mtimedb.commit().
151 #
152 # This database contains information about global updates (i.e., what
153 # version of portage we have) and what we're currently doing. Portage
154 # saves what it is currently doing in this database so that it can be
155 # resumed when you call it with the --resume option.
156 #
157 # parallel_emerge does not save what it is currently doing in the mtimedb,
158 # so we do not support the --resume option.
159 self.mtimedb = None
160
161 # The portage configuration for our current root. This contains the portage
162 # settings (see below) and the three portage trees for our current root.
163 # (The three portage trees are explained below, in the documentation for
164 # the "trees" member.)
165 self.root_config = None
166
167 # The scheduler graph is used by emerge to calculate what packages to
168 # install. We don't actually install any deps, so this isn't really used,
169 # but we pass it in to the Scheduler object anyway.
170 self.scheduler_graph = None
171
172 # Portage settings for our current session. Most of these settings are set
173 # in make.conf inside our current install root.
174 self.settings = None
175
176 # The spinner, which spews stuff to stdout to indicate that portage is
177 # doing something. We maintain our own spinner, so we set the portage
178 # spinner to "silent" mode.
179 self.spinner = None
180
181 # The portage trees. There are separate portage trees for each root. To get
182 # the portage tree for the current root, you can look in self.trees[root],
183 # where root = self.settings["ROOT"].
184 #
185 # In each root, there are three trees: vartree, porttree, and bintree.
186 # - vartree: A database of the currently-installed packages.
187 # - porttree: A database of ebuilds, that can be used to build packages.
188 # - bintree: A database of binary packages.
189 self.trees = None
190
191
192class DepGraphGenerator(object):
193 """Grab dependency information about packages from portage.
194
195 Typical usage:
196 deps = DepGraphGenerator()
197 deps.Initialize(sys.argv[1:])
198 deps_tree, deps_info = deps.GenDependencyTree()
199 deps_graph = deps.GenDependencyGraph(deps_tree, deps_info)
200 deps.PrintTree(deps_tree)
201 PrintDepsMap(deps_graph)
202 """
203
David James386ccd12011-05-04 20:17:42 -0700204 __slots__ = ["board", "emerge", "package_db", "show_output"]
David Jamesfcb70ef2011-02-02 16:02:30 -0800205
206 def __init__(self):
207 self.board = None
208 self.emerge = EmergeData()
David Jamesfcb70ef2011-02-02 16:02:30 -0800209 self.package_db = {}
David Jamesfcb70ef2011-02-02 16:02:30 -0800210 self.show_output = False
David Jamesfcb70ef2011-02-02 16:02:30 -0800211
212 def ParseParallelEmergeArgs(self, argv):
213 """Read the parallel emerge arguments from the command-line.
214
215 We need to be compatible with emerge arg format. We scrape arguments that
216 are specific to parallel_emerge, and pass through the rest directly to
217 emerge.
218 Args:
219 argv: arguments list
220 Returns:
221 Arguments that don't belong to parallel_emerge
222 """
223 emerge_args = []
224 for arg in argv:
225 # Specifically match arguments that are specific to parallel_emerge, and
226 # pass through the rest.
227 if arg.startswith("--board="):
228 self.board = arg.replace("--board=", "")
229 elif arg.startswith("--workon="):
230 workon_str = arg.replace("--workon=", "")
David James7a1ea4b2011-10-13 15:06:41 -0700231 emerge_args.append("--reinstall-atoms=%s" % workon_str)
232 emerge_args.append("--usepkg-exclude=%s" % workon_str)
David Jamesfcb70ef2011-02-02 16:02:30 -0800233 elif arg.startswith("--force-remote-binary="):
234 force_remote_binary = arg.replace("--force-remote-binary=", "")
David James7a1ea4b2011-10-13 15:06:41 -0700235 emerge_args.append("--useoldpkg-atoms=%s" % force_remote_binary)
David Jamesfcb70ef2011-02-02 16:02:30 -0800236 elif arg == "--show-output":
237 self.show_output = True
David James386ccd12011-05-04 20:17:42 -0700238 elif arg == "--rebuild":
David James7a1ea4b2011-10-13 15:06:41 -0700239 emerge_args.append("--rebuild-if-unbuilt")
David Jamesfcb70ef2011-02-02 16:02:30 -0800240 else:
241 # Not one of our options, so pass through to emerge.
242 emerge_args.append(arg)
243
David James386ccd12011-05-04 20:17:42 -0700244 # These packages take a really long time to build, so, for expediency, we
245 # are blacklisting them from automatic rebuilds because one of their
246 # dependencies needs to be recompiled.
247 for pkg in ("chromeos-base/chromeos-chrome", "media-plugins/o3d",
248 "dev-java/icedtea"):
David James7a1ea4b2011-10-13 15:06:41 -0700249 emerge_args.append("--rebuild-exclude=%s" % pkg)
David Jamesfcb70ef2011-02-02 16:02:30 -0800250
251 return emerge_args
252
253 def Initialize(self, args):
254 """Initializer. Parses arguments and sets up portage state."""
255
256 # Parse and strip out args that are just intended for parallel_emerge.
257 emerge_args = self.ParseParallelEmergeArgs(args)
258
259 # Setup various environment variables based on our current board. These
260 # variables are normally setup inside emerge-${BOARD}, but since we don't
261 # call that script, we have to set it up here. These variables serve to
262 # point our tools at /build/BOARD and to setup cross compiles to the
263 # appropriate board as configured in toolchain.conf.
264 if self.board:
265 os.environ["PORTAGE_CONFIGROOT"] = "/build/" + self.board
266 os.environ["PORTAGE_SYSROOT"] = "/build/" + self.board
267 os.environ["SYSROOT"] = "/build/" + self.board
David Jamesfcb70ef2011-02-02 16:02:30 -0800268
269 # Although CHROMEOS_ROOT isn't specific to boards, it's normally setup
270 # inside emerge-${BOARD}, so we set it up here for compatibility. It
271 # will be going away soon as we migrate to CROS_WORKON_SRCROOT.
272 os.environ.setdefault("CHROMEOS_ROOT", os.environ["HOME"] + "/trunk")
273
274 # Turn off interactive delays
275 os.environ["EBEEP_IGNORE"] = "1"
276 os.environ["EPAUSE_IGNORE"] = "1"
Mike Frysinger0a647fc2012-08-06 14:36:05 -0400277 os.environ["CLEAN_DELAY"] = "0"
David Jamesfcb70ef2011-02-02 16:02:30 -0800278
279 # Parse the emerge options.
David Jamesea3ca332011-05-26 11:48:29 -0700280 action, opts, cmdline_packages = parse_opts(emerge_args, silent=True)
David Jamesfcb70ef2011-02-02 16:02:30 -0800281
282 # Set environment variables based on options. Portage normally sets these
283 # environment variables in emerge_main, but we can't use that function,
284 # because it also does a bunch of other stuff that we don't want.
285 # TODO(davidjames): Patch portage to move this logic into a function we can
286 # reuse here.
287 if "--debug" in opts:
288 os.environ["PORTAGE_DEBUG"] = "1"
289 if "--config-root" in opts:
290 os.environ["PORTAGE_CONFIGROOT"] = opts["--config-root"]
291 if "--root" in opts:
292 os.environ["ROOT"] = opts["--root"]
293 if "--accept-properties" in opts:
294 os.environ["ACCEPT_PROPERTIES"] = opts["--accept-properties"]
295
David Jamesfcb70ef2011-02-02 16:02:30 -0800296 # If we're installing packages to the board, and we're not using the
David James927a56d2012-04-03 11:26:39 -0700297 # official flag, we can disable vardb locks. This is safe because we
298 # only run up to one instance of parallel_emerge in parallel.
David Jamesfcb70ef2011-02-02 16:02:30 -0800299 if self.board and os.environ.get("CHROMEOS_OFFICIAL") != "1":
300 os.environ.setdefault("PORTAGE_LOCKS", "false")
David Jamesfcb70ef2011-02-02 16:02:30 -0800301
302 # Now that we've setup the necessary environment variables, we can load the
303 # emerge config from disk.
304 settings, trees, mtimedb = load_emerge_config()
305
David Jamesea3ca332011-05-26 11:48:29 -0700306 # Add in EMERGE_DEFAULT_OPTS, if specified.
307 tmpcmdline = []
308 if "--ignore-default-opts" not in opts:
309 tmpcmdline.extend(settings["EMERGE_DEFAULT_OPTS"].split())
310 tmpcmdline.extend(emerge_args)
311 action, opts, cmdline_packages = parse_opts(tmpcmdline)
312
313 # If we're installing to the board, we want the --root-deps option so that
314 # portage will install the build dependencies to that location as well.
315 if self.board:
316 opts.setdefault("--root-deps", True)
317
David Jamesfcb70ef2011-02-02 16:02:30 -0800318 # Check whether our portage tree is out of date. Typically, this happens
319 # when you're setting up a new portage tree, such as in setup_board and
320 # make_chroot. In that case, portage applies a bunch of global updates
321 # here. Once the updates are finished, we need to commit any changes
322 # that the global update made to our mtimedb, and reload the config.
323 #
324 # Portage normally handles this logic in emerge_main, but again, we can't
325 # use that function here.
326 if _global_updates(trees, mtimedb["updates"]):
327 mtimedb.commit()
328 settings, trees, mtimedb = load_emerge_config(trees=trees)
329
330 # Setup implied options. Portage normally handles this logic in
331 # emerge_main.
332 if "--buildpkgonly" in opts or "buildpkg" in settings.features:
333 opts.setdefault("--buildpkg", True)
334 if "--getbinpkgonly" in opts:
335 opts.setdefault("--usepkgonly", True)
336 opts.setdefault("--getbinpkg", True)
337 if "getbinpkg" in settings.features:
338 # Per emerge_main, FEATURES=getbinpkg overrides --getbinpkg=n
339 opts["--getbinpkg"] = True
340 if "--getbinpkg" in opts or "--usepkgonly" in opts:
341 opts.setdefault("--usepkg", True)
342 if "--fetch-all-uri" in opts:
343 opts.setdefault("--fetchonly", True)
344 if "--skipfirst" in opts:
345 opts.setdefault("--resume", True)
346 if "--buildpkgonly" in opts:
347 # --buildpkgonly will not merge anything, so it overrides all binary
348 # package options.
349 for opt in ("--getbinpkg", "--getbinpkgonly",
350 "--usepkg", "--usepkgonly"):
351 opts.pop(opt, None)
352 if (settings.get("PORTAGE_DEBUG", "") == "1" and
353 "python-trace" in settings.features):
354 portage.debug.set_trace(True)
355
356 # Complain about unsupported options
David James386ccd12011-05-04 20:17:42 -0700357 for opt in ("--ask", "--ask-enter-invalid", "--resume", "--skipfirst"):
David Jamesfcb70ef2011-02-02 16:02:30 -0800358 if opt in opts:
359 print "%s is not supported by parallel_emerge" % opt
360 sys.exit(1)
361
362 # Make emerge specific adjustments to the config (e.g. colors!)
363 adjust_configs(opts, trees)
364
365 # Save our configuration so far in the emerge object
366 emerge = self.emerge
367 emerge.action, emerge.opts = action, opts
368 emerge.settings, emerge.trees, emerge.mtimedb = settings, trees, mtimedb
369 emerge.cmdline_packages = cmdline_packages
370 root = settings["ROOT"]
371 emerge.root_config = trees[root]["root_config"]
372
David James386ccd12011-05-04 20:17:42 -0700373 if "--usepkg" in opts:
David Jamesfcb70ef2011-02-02 16:02:30 -0800374 emerge.trees[root]["bintree"].populate("--getbinpkg" in opts)
375
David Jamesfcb70ef2011-02-02 16:02:30 -0800376 def CreateDepgraph(self, emerge, packages):
377 """Create an emerge depgraph object."""
378 # Setup emerge options.
379 emerge_opts = emerge.opts.copy()
380
David James386ccd12011-05-04 20:17:42 -0700381 # Ask portage to build a dependency graph. with the options we specified
382 # above.
David Jamesfcb70ef2011-02-02 16:02:30 -0800383 params = create_depgraph_params(emerge_opts, emerge.action)
David Jamesbf1e3442011-05-28 07:44:20 -0700384 success, depgraph, favorites = backtrack_depgraph(
David James386ccd12011-05-04 20:17:42 -0700385 emerge.settings, emerge.trees, emerge_opts, params, emerge.action,
386 packages, emerge.spinner)
387 emerge.depgraph = depgraph
David Jamesfcb70ef2011-02-02 16:02:30 -0800388
David James386ccd12011-05-04 20:17:42 -0700389 # Is it impossible to honor the user's request? Bail!
390 if not success:
391 depgraph.display_problems()
392 sys.exit(1)
David Jamesfcb70ef2011-02-02 16:02:30 -0800393
394 emerge.depgraph = depgraph
David Jamesbf1e3442011-05-28 07:44:20 -0700395 emerge.favorites = favorites
David Jamesfcb70ef2011-02-02 16:02:30 -0800396
David Jamesdeebd692011-05-09 17:02:52 -0700397 # Prime and flush emerge caches.
398 root = emerge.settings["ROOT"]
399 vardb = emerge.trees[root]["vartree"].dbapi
David James0bdc5de2011-05-12 16:22:26 -0700400 if "--pretend" not in emerge.opts:
401 vardb.counter_tick()
David Jamesdeebd692011-05-09 17:02:52 -0700402 vardb.flush_cache()
403
David James386ccd12011-05-04 20:17:42 -0700404 def GenDependencyTree(self):
David Jamesfcb70ef2011-02-02 16:02:30 -0800405 """Get dependency tree info from emerge.
406
David Jamesfcb70ef2011-02-02 16:02:30 -0800407 Returns:
408 Dependency tree
409 """
410 start = time.time()
411
412 emerge = self.emerge
413
414 # Create a list of packages to merge
415 packages = set(emerge.cmdline_packages[:])
David Jamesfcb70ef2011-02-02 16:02:30 -0800416
417 # Tell emerge to be quiet. We print plenty of info ourselves so we don't
418 # need any extra output from portage.
419 portage.util.noiselimit = -1
420
421 # My favorite feature: The silent spinner. It doesn't spin. Ever.
422 # I'd disable the colors by default too, but they look kind of cool.
423 emerge.spinner = stdout_spinner()
424 emerge.spinner.update = emerge.spinner.update_quiet
425
426 if "--quiet" not in emerge.opts:
427 print "Calculating deps..."
428
429 self.CreateDepgraph(emerge, packages)
430 depgraph = emerge.depgraph
431
432 # Build our own tree from the emerge digraph.
433 deps_tree = {}
434 digraph = depgraph._dynamic_config.digraph
David James3f778802011-08-25 19:31:45 -0700435 root = emerge.settings["ROOT"]
436 final_db = depgraph._dynamic_config.mydbapi[root]
David Jamesfcb70ef2011-02-02 16:02:30 -0800437 for node, node_deps in digraph.nodes.items():
438 # Calculate dependency packages that need to be installed first. Each
439 # child on the digraph is a dependency. The "operation" field specifies
440 # what we're doing (e.g. merge, uninstall, etc.). The "priorities" array
441 # contains the type of dependency (e.g. build, runtime, runtime_post,
442 # etc.)
443 #
David Jamesfcb70ef2011-02-02 16:02:30 -0800444 # Portage refers to the identifiers for packages as a CPV. This acronym
445 # stands for Component/Path/Version.
446 #
447 # Here's an example CPV: chromeos-base/power_manager-0.0.1-r1
448 # Split up, this CPV would be:
449 # C -- Component: chromeos-base
450 # P -- Path: power_manager
451 # V -- Version: 0.0.1-r1
452 #
453 # We just refer to CPVs as packages here because it's easier.
454 deps = {}
455 for child, priorities in node_deps[0].items():
David James3f778802011-08-25 19:31:45 -0700456 if isinstance(child, Package) and child.root == root:
457 cpv = str(child.cpv)
458 action = str(child.operation)
459
460 # If we're uninstalling a package, check whether Portage is
461 # installing a replacement. If so, just depend on the installation
462 # of the new package, because the old package will automatically
463 # be uninstalled at that time.
464 if action == "uninstall":
465 for pkg in final_db.match_pkgs(child.slot_atom):
466 cpv = str(pkg.cpv)
467 action = "merge"
468 break
469
470 deps[cpv] = dict(action=action,
471 deptypes=[str(x) for x in priorities],
472 deps={})
David Jamesfcb70ef2011-02-02 16:02:30 -0800473
474 # We've built our list of deps, so we can add our package to the tree.
David James3f778802011-08-25 19:31:45 -0700475 if isinstance(node, Package) and node.root == root:
David Jamesfcb70ef2011-02-02 16:02:30 -0800476 deps_tree[str(node.cpv)] = dict(action=str(node.operation),
477 deps=deps)
478
David Jamesfcb70ef2011-02-02 16:02:30 -0800479 # Ask portage for its install plan, so that we can only throw out
David James386ccd12011-05-04 20:17:42 -0700480 # dependencies that portage throws out.
David Jamesfcb70ef2011-02-02 16:02:30 -0800481 deps_info = {}
482 for pkg in depgraph.altlist():
483 if isinstance(pkg, Package):
David James3f778802011-08-25 19:31:45 -0700484 assert pkg.root == root
David Jamesfcb70ef2011-02-02 16:02:30 -0800485 self.package_db[pkg.cpv] = pkg
486
David Jamesfcb70ef2011-02-02 16:02:30 -0800487 # Save off info about the package
David James386ccd12011-05-04 20:17:42 -0700488 deps_info[str(pkg.cpv)] = {"idx": len(deps_info)}
David Jamesfcb70ef2011-02-02 16:02:30 -0800489
490 seconds = time.time() - start
491 if "--quiet" not in emerge.opts:
492 print "Deps calculated in %dm%.1fs" % (seconds / 60, seconds % 60)
493
494 return deps_tree, deps_info
495
496 def PrintTree(self, deps, depth=""):
497 """Print the deps we have seen in the emerge output.
498
499 Args:
500 deps: Dependency tree structure.
501 depth: Allows printing the tree recursively, with indentation.
502 """
503 for entry in sorted(deps):
504 action = deps[entry]["action"]
505 print "%s %s (%s)" % (depth, entry, action)
506 self.PrintTree(deps[entry]["deps"], depth=depth + " ")
507
David James386ccd12011-05-04 20:17:42 -0700508 def GenDependencyGraph(self, deps_tree, deps_info):
David Jamesfcb70ef2011-02-02 16:02:30 -0800509 """Generate a doubly linked dependency graph.
510
511 Args:
512 deps_tree: Dependency tree structure.
513 deps_info: More details on the dependencies.
514 Returns:
515 Deps graph in the form of a dict of packages, with each package
516 specifying a "needs" list and "provides" list.
517 """
518 emerge = self.emerge
David Jamesfcb70ef2011-02-02 16:02:30 -0800519
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]
David James321490a2012-12-17 12:05:56 -0800617 for i in xrange(len(mycycle) - 1):
David Jamesfcb70ef2011-02-02 16:02:30 -0800618 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.
David James321490a2012-12-17 12:05:56 -0800674 for i in xrange(len(mycycle) - 1):
David Jamesfcb70ef2011-02-02 16:02:30 -0800675 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 James321490a2012-12-17 12:05:56 -0800832def KillHandler(_signum, _frame):
David James7358d032011-05-19 10:40:03 -0700833 # Kill self and all subprocesses.
834 os.killpg(0, signal.SIGKILL)
835
David Jamesfcb70ef2011-02-02 16:02:30 -0800836def SetupWorkerSignals():
David James321490a2012-12-17 12:05:56 -0800837 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 James6b29d052012-11-02 10:27:27 -0700852def EmergeProcess(output, *args, **kwargs):
David James1ed3e252011-10-05 20:26:15 -0700853 """Merge a package in a subprocess.
854
855 Args:
David James1ed3e252011-10-05 20:26:15 -0700856 output: Temporary file to write output.
David James6b29d052012-11-02 10:27:27 -0700857 *args: Arguments to pass to Scheduler constructor.
858 **kwargs: Keyword arguments to pass to Scheduler constructor.
David James1ed3e252011-10-05 20:26:15 -0700859
860 Returns:
861 The exit code returned by the subprocess.
862 """
863 pid = os.fork()
864 if pid == 0:
865 try:
866 # Sanity checks.
867 if sys.stdout.fileno() != 1: raise Exception("sys.stdout.fileno() != 1")
868 if sys.stderr.fileno() != 2: raise Exception("sys.stderr.fileno() != 2")
869
870 # - Redirect 1 (stdout) and 2 (stderr) at our temporary file.
871 # - Redirect 0 to point at sys.stdin. In this case, sys.stdin
872 # points at a file reading os.devnull, because multiprocessing mucks
873 # with sys.stdin.
874 # - Leave the sys.stdin and output filehandles alone.
875 fd_pipes = {0: sys.stdin.fileno(),
876 1: output.fileno(),
877 2: output.fileno(),
878 sys.stdin.fileno(): sys.stdin.fileno(),
879 output.fileno(): output.fileno()}
880 portage.process._setup_pipes(fd_pipes)
881
882 # Portage doesn't like when sys.stdin.fileno() != 0, so point sys.stdin
883 # at the filehandle we just created in _setup_pipes.
884 if sys.stdin.fileno() != 0:
David James6b29d052012-11-02 10:27:27 -0700885 sys.__stdin__ = sys.stdin = os.fdopen(0, "r")
886
887 scheduler = Scheduler(*args, **kwargs)
888
889 # Enable blocker handling even though we're in --nodeps mode. This
890 # allows us to unmerge the blocker after we've merged the replacement.
891 scheduler._opts_ignore_blockers = frozenset()
David James1ed3e252011-10-05 20:26:15 -0700892
893 # Actually do the merge.
894 retval = scheduler.merge()
895
896 # We catch all exceptions here (including SystemExit, KeyboardInterrupt,
897 # etc) so as to ensure that we don't confuse the multiprocessing module,
898 # which expects that all forked children exit with os._exit().
David James321490a2012-12-17 12:05:56 -0800899 # pylint: disable=W0702
David James1ed3e252011-10-05 20:26:15 -0700900 except:
901 traceback.print_exc(file=output)
902 retval = 1
903 sys.stdout.flush()
904 sys.stderr.flush()
905 output.flush()
906 os._exit(retval)
907 else:
908 # Return the exit code of the subprocess.
909 return os.waitpid(pid, 0)[1]
David Jamesfcb70ef2011-02-02 16:02:30 -0800910
Brian Harring0be85c62012-03-17 19:52:12 -0700911def EmergeWorker(task_queue, job_queue, emerge, package_db, fetch_only=False):
David Jamesfcb70ef2011-02-02 16:02:30 -0800912 """This worker emerges any packages given to it on the task_queue.
913
914 Args:
915 task_queue: The queue of tasks for this worker to do.
916 job_queue: The queue of results from the worker.
917 emerge: An EmergeData() object.
918 package_db: A dict, mapping package ids to portage Package objects.
Brian Harring0be85c62012-03-17 19:52:12 -0700919 fetch_only: A bool, indicating if we should just fetch the target.
David Jamesfcb70ef2011-02-02 16:02:30 -0800920
921 It expects package identifiers to be passed to it via task_queue. When
922 a task is started, it pushes the (target, filename) to the started_queue.
923 The output is stored in filename. When a merge starts or finishes, we push
924 EmergeJobState objects to the job_queue.
925 """
926
927 SetupWorkerSignals()
928 settings, trees, mtimedb = emerge.settings, emerge.trees, emerge.mtimedb
David Jamesdeebd692011-05-09 17:02:52 -0700929
930 # Disable flushing of caches to save on I/O.
David James7a1ea4b2011-10-13 15:06:41 -0700931 root = emerge.settings["ROOT"]
932 vardb = emerge.trees[root]["vartree"].dbapi
933 vardb._flush_cache_enabled = False
Brian Harring0be85c62012-03-17 19:52:12 -0700934 bindb = emerge.trees[root]["bintree"].dbapi
935 # Might be a set, might be a list, might be None; no clue, just use shallow
936 # copy to ensure we can roll it back.
937 original_remotepkgs = copy.copy(bindb.bintree._remotepkgs)
David Jamesdeebd692011-05-09 17:02:52 -0700938
David Jamesfcb70ef2011-02-02 16:02:30 -0800939 opts, spinner = emerge.opts, emerge.spinner
940 opts["--nodeps"] = True
Brian Harring0be85c62012-03-17 19:52:12 -0700941 if fetch_only:
942 opts["--fetchonly"] = True
943
David Jamesfcb70ef2011-02-02 16:02:30 -0800944 while True:
945 # Wait for a new item to show up on the queue. This is a blocking wait,
946 # so if there's nothing to do, we just sit here.
Brian Harring0be85c62012-03-17 19:52:12 -0700947 pkg_state = task_queue.get()
948 if pkg_state is None:
David Jamesfcb70ef2011-02-02 16:02:30 -0800949 # If target is None, this means that the main thread wants us to quit.
950 # The other workers need to exit too, so we'll push the message back on
951 # to the queue so they'll get it too.
Brian Harring0be85c62012-03-17 19:52:12 -0700952 task_queue.put(None)
David Jamesfcb70ef2011-02-02 16:02:30 -0800953 return
David James7358d032011-05-19 10:40:03 -0700954 if KILLED.is_set():
955 return
956
Brian Harring0be85c62012-03-17 19:52:12 -0700957 target = pkg_state.target
958
David Jamesfcb70ef2011-02-02 16:02:30 -0800959 db_pkg = package_db[target]
Brian Harring0be85c62012-03-17 19:52:12 -0700960
961 if db_pkg.type_name == "binary":
962 if not fetch_only and pkg_state.fetched_successfully:
963 # Ensure portage doesn't think our pkg is remote- else it'll force
964 # a redownload of it (even if the on-disk file is fine). In-memory
965 # caching basically, implemented dumbly.
966 bindb.bintree._remotepkgs = None
967 else:
968 bindb.bintree_remotepkgs = original_remotepkgs
969
David Jamesfcb70ef2011-02-02 16:02:30 -0800970 db_pkg.root_config = emerge.root_config
971 install_list = [db_pkg]
972 pkgname = db_pkg.pf
973 output = tempfile.NamedTemporaryFile(prefix=pkgname + "-", delete=False)
David James01b1e0f2012-06-07 17:18:05 -0700974 os.chmod(output.name, 644)
David Jamesfcb70ef2011-02-02 16:02:30 -0800975 start_timestamp = time.time()
Brian Harring0be85c62012-03-17 19:52:12 -0700976 job = EmergeJobState(target, pkgname, False, output.name, start_timestamp,
977 fetch_only=fetch_only)
David Jamesfcb70ef2011-02-02 16:02:30 -0800978 job_queue.put(job)
979 if "--pretend" in opts:
980 retcode = 0
981 else:
David Jamesfcb70ef2011-02-02 16:02:30 -0800982 try:
David James386ccd12011-05-04 20:17:42 -0700983 emerge.scheduler_graph.mergelist = install_list
David James6b29d052012-11-02 10:27:27 -0700984 retcode = EmergeProcess(output, settings, trees, mtimedb, opts,
985 spinner, favorites=emerge.favorites,
986 graph_config=emerge.scheduler_graph)
David Jamesfcb70ef2011-02-02 16:02:30 -0800987 except Exception:
988 traceback.print_exc(file=output)
989 retcode = 1
David James1ed3e252011-10-05 20:26:15 -0700990 output.close()
David Jamesfcb70ef2011-02-02 16:02:30 -0800991
David James7358d032011-05-19 10:40:03 -0700992 if KILLED.is_set():
993 return
994
David Jamesfcb70ef2011-02-02 16:02:30 -0800995 job = EmergeJobState(target, pkgname, True, output.name, start_timestamp,
Brian Harring0be85c62012-03-17 19:52:12 -0700996 retcode, fetch_only=fetch_only)
David Jamesfcb70ef2011-02-02 16:02:30 -0800997 job_queue.put(job)
998
999
1000class LinePrinter(object):
1001 """Helper object to print a single line."""
1002
1003 def __init__(self, line):
1004 self.line = line
1005
David James321490a2012-12-17 12:05:56 -08001006 def Print(self, _seek_locations):
David Jamesfcb70ef2011-02-02 16:02:30 -08001007 print self.line
1008
1009
1010class JobPrinter(object):
1011 """Helper object to print output of a job."""
1012
1013 def __init__(self, job, unlink=False):
1014 """Print output of job.
1015
1016 If unlink is True, unlink the job output file when done."""
1017 self.current_time = time.time()
1018 self.job = job
1019 self.unlink = unlink
1020
1021 def Print(self, seek_locations):
1022
1023 job = self.job
1024
1025 # Calculate how long the job has been running.
1026 seconds = self.current_time - job.start_timestamp
1027
1028 # Note that we've printed out the job so far.
1029 job.last_output_timestamp = self.current_time
1030
1031 # Note that we're starting the job
1032 info = "job %s (%dm%.1fs)" % (job.pkgname, seconds / 60, seconds % 60)
1033 last_output_seek = seek_locations.get(job.filename, 0)
1034 if last_output_seek:
1035 print "=== Continue output for %s ===" % info
1036 else:
1037 print "=== Start output for %s ===" % info
1038
1039 # Print actual output from job
1040 f = codecs.open(job.filename, encoding='utf-8', errors='replace')
1041 f.seek(last_output_seek)
1042 prefix = job.pkgname + ":"
1043 for line in f:
1044
1045 # Save off our position in the file
1046 if line and line[-1] == "\n":
1047 last_output_seek = f.tell()
1048 line = line[:-1]
1049
1050 # Print our line
1051 print prefix, line.encode('utf-8', 'replace')
1052 f.close()
1053
1054 # Save our last spot in the file so that we don't print out the same
1055 # location twice.
1056 seek_locations[job.filename] = last_output_seek
1057
1058 # Note end of output section
1059 if job.done:
1060 print "=== Complete: %s ===" % info
1061 else:
1062 print "=== Still running: %s ===" % info
1063
1064 if self.unlink:
1065 os.unlink(job.filename)
1066
1067
1068def PrintWorker(queue):
1069 """A worker that prints stuff to the screen as requested."""
1070
David James321490a2012-12-17 12:05:56 -08001071 def ExitHandler(_signum, _frame):
David James7358d032011-05-19 10:40:03 -07001072 # Set KILLED flag.
1073 KILLED.set()
1074
David Jamesfcb70ef2011-02-02 16:02:30 -08001075 # Switch to default signal handlers so that we'll die after two signals.
David James7358d032011-05-19 10:40:03 -07001076 signal.signal(signal.SIGINT, KillHandler)
1077 signal.signal(signal.SIGTERM, KillHandler)
David Jamesfcb70ef2011-02-02 16:02:30 -08001078
1079 # Don't exit on the first SIGINT / SIGTERM, because the parent worker will
1080 # handle it and tell us when we need to exit.
1081 signal.signal(signal.SIGINT, ExitHandler)
1082 signal.signal(signal.SIGTERM, ExitHandler)
1083
1084 # seek_locations is a map indicating the position we are at in each file.
1085 # It starts off empty, but is set by the various Print jobs as we go along
1086 # to indicate where we left off in each file.
1087 seek_locations = {}
1088 while True:
1089 try:
1090 job = queue.get()
1091 if job:
1092 job.Print(seek_locations)
David Jamesbccf8eb2011-07-27 14:06:06 -07001093 sys.stdout.flush()
David Jamesfcb70ef2011-02-02 16:02:30 -08001094 else:
1095 break
1096 except IOError as ex:
1097 if ex.errno == errno.EINTR:
1098 # Looks like we received a signal. Keep printing.
1099 continue
1100 raise
1101
Brian Harring867e2362012-03-17 04:05:17 -07001102
Brian Harring0be85c62012-03-17 19:52:12 -07001103class TargetState(object):
Brian Harring867e2362012-03-17 04:05:17 -07001104
Brian Harring0be85c62012-03-17 19:52:12 -07001105 __slots__ = ("target", "info", "score", "prefetched", "fetched_successfully")
Brian Harring867e2362012-03-17 04:05:17 -07001106
David James321490a2012-12-17 12:05:56 -08001107 def __init__(self, target, info):
Brian Harring867e2362012-03-17 04:05:17 -07001108 self.target, self.info = target, info
Brian Harring0be85c62012-03-17 19:52:12 -07001109 self.fetched_successfully = False
1110 self.prefetched = False
David James321490a2012-12-17 12:05:56 -08001111 self.score = None
Brian Harring867e2362012-03-17 04:05:17 -07001112 self.update_score()
1113
1114 def __cmp__(self, other):
1115 return cmp(self.score, other.score)
1116
1117 def update_score(self):
1118 self.score = (
1119 -len(self.info["tprovides"]),
Brian Harring0be85c62012-03-17 19:52:12 -07001120 len(self.info["needs"]),
Brian Harring11c5eeb2012-03-18 11:02:39 -07001121 not self.info["binary"],
Brian Harring867e2362012-03-17 04:05:17 -07001122 -len(self.info["provides"]),
1123 self.info["idx"],
1124 self.target,
1125 )
1126
1127
1128class ScoredHeap(object):
1129
Brian Harring0be85c62012-03-17 19:52:12 -07001130 __slots__ = ("heap", "_heap_set")
1131
Brian Harring867e2362012-03-17 04:05:17 -07001132 def __init__(self, initial=()):
Brian Harring0be85c62012-03-17 19:52:12 -07001133 self.heap = list()
1134 self._heap_set = set()
1135 if initial:
1136 self.multi_put(initial)
Brian Harring867e2362012-03-17 04:05:17 -07001137
1138 def get(self):
Brian Harring0be85c62012-03-17 19:52:12 -07001139 item = heapq.heappop(self.heap)
1140 self._heap_set.remove(item.target)
1141 return item
Brian Harring867e2362012-03-17 04:05:17 -07001142
Brian Harring0be85c62012-03-17 19:52:12 -07001143 def put(self, item):
1144 if not isinstance(item, TargetState):
1145 raise ValueError("Item %r isn't a TargetState" % (item,))
1146 heapq.heappush(self.heap, item)
1147 self._heap_set.add(item.target)
Brian Harring867e2362012-03-17 04:05:17 -07001148
Brian Harring0be85c62012-03-17 19:52:12 -07001149 def multi_put(self, sequence):
1150 sequence = list(sequence)
1151 self.heap.extend(sequence)
1152 self._heap_set.update(x.target for x in sequence)
Brian Harring867e2362012-03-17 04:05:17 -07001153 self.sort()
1154
David James5c9996d2012-03-24 10:50:46 -07001155 def sort(self):
1156 heapq.heapify(self.heap)
1157
Brian Harring0be85c62012-03-17 19:52:12 -07001158 def __contains__(self, target):
1159 return target in self._heap_set
1160
1161 def __nonzero__(self):
1162 return bool(self.heap)
1163
Brian Harring867e2362012-03-17 04:05:17 -07001164 def __len__(self):
1165 return len(self.heap)
1166
1167
David Jamesfcb70ef2011-02-02 16:02:30 -08001168class EmergeQueue(object):
1169 """Class to schedule emerge jobs according to a dependency graph."""
1170
1171 def __init__(self, deps_map, emerge, package_db, show_output):
1172 # Store the dependency graph.
1173 self._deps_map = deps_map
Brian Harring0be85c62012-03-17 19:52:12 -07001174 self._state_map = {}
David Jamesfcb70ef2011-02-02 16:02:30 -08001175 # Initialize the running queue to empty
Brian Harring0be85c62012-03-17 19:52:12 -07001176 self._build_jobs = {}
1177 self._build_ready = ScoredHeap()
1178 self._fetch_jobs = {}
1179 self._fetch_ready = ScoredHeap()
David Jamesfcb70ef2011-02-02 16:02:30 -08001180 # List of total package installs represented in deps_map.
1181 install_jobs = [x for x in deps_map if deps_map[x]["action"] == "merge"]
1182 self._total_jobs = len(install_jobs)
1183 self._show_output = show_output
1184
1185 if "--pretend" in emerge.opts:
1186 print "Skipping merge because of --pretend mode."
1187 sys.exit(0)
1188
David James7358d032011-05-19 10:40:03 -07001189 # Set a process group so we can easily terminate all children.
1190 os.setsid()
1191
David Jamesfcb70ef2011-02-02 16:02:30 -08001192 # Setup scheduler graph object. This is used by the child processes
1193 # to help schedule jobs.
1194 emerge.scheduler_graph = emerge.depgraph.schedulerGraph()
1195
1196 # Calculate how many jobs we can run in parallel. We don't want to pass
1197 # the --jobs flag over to emerge itself, because that'll tell emerge to
1198 # hide its output, and said output is quite useful for debugging hung
1199 # jobs.
1200 procs = min(self._total_jobs,
1201 emerge.opts.pop("--jobs", multiprocessing.cpu_count()))
Brian Harring0be85c62012-03-17 19:52:12 -07001202 self._build_procs = procs
1203 self._fetch_procs = procs
David James8c7e5e32011-06-28 11:26:03 -07001204 self._load_avg = emerge.opts.pop("--load-average", None)
David Jamesfcb70ef2011-02-02 16:02:30 -08001205 self._job_queue = multiprocessing.Queue()
1206 self._print_queue = multiprocessing.Queue()
Brian Harring0be85c62012-03-17 19:52:12 -07001207
1208 self._fetch_queue = multiprocessing.Queue()
1209 args = (self._fetch_queue, self._job_queue, emerge, package_db, True)
1210 self._fetch_pool = multiprocessing.Pool(self._fetch_procs, EmergeWorker,
1211 args)
1212
1213 self._build_queue = multiprocessing.Queue()
1214 args = (self._build_queue, self._job_queue, emerge, package_db)
1215 self._build_pool = multiprocessing.Pool(self._build_procs, EmergeWorker,
1216 args)
1217
David Jamesfcb70ef2011-02-02 16:02:30 -08001218 self._print_worker = multiprocessing.Process(target=PrintWorker,
1219 args=[self._print_queue])
1220 self._print_worker.start()
1221
1222 # Initialize the failed queue to empty.
1223 self._retry_queue = []
1224 self._failed = set()
1225
David Jamesfcb70ef2011-02-02 16:02:30 -08001226 # Setup an exit handler so that we print nice messages if we are
1227 # terminated.
1228 self._SetupExitHandler()
1229
1230 # Schedule our jobs.
Brian Harring0be85c62012-03-17 19:52:12 -07001231 self._state_map.update(
1232 (pkg, TargetState(pkg, data)) for pkg, data in deps_map.iteritems())
1233 self._fetch_ready.multi_put(self._state_map.itervalues())
David Jamesfcb70ef2011-02-02 16:02:30 -08001234
1235 def _SetupExitHandler(self):
1236
David James321490a2012-12-17 12:05:56 -08001237 def ExitHandler(signum, _frame):
David James7358d032011-05-19 10:40:03 -07001238 # Set KILLED flag.
1239 KILLED.set()
David Jamesfcb70ef2011-02-02 16:02:30 -08001240
1241 # Kill our signal handlers so we don't get called recursively
David James7358d032011-05-19 10:40:03 -07001242 signal.signal(signal.SIGINT, KillHandler)
1243 signal.signal(signal.SIGTERM, KillHandler)
David Jamesfcb70ef2011-02-02 16:02:30 -08001244
1245 # Print our current job status
Brian Harring0be85c62012-03-17 19:52:12 -07001246 for job in self._build_jobs.itervalues():
David Jamesfcb70ef2011-02-02 16:02:30 -08001247 if job:
1248 self._print_queue.put(JobPrinter(job, unlink=True))
1249
1250 # Notify the user that we are exiting
1251 self._Print("Exiting on signal %s" % signum)
David James7358d032011-05-19 10:40:03 -07001252 self._print_queue.put(None)
1253 self._print_worker.join()
David Jamesfcb70ef2011-02-02 16:02:30 -08001254
1255 # Kill child threads, then exit.
David James7358d032011-05-19 10:40:03 -07001256 os.killpg(0, signal.SIGKILL)
David Jamesfcb70ef2011-02-02 16:02:30 -08001257 sys.exit(1)
1258
1259 # Print out job status when we are killed
1260 signal.signal(signal.SIGINT, ExitHandler)
1261 signal.signal(signal.SIGTERM, ExitHandler)
1262
Brian Harring0be85c62012-03-17 19:52:12 -07001263 def _Schedule(self, pkg_state):
David Jamesfcb70ef2011-02-02 16:02:30 -08001264 # We maintain a tree of all deps, if this doesn't need
David James8c7e5e32011-06-28 11:26:03 -07001265 # to be installed just free up its children and continue.
David Jamesfcb70ef2011-02-02 16:02:30 -08001266 # It is possible to reinstall deps of deps, without reinstalling
1267 # first level deps, like so:
1268 # chromeos (merge) -> eselect (nomerge) -> python (merge)
Brian Harring0be85c62012-03-17 19:52:12 -07001269 this_pkg = pkg_state.info
1270 target = pkg_state.target
1271 if pkg_state.info is not None:
1272 if this_pkg["action"] == "nomerge":
1273 self._Finish(target)
1274 elif target not in self._build_jobs:
1275 # Kick off the build if it's marked to be built.
1276 self._build_jobs[target] = None
1277 self._build_queue.put(pkg_state)
1278 return True
David Jamesfcb70ef2011-02-02 16:02:30 -08001279
David James8c7e5e32011-06-28 11:26:03 -07001280 def _ScheduleLoop(self):
1281 # If the current load exceeds our desired load average, don't schedule
1282 # more than one job.
1283 if self._load_avg and os.getloadavg()[0] > self._load_avg:
1284 needed_jobs = 1
1285 else:
Brian Harring0be85c62012-03-17 19:52:12 -07001286 needed_jobs = self._build_procs
David James8c7e5e32011-06-28 11:26:03 -07001287
1288 # Schedule more jobs.
Brian Harring0be85c62012-03-17 19:52:12 -07001289 while self._build_ready and len(self._build_jobs) < needed_jobs:
1290 state = self._build_ready.get()
1291 if state.target not in self._failed:
1292 self._Schedule(state)
David Jamesfcb70ef2011-02-02 16:02:30 -08001293
1294 def _Print(self, line):
1295 """Print a single line."""
1296 self._print_queue.put(LinePrinter(line))
1297
1298 def _Status(self):
1299 """Print status."""
1300 current_time = time.time()
1301 no_output = True
1302
1303 # Print interim output every minute if --show-output is used. Otherwise,
1304 # print notifications about running packages every 2 minutes, and print
1305 # full output for jobs that have been running for 60 minutes or more.
1306 if self._show_output:
1307 interval = 60
1308 notify_interval = 0
1309 else:
1310 interval = 60 * 60
1311 notify_interval = 60 * 2
David James321490a2012-12-17 12:05:56 -08001312 for job in self._build_jobs.itervalues():
David Jamesfcb70ef2011-02-02 16:02:30 -08001313 if job:
1314 last_timestamp = max(job.start_timestamp, job.last_output_timestamp)
1315 if last_timestamp + interval < current_time:
1316 self._print_queue.put(JobPrinter(job))
1317 job.last_output_timestamp = current_time
1318 no_output = False
1319 elif (notify_interval and
1320 job.last_notify_timestamp + notify_interval < current_time):
1321 job_seconds = current_time - job.start_timestamp
1322 args = (job.pkgname, job_seconds / 60, job_seconds % 60, job.filename)
1323 info = "Still building %s (%dm%.1fs). Logs in %s" % args
1324 job.last_notify_timestamp = current_time
1325 self._Print(info)
1326 no_output = False
1327
1328 # If we haven't printed any messages yet, print a general status message
1329 # here.
1330 if no_output:
1331 seconds = current_time - GLOBAL_START
Brian Harring0be85c62012-03-17 19:52:12 -07001332 fjobs, fready = len(self._fetch_jobs), len(self._fetch_ready)
1333 bjobs, bready = len(self._build_jobs), len(self._build_ready)
1334 retries = len(self._retry_queue)
1335 pending = max(0, len(self._deps_map) - fjobs - bjobs)
1336 line = "Pending %s/%s, " % (pending, self._total_jobs)
1337 if fjobs or fready:
1338 line += "Fetching %s/%s, " % (fjobs, fready + fjobs)
1339 if bjobs or bready or retries:
1340 line += "Building %s/%s, " % (bjobs, bready + bjobs)
1341 if retries:
1342 line += "Retrying %s, " % (retries,)
David James8c7e5e32011-06-28 11:26:03 -07001343 load = " ".join(str(x) for x in os.getloadavg())
Brian Harring0be85c62012-03-17 19:52:12 -07001344 line += ("[Time %dm%.1fs Load %s]" % (seconds/60, seconds %60, load))
1345 self._Print(line)
David Jamesfcb70ef2011-02-02 16:02:30 -08001346
1347 def _Finish(self, target):
David James8c7e5e32011-06-28 11:26:03 -07001348 """Mark a target as completed and unblock dependencies."""
1349 this_pkg = self._deps_map[target]
1350 if this_pkg["needs"] and this_pkg["nodeps"]:
1351 # We got installed, but our deps have not been installed yet. Dependent
1352 # packages should only be installed when our needs have been fully met.
1353 this_pkg["action"] = "nomerge"
1354 else:
David James8c7e5e32011-06-28 11:26:03 -07001355 for dep in this_pkg["provides"]:
1356 dep_pkg = self._deps_map[dep]
Brian Harring0be85c62012-03-17 19:52:12 -07001357 state = self._state_map[dep]
David James8c7e5e32011-06-28 11:26:03 -07001358 del dep_pkg["needs"][target]
Brian Harring0be85c62012-03-17 19:52:12 -07001359 state.update_score()
1360 if not state.prefetched:
1361 if dep in self._fetch_ready:
1362 # If it's not currently being fetched, update the prioritization
1363 self._fetch_ready.sort()
1364 elif not dep_pkg["needs"]:
David James8c7e5e32011-06-28 11:26:03 -07001365 if dep_pkg["nodeps"] and dep_pkg["action"] == "nomerge":
1366 self._Finish(dep)
1367 else:
Brian Harring0be85c62012-03-17 19:52:12 -07001368 self._build_ready.put(self._state_map[dep])
David James8c7e5e32011-06-28 11:26:03 -07001369 self._deps_map.pop(target)
David Jamesfcb70ef2011-02-02 16:02:30 -08001370
1371 def _Retry(self):
David James8c7e5e32011-06-28 11:26:03 -07001372 while self._retry_queue:
Brian Harring0be85c62012-03-17 19:52:12 -07001373 state = self._retry_queue.pop(0)
1374 if self._Schedule(state):
1375 self._Print("Retrying emerge of %s." % state.target)
David James8c7e5e32011-06-28 11:26:03 -07001376 break
David Jamesfcb70ef2011-02-02 16:02:30 -08001377
Brian Harringa43f5952012-04-12 01:19:34 -07001378 def _Shutdown(self):
David Jamesfcb70ef2011-02-02 16:02:30 -08001379 # Tell emerge workers to exit. They all exit when 'None' is pushed
1380 # to the queue.
Brian Harring0be85c62012-03-17 19:52:12 -07001381
Brian Harringa43f5952012-04-12 01:19:34 -07001382 # Shutdown the workers first; then jobs (which is how they feed things back)
1383 # then finally the print queue.
Brian Harring0be85c62012-03-17 19:52:12 -07001384
Brian Harringa43f5952012-04-12 01:19:34 -07001385 def _stop(queue, pool):
1386 if pool is None:
1387 return
1388 try:
1389 queue.put(None)
1390 pool.close()
1391 pool.join()
1392 finally:
1393 pool.terminate()
Brian Harring0be85c62012-03-17 19:52:12 -07001394
Brian Harringa43f5952012-04-12 01:19:34 -07001395 _stop(self._fetch_queue, self._fetch_pool)
1396 self._fetch_queue = self._fetch_pool = None
Brian Harring0be85c62012-03-17 19:52:12 -07001397
Brian Harringa43f5952012-04-12 01:19:34 -07001398 _stop(self._build_queue, self._build_pool)
1399 self._build_queue = self._build_pool = None
1400
1401 if self._job_queue is not None:
1402 self._job_queue.close()
1403 self._job_queue = None
David Jamesfcb70ef2011-02-02 16:02:30 -08001404
1405 # Now that our workers are finished, we can kill the print queue.
Brian Harringa43f5952012-04-12 01:19:34 -07001406 if self._print_worker is not None:
1407 try:
1408 self._print_queue.put(None)
1409 self._print_queue.close()
1410 self._print_worker.join()
1411 finally:
1412 self._print_worker.terminate()
1413 self._print_queue = self._print_worker = None
David Jamesfcb70ef2011-02-02 16:02:30 -08001414
1415 def Run(self):
1416 """Run through the scheduled ebuilds.
1417
1418 Keep running so long as we have uninstalled packages in the
1419 dependency graph to merge.
1420 """
Brian Harringa43f5952012-04-12 01:19:34 -07001421 if not self._deps_map:
1422 return
1423
Brian Harring0be85c62012-03-17 19:52:12 -07001424 # Start the fetchers.
1425 for _ in xrange(min(self._fetch_procs, len(self._fetch_ready))):
1426 state = self._fetch_ready.get()
1427 self._fetch_jobs[state.target] = None
1428 self._fetch_queue.put(state)
1429
1430 # Print an update, then get going.
1431 self._Status()
1432
David Jamese703d0f2012-01-12 16:27:45 -08001433 retried = set()
David Jamesfcb70ef2011-02-02 16:02:30 -08001434 while self._deps_map:
1435 # Check here that we are actually waiting for something.
Brian Harring0be85c62012-03-17 19:52:12 -07001436 if (self._build_queue.empty() and
David Jamesfcb70ef2011-02-02 16:02:30 -08001437 self._job_queue.empty() and
Brian Harring0be85c62012-03-17 19:52:12 -07001438 not self._fetch_jobs and
1439 not self._fetch_ready and
1440 not self._build_jobs and
1441 not self._build_ready and
David Jamesfcb70ef2011-02-02 16:02:30 -08001442 self._deps_map):
1443 # If we have failed on a package, retry it now.
1444 if self._retry_queue:
1445 self._Retry()
1446 else:
David Jamesfcb70ef2011-02-02 16:02:30 -08001447 # Tell the user why we're exiting.
1448 if self._failed:
Mike Frysingerf2ff9172012-11-01 18:47:41 -04001449 print 'Packages failed:\n\t%s' % '\n\t'.join(self._failed)
David James0eae23e2012-07-03 15:04:25 -07001450 status_file = os.environ.get("PARALLEL_EMERGE_STATUS_FILE")
1451 if status_file:
David James321490a2012-12-17 12:05:56 -08001452 failed_pkgs = set(portage.versions.cpv_getkey(x)
1453 for x in self._failed)
David James0eae23e2012-07-03 15:04:25 -07001454 with open(status_file, "a") as f:
1455 f.write("%s\n" % " ".join(failed_pkgs))
David Jamesfcb70ef2011-02-02 16:02:30 -08001456 else:
1457 print "Deadlock! Circular dependencies!"
1458 sys.exit(1)
1459
David James321490a2012-12-17 12:05:56 -08001460 for _ in xrange(12):
David Jamesa74289a2011-08-12 10:41:24 -07001461 try:
1462 job = self._job_queue.get(timeout=5)
1463 break
1464 except Queue.Empty:
1465 # Check if any more jobs can be scheduled.
1466 self._ScheduleLoop()
1467 else:
Brian Harring706747c2012-03-16 03:04:31 -07001468 # Print an update every 60 seconds.
David Jamesfcb70ef2011-02-02 16:02:30 -08001469 self._Status()
1470 continue
1471
1472 target = job.target
1473
Brian Harring0be85c62012-03-17 19:52:12 -07001474 if job.fetch_only:
1475 if not job.done:
1476 self._fetch_jobs[job.target] = job
1477 else:
1478 state = self._state_map[job.target]
1479 state.prefetched = True
1480 state.fetched_successfully = (job.retcode == 0)
1481 del self._fetch_jobs[job.target]
1482 self._Print("Fetched %s in %2.2fs"
1483 % (target, time.time() - job.start_timestamp))
1484
1485 if self._show_output or job.retcode != 0:
1486 self._print_queue.put(JobPrinter(job, unlink=True))
1487 else:
1488 os.unlink(job.filename)
1489 # Failure or not, let build work with it next.
1490 if not self._deps_map[job.target]["needs"]:
1491 self._build_ready.put(state)
1492 self._ScheduleLoop()
1493
1494 if self._fetch_ready:
1495 state = self._fetch_ready.get()
1496 self._fetch_queue.put(state)
1497 self._fetch_jobs[state.target] = None
1498 else:
1499 # Minor optimization; shut down fetchers early since we know
1500 # the queue is empty.
1501 self._fetch_queue.put(None)
1502 continue
1503
David Jamesfcb70ef2011-02-02 16:02:30 -08001504 if not job.done:
Brian Harring0be85c62012-03-17 19:52:12 -07001505 self._build_jobs[target] = job
David Jamesfcb70ef2011-02-02 16:02:30 -08001506 self._Print("Started %s (logged in %s)" % (target, job.filename))
1507 continue
1508
1509 # Print output of job
1510 if self._show_output or job.retcode != 0:
1511 self._print_queue.put(JobPrinter(job, unlink=True))
1512 else:
1513 os.unlink(job.filename)
Brian Harring0be85c62012-03-17 19:52:12 -07001514 del self._build_jobs[target]
David Jamesfcb70ef2011-02-02 16:02:30 -08001515
1516 seconds = time.time() - job.start_timestamp
1517 details = "%s (in %dm%.1fs)" % (target, seconds / 60, seconds % 60)
David James32420cc2011-08-25 21:32:46 -07001518 previously_failed = target in self._failed
David Jamesfcb70ef2011-02-02 16:02:30 -08001519
1520 # Complain if necessary.
1521 if job.retcode != 0:
1522 # Handle job failure.
David James32420cc2011-08-25 21:32:46 -07001523 if previously_failed:
David Jamesfcb70ef2011-02-02 16:02:30 -08001524 # If this job has failed previously, give up.
1525 self._Print("Failed %s. Your build has failed." % details)
1526 else:
1527 # Queue up this build to try again after a long while.
David Jamese703d0f2012-01-12 16:27:45 -08001528 retried.add(target)
Brian Harring0be85c62012-03-17 19:52:12 -07001529 self._retry_queue.append(self._state_map[target])
David Jamesfcb70ef2011-02-02 16:02:30 -08001530 self._failed.add(target)
1531 self._Print("Failed %s, retrying later." % details)
1532 else:
David James32420cc2011-08-25 21:32:46 -07001533 if previously_failed:
1534 # Remove target from list of failed packages.
1535 self._failed.remove(target)
1536
1537 self._Print("Completed %s" % details)
1538
1539 # Mark as completed and unblock waiting ebuilds.
1540 self._Finish(target)
1541
1542 if previously_failed and self._retry_queue:
David Jamesfcb70ef2011-02-02 16:02:30 -08001543 # If we have successfully retried a failed package, and there
1544 # are more failed packages, try the next one. We will only have
1545 # one retrying package actively running at a time.
1546 self._Retry()
1547
David Jamesfcb70ef2011-02-02 16:02:30 -08001548
David James8c7e5e32011-06-28 11:26:03 -07001549 # Schedule pending jobs and print an update.
1550 self._ScheduleLoop()
1551 self._Status()
David Jamesfcb70ef2011-02-02 16:02:30 -08001552
David Jamese703d0f2012-01-12 16:27:45 -08001553 # If packages were retried, output a warning.
1554 if retried:
1555 self._Print("")
1556 self._Print("WARNING: The following packages failed the first time,")
1557 self._Print("but succeeded upon retry. This might indicate incorrect")
1558 self._Print("dependencies.")
1559 for pkg in retried:
1560 self._Print(" %s" % pkg)
1561 self._Print("@@@STEP_WARNINGS@@@")
1562 self._Print("")
1563
David Jamesfcb70ef2011-02-02 16:02:30 -08001564 # Tell child threads to exit.
1565 self._Print("Merge complete")
David Jamesfcb70ef2011-02-02 16:02:30 -08001566
1567
Brian Harring30675052012-02-29 12:18:22 -08001568def main(argv):
Brian Harring8294d652012-05-23 02:20:52 -07001569 try:
1570 return real_main(argv)
1571 finally:
1572 # Work around multiprocessing sucking and not cleaning up after itself.
1573 # http://bugs.python.org/issue4106;
1574 # Step one; ensure GC is ran *prior* to the VM starting shutdown.
1575 gc.collect()
1576 # Step two; go looking for those threads and try to manually reap
1577 # them if we can.
1578 for x in threading.enumerate():
1579 # Filter on the name, and ident; if ident is None, the thread
1580 # wasn't started.
1581 if x.name == 'QueueFeederThread' and x.ident is not None:
1582 x.join(1)
David Jamesfcb70ef2011-02-02 16:02:30 -08001583
Brian Harring8294d652012-05-23 02:20:52 -07001584
1585def real_main(argv):
Brian Harring30675052012-02-29 12:18:22 -08001586 parallel_emerge_args = argv[:]
David Jamesfcb70ef2011-02-02 16:02:30 -08001587 deps = DepGraphGenerator()
Brian Harring30675052012-02-29 12:18:22 -08001588 deps.Initialize(parallel_emerge_args)
David Jamesfcb70ef2011-02-02 16:02:30 -08001589 emerge = deps.emerge
1590
1591 if emerge.action is not None:
Brian Harring30675052012-02-29 12:18:22 -08001592 argv = deps.ParseParallelEmergeArgs(argv)
Brian Harring8294d652012-05-23 02:20:52 -07001593 return emerge_main(argv)
David Jamesfcb70ef2011-02-02 16:02:30 -08001594 elif not emerge.cmdline_packages:
1595 Usage()
Brian Harring8294d652012-05-23 02:20:52 -07001596 return 1
David Jamesfcb70ef2011-02-02 16:02:30 -08001597
1598 # Unless we're in pretend mode, there's not much point running without
1599 # root access. We need to be able to install packages.
1600 #
1601 # NOTE: Even if you're running --pretend, it's a good idea to run
1602 # parallel_emerge with root access so that portage can write to the
1603 # dependency cache. This is important for performance.
David James321490a2012-12-17 12:05:56 -08001604 if "--pretend" not in emerge.opts and portage.data.secpass < 2:
David Jamesfcb70ef2011-02-02 16:02:30 -08001605 print "parallel_emerge: superuser access is required."
Brian Harring8294d652012-05-23 02:20:52 -07001606 return 1
David Jamesfcb70ef2011-02-02 16:02:30 -08001607
1608 if "--quiet" not in emerge.opts:
1609 cmdline_packages = " ".join(emerge.cmdline_packages)
David Jamesfcb70ef2011-02-02 16:02:30 -08001610 print "Starting fast-emerge."
1611 print " Building package %s on %s" % (cmdline_packages,
1612 deps.board or "root")
David Jamesfcb70ef2011-02-02 16:02:30 -08001613
David James386ccd12011-05-04 20:17:42 -07001614 deps_tree, deps_info = deps.GenDependencyTree()
David Jamesfcb70ef2011-02-02 16:02:30 -08001615
1616 # You want me to be verbose? I'll give you two trees! Twice as much value.
1617 if "--tree" in emerge.opts and "--verbose" in emerge.opts:
1618 deps.PrintTree(deps_tree)
1619
David James386ccd12011-05-04 20:17:42 -07001620 deps_graph = deps.GenDependencyGraph(deps_tree, deps_info)
David Jamesfcb70ef2011-02-02 16:02:30 -08001621
1622 # OK, time to print out our progress so far.
1623 deps.PrintInstallPlan(deps_graph)
1624 if "--tree" in emerge.opts:
1625 PrintDepsMap(deps_graph)
1626
1627 # Are we upgrading portage? If so, and there are more packages to merge,
1628 # schedule a restart of parallel_emerge to merge the rest. This ensures that
1629 # we pick up all updates to portage settings before merging any more
1630 # packages.
1631 portage_upgrade = False
1632 root = emerge.settings["ROOT"]
1633 final_db = emerge.depgraph._dynamic_config.mydbapi[root]
1634 if root == "/":
1635 for db_pkg in final_db.match_pkgs("sys-apps/portage"):
1636 portage_pkg = deps_graph.get(db_pkg.cpv)
David James0ff16f22012-11-02 14:18:07 -07001637 if portage_pkg:
David Jamesfcb70ef2011-02-02 16:02:30 -08001638 portage_upgrade = True
1639 if "--quiet" not in emerge.opts:
1640 print "Upgrading portage first, then restarting..."
1641
David James0ff16f22012-11-02 14:18:07 -07001642 # Upgrade Portage first, then the rest of the packages.
1643 #
1644 # In order to grant the child permission to run setsid, we need to run sudo
1645 # again. We preserve SUDO_USER here in case an ebuild depends on it.
1646 if portage_upgrade:
1647 # Calculate what arguments to use when re-invoking.
1648 args = ["sudo", "-E", "SUDO_USER=%s" % os.environ.get("SUDO_USER", "")]
1649 args += [os.path.abspath(sys.argv[0])] + parallel_emerge_args
1650 args += ["--exclude=sys-apps/portage"]
1651
1652 # First upgrade Portage.
1653 passthrough_args = ("--quiet", "--pretend", "--verbose")
1654 emerge_args = [k for k in emerge.opts if k in passthrough_args]
1655 ret = emerge_main(emerge_args + ["portage"])
1656 if ret != 0:
1657 return ret
1658
1659 # Now upgrade the rest.
1660 os.execvp(args[0], args)
1661
David Jamesfcb70ef2011-02-02 16:02:30 -08001662 # Run the queued emerges.
1663 scheduler = EmergeQueue(deps_graph, emerge, deps.package_db, deps.show_output)
Brian Harringa43f5952012-04-12 01:19:34 -07001664 try:
1665 scheduler.Run()
1666 finally:
1667 scheduler._Shutdown()
David James97ce8902011-08-16 09:51:05 -07001668 scheduler = None
David Jamesfcb70ef2011-02-02 16:02:30 -08001669
Mike Frysingerd20a6e22012-10-04 19:01:10 -04001670 clean_logs(emerge.settings)
1671
David Jamesfcb70ef2011-02-02 16:02:30 -08001672 print "Done"
Brian Harring8294d652012-05-23 02:20:52 -07001673 return 0