blob: db54021ea39c1b3256d693fe5976714de0540067 [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
Mike Frysinger1ae28092013-10-17 17:17:22 -040024try:
25 import Queue
26except ImportError:
27 # Python-3 renamed to "queue". We still use Queue to avoid collisions
28 # with naming variables as "queue". Maybe we'll transition at some point.
29 # pylint: disable=F0401
30 import queue as Queue
David Jamesfcb70ef2011-02-02 16:02:30 -080031import signal
32import sys
33import tempfile
Brian Harring8294d652012-05-23 02:20:52 -070034import threading
David Jamesfcb70ef2011-02-02 16:02:30 -080035import time
36import traceback
David Jamesfcb70ef2011-02-02 16:02:30 -080037
Thiago Goncalesf4acc422013-07-17 10:26:35 -070038from chromite.lib import cros_build_lib
39
David Jamesfcb70ef2011-02-02 16:02:30 -080040# If PORTAGE_USERNAME isn't specified, scrape it from the $HOME variable. On
41# Chromium OS, the default "portage" user doesn't have the necessary
42# permissions. It'd be easier if we could default to $USERNAME, but $USERNAME
43# is "root" here because we get called through sudo.
44#
45# We need to set this before importing any portage modules, because portage
46# looks up "PORTAGE_USERNAME" at import time.
47#
48# NOTE: .bashrc sets PORTAGE_USERNAME = $USERNAME, so most people won't
49# encounter this case unless they have an old chroot or blow away the
50# environment by running sudo without the -E specifier.
51if "PORTAGE_USERNAME" not in os.environ:
52 homedir = os.environ.get("HOME")
53 if homedir:
54 os.environ["PORTAGE_USERNAME"] = os.path.basename(homedir)
55
56# Portage doesn't expose dependency trees in its public API, so we have to
57# make use of some private APIs here. These modules are found under
58# /usr/lib/portage/pym/.
59#
60# TODO(davidjames): Update Portage to expose public APIs for these features.
David James321490a2012-12-17 12:05:56 -080061# pylint: disable=W0212
David Jamesfcb70ef2011-02-02 16:02:30 -080062from _emerge.actions import adjust_configs
63from _emerge.actions import load_emerge_config
64from _emerge.create_depgraph_params import create_depgraph_params
David James386ccd12011-05-04 20:17:42 -070065from _emerge.depgraph import backtrack_depgraph
Mike Frysinger901eaad2012-10-10 18:18:03 -040066try:
67 from _emerge.main import clean_logs
68except ImportError:
69 # Older portage versions did not provide clean_logs, so stub it.
70 # We need this if running in an older chroot that hasn't yet upgraded
71 # the portage version.
72 clean_logs = lambda x: None
David Jamesfcb70ef2011-02-02 16:02:30 -080073from _emerge.main import emerge_main
74from _emerge.main import parse_opts
75from _emerge.Package import Package
76from _emerge.Scheduler import Scheduler
David Jamesfcb70ef2011-02-02 16:02:30 -080077from _emerge.stdout_spinner import stdout_spinner
David James386ccd12011-05-04 20:17:42 -070078from portage._global_updates import _global_updates
David Jamesfcb70ef2011-02-02 16:02:30 -080079import portage
80import portage.debug
Mike Frysinger91d7da92013-02-19 15:53:46 -050081from portage.versions import vercmp
82
David Jamesfcb70ef2011-02-02 16:02:30 -080083
David Jamesfcb70ef2011-02-02 16:02:30 -080084def Usage():
85 """Print usage."""
86 print "Usage:"
David James386ccd12011-05-04 20:17:42 -070087 print " ./parallel_emerge [--board=BOARD] [--workon=PKGS]"
David Jamesfcb70ef2011-02-02 16:02:30 -080088 print " [--rebuild] [emerge args] package"
89 print
90 print "Packages specified as workon packages are always built from source."
David Jamesfcb70ef2011-02-02 16:02:30 -080091 print
92 print "The --workon argument is mainly useful when you want to build and"
93 print "install packages that you are working on unconditionally, but do not"
94 print "to have to rev the package to indicate you want to build it from"
95 print "source. The build_packages script will automatically supply the"
96 print "workon argument to emerge, ensuring that packages selected using"
97 print "cros-workon are rebuilt."
98 print
99 print "The --rebuild option rebuilds packages whenever their dependencies"
100 print "are changed. This ensures that your build is correct."
David Jamesfcb70ef2011-02-02 16:02:30 -0800101
102
David Jamesfcb70ef2011-02-02 16:02:30 -0800103# Global start time
104GLOBAL_START = time.time()
105
David James7358d032011-05-19 10:40:03 -0700106# Whether process has been killed by a signal.
107KILLED = multiprocessing.Event()
108
David Jamesfcb70ef2011-02-02 16:02:30 -0800109
110class EmergeData(object):
111 """This simple struct holds various emerge variables.
112
113 This struct helps us easily pass emerge variables around as a unit.
114 These variables are used for calculating dependencies and installing
115 packages.
116 """
117
David Jamesbf1e3442011-05-28 07:44:20 -0700118 __slots__ = ["action", "cmdline_packages", "depgraph", "favorites",
119 "mtimedb", "opts", "root_config", "scheduler_graph",
120 "settings", "spinner", "trees"]
David Jamesfcb70ef2011-02-02 16:02:30 -0800121
122 def __init__(self):
123 # The action the user requested. If the user is installing packages, this
124 # is None. If the user is doing anything other than installing packages,
125 # this will contain the action name, which will map exactly to the
126 # long-form name of the associated emerge option.
127 #
128 # Example: If you call parallel_emerge --unmerge package, the action name
129 # will be "unmerge"
130 self.action = None
131
132 # The list of packages the user passed on the command-line.
133 self.cmdline_packages = None
134
135 # The emerge dependency graph. It'll contain all the packages involved in
136 # this merge, along with their versions.
137 self.depgraph = None
138
David Jamesbf1e3442011-05-28 07:44:20 -0700139 # The list of candidates to add to the world file.
140 self.favorites = None
141
David Jamesfcb70ef2011-02-02 16:02:30 -0800142 # A dict of the options passed to emerge. This dict has been cleaned up
143 # a bit by parse_opts, so that it's a bit easier for the emerge code to
144 # look at the options.
145 #
146 # Emerge takes a few shortcuts in its cleanup process to make parsing of
147 # the options dict easier. For example, if you pass in "--usepkg=n", the
148 # "--usepkg" flag is just left out of the dictionary altogether. Because
149 # --usepkg=n is the default, this makes parsing easier, because emerge
150 # can just assume that if "--usepkg" is in the dictionary, it's enabled.
151 #
152 # These cleanup processes aren't applied to all options. For example, the
153 # --with-bdeps flag is passed in as-is. For a full list of the cleanups
154 # applied by emerge, see the parse_opts function in the _emerge.main
155 # package.
156 self.opts = None
157
158 # A dictionary used by portage to maintain global state. This state is
159 # loaded from disk when portage starts up, and saved to disk whenever we
160 # call mtimedb.commit().
161 #
162 # This database contains information about global updates (i.e., what
163 # version of portage we have) and what we're currently doing. Portage
164 # saves what it is currently doing in this database so that it can be
165 # resumed when you call it with the --resume option.
166 #
167 # parallel_emerge does not save what it is currently doing in the mtimedb,
168 # so we do not support the --resume option.
169 self.mtimedb = None
170
171 # The portage configuration for our current root. This contains the portage
172 # settings (see below) and the three portage trees for our current root.
173 # (The three portage trees are explained below, in the documentation for
174 # the "trees" member.)
175 self.root_config = None
176
177 # The scheduler graph is used by emerge to calculate what packages to
178 # install. We don't actually install any deps, so this isn't really used,
179 # but we pass it in to the Scheduler object anyway.
180 self.scheduler_graph = None
181
182 # Portage settings for our current session. Most of these settings are set
183 # in make.conf inside our current install root.
184 self.settings = None
185
186 # The spinner, which spews stuff to stdout to indicate that portage is
187 # doing something. We maintain our own spinner, so we set the portage
188 # spinner to "silent" mode.
189 self.spinner = None
190
191 # The portage trees. There are separate portage trees for each root. To get
192 # the portage tree for the current root, you can look in self.trees[root],
193 # where root = self.settings["ROOT"].
194 #
195 # In each root, there are three trees: vartree, porttree, and bintree.
196 # - vartree: A database of the currently-installed packages.
197 # - porttree: A database of ebuilds, that can be used to build packages.
198 # - bintree: A database of binary packages.
199 self.trees = None
200
201
202class DepGraphGenerator(object):
203 """Grab dependency information about packages from portage.
204
205 Typical usage:
206 deps = DepGraphGenerator()
207 deps.Initialize(sys.argv[1:])
208 deps_tree, deps_info = deps.GenDependencyTree()
209 deps_graph = deps.GenDependencyGraph(deps_tree, deps_info)
210 deps.PrintTree(deps_tree)
211 PrintDepsMap(deps_graph)
212 """
213
Thiago Goncalesf4acc422013-07-17 10:26:35 -0700214 __slots__ = ["board", "emerge", "package_db", "show_output", "unpack_only"]
David Jamesfcb70ef2011-02-02 16:02:30 -0800215
216 def __init__(self):
217 self.board = None
218 self.emerge = EmergeData()
David Jamesfcb70ef2011-02-02 16:02:30 -0800219 self.package_db = {}
David Jamesfcb70ef2011-02-02 16:02:30 -0800220 self.show_output = False
Thiago Goncalesf4acc422013-07-17 10:26:35 -0700221 self.unpack_only = False
David Jamesfcb70ef2011-02-02 16:02:30 -0800222
223 def ParseParallelEmergeArgs(self, argv):
224 """Read the parallel emerge arguments from the command-line.
225
226 We need to be compatible with emerge arg format. We scrape arguments that
227 are specific to parallel_emerge, and pass through the rest directly to
228 emerge.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500229
David Jamesfcb70ef2011-02-02 16:02:30 -0800230 Args:
231 argv: arguments list
Mike Frysinger1a736a82013-12-12 01:50:59 -0500232
David Jamesfcb70ef2011-02-02 16:02:30 -0800233 Returns:
234 Arguments that don't belong to parallel_emerge
235 """
236 emerge_args = []
237 for arg in argv:
238 # Specifically match arguments that are specific to parallel_emerge, and
239 # pass through the rest.
240 if arg.startswith("--board="):
241 self.board = arg.replace("--board=", "")
242 elif arg.startswith("--workon="):
243 workon_str = arg.replace("--workon=", "")
David James7a1ea4b2011-10-13 15:06:41 -0700244 emerge_args.append("--reinstall-atoms=%s" % workon_str)
245 emerge_args.append("--usepkg-exclude=%s" % workon_str)
David Jamesfcb70ef2011-02-02 16:02:30 -0800246 elif arg.startswith("--force-remote-binary="):
247 force_remote_binary = arg.replace("--force-remote-binary=", "")
David James7a1ea4b2011-10-13 15:06:41 -0700248 emerge_args.append("--useoldpkg-atoms=%s" % force_remote_binary)
David Jamesfcb70ef2011-02-02 16:02:30 -0800249 elif arg == "--show-output":
250 self.show_output = True
David James386ccd12011-05-04 20:17:42 -0700251 elif arg == "--rebuild":
David James7a1ea4b2011-10-13 15:06:41 -0700252 emerge_args.append("--rebuild-if-unbuilt")
Thiago Goncalesf4acc422013-07-17 10:26:35 -0700253 elif arg == "--unpackonly":
254 emerge_args.append("--fetchonly")
255 self.unpack_only = True
David Jamesfcb70ef2011-02-02 16:02:30 -0800256 else:
257 # Not one of our options, so pass through to emerge.
258 emerge_args.append(arg)
259
David James386ccd12011-05-04 20:17:42 -0700260 # These packages take a really long time to build, so, for expediency, we
261 # are blacklisting them from automatic rebuilds because one of their
262 # dependencies needs to be recompiled.
263 for pkg in ("chromeos-base/chromeos-chrome", "media-plugins/o3d",
264 "dev-java/icedtea"):
David James7a1ea4b2011-10-13 15:06:41 -0700265 emerge_args.append("--rebuild-exclude=%s" % pkg)
David Jamesfcb70ef2011-02-02 16:02:30 -0800266
267 return emerge_args
268
269 def Initialize(self, args):
270 """Initializer. Parses arguments and sets up portage state."""
271
272 # Parse and strip out args that are just intended for parallel_emerge.
273 emerge_args = self.ParseParallelEmergeArgs(args)
274
275 # Setup various environment variables based on our current board. These
276 # variables are normally setup inside emerge-${BOARD}, but since we don't
277 # call that script, we have to set it up here. These variables serve to
278 # point our tools at /build/BOARD and to setup cross compiles to the
279 # appropriate board as configured in toolchain.conf.
280 if self.board:
281 os.environ["PORTAGE_CONFIGROOT"] = "/build/" + self.board
282 os.environ["PORTAGE_SYSROOT"] = "/build/" + self.board
283 os.environ["SYSROOT"] = "/build/" + self.board
David Jamesfcb70ef2011-02-02 16:02:30 -0800284
285 # Although CHROMEOS_ROOT isn't specific to boards, it's normally setup
286 # inside emerge-${BOARD}, so we set it up here for compatibility. It
287 # will be going away soon as we migrate to CROS_WORKON_SRCROOT.
288 os.environ.setdefault("CHROMEOS_ROOT", os.environ["HOME"] + "/trunk")
289
290 # Turn off interactive delays
291 os.environ["EBEEP_IGNORE"] = "1"
292 os.environ["EPAUSE_IGNORE"] = "1"
Mike Frysinger0a647fc2012-08-06 14:36:05 -0400293 os.environ["CLEAN_DELAY"] = "0"
David Jamesfcb70ef2011-02-02 16:02:30 -0800294
295 # Parse the emerge options.
David Jamesea3ca332011-05-26 11:48:29 -0700296 action, opts, cmdline_packages = parse_opts(emerge_args, silent=True)
David Jamesfcb70ef2011-02-02 16:02:30 -0800297
298 # Set environment variables based on options. Portage normally sets these
299 # environment variables in emerge_main, but we can't use that function,
300 # because it also does a bunch of other stuff that we don't want.
301 # TODO(davidjames): Patch portage to move this logic into a function we can
302 # reuse here.
303 if "--debug" in opts:
304 os.environ["PORTAGE_DEBUG"] = "1"
305 if "--config-root" in opts:
306 os.environ["PORTAGE_CONFIGROOT"] = opts["--config-root"]
307 if "--root" in opts:
308 os.environ["ROOT"] = opts["--root"]
309 if "--accept-properties" in opts:
310 os.environ["ACCEPT_PROPERTIES"] = opts["--accept-properties"]
311
David Jamesfcb70ef2011-02-02 16:02:30 -0800312 # If we're installing packages to the board, and we're not using the
David James927a56d2012-04-03 11:26:39 -0700313 # official flag, we can disable vardb locks. This is safe because we
314 # only run up to one instance of parallel_emerge in parallel.
David Jamesfcb70ef2011-02-02 16:02:30 -0800315 if self.board and os.environ.get("CHROMEOS_OFFICIAL") != "1":
316 os.environ.setdefault("PORTAGE_LOCKS", "false")
David Jamesfcb70ef2011-02-02 16:02:30 -0800317
318 # Now that we've setup the necessary environment variables, we can load the
319 # emerge config from disk.
320 settings, trees, mtimedb = load_emerge_config()
321
David Jamesea3ca332011-05-26 11:48:29 -0700322 # Add in EMERGE_DEFAULT_OPTS, if specified.
323 tmpcmdline = []
324 if "--ignore-default-opts" not in opts:
325 tmpcmdline.extend(settings["EMERGE_DEFAULT_OPTS"].split())
326 tmpcmdline.extend(emerge_args)
327 action, opts, cmdline_packages = parse_opts(tmpcmdline)
328
329 # If we're installing to the board, we want the --root-deps option so that
330 # portage will install the build dependencies to that location as well.
331 if self.board:
332 opts.setdefault("--root-deps", True)
333
David Jamesfcb70ef2011-02-02 16:02:30 -0800334 # Check whether our portage tree is out of date. Typically, this happens
335 # when you're setting up a new portage tree, such as in setup_board and
336 # make_chroot. In that case, portage applies a bunch of global updates
337 # here. Once the updates are finished, we need to commit any changes
338 # that the global update made to our mtimedb, and reload the config.
339 #
340 # Portage normally handles this logic in emerge_main, but again, we can't
341 # use that function here.
342 if _global_updates(trees, mtimedb["updates"]):
343 mtimedb.commit()
344 settings, trees, mtimedb = load_emerge_config(trees=trees)
345
346 # Setup implied options. Portage normally handles this logic in
347 # emerge_main.
348 if "--buildpkgonly" in opts or "buildpkg" in settings.features:
349 opts.setdefault("--buildpkg", True)
350 if "--getbinpkgonly" in opts:
351 opts.setdefault("--usepkgonly", True)
352 opts.setdefault("--getbinpkg", True)
353 if "getbinpkg" in settings.features:
354 # Per emerge_main, FEATURES=getbinpkg overrides --getbinpkg=n
355 opts["--getbinpkg"] = True
356 if "--getbinpkg" in opts or "--usepkgonly" in opts:
357 opts.setdefault("--usepkg", True)
358 if "--fetch-all-uri" in opts:
359 opts.setdefault("--fetchonly", True)
360 if "--skipfirst" in opts:
361 opts.setdefault("--resume", True)
362 if "--buildpkgonly" in opts:
363 # --buildpkgonly will not merge anything, so it overrides all binary
364 # package options.
365 for opt in ("--getbinpkg", "--getbinpkgonly",
366 "--usepkg", "--usepkgonly"):
367 opts.pop(opt, None)
368 if (settings.get("PORTAGE_DEBUG", "") == "1" and
369 "python-trace" in settings.features):
370 portage.debug.set_trace(True)
371
372 # Complain about unsupported options
David James386ccd12011-05-04 20:17:42 -0700373 for opt in ("--ask", "--ask-enter-invalid", "--resume", "--skipfirst"):
David Jamesfcb70ef2011-02-02 16:02:30 -0800374 if opt in opts:
375 print "%s is not supported by parallel_emerge" % opt
376 sys.exit(1)
377
378 # Make emerge specific adjustments to the config (e.g. colors!)
379 adjust_configs(opts, trees)
380
381 # Save our configuration so far in the emerge object
382 emerge = self.emerge
383 emerge.action, emerge.opts = action, opts
384 emerge.settings, emerge.trees, emerge.mtimedb = settings, trees, mtimedb
385 emerge.cmdline_packages = cmdline_packages
386 root = settings["ROOT"]
387 emerge.root_config = trees[root]["root_config"]
388
David James386ccd12011-05-04 20:17:42 -0700389 if "--usepkg" in opts:
David Jamesfcb70ef2011-02-02 16:02:30 -0800390 emerge.trees[root]["bintree"].populate("--getbinpkg" in opts)
391
David Jamesfcb70ef2011-02-02 16:02:30 -0800392 def CreateDepgraph(self, emerge, packages):
393 """Create an emerge depgraph object."""
394 # Setup emerge options.
395 emerge_opts = emerge.opts.copy()
396
David James386ccd12011-05-04 20:17:42 -0700397 # Ask portage to build a dependency graph. with the options we specified
398 # above.
David Jamesfcb70ef2011-02-02 16:02:30 -0800399 params = create_depgraph_params(emerge_opts, emerge.action)
David Jamesbf1e3442011-05-28 07:44:20 -0700400 success, depgraph, favorites = backtrack_depgraph(
David James386ccd12011-05-04 20:17:42 -0700401 emerge.settings, emerge.trees, emerge_opts, params, emerge.action,
402 packages, emerge.spinner)
403 emerge.depgraph = depgraph
David Jamesfcb70ef2011-02-02 16:02:30 -0800404
David James386ccd12011-05-04 20:17:42 -0700405 # Is it impossible to honor the user's request? Bail!
406 if not success:
407 depgraph.display_problems()
408 sys.exit(1)
David Jamesfcb70ef2011-02-02 16:02:30 -0800409
410 emerge.depgraph = depgraph
David Jamesbf1e3442011-05-28 07:44:20 -0700411 emerge.favorites = favorites
David Jamesfcb70ef2011-02-02 16:02:30 -0800412
David Jamesdeebd692011-05-09 17:02:52 -0700413 # Prime and flush emerge caches.
414 root = emerge.settings["ROOT"]
415 vardb = emerge.trees[root]["vartree"].dbapi
David James0bdc5de2011-05-12 16:22:26 -0700416 if "--pretend" not in emerge.opts:
417 vardb.counter_tick()
David Jamesdeebd692011-05-09 17:02:52 -0700418 vardb.flush_cache()
419
David James386ccd12011-05-04 20:17:42 -0700420 def GenDependencyTree(self):
David Jamesfcb70ef2011-02-02 16:02:30 -0800421 """Get dependency tree info from emerge.
422
David Jamesfcb70ef2011-02-02 16:02:30 -0800423 Returns:
424 Dependency tree
425 """
426 start = time.time()
427
428 emerge = self.emerge
429
430 # Create a list of packages to merge
431 packages = set(emerge.cmdline_packages[:])
David Jamesfcb70ef2011-02-02 16:02:30 -0800432
433 # Tell emerge to be quiet. We print plenty of info ourselves so we don't
434 # need any extra output from portage.
435 portage.util.noiselimit = -1
436
437 # My favorite feature: The silent spinner. It doesn't spin. Ever.
438 # I'd disable the colors by default too, but they look kind of cool.
439 emerge.spinner = stdout_spinner()
440 emerge.spinner.update = emerge.spinner.update_quiet
441
442 if "--quiet" not in emerge.opts:
443 print "Calculating deps..."
444
445 self.CreateDepgraph(emerge, packages)
446 depgraph = emerge.depgraph
447
448 # Build our own tree from the emerge digraph.
449 deps_tree = {}
450 digraph = depgraph._dynamic_config.digraph
David James3f778802011-08-25 19:31:45 -0700451 root = emerge.settings["ROOT"]
452 final_db = depgraph._dynamic_config.mydbapi[root]
David Jamesfcb70ef2011-02-02 16:02:30 -0800453 for node, node_deps in digraph.nodes.items():
454 # Calculate dependency packages that need to be installed first. Each
455 # child on the digraph is a dependency. The "operation" field specifies
456 # what we're doing (e.g. merge, uninstall, etc.). The "priorities" array
457 # contains the type of dependency (e.g. build, runtime, runtime_post,
458 # etc.)
459 #
David Jamesfcb70ef2011-02-02 16:02:30 -0800460 # Portage refers to the identifiers for packages as a CPV. This acronym
461 # stands for Component/Path/Version.
462 #
463 # Here's an example CPV: chromeos-base/power_manager-0.0.1-r1
464 # Split up, this CPV would be:
465 # C -- Component: chromeos-base
466 # P -- Path: power_manager
467 # V -- Version: 0.0.1-r1
468 #
469 # We just refer to CPVs as packages here because it's easier.
470 deps = {}
471 for child, priorities in node_deps[0].items():
David James3f778802011-08-25 19:31:45 -0700472 if isinstance(child, Package) and child.root == root:
473 cpv = str(child.cpv)
474 action = str(child.operation)
475
476 # If we're uninstalling a package, check whether Portage is
477 # installing a replacement. If so, just depend on the installation
478 # of the new package, because the old package will automatically
479 # be uninstalled at that time.
480 if action == "uninstall":
481 for pkg in final_db.match_pkgs(child.slot_atom):
482 cpv = str(pkg.cpv)
483 action = "merge"
484 break
485
486 deps[cpv] = dict(action=action,
487 deptypes=[str(x) for x in priorities],
488 deps={})
David Jamesfcb70ef2011-02-02 16:02:30 -0800489
490 # We've built our list of deps, so we can add our package to the tree.
David James3f778802011-08-25 19:31:45 -0700491 if isinstance(node, Package) and node.root == root:
David Jamesfcb70ef2011-02-02 16:02:30 -0800492 deps_tree[str(node.cpv)] = dict(action=str(node.operation),
493 deps=deps)
494
David Jamesfcb70ef2011-02-02 16:02:30 -0800495 # Ask portage for its install plan, so that we can only throw out
David James386ccd12011-05-04 20:17:42 -0700496 # dependencies that portage throws out.
David Jamesfcb70ef2011-02-02 16:02:30 -0800497 deps_info = {}
498 for pkg in depgraph.altlist():
499 if isinstance(pkg, Package):
David James3f778802011-08-25 19:31:45 -0700500 assert pkg.root == root
David Jamesfcb70ef2011-02-02 16:02:30 -0800501 self.package_db[pkg.cpv] = pkg
502
David Jamesfcb70ef2011-02-02 16:02:30 -0800503 # Save off info about the package
David James386ccd12011-05-04 20:17:42 -0700504 deps_info[str(pkg.cpv)] = {"idx": len(deps_info)}
David Jamesfcb70ef2011-02-02 16:02:30 -0800505
506 seconds = time.time() - start
507 if "--quiet" not in emerge.opts:
508 print "Deps calculated in %dm%.1fs" % (seconds / 60, seconds % 60)
509
510 return deps_tree, deps_info
511
512 def PrintTree(self, deps, depth=""):
513 """Print the deps we have seen in the emerge output.
514
515 Args:
516 deps: Dependency tree structure.
517 depth: Allows printing the tree recursively, with indentation.
518 """
519 for entry in sorted(deps):
520 action = deps[entry]["action"]
521 print "%s %s (%s)" % (depth, entry, action)
522 self.PrintTree(deps[entry]["deps"], depth=depth + " ")
523
David James386ccd12011-05-04 20:17:42 -0700524 def GenDependencyGraph(self, deps_tree, deps_info):
David Jamesfcb70ef2011-02-02 16:02:30 -0800525 """Generate a doubly linked dependency graph.
526
527 Args:
528 deps_tree: Dependency tree structure.
529 deps_info: More details on the dependencies.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500530
David Jamesfcb70ef2011-02-02 16:02:30 -0800531 Returns:
532 Deps graph in the form of a dict of packages, with each package
533 specifying a "needs" list and "provides" list.
534 """
535 emerge = self.emerge
David Jamesfcb70ef2011-02-02 16:02:30 -0800536
David Jamesfcb70ef2011-02-02 16:02:30 -0800537 # deps_map is the actual dependency graph.
538 #
539 # Each package specifies a "needs" list and a "provides" list. The "needs"
540 # list indicates which packages we depend on. The "provides" list
541 # indicates the reverse dependencies -- what packages need us.
542 #
543 # We also provide some other information in the dependency graph:
544 # - action: What we're planning on doing with this package. Generally,
545 # "merge", "nomerge", or "uninstall"
David Jamesfcb70ef2011-02-02 16:02:30 -0800546 deps_map = {}
547
548 def ReverseTree(packages):
549 """Convert tree to digraph.
550
551 Take the tree of package -> requirements and reverse it to a digraph of
552 buildable packages -> packages they unblock.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500553
David Jamesfcb70ef2011-02-02 16:02:30 -0800554 Args:
555 packages: Tree(s) of dependencies.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500556
David Jamesfcb70ef2011-02-02 16:02:30 -0800557 Returns:
558 Unsanitized digraph.
559 """
David James8c7e5e32011-06-28 11:26:03 -0700560 binpkg_phases = set(["setup", "preinst", "postinst"])
David James3f778802011-08-25 19:31:45 -0700561 needed_dep_types = set(["blocker", "buildtime", "runtime"])
David Jamesfcb70ef2011-02-02 16:02:30 -0800562 for pkg in packages:
563
564 # Create an entry for the package
565 action = packages[pkg]["action"]
David James8c7e5e32011-06-28 11:26:03 -0700566 default_pkg = {"needs": {}, "provides": set(), "action": action,
567 "nodeps": False, "binary": False}
David Jamesfcb70ef2011-02-02 16:02:30 -0800568 this_pkg = deps_map.setdefault(pkg, default_pkg)
569
David James8c7e5e32011-06-28 11:26:03 -0700570 if pkg in deps_info:
571 this_pkg["idx"] = deps_info[pkg]["idx"]
572
573 # If a package doesn't have any defined phases that might use the
574 # dependent packages (i.e. pkg_setup, pkg_preinst, or pkg_postinst),
575 # we can install this package before its deps are ready.
576 emerge_pkg = self.package_db.get(pkg)
577 if emerge_pkg and emerge_pkg.type_name == "binary":
578 this_pkg["binary"] = True
Mike Frysinger91d7da92013-02-19 15:53:46 -0500579 if 0 <= vercmp(portage.VERSION, "2.1.11.50"):
580 defined_phases = emerge_pkg.defined_phases
581 else:
582 defined_phases = emerge_pkg.metadata.defined_phases
David James8c7e5e32011-06-28 11:26:03 -0700583 defined_binpkg_phases = binpkg_phases.intersection(defined_phases)
584 if not defined_binpkg_phases:
585 this_pkg["nodeps"] = True
586
David Jamesfcb70ef2011-02-02 16:02:30 -0800587 # Create entries for dependencies of this package first.
588 ReverseTree(packages[pkg]["deps"])
589
590 # Add dependencies to this package.
591 for dep, dep_item in packages[pkg]["deps"].iteritems():
David James8c7e5e32011-06-28 11:26:03 -0700592 # We only need to enforce strict ordering of dependencies if the
David James3f778802011-08-25 19:31:45 -0700593 # dependency is a blocker, or is a buildtime or runtime dependency.
594 # (I.e., ignored, optional, and runtime_post dependencies don't
595 # depend on ordering.)
David James8c7e5e32011-06-28 11:26:03 -0700596 dep_types = dep_item["deptypes"]
597 if needed_dep_types.intersection(dep_types):
598 deps_map[dep]["provides"].add(pkg)
599 this_pkg["needs"][dep] = "/".join(dep_types)
David Jamesfcb70ef2011-02-02 16:02:30 -0800600
David James3f778802011-08-25 19:31:45 -0700601 # If there's a blocker, Portage may need to move files from one
602 # package to another, which requires editing the CONTENTS files of
603 # both packages. To avoid race conditions while editing this file,
604 # the two packages must not be installed in parallel, so we can't
605 # safely ignore dependencies. See http://crosbug.com/19328
606 if "blocker" in dep_types:
607 this_pkg["nodeps"] = False
608
David Jamesfcb70ef2011-02-02 16:02:30 -0800609 def FindCycles():
610 """Find cycles in the dependency tree.
611
612 Returns:
613 A dict mapping cyclic packages to a dict of the deps that cause
614 cycles. For each dep that causes cycles, it returns an example
615 traversal of the graph that shows the cycle.
616 """
617
618 def FindCyclesAtNode(pkg, cycles, unresolved, resolved):
619 """Find cycles in cyclic dependencies starting at specified package.
620
621 Args:
622 pkg: Package identifier.
623 cycles: A dict mapping cyclic packages to a dict of the deps that
624 cause cycles. For each dep that causes cycles, it returns an
625 example traversal of the graph that shows the cycle.
626 unresolved: Nodes that have been visited but are not fully processed.
627 resolved: Nodes that have been visited and are fully processed.
628 """
629 pkg_cycles = cycles.get(pkg)
630 if pkg in resolved and not pkg_cycles:
631 # If we already looked at this package, and found no cyclic
632 # dependencies, we can stop now.
633 return
634 unresolved.append(pkg)
635 for dep in deps_map[pkg]["needs"]:
636 if dep in unresolved:
637 idx = unresolved.index(dep)
638 mycycle = unresolved[idx:] + [dep]
David James321490a2012-12-17 12:05:56 -0800639 for i in xrange(len(mycycle) - 1):
David Jamesfcb70ef2011-02-02 16:02:30 -0800640 pkg1, pkg2 = mycycle[i], mycycle[i+1]
641 cycles.setdefault(pkg1, {}).setdefault(pkg2, mycycle)
642 elif not pkg_cycles or dep not in pkg_cycles:
643 # Looks like we haven't seen this edge before.
644 FindCyclesAtNode(dep, cycles, unresolved, resolved)
645 unresolved.pop()
646 resolved.add(pkg)
647
648 cycles, unresolved, resolved = {}, [], set()
649 for pkg in deps_map:
650 FindCyclesAtNode(pkg, cycles, unresolved, resolved)
651 return cycles
652
David James386ccd12011-05-04 20:17:42 -0700653 def RemoveUnusedPackages():
David Jamesfcb70ef2011-02-02 16:02:30 -0800654 """Remove installed packages, propagating dependencies."""
David Jamesfcb70ef2011-02-02 16:02:30 -0800655 # Schedule packages that aren't on the install list for removal
656 rm_pkgs = set(deps_map.keys()) - set(deps_info.keys())
657
David Jamesfcb70ef2011-02-02 16:02:30 -0800658 # Remove the packages we don't want, simplifying the graph and making
659 # it easier for us to crack cycles.
660 for pkg in sorted(rm_pkgs):
661 this_pkg = deps_map[pkg]
662 needs = this_pkg["needs"]
663 provides = this_pkg["provides"]
664 for dep in needs:
665 dep_provides = deps_map[dep]["provides"]
666 dep_provides.update(provides)
667 dep_provides.discard(pkg)
668 dep_provides.discard(dep)
669 for target in provides:
670 target_needs = deps_map[target]["needs"]
671 target_needs.update(needs)
672 target_needs.pop(pkg, None)
673 target_needs.pop(target, None)
674 del deps_map[pkg]
675
676 def PrintCycleBreak(basedep, dep, mycycle):
677 """Print details about a cycle that we are planning on breaking.
678
Mike Frysinger02e1e072013-11-10 22:11:34 -0500679 We are breaking a cycle where dep needs basedep. mycycle is an
680 example cycle which contains dep -> basedep.
681 """
David Jamesfcb70ef2011-02-02 16:02:30 -0800682
David Jamesfcb70ef2011-02-02 16:02:30 -0800683 needs = deps_map[dep]["needs"]
684 depinfo = needs.get(basedep, "deleted")
David Jamesfcb70ef2011-02-02 16:02:30 -0800685
David James3f778802011-08-25 19:31:45 -0700686 # It's OK to swap install order for blockers, as long as the two
687 # packages aren't installed in parallel. If there is a cycle, then
688 # we know the packages depend on each other already, so we can drop the
689 # blocker safely without printing a warning.
690 if depinfo == "blocker":
691 return
692
David Jamesfcb70ef2011-02-02 16:02:30 -0800693 # Notify the user that we're breaking a cycle.
694 print "Breaking %s -> %s (%s)" % (dep, basedep, depinfo)
695
696 # Show cycle.
David James321490a2012-12-17 12:05:56 -0800697 for i in xrange(len(mycycle) - 1):
David Jamesfcb70ef2011-02-02 16:02:30 -0800698 pkg1, pkg2 = mycycle[i], mycycle[i+1]
699 needs = deps_map[pkg1]["needs"]
700 depinfo = needs.get(pkg2, "deleted")
701 if pkg1 == dep and pkg2 == basedep:
702 depinfo = depinfo + ", deleting"
703 print " %s -> %s (%s)" % (pkg1, pkg2, depinfo)
704
705 def SanitizeTree():
706 """Remove circular dependencies.
707
708 We prune all dependencies involved in cycles that go against the emerge
709 ordering. This has a nice property: we're guaranteed to merge
710 dependencies in the same order that portage does.
711
712 Because we don't treat any dependencies as "soft" unless they're killed
713 by a cycle, we pay attention to a larger number of dependencies when
714 merging. This hurts performance a bit, but helps reliability.
715 """
716 start = time.time()
717 cycles = FindCycles()
718 while cycles:
719 for dep, mycycles in cycles.iteritems():
720 for basedep, mycycle in mycycles.iteritems():
721 if deps_info[basedep]["idx"] >= deps_info[dep]["idx"]:
Matt Tennant08797302011-10-17 16:18:45 -0700722 if "--quiet" not in emerge.opts:
723 PrintCycleBreak(basedep, dep, mycycle)
David Jamesfcb70ef2011-02-02 16:02:30 -0800724 del deps_map[dep]["needs"][basedep]
725 deps_map[basedep]["provides"].remove(dep)
726 cycles = FindCycles()
727 seconds = time.time() - start
728 if "--quiet" not in emerge.opts and seconds >= 0.1:
729 print "Tree sanitized in %dm%.1fs" % (seconds / 60, seconds % 60)
730
David James8c7e5e32011-06-28 11:26:03 -0700731 def FindRecursiveProvides(pkg, seen):
732 """Find all nodes that require a particular package.
733
734 Assumes that graph is acyclic.
735
736 Args:
737 pkg: Package identifier.
738 seen: Nodes that have been visited so far.
739 """
740 if pkg in seen:
741 return
742 seen.add(pkg)
743 info = deps_map[pkg]
744 info["tprovides"] = info["provides"].copy()
745 for dep in info["provides"]:
746 FindRecursiveProvides(dep, seen)
747 info["tprovides"].update(deps_map[dep]["tprovides"])
748
David Jamesa22906f2011-05-04 19:53:26 -0700749 ReverseTree(deps_tree)
David Jamesa22906f2011-05-04 19:53:26 -0700750
David James386ccd12011-05-04 20:17:42 -0700751 # We need to remove unused packages so that we can use the dependency
752 # ordering of the install process to show us what cycles to crack.
753 RemoveUnusedPackages()
David Jamesfcb70ef2011-02-02 16:02:30 -0800754 SanitizeTree()
David James8c7e5e32011-06-28 11:26:03 -0700755 seen = set()
756 for pkg in deps_map:
757 FindRecursiveProvides(pkg, seen)
David Jamesfcb70ef2011-02-02 16:02:30 -0800758 return deps_map
759
760 def PrintInstallPlan(self, deps_map):
761 """Print an emerge-style install plan.
762
763 The install plan lists what packages we're installing, in order.
764 It's useful for understanding what parallel_emerge is doing.
765
766 Args:
767 deps_map: The dependency graph.
768 """
769
770 def InstallPlanAtNode(target, deps_map):
771 nodes = []
772 nodes.append(target)
773 for dep in deps_map[target]["provides"]:
774 del deps_map[dep]["needs"][target]
775 if not deps_map[dep]["needs"]:
776 nodes.extend(InstallPlanAtNode(dep, deps_map))
777 return nodes
778
779 deps_map = copy.deepcopy(deps_map)
780 install_plan = []
781 plan = set()
782 for target, info in deps_map.iteritems():
783 if not info["needs"] and target not in plan:
784 for item in InstallPlanAtNode(target, deps_map):
785 plan.add(item)
786 install_plan.append(self.package_db[item])
787
788 for pkg in plan:
789 del deps_map[pkg]
790
791 if deps_map:
792 print "Cyclic dependencies:", " ".join(deps_map)
793 PrintDepsMap(deps_map)
794 sys.exit(1)
795
796 self.emerge.depgraph.display(install_plan)
797
798
799def PrintDepsMap(deps_map):
800 """Print dependency graph, for each package list it's prerequisites."""
801 for i in sorted(deps_map):
802 print "%s: (%s) needs" % (i, deps_map[i]["action"])
803 needs = deps_map[i]["needs"]
804 for j in sorted(needs):
805 print " %s" % (j)
806 if not needs:
807 print " no dependencies"
808
809
810class EmergeJobState(object):
811 __slots__ = ["done", "filename", "last_notify_timestamp", "last_output_seek",
812 "last_output_timestamp", "pkgname", "retcode", "start_timestamp",
Thiago Goncalesf4acc422013-07-17 10:26:35 -0700813 "target", "fetch_only", "unpack_only"]
David Jamesfcb70ef2011-02-02 16:02:30 -0800814
815 def __init__(self, target, pkgname, done, filename, start_timestamp,
Thiago Goncalesf4acc422013-07-17 10:26:35 -0700816 retcode=None, fetch_only=False, unpack_only=False):
David Jamesfcb70ef2011-02-02 16:02:30 -0800817
818 # The full name of the target we're building (e.g.
819 # chromeos-base/chromeos-0.0.1-r60)
820 self.target = target
821
822 # The short name of the target we're building (e.g. chromeos-0.0.1-r60)
823 self.pkgname = pkgname
824
825 # Whether the job is done. (True if the job is done; false otherwise.)
826 self.done = done
827
828 # The filename where output is currently stored.
829 self.filename = filename
830
831 # The timestamp of the last time we printed the name of the log file. We
832 # print this at the beginning of the job, so this starts at
833 # start_timestamp.
834 self.last_notify_timestamp = start_timestamp
835
836 # The location (in bytes) of the end of the last complete line we printed.
837 # This starts off at zero. We use this to jump to the right place when we
838 # print output from the same ebuild multiple times.
839 self.last_output_seek = 0
840
841 # The timestamp of the last time we printed output. Since we haven't
842 # printed output yet, this starts at zero.
843 self.last_output_timestamp = 0
844
845 # The return code of our job, if the job is actually finished.
846 self.retcode = retcode
847
Brian Harring0be85c62012-03-17 19:52:12 -0700848 # Was this just a fetch job?
849 self.fetch_only = fetch_only
850
David Jamesfcb70ef2011-02-02 16:02:30 -0800851 # The timestamp when our job started.
852 self.start_timestamp = start_timestamp
853
Thiago Goncalesf4acc422013-07-17 10:26:35 -0700854 # No emerge, only unpack packages.
855 self.unpack_only = unpack_only
856
David Jamesfcb70ef2011-02-02 16:02:30 -0800857
David James321490a2012-12-17 12:05:56 -0800858def KillHandler(_signum, _frame):
David James7358d032011-05-19 10:40:03 -0700859 # Kill self and all subprocesses.
860 os.killpg(0, signal.SIGKILL)
861
David Jamesfcb70ef2011-02-02 16:02:30 -0800862def SetupWorkerSignals():
David James321490a2012-12-17 12:05:56 -0800863 def ExitHandler(_signum, _frame):
David James7358d032011-05-19 10:40:03 -0700864 # Set KILLED flag.
865 KILLED.set()
David James13cead42011-05-18 16:22:01 -0700866
David James7358d032011-05-19 10:40:03 -0700867 # Remove our signal handlers so we don't get called recursively.
868 signal.signal(signal.SIGINT, KillHandler)
869 signal.signal(signal.SIGTERM, KillHandler)
David Jamesfcb70ef2011-02-02 16:02:30 -0800870
871 # Ensure that we exit quietly and cleanly, if possible, when we receive
872 # SIGTERM or SIGINT signals. By default, when the user hits CTRL-C, all
873 # of the child processes will print details about KeyboardInterrupt
874 # exceptions, which isn't very helpful.
875 signal.signal(signal.SIGINT, ExitHandler)
876 signal.signal(signal.SIGTERM, ExitHandler)
877
David James6b29d052012-11-02 10:27:27 -0700878def EmergeProcess(output, *args, **kwargs):
David James1ed3e252011-10-05 20:26:15 -0700879 """Merge a package in a subprocess.
880
881 Args:
David James1ed3e252011-10-05 20:26:15 -0700882 output: Temporary file to write output.
David James6b29d052012-11-02 10:27:27 -0700883 *args: Arguments to pass to Scheduler constructor.
884 **kwargs: Keyword arguments to pass to Scheduler constructor.
David James1ed3e252011-10-05 20:26:15 -0700885
886 Returns:
887 The exit code returned by the subprocess.
888 """
889 pid = os.fork()
890 if pid == 0:
891 try:
892 # Sanity checks.
Mike Frysingerf02736e2013-11-08 15:27:00 -0500893 if sys.stdout.fileno() != 1:
894 raise Exception("sys.stdout.fileno() != 1")
895 if sys.stderr.fileno() != 2:
896 raise Exception("sys.stderr.fileno() != 2")
David James1ed3e252011-10-05 20:26:15 -0700897
898 # - Redirect 1 (stdout) and 2 (stderr) at our temporary file.
899 # - Redirect 0 to point at sys.stdin. In this case, sys.stdin
900 # points at a file reading os.devnull, because multiprocessing mucks
901 # with sys.stdin.
902 # - Leave the sys.stdin and output filehandles alone.
903 fd_pipes = {0: sys.stdin.fileno(),
904 1: output.fileno(),
905 2: output.fileno(),
906 sys.stdin.fileno(): sys.stdin.fileno(),
907 output.fileno(): output.fileno()}
David Jamesa249eef2013-07-19 14:03:45 -0700908 if 0 <= vercmp(portage.VERSION, "2.1.11.50"):
909 portage.process._setup_pipes(fd_pipes, close_fds=False)
910 else:
911 portage.process._setup_pipes(fd_pipes)
David James1ed3e252011-10-05 20:26:15 -0700912
913 # Portage doesn't like when sys.stdin.fileno() != 0, so point sys.stdin
914 # at the filehandle we just created in _setup_pipes.
915 if sys.stdin.fileno() != 0:
David James6b29d052012-11-02 10:27:27 -0700916 sys.__stdin__ = sys.stdin = os.fdopen(0, "r")
917
918 scheduler = Scheduler(*args, **kwargs)
919
920 # Enable blocker handling even though we're in --nodeps mode. This
921 # allows us to unmerge the blocker after we've merged the replacement.
922 scheduler._opts_ignore_blockers = frozenset()
David James1ed3e252011-10-05 20:26:15 -0700923
924 # Actually do the merge.
925 retval = scheduler.merge()
926
927 # We catch all exceptions here (including SystemExit, KeyboardInterrupt,
928 # etc) so as to ensure that we don't confuse the multiprocessing module,
929 # which expects that all forked children exit with os._exit().
David James321490a2012-12-17 12:05:56 -0800930 # pylint: disable=W0702
David James1ed3e252011-10-05 20:26:15 -0700931 except:
932 traceback.print_exc(file=output)
933 retval = 1
934 sys.stdout.flush()
935 sys.stderr.flush()
936 output.flush()
937 os._exit(retval)
938 else:
939 # Return the exit code of the subprocess.
940 return os.waitpid(pid, 0)[1]
David Jamesfcb70ef2011-02-02 16:02:30 -0800941
Thiago Goncalesf4acc422013-07-17 10:26:35 -0700942
943def UnpackPackage(pkg_state):
944 """Unpacks package described by pkg_state.
945
946 Args:
947 pkg_state: EmergeJobState object describing target.
948
949 Returns:
950 Exit code returned by subprocess.
951 """
952 pkgdir = os.environ.get("PKGDIR",
953 os.path.join(os.environ["SYSROOT"], "packages"))
954 root = os.environ.get("ROOT", os.environ["SYSROOT"])
955 path = os.path.join(pkgdir, pkg_state.target + ".tbz2")
956 comp = cros_build_lib.FindCompressor(cros_build_lib.COMP_BZIP2)
957 cmd = [comp, "-dc"]
958 if comp.endswith("pbzip2"):
959 cmd.append("--ignore-trailing-garbage=1")
960 cmd.append(path)
961
962 result = cros_build_lib.RunCommand(cmd, cwd=root, stdout_to_pipe=True,
963 print_cmd=False, error_code_ok=True)
964
965 # If we were not successful, return now and don't attempt untar.
966 if result.returncode:
967 return result.returncode
968
969 cmd = ["sudo", "tar", "-xf", "-", "-C", root]
970 result = cros_build_lib.RunCommand(cmd, cwd=root, input=result.output,
971 print_cmd=False, error_code_ok=True)
972
973 return result.returncode
974
975
976def EmergeWorker(task_queue, job_queue, emerge, package_db, fetch_only=False,
977 unpack_only=False):
David Jamesfcb70ef2011-02-02 16:02:30 -0800978 """This worker emerges any packages given to it on the task_queue.
979
980 Args:
981 task_queue: The queue of tasks for this worker to do.
982 job_queue: The queue of results from the worker.
983 emerge: An EmergeData() object.
984 package_db: A dict, mapping package ids to portage Package objects.
Brian Harring0be85c62012-03-17 19:52:12 -0700985 fetch_only: A bool, indicating if we should just fetch the target.
Thiago Goncalesf4acc422013-07-17 10:26:35 -0700986 unpack_only: A bool, indicating if we should just unpack the target.
David Jamesfcb70ef2011-02-02 16:02:30 -0800987
988 It expects package identifiers to be passed to it via task_queue. When
989 a task is started, it pushes the (target, filename) to the started_queue.
990 The output is stored in filename. When a merge starts or finishes, we push
991 EmergeJobState objects to the job_queue.
992 """
993
994 SetupWorkerSignals()
995 settings, trees, mtimedb = emerge.settings, emerge.trees, emerge.mtimedb
David Jamesdeebd692011-05-09 17:02:52 -0700996
997 # Disable flushing of caches to save on I/O.
David James7a1ea4b2011-10-13 15:06:41 -0700998 root = emerge.settings["ROOT"]
999 vardb = emerge.trees[root]["vartree"].dbapi
1000 vardb._flush_cache_enabled = False
Brian Harring0be85c62012-03-17 19:52:12 -07001001 bindb = emerge.trees[root]["bintree"].dbapi
1002 # Might be a set, might be a list, might be None; no clue, just use shallow
1003 # copy to ensure we can roll it back.
1004 original_remotepkgs = copy.copy(bindb.bintree._remotepkgs)
David Jamesdeebd692011-05-09 17:02:52 -07001005
David Jamesfcb70ef2011-02-02 16:02:30 -08001006 opts, spinner = emerge.opts, emerge.spinner
1007 opts["--nodeps"] = True
Brian Harring0be85c62012-03-17 19:52:12 -07001008 if fetch_only:
1009 opts["--fetchonly"] = True
1010
David Jamesfcb70ef2011-02-02 16:02:30 -08001011 while True:
1012 # Wait for a new item to show up on the queue. This is a blocking wait,
1013 # so if there's nothing to do, we just sit here.
Brian Harring0be85c62012-03-17 19:52:12 -07001014 pkg_state = task_queue.get()
1015 if pkg_state is None:
David Jamesfcb70ef2011-02-02 16:02:30 -08001016 # If target is None, this means that the main thread wants us to quit.
1017 # The other workers need to exit too, so we'll push the message back on
1018 # to the queue so they'll get it too.
Brian Harring0be85c62012-03-17 19:52:12 -07001019 task_queue.put(None)
David Jamesfcb70ef2011-02-02 16:02:30 -08001020 return
David James7358d032011-05-19 10:40:03 -07001021 if KILLED.is_set():
1022 return
1023
Brian Harring0be85c62012-03-17 19:52:12 -07001024 target = pkg_state.target
1025
David Jamesfcb70ef2011-02-02 16:02:30 -08001026 db_pkg = package_db[target]
Brian Harring0be85c62012-03-17 19:52:12 -07001027
1028 if db_pkg.type_name == "binary":
1029 if not fetch_only and pkg_state.fetched_successfully:
1030 # Ensure portage doesn't think our pkg is remote- else it'll force
1031 # a redownload of it (even if the on-disk file is fine). In-memory
1032 # caching basically, implemented dumbly.
1033 bindb.bintree._remotepkgs = None
1034 else:
1035 bindb.bintree_remotepkgs = original_remotepkgs
1036
David Jamesfcb70ef2011-02-02 16:02:30 -08001037 db_pkg.root_config = emerge.root_config
1038 install_list = [db_pkg]
1039 pkgname = db_pkg.pf
1040 output = tempfile.NamedTemporaryFile(prefix=pkgname + "-", delete=False)
David James01b1e0f2012-06-07 17:18:05 -07001041 os.chmod(output.name, 644)
David Jamesfcb70ef2011-02-02 16:02:30 -08001042 start_timestamp = time.time()
Brian Harring0be85c62012-03-17 19:52:12 -07001043 job = EmergeJobState(target, pkgname, False, output.name, start_timestamp,
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001044 fetch_only=fetch_only, unpack_only=unpack_only)
David Jamesfcb70ef2011-02-02 16:02:30 -08001045 job_queue.put(job)
1046 if "--pretend" in opts:
1047 retcode = 0
1048 else:
David Jamesfcb70ef2011-02-02 16:02:30 -08001049 try:
David James386ccd12011-05-04 20:17:42 -07001050 emerge.scheduler_graph.mergelist = install_list
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001051 if unpack_only:
1052 retcode = UnpackPackage(pkg_state)
1053 else:
1054 retcode = EmergeProcess(output, settings, trees, mtimedb, opts,
1055 spinner, favorites=emerge.favorites,
1056 graph_config=emerge.scheduler_graph)
David Jamesfcb70ef2011-02-02 16:02:30 -08001057 except Exception:
1058 traceback.print_exc(file=output)
1059 retcode = 1
David James1ed3e252011-10-05 20:26:15 -07001060 output.close()
David Jamesfcb70ef2011-02-02 16:02:30 -08001061
David James7358d032011-05-19 10:40:03 -07001062 if KILLED.is_set():
1063 return
1064
David Jamesfcb70ef2011-02-02 16:02:30 -08001065 job = EmergeJobState(target, pkgname, True, output.name, start_timestamp,
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001066 retcode, fetch_only=fetch_only,
1067 unpack_only=unpack_only)
David Jamesfcb70ef2011-02-02 16:02:30 -08001068 job_queue.put(job)
1069
1070
1071class LinePrinter(object):
1072 """Helper object to print a single line."""
1073
1074 def __init__(self, line):
1075 self.line = line
1076
David James321490a2012-12-17 12:05:56 -08001077 def Print(self, _seek_locations):
David Jamesfcb70ef2011-02-02 16:02:30 -08001078 print self.line
1079
1080
1081class JobPrinter(object):
1082 """Helper object to print output of a job."""
1083
1084 def __init__(self, job, unlink=False):
1085 """Print output of job.
1086
Mike Frysinger02e1e072013-11-10 22:11:34 -05001087 If unlink is True, unlink the job output file when done.
1088 """
David Jamesfcb70ef2011-02-02 16:02:30 -08001089 self.current_time = time.time()
1090 self.job = job
1091 self.unlink = unlink
1092
1093 def Print(self, seek_locations):
1094
1095 job = self.job
1096
1097 # Calculate how long the job has been running.
1098 seconds = self.current_time - job.start_timestamp
1099
1100 # Note that we've printed out the job so far.
1101 job.last_output_timestamp = self.current_time
1102
1103 # Note that we're starting the job
1104 info = "job %s (%dm%.1fs)" % (job.pkgname, seconds / 60, seconds % 60)
1105 last_output_seek = seek_locations.get(job.filename, 0)
1106 if last_output_seek:
1107 print "=== Continue output for %s ===" % info
1108 else:
1109 print "=== Start output for %s ===" % info
1110
1111 # Print actual output from job
1112 f = codecs.open(job.filename, encoding='utf-8', errors='replace')
1113 f.seek(last_output_seek)
1114 prefix = job.pkgname + ":"
1115 for line in f:
1116
1117 # Save off our position in the file
1118 if line and line[-1] == "\n":
1119 last_output_seek = f.tell()
1120 line = line[:-1]
1121
1122 # Print our line
1123 print prefix, line.encode('utf-8', 'replace')
1124 f.close()
1125
1126 # Save our last spot in the file so that we don't print out the same
1127 # location twice.
1128 seek_locations[job.filename] = last_output_seek
1129
1130 # Note end of output section
1131 if job.done:
1132 print "=== Complete: %s ===" % info
1133 else:
1134 print "=== Still running: %s ===" % info
1135
1136 if self.unlink:
1137 os.unlink(job.filename)
1138
1139
1140def PrintWorker(queue):
1141 """A worker that prints stuff to the screen as requested."""
1142
David James321490a2012-12-17 12:05:56 -08001143 def ExitHandler(_signum, _frame):
David James7358d032011-05-19 10:40:03 -07001144 # Set KILLED flag.
1145 KILLED.set()
1146
David Jamesfcb70ef2011-02-02 16:02:30 -08001147 # Switch to default signal handlers so that we'll die after two signals.
David James7358d032011-05-19 10:40:03 -07001148 signal.signal(signal.SIGINT, KillHandler)
1149 signal.signal(signal.SIGTERM, KillHandler)
David Jamesfcb70ef2011-02-02 16:02:30 -08001150
1151 # Don't exit on the first SIGINT / SIGTERM, because the parent worker will
1152 # handle it and tell us when we need to exit.
1153 signal.signal(signal.SIGINT, ExitHandler)
1154 signal.signal(signal.SIGTERM, ExitHandler)
1155
1156 # seek_locations is a map indicating the position we are at in each file.
1157 # It starts off empty, but is set by the various Print jobs as we go along
1158 # to indicate where we left off in each file.
1159 seek_locations = {}
1160 while True:
1161 try:
1162 job = queue.get()
1163 if job:
1164 job.Print(seek_locations)
David Jamesbccf8eb2011-07-27 14:06:06 -07001165 sys.stdout.flush()
David Jamesfcb70ef2011-02-02 16:02:30 -08001166 else:
1167 break
1168 except IOError as ex:
1169 if ex.errno == errno.EINTR:
1170 # Looks like we received a signal. Keep printing.
1171 continue
1172 raise
1173
Brian Harring867e2362012-03-17 04:05:17 -07001174
Brian Harring0be85c62012-03-17 19:52:12 -07001175class TargetState(object):
Brian Harring867e2362012-03-17 04:05:17 -07001176
Brian Harring0be85c62012-03-17 19:52:12 -07001177 __slots__ = ("target", "info", "score", "prefetched", "fetched_successfully")
Brian Harring867e2362012-03-17 04:05:17 -07001178
David James321490a2012-12-17 12:05:56 -08001179 def __init__(self, target, info):
Brian Harring867e2362012-03-17 04:05:17 -07001180 self.target, self.info = target, info
Brian Harring0be85c62012-03-17 19:52:12 -07001181 self.fetched_successfully = False
1182 self.prefetched = False
David James321490a2012-12-17 12:05:56 -08001183 self.score = None
Brian Harring867e2362012-03-17 04:05:17 -07001184 self.update_score()
1185
1186 def __cmp__(self, other):
1187 return cmp(self.score, other.score)
1188
1189 def update_score(self):
1190 self.score = (
1191 -len(self.info["tprovides"]),
Brian Harring0be85c62012-03-17 19:52:12 -07001192 len(self.info["needs"]),
Brian Harring11c5eeb2012-03-18 11:02:39 -07001193 not self.info["binary"],
Brian Harring867e2362012-03-17 04:05:17 -07001194 -len(self.info["provides"]),
1195 self.info["idx"],
1196 self.target,
1197 )
1198
1199
1200class ScoredHeap(object):
1201
Brian Harring0be85c62012-03-17 19:52:12 -07001202 __slots__ = ("heap", "_heap_set")
1203
Brian Harring867e2362012-03-17 04:05:17 -07001204 def __init__(self, initial=()):
Brian Harring0be85c62012-03-17 19:52:12 -07001205 self.heap = list()
1206 self._heap_set = set()
1207 if initial:
1208 self.multi_put(initial)
Brian Harring867e2362012-03-17 04:05:17 -07001209
1210 def get(self):
Brian Harring0be85c62012-03-17 19:52:12 -07001211 item = heapq.heappop(self.heap)
1212 self._heap_set.remove(item.target)
1213 return item
Brian Harring867e2362012-03-17 04:05:17 -07001214
Brian Harring0be85c62012-03-17 19:52:12 -07001215 def put(self, item):
1216 if not isinstance(item, TargetState):
1217 raise ValueError("Item %r isn't a TargetState" % (item,))
1218 heapq.heappush(self.heap, item)
1219 self._heap_set.add(item.target)
Brian Harring867e2362012-03-17 04:05:17 -07001220
Brian Harring0be85c62012-03-17 19:52:12 -07001221 def multi_put(self, sequence):
1222 sequence = list(sequence)
1223 self.heap.extend(sequence)
1224 self._heap_set.update(x.target for x in sequence)
Brian Harring867e2362012-03-17 04:05:17 -07001225 self.sort()
1226
David James5c9996d2012-03-24 10:50:46 -07001227 def sort(self):
1228 heapq.heapify(self.heap)
1229
Brian Harring0be85c62012-03-17 19:52:12 -07001230 def __contains__(self, target):
1231 return target in self._heap_set
1232
1233 def __nonzero__(self):
1234 return bool(self.heap)
1235
Brian Harring867e2362012-03-17 04:05:17 -07001236 def __len__(self):
1237 return len(self.heap)
1238
1239
David Jamesfcb70ef2011-02-02 16:02:30 -08001240class EmergeQueue(object):
1241 """Class to schedule emerge jobs according to a dependency graph."""
1242
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001243 def __init__(self, deps_map, emerge, package_db, show_output, unpack_only):
David Jamesfcb70ef2011-02-02 16:02:30 -08001244 # Store the dependency graph.
1245 self._deps_map = deps_map
Brian Harring0be85c62012-03-17 19:52:12 -07001246 self._state_map = {}
David Jamesfcb70ef2011-02-02 16:02:30 -08001247 # Initialize the running queue to empty
Brian Harring0be85c62012-03-17 19:52:12 -07001248 self._build_jobs = {}
1249 self._build_ready = ScoredHeap()
1250 self._fetch_jobs = {}
1251 self._fetch_ready = ScoredHeap()
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001252 self._unpack_jobs = {}
1253 self._unpack_ready = ScoredHeap()
David Jamesfcb70ef2011-02-02 16:02:30 -08001254 # List of total package installs represented in deps_map.
1255 install_jobs = [x for x in deps_map if deps_map[x]["action"] == "merge"]
1256 self._total_jobs = len(install_jobs)
1257 self._show_output = show_output
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001258 self._unpack_only = unpack_only
David Jamesfcb70ef2011-02-02 16:02:30 -08001259
1260 if "--pretend" in emerge.opts:
1261 print "Skipping merge because of --pretend mode."
1262 sys.exit(0)
1263
David James7358d032011-05-19 10:40:03 -07001264 # Set a process group so we can easily terminate all children.
1265 os.setsid()
1266
David Jamesfcb70ef2011-02-02 16:02:30 -08001267 # Setup scheduler graph object. This is used by the child processes
1268 # to help schedule jobs.
1269 emerge.scheduler_graph = emerge.depgraph.schedulerGraph()
1270
1271 # Calculate how many jobs we can run in parallel. We don't want to pass
1272 # the --jobs flag over to emerge itself, because that'll tell emerge to
1273 # hide its output, and said output is quite useful for debugging hung
1274 # jobs.
1275 procs = min(self._total_jobs,
1276 emerge.opts.pop("--jobs", multiprocessing.cpu_count()))
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001277 self._build_procs = self._unpack_procs = self._fetch_procs = max(1, procs)
David James8c7e5e32011-06-28 11:26:03 -07001278 self._load_avg = emerge.opts.pop("--load-average", None)
David Jamesfcb70ef2011-02-02 16:02:30 -08001279 self._job_queue = multiprocessing.Queue()
1280 self._print_queue = multiprocessing.Queue()
Brian Harring0be85c62012-03-17 19:52:12 -07001281
1282 self._fetch_queue = multiprocessing.Queue()
1283 args = (self._fetch_queue, self._job_queue, emerge, package_db, True)
1284 self._fetch_pool = multiprocessing.Pool(self._fetch_procs, EmergeWorker,
1285 args)
1286
1287 self._build_queue = multiprocessing.Queue()
1288 args = (self._build_queue, self._job_queue, emerge, package_db)
1289 self._build_pool = multiprocessing.Pool(self._build_procs, EmergeWorker,
1290 args)
1291
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001292 if self._unpack_only:
1293 # Unpack pool only required on unpack_only jobs.
1294 self._unpack_queue = multiprocessing.Queue()
1295 args = (self._unpack_queue, self._job_queue, emerge, package_db, False,
1296 True)
1297 self._unpack_pool = multiprocessing.Pool(self._unpack_procs, EmergeWorker,
1298 args)
1299
David Jamesfcb70ef2011-02-02 16:02:30 -08001300 self._print_worker = multiprocessing.Process(target=PrintWorker,
1301 args=[self._print_queue])
1302 self._print_worker.start()
1303
1304 # Initialize the failed queue to empty.
1305 self._retry_queue = []
1306 self._failed = set()
1307
David Jamesfcb70ef2011-02-02 16:02:30 -08001308 # Setup an exit handler so that we print nice messages if we are
1309 # terminated.
1310 self._SetupExitHandler()
1311
1312 # Schedule our jobs.
Brian Harring0be85c62012-03-17 19:52:12 -07001313 self._state_map.update(
1314 (pkg, TargetState(pkg, data)) for pkg, data in deps_map.iteritems())
1315 self._fetch_ready.multi_put(self._state_map.itervalues())
David Jamesfcb70ef2011-02-02 16:02:30 -08001316
1317 def _SetupExitHandler(self):
1318
David James321490a2012-12-17 12:05:56 -08001319 def ExitHandler(signum, _frame):
David James7358d032011-05-19 10:40:03 -07001320 # Set KILLED flag.
1321 KILLED.set()
David Jamesfcb70ef2011-02-02 16:02:30 -08001322
1323 # Kill our signal handlers so we don't get called recursively
David James7358d032011-05-19 10:40:03 -07001324 signal.signal(signal.SIGINT, KillHandler)
1325 signal.signal(signal.SIGTERM, KillHandler)
David Jamesfcb70ef2011-02-02 16:02:30 -08001326
1327 # Print our current job status
Brian Harring0be85c62012-03-17 19:52:12 -07001328 for job in self._build_jobs.itervalues():
David Jamesfcb70ef2011-02-02 16:02:30 -08001329 if job:
1330 self._print_queue.put(JobPrinter(job, unlink=True))
1331
1332 # Notify the user that we are exiting
1333 self._Print("Exiting on signal %s" % signum)
David James7358d032011-05-19 10:40:03 -07001334 self._print_queue.put(None)
1335 self._print_worker.join()
David Jamesfcb70ef2011-02-02 16:02:30 -08001336
1337 # Kill child threads, then exit.
David James7358d032011-05-19 10:40:03 -07001338 os.killpg(0, signal.SIGKILL)
David Jamesfcb70ef2011-02-02 16:02:30 -08001339 sys.exit(1)
1340
1341 # Print out job status when we are killed
1342 signal.signal(signal.SIGINT, ExitHandler)
1343 signal.signal(signal.SIGTERM, ExitHandler)
1344
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001345 def _ScheduleUnpack(self, pkg_state):
1346 self._unpack_jobs[pkg_state.target] = None
1347 self._unpack_queue.put(pkg_state)
1348
Brian Harring0be85c62012-03-17 19:52:12 -07001349 def _Schedule(self, pkg_state):
David Jamesfcb70ef2011-02-02 16:02:30 -08001350 # We maintain a tree of all deps, if this doesn't need
David James8c7e5e32011-06-28 11:26:03 -07001351 # to be installed just free up its children and continue.
David Jamesfcb70ef2011-02-02 16:02:30 -08001352 # It is possible to reinstall deps of deps, without reinstalling
1353 # first level deps, like so:
1354 # chromeos (merge) -> eselect (nomerge) -> python (merge)
Brian Harring0be85c62012-03-17 19:52:12 -07001355 this_pkg = pkg_state.info
1356 target = pkg_state.target
1357 if pkg_state.info is not None:
1358 if this_pkg["action"] == "nomerge":
1359 self._Finish(target)
1360 elif target not in self._build_jobs:
1361 # Kick off the build if it's marked to be built.
1362 self._build_jobs[target] = None
1363 self._build_queue.put(pkg_state)
1364 return True
David Jamesfcb70ef2011-02-02 16:02:30 -08001365
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001366 def _ScheduleLoop(self, unpack_only=False):
1367 if unpack_only:
1368 ready_queue = self._unpack_ready
1369 jobs_queue = self._unpack_jobs
1370 procs = self._unpack_procs
1371 else:
1372 ready_queue = self._build_ready
1373 jobs_queue = self._build_jobs
1374 procs = self._build_procs
1375
David James8c7e5e32011-06-28 11:26:03 -07001376 # If the current load exceeds our desired load average, don't schedule
1377 # more than one job.
1378 if self._load_avg and os.getloadavg()[0] > self._load_avg:
1379 needed_jobs = 1
1380 else:
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001381 needed_jobs = procs
David James8c7e5e32011-06-28 11:26:03 -07001382
1383 # Schedule more jobs.
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001384 while ready_queue and len(jobs_queue) < needed_jobs:
1385 state = ready_queue.get()
1386 if unpack_only:
1387 self._ScheduleUnpack(state)
1388 else:
1389 if state.target not in self._failed:
1390 self._Schedule(state)
David Jamesfcb70ef2011-02-02 16:02:30 -08001391
1392 def _Print(self, line):
1393 """Print a single line."""
1394 self._print_queue.put(LinePrinter(line))
1395
1396 def _Status(self):
1397 """Print status."""
1398 current_time = time.time()
1399 no_output = True
1400
1401 # Print interim output every minute if --show-output is used. Otherwise,
1402 # print notifications about running packages every 2 minutes, and print
1403 # full output for jobs that have been running for 60 minutes or more.
1404 if self._show_output:
1405 interval = 60
1406 notify_interval = 0
1407 else:
1408 interval = 60 * 60
1409 notify_interval = 60 * 2
David James321490a2012-12-17 12:05:56 -08001410 for job in self._build_jobs.itervalues():
David Jamesfcb70ef2011-02-02 16:02:30 -08001411 if job:
1412 last_timestamp = max(job.start_timestamp, job.last_output_timestamp)
1413 if last_timestamp + interval < current_time:
1414 self._print_queue.put(JobPrinter(job))
1415 job.last_output_timestamp = current_time
1416 no_output = False
1417 elif (notify_interval and
1418 job.last_notify_timestamp + notify_interval < current_time):
1419 job_seconds = current_time - job.start_timestamp
1420 args = (job.pkgname, job_seconds / 60, job_seconds % 60, job.filename)
1421 info = "Still building %s (%dm%.1fs). Logs in %s" % args
1422 job.last_notify_timestamp = current_time
1423 self._Print(info)
1424 no_output = False
1425
1426 # If we haven't printed any messages yet, print a general status message
1427 # here.
1428 if no_output:
1429 seconds = current_time - GLOBAL_START
Brian Harring0be85c62012-03-17 19:52:12 -07001430 fjobs, fready = len(self._fetch_jobs), len(self._fetch_ready)
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001431 ujobs, uready = len(self._unpack_jobs), len(self._unpack_ready)
Brian Harring0be85c62012-03-17 19:52:12 -07001432 bjobs, bready = len(self._build_jobs), len(self._build_ready)
1433 retries = len(self._retry_queue)
1434 pending = max(0, len(self._deps_map) - fjobs - bjobs)
1435 line = "Pending %s/%s, " % (pending, self._total_jobs)
1436 if fjobs or fready:
1437 line += "Fetching %s/%s, " % (fjobs, fready + fjobs)
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001438 if ujobs or uready:
1439 line += "Unpacking %s/%s, " % (ujobs, uready + ujobs)
Brian Harring0be85c62012-03-17 19:52:12 -07001440 if bjobs or bready or retries:
1441 line += "Building %s/%s, " % (bjobs, bready + bjobs)
1442 if retries:
1443 line += "Retrying %s, " % (retries,)
David James8c7e5e32011-06-28 11:26:03 -07001444 load = " ".join(str(x) for x in os.getloadavg())
Brian Harring0be85c62012-03-17 19:52:12 -07001445 line += ("[Time %dm%.1fs Load %s]" % (seconds/60, seconds %60, load))
1446 self._Print(line)
David Jamesfcb70ef2011-02-02 16:02:30 -08001447
1448 def _Finish(self, target):
David James8c7e5e32011-06-28 11:26:03 -07001449 """Mark a target as completed and unblock dependencies."""
1450 this_pkg = self._deps_map[target]
1451 if this_pkg["needs"] and this_pkg["nodeps"]:
1452 # We got installed, but our deps have not been installed yet. Dependent
1453 # packages should only be installed when our needs have been fully met.
1454 this_pkg["action"] = "nomerge"
1455 else:
David James8c7e5e32011-06-28 11:26:03 -07001456 for dep in this_pkg["provides"]:
1457 dep_pkg = self._deps_map[dep]
Brian Harring0be85c62012-03-17 19:52:12 -07001458 state = self._state_map[dep]
David James8c7e5e32011-06-28 11:26:03 -07001459 del dep_pkg["needs"][target]
Brian Harring0be85c62012-03-17 19:52:12 -07001460 state.update_score()
1461 if not state.prefetched:
1462 if dep in self._fetch_ready:
1463 # If it's not currently being fetched, update the prioritization
1464 self._fetch_ready.sort()
1465 elif not dep_pkg["needs"]:
David James8c7e5e32011-06-28 11:26:03 -07001466 if dep_pkg["nodeps"] and dep_pkg["action"] == "nomerge":
1467 self._Finish(dep)
1468 else:
Brian Harring0be85c62012-03-17 19:52:12 -07001469 self._build_ready.put(self._state_map[dep])
David James8c7e5e32011-06-28 11:26:03 -07001470 self._deps_map.pop(target)
David Jamesfcb70ef2011-02-02 16:02:30 -08001471
1472 def _Retry(self):
David James8c7e5e32011-06-28 11:26:03 -07001473 while self._retry_queue:
Brian Harring0be85c62012-03-17 19:52:12 -07001474 state = self._retry_queue.pop(0)
1475 if self._Schedule(state):
1476 self._Print("Retrying emerge of %s." % state.target)
David James8c7e5e32011-06-28 11:26:03 -07001477 break
David Jamesfcb70ef2011-02-02 16:02:30 -08001478
Brian Harringa43f5952012-04-12 01:19:34 -07001479 def _Shutdown(self):
David Jamesfcb70ef2011-02-02 16:02:30 -08001480 # Tell emerge workers to exit. They all exit when 'None' is pushed
1481 # to the queue.
Brian Harring0be85c62012-03-17 19:52:12 -07001482
Brian Harringa43f5952012-04-12 01:19:34 -07001483 # Shutdown the workers first; then jobs (which is how they feed things back)
1484 # then finally the print queue.
Brian Harring0be85c62012-03-17 19:52:12 -07001485
Brian Harringa43f5952012-04-12 01:19:34 -07001486 def _stop(queue, pool):
1487 if pool is None:
1488 return
1489 try:
1490 queue.put(None)
1491 pool.close()
1492 pool.join()
1493 finally:
1494 pool.terminate()
Brian Harring0be85c62012-03-17 19:52:12 -07001495
Brian Harringa43f5952012-04-12 01:19:34 -07001496 _stop(self._fetch_queue, self._fetch_pool)
1497 self._fetch_queue = self._fetch_pool = None
Brian Harring0be85c62012-03-17 19:52:12 -07001498
Brian Harringa43f5952012-04-12 01:19:34 -07001499 _stop(self._build_queue, self._build_pool)
1500 self._build_queue = self._build_pool = None
1501
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001502 if self._unpack_only:
1503 _stop(self._unpack_queue, self._unpack_pool)
1504 self._unpack_queue = self._unpack_pool = None
1505
Brian Harringa43f5952012-04-12 01:19:34 -07001506 if self._job_queue is not None:
1507 self._job_queue.close()
1508 self._job_queue = None
David Jamesfcb70ef2011-02-02 16:02:30 -08001509
1510 # Now that our workers are finished, we can kill the print queue.
Brian Harringa43f5952012-04-12 01:19:34 -07001511 if self._print_worker is not None:
1512 try:
1513 self._print_queue.put(None)
1514 self._print_queue.close()
1515 self._print_worker.join()
1516 finally:
1517 self._print_worker.terminate()
1518 self._print_queue = self._print_worker = None
David Jamesfcb70ef2011-02-02 16:02:30 -08001519
1520 def Run(self):
1521 """Run through the scheduled ebuilds.
1522
1523 Keep running so long as we have uninstalled packages in the
1524 dependency graph to merge.
1525 """
Brian Harringa43f5952012-04-12 01:19:34 -07001526 if not self._deps_map:
1527 return
1528
Brian Harring0be85c62012-03-17 19:52:12 -07001529 # Start the fetchers.
1530 for _ in xrange(min(self._fetch_procs, len(self._fetch_ready))):
1531 state = self._fetch_ready.get()
1532 self._fetch_jobs[state.target] = None
1533 self._fetch_queue.put(state)
1534
1535 # Print an update, then get going.
1536 self._Status()
1537
David Jamese703d0f2012-01-12 16:27:45 -08001538 retried = set()
David Jamesfcb70ef2011-02-02 16:02:30 -08001539 while self._deps_map:
1540 # Check here that we are actually waiting for something.
Brian Harring0be85c62012-03-17 19:52:12 -07001541 if (self._build_queue.empty() and
David Jamesfcb70ef2011-02-02 16:02:30 -08001542 self._job_queue.empty() and
Brian Harring0be85c62012-03-17 19:52:12 -07001543 not self._fetch_jobs and
1544 not self._fetch_ready and
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001545 not self._unpack_jobs and
1546 not self._unpack_ready and
Brian Harring0be85c62012-03-17 19:52:12 -07001547 not self._build_jobs and
1548 not self._build_ready and
David Jamesfcb70ef2011-02-02 16:02:30 -08001549 self._deps_map):
1550 # If we have failed on a package, retry it now.
1551 if self._retry_queue:
1552 self._Retry()
1553 else:
David Jamesfcb70ef2011-02-02 16:02:30 -08001554 # Tell the user why we're exiting.
1555 if self._failed:
Mike Frysingerf2ff9172012-11-01 18:47:41 -04001556 print 'Packages failed:\n\t%s' % '\n\t'.join(self._failed)
David James0eae23e2012-07-03 15:04:25 -07001557 status_file = os.environ.get("PARALLEL_EMERGE_STATUS_FILE")
1558 if status_file:
David James321490a2012-12-17 12:05:56 -08001559 failed_pkgs = set(portage.versions.cpv_getkey(x)
1560 for x in self._failed)
David James0eae23e2012-07-03 15:04:25 -07001561 with open(status_file, "a") as f:
1562 f.write("%s\n" % " ".join(failed_pkgs))
David Jamesfcb70ef2011-02-02 16:02:30 -08001563 else:
1564 print "Deadlock! Circular dependencies!"
1565 sys.exit(1)
1566
David James321490a2012-12-17 12:05:56 -08001567 for _ in xrange(12):
David Jamesa74289a2011-08-12 10:41:24 -07001568 try:
1569 job = self._job_queue.get(timeout=5)
1570 break
1571 except Queue.Empty:
1572 # Check if any more jobs can be scheduled.
1573 self._ScheduleLoop()
1574 else:
Brian Harring706747c2012-03-16 03:04:31 -07001575 # Print an update every 60 seconds.
David Jamesfcb70ef2011-02-02 16:02:30 -08001576 self._Status()
1577 continue
1578
1579 target = job.target
1580
Brian Harring0be85c62012-03-17 19:52:12 -07001581 if job.fetch_only:
1582 if not job.done:
1583 self._fetch_jobs[job.target] = job
1584 else:
1585 state = self._state_map[job.target]
1586 state.prefetched = True
1587 state.fetched_successfully = (job.retcode == 0)
1588 del self._fetch_jobs[job.target]
1589 self._Print("Fetched %s in %2.2fs"
1590 % (target, time.time() - job.start_timestamp))
1591
1592 if self._show_output or job.retcode != 0:
1593 self._print_queue.put(JobPrinter(job, unlink=True))
1594 else:
1595 os.unlink(job.filename)
1596 # Failure or not, let build work with it next.
1597 if not self._deps_map[job.target]["needs"]:
1598 self._build_ready.put(state)
1599 self._ScheduleLoop()
1600
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001601 if self._unpack_only and job.retcode == 0:
1602 self._unpack_ready.put(state)
1603 self._ScheduleLoop(unpack_only=True)
1604
Brian Harring0be85c62012-03-17 19:52:12 -07001605 if self._fetch_ready:
1606 state = self._fetch_ready.get()
1607 self._fetch_queue.put(state)
1608 self._fetch_jobs[state.target] = None
1609 else:
1610 # Minor optimization; shut down fetchers early since we know
1611 # the queue is empty.
1612 self._fetch_queue.put(None)
1613 continue
1614
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001615 if job.unpack_only:
1616 if not job.done:
1617 self._unpack_jobs[target] = job
1618 else:
1619 del self._unpack_jobs[target]
1620 self._Print("Unpacked %s in %2.2fs"
1621 % (target, time.time() - job.start_timestamp))
1622 if self._show_output or job.retcode != 0:
1623 self._print_queue.put(JobPrinter(job, unlink=True))
1624 else:
1625 os.unlink(job.filename)
1626 if self._unpack_ready:
1627 state = self._unpack_ready.get()
1628 self._unpack_queue.put(state)
1629 self._unpack_jobs[state.target] = None
1630 continue
1631
David Jamesfcb70ef2011-02-02 16:02:30 -08001632 if not job.done:
Brian Harring0be85c62012-03-17 19:52:12 -07001633 self._build_jobs[target] = job
David Jamesfcb70ef2011-02-02 16:02:30 -08001634 self._Print("Started %s (logged in %s)" % (target, job.filename))
1635 continue
1636
1637 # Print output of job
1638 if self._show_output or job.retcode != 0:
1639 self._print_queue.put(JobPrinter(job, unlink=True))
1640 else:
1641 os.unlink(job.filename)
Brian Harring0be85c62012-03-17 19:52:12 -07001642 del self._build_jobs[target]
David Jamesfcb70ef2011-02-02 16:02:30 -08001643
1644 seconds = time.time() - job.start_timestamp
1645 details = "%s (in %dm%.1fs)" % (target, seconds / 60, seconds % 60)
David James32420cc2011-08-25 21:32:46 -07001646 previously_failed = target in self._failed
David Jamesfcb70ef2011-02-02 16:02:30 -08001647
1648 # Complain if necessary.
1649 if job.retcode != 0:
1650 # Handle job failure.
David James32420cc2011-08-25 21:32:46 -07001651 if previously_failed:
David Jamesfcb70ef2011-02-02 16:02:30 -08001652 # If this job has failed previously, give up.
1653 self._Print("Failed %s. Your build has failed." % details)
1654 else:
1655 # Queue up this build to try again after a long while.
David Jamese703d0f2012-01-12 16:27:45 -08001656 retried.add(target)
Brian Harring0be85c62012-03-17 19:52:12 -07001657 self._retry_queue.append(self._state_map[target])
David Jamesfcb70ef2011-02-02 16:02:30 -08001658 self._failed.add(target)
1659 self._Print("Failed %s, retrying later." % details)
1660 else:
David James32420cc2011-08-25 21:32:46 -07001661 if previously_failed:
1662 # Remove target from list of failed packages.
1663 self._failed.remove(target)
1664
1665 self._Print("Completed %s" % details)
1666
1667 # Mark as completed and unblock waiting ebuilds.
1668 self._Finish(target)
1669
1670 if previously_failed and self._retry_queue:
David Jamesfcb70ef2011-02-02 16:02:30 -08001671 # If we have successfully retried a failed package, and there
1672 # are more failed packages, try the next one. We will only have
1673 # one retrying package actively running at a time.
1674 self._Retry()
1675
David Jamesfcb70ef2011-02-02 16:02:30 -08001676
David James8c7e5e32011-06-28 11:26:03 -07001677 # Schedule pending jobs and print an update.
1678 self._ScheduleLoop()
1679 self._Status()
David Jamesfcb70ef2011-02-02 16:02:30 -08001680
David Jamese703d0f2012-01-12 16:27:45 -08001681 # If packages were retried, output a warning.
1682 if retried:
1683 self._Print("")
1684 self._Print("WARNING: The following packages failed the first time,")
1685 self._Print("but succeeded upon retry. This might indicate incorrect")
1686 self._Print("dependencies.")
1687 for pkg in retried:
1688 self._Print(" %s" % pkg)
1689 self._Print("@@@STEP_WARNINGS@@@")
1690 self._Print("")
1691
David Jamesfcb70ef2011-02-02 16:02:30 -08001692 # Tell child threads to exit.
1693 self._Print("Merge complete")
David Jamesfcb70ef2011-02-02 16:02:30 -08001694
1695
Brian Harring30675052012-02-29 12:18:22 -08001696def main(argv):
Brian Harring8294d652012-05-23 02:20:52 -07001697 try:
1698 return real_main(argv)
1699 finally:
1700 # Work around multiprocessing sucking and not cleaning up after itself.
1701 # http://bugs.python.org/issue4106;
1702 # Step one; ensure GC is ran *prior* to the VM starting shutdown.
1703 gc.collect()
1704 # Step two; go looking for those threads and try to manually reap
1705 # them if we can.
1706 for x in threading.enumerate():
1707 # Filter on the name, and ident; if ident is None, the thread
1708 # wasn't started.
1709 if x.name == 'QueueFeederThread' and x.ident is not None:
1710 x.join(1)
David Jamesfcb70ef2011-02-02 16:02:30 -08001711
Brian Harring8294d652012-05-23 02:20:52 -07001712
1713def real_main(argv):
Brian Harring30675052012-02-29 12:18:22 -08001714 parallel_emerge_args = argv[:]
David Jamesfcb70ef2011-02-02 16:02:30 -08001715 deps = DepGraphGenerator()
Brian Harring30675052012-02-29 12:18:22 -08001716 deps.Initialize(parallel_emerge_args)
David Jamesfcb70ef2011-02-02 16:02:30 -08001717 emerge = deps.emerge
1718
1719 if emerge.action is not None:
Brian Harring30675052012-02-29 12:18:22 -08001720 argv = deps.ParseParallelEmergeArgs(argv)
Brian Harring8294d652012-05-23 02:20:52 -07001721 return emerge_main(argv)
David Jamesfcb70ef2011-02-02 16:02:30 -08001722 elif not emerge.cmdline_packages:
1723 Usage()
Brian Harring8294d652012-05-23 02:20:52 -07001724 return 1
David Jamesfcb70ef2011-02-02 16:02:30 -08001725
1726 # Unless we're in pretend mode, there's not much point running without
1727 # root access. We need to be able to install packages.
1728 #
1729 # NOTE: Even if you're running --pretend, it's a good idea to run
1730 # parallel_emerge with root access so that portage can write to the
1731 # dependency cache. This is important for performance.
David James321490a2012-12-17 12:05:56 -08001732 if "--pretend" not in emerge.opts and portage.data.secpass < 2:
David Jamesfcb70ef2011-02-02 16:02:30 -08001733 print "parallel_emerge: superuser access is required."
Brian Harring8294d652012-05-23 02:20:52 -07001734 return 1
David Jamesfcb70ef2011-02-02 16:02:30 -08001735
1736 if "--quiet" not in emerge.opts:
1737 cmdline_packages = " ".join(emerge.cmdline_packages)
David Jamesfcb70ef2011-02-02 16:02:30 -08001738 print "Starting fast-emerge."
1739 print " Building package %s on %s" % (cmdline_packages,
1740 deps.board or "root")
David Jamesfcb70ef2011-02-02 16:02:30 -08001741
David James386ccd12011-05-04 20:17:42 -07001742 deps_tree, deps_info = deps.GenDependencyTree()
David Jamesfcb70ef2011-02-02 16:02:30 -08001743
1744 # You want me to be verbose? I'll give you two trees! Twice as much value.
1745 if "--tree" in emerge.opts and "--verbose" in emerge.opts:
1746 deps.PrintTree(deps_tree)
1747
David James386ccd12011-05-04 20:17:42 -07001748 deps_graph = deps.GenDependencyGraph(deps_tree, deps_info)
David Jamesfcb70ef2011-02-02 16:02:30 -08001749
1750 # OK, time to print out our progress so far.
1751 deps.PrintInstallPlan(deps_graph)
1752 if "--tree" in emerge.opts:
1753 PrintDepsMap(deps_graph)
1754
1755 # Are we upgrading portage? If so, and there are more packages to merge,
1756 # schedule a restart of parallel_emerge to merge the rest. This ensures that
1757 # we pick up all updates to portage settings before merging any more
1758 # packages.
1759 portage_upgrade = False
1760 root = emerge.settings["ROOT"]
1761 final_db = emerge.depgraph._dynamic_config.mydbapi[root]
1762 if root == "/":
1763 for db_pkg in final_db.match_pkgs("sys-apps/portage"):
1764 portage_pkg = deps_graph.get(db_pkg.cpv)
David James0ff16f22012-11-02 14:18:07 -07001765 if portage_pkg:
David Jamesfcb70ef2011-02-02 16:02:30 -08001766 portage_upgrade = True
1767 if "--quiet" not in emerge.opts:
1768 print "Upgrading portage first, then restarting..."
1769
David James0ff16f22012-11-02 14:18:07 -07001770 # Upgrade Portage first, then the rest of the packages.
1771 #
1772 # In order to grant the child permission to run setsid, we need to run sudo
1773 # again. We preserve SUDO_USER here in case an ebuild depends on it.
1774 if portage_upgrade:
1775 # Calculate what arguments to use when re-invoking.
1776 args = ["sudo", "-E", "SUDO_USER=%s" % os.environ.get("SUDO_USER", "")]
1777 args += [os.path.abspath(sys.argv[0])] + parallel_emerge_args
1778 args += ["--exclude=sys-apps/portage"]
1779
1780 # First upgrade Portage.
1781 passthrough_args = ("--quiet", "--pretend", "--verbose")
1782 emerge_args = [k for k in emerge.opts if k in passthrough_args]
1783 ret = emerge_main(emerge_args + ["portage"])
1784 if ret != 0:
1785 return ret
1786
1787 # Now upgrade the rest.
1788 os.execvp(args[0], args)
1789
David Jamesfcb70ef2011-02-02 16:02:30 -08001790 # Run the queued emerges.
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001791 scheduler = EmergeQueue(deps_graph, emerge, deps.package_db, deps.show_output,
1792 deps.unpack_only)
Brian Harringa43f5952012-04-12 01:19:34 -07001793 try:
1794 scheduler.Run()
1795 finally:
1796 scheduler._Shutdown()
David James97ce8902011-08-16 09:51:05 -07001797 scheduler = None
David Jamesfcb70ef2011-02-02 16:02:30 -08001798
Mike Frysingerd20a6e22012-10-04 19:01:10 -04001799 clean_logs(emerge.settings)
1800
David Jamesfcb70ef2011-02-02 16:02:30 -08001801 print "Done"
Brian Harring8294d652012-05-23 02:20:52 -07001802 return 0