blob: 5e5833b00e6779ade0ffdc78a9462d16440478f0 [file] [log] [blame]
Mike Frysinger9f7e4ee2013-03-13 15:43:03 -04001#!/usr/bin/python
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
Mike Frysinger91d7da92013-02-19 15:53:46 -050073from portage.versions import vercmp
74
David Jamesfcb70ef2011-02-02 16:02:30 -080075
David Jamesfcb70ef2011-02-02 16:02:30 -080076def Usage():
77 """Print usage."""
78 print "Usage:"
David James386ccd12011-05-04 20:17:42 -070079 print " ./parallel_emerge [--board=BOARD] [--workon=PKGS]"
David Jamesfcb70ef2011-02-02 16:02:30 -080080 print " [--rebuild] [emerge args] package"
81 print
82 print "Packages specified as workon packages are always built from source."
David Jamesfcb70ef2011-02-02 16:02:30 -080083 print
84 print "The --workon argument is mainly useful when you want to build and"
85 print "install packages that you are working on unconditionally, but do not"
86 print "to have to rev the package to indicate you want to build it from"
87 print "source. The build_packages script will automatically supply the"
88 print "workon argument to emerge, ensuring that packages selected using"
89 print "cros-workon are rebuilt."
90 print
91 print "The --rebuild option rebuilds packages whenever their dependencies"
92 print "are changed. This ensures that your build is correct."
David Jamesfcb70ef2011-02-02 16:02:30 -080093
94
David Jamesfcb70ef2011-02-02 16:02:30 -080095# Global start time
96GLOBAL_START = time.time()
97
David James7358d032011-05-19 10:40:03 -070098# Whether process has been killed by a signal.
99KILLED = multiprocessing.Event()
100
David Jamesfcb70ef2011-02-02 16:02:30 -0800101
102class EmergeData(object):
103 """This simple struct holds various emerge variables.
104
105 This struct helps us easily pass emerge variables around as a unit.
106 These variables are used for calculating dependencies and installing
107 packages.
108 """
109
David Jamesbf1e3442011-05-28 07:44:20 -0700110 __slots__ = ["action", "cmdline_packages", "depgraph", "favorites",
111 "mtimedb", "opts", "root_config", "scheduler_graph",
112 "settings", "spinner", "trees"]
David Jamesfcb70ef2011-02-02 16:02:30 -0800113
114 def __init__(self):
115 # The action the user requested. If the user is installing packages, this
116 # is None. If the user is doing anything other than installing packages,
117 # this will contain the action name, which will map exactly to the
118 # long-form name of the associated emerge option.
119 #
120 # Example: If you call parallel_emerge --unmerge package, the action name
121 # will be "unmerge"
122 self.action = None
123
124 # The list of packages the user passed on the command-line.
125 self.cmdline_packages = None
126
127 # The emerge dependency graph. It'll contain all the packages involved in
128 # this merge, along with their versions.
129 self.depgraph = None
130
David Jamesbf1e3442011-05-28 07:44:20 -0700131 # The list of candidates to add to the world file.
132 self.favorites = None
133
David Jamesfcb70ef2011-02-02 16:02:30 -0800134 # A dict of the options passed to emerge. This dict has been cleaned up
135 # a bit by parse_opts, so that it's a bit easier for the emerge code to
136 # look at the options.
137 #
138 # Emerge takes a few shortcuts in its cleanup process to make parsing of
139 # the options dict easier. For example, if you pass in "--usepkg=n", the
140 # "--usepkg" flag is just left out of the dictionary altogether. Because
141 # --usepkg=n is the default, this makes parsing easier, because emerge
142 # can just assume that if "--usepkg" is in the dictionary, it's enabled.
143 #
144 # These cleanup processes aren't applied to all options. For example, the
145 # --with-bdeps flag is passed in as-is. For a full list of the cleanups
146 # applied by emerge, see the parse_opts function in the _emerge.main
147 # package.
148 self.opts = None
149
150 # A dictionary used by portage to maintain global state. This state is
151 # loaded from disk when portage starts up, and saved to disk whenever we
152 # call mtimedb.commit().
153 #
154 # This database contains information about global updates (i.e., what
155 # version of portage we have) and what we're currently doing. Portage
156 # saves what it is currently doing in this database so that it can be
157 # resumed when you call it with the --resume option.
158 #
159 # parallel_emerge does not save what it is currently doing in the mtimedb,
160 # so we do not support the --resume option.
161 self.mtimedb = None
162
163 # The portage configuration for our current root. This contains the portage
164 # settings (see below) and the three portage trees for our current root.
165 # (The three portage trees are explained below, in the documentation for
166 # the "trees" member.)
167 self.root_config = None
168
169 # The scheduler graph is used by emerge to calculate what packages to
170 # install. We don't actually install any deps, so this isn't really used,
171 # but we pass it in to the Scheduler object anyway.
172 self.scheduler_graph = None
173
174 # Portage settings for our current session. Most of these settings are set
175 # in make.conf inside our current install root.
176 self.settings = None
177
178 # The spinner, which spews stuff to stdout to indicate that portage is
179 # doing something. We maintain our own spinner, so we set the portage
180 # spinner to "silent" mode.
181 self.spinner = None
182
183 # The portage trees. There are separate portage trees for each root. To get
184 # the portage tree for the current root, you can look in self.trees[root],
185 # where root = self.settings["ROOT"].
186 #
187 # In each root, there are three trees: vartree, porttree, and bintree.
188 # - vartree: A database of the currently-installed packages.
189 # - porttree: A database of ebuilds, that can be used to build packages.
190 # - bintree: A database of binary packages.
191 self.trees = None
192
193
194class DepGraphGenerator(object):
195 """Grab dependency information about packages from portage.
196
197 Typical usage:
198 deps = DepGraphGenerator()
199 deps.Initialize(sys.argv[1:])
200 deps_tree, deps_info = deps.GenDependencyTree()
201 deps_graph = deps.GenDependencyGraph(deps_tree, deps_info)
202 deps.PrintTree(deps_tree)
203 PrintDepsMap(deps_graph)
204 """
205
David James386ccd12011-05-04 20:17:42 -0700206 __slots__ = ["board", "emerge", "package_db", "show_output"]
David Jamesfcb70ef2011-02-02 16:02:30 -0800207
208 def __init__(self):
209 self.board = None
210 self.emerge = EmergeData()
David Jamesfcb70ef2011-02-02 16:02:30 -0800211 self.package_db = {}
David Jamesfcb70ef2011-02-02 16:02:30 -0800212 self.show_output = False
David Jamesfcb70ef2011-02-02 16:02:30 -0800213
214 def ParseParallelEmergeArgs(self, argv):
215 """Read the parallel emerge arguments from the command-line.
216
217 We need to be compatible with emerge arg format. We scrape arguments that
218 are specific to parallel_emerge, and pass through the rest directly to
219 emerge.
220 Args:
221 argv: arguments list
222 Returns:
223 Arguments that don't belong to parallel_emerge
224 """
225 emerge_args = []
226 for arg in argv:
227 # Specifically match arguments that are specific to parallel_emerge, and
228 # pass through the rest.
229 if arg.startswith("--board="):
230 self.board = arg.replace("--board=", "")
231 elif arg.startswith("--workon="):
232 workon_str = arg.replace("--workon=", "")
David James7a1ea4b2011-10-13 15:06:41 -0700233 emerge_args.append("--reinstall-atoms=%s" % workon_str)
234 emerge_args.append("--usepkg-exclude=%s" % workon_str)
David Jamesfcb70ef2011-02-02 16:02:30 -0800235 elif arg.startswith("--force-remote-binary="):
236 force_remote_binary = arg.replace("--force-remote-binary=", "")
David James7a1ea4b2011-10-13 15:06:41 -0700237 emerge_args.append("--useoldpkg-atoms=%s" % force_remote_binary)
David Jamesfcb70ef2011-02-02 16:02:30 -0800238 elif arg == "--show-output":
239 self.show_output = True
David James386ccd12011-05-04 20:17:42 -0700240 elif arg == "--rebuild":
David James7a1ea4b2011-10-13 15:06:41 -0700241 emerge_args.append("--rebuild-if-unbuilt")
David Jamesfcb70ef2011-02-02 16:02:30 -0800242 else:
243 # Not one of our options, so pass through to emerge.
244 emerge_args.append(arg)
245
David James386ccd12011-05-04 20:17:42 -0700246 # These packages take a really long time to build, so, for expediency, we
247 # are blacklisting them from automatic rebuilds because one of their
248 # dependencies needs to be recompiled.
249 for pkg in ("chromeos-base/chromeos-chrome", "media-plugins/o3d",
250 "dev-java/icedtea"):
David James7a1ea4b2011-10-13 15:06:41 -0700251 emerge_args.append("--rebuild-exclude=%s" % pkg)
David Jamesfcb70ef2011-02-02 16:02:30 -0800252
253 return emerge_args
254
255 def Initialize(self, args):
256 """Initializer. Parses arguments and sets up portage state."""
257
258 # Parse and strip out args that are just intended for parallel_emerge.
259 emerge_args = self.ParseParallelEmergeArgs(args)
260
261 # Setup various environment variables based on our current board. These
262 # variables are normally setup inside emerge-${BOARD}, but since we don't
263 # call that script, we have to set it up here. These variables serve to
264 # point our tools at /build/BOARD and to setup cross compiles to the
265 # appropriate board as configured in toolchain.conf.
266 if self.board:
267 os.environ["PORTAGE_CONFIGROOT"] = "/build/" + self.board
268 os.environ["PORTAGE_SYSROOT"] = "/build/" + self.board
269 os.environ["SYSROOT"] = "/build/" + self.board
David Jamesfcb70ef2011-02-02 16:02:30 -0800270
271 # Although CHROMEOS_ROOT isn't specific to boards, it's normally setup
272 # inside emerge-${BOARD}, so we set it up here for compatibility. It
273 # will be going away soon as we migrate to CROS_WORKON_SRCROOT.
274 os.environ.setdefault("CHROMEOS_ROOT", os.environ["HOME"] + "/trunk")
275
276 # Turn off interactive delays
277 os.environ["EBEEP_IGNORE"] = "1"
278 os.environ["EPAUSE_IGNORE"] = "1"
Mike Frysinger0a647fc2012-08-06 14:36:05 -0400279 os.environ["CLEAN_DELAY"] = "0"
David Jamesfcb70ef2011-02-02 16:02:30 -0800280
281 # Parse the emerge options.
David Jamesea3ca332011-05-26 11:48:29 -0700282 action, opts, cmdline_packages = parse_opts(emerge_args, silent=True)
David Jamesfcb70ef2011-02-02 16:02:30 -0800283
284 # Set environment variables based on options. Portage normally sets these
285 # environment variables in emerge_main, but we can't use that function,
286 # because it also does a bunch of other stuff that we don't want.
287 # TODO(davidjames): Patch portage to move this logic into a function we can
288 # reuse here.
289 if "--debug" in opts:
290 os.environ["PORTAGE_DEBUG"] = "1"
291 if "--config-root" in opts:
292 os.environ["PORTAGE_CONFIGROOT"] = opts["--config-root"]
293 if "--root" in opts:
294 os.environ["ROOT"] = opts["--root"]
295 if "--accept-properties" in opts:
296 os.environ["ACCEPT_PROPERTIES"] = opts["--accept-properties"]
297
David Jamesfcb70ef2011-02-02 16:02:30 -0800298 # If we're installing packages to the board, and we're not using the
David James927a56d2012-04-03 11:26:39 -0700299 # official flag, we can disable vardb locks. This is safe because we
300 # only run up to one instance of parallel_emerge in parallel.
David Jamesfcb70ef2011-02-02 16:02:30 -0800301 if self.board and os.environ.get("CHROMEOS_OFFICIAL") != "1":
302 os.environ.setdefault("PORTAGE_LOCKS", "false")
David Jamesfcb70ef2011-02-02 16:02:30 -0800303
304 # Now that we've setup the necessary environment variables, we can load the
305 # emerge config from disk.
306 settings, trees, mtimedb = load_emerge_config()
307
David Jamesea3ca332011-05-26 11:48:29 -0700308 # Add in EMERGE_DEFAULT_OPTS, if specified.
309 tmpcmdline = []
310 if "--ignore-default-opts" not in opts:
311 tmpcmdline.extend(settings["EMERGE_DEFAULT_OPTS"].split())
312 tmpcmdline.extend(emerge_args)
313 action, opts, cmdline_packages = parse_opts(tmpcmdline)
314
315 # If we're installing to the board, we want the --root-deps option so that
316 # portage will install the build dependencies to that location as well.
317 if self.board:
318 opts.setdefault("--root-deps", True)
319
David Jamesfcb70ef2011-02-02 16:02:30 -0800320 # Check whether our portage tree is out of date. Typically, this happens
321 # when you're setting up a new portage tree, such as in setup_board and
322 # make_chroot. In that case, portage applies a bunch of global updates
323 # here. Once the updates are finished, we need to commit any changes
324 # that the global update made to our mtimedb, and reload the config.
325 #
326 # Portage normally handles this logic in emerge_main, but again, we can't
327 # use that function here.
328 if _global_updates(trees, mtimedb["updates"]):
329 mtimedb.commit()
330 settings, trees, mtimedb = load_emerge_config(trees=trees)
331
332 # Setup implied options. Portage normally handles this logic in
333 # emerge_main.
334 if "--buildpkgonly" in opts or "buildpkg" in settings.features:
335 opts.setdefault("--buildpkg", True)
336 if "--getbinpkgonly" in opts:
337 opts.setdefault("--usepkgonly", True)
338 opts.setdefault("--getbinpkg", True)
339 if "getbinpkg" in settings.features:
340 # Per emerge_main, FEATURES=getbinpkg overrides --getbinpkg=n
341 opts["--getbinpkg"] = True
342 if "--getbinpkg" in opts or "--usepkgonly" in opts:
343 opts.setdefault("--usepkg", True)
344 if "--fetch-all-uri" in opts:
345 opts.setdefault("--fetchonly", True)
346 if "--skipfirst" in opts:
347 opts.setdefault("--resume", True)
348 if "--buildpkgonly" in opts:
349 # --buildpkgonly will not merge anything, so it overrides all binary
350 # package options.
351 for opt in ("--getbinpkg", "--getbinpkgonly",
352 "--usepkg", "--usepkgonly"):
353 opts.pop(opt, None)
354 if (settings.get("PORTAGE_DEBUG", "") == "1" and
355 "python-trace" in settings.features):
356 portage.debug.set_trace(True)
357
358 # Complain about unsupported options
David James386ccd12011-05-04 20:17:42 -0700359 for opt in ("--ask", "--ask-enter-invalid", "--resume", "--skipfirst"):
David Jamesfcb70ef2011-02-02 16:02:30 -0800360 if opt in opts:
361 print "%s is not supported by parallel_emerge" % opt
362 sys.exit(1)
363
364 # Make emerge specific adjustments to the config (e.g. colors!)
365 adjust_configs(opts, trees)
366
367 # Save our configuration so far in the emerge object
368 emerge = self.emerge
369 emerge.action, emerge.opts = action, opts
370 emerge.settings, emerge.trees, emerge.mtimedb = settings, trees, mtimedb
371 emerge.cmdline_packages = cmdline_packages
372 root = settings["ROOT"]
373 emerge.root_config = trees[root]["root_config"]
374
David James386ccd12011-05-04 20:17:42 -0700375 if "--usepkg" in opts:
David Jamesfcb70ef2011-02-02 16:02:30 -0800376 emerge.trees[root]["bintree"].populate("--getbinpkg" in opts)
377
David Jamesfcb70ef2011-02-02 16:02:30 -0800378 def CreateDepgraph(self, emerge, packages):
379 """Create an emerge depgraph object."""
380 # Setup emerge options.
381 emerge_opts = emerge.opts.copy()
382
David James386ccd12011-05-04 20:17:42 -0700383 # Ask portage to build a dependency graph. with the options we specified
384 # above.
David Jamesfcb70ef2011-02-02 16:02:30 -0800385 params = create_depgraph_params(emerge_opts, emerge.action)
David Jamesbf1e3442011-05-28 07:44:20 -0700386 success, depgraph, favorites = backtrack_depgraph(
David James386ccd12011-05-04 20:17:42 -0700387 emerge.settings, emerge.trees, emerge_opts, params, emerge.action,
388 packages, emerge.spinner)
389 emerge.depgraph = depgraph
David Jamesfcb70ef2011-02-02 16:02:30 -0800390
David James386ccd12011-05-04 20:17:42 -0700391 # Is it impossible to honor the user's request? Bail!
392 if not success:
393 depgraph.display_problems()
394 sys.exit(1)
David Jamesfcb70ef2011-02-02 16:02:30 -0800395
396 emerge.depgraph = depgraph
David Jamesbf1e3442011-05-28 07:44:20 -0700397 emerge.favorites = favorites
David Jamesfcb70ef2011-02-02 16:02:30 -0800398
David Jamesdeebd692011-05-09 17:02:52 -0700399 # Prime and flush emerge caches.
400 root = emerge.settings["ROOT"]
401 vardb = emerge.trees[root]["vartree"].dbapi
David James0bdc5de2011-05-12 16:22:26 -0700402 if "--pretend" not in emerge.opts:
403 vardb.counter_tick()
David Jamesdeebd692011-05-09 17:02:52 -0700404 vardb.flush_cache()
405
David James386ccd12011-05-04 20:17:42 -0700406 def GenDependencyTree(self):
David Jamesfcb70ef2011-02-02 16:02:30 -0800407 """Get dependency tree info from emerge.
408
David Jamesfcb70ef2011-02-02 16:02:30 -0800409 Returns:
410 Dependency tree
411 """
412 start = time.time()
413
414 emerge = self.emerge
415
416 # Create a list of packages to merge
417 packages = set(emerge.cmdline_packages[:])
David Jamesfcb70ef2011-02-02 16:02:30 -0800418
419 # Tell emerge to be quiet. We print plenty of info ourselves so we don't
420 # need any extra output from portage.
421 portage.util.noiselimit = -1
422
423 # My favorite feature: The silent spinner. It doesn't spin. Ever.
424 # I'd disable the colors by default too, but they look kind of cool.
425 emerge.spinner = stdout_spinner()
426 emerge.spinner.update = emerge.spinner.update_quiet
427
428 if "--quiet" not in emerge.opts:
429 print "Calculating deps..."
430
431 self.CreateDepgraph(emerge, packages)
432 depgraph = emerge.depgraph
433
434 # Build our own tree from the emerge digraph.
435 deps_tree = {}
436 digraph = depgraph._dynamic_config.digraph
David James3f778802011-08-25 19:31:45 -0700437 root = emerge.settings["ROOT"]
438 final_db = depgraph._dynamic_config.mydbapi[root]
David Jamesfcb70ef2011-02-02 16:02:30 -0800439 for node, node_deps in digraph.nodes.items():
440 # Calculate dependency packages that need to be installed first. Each
441 # child on the digraph is a dependency. The "operation" field specifies
442 # what we're doing (e.g. merge, uninstall, etc.). The "priorities" array
443 # contains the type of dependency (e.g. build, runtime, runtime_post,
444 # etc.)
445 #
David Jamesfcb70ef2011-02-02 16:02:30 -0800446 # Portage refers to the identifiers for packages as a CPV. This acronym
447 # stands for Component/Path/Version.
448 #
449 # Here's an example CPV: chromeos-base/power_manager-0.0.1-r1
450 # Split up, this CPV would be:
451 # C -- Component: chromeos-base
452 # P -- Path: power_manager
453 # V -- Version: 0.0.1-r1
454 #
455 # We just refer to CPVs as packages here because it's easier.
456 deps = {}
457 for child, priorities in node_deps[0].items():
David James3f778802011-08-25 19:31:45 -0700458 if isinstance(child, Package) and child.root == root:
459 cpv = str(child.cpv)
460 action = str(child.operation)
461
462 # If we're uninstalling a package, check whether Portage is
463 # installing a replacement. If so, just depend on the installation
464 # of the new package, because the old package will automatically
465 # be uninstalled at that time.
466 if action == "uninstall":
467 for pkg in final_db.match_pkgs(child.slot_atom):
468 cpv = str(pkg.cpv)
469 action = "merge"
470 break
471
472 deps[cpv] = dict(action=action,
473 deptypes=[str(x) for x in priorities],
474 deps={})
David Jamesfcb70ef2011-02-02 16:02:30 -0800475
476 # We've built our list of deps, so we can add our package to the tree.
David James3f778802011-08-25 19:31:45 -0700477 if isinstance(node, Package) and node.root == root:
David Jamesfcb70ef2011-02-02 16:02:30 -0800478 deps_tree[str(node.cpv)] = dict(action=str(node.operation),
479 deps=deps)
480
David Jamesfcb70ef2011-02-02 16:02:30 -0800481 # Ask portage for its install plan, so that we can only throw out
David James386ccd12011-05-04 20:17:42 -0700482 # dependencies that portage throws out.
David Jamesfcb70ef2011-02-02 16:02:30 -0800483 deps_info = {}
484 for pkg in depgraph.altlist():
485 if isinstance(pkg, Package):
David James3f778802011-08-25 19:31:45 -0700486 assert pkg.root == root
David Jamesfcb70ef2011-02-02 16:02:30 -0800487 self.package_db[pkg.cpv] = pkg
488
David Jamesfcb70ef2011-02-02 16:02:30 -0800489 # Save off info about the package
David James386ccd12011-05-04 20:17:42 -0700490 deps_info[str(pkg.cpv)] = {"idx": len(deps_info)}
David Jamesfcb70ef2011-02-02 16:02:30 -0800491
492 seconds = time.time() - start
493 if "--quiet" not in emerge.opts:
494 print "Deps calculated in %dm%.1fs" % (seconds / 60, seconds % 60)
495
496 return deps_tree, deps_info
497
498 def PrintTree(self, deps, depth=""):
499 """Print the deps we have seen in the emerge output.
500
501 Args:
502 deps: Dependency tree structure.
503 depth: Allows printing the tree recursively, with indentation.
504 """
505 for entry in sorted(deps):
506 action = deps[entry]["action"]
507 print "%s %s (%s)" % (depth, entry, action)
508 self.PrintTree(deps[entry]["deps"], depth=depth + " ")
509
David James386ccd12011-05-04 20:17:42 -0700510 def GenDependencyGraph(self, deps_tree, deps_info):
David Jamesfcb70ef2011-02-02 16:02:30 -0800511 """Generate a doubly linked dependency graph.
512
513 Args:
514 deps_tree: Dependency tree structure.
515 deps_info: More details on the dependencies.
516 Returns:
517 Deps graph in the form of a dict of packages, with each package
518 specifying a "needs" list and "provides" list.
519 """
520 emerge = self.emerge
David Jamesfcb70ef2011-02-02 16:02:30 -0800521
David Jamesfcb70ef2011-02-02 16:02:30 -0800522 # deps_map is the actual dependency graph.
523 #
524 # Each package specifies a "needs" list and a "provides" list. The "needs"
525 # list indicates which packages we depend on. The "provides" list
526 # indicates the reverse dependencies -- what packages need us.
527 #
528 # We also provide some other information in the dependency graph:
529 # - action: What we're planning on doing with this package. Generally,
530 # "merge", "nomerge", or "uninstall"
David Jamesfcb70ef2011-02-02 16:02:30 -0800531 deps_map = {}
532
533 def ReverseTree(packages):
534 """Convert tree to digraph.
535
536 Take the tree of package -> requirements and reverse it to a digraph of
537 buildable packages -> packages they unblock.
538 Args:
539 packages: Tree(s) of dependencies.
540 Returns:
541 Unsanitized digraph.
542 """
David James8c7e5e32011-06-28 11:26:03 -0700543 binpkg_phases = set(["setup", "preinst", "postinst"])
David James3f778802011-08-25 19:31:45 -0700544 needed_dep_types = set(["blocker", "buildtime", "runtime"])
David Jamesfcb70ef2011-02-02 16:02:30 -0800545 for pkg in packages:
546
547 # Create an entry for the package
548 action = packages[pkg]["action"]
David James8c7e5e32011-06-28 11:26:03 -0700549 default_pkg = {"needs": {}, "provides": set(), "action": action,
550 "nodeps": False, "binary": False}
David Jamesfcb70ef2011-02-02 16:02:30 -0800551 this_pkg = deps_map.setdefault(pkg, default_pkg)
552
David James8c7e5e32011-06-28 11:26:03 -0700553 if pkg in deps_info:
554 this_pkg["idx"] = deps_info[pkg]["idx"]
555
556 # If a package doesn't have any defined phases that might use the
557 # dependent packages (i.e. pkg_setup, pkg_preinst, or pkg_postinst),
558 # we can install this package before its deps are ready.
559 emerge_pkg = self.package_db.get(pkg)
560 if emerge_pkg and emerge_pkg.type_name == "binary":
561 this_pkg["binary"] = True
Mike Frysinger91d7da92013-02-19 15:53:46 -0500562 if 0 <= vercmp(portage.VERSION, "2.1.11.50"):
563 defined_phases = emerge_pkg.defined_phases
564 else:
565 defined_phases = emerge_pkg.metadata.defined_phases
David James8c7e5e32011-06-28 11:26:03 -0700566 defined_binpkg_phases = binpkg_phases.intersection(defined_phases)
567 if not defined_binpkg_phases:
568 this_pkg["nodeps"] = True
569
David Jamesfcb70ef2011-02-02 16:02:30 -0800570 # Create entries for dependencies of this package first.
571 ReverseTree(packages[pkg]["deps"])
572
573 # Add dependencies to this package.
574 for dep, dep_item in packages[pkg]["deps"].iteritems():
David James8c7e5e32011-06-28 11:26:03 -0700575 # We only need to enforce strict ordering of dependencies if the
David James3f778802011-08-25 19:31:45 -0700576 # dependency is a blocker, or is a buildtime or runtime dependency.
577 # (I.e., ignored, optional, and runtime_post dependencies don't
578 # depend on ordering.)
David James8c7e5e32011-06-28 11:26:03 -0700579 dep_types = dep_item["deptypes"]
580 if needed_dep_types.intersection(dep_types):
581 deps_map[dep]["provides"].add(pkg)
582 this_pkg["needs"][dep] = "/".join(dep_types)
David Jamesfcb70ef2011-02-02 16:02:30 -0800583
David James3f778802011-08-25 19:31:45 -0700584 # If there's a blocker, Portage may need to move files from one
585 # package to another, which requires editing the CONTENTS files of
586 # both packages. To avoid race conditions while editing this file,
587 # the two packages must not be installed in parallel, so we can't
588 # safely ignore dependencies. See http://crosbug.com/19328
589 if "blocker" in dep_types:
590 this_pkg["nodeps"] = False
591
David Jamesfcb70ef2011-02-02 16:02:30 -0800592 def FindCycles():
593 """Find cycles in the dependency tree.
594
595 Returns:
596 A dict mapping cyclic packages to a dict of the deps that cause
597 cycles. For each dep that causes cycles, it returns an example
598 traversal of the graph that shows the cycle.
599 """
600
601 def FindCyclesAtNode(pkg, cycles, unresolved, resolved):
602 """Find cycles in cyclic dependencies starting at specified package.
603
604 Args:
605 pkg: Package identifier.
606 cycles: A dict mapping cyclic packages to a dict of the deps that
607 cause cycles. For each dep that causes cycles, it returns an
608 example traversal of the graph that shows the cycle.
609 unresolved: Nodes that have been visited but are not fully processed.
610 resolved: Nodes that have been visited and are fully processed.
611 """
612 pkg_cycles = cycles.get(pkg)
613 if pkg in resolved and not pkg_cycles:
614 # If we already looked at this package, and found no cyclic
615 # dependencies, we can stop now.
616 return
617 unresolved.append(pkg)
618 for dep in deps_map[pkg]["needs"]:
619 if dep in unresolved:
620 idx = unresolved.index(dep)
621 mycycle = unresolved[idx:] + [dep]
David James321490a2012-12-17 12:05:56 -0800622 for i in xrange(len(mycycle) - 1):
David Jamesfcb70ef2011-02-02 16:02:30 -0800623 pkg1, pkg2 = mycycle[i], mycycle[i+1]
624 cycles.setdefault(pkg1, {}).setdefault(pkg2, mycycle)
625 elif not pkg_cycles or dep not in pkg_cycles:
626 # Looks like we haven't seen this edge before.
627 FindCyclesAtNode(dep, cycles, unresolved, resolved)
628 unresolved.pop()
629 resolved.add(pkg)
630
631 cycles, unresolved, resolved = {}, [], set()
632 for pkg in deps_map:
633 FindCyclesAtNode(pkg, cycles, unresolved, resolved)
634 return cycles
635
David James386ccd12011-05-04 20:17:42 -0700636 def RemoveUnusedPackages():
David Jamesfcb70ef2011-02-02 16:02:30 -0800637 """Remove installed packages, propagating dependencies."""
David Jamesfcb70ef2011-02-02 16:02:30 -0800638 # Schedule packages that aren't on the install list for removal
639 rm_pkgs = set(deps_map.keys()) - set(deps_info.keys())
640
David Jamesfcb70ef2011-02-02 16:02:30 -0800641 # Remove the packages we don't want, simplifying the graph and making
642 # it easier for us to crack cycles.
643 for pkg in sorted(rm_pkgs):
644 this_pkg = deps_map[pkg]
645 needs = this_pkg["needs"]
646 provides = this_pkg["provides"]
647 for dep in needs:
648 dep_provides = deps_map[dep]["provides"]
649 dep_provides.update(provides)
650 dep_provides.discard(pkg)
651 dep_provides.discard(dep)
652 for target in provides:
653 target_needs = deps_map[target]["needs"]
654 target_needs.update(needs)
655 target_needs.pop(pkg, None)
656 target_needs.pop(target, None)
657 del deps_map[pkg]
658
659 def PrintCycleBreak(basedep, dep, mycycle):
660 """Print details about a cycle that we are planning on breaking.
661
662 We are breaking a cycle where dep needs basedep. mycycle is an
663 example cycle which contains dep -> basedep."""
664
David Jamesfcb70ef2011-02-02 16:02:30 -0800665 needs = deps_map[dep]["needs"]
666 depinfo = needs.get(basedep, "deleted")
David Jamesfcb70ef2011-02-02 16:02:30 -0800667
David James3f778802011-08-25 19:31:45 -0700668 # It's OK to swap install order for blockers, as long as the two
669 # packages aren't installed in parallel. If there is a cycle, then
670 # we know the packages depend on each other already, so we can drop the
671 # blocker safely without printing a warning.
672 if depinfo == "blocker":
673 return
674
David Jamesfcb70ef2011-02-02 16:02:30 -0800675 # Notify the user that we're breaking a cycle.
676 print "Breaking %s -> %s (%s)" % (dep, basedep, depinfo)
677
678 # Show cycle.
David James321490a2012-12-17 12:05:56 -0800679 for i in xrange(len(mycycle) - 1):
David Jamesfcb70ef2011-02-02 16:02:30 -0800680 pkg1, pkg2 = mycycle[i], mycycle[i+1]
681 needs = deps_map[pkg1]["needs"]
682 depinfo = needs.get(pkg2, "deleted")
683 if pkg1 == dep and pkg2 == basedep:
684 depinfo = depinfo + ", deleting"
685 print " %s -> %s (%s)" % (pkg1, pkg2, depinfo)
686
687 def SanitizeTree():
688 """Remove circular dependencies.
689
690 We prune all dependencies involved in cycles that go against the emerge
691 ordering. This has a nice property: we're guaranteed to merge
692 dependencies in the same order that portage does.
693
694 Because we don't treat any dependencies as "soft" unless they're killed
695 by a cycle, we pay attention to a larger number of dependencies when
696 merging. This hurts performance a bit, but helps reliability.
697 """
698 start = time.time()
699 cycles = FindCycles()
700 while cycles:
701 for dep, mycycles in cycles.iteritems():
702 for basedep, mycycle in mycycles.iteritems():
703 if deps_info[basedep]["idx"] >= deps_info[dep]["idx"]:
Matt Tennant08797302011-10-17 16:18:45 -0700704 if "--quiet" not in emerge.opts:
705 PrintCycleBreak(basedep, dep, mycycle)
David Jamesfcb70ef2011-02-02 16:02:30 -0800706 del deps_map[dep]["needs"][basedep]
707 deps_map[basedep]["provides"].remove(dep)
708 cycles = FindCycles()
709 seconds = time.time() - start
710 if "--quiet" not in emerge.opts and seconds >= 0.1:
711 print "Tree sanitized in %dm%.1fs" % (seconds / 60, seconds % 60)
712
David James8c7e5e32011-06-28 11:26:03 -0700713 def FindRecursiveProvides(pkg, seen):
714 """Find all nodes that require a particular package.
715
716 Assumes that graph is acyclic.
717
718 Args:
719 pkg: Package identifier.
720 seen: Nodes that have been visited so far.
721 """
722 if pkg in seen:
723 return
724 seen.add(pkg)
725 info = deps_map[pkg]
726 info["tprovides"] = info["provides"].copy()
727 for dep in info["provides"]:
728 FindRecursiveProvides(dep, seen)
729 info["tprovides"].update(deps_map[dep]["tprovides"])
730
David Jamesa22906f2011-05-04 19:53:26 -0700731 ReverseTree(deps_tree)
David Jamesa22906f2011-05-04 19:53:26 -0700732
David James386ccd12011-05-04 20:17:42 -0700733 # We need to remove unused packages so that we can use the dependency
734 # ordering of the install process to show us what cycles to crack.
735 RemoveUnusedPackages()
David Jamesfcb70ef2011-02-02 16:02:30 -0800736 SanitizeTree()
David James8c7e5e32011-06-28 11:26:03 -0700737 seen = set()
738 for pkg in deps_map:
739 FindRecursiveProvides(pkg, seen)
David Jamesfcb70ef2011-02-02 16:02:30 -0800740 return deps_map
741
742 def PrintInstallPlan(self, deps_map):
743 """Print an emerge-style install plan.
744
745 The install plan lists what packages we're installing, in order.
746 It's useful for understanding what parallel_emerge is doing.
747
748 Args:
749 deps_map: The dependency graph.
750 """
751
752 def InstallPlanAtNode(target, deps_map):
753 nodes = []
754 nodes.append(target)
755 for dep in deps_map[target]["provides"]:
756 del deps_map[dep]["needs"][target]
757 if not deps_map[dep]["needs"]:
758 nodes.extend(InstallPlanAtNode(dep, deps_map))
759 return nodes
760
761 deps_map = copy.deepcopy(deps_map)
762 install_plan = []
763 plan = set()
764 for target, info in deps_map.iteritems():
765 if not info["needs"] and target not in plan:
766 for item in InstallPlanAtNode(target, deps_map):
767 plan.add(item)
768 install_plan.append(self.package_db[item])
769
770 for pkg in plan:
771 del deps_map[pkg]
772
773 if deps_map:
774 print "Cyclic dependencies:", " ".join(deps_map)
775 PrintDepsMap(deps_map)
776 sys.exit(1)
777
778 self.emerge.depgraph.display(install_plan)
779
780
781def PrintDepsMap(deps_map):
782 """Print dependency graph, for each package list it's prerequisites."""
783 for i in sorted(deps_map):
784 print "%s: (%s) needs" % (i, deps_map[i]["action"])
785 needs = deps_map[i]["needs"]
786 for j in sorted(needs):
787 print " %s" % (j)
788 if not needs:
789 print " no dependencies"
790
791
792class EmergeJobState(object):
793 __slots__ = ["done", "filename", "last_notify_timestamp", "last_output_seek",
794 "last_output_timestamp", "pkgname", "retcode", "start_timestamp",
Brian Harring0be85c62012-03-17 19:52:12 -0700795 "target", "fetch_only"]
David Jamesfcb70ef2011-02-02 16:02:30 -0800796
797 def __init__(self, target, pkgname, done, filename, start_timestamp,
Brian Harring0be85c62012-03-17 19:52:12 -0700798 retcode=None, fetch_only=False):
David Jamesfcb70ef2011-02-02 16:02:30 -0800799
800 # The full name of the target we're building (e.g.
801 # chromeos-base/chromeos-0.0.1-r60)
802 self.target = target
803
804 # The short name of the target we're building (e.g. chromeos-0.0.1-r60)
805 self.pkgname = pkgname
806
807 # Whether the job is done. (True if the job is done; false otherwise.)
808 self.done = done
809
810 # The filename where output is currently stored.
811 self.filename = filename
812
813 # The timestamp of the last time we printed the name of the log file. We
814 # print this at the beginning of the job, so this starts at
815 # start_timestamp.
816 self.last_notify_timestamp = start_timestamp
817
818 # The location (in bytes) of the end of the last complete line we printed.
819 # This starts off at zero. We use this to jump to the right place when we
820 # print output from the same ebuild multiple times.
821 self.last_output_seek = 0
822
823 # The timestamp of the last time we printed output. Since we haven't
824 # printed output yet, this starts at zero.
825 self.last_output_timestamp = 0
826
827 # The return code of our job, if the job is actually finished.
828 self.retcode = retcode
829
Brian Harring0be85c62012-03-17 19:52:12 -0700830 # Was this just a fetch job?
831 self.fetch_only = fetch_only
832
David Jamesfcb70ef2011-02-02 16:02:30 -0800833 # The timestamp when our job started.
834 self.start_timestamp = start_timestamp
835
836
David James321490a2012-12-17 12:05:56 -0800837def KillHandler(_signum, _frame):
David James7358d032011-05-19 10:40:03 -0700838 # Kill self and all subprocesses.
839 os.killpg(0, signal.SIGKILL)
840
David Jamesfcb70ef2011-02-02 16:02:30 -0800841def SetupWorkerSignals():
David James321490a2012-12-17 12:05:56 -0800842 def ExitHandler(_signum, _frame):
David James7358d032011-05-19 10:40:03 -0700843 # Set KILLED flag.
844 KILLED.set()
David James13cead42011-05-18 16:22:01 -0700845
David James7358d032011-05-19 10:40:03 -0700846 # Remove our signal handlers so we don't get called recursively.
847 signal.signal(signal.SIGINT, KillHandler)
848 signal.signal(signal.SIGTERM, KillHandler)
David Jamesfcb70ef2011-02-02 16:02:30 -0800849
850 # Ensure that we exit quietly and cleanly, if possible, when we receive
851 # SIGTERM or SIGINT signals. By default, when the user hits CTRL-C, all
852 # of the child processes will print details about KeyboardInterrupt
853 # exceptions, which isn't very helpful.
854 signal.signal(signal.SIGINT, ExitHandler)
855 signal.signal(signal.SIGTERM, ExitHandler)
856
David James6b29d052012-11-02 10:27:27 -0700857def EmergeProcess(output, *args, **kwargs):
David James1ed3e252011-10-05 20:26:15 -0700858 """Merge a package in a subprocess.
859
860 Args:
David James1ed3e252011-10-05 20:26:15 -0700861 output: Temporary file to write output.
David James6b29d052012-11-02 10:27:27 -0700862 *args: Arguments to pass to Scheduler constructor.
863 **kwargs: Keyword arguments to pass to Scheduler constructor.
David James1ed3e252011-10-05 20:26:15 -0700864
865 Returns:
866 The exit code returned by the subprocess.
867 """
868 pid = os.fork()
869 if pid == 0:
870 try:
871 # Sanity checks.
872 if sys.stdout.fileno() != 1: raise Exception("sys.stdout.fileno() != 1")
873 if sys.stderr.fileno() != 2: raise Exception("sys.stderr.fileno() != 2")
874
875 # - Redirect 1 (stdout) and 2 (stderr) at our temporary file.
876 # - Redirect 0 to point at sys.stdin. In this case, sys.stdin
877 # points at a file reading os.devnull, because multiprocessing mucks
878 # with sys.stdin.
879 # - Leave the sys.stdin and output filehandles alone.
880 fd_pipes = {0: sys.stdin.fileno(),
881 1: output.fileno(),
882 2: output.fileno(),
883 sys.stdin.fileno(): sys.stdin.fileno(),
884 output.fileno(): output.fileno()}
David Jamesa249eef2013-07-19 14:03:45 -0700885 if 0 <= vercmp(portage.VERSION, "2.1.11.50"):
886 portage.process._setup_pipes(fd_pipes, close_fds=False)
887 else:
888 portage.process._setup_pipes(fd_pipes)
David James1ed3e252011-10-05 20:26:15 -0700889
890 # Portage doesn't like when sys.stdin.fileno() != 0, so point sys.stdin
891 # at the filehandle we just created in _setup_pipes.
892 if sys.stdin.fileno() != 0:
David James6b29d052012-11-02 10:27:27 -0700893 sys.__stdin__ = sys.stdin = os.fdopen(0, "r")
894
895 scheduler = Scheduler(*args, **kwargs)
896
897 # Enable blocker handling even though we're in --nodeps mode. This
898 # allows us to unmerge the blocker after we've merged the replacement.
899 scheduler._opts_ignore_blockers = frozenset()
David James1ed3e252011-10-05 20:26:15 -0700900
901 # Actually do the merge.
902 retval = scheduler.merge()
903
904 # We catch all exceptions here (including SystemExit, KeyboardInterrupt,
905 # etc) so as to ensure that we don't confuse the multiprocessing module,
906 # which expects that all forked children exit with os._exit().
David James321490a2012-12-17 12:05:56 -0800907 # pylint: disable=W0702
David James1ed3e252011-10-05 20:26:15 -0700908 except:
909 traceback.print_exc(file=output)
910 retval = 1
911 sys.stdout.flush()
912 sys.stderr.flush()
913 output.flush()
914 os._exit(retval)
915 else:
916 # Return the exit code of the subprocess.
917 return os.waitpid(pid, 0)[1]
David Jamesfcb70ef2011-02-02 16:02:30 -0800918
Brian Harring0be85c62012-03-17 19:52:12 -0700919def EmergeWorker(task_queue, job_queue, emerge, package_db, fetch_only=False):
David Jamesfcb70ef2011-02-02 16:02:30 -0800920 """This worker emerges any packages given to it on the task_queue.
921
922 Args:
923 task_queue: The queue of tasks for this worker to do.
924 job_queue: The queue of results from the worker.
925 emerge: An EmergeData() object.
926 package_db: A dict, mapping package ids to portage Package objects.
Brian Harring0be85c62012-03-17 19:52:12 -0700927 fetch_only: A bool, indicating if we should just fetch the target.
David Jamesfcb70ef2011-02-02 16:02:30 -0800928
929 It expects package identifiers to be passed to it via task_queue. When
930 a task is started, it pushes the (target, filename) to the started_queue.
931 The output is stored in filename. When a merge starts or finishes, we push
932 EmergeJobState objects to the job_queue.
933 """
934
935 SetupWorkerSignals()
936 settings, trees, mtimedb = emerge.settings, emerge.trees, emerge.mtimedb
David Jamesdeebd692011-05-09 17:02:52 -0700937
938 # Disable flushing of caches to save on I/O.
David James7a1ea4b2011-10-13 15:06:41 -0700939 root = emerge.settings["ROOT"]
940 vardb = emerge.trees[root]["vartree"].dbapi
941 vardb._flush_cache_enabled = False
Brian Harring0be85c62012-03-17 19:52:12 -0700942 bindb = emerge.trees[root]["bintree"].dbapi
943 # Might be a set, might be a list, might be None; no clue, just use shallow
944 # copy to ensure we can roll it back.
945 original_remotepkgs = copy.copy(bindb.bintree._remotepkgs)
David Jamesdeebd692011-05-09 17:02:52 -0700946
David Jamesfcb70ef2011-02-02 16:02:30 -0800947 opts, spinner = emerge.opts, emerge.spinner
948 opts["--nodeps"] = True
Brian Harring0be85c62012-03-17 19:52:12 -0700949 if fetch_only:
950 opts["--fetchonly"] = True
951
David Jamesfcb70ef2011-02-02 16:02:30 -0800952 while True:
953 # Wait for a new item to show up on the queue. This is a blocking wait,
954 # so if there's nothing to do, we just sit here.
Brian Harring0be85c62012-03-17 19:52:12 -0700955 pkg_state = task_queue.get()
956 if pkg_state is None:
David Jamesfcb70ef2011-02-02 16:02:30 -0800957 # If target is None, this means that the main thread wants us to quit.
958 # The other workers need to exit too, so we'll push the message back on
959 # to the queue so they'll get it too.
Brian Harring0be85c62012-03-17 19:52:12 -0700960 task_queue.put(None)
David Jamesfcb70ef2011-02-02 16:02:30 -0800961 return
David James7358d032011-05-19 10:40:03 -0700962 if KILLED.is_set():
963 return
964
Brian Harring0be85c62012-03-17 19:52:12 -0700965 target = pkg_state.target
966
David Jamesfcb70ef2011-02-02 16:02:30 -0800967 db_pkg = package_db[target]
Brian Harring0be85c62012-03-17 19:52:12 -0700968
969 if db_pkg.type_name == "binary":
970 if not fetch_only and pkg_state.fetched_successfully:
971 # Ensure portage doesn't think our pkg is remote- else it'll force
972 # a redownload of it (even if the on-disk file is fine). In-memory
973 # caching basically, implemented dumbly.
974 bindb.bintree._remotepkgs = None
975 else:
976 bindb.bintree_remotepkgs = original_remotepkgs
977
David Jamesfcb70ef2011-02-02 16:02:30 -0800978 db_pkg.root_config = emerge.root_config
979 install_list = [db_pkg]
980 pkgname = db_pkg.pf
981 output = tempfile.NamedTemporaryFile(prefix=pkgname + "-", delete=False)
David James01b1e0f2012-06-07 17:18:05 -0700982 os.chmod(output.name, 644)
David Jamesfcb70ef2011-02-02 16:02:30 -0800983 start_timestamp = time.time()
Brian Harring0be85c62012-03-17 19:52:12 -0700984 job = EmergeJobState(target, pkgname, False, output.name, start_timestamp,
985 fetch_only=fetch_only)
David Jamesfcb70ef2011-02-02 16:02:30 -0800986 job_queue.put(job)
987 if "--pretend" in opts:
988 retcode = 0
989 else:
David Jamesfcb70ef2011-02-02 16:02:30 -0800990 try:
David James386ccd12011-05-04 20:17:42 -0700991 emerge.scheduler_graph.mergelist = install_list
David James6b29d052012-11-02 10:27:27 -0700992 retcode = EmergeProcess(output, settings, trees, mtimedb, opts,
993 spinner, favorites=emerge.favorites,
994 graph_config=emerge.scheduler_graph)
David Jamesfcb70ef2011-02-02 16:02:30 -0800995 except Exception:
996 traceback.print_exc(file=output)
997 retcode = 1
David James1ed3e252011-10-05 20:26:15 -0700998 output.close()
David Jamesfcb70ef2011-02-02 16:02:30 -0800999
David James7358d032011-05-19 10:40:03 -07001000 if KILLED.is_set():
1001 return
1002
David Jamesfcb70ef2011-02-02 16:02:30 -08001003 job = EmergeJobState(target, pkgname, True, output.name, start_timestamp,
Brian Harring0be85c62012-03-17 19:52:12 -07001004 retcode, fetch_only=fetch_only)
David Jamesfcb70ef2011-02-02 16:02:30 -08001005 job_queue.put(job)
1006
1007
1008class LinePrinter(object):
1009 """Helper object to print a single line."""
1010
1011 def __init__(self, line):
1012 self.line = line
1013
David James321490a2012-12-17 12:05:56 -08001014 def Print(self, _seek_locations):
David Jamesfcb70ef2011-02-02 16:02:30 -08001015 print self.line
1016
1017
1018class JobPrinter(object):
1019 """Helper object to print output of a job."""
1020
1021 def __init__(self, job, unlink=False):
1022 """Print output of job.
1023
1024 If unlink is True, unlink the job output file when done."""
1025 self.current_time = time.time()
1026 self.job = job
1027 self.unlink = unlink
1028
1029 def Print(self, seek_locations):
1030
1031 job = self.job
1032
1033 # Calculate how long the job has been running.
1034 seconds = self.current_time - job.start_timestamp
1035
1036 # Note that we've printed out the job so far.
1037 job.last_output_timestamp = self.current_time
1038
1039 # Note that we're starting the job
1040 info = "job %s (%dm%.1fs)" % (job.pkgname, seconds / 60, seconds % 60)
1041 last_output_seek = seek_locations.get(job.filename, 0)
1042 if last_output_seek:
1043 print "=== Continue output for %s ===" % info
1044 else:
1045 print "=== Start output for %s ===" % info
1046
1047 # Print actual output from job
1048 f = codecs.open(job.filename, encoding='utf-8', errors='replace')
1049 f.seek(last_output_seek)
1050 prefix = job.pkgname + ":"
1051 for line in f:
1052
1053 # Save off our position in the file
1054 if line and line[-1] == "\n":
1055 last_output_seek = f.tell()
1056 line = line[:-1]
1057
1058 # Print our line
1059 print prefix, line.encode('utf-8', 'replace')
1060 f.close()
1061
1062 # Save our last spot in the file so that we don't print out the same
1063 # location twice.
1064 seek_locations[job.filename] = last_output_seek
1065
1066 # Note end of output section
1067 if job.done:
1068 print "=== Complete: %s ===" % info
1069 else:
1070 print "=== Still running: %s ===" % info
1071
1072 if self.unlink:
1073 os.unlink(job.filename)
1074
1075
1076def PrintWorker(queue):
1077 """A worker that prints stuff to the screen as requested."""
1078
David James321490a2012-12-17 12:05:56 -08001079 def ExitHandler(_signum, _frame):
David James7358d032011-05-19 10:40:03 -07001080 # Set KILLED flag.
1081 KILLED.set()
1082
David Jamesfcb70ef2011-02-02 16:02:30 -08001083 # Switch to default signal handlers so that we'll die after two signals.
David James7358d032011-05-19 10:40:03 -07001084 signal.signal(signal.SIGINT, KillHandler)
1085 signal.signal(signal.SIGTERM, KillHandler)
David Jamesfcb70ef2011-02-02 16:02:30 -08001086
1087 # Don't exit on the first SIGINT / SIGTERM, because the parent worker will
1088 # handle it and tell us when we need to exit.
1089 signal.signal(signal.SIGINT, ExitHandler)
1090 signal.signal(signal.SIGTERM, ExitHandler)
1091
1092 # seek_locations is a map indicating the position we are at in each file.
1093 # It starts off empty, but is set by the various Print jobs as we go along
1094 # to indicate where we left off in each file.
1095 seek_locations = {}
1096 while True:
1097 try:
1098 job = queue.get()
1099 if job:
1100 job.Print(seek_locations)
David Jamesbccf8eb2011-07-27 14:06:06 -07001101 sys.stdout.flush()
David Jamesfcb70ef2011-02-02 16:02:30 -08001102 else:
1103 break
1104 except IOError as ex:
1105 if ex.errno == errno.EINTR:
1106 # Looks like we received a signal. Keep printing.
1107 continue
1108 raise
1109
Brian Harring867e2362012-03-17 04:05:17 -07001110
Brian Harring0be85c62012-03-17 19:52:12 -07001111class TargetState(object):
Brian Harring867e2362012-03-17 04:05:17 -07001112
Brian Harring0be85c62012-03-17 19:52:12 -07001113 __slots__ = ("target", "info", "score", "prefetched", "fetched_successfully")
Brian Harring867e2362012-03-17 04:05:17 -07001114
David James321490a2012-12-17 12:05:56 -08001115 def __init__(self, target, info):
Brian Harring867e2362012-03-17 04:05:17 -07001116 self.target, self.info = target, info
Brian Harring0be85c62012-03-17 19:52:12 -07001117 self.fetched_successfully = False
1118 self.prefetched = False
David James321490a2012-12-17 12:05:56 -08001119 self.score = None
Brian Harring867e2362012-03-17 04:05:17 -07001120 self.update_score()
1121
1122 def __cmp__(self, other):
1123 return cmp(self.score, other.score)
1124
1125 def update_score(self):
1126 self.score = (
1127 -len(self.info["tprovides"]),
Brian Harring0be85c62012-03-17 19:52:12 -07001128 len(self.info["needs"]),
Brian Harring11c5eeb2012-03-18 11:02:39 -07001129 not self.info["binary"],
Brian Harring867e2362012-03-17 04:05:17 -07001130 -len(self.info["provides"]),
1131 self.info["idx"],
1132 self.target,
1133 )
1134
1135
1136class ScoredHeap(object):
1137
Brian Harring0be85c62012-03-17 19:52:12 -07001138 __slots__ = ("heap", "_heap_set")
1139
Brian Harring867e2362012-03-17 04:05:17 -07001140 def __init__(self, initial=()):
Brian Harring0be85c62012-03-17 19:52:12 -07001141 self.heap = list()
1142 self._heap_set = set()
1143 if initial:
1144 self.multi_put(initial)
Brian Harring867e2362012-03-17 04:05:17 -07001145
1146 def get(self):
Brian Harring0be85c62012-03-17 19:52:12 -07001147 item = heapq.heappop(self.heap)
1148 self._heap_set.remove(item.target)
1149 return item
Brian Harring867e2362012-03-17 04:05:17 -07001150
Brian Harring0be85c62012-03-17 19:52:12 -07001151 def put(self, item):
1152 if not isinstance(item, TargetState):
1153 raise ValueError("Item %r isn't a TargetState" % (item,))
1154 heapq.heappush(self.heap, item)
1155 self._heap_set.add(item.target)
Brian Harring867e2362012-03-17 04:05:17 -07001156
Brian Harring0be85c62012-03-17 19:52:12 -07001157 def multi_put(self, sequence):
1158 sequence = list(sequence)
1159 self.heap.extend(sequence)
1160 self._heap_set.update(x.target for x in sequence)
Brian Harring867e2362012-03-17 04:05:17 -07001161 self.sort()
1162
David James5c9996d2012-03-24 10:50:46 -07001163 def sort(self):
1164 heapq.heapify(self.heap)
1165
Brian Harring0be85c62012-03-17 19:52:12 -07001166 def __contains__(self, target):
1167 return target in self._heap_set
1168
1169 def __nonzero__(self):
1170 return bool(self.heap)
1171
Brian Harring867e2362012-03-17 04:05:17 -07001172 def __len__(self):
1173 return len(self.heap)
1174
1175
David Jamesfcb70ef2011-02-02 16:02:30 -08001176class EmergeQueue(object):
1177 """Class to schedule emerge jobs according to a dependency graph."""
1178
1179 def __init__(self, deps_map, emerge, package_db, show_output):
1180 # Store the dependency graph.
1181 self._deps_map = deps_map
Brian Harring0be85c62012-03-17 19:52:12 -07001182 self._state_map = {}
David Jamesfcb70ef2011-02-02 16:02:30 -08001183 # Initialize the running queue to empty
Brian Harring0be85c62012-03-17 19:52:12 -07001184 self._build_jobs = {}
1185 self._build_ready = ScoredHeap()
1186 self._fetch_jobs = {}
1187 self._fetch_ready = ScoredHeap()
David Jamesfcb70ef2011-02-02 16:02:30 -08001188 # List of total package installs represented in deps_map.
1189 install_jobs = [x for x in deps_map if deps_map[x]["action"] == "merge"]
1190 self._total_jobs = len(install_jobs)
1191 self._show_output = show_output
1192
1193 if "--pretend" in emerge.opts:
1194 print "Skipping merge because of --pretend mode."
1195 sys.exit(0)
1196
David James7358d032011-05-19 10:40:03 -07001197 # Set a process group so we can easily terminate all children.
1198 os.setsid()
1199
David Jamesfcb70ef2011-02-02 16:02:30 -08001200 # Setup scheduler graph object. This is used by the child processes
1201 # to help schedule jobs.
1202 emerge.scheduler_graph = emerge.depgraph.schedulerGraph()
1203
1204 # Calculate how many jobs we can run in parallel. We don't want to pass
1205 # the --jobs flag over to emerge itself, because that'll tell emerge to
1206 # hide its output, and said output is quite useful for debugging hung
1207 # jobs.
1208 procs = min(self._total_jobs,
1209 emerge.opts.pop("--jobs", multiprocessing.cpu_count()))
David James7746e112013-02-24 19:32:50 -08001210 self._build_procs = self._fetch_procs = max(1, procs)
David James8c7e5e32011-06-28 11:26:03 -07001211 self._load_avg = emerge.opts.pop("--load-average", None)
David Jamesfcb70ef2011-02-02 16:02:30 -08001212 self._job_queue = multiprocessing.Queue()
1213 self._print_queue = multiprocessing.Queue()
Brian Harring0be85c62012-03-17 19:52:12 -07001214
1215 self._fetch_queue = multiprocessing.Queue()
1216 args = (self._fetch_queue, self._job_queue, emerge, package_db, True)
1217 self._fetch_pool = multiprocessing.Pool(self._fetch_procs, EmergeWorker,
1218 args)
1219
1220 self._build_queue = multiprocessing.Queue()
1221 args = (self._build_queue, self._job_queue, emerge, package_db)
1222 self._build_pool = multiprocessing.Pool(self._build_procs, EmergeWorker,
1223 args)
1224
David Jamesfcb70ef2011-02-02 16:02:30 -08001225 self._print_worker = multiprocessing.Process(target=PrintWorker,
1226 args=[self._print_queue])
1227 self._print_worker.start()
1228
1229 # Initialize the failed queue to empty.
1230 self._retry_queue = []
1231 self._failed = set()
1232
David Jamesfcb70ef2011-02-02 16:02:30 -08001233 # Setup an exit handler so that we print nice messages if we are
1234 # terminated.
1235 self._SetupExitHandler()
1236
1237 # Schedule our jobs.
Brian Harring0be85c62012-03-17 19:52:12 -07001238 self._state_map.update(
1239 (pkg, TargetState(pkg, data)) for pkg, data in deps_map.iteritems())
1240 self._fetch_ready.multi_put(self._state_map.itervalues())
David Jamesfcb70ef2011-02-02 16:02:30 -08001241
1242 def _SetupExitHandler(self):
1243
David James321490a2012-12-17 12:05:56 -08001244 def ExitHandler(signum, _frame):
David James7358d032011-05-19 10:40:03 -07001245 # Set KILLED flag.
1246 KILLED.set()
David Jamesfcb70ef2011-02-02 16:02:30 -08001247
1248 # Kill our signal handlers so we don't get called recursively
David James7358d032011-05-19 10:40:03 -07001249 signal.signal(signal.SIGINT, KillHandler)
1250 signal.signal(signal.SIGTERM, KillHandler)
David Jamesfcb70ef2011-02-02 16:02:30 -08001251
1252 # Print our current job status
Brian Harring0be85c62012-03-17 19:52:12 -07001253 for job in self._build_jobs.itervalues():
David Jamesfcb70ef2011-02-02 16:02:30 -08001254 if job:
1255 self._print_queue.put(JobPrinter(job, unlink=True))
1256
1257 # Notify the user that we are exiting
1258 self._Print("Exiting on signal %s" % signum)
David James7358d032011-05-19 10:40:03 -07001259 self._print_queue.put(None)
1260 self._print_worker.join()
David Jamesfcb70ef2011-02-02 16:02:30 -08001261
1262 # Kill child threads, then exit.
David James7358d032011-05-19 10:40:03 -07001263 os.killpg(0, signal.SIGKILL)
David Jamesfcb70ef2011-02-02 16:02:30 -08001264 sys.exit(1)
1265
1266 # Print out job status when we are killed
1267 signal.signal(signal.SIGINT, ExitHandler)
1268 signal.signal(signal.SIGTERM, ExitHandler)
1269
Brian Harring0be85c62012-03-17 19:52:12 -07001270 def _Schedule(self, pkg_state):
David Jamesfcb70ef2011-02-02 16:02:30 -08001271 # We maintain a tree of all deps, if this doesn't need
David James8c7e5e32011-06-28 11:26:03 -07001272 # to be installed just free up its children and continue.
David Jamesfcb70ef2011-02-02 16:02:30 -08001273 # It is possible to reinstall deps of deps, without reinstalling
1274 # first level deps, like so:
1275 # chromeos (merge) -> eselect (nomerge) -> python (merge)
Brian Harring0be85c62012-03-17 19:52:12 -07001276 this_pkg = pkg_state.info
1277 target = pkg_state.target
1278 if pkg_state.info is not None:
1279 if this_pkg["action"] == "nomerge":
1280 self._Finish(target)
1281 elif target not in self._build_jobs:
1282 # Kick off the build if it's marked to be built.
1283 self._build_jobs[target] = None
1284 self._build_queue.put(pkg_state)
1285 return True
David Jamesfcb70ef2011-02-02 16:02:30 -08001286
David James8c7e5e32011-06-28 11:26:03 -07001287 def _ScheduleLoop(self):
1288 # If the current load exceeds our desired load average, don't schedule
1289 # more than one job.
1290 if self._load_avg and os.getloadavg()[0] > self._load_avg:
1291 needed_jobs = 1
1292 else:
Brian Harring0be85c62012-03-17 19:52:12 -07001293 needed_jobs = self._build_procs
David James8c7e5e32011-06-28 11:26:03 -07001294
1295 # Schedule more jobs.
Brian Harring0be85c62012-03-17 19:52:12 -07001296 while self._build_ready and len(self._build_jobs) < needed_jobs:
1297 state = self._build_ready.get()
1298 if state.target not in self._failed:
1299 self._Schedule(state)
David Jamesfcb70ef2011-02-02 16:02:30 -08001300
1301 def _Print(self, line):
1302 """Print a single line."""
1303 self._print_queue.put(LinePrinter(line))
1304
1305 def _Status(self):
1306 """Print status."""
1307 current_time = time.time()
1308 no_output = True
1309
1310 # Print interim output every minute if --show-output is used. Otherwise,
1311 # print notifications about running packages every 2 minutes, and print
1312 # full output for jobs that have been running for 60 minutes or more.
1313 if self._show_output:
1314 interval = 60
1315 notify_interval = 0
1316 else:
1317 interval = 60 * 60
1318 notify_interval = 60 * 2
David James321490a2012-12-17 12:05:56 -08001319 for job in self._build_jobs.itervalues():
David Jamesfcb70ef2011-02-02 16:02:30 -08001320 if job:
1321 last_timestamp = max(job.start_timestamp, job.last_output_timestamp)
1322 if last_timestamp + interval < current_time:
1323 self._print_queue.put(JobPrinter(job))
1324 job.last_output_timestamp = current_time
1325 no_output = False
1326 elif (notify_interval and
1327 job.last_notify_timestamp + notify_interval < current_time):
1328 job_seconds = current_time - job.start_timestamp
1329 args = (job.pkgname, job_seconds / 60, job_seconds % 60, job.filename)
1330 info = "Still building %s (%dm%.1fs). Logs in %s" % args
1331 job.last_notify_timestamp = current_time
1332 self._Print(info)
1333 no_output = False
1334
1335 # If we haven't printed any messages yet, print a general status message
1336 # here.
1337 if no_output:
1338 seconds = current_time - GLOBAL_START
Brian Harring0be85c62012-03-17 19:52:12 -07001339 fjobs, fready = len(self._fetch_jobs), len(self._fetch_ready)
1340 bjobs, bready = len(self._build_jobs), len(self._build_ready)
1341 retries = len(self._retry_queue)
1342 pending = max(0, len(self._deps_map) - fjobs - bjobs)
1343 line = "Pending %s/%s, " % (pending, self._total_jobs)
1344 if fjobs or fready:
1345 line += "Fetching %s/%s, " % (fjobs, fready + fjobs)
1346 if bjobs or bready or retries:
1347 line += "Building %s/%s, " % (bjobs, bready + bjobs)
1348 if retries:
1349 line += "Retrying %s, " % (retries,)
David James8c7e5e32011-06-28 11:26:03 -07001350 load = " ".join(str(x) for x in os.getloadavg())
Brian Harring0be85c62012-03-17 19:52:12 -07001351 line += ("[Time %dm%.1fs Load %s]" % (seconds/60, seconds %60, load))
1352 self._Print(line)
David Jamesfcb70ef2011-02-02 16:02:30 -08001353
1354 def _Finish(self, target):
David James8c7e5e32011-06-28 11:26:03 -07001355 """Mark a target as completed and unblock dependencies."""
1356 this_pkg = self._deps_map[target]
1357 if this_pkg["needs"] and this_pkg["nodeps"]:
1358 # We got installed, but our deps have not been installed yet. Dependent
1359 # packages should only be installed when our needs have been fully met.
1360 this_pkg["action"] = "nomerge"
1361 else:
David James8c7e5e32011-06-28 11:26:03 -07001362 for dep in this_pkg["provides"]:
1363 dep_pkg = self._deps_map[dep]
Brian Harring0be85c62012-03-17 19:52:12 -07001364 state = self._state_map[dep]
David James8c7e5e32011-06-28 11:26:03 -07001365 del dep_pkg["needs"][target]
Brian Harring0be85c62012-03-17 19:52:12 -07001366 state.update_score()
1367 if not state.prefetched:
1368 if dep in self._fetch_ready:
1369 # If it's not currently being fetched, update the prioritization
1370 self._fetch_ready.sort()
1371 elif not dep_pkg["needs"]:
David James8c7e5e32011-06-28 11:26:03 -07001372 if dep_pkg["nodeps"] and dep_pkg["action"] == "nomerge":
1373 self._Finish(dep)
1374 else:
Brian Harring0be85c62012-03-17 19:52:12 -07001375 self._build_ready.put(self._state_map[dep])
David James8c7e5e32011-06-28 11:26:03 -07001376 self._deps_map.pop(target)
David Jamesfcb70ef2011-02-02 16:02:30 -08001377
1378 def _Retry(self):
David James8c7e5e32011-06-28 11:26:03 -07001379 while self._retry_queue:
Brian Harring0be85c62012-03-17 19:52:12 -07001380 state = self._retry_queue.pop(0)
1381 if self._Schedule(state):
1382 self._Print("Retrying emerge of %s." % state.target)
David James8c7e5e32011-06-28 11:26:03 -07001383 break
David Jamesfcb70ef2011-02-02 16:02:30 -08001384
Brian Harringa43f5952012-04-12 01:19:34 -07001385 def _Shutdown(self):
David Jamesfcb70ef2011-02-02 16:02:30 -08001386 # Tell emerge workers to exit. They all exit when 'None' is pushed
1387 # to the queue.
Brian Harring0be85c62012-03-17 19:52:12 -07001388
Brian Harringa43f5952012-04-12 01:19:34 -07001389 # Shutdown the workers first; then jobs (which is how they feed things back)
1390 # then finally the print queue.
Brian Harring0be85c62012-03-17 19:52:12 -07001391
Brian Harringa43f5952012-04-12 01:19:34 -07001392 def _stop(queue, pool):
1393 if pool is None:
1394 return
1395 try:
1396 queue.put(None)
1397 pool.close()
1398 pool.join()
1399 finally:
1400 pool.terminate()
Brian Harring0be85c62012-03-17 19:52:12 -07001401
Brian Harringa43f5952012-04-12 01:19:34 -07001402 _stop(self._fetch_queue, self._fetch_pool)
1403 self._fetch_queue = self._fetch_pool = None
Brian Harring0be85c62012-03-17 19:52:12 -07001404
Brian Harringa43f5952012-04-12 01:19:34 -07001405 _stop(self._build_queue, self._build_pool)
1406 self._build_queue = self._build_pool = None
1407
1408 if self._job_queue is not None:
1409 self._job_queue.close()
1410 self._job_queue = None
David Jamesfcb70ef2011-02-02 16:02:30 -08001411
1412 # Now that our workers are finished, we can kill the print queue.
Brian Harringa43f5952012-04-12 01:19:34 -07001413 if self._print_worker is not None:
1414 try:
1415 self._print_queue.put(None)
1416 self._print_queue.close()
1417 self._print_worker.join()
1418 finally:
1419 self._print_worker.terminate()
1420 self._print_queue = self._print_worker = None
David Jamesfcb70ef2011-02-02 16:02:30 -08001421
1422 def Run(self):
1423 """Run through the scheduled ebuilds.
1424
1425 Keep running so long as we have uninstalled packages in the
1426 dependency graph to merge.
1427 """
Brian Harringa43f5952012-04-12 01:19:34 -07001428 if not self._deps_map:
1429 return
1430
Brian Harring0be85c62012-03-17 19:52:12 -07001431 # Start the fetchers.
1432 for _ in xrange(min(self._fetch_procs, len(self._fetch_ready))):
1433 state = self._fetch_ready.get()
1434 self._fetch_jobs[state.target] = None
1435 self._fetch_queue.put(state)
1436
1437 # Print an update, then get going.
1438 self._Status()
1439
David Jamese703d0f2012-01-12 16:27:45 -08001440 retried = set()
David Jamesfcb70ef2011-02-02 16:02:30 -08001441 while self._deps_map:
1442 # Check here that we are actually waiting for something.
Brian Harring0be85c62012-03-17 19:52:12 -07001443 if (self._build_queue.empty() and
David Jamesfcb70ef2011-02-02 16:02:30 -08001444 self._job_queue.empty() and
Brian Harring0be85c62012-03-17 19:52:12 -07001445 not self._fetch_jobs and
1446 not self._fetch_ready and
1447 not self._build_jobs and
1448 not self._build_ready and
David Jamesfcb70ef2011-02-02 16:02:30 -08001449 self._deps_map):
1450 # If we have failed on a package, retry it now.
1451 if self._retry_queue:
1452 self._Retry()
1453 else:
David Jamesfcb70ef2011-02-02 16:02:30 -08001454 # Tell the user why we're exiting.
1455 if self._failed:
Mike Frysingerf2ff9172012-11-01 18:47:41 -04001456 print 'Packages failed:\n\t%s' % '\n\t'.join(self._failed)
David James0eae23e2012-07-03 15:04:25 -07001457 status_file = os.environ.get("PARALLEL_EMERGE_STATUS_FILE")
1458 if status_file:
David James321490a2012-12-17 12:05:56 -08001459 failed_pkgs = set(portage.versions.cpv_getkey(x)
1460 for x in self._failed)
David James0eae23e2012-07-03 15:04:25 -07001461 with open(status_file, "a") as f:
1462 f.write("%s\n" % " ".join(failed_pkgs))
David Jamesfcb70ef2011-02-02 16:02:30 -08001463 else:
1464 print "Deadlock! Circular dependencies!"
1465 sys.exit(1)
1466
David James321490a2012-12-17 12:05:56 -08001467 for _ in xrange(12):
David Jamesa74289a2011-08-12 10:41:24 -07001468 try:
1469 job = self._job_queue.get(timeout=5)
1470 break
1471 except Queue.Empty:
1472 # Check if any more jobs can be scheduled.
1473 self._ScheduleLoop()
1474 else:
Brian Harring706747c2012-03-16 03:04:31 -07001475 # Print an update every 60 seconds.
David Jamesfcb70ef2011-02-02 16:02:30 -08001476 self._Status()
1477 continue
1478
1479 target = job.target
1480
Brian Harring0be85c62012-03-17 19:52:12 -07001481 if job.fetch_only:
1482 if not job.done:
1483 self._fetch_jobs[job.target] = job
1484 else:
1485 state = self._state_map[job.target]
1486 state.prefetched = True
1487 state.fetched_successfully = (job.retcode == 0)
1488 del self._fetch_jobs[job.target]
1489 self._Print("Fetched %s in %2.2fs"
1490 % (target, time.time() - job.start_timestamp))
1491
1492 if self._show_output or job.retcode != 0:
1493 self._print_queue.put(JobPrinter(job, unlink=True))
1494 else:
1495 os.unlink(job.filename)
1496 # Failure or not, let build work with it next.
1497 if not self._deps_map[job.target]["needs"]:
1498 self._build_ready.put(state)
1499 self._ScheduleLoop()
1500
1501 if self._fetch_ready:
1502 state = self._fetch_ready.get()
1503 self._fetch_queue.put(state)
1504 self._fetch_jobs[state.target] = None
1505 else:
1506 # Minor optimization; shut down fetchers early since we know
1507 # the queue is empty.
1508 self._fetch_queue.put(None)
1509 continue
1510
David Jamesfcb70ef2011-02-02 16:02:30 -08001511 if not job.done:
Brian Harring0be85c62012-03-17 19:52:12 -07001512 self._build_jobs[target] = job
David Jamesfcb70ef2011-02-02 16:02:30 -08001513 self._Print("Started %s (logged in %s)" % (target, job.filename))
1514 continue
1515
1516 # Print output of job
1517 if self._show_output or job.retcode != 0:
1518 self._print_queue.put(JobPrinter(job, unlink=True))
1519 else:
1520 os.unlink(job.filename)
Brian Harring0be85c62012-03-17 19:52:12 -07001521 del self._build_jobs[target]
David Jamesfcb70ef2011-02-02 16:02:30 -08001522
1523 seconds = time.time() - job.start_timestamp
1524 details = "%s (in %dm%.1fs)" % (target, seconds / 60, seconds % 60)
David James32420cc2011-08-25 21:32:46 -07001525 previously_failed = target in self._failed
David Jamesfcb70ef2011-02-02 16:02:30 -08001526
1527 # Complain if necessary.
1528 if job.retcode != 0:
1529 # Handle job failure.
David James32420cc2011-08-25 21:32:46 -07001530 if previously_failed:
David Jamesfcb70ef2011-02-02 16:02:30 -08001531 # If this job has failed previously, give up.
1532 self._Print("Failed %s. Your build has failed." % details)
1533 else:
1534 # Queue up this build to try again after a long while.
David Jamese703d0f2012-01-12 16:27:45 -08001535 retried.add(target)
Brian Harring0be85c62012-03-17 19:52:12 -07001536 self._retry_queue.append(self._state_map[target])
David Jamesfcb70ef2011-02-02 16:02:30 -08001537 self._failed.add(target)
1538 self._Print("Failed %s, retrying later." % details)
1539 else:
David James32420cc2011-08-25 21:32:46 -07001540 if previously_failed:
1541 # Remove target from list of failed packages.
1542 self._failed.remove(target)
1543
1544 self._Print("Completed %s" % details)
1545
1546 # Mark as completed and unblock waiting ebuilds.
1547 self._Finish(target)
1548
1549 if previously_failed and self._retry_queue:
David Jamesfcb70ef2011-02-02 16:02:30 -08001550 # If we have successfully retried a failed package, and there
1551 # are more failed packages, try the next one. We will only have
1552 # one retrying package actively running at a time.
1553 self._Retry()
1554
David Jamesfcb70ef2011-02-02 16:02:30 -08001555
David James8c7e5e32011-06-28 11:26:03 -07001556 # Schedule pending jobs and print an update.
1557 self._ScheduleLoop()
1558 self._Status()
David Jamesfcb70ef2011-02-02 16:02:30 -08001559
David Jamese703d0f2012-01-12 16:27:45 -08001560 # If packages were retried, output a warning.
1561 if retried:
1562 self._Print("")
1563 self._Print("WARNING: The following packages failed the first time,")
1564 self._Print("but succeeded upon retry. This might indicate incorrect")
1565 self._Print("dependencies.")
1566 for pkg in retried:
1567 self._Print(" %s" % pkg)
1568 self._Print("@@@STEP_WARNINGS@@@")
1569 self._Print("")
1570
David Jamesfcb70ef2011-02-02 16:02:30 -08001571 # Tell child threads to exit.
1572 self._Print("Merge complete")
David Jamesfcb70ef2011-02-02 16:02:30 -08001573
1574
Brian Harring30675052012-02-29 12:18:22 -08001575def main(argv):
Brian Harring8294d652012-05-23 02:20:52 -07001576 try:
1577 return real_main(argv)
1578 finally:
1579 # Work around multiprocessing sucking and not cleaning up after itself.
1580 # http://bugs.python.org/issue4106;
1581 # Step one; ensure GC is ran *prior* to the VM starting shutdown.
1582 gc.collect()
1583 # Step two; go looking for those threads and try to manually reap
1584 # them if we can.
1585 for x in threading.enumerate():
1586 # Filter on the name, and ident; if ident is None, the thread
1587 # wasn't started.
1588 if x.name == 'QueueFeederThread' and x.ident is not None:
1589 x.join(1)
David Jamesfcb70ef2011-02-02 16:02:30 -08001590
Brian Harring8294d652012-05-23 02:20:52 -07001591
1592def real_main(argv):
Brian Harring30675052012-02-29 12:18:22 -08001593 parallel_emerge_args = argv[:]
David Jamesfcb70ef2011-02-02 16:02:30 -08001594 deps = DepGraphGenerator()
Brian Harring30675052012-02-29 12:18:22 -08001595 deps.Initialize(parallel_emerge_args)
David Jamesfcb70ef2011-02-02 16:02:30 -08001596 emerge = deps.emerge
1597
1598 if emerge.action is not None:
Brian Harring30675052012-02-29 12:18:22 -08001599 argv = deps.ParseParallelEmergeArgs(argv)
Brian Harring8294d652012-05-23 02:20:52 -07001600 return emerge_main(argv)
David Jamesfcb70ef2011-02-02 16:02:30 -08001601 elif not emerge.cmdline_packages:
1602 Usage()
Brian Harring8294d652012-05-23 02:20:52 -07001603 return 1
David Jamesfcb70ef2011-02-02 16:02:30 -08001604
1605 # Unless we're in pretend mode, there's not much point running without
1606 # root access. We need to be able to install packages.
1607 #
1608 # NOTE: Even if you're running --pretend, it's a good idea to run
1609 # parallel_emerge with root access so that portage can write to the
1610 # dependency cache. This is important for performance.
David James321490a2012-12-17 12:05:56 -08001611 if "--pretend" not in emerge.opts and portage.data.secpass < 2:
David Jamesfcb70ef2011-02-02 16:02:30 -08001612 print "parallel_emerge: superuser access is required."
Brian Harring8294d652012-05-23 02:20:52 -07001613 return 1
David Jamesfcb70ef2011-02-02 16:02:30 -08001614
1615 if "--quiet" not in emerge.opts:
1616 cmdline_packages = " ".join(emerge.cmdline_packages)
David Jamesfcb70ef2011-02-02 16:02:30 -08001617 print "Starting fast-emerge."
1618 print " Building package %s on %s" % (cmdline_packages,
1619 deps.board or "root")
David Jamesfcb70ef2011-02-02 16:02:30 -08001620
David James386ccd12011-05-04 20:17:42 -07001621 deps_tree, deps_info = deps.GenDependencyTree()
David Jamesfcb70ef2011-02-02 16:02:30 -08001622
1623 # You want me to be verbose? I'll give you two trees! Twice as much value.
1624 if "--tree" in emerge.opts and "--verbose" in emerge.opts:
1625 deps.PrintTree(deps_tree)
1626
David James386ccd12011-05-04 20:17:42 -07001627 deps_graph = deps.GenDependencyGraph(deps_tree, deps_info)
David Jamesfcb70ef2011-02-02 16:02:30 -08001628
1629 # OK, time to print out our progress so far.
1630 deps.PrintInstallPlan(deps_graph)
1631 if "--tree" in emerge.opts:
1632 PrintDepsMap(deps_graph)
1633
1634 # Are we upgrading portage? If so, and there are more packages to merge,
1635 # schedule a restart of parallel_emerge to merge the rest. This ensures that
1636 # we pick up all updates to portage settings before merging any more
1637 # packages.
1638 portage_upgrade = False
1639 root = emerge.settings["ROOT"]
1640 final_db = emerge.depgraph._dynamic_config.mydbapi[root]
1641 if root == "/":
1642 for db_pkg in final_db.match_pkgs("sys-apps/portage"):
1643 portage_pkg = deps_graph.get(db_pkg.cpv)
David James0ff16f22012-11-02 14:18:07 -07001644 if portage_pkg:
David Jamesfcb70ef2011-02-02 16:02:30 -08001645 portage_upgrade = True
1646 if "--quiet" not in emerge.opts:
1647 print "Upgrading portage first, then restarting..."
1648
David James0ff16f22012-11-02 14:18:07 -07001649 # Upgrade Portage first, then the rest of the packages.
1650 #
1651 # In order to grant the child permission to run setsid, we need to run sudo
1652 # again. We preserve SUDO_USER here in case an ebuild depends on it.
1653 if portage_upgrade:
1654 # Calculate what arguments to use when re-invoking.
1655 args = ["sudo", "-E", "SUDO_USER=%s" % os.environ.get("SUDO_USER", "")]
1656 args += [os.path.abspath(sys.argv[0])] + parallel_emerge_args
1657 args += ["--exclude=sys-apps/portage"]
1658
1659 # First upgrade Portage.
1660 passthrough_args = ("--quiet", "--pretend", "--verbose")
1661 emerge_args = [k for k in emerge.opts if k in passthrough_args]
1662 ret = emerge_main(emerge_args + ["portage"])
1663 if ret != 0:
1664 return ret
1665
1666 # Now upgrade the rest.
1667 os.execvp(args[0], args)
1668
David Jamesfcb70ef2011-02-02 16:02:30 -08001669 # Run the queued emerges.
1670 scheduler = EmergeQueue(deps_graph, emerge, deps.package_db, deps.show_output)
Brian Harringa43f5952012-04-12 01:19:34 -07001671 try:
1672 scheduler.Run()
1673 finally:
1674 scheduler._Shutdown()
David James97ce8902011-08-16 09:51:05 -07001675 scheduler = None
David Jamesfcb70ef2011-02-02 16:02:30 -08001676
Mike Frysingerd20a6e22012-10-04 19:01:10 -04001677 clean_logs(emerge.settings)
1678
David Jamesfcb70ef2011-02-02 16:02:30 -08001679 print "Done"
Brian Harring8294d652012-05-23 02:20:52 -07001680 return 0