blob: 4e59572a905ada0304d5ec0d862f10d44a610c0d [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:
Yu-Ju Hongdd9bb2b2014-01-03 17:08:26 -0800281 sysroot = cros_build_lib.GetSysroot(board=self.board)
282 os.environ["PORTAGE_CONFIGROOT"] = sysroot
283 os.environ["PORTAGE_SYSROOT"] = sysroot
284 os.environ["SYSROOT"] = sysroot
David Jamesfcb70ef2011-02-02 16:02:30 -0800285
286 # Although CHROMEOS_ROOT isn't specific to boards, it's normally setup
287 # inside emerge-${BOARD}, so we set it up here for compatibility. It
288 # will be going away soon as we migrate to CROS_WORKON_SRCROOT.
289 os.environ.setdefault("CHROMEOS_ROOT", os.environ["HOME"] + "/trunk")
290
291 # Turn off interactive delays
292 os.environ["EBEEP_IGNORE"] = "1"
293 os.environ["EPAUSE_IGNORE"] = "1"
Mike Frysinger0a647fc2012-08-06 14:36:05 -0400294 os.environ["CLEAN_DELAY"] = "0"
David Jamesfcb70ef2011-02-02 16:02:30 -0800295
296 # Parse the emerge options.
David Jamesea3ca332011-05-26 11:48:29 -0700297 action, opts, cmdline_packages = parse_opts(emerge_args, silent=True)
David Jamesfcb70ef2011-02-02 16:02:30 -0800298
299 # Set environment variables based on options. Portage normally sets these
300 # environment variables in emerge_main, but we can't use that function,
301 # because it also does a bunch of other stuff that we don't want.
302 # TODO(davidjames): Patch portage to move this logic into a function we can
303 # reuse here.
304 if "--debug" in opts:
305 os.environ["PORTAGE_DEBUG"] = "1"
306 if "--config-root" in opts:
307 os.environ["PORTAGE_CONFIGROOT"] = opts["--config-root"]
308 if "--root" in opts:
309 os.environ["ROOT"] = opts["--root"]
310 if "--accept-properties" in opts:
311 os.environ["ACCEPT_PROPERTIES"] = opts["--accept-properties"]
312
David James88d780c2014-02-05 13:03:29 -0800313 # If we're installing packages to the board, we can disable vardb locks.
314 # This is safe because we only run up to one instance of parallel_emerge in
315 # parallel.
316 # TODO(davidjames): Enable this for the host too.
317 if self.board:
David Jamesfcb70ef2011-02-02 16:02:30 -0800318 os.environ.setdefault("PORTAGE_LOCKS", "false")
David Jamesfcb70ef2011-02-02 16:02:30 -0800319
320 # Now that we've setup the necessary environment variables, we can load the
321 # emerge config from disk.
322 settings, trees, mtimedb = load_emerge_config()
323
David Jamesea3ca332011-05-26 11:48:29 -0700324 # Add in EMERGE_DEFAULT_OPTS, if specified.
325 tmpcmdline = []
326 if "--ignore-default-opts" not in opts:
327 tmpcmdline.extend(settings["EMERGE_DEFAULT_OPTS"].split())
328 tmpcmdline.extend(emerge_args)
329 action, opts, cmdline_packages = parse_opts(tmpcmdline)
330
331 # If we're installing to the board, we want the --root-deps option so that
332 # portage will install the build dependencies to that location as well.
333 if self.board:
334 opts.setdefault("--root-deps", True)
335
David Jamesfcb70ef2011-02-02 16:02:30 -0800336 # Check whether our portage tree is out of date. Typically, this happens
337 # when you're setting up a new portage tree, such as in setup_board and
338 # make_chroot. In that case, portage applies a bunch of global updates
339 # here. Once the updates are finished, we need to commit any changes
340 # that the global update made to our mtimedb, and reload the config.
341 #
342 # Portage normally handles this logic in emerge_main, but again, we can't
343 # use that function here.
344 if _global_updates(trees, mtimedb["updates"]):
345 mtimedb.commit()
346 settings, trees, mtimedb = load_emerge_config(trees=trees)
347
348 # Setup implied options. Portage normally handles this logic in
349 # emerge_main.
350 if "--buildpkgonly" in opts or "buildpkg" in settings.features:
351 opts.setdefault("--buildpkg", True)
352 if "--getbinpkgonly" in opts:
353 opts.setdefault("--usepkgonly", True)
354 opts.setdefault("--getbinpkg", True)
355 if "getbinpkg" in settings.features:
356 # Per emerge_main, FEATURES=getbinpkg overrides --getbinpkg=n
357 opts["--getbinpkg"] = True
358 if "--getbinpkg" in opts or "--usepkgonly" in opts:
359 opts.setdefault("--usepkg", True)
360 if "--fetch-all-uri" in opts:
361 opts.setdefault("--fetchonly", True)
362 if "--skipfirst" in opts:
363 opts.setdefault("--resume", True)
364 if "--buildpkgonly" in opts:
365 # --buildpkgonly will not merge anything, so it overrides all binary
366 # package options.
367 for opt in ("--getbinpkg", "--getbinpkgonly",
368 "--usepkg", "--usepkgonly"):
369 opts.pop(opt, None)
370 if (settings.get("PORTAGE_DEBUG", "") == "1" and
371 "python-trace" in settings.features):
372 portage.debug.set_trace(True)
373
374 # Complain about unsupported options
David James386ccd12011-05-04 20:17:42 -0700375 for opt in ("--ask", "--ask-enter-invalid", "--resume", "--skipfirst"):
David Jamesfcb70ef2011-02-02 16:02:30 -0800376 if opt in opts:
377 print "%s is not supported by parallel_emerge" % opt
378 sys.exit(1)
379
380 # Make emerge specific adjustments to the config (e.g. colors!)
381 adjust_configs(opts, trees)
382
383 # Save our configuration so far in the emerge object
384 emerge = self.emerge
385 emerge.action, emerge.opts = action, opts
386 emerge.settings, emerge.trees, emerge.mtimedb = settings, trees, mtimedb
387 emerge.cmdline_packages = cmdline_packages
388 root = settings["ROOT"]
389 emerge.root_config = trees[root]["root_config"]
390
David James386ccd12011-05-04 20:17:42 -0700391 if "--usepkg" in opts:
David Jamesfcb70ef2011-02-02 16:02:30 -0800392 emerge.trees[root]["bintree"].populate("--getbinpkg" in opts)
393
David Jamesfcb70ef2011-02-02 16:02:30 -0800394 def CreateDepgraph(self, emerge, packages):
395 """Create an emerge depgraph object."""
396 # Setup emerge options.
397 emerge_opts = emerge.opts.copy()
398
David James386ccd12011-05-04 20:17:42 -0700399 # Ask portage to build a dependency graph. with the options we specified
400 # above.
David Jamesfcb70ef2011-02-02 16:02:30 -0800401 params = create_depgraph_params(emerge_opts, emerge.action)
David Jamesbf1e3442011-05-28 07:44:20 -0700402 success, depgraph, favorites = backtrack_depgraph(
David James386ccd12011-05-04 20:17:42 -0700403 emerge.settings, emerge.trees, emerge_opts, params, emerge.action,
404 packages, emerge.spinner)
405 emerge.depgraph = depgraph
David Jamesfcb70ef2011-02-02 16:02:30 -0800406
David James386ccd12011-05-04 20:17:42 -0700407 # Is it impossible to honor the user's request? Bail!
408 if not success:
409 depgraph.display_problems()
410 sys.exit(1)
David Jamesfcb70ef2011-02-02 16:02:30 -0800411
412 emerge.depgraph = depgraph
David Jamesbf1e3442011-05-28 07:44:20 -0700413 emerge.favorites = favorites
David Jamesfcb70ef2011-02-02 16:02:30 -0800414
David Jamesdeebd692011-05-09 17:02:52 -0700415 # Prime and flush emerge caches.
416 root = emerge.settings["ROOT"]
417 vardb = emerge.trees[root]["vartree"].dbapi
David James0bdc5de2011-05-12 16:22:26 -0700418 if "--pretend" not in emerge.opts:
419 vardb.counter_tick()
David Jamesdeebd692011-05-09 17:02:52 -0700420 vardb.flush_cache()
421
David James386ccd12011-05-04 20:17:42 -0700422 def GenDependencyTree(self):
David Jamesfcb70ef2011-02-02 16:02:30 -0800423 """Get dependency tree info from emerge.
424
David Jamesfcb70ef2011-02-02 16:02:30 -0800425 Returns:
426 Dependency tree
427 """
428 start = time.time()
429
430 emerge = self.emerge
431
432 # Create a list of packages to merge
433 packages = set(emerge.cmdline_packages[:])
David Jamesfcb70ef2011-02-02 16:02:30 -0800434
435 # Tell emerge to be quiet. We print plenty of info ourselves so we don't
436 # need any extra output from portage.
437 portage.util.noiselimit = -1
438
439 # My favorite feature: The silent spinner. It doesn't spin. Ever.
440 # I'd disable the colors by default too, but they look kind of cool.
441 emerge.spinner = stdout_spinner()
442 emerge.spinner.update = emerge.spinner.update_quiet
443
444 if "--quiet" not in emerge.opts:
445 print "Calculating deps..."
446
447 self.CreateDepgraph(emerge, packages)
448 depgraph = emerge.depgraph
449
450 # Build our own tree from the emerge digraph.
451 deps_tree = {}
452 digraph = depgraph._dynamic_config.digraph
David James3f778802011-08-25 19:31:45 -0700453 root = emerge.settings["ROOT"]
454 final_db = depgraph._dynamic_config.mydbapi[root]
David Jamesfcb70ef2011-02-02 16:02:30 -0800455 for node, node_deps in digraph.nodes.items():
456 # Calculate dependency packages that need to be installed first. Each
457 # child on the digraph is a dependency. The "operation" field specifies
458 # what we're doing (e.g. merge, uninstall, etc.). The "priorities" array
459 # contains the type of dependency (e.g. build, runtime, runtime_post,
460 # etc.)
461 #
David Jamesfcb70ef2011-02-02 16:02:30 -0800462 # Portage refers to the identifiers for packages as a CPV. This acronym
463 # stands for Component/Path/Version.
464 #
465 # Here's an example CPV: chromeos-base/power_manager-0.0.1-r1
466 # Split up, this CPV would be:
467 # C -- Component: chromeos-base
468 # P -- Path: power_manager
469 # V -- Version: 0.0.1-r1
470 #
471 # We just refer to CPVs as packages here because it's easier.
472 deps = {}
473 for child, priorities in node_deps[0].items():
David James3f778802011-08-25 19:31:45 -0700474 if isinstance(child, Package) and child.root == root:
475 cpv = str(child.cpv)
476 action = str(child.operation)
477
478 # If we're uninstalling a package, check whether Portage is
479 # installing a replacement. If so, just depend on the installation
480 # of the new package, because the old package will automatically
481 # be uninstalled at that time.
482 if action == "uninstall":
483 for pkg in final_db.match_pkgs(child.slot_atom):
484 cpv = str(pkg.cpv)
485 action = "merge"
486 break
487
488 deps[cpv] = dict(action=action,
489 deptypes=[str(x) for x in priorities],
490 deps={})
David Jamesfcb70ef2011-02-02 16:02:30 -0800491
492 # We've built our list of deps, so we can add our package to the tree.
David James3f778802011-08-25 19:31:45 -0700493 if isinstance(node, Package) and node.root == root:
David Jamesfcb70ef2011-02-02 16:02:30 -0800494 deps_tree[str(node.cpv)] = dict(action=str(node.operation),
495 deps=deps)
496
David Jamesfcb70ef2011-02-02 16:02:30 -0800497 # Ask portage for its install plan, so that we can only throw out
David James386ccd12011-05-04 20:17:42 -0700498 # dependencies that portage throws out.
David Jamesfcb70ef2011-02-02 16:02:30 -0800499 deps_info = {}
500 for pkg in depgraph.altlist():
501 if isinstance(pkg, Package):
David James3f778802011-08-25 19:31:45 -0700502 assert pkg.root == root
David Jamesfcb70ef2011-02-02 16:02:30 -0800503 self.package_db[pkg.cpv] = pkg
504
David Jamesfcb70ef2011-02-02 16:02:30 -0800505 # Save off info about the package
David James386ccd12011-05-04 20:17:42 -0700506 deps_info[str(pkg.cpv)] = {"idx": len(deps_info)}
David Jamesfcb70ef2011-02-02 16:02:30 -0800507
508 seconds = time.time() - start
509 if "--quiet" not in emerge.opts:
510 print "Deps calculated in %dm%.1fs" % (seconds / 60, seconds % 60)
511
512 return deps_tree, deps_info
513
514 def PrintTree(self, deps, depth=""):
515 """Print the deps we have seen in the emerge output.
516
517 Args:
518 deps: Dependency tree structure.
519 depth: Allows printing the tree recursively, with indentation.
520 """
521 for entry in sorted(deps):
522 action = deps[entry]["action"]
523 print "%s %s (%s)" % (depth, entry, action)
524 self.PrintTree(deps[entry]["deps"], depth=depth + " ")
525
David James386ccd12011-05-04 20:17:42 -0700526 def GenDependencyGraph(self, deps_tree, deps_info):
David Jamesfcb70ef2011-02-02 16:02:30 -0800527 """Generate a doubly linked dependency graph.
528
529 Args:
530 deps_tree: Dependency tree structure.
531 deps_info: More details on the dependencies.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500532
David Jamesfcb70ef2011-02-02 16:02:30 -0800533 Returns:
534 Deps graph in the form of a dict of packages, with each package
535 specifying a "needs" list and "provides" list.
536 """
537 emerge = self.emerge
David Jamesfcb70ef2011-02-02 16:02:30 -0800538
David Jamesfcb70ef2011-02-02 16:02:30 -0800539 # deps_map is the actual dependency graph.
540 #
541 # Each package specifies a "needs" list and a "provides" list. The "needs"
542 # list indicates which packages we depend on. The "provides" list
543 # indicates the reverse dependencies -- what packages need us.
544 #
545 # We also provide some other information in the dependency graph:
546 # - action: What we're planning on doing with this package. Generally,
547 # "merge", "nomerge", or "uninstall"
David Jamesfcb70ef2011-02-02 16:02:30 -0800548 deps_map = {}
549
550 def ReverseTree(packages):
551 """Convert tree to digraph.
552
553 Take the tree of package -> requirements and reverse it to a digraph of
554 buildable packages -> packages they unblock.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500555
David Jamesfcb70ef2011-02-02 16:02:30 -0800556 Args:
557 packages: Tree(s) of dependencies.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500558
David Jamesfcb70ef2011-02-02 16:02:30 -0800559 Returns:
560 Unsanitized digraph.
561 """
David James8c7e5e32011-06-28 11:26:03 -0700562 binpkg_phases = set(["setup", "preinst", "postinst"])
David James3f778802011-08-25 19:31:45 -0700563 needed_dep_types = set(["blocker", "buildtime", "runtime"])
David Jamesfcb70ef2011-02-02 16:02:30 -0800564 for pkg in packages:
565
566 # Create an entry for the package
567 action = packages[pkg]["action"]
David James8c7e5e32011-06-28 11:26:03 -0700568 default_pkg = {"needs": {}, "provides": set(), "action": action,
569 "nodeps": False, "binary": False}
David Jamesfcb70ef2011-02-02 16:02:30 -0800570 this_pkg = deps_map.setdefault(pkg, default_pkg)
571
David James8c7e5e32011-06-28 11:26:03 -0700572 if pkg in deps_info:
573 this_pkg["idx"] = deps_info[pkg]["idx"]
574
575 # If a package doesn't have any defined phases that might use the
576 # dependent packages (i.e. pkg_setup, pkg_preinst, or pkg_postinst),
577 # we can install this package before its deps are ready.
578 emerge_pkg = self.package_db.get(pkg)
579 if emerge_pkg and emerge_pkg.type_name == "binary":
580 this_pkg["binary"] = True
Mike Frysinger91d7da92013-02-19 15:53:46 -0500581 if 0 <= vercmp(portage.VERSION, "2.1.11.50"):
582 defined_phases = emerge_pkg.defined_phases
583 else:
584 defined_phases = emerge_pkg.metadata.defined_phases
David James8c7e5e32011-06-28 11:26:03 -0700585 defined_binpkg_phases = binpkg_phases.intersection(defined_phases)
586 if not defined_binpkg_phases:
587 this_pkg["nodeps"] = True
588
David Jamesfcb70ef2011-02-02 16:02:30 -0800589 # Create entries for dependencies of this package first.
590 ReverseTree(packages[pkg]["deps"])
591
592 # Add dependencies to this package.
593 for dep, dep_item in packages[pkg]["deps"].iteritems():
David James8c7e5e32011-06-28 11:26:03 -0700594 # We only need to enforce strict ordering of dependencies if the
David James3f778802011-08-25 19:31:45 -0700595 # dependency is a blocker, or is a buildtime or runtime dependency.
596 # (I.e., ignored, optional, and runtime_post dependencies don't
597 # depend on ordering.)
David James8c7e5e32011-06-28 11:26:03 -0700598 dep_types = dep_item["deptypes"]
599 if needed_dep_types.intersection(dep_types):
600 deps_map[dep]["provides"].add(pkg)
601 this_pkg["needs"][dep] = "/".join(dep_types)
David Jamesfcb70ef2011-02-02 16:02:30 -0800602
David James3f778802011-08-25 19:31:45 -0700603 # If there's a blocker, Portage may need to move files from one
604 # package to another, which requires editing the CONTENTS files of
605 # both packages. To avoid race conditions while editing this file,
606 # the two packages must not be installed in parallel, so we can't
607 # safely ignore dependencies. See http://crosbug.com/19328
608 if "blocker" in dep_types:
609 this_pkg["nodeps"] = False
610
David Jamesfcb70ef2011-02-02 16:02:30 -0800611 def FindCycles():
612 """Find cycles in the dependency tree.
613
614 Returns:
615 A dict mapping cyclic packages to a dict of the deps that cause
616 cycles. For each dep that causes cycles, it returns an example
617 traversal of the graph that shows the cycle.
618 """
619
620 def FindCyclesAtNode(pkg, cycles, unresolved, resolved):
621 """Find cycles in cyclic dependencies starting at specified package.
622
623 Args:
624 pkg: Package identifier.
625 cycles: A dict mapping cyclic packages to a dict of the deps that
626 cause cycles. For each dep that causes cycles, it returns an
627 example traversal of the graph that shows the cycle.
628 unresolved: Nodes that have been visited but are not fully processed.
629 resolved: Nodes that have been visited and are fully processed.
630 """
631 pkg_cycles = cycles.get(pkg)
632 if pkg in resolved and not pkg_cycles:
633 # If we already looked at this package, and found no cyclic
634 # dependencies, we can stop now.
635 return
636 unresolved.append(pkg)
637 for dep in deps_map[pkg]["needs"]:
638 if dep in unresolved:
639 idx = unresolved.index(dep)
640 mycycle = unresolved[idx:] + [dep]
David James321490a2012-12-17 12:05:56 -0800641 for i in xrange(len(mycycle) - 1):
David Jamesfcb70ef2011-02-02 16:02:30 -0800642 pkg1, pkg2 = mycycle[i], mycycle[i+1]
643 cycles.setdefault(pkg1, {}).setdefault(pkg2, mycycle)
644 elif not pkg_cycles or dep not in pkg_cycles:
645 # Looks like we haven't seen this edge before.
646 FindCyclesAtNode(dep, cycles, unresolved, resolved)
647 unresolved.pop()
648 resolved.add(pkg)
649
650 cycles, unresolved, resolved = {}, [], set()
651 for pkg in deps_map:
652 FindCyclesAtNode(pkg, cycles, unresolved, resolved)
653 return cycles
654
David James386ccd12011-05-04 20:17:42 -0700655 def RemoveUnusedPackages():
David Jamesfcb70ef2011-02-02 16:02:30 -0800656 """Remove installed packages, propagating dependencies."""
David Jamesfcb70ef2011-02-02 16:02:30 -0800657 # Schedule packages that aren't on the install list for removal
658 rm_pkgs = set(deps_map.keys()) - set(deps_info.keys())
659
David Jamesfcb70ef2011-02-02 16:02:30 -0800660 # Remove the packages we don't want, simplifying the graph and making
661 # it easier for us to crack cycles.
662 for pkg in sorted(rm_pkgs):
663 this_pkg = deps_map[pkg]
664 needs = this_pkg["needs"]
665 provides = this_pkg["provides"]
666 for dep in needs:
667 dep_provides = deps_map[dep]["provides"]
668 dep_provides.update(provides)
669 dep_provides.discard(pkg)
670 dep_provides.discard(dep)
671 for target in provides:
672 target_needs = deps_map[target]["needs"]
673 target_needs.update(needs)
674 target_needs.pop(pkg, None)
675 target_needs.pop(target, None)
676 del deps_map[pkg]
677
678 def PrintCycleBreak(basedep, dep, mycycle):
679 """Print details about a cycle that we are planning on breaking.
680
Mike Frysinger02e1e072013-11-10 22:11:34 -0500681 We are breaking a cycle where dep needs basedep. mycycle is an
682 example cycle which contains dep -> basedep.
683 """
David Jamesfcb70ef2011-02-02 16:02:30 -0800684
David Jamesfcb70ef2011-02-02 16:02:30 -0800685 needs = deps_map[dep]["needs"]
686 depinfo = needs.get(basedep, "deleted")
David Jamesfcb70ef2011-02-02 16:02:30 -0800687
David James3f778802011-08-25 19:31:45 -0700688 # It's OK to swap install order for blockers, as long as the two
689 # packages aren't installed in parallel. If there is a cycle, then
690 # we know the packages depend on each other already, so we can drop the
691 # blocker safely without printing a warning.
692 if depinfo == "blocker":
693 return
694
David Jamesfcb70ef2011-02-02 16:02:30 -0800695 # Notify the user that we're breaking a cycle.
696 print "Breaking %s -> %s (%s)" % (dep, basedep, depinfo)
697
698 # Show cycle.
David James321490a2012-12-17 12:05:56 -0800699 for i in xrange(len(mycycle) - 1):
David Jamesfcb70ef2011-02-02 16:02:30 -0800700 pkg1, pkg2 = mycycle[i], mycycle[i+1]
701 needs = deps_map[pkg1]["needs"]
702 depinfo = needs.get(pkg2, "deleted")
703 if pkg1 == dep and pkg2 == basedep:
704 depinfo = depinfo + ", deleting"
705 print " %s -> %s (%s)" % (pkg1, pkg2, depinfo)
706
707 def SanitizeTree():
708 """Remove circular dependencies.
709
710 We prune all dependencies involved in cycles that go against the emerge
711 ordering. This has a nice property: we're guaranteed to merge
712 dependencies in the same order that portage does.
713
714 Because we don't treat any dependencies as "soft" unless they're killed
715 by a cycle, we pay attention to a larger number of dependencies when
716 merging. This hurts performance a bit, but helps reliability.
717 """
718 start = time.time()
719 cycles = FindCycles()
720 while cycles:
721 for dep, mycycles in cycles.iteritems():
722 for basedep, mycycle in mycycles.iteritems():
723 if deps_info[basedep]["idx"] >= deps_info[dep]["idx"]:
Matt Tennant08797302011-10-17 16:18:45 -0700724 if "--quiet" not in emerge.opts:
725 PrintCycleBreak(basedep, dep, mycycle)
David Jamesfcb70ef2011-02-02 16:02:30 -0800726 del deps_map[dep]["needs"][basedep]
727 deps_map[basedep]["provides"].remove(dep)
728 cycles = FindCycles()
729 seconds = time.time() - start
730 if "--quiet" not in emerge.opts and seconds >= 0.1:
731 print "Tree sanitized in %dm%.1fs" % (seconds / 60, seconds % 60)
732
David James8c7e5e32011-06-28 11:26:03 -0700733 def FindRecursiveProvides(pkg, seen):
734 """Find all nodes that require a particular package.
735
736 Assumes that graph is acyclic.
737
738 Args:
739 pkg: Package identifier.
740 seen: Nodes that have been visited so far.
741 """
742 if pkg in seen:
743 return
744 seen.add(pkg)
745 info = deps_map[pkg]
746 info["tprovides"] = info["provides"].copy()
747 for dep in info["provides"]:
748 FindRecursiveProvides(dep, seen)
749 info["tprovides"].update(deps_map[dep]["tprovides"])
750
David Jamesa22906f2011-05-04 19:53:26 -0700751 ReverseTree(deps_tree)
David Jamesa22906f2011-05-04 19:53:26 -0700752
David James386ccd12011-05-04 20:17:42 -0700753 # We need to remove unused packages so that we can use the dependency
754 # ordering of the install process to show us what cycles to crack.
755 RemoveUnusedPackages()
David Jamesfcb70ef2011-02-02 16:02:30 -0800756 SanitizeTree()
David James8c7e5e32011-06-28 11:26:03 -0700757 seen = set()
758 for pkg in deps_map:
759 FindRecursiveProvides(pkg, seen)
David Jamesfcb70ef2011-02-02 16:02:30 -0800760 return deps_map
761
762 def PrintInstallPlan(self, deps_map):
763 """Print an emerge-style install plan.
764
765 The install plan lists what packages we're installing, in order.
766 It's useful for understanding what parallel_emerge is doing.
767
768 Args:
769 deps_map: The dependency graph.
770 """
771
772 def InstallPlanAtNode(target, deps_map):
773 nodes = []
774 nodes.append(target)
775 for dep in deps_map[target]["provides"]:
776 del deps_map[dep]["needs"][target]
777 if not deps_map[dep]["needs"]:
778 nodes.extend(InstallPlanAtNode(dep, deps_map))
779 return nodes
780
781 deps_map = copy.deepcopy(deps_map)
782 install_plan = []
783 plan = set()
784 for target, info in deps_map.iteritems():
785 if not info["needs"] and target not in plan:
786 for item in InstallPlanAtNode(target, deps_map):
787 plan.add(item)
788 install_plan.append(self.package_db[item])
789
790 for pkg in plan:
791 del deps_map[pkg]
792
793 if deps_map:
794 print "Cyclic dependencies:", " ".join(deps_map)
795 PrintDepsMap(deps_map)
796 sys.exit(1)
797
798 self.emerge.depgraph.display(install_plan)
799
800
801def PrintDepsMap(deps_map):
802 """Print dependency graph, for each package list it's prerequisites."""
803 for i in sorted(deps_map):
804 print "%s: (%s) needs" % (i, deps_map[i]["action"])
805 needs = deps_map[i]["needs"]
806 for j in sorted(needs):
807 print " %s" % (j)
808 if not needs:
809 print " no dependencies"
810
811
812class EmergeJobState(object):
813 __slots__ = ["done", "filename", "last_notify_timestamp", "last_output_seek",
814 "last_output_timestamp", "pkgname", "retcode", "start_timestamp",
Thiago Goncalesf4acc422013-07-17 10:26:35 -0700815 "target", "fetch_only", "unpack_only"]
David Jamesfcb70ef2011-02-02 16:02:30 -0800816
817 def __init__(self, target, pkgname, done, filename, start_timestamp,
Thiago Goncalesf4acc422013-07-17 10:26:35 -0700818 retcode=None, fetch_only=False, unpack_only=False):
David Jamesfcb70ef2011-02-02 16:02:30 -0800819
820 # The full name of the target we're building (e.g.
821 # chromeos-base/chromeos-0.0.1-r60)
822 self.target = target
823
824 # The short name of the target we're building (e.g. chromeos-0.0.1-r60)
825 self.pkgname = pkgname
826
827 # Whether the job is done. (True if the job is done; false otherwise.)
828 self.done = done
829
830 # The filename where output is currently stored.
831 self.filename = filename
832
833 # The timestamp of the last time we printed the name of the log file. We
834 # print this at the beginning of the job, so this starts at
835 # start_timestamp.
836 self.last_notify_timestamp = start_timestamp
837
838 # The location (in bytes) of the end of the last complete line we printed.
839 # This starts off at zero. We use this to jump to the right place when we
840 # print output from the same ebuild multiple times.
841 self.last_output_seek = 0
842
843 # The timestamp of the last time we printed output. Since we haven't
844 # printed output yet, this starts at zero.
845 self.last_output_timestamp = 0
846
847 # The return code of our job, if the job is actually finished.
848 self.retcode = retcode
849
Brian Harring0be85c62012-03-17 19:52:12 -0700850 # Was this just a fetch job?
851 self.fetch_only = fetch_only
852
David Jamesfcb70ef2011-02-02 16:02:30 -0800853 # The timestamp when our job started.
854 self.start_timestamp = start_timestamp
855
Thiago Goncalesf4acc422013-07-17 10:26:35 -0700856 # No emerge, only unpack packages.
857 self.unpack_only = unpack_only
858
David Jamesfcb70ef2011-02-02 16:02:30 -0800859
David James321490a2012-12-17 12:05:56 -0800860def KillHandler(_signum, _frame):
David James7358d032011-05-19 10:40:03 -0700861 # Kill self and all subprocesses.
862 os.killpg(0, signal.SIGKILL)
863
David Jamesfcb70ef2011-02-02 16:02:30 -0800864def SetupWorkerSignals():
David James321490a2012-12-17 12:05:56 -0800865 def ExitHandler(_signum, _frame):
David James7358d032011-05-19 10:40:03 -0700866 # Set KILLED flag.
867 KILLED.set()
David James13cead42011-05-18 16:22:01 -0700868
David James7358d032011-05-19 10:40:03 -0700869 # Remove our signal handlers so we don't get called recursively.
870 signal.signal(signal.SIGINT, KillHandler)
871 signal.signal(signal.SIGTERM, KillHandler)
David Jamesfcb70ef2011-02-02 16:02:30 -0800872
873 # Ensure that we exit quietly and cleanly, if possible, when we receive
874 # SIGTERM or SIGINT signals. By default, when the user hits CTRL-C, all
875 # of the child processes will print details about KeyboardInterrupt
876 # exceptions, which isn't very helpful.
877 signal.signal(signal.SIGINT, ExitHandler)
878 signal.signal(signal.SIGTERM, ExitHandler)
879
David James6b29d052012-11-02 10:27:27 -0700880def EmergeProcess(output, *args, **kwargs):
David James1ed3e252011-10-05 20:26:15 -0700881 """Merge a package in a subprocess.
882
883 Args:
David James1ed3e252011-10-05 20:26:15 -0700884 output: Temporary file to write output.
David James6b29d052012-11-02 10:27:27 -0700885 *args: Arguments to pass to Scheduler constructor.
886 **kwargs: Keyword arguments to pass to Scheduler constructor.
David James1ed3e252011-10-05 20:26:15 -0700887
888 Returns:
889 The exit code returned by the subprocess.
890 """
891 pid = os.fork()
892 if pid == 0:
893 try:
894 # Sanity checks.
Mike Frysingerf02736e2013-11-08 15:27:00 -0500895 if sys.stdout.fileno() != 1:
896 raise Exception("sys.stdout.fileno() != 1")
897 if sys.stderr.fileno() != 2:
898 raise Exception("sys.stderr.fileno() != 2")
David James1ed3e252011-10-05 20:26:15 -0700899
900 # - Redirect 1 (stdout) and 2 (stderr) at our temporary file.
901 # - Redirect 0 to point at sys.stdin. In this case, sys.stdin
902 # points at a file reading os.devnull, because multiprocessing mucks
903 # with sys.stdin.
904 # - Leave the sys.stdin and output filehandles alone.
905 fd_pipes = {0: sys.stdin.fileno(),
906 1: output.fileno(),
907 2: output.fileno(),
908 sys.stdin.fileno(): sys.stdin.fileno(),
909 output.fileno(): output.fileno()}
David Jamesa249eef2013-07-19 14:03:45 -0700910 if 0 <= vercmp(portage.VERSION, "2.1.11.50"):
911 portage.process._setup_pipes(fd_pipes, close_fds=False)
912 else:
913 portage.process._setup_pipes(fd_pipes)
David James1ed3e252011-10-05 20:26:15 -0700914
915 # Portage doesn't like when sys.stdin.fileno() != 0, so point sys.stdin
916 # at the filehandle we just created in _setup_pipes.
917 if sys.stdin.fileno() != 0:
David James6b29d052012-11-02 10:27:27 -0700918 sys.__stdin__ = sys.stdin = os.fdopen(0, "r")
919
920 scheduler = Scheduler(*args, **kwargs)
921
922 # Enable blocker handling even though we're in --nodeps mode. This
923 # allows us to unmerge the blocker after we've merged the replacement.
924 scheduler._opts_ignore_blockers = frozenset()
David James1ed3e252011-10-05 20:26:15 -0700925
926 # Actually do the merge.
927 retval = scheduler.merge()
928
929 # We catch all exceptions here (including SystemExit, KeyboardInterrupt,
930 # etc) so as to ensure that we don't confuse the multiprocessing module,
931 # which expects that all forked children exit with os._exit().
David James321490a2012-12-17 12:05:56 -0800932 # pylint: disable=W0702
David James1ed3e252011-10-05 20:26:15 -0700933 except:
934 traceback.print_exc(file=output)
935 retval = 1
936 sys.stdout.flush()
937 sys.stderr.flush()
938 output.flush()
939 os._exit(retval)
940 else:
941 # Return the exit code of the subprocess.
942 return os.waitpid(pid, 0)[1]
David Jamesfcb70ef2011-02-02 16:02:30 -0800943
Thiago Goncalesf4acc422013-07-17 10:26:35 -0700944
945def UnpackPackage(pkg_state):
946 """Unpacks package described by pkg_state.
947
948 Args:
949 pkg_state: EmergeJobState object describing target.
950
951 Returns:
952 Exit code returned by subprocess.
953 """
954 pkgdir = os.environ.get("PKGDIR",
955 os.path.join(os.environ["SYSROOT"], "packages"))
956 root = os.environ.get("ROOT", os.environ["SYSROOT"])
957 path = os.path.join(pkgdir, pkg_state.target + ".tbz2")
958 comp = cros_build_lib.FindCompressor(cros_build_lib.COMP_BZIP2)
959 cmd = [comp, "-dc"]
960 if comp.endswith("pbzip2"):
961 cmd.append("--ignore-trailing-garbage=1")
962 cmd.append(path)
963
964 result = cros_build_lib.RunCommand(cmd, cwd=root, stdout_to_pipe=True,
965 print_cmd=False, error_code_ok=True)
966
967 # If we were not successful, return now and don't attempt untar.
968 if result.returncode:
969 return result.returncode
970
971 cmd = ["sudo", "tar", "-xf", "-", "-C", root]
972 result = cros_build_lib.RunCommand(cmd, cwd=root, input=result.output,
973 print_cmd=False, error_code_ok=True)
974
975 return result.returncode
976
977
978def EmergeWorker(task_queue, job_queue, emerge, package_db, fetch_only=False,
979 unpack_only=False):
David Jamesfcb70ef2011-02-02 16:02:30 -0800980 """This worker emerges any packages given to it on the task_queue.
981
982 Args:
983 task_queue: The queue of tasks for this worker to do.
984 job_queue: The queue of results from the worker.
985 emerge: An EmergeData() object.
986 package_db: A dict, mapping package ids to portage Package objects.
Brian Harring0be85c62012-03-17 19:52:12 -0700987 fetch_only: A bool, indicating if we should just fetch the target.
Thiago Goncalesf4acc422013-07-17 10:26:35 -0700988 unpack_only: A bool, indicating if we should just unpack the target.
David Jamesfcb70ef2011-02-02 16:02:30 -0800989
990 It expects package identifiers to be passed to it via task_queue. When
991 a task is started, it pushes the (target, filename) to the started_queue.
992 The output is stored in filename. When a merge starts or finishes, we push
993 EmergeJobState objects to the job_queue.
994 """
995
996 SetupWorkerSignals()
997 settings, trees, mtimedb = emerge.settings, emerge.trees, emerge.mtimedb
David Jamesdeebd692011-05-09 17:02:52 -0700998
999 # Disable flushing of caches to save on I/O.
David James7a1ea4b2011-10-13 15:06:41 -07001000 root = emerge.settings["ROOT"]
1001 vardb = emerge.trees[root]["vartree"].dbapi
1002 vardb._flush_cache_enabled = False
Brian Harring0be85c62012-03-17 19:52:12 -07001003 bindb = emerge.trees[root]["bintree"].dbapi
1004 # Might be a set, might be a list, might be None; no clue, just use shallow
1005 # copy to ensure we can roll it back.
1006 original_remotepkgs = copy.copy(bindb.bintree._remotepkgs)
David Jamesdeebd692011-05-09 17:02:52 -07001007
David Jamesfcb70ef2011-02-02 16:02:30 -08001008 opts, spinner = emerge.opts, emerge.spinner
1009 opts["--nodeps"] = True
Brian Harring0be85c62012-03-17 19:52:12 -07001010 if fetch_only:
1011 opts["--fetchonly"] = True
1012
David Jamesfcb70ef2011-02-02 16:02:30 -08001013 while True:
1014 # Wait for a new item to show up on the queue. This is a blocking wait,
1015 # so if there's nothing to do, we just sit here.
Brian Harring0be85c62012-03-17 19:52:12 -07001016 pkg_state = task_queue.get()
1017 if pkg_state is None:
David Jamesfcb70ef2011-02-02 16:02:30 -08001018 # If target is None, this means that the main thread wants us to quit.
1019 # The other workers need to exit too, so we'll push the message back on
1020 # to the queue so they'll get it too.
Brian Harring0be85c62012-03-17 19:52:12 -07001021 task_queue.put(None)
David Jamesfcb70ef2011-02-02 16:02:30 -08001022 return
David James7358d032011-05-19 10:40:03 -07001023 if KILLED.is_set():
1024 return
1025
Brian Harring0be85c62012-03-17 19:52:12 -07001026 target = pkg_state.target
1027
David Jamesfcb70ef2011-02-02 16:02:30 -08001028 db_pkg = package_db[target]
Brian Harring0be85c62012-03-17 19:52:12 -07001029
1030 if db_pkg.type_name == "binary":
1031 if not fetch_only and pkg_state.fetched_successfully:
1032 # Ensure portage doesn't think our pkg is remote- else it'll force
1033 # a redownload of it (even if the on-disk file is fine). In-memory
1034 # caching basically, implemented dumbly.
1035 bindb.bintree._remotepkgs = None
1036 else:
1037 bindb.bintree_remotepkgs = original_remotepkgs
1038
David Jamesfcb70ef2011-02-02 16:02:30 -08001039 db_pkg.root_config = emerge.root_config
1040 install_list = [db_pkg]
1041 pkgname = db_pkg.pf
1042 output = tempfile.NamedTemporaryFile(prefix=pkgname + "-", delete=False)
David James01b1e0f2012-06-07 17:18:05 -07001043 os.chmod(output.name, 644)
David Jamesfcb70ef2011-02-02 16:02:30 -08001044 start_timestamp = time.time()
Brian Harring0be85c62012-03-17 19:52:12 -07001045 job = EmergeJobState(target, pkgname, False, output.name, start_timestamp,
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001046 fetch_only=fetch_only, unpack_only=unpack_only)
David Jamesfcb70ef2011-02-02 16:02:30 -08001047 job_queue.put(job)
1048 if "--pretend" in opts:
1049 retcode = 0
1050 else:
David Jamesfcb70ef2011-02-02 16:02:30 -08001051 try:
David James386ccd12011-05-04 20:17:42 -07001052 emerge.scheduler_graph.mergelist = install_list
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001053 if unpack_only:
1054 retcode = UnpackPackage(pkg_state)
1055 else:
1056 retcode = EmergeProcess(output, settings, trees, mtimedb, opts,
1057 spinner, favorites=emerge.favorites,
1058 graph_config=emerge.scheduler_graph)
David Jamesfcb70ef2011-02-02 16:02:30 -08001059 except Exception:
1060 traceback.print_exc(file=output)
1061 retcode = 1
David James1ed3e252011-10-05 20:26:15 -07001062 output.close()
David Jamesfcb70ef2011-02-02 16:02:30 -08001063
David James7358d032011-05-19 10:40:03 -07001064 if KILLED.is_set():
1065 return
1066
David Jamesfcb70ef2011-02-02 16:02:30 -08001067 job = EmergeJobState(target, pkgname, True, output.name, start_timestamp,
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001068 retcode, fetch_only=fetch_only,
1069 unpack_only=unpack_only)
David Jamesfcb70ef2011-02-02 16:02:30 -08001070 job_queue.put(job)
1071
1072
1073class LinePrinter(object):
1074 """Helper object to print a single line."""
1075
1076 def __init__(self, line):
1077 self.line = line
1078
David James321490a2012-12-17 12:05:56 -08001079 def Print(self, _seek_locations):
David Jamesfcb70ef2011-02-02 16:02:30 -08001080 print self.line
1081
1082
1083class JobPrinter(object):
1084 """Helper object to print output of a job."""
1085
1086 def __init__(self, job, unlink=False):
1087 """Print output of job.
1088
Mike Frysinger02e1e072013-11-10 22:11:34 -05001089 If unlink is True, unlink the job output file when done.
1090 """
David Jamesfcb70ef2011-02-02 16:02:30 -08001091 self.current_time = time.time()
1092 self.job = job
1093 self.unlink = unlink
1094
1095 def Print(self, seek_locations):
1096
1097 job = self.job
1098
1099 # Calculate how long the job has been running.
1100 seconds = self.current_time - job.start_timestamp
1101
1102 # Note that we've printed out the job so far.
1103 job.last_output_timestamp = self.current_time
1104
1105 # Note that we're starting the job
1106 info = "job %s (%dm%.1fs)" % (job.pkgname, seconds / 60, seconds % 60)
1107 last_output_seek = seek_locations.get(job.filename, 0)
1108 if last_output_seek:
1109 print "=== Continue output for %s ===" % info
1110 else:
1111 print "=== Start output for %s ===" % info
1112
1113 # Print actual output from job
1114 f = codecs.open(job.filename, encoding='utf-8', errors='replace')
1115 f.seek(last_output_seek)
1116 prefix = job.pkgname + ":"
1117 for line in f:
1118
1119 # Save off our position in the file
1120 if line and line[-1] == "\n":
1121 last_output_seek = f.tell()
1122 line = line[:-1]
1123
1124 # Print our line
1125 print prefix, line.encode('utf-8', 'replace')
1126 f.close()
1127
1128 # Save our last spot in the file so that we don't print out the same
1129 # location twice.
1130 seek_locations[job.filename] = last_output_seek
1131
1132 # Note end of output section
1133 if job.done:
1134 print "=== Complete: %s ===" % info
1135 else:
1136 print "=== Still running: %s ===" % info
1137
1138 if self.unlink:
1139 os.unlink(job.filename)
1140
1141
1142def PrintWorker(queue):
1143 """A worker that prints stuff to the screen as requested."""
1144
David James321490a2012-12-17 12:05:56 -08001145 def ExitHandler(_signum, _frame):
David James7358d032011-05-19 10:40:03 -07001146 # Set KILLED flag.
1147 KILLED.set()
1148
David Jamesfcb70ef2011-02-02 16:02:30 -08001149 # Switch to default signal handlers so that we'll die after two signals.
David James7358d032011-05-19 10:40:03 -07001150 signal.signal(signal.SIGINT, KillHandler)
1151 signal.signal(signal.SIGTERM, KillHandler)
David Jamesfcb70ef2011-02-02 16:02:30 -08001152
1153 # Don't exit on the first SIGINT / SIGTERM, because the parent worker will
1154 # handle it and tell us when we need to exit.
1155 signal.signal(signal.SIGINT, ExitHandler)
1156 signal.signal(signal.SIGTERM, ExitHandler)
1157
1158 # seek_locations is a map indicating the position we are at in each file.
1159 # It starts off empty, but is set by the various Print jobs as we go along
1160 # to indicate where we left off in each file.
1161 seek_locations = {}
1162 while True:
1163 try:
1164 job = queue.get()
1165 if job:
1166 job.Print(seek_locations)
David Jamesbccf8eb2011-07-27 14:06:06 -07001167 sys.stdout.flush()
David Jamesfcb70ef2011-02-02 16:02:30 -08001168 else:
1169 break
1170 except IOError as ex:
1171 if ex.errno == errno.EINTR:
1172 # Looks like we received a signal. Keep printing.
1173 continue
1174 raise
1175
Brian Harring867e2362012-03-17 04:05:17 -07001176
Brian Harring0be85c62012-03-17 19:52:12 -07001177class TargetState(object):
Brian Harring867e2362012-03-17 04:05:17 -07001178
Brian Harring0be85c62012-03-17 19:52:12 -07001179 __slots__ = ("target", "info", "score", "prefetched", "fetched_successfully")
Brian Harring867e2362012-03-17 04:05:17 -07001180
David James321490a2012-12-17 12:05:56 -08001181 def __init__(self, target, info):
Brian Harring867e2362012-03-17 04:05:17 -07001182 self.target, self.info = target, info
Brian Harring0be85c62012-03-17 19:52:12 -07001183 self.fetched_successfully = False
1184 self.prefetched = False
David James321490a2012-12-17 12:05:56 -08001185 self.score = None
Brian Harring867e2362012-03-17 04:05:17 -07001186 self.update_score()
1187
1188 def __cmp__(self, other):
1189 return cmp(self.score, other.score)
1190
1191 def update_score(self):
1192 self.score = (
1193 -len(self.info["tprovides"]),
Brian Harring0be85c62012-03-17 19:52:12 -07001194 len(self.info["needs"]),
Brian Harring11c5eeb2012-03-18 11:02:39 -07001195 not self.info["binary"],
Brian Harring867e2362012-03-17 04:05:17 -07001196 -len(self.info["provides"]),
1197 self.info["idx"],
1198 self.target,
1199 )
1200
1201
1202class ScoredHeap(object):
1203
Brian Harring0be85c62012-03-17 19:52:12 -07001204 __slots__ = ("heap", "_heap_set")
1205
Brian Harring867e2362012-03-17 04:05:17 -07001206 def __init__(self, initial=()):
Brian Harring0be85c62012-03-17 19:52:12 -07001207 self.heap = list()
1208 self._heap_set = set()
1209 if initial:
1210 self.multi_put(initial)
Brian Harring867e2362012-03-17 04:05:17 -07001211
1212 def get(self):
Brian Harring0be85c62012-03-17 19:52:12 -07001213 item = heapq.heappop(self.heap)
1214 self._heap_set.remove(item.target)
1215 return item
Brian Harring867e2362012-03-17 04:05:17 -07001216
Brian Harring0be85c62012-03-17 19:52:12 -07001217 def put(self, item):
1218 if not isinstance(item, TargetState):
1219 raise ValueError("Item %r isn't a TargetState" % (item,))
1220 heapq.heappush(self.heap, item)
1221 self._heap_set.add(item.target)
Brian Harring867e2362012-03-17 04:05:17 -07001222
Brian Harring0be85c62012-03-17 19:52:12 -07001223 def multi_put(self, sequence):
1224 sequence = list(sequence)
1225 self.heap.extend(sequence)
1226 self._heap_set.update(x.target for x in sequence)
Brian Harring867e2362012-03-17 04:05:17 -07001227 self.sort()
1228
David James5c9996d2012-03-24 10:50:46 -07001229 def sort(self):
1230 heapq.heapify(self.heap)
1231
Brian Harring0be85c62012-03-17 19:52:12 -07001232 def __contains__(self, target):
1233 return target in self._heap_set
1234
1235 def __nonzero__(self):
1236 return bool(self.heap)
1237
Brian Harring867e2362012-03-17 04:05:17 -07001238 def __len__(self):
1239 return len(self.heap)
1240
1241
David Jamesfcb70ef2011-02-02 16:02:30 -08001242class EmergeQueue(object):
1243 """Class to schedule emerge jobs according to a dependency graph."""
1244
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001245 def __init__(self, deps_map, emerge, package_db, show_output, unpack_only):
David Jamesfcb70ef2011-02-02 16:02:30 -08001246 # Store the dependency graph.
1247 self._deps_map = deps_map
Brian Harring0be85c62012-03-17 19:52:12 -07001248 self._state_map = {}
David Jamesfcb70ef2011-02-02 16:02:30 -08001249 # Initialize the running queue to empty
Brian Harring0be85c62012-03-17 19:52:12 -07001250 self._build_jobs = {}
1251 self._build_ready = ScoredHeap()
1252 self._fetch_jobs = {}
1253 self._fetch_ready = ScoredHeap()
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001254 self._unpack_jobs = {}
1255 self._unpack_ready = ScoredHeap()
David Jamesfcb70ef2011-02-02 16:02:30 -08001256 # List of total package installs represented in deps_map.
1257 install_jobs = [x for x in deps_map if deps_map[x]["action"] == "merge"]
1258 self._total_jobs = len(install_jobs)
1259 self._show_output = show_output
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001260 self._unpack_only = unpack_only
David Jamesfcb70ef2011-02-02 16:02:30 -08001261
1262 if "--pretend" in emerge.opts:
1263 print "Skipping merge because of --pretend mode."
1264 sys.exit(0)
1265
David James7358d032011-05-19 10:40:03 -07001266 # Set a process group so we can easily terminate all children.
1267 os.setsid()
1268
David Jamesfcb70ef2011-02-02 16:02:30 -08001269 # Setup scheduler graph object. This is used by the child processes
1270 # to help schedule jobs.
1271 emerge.scheduler_graph = emerge.depgraph.schedulerGraph()
1272
1273 # Calculate how many jobs we can run in parallel. We don't want to pass
1274 # the --jobs flag over to emerge itself, because that'll tell emerge to
1275 # hide its output, and said output is quite useful for debugging hung
1276 # jobs.
1277 procs = min(self._total_jobs,
1278 emerge.opts.pop("--jobs", multiprocessing.cpu_count()))
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001279 self._build_procs = self._unpack_procs = self._fetch_procs = max(1, procs)
David James8c7e5e32011-06-28 11:26:03 -07001280 self._load_avg = emerge.opts.pop("--load-average", None)
David Jamesfcb70ef2011-02-02 16:02:30 -08001281 self._job_queue = multiprocessing.Queue()
1282 self._print_queue = multiprocessing.Queue()
Brian Harring0be85c62012-03-17 19:52:12 -07001283
1284 self._fetch_queue = multiprocessing.Queue()
1285 args = (self._fetch_queue, self._job_queue, emerge, package_db, True)
1286 self._fetch_pool = multiprocessing.Pool(self._fetch_procs, EmergeWorker,
1287 args)
1288
1289 self._build_queue = multiprocessing.Queue()
1290 args = (self._build_queue, self._job_queue, emerge, package_db)
1291 self._build_pool = multiprocessing.Pool(self._build_procs, EmergeWorker,
1292 args)
1293
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001294 if self._unpack_only:
1295 # Unpack pool only required on unpack_only jobs.
1296 self._unpack_queue = multiprocessing.Queue()
1297 args = (self._unpack_queue, self._job_queue, emerge, package_db, False,
1298 True)
1299 self._unpack_pool = multiprocessing.Pool(self._unpack_procs, EmergeWorker,
1300 args)
1301
David Jamesfcb70ef2011-02-02 16:02:30 -08001302 self._print_worker = multiprocessing.Process(target=PrintWorker,
1303 args=[self._print_queue])
1304 self._print_worker.start()
1305
1306 # Initialize the failed queue to empty.
1307 self._retry_queue = []
1308 self._failed = set()
1309
David Jamesfcb70ef2011-02-02 16:02:30 -08001310 # Setup an exit handler so that we print nice messages if we are
1311 # terminated.
1312 self._SetupExitHandler()
1313
1314 # Schedule our jobs.
Brian Harring0be85c62012-03-17 19:52:12 -07001315 self._state_map.update(
1316 (pkg, TargetState(pkg, data)) for pkg, data in deps_map.iteritems())
1317 self._fetch_ready.multi_put(self._state_map.itervalues())
David Jamesfcb70ef2011-02-02 16:02:30 -08001318
1319 def _SetupExitHandler(self):
1320
David James321490a2012-12-17 12:05:56 -08001321 def ExitHandler(signum, _frame):
David James7358d032011-05-19 10:40:03 -07001322 # Set KILLED flag.
1323 KILLED.set()
David Jamesfcb70ef2011-02-02 16:02:30 -08001324
1325 # Kill our signal handlers so we don't get called recursively
David James7358d032011-05-19 10:40:03 -07001326 signal.signal(signal.SIGINT, KillHandler)
1327 signal.signal(signal.SIGTERM, KillHandler)
David Jamesfcb70ef2011-02-02 16:02:30 -08001328
1329 # Print our current job status
Brian Harring0be85c62012-03-17 19:52:12 -07001330 for job in self._build_jobs.itervalues():
David Jamesfcb70ef2011-02-02 16:02:30 -08001331 if job:
1332 self._print_queue.put(JobPrinter(job, unlink=True))
1333
1334 # Notify the user that we are exiting
1335 self._Print("Exiting on signal %s" % signum)
David James7358d032011-05-19 10:40:03 -07001336 self._print_queue.put(None)
1337 self._print_worker.join()
David Jamesfcb70ef2011-02-02 16:02:30 -08001338
1339 # Kill child threads, then exit.
David James7358d032011-05-19 10:40:03 -07001340 os.killpg(0, signal.SIGKILL)
David Jamesfcb70ef2011-02-02 16:02:30 -08001341 sys.exit(1)
1342
1343 # Print out job status when we are killed
1344 signal.signal(signal.SIGINT, ExitHandler)
1345 signal.signal(signal.SIGTERM, ExitHandler)
1346
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001347 def _ScheduleUnpack(self, pkg_state):
1348 self._unpack_jobs[pkg_state.target] = None
1349 self._unpack_queue.put(pkg_state)
1350
Brian Harring0be85c62012-03-17 19:52:12 -07001351 def _Schedule(self, pkg_state):
David Jamesfcb70ef2011-02-02 16:02:30 -08001352 # We maintain a tree of all deps, if this doesn't need
David James8c7e5e32011-06-28 11:26:03 -07001353 # to be installed just free up its children and continue.
David Jamesfcb70ef2011-02-02 16:02:30 -08001354 # It is possible to reinstall deps of deps, without reinstalling
1355 # first level deps, like so:
1356 # chromeos (merge) -> eselect (nomerge) -> python (merge)
Brian Harring0be85c62012-03-17 19:52:12 -07001357 this_pkg = pkg_state.info
1358 target = pkg_state.target
1359 if pkg_state.info is not None:
1360 if this_pkg["action"] == "nomerge":
1361 self._Finish(target)
1362 elif target not in self._build_jobs:
1363 # Kick off the build if it's marked to be built.
1364 self._build_jobs[target] = None
1365 self._build_queue.put(pkg_state)
1366 return True
David Jamesfcb70ef2011-02-02 16:02:30 -08001367
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001368 def _ScheduleLoop(self, unpack_only=False):
1369 if unpack_only:
1370 ready_queue = self._unpack_ready
1371 jobs_queue = self._unpack_jobs
1372 procs = self._unpack_procs
1373 else:
1374 ready_queue = self._build_ready
1375 jobs_queue = self._build_jobs
1376 procs = self._build_procs
1377
David James8c7e5e32011-06-28 11:26:03 -07001378 # If the current load exceeds our desired load average, don't schedule
1379 # more than one job.
1380 if self._load_avg and os.getloadavg()[0] > self._load_avg:
1381 needed_jobs = 1
1382 else:
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001383 needed_jobs = procs
David James8c7e5e32011-06-28 11:26:03 -07001384
1385 # Schedule more jobs.
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001386 while ready_queue and len(jobs_queue) < needed_jobs:
1387 state = ready_queue.get()
1388 if unpack_only:
1389 self._ScheduleUnpack(state)
1390 else:
1391 if state.target not in self._failed:
1392 self._Schedule(state)
David Jamesfcb70ef2011-02-02 16:02:30 -08001393
1394 def _Print(self, line):
1395 """Print a single line."""
1396 self._print_queue.put(LinePrinter(line))
1397
1398 def _Status(self):
1399 """Print status."""
1400 current_time = time.time()
1401 no_output = True
1402
1403 # Print interim output every minute if --show-output is used. Otherwise,
1404 # print notifications about running packages every 2 minutes, and print
1405 # full output for jobs that have been running for 60 minutes or more.
1406 if self._show_output:
1407 interval = 60
1408 notify_interval = 0
1409 else:
1410 interval = 60 * 60
1411 notify_interval = 60 * 2
David James321490a2012-12-17 12:05:56 -08001412 for job in self._build_jobs.itervalues():
David Jamesfcb70ef2011-02-02 16:02:30 -08001413 if job:
1414 last_timestamp = max(job.start_timestamp, job.last_output_timestamp)
1415 if last_timestamp + interval < current_time:
1416 self._print_queue.put(JobPrinter(job))
1417 job.last_output_timestamp = current_time
1418 no_output = False
1419 elif (notify_interval and
1420 job.last_notify_timestamp + notify_interval < current_time):
1421 job_seconds = current_time - job.start_timestamp
1422 args = (job.pkgname, job_seconds / 60, job_seconds % 60, job.filename)
1423 info = "Still building %s (%dm%.1fs). Logs in %s" % args
1424 job.last_notify_timestamp = current_time
1425 self._Print(info)
1426 no_output = False
1427
1428 # If we haven't printed any messages yet, print a general status message
1429 # here.
1430 if no_output:
1431 seconds = current_time - GLOBAL_START
Brian Harring0be85c62012-03-17 19:52:12 -07001432 fjobs, fready = len(self._fetch_jobs), len(self._fetch_ready)
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001433 ujobs, uready = len(self._unpack_jobs), len(self._unpack_ready)
Brian Harring0be85c62012-03-17 19:52:12 -07001434 bjobs, bready = len(self._build_jobs), len(self._build_ready)
1435 retries = len(self._retry_queue)
1436 pending = max(0, len(self._deps_map) - fjobs - bjobs)
1437 line = "Pending %s/%s, " % (pending, self._total_jobs)
1438 if fjobs or fready:
1439 line += "Fetching %s/%s, " % (fjobs, fready + fjobs)
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001440 if ujobs or uready:
1441 line += "Unpacking %s/%s, " % (ujobs, uready + ujobs)
Brian Harring0be85c62012-03-17 19:52:12 -07001442 if bjobs or bready or retries:
1443 line += "Building %s/%s, " % (bjobs, bready + bjobs)
1444 if retries:
1445 line += "Retrying %s, " % (retries,)
David James8c7e5e32011-06-28 11:26:03 -07001446 load = " ".join(str(x) for x in os.getloadavg())
Brian Harring0be85c62012-03-17 19:52:12 -07001447 line += ("[Time %dm%.1fs Load %s]" % (seconds/60, seconds %60, load))
1448 self._Print(line)
David Jamesfcb70ef2011-02-02 16:02:30 -08001449
1450 def _Finish(self, target):
David James8c7e5e32011-06-28 11:26:03 -07001451 """Mark a target as completed and unblock dependencies."""
1452 this_pkg = self._deps_map[target]
1453 if this_pkg["needs"] and this_pkg["nodeps"]:
1454 # We got installed, but our deps have not been installed yet. Dependent
1455 # packages should only be installed when our needs have been fully met.
1456 this_pkg["action"] = "nomerge"
1457 else:
David James8c7e5e32011-06-28 11:26:03 -07001458 for dep in this_pkg["provides"]:
1459 dep_pkg = self._deps_map[dep]
Brian Harring0be85c62012-03-17 19:52:12 -07001460 state = self._state_map[dep]
David James8c7e5e32011-06-28 11:26:03 -07001461 del dep_pkg["needs"][target]
Brian Harring0be85c62012-03-17 19:52:12 -07001462 state.update_score()
1463 if not state.prefetched:
1464 if dep in self._fetch_ready:
1465 # If it's not currently being fetched, update the prioritization
1466 self._fetch_ready.sort()
1467 elif not dep_pkg["needs"]:
David James8c7e5e32011-06-28 11:26:03 -07001468 if dep_pkg["nodeps"] and dep_pkg["action"] == "nomerge":
1469 self._Finish(dep)
1470 else:
Brian Harring0be85c62012-03-17 19:52:12 -07001471 self._build_ready.put(self._state_map[dep])
David James8c7e5e32011-06-28 11:26:03 -07001472 self._deps_map.pop(target)
David Jamesfcb70ef2011-02-02 16:02:30 -08001473
1474 def _Retry(self):
David James8c7e5e32011-06-28 11:26:03 -07001475 while self._retry_queue:
Brian Harring0be85c62012-03-17 19:52:12 -07001476 state = self._retry_queue.pop(0)
1477 if self._Schedule(state):
1478 self._Print("Retrying emerge of %s." % state.target)
David James8c7e5e32011-06-28 11:26:03 -07001479 break
David Jamesfcb70ef2011-02-02 16:02:30 -08001480
Brian Harringa43f5952012-04-12 01:19:34 -07001481 def _Shutdown(self):
David Jamesfcb70ef2011-02-02 16:02:30 -08001482 # Tell emerge workers to exit. They all exit when 'None' is pushed
1483 # to the queue.
Brian Harring0be85c62012-03-17 19:52:12 -07001484
Brian Harringa43f5952012-04-12 01:19:34 -07001485 # Shutdown the workers first; then jobs (which is how they feed things back)
1486 # then finally the print queue.
Brian Harring0be85c62012-03-17 19:52:12 -07001487
Brian Harringa43f5952012-04-12 01:19:34 -07001488 def _stop(queue, pool):
1489 if pool is None:
1490 return
1491 try:
1492 queue.put(None)
1493 pool.close()
1494 pool.join()
1495 finally:
1496 pool.terminate()
Brian Harring0be85c62012-03-17 19:52:12 -07001497
Brian Harringa43f5952012-04-12 01:19:34 -07001498 _stop(self._fetch_queue, self._fetch_pool)
1499 self._fetch_queue = self._fetch_pool = None
Brian Harring0be85c62012-03-17 19:52:12 -07001500
Brian Harringa43f5952012-04-12 01:19:34 -07001501 _stop(self._build_queue, self._build_pool)
1502 self._build_queue = self._build_pool = None
1503
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001504 if self._unpack_only:
1505 _stop(self._unpack_queue, self._unpack_pool)
1506 self._unpack_queue = self._unpack_pool = None
1507
Brian Harringa43f5952012-04-12 01:19:34 -07001508 if self._job_queue is not None:
1509 self._job_queue.close()
1510 self._job_queue = None
David Jamesfcb70ef2011-02-02 16:02:30 -08001511
1512 # Now that our workers are finished, we can kill the print queue.
Brian Harringa43f5952012-04-12 01:19:34 -07001513 if self._print_worker is not None:
1514 try:
1515 self._print_queue.put(None)
1516 self._print_queue.close()
1517 self._print_worker.join()
1518 finally:
1519 self._print_worker.terminate()
1520 self._print_queue = self._print_worker = None
David Jamesfcb70ef2011-02-02 16:02:30 -08001521
1522 def Run(self):
1523 """Run through the scheduled ebuilds.
1524
1525 Keep running so long as we have uninstalled packages in the
1526 dependency graph to merge.
1527 """
Brian Harringa43f5952012-04-12 01:19:34 -07001528 if not self._deps_map:
1529 return
1530
Brian Harring0be85c62012-03-17 19:52:12 -07001531 # Start the fetchers.
1532 for _ in xrange(min(self._fetch_procs, len(self._fetch_ready))):
1533 state = self._fetch_ready.get()
1534 self._fetch_jobs[state.target] = None
1535 self._fetch_queue.put(state)
1536
1537 # Print an update, then get going.
1538 self._Status()
1539
David Jamese703d0f2012-01-12 16:27:45 -08001540 retried = set()
David Jamesfcb70ef2011-02-02 16:02:30 -08001541 while self._deps_map:
1542 # Check here that we are actually waiting for something.
Brian Harring0be85c62012-03-17 19:52:12 -07001543 if (self._build_queue.empty() and
David Jamesfcb70ef2011-02-02 16:02:30 -08001544 self._job_queue.empty() and
Brian Harring0be85c62012-03-17 19:52:12 -07001545 not self._fetch_jobs and
1546 not self._fetch_ready and
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001547 not self._unpack_jobs and
1548 not self._unpack_ready and
Brian Harring0be85c62012-03-17 19:52:12 -07001549 not self._build_jobs and
1550 not self._build_ready and
David Jamesfcb70ef2011-02-02 16:02:30 -08001551 self._deps_map):
1552 # If we have failed on a package, retry it now.
1553 if self._retry_queue:
1554 self._Retry()
1555 else:
David Jamesfcb70ef2011-02-02 16:02:30 -08001556 # Tell the user why we're exiting.
1557 if self._failed:
Mike Frysingerf2ff9172012-11-01 18:47:41 -04001558 print 'Packages failed:\n\t%s' % '\n\t'.join(self._failed)
David James0eae23e2012-07-03 15:04:25 -07001559 status_file = os.environ.get("PARALLEL_EMERGE_STATUS_FILE")
1560 if status_file:
David James321490a2012-12-17 12:05:56 -08001561 failed_pkgs = set(portage.versions.cpv_getkey(x)
1562 for x in self._failed)
David James0eae23e2012-07-03 15:04:25 -07001563 with open(status_file, "a") as f:
1564 f.write("%s\n" % " ".join(failed_pkgs))
David Jamesfcb70ef2011-02-02 16:02:30 -08001565 else:
1566 print "Deadlock! Circular dependencies!"
1567 sys.exit(1)
1568
David James321490a2012-12-17 12:05:56 -08001569 for _ in xrange(12):
David Jamesa74289a2011-08-12 10:41:24 -07001570 try:
1571 job = self._job_queue.get(timeout=5)
1572 break
1573 except Queue.Empty:
1574 # Check if any more jobs can be scheduled.
1575 self._ScheduleLoop()
1576 else:
Brian Harring706747c2012-03-16 03:04:31 -07001577 # Print an update every 60 seconds.
David Jamesfcb70ef2011-02-02 16:02:30 -08001578 self._Status()
1579 continue
1580
1581 target = job.target
1582
Brian Harring0be85c62012-03-17 19:52:12 -07001583 if job.fetch_only:
1584 if not job.done:
1585 self._fetch_jobs[job.target] = job
1586 else:
1587 state = self._state_map[job.target]
1588 state.prefetched = True
1589 state.fetched_successfully = (job.retcode == 0)
1590 del self._fetch_jobs[job.target]
1591 self._Print("Fetched %s in %2.2fs"
1592 % (target, time.time() - job.start_timestamp))
1593
1594 if self._show_output or job.retcode != 0:
1595 self._print_queue.put(JobPrinter(job, unlink=True))
1596 else:
1597 os.unlink(job.filename)
1598 # Failure or not, let build work with it next.
1599 if not self._deps_map[job.target]["needs"]:
1600 self._build_ready.put(state)
1601 self._ScheduleLoop()
1602
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001603 if self._unpack_only and job.retcode == 0:
1604 self._unpack_ready.put(state)
1605 self._ScheduleLoop(unpack_only=True)
1606
Brian Harring0be85c62012-03-17 19:52:12 -07001607 if self._fetch_ready:
1608 state = self._fetch_ready.get()
1609 self._fetch_queue.put(state)
1610 self._fetch_jobs[state.target] = None
1611 else:
1612 # Minor optimization; shut down fetchers early since we know
1613 # the queue is empty.
1614 self._fetch_queue.put(None)
1615 continue
1616
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001617 if job.unpack_only:
1618 if not job.done:
1619 self._unpack_jobs[target] = job
1620 else:
1621 del self._unpack_jobs[target]
1622 self._Print("Unpacked %s in %2.2fs"
1623 % (target, time.time() - job.start_timestamp))
1624 if self._show_output or job.retcode != 0:
1625 self._print_queue.put(JobPrinter(job, unlink=True))
1626 else:
1627 os.unlink(job.filename)
1628 if self._unpack_ready:
1629 state = self._unpack_ready.get()
1630 self._unpack_queue.put(state)
1631 self._unpack_jobs[state.target] = None
1632 continue
1633
David Jamesfcb70ef2011-02-02 16:02:30 -08001634 if not job.done:
Brian Harring0be85c62012-03-17 19:52:12 -07001635 self._build_jobs[target] = job
David Jamesfcb70ef2011-02-02 16:02:30 -08001636 self._Print("Started %s (logged in %s)" % (target, job.filename))
1637 continue
1638
1639 # Print output of job
1640 if self._show_output or job.retcode != 0:
1641 self._print_queue.put(JobPrinter(job, unlink=True))
1642 else:
1643 os.unlink(job.filename)
Brian Harring0be85c62012-03-17 19:52:12 -07001644 del self._build_jobs[target]
David Jamesfcb70ef2011-02-02 16:02:30 -08001645
1646 seconds = time.time() - job.start_timestamp
1647 details = "%s (in %dm%.1fs)" % (target, seconds / 60, seconds % 60)
David James32420cc2011-08-25 21:32:46 -07001648 previously_failed = target in self._failed
David Jamesfcb70ef2011-02-02 16:02:30 -08001649
1650 # Complain if necessary.
1651 if job.retcode != 0:
1652 # Handle job failure.
David James32420cc2011-08-25 21:32:46 -07001653 if previously_failed:
David Jamesfcb70ef2011-02-02 16:02:30 -08001654 # If this job has failed previously, give up.
1655 self._Print("Failed %s. Your build has failed." % details)
1656 else:
1657 # Queue up this build to try again after a long while.
David Jamese703d0f2012-01-12 16:27:45 -08001658 retried.add(target)
Brian Harring0be85c62012-03-17 19:52:12 -07001659 self._retry_queue.append(self._state_map[target])
David Jamesfcb70ef2011-02-02 16:02:30 -08001660 self._failed.add(target)
1661 self._Print("Failed %s, retrying later." % details)
1662 else:
David James32420cc2011-08-25 21:32:46 -07001663 if previously_failed:
1664 # Remove target from list of failed packages.
1665 self._failed.remove(target)
1666
1667 self._Print("Completed %s" % details)
1668
1669 # Mark as completed and unblock waiting ebuilds.
1670 self._Finish(target)
1671
1672 if previously_failed and self._retry_queue:
David Jamesfcb70ef2011-02-02 16:02:30 -08001673 # If we have successfully retried a failed package, and there
1674 # are more failed packages, try the next one. We will only have
1675 # one retrying package actively running at a time.
1676 self._Retry()
1677
David Jamesfcb70ef2011-02-02 16:02:30 -08001678
David James8c7e5e32011-06-28 11:26:03 -07001679 # Schedule pending jobs and print an update.
1680 self._ScheduleLoop()
1681 self._Status()
David Jamesfcb70ef2011-02-02 16:02:30 -08001682
David Jamese703d0f2012-01-12 16:27:45 -08001683 # If packages were retried, output a warning.
1684 if retried:
1685 self._Print("")
1686 self._Print("WARNING: The following packages failed the first time,")
1687 self._Print("but succeeded upon retry. This might indicate incorrect")
1688 self._Print("dependencies.")
1689 for pkg in retried:
1690 self._Print(" %s" % pkg)
1691 self._Print("@@@STEP_WARNINGS@@@")
1692 self._Print("")
1693
David Jamesfcb70ef2011-02-02 16:02:30 -08001694 # Tell child threads to exit.
1695 self._Print("Merge complete")
David Jamesfcb70ef2011-02-02 16:02:30 -08001696
1697
Brian Harring30675052012-02-29 12:18:22 -08001698def main(argv):
Brian Harring8294d652012-05-23 02:20:52 -07001699 try:
1700 return real_main(argv)
1701 finally:
1702 # Work around multiprocessing sucking and not cleaning up after itself.
1703 # http://bugs.python.org/issue4106;
1704 # Step one; ensure GC is ran *prior* to the VM starting shutdown.
1705 gc.collect()
1706 # Step two; go looking for those threads and try to manually reap
1707 # them if we can.
1708 for x in threading.enumerate():
1709 # Filter on the name, and ident; if ident is None, the thread
1710 # wasn't started.
1711 if x.name == 'QueueFeederThread' and x.ident is not None:
1712 x.join(1)
David Jamesfcb70ef2011-02-02 16:02:30 -08001713
Brian Harring8294d652012-05-23 02:20:52 -07001714
1715def real_main(argv):
Brian Harring30675052012-02-29 12:18:22 -08001716 parallel_emerge_args = argv[:]
David Jamesfcb70ef2011-02-02 16:02:30 -08001717 deps = DepGraphGenerator()
Brian Harring30675052012-02-29 12:18:22 -08001718 deps.Initialize(parallel_emerge_args)
David Jamesfcb70ef2011-02-02 16:02:30 -08001719 emerge = deps.emerge
1720
1721 if emerge.action is not None:
Brian Harring30675052012-02-29 12:18:22 -08001722 argv = deps.ParseParallelEmergeArgs(argv)
Brian Harring8294d652012-05-23 02:20:52 -07001723 return emerge_main(argv)
David Jamesfcb70ef2011-02-02 16:02:30 -08001724 elif not emerge.cmdline_packages:
1725 Usage()
Brian Harring8294d652012-05-23 02:20:52 -07001726 return 1
David Jamesfcb70ef2011-02-02 16:02:30 -08001727
1728 # Unless we're in pretend mode, there's not much point running without
1729 # root access. We need to be able to install packages.
1730 #
1731 # NOTE: Even if you're running --pretend, it's a good idea to run
1732 # parallel_emerge with root access so that portage can write to the
1733 # dependency cache. This is important for performance.
David James321490a2012-12-17 12:05:56 -08001734 if "--pretend" not in emerge.opts and portage.data.secpass < 2:
David Jamesfcb70ef2011-02-02 16:02:30 -08001735 print "parallel_emerge: superuser access is required."
Brian Harring8294d652012-05-23 02:20:52 -07001736 return 1
David Jamesfcb70ef2011-02-02 16:02:30 -08001737
1738 if "--quiet" not in emerge.opts:
1739 cmdline_packages = " ".join(emerge.cmdline_packages)
David Jamesfcb70ef2011-02-02 16:02:30 -08001740 print "Starting fast-emerge."
1741 print " Building package %s on %s" % (cmdline_packages,
1742 deps.board or "root")
David Jamesfcb70ef2011-02-02 16:02:30 -08001743
David James386ccd12011-05-04 20:17:42 -07001744 deps_tree, deps_info = deps.GenDependencyTree()
David Jamesfcb70ef2011-02-02 16:02:30 -08001745
1746 # You want me to be verbose? I'll give you two trees! Twice as much value.
1747 if "--tree" in emerge.opts and "--verbose" in emerge.opts:
1748 deps.PrintTree(deps_tree)
1749
David James386ccd12011-05-04 20:17:42 -07001750 deps_graph = deps.GenDependencyGraph(deps_tree, deps_info)
David Jamesfcb70ef2011-02-02 16:02:30 -08001751
1752 # OK, time to print out our progress so far.
1753 deps.PrintInstallPlan(deps_graph)
1754 if "--tree" in emerge.opts:
1755 PrintDepsMap(deps_graph)
1756
1757 # Are we upgrading portage? If so, and there are more packages to merge,
1758 # schedule a restart of parallel_emerge to merge the rest. This ensures that
1759 # we pick up all updates to portage settings before merging any more
1760 # packages.
1761 portage_upgrade = False
1762 root = emerge.settings["ROOT"]
1763 final_db = emerge.depgraph._dynamic_config.mydbapi[root]
1764 if root == "/":
1765 for db_pkg in final_db.match_pkgs("sys-apps/portage"):
1766 portage_pkg = deps_graph.get(db_pkg.cpv)
David James0ff16f22012-11-02 14:18:07 -07001767 if portage_pkg:
David Jamesfcb70ef2011-02-02 16:02:30 -08001768 portage_upgrade = True
1769 if "--quiet" not in emerge.opts:
1770 print "Upgrading portage first, then restarting..."
1771
David James0ff16f22012-11-02 14:18:07 -07001772 # Upgrade Portage first, then the rest of the packages.
1773 #
1774 # In order to grant the child permission to run setsid, we need to run sudo
1775 # again. We preserve SUDO_USER here in case an ebuild depends on it.
1776 if portage_upgrade:
1777 # Calculate what arguments to use when re-invoking.
1778 args = ["sudo", "-E", "SUDO_USER=%s" % os.environ.get("SUDO_USER", "")]
1779 args += [os.path.abspath(sys.argv[0])] + parallel_emerge_args
1780 args += ["--exclude=sys-apps/portage"]
1781
1782 # First upgrade Portage.
1783 passthrough_args = ("--quiet", "--pretend", "--verbose")
1784 emerge_args = [k for k in emerge.opts if k in passthrough_args]
1785 ret = emerge_main(emerge_args + ["portage"])
1786 if ret != 0:
1787 return ret
1788
1789 # Now upgrade the rest.
1790 os.execvp(args[0], args)
1791
David Jamesfcb70ef2011-02-02 16:02:30 -08001792 # Run the queued emerges.
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001793 scheduler = EmergeQueue(deps_graph, emerge, deps.package_db, deps.show_output,
1794 deps.unpack_only)
Brian Harringa43f5952012-04-12 01:19:34 -07001795 try:
1796 scheduler.Run()
1797 finally:
1798 scheduler._Shutdown()
David James97ce8902011-08-16 09:51:05 -07001799 scheduler = None
David Jamesfcb70ef2011-02-02 16:02:30 -08001800
Mike Frysingerd20a6e22012-10-04 19:01:10 -04001801 clean_logs(emerge.settings)
1802
David Jamesfcb70ef2011-02-02 16:02:30 -08001803 print "Done"
Brian Harring8294d652012-05-23 02:20:52 -07001804 return 0