blob: 25bc53c1e1c4ee1f01c6d1b4873e246f864ecaa9 [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 Jamesfcb70ef2011-02-02 16:02:30 -0800313 # If we're installing packages to the board, and we're not using the
David James927a56d2012-04-03 11:26:39 -0700314 # official flag, we can disable vardb locks. This is safe because we
315 # only run up to one instance of parallel_emerge in parallel.
David Jamesfcb70ef2011-02-02 16:02:30 -0800316 if self.board and os.environ.get("CHROMEOS_OFFICIAL") != "1":
317 os.environ.setdefault("PORTAGE_LOCKS", "false")
David Jamesfcb70ef2011-02-02 16:02:30 -0800318
319 # Now that we've setup the necessary environment variables, we can load the
320 # emerge config from disk.
321 settings, trees, mtimedb = load_emerge_config()
322
David Jamesea3ca332011-05-26 11:48:29 -0700323 # Add in EMERGE_DEFAULT_OPTS, if specified.
324 tmpcmdline = []
325 if "--ignore-default-opts" not in opts:
326 tmpcmdline.extend(settings["EMERGE_DEFAULT_OPTS"].split())
327 tmpcmdline.extend(emerge_args)
328 action, opts, cmdline_packages = parse_opts(tmpcmdline)
329
330 # If we're installing to the board, we want the --root-deps option so that
331 # portage will install the build dependencies to that location as well.
332 if self.board:
333 opts.setdefault("--root-deps", True)
334
David Jamesfcb70ef2011-02-02 16:02:30 -0800335 # Check whether our portage tree is out of date. Typically, this happens
336 # when you're setting up a new portage tree, such as in setup_board and
337 # make_chroot. In that case, portage applies a bunch of global updates
338 # here. Once the updates are finished, we need to commit any changes
339 # that the global update made to our mtimedb, and reload the config.
340 #
341 # Portage normally handles this logic in emerge_main, but again, we can't
342 # use that function here.
343 if _global_updates(trees, mtimedb["updates"]):
344 mtimedb.commit()
345 settings, trees, mtimedb = load_emerge_config(trees=trees)
346
347 # Setup implied options. Portage normally handles this logic in
348 # emerge_main.
349 if "--buildpkgonly" in opts or "buildpkg" in settings.features:
350 opts.setdefault("--buildpkg", True)
351 if "--getbinpkgonly" in opts:
352 opts.setdefault("--usepkgonly", True)
353 opts.setdefault("--getbinpkg", True)
354 if "getbinpkg" in settings.features:
355 # Per emerge_main, FEATURES=getbinpkg overrides --getbinpkg=n
356 opts["--getbinpkg"] = True
357 if "--getbinpkg" in opts or "--usepkgonly" in opts:
358 opts.setdefault("--usepkg", True)
359 if "--fetch-all-uri" in opts:
360 opts.setdefault("--fetchonly", True)
361 if "--skipfirst" in opts:
362 opts.setdefault("--resume", True)
363 if "--buildpkgonly" in opts:
364 # --buildpkgonly will not merge anything, so it overrides all binary
365 # package options.
366 for opt in ("--getbinpkg", "--getbinpkgonly",
367 "--usepkg", "--usepkgonly"):
368 opts.pop(opt, None)
369 if (settings.get("PORTAGE_DEBUG", "") == "1" and
370 "python-trace" in settings.features):
371 portage.debug.set_trace(True)
372
373 # Complain about unsupported options
David James386ccd12011-05-04 20:17:42 -0700374 for opt in ("--ask", "--ask-enter-invalid", "--resume", "--skipfirst"):
David Jamesfcb70ef2011-02-02 16:02:30 -0800375 if opt in opts:
376 print "%s is not supported by parallel_emerge" % opt
377 sys.exit(1)
378
379 # Make emerge specific adjustments to the config (e.g. colors!)
380 adjust_configs(opts, trees)
381
382 # Save our configuration so far in the emerge object
383 emerge = self.emerge
384 emerge.action, emerge.opts = action, opts
385 emerge.settings, emerge.trees, emerge.mtimedb = settings, trees, mtimedb
386 emerge.cmdline_packages = cmdline_packages
387 root = settings["ROOT"]
388 emerge.root_config = trees[root]["root_config"]
389
David James386ccd12011-05-04 20:17:42 -0700390 if "--usepkg" in opts:
David Jamesfcb70ef2011-02-02 16:02:30 -0800391 emerge.trees[root]["bintree"].populate("--getbinpkg" in opts)
392
David Jamesfcb70ef2011-02-02 16:02:30 -0800393 def CreateDepgraph(self, emerge, packages):
394 """Create an emerge depgraph object."""
395 # Setup emerge options.
396 emerge_opts = emerge.opts.copy()
397
David James386ccd12011-05-04 20:17:42 -0700398 # Ask portage to build a dependency graph. with the options we specified
399 # above.
David Jamesfcb70ef2011-02-02 16:02:30 -0800400 params = create_depgraph_params(emerge_opts, emerge.action)
David Jamesbf1e3442011-05-28 07:44:20 -0700401 success, depgraph, favorites = backtrack_depgraph(
David James386ccd12011-05-04 20:17:42 -0700402 emerge.settings, emerge.trees, emerge_opts, params, emerge.action,
403 packages, emerge.spinner)
404 emerge.depgraph = depgraph
David Jamesfcb70ef2011-02-02 16:02:30 -0800405
David James386ccd12011-05-04 20:17:42 -0700406 # Is it impossible to honor the user's request? Bail!
407 if not success:
408 depgraph.display_problems()
409 sys.exit(1)
David Jamesfcb70ef2011-02-02 16:02:30 -0800410
411 emerge.depgraph = depgraph
David Jamesbf1e3442011-05-28 07:44:20 -0700412 emerge.favorites = favorites
David Jamesfcb70ef2011-02-02 16:02:30 -0800413
David Jamesdeebd692011-05-09 17:02:52 -0700414 # Prime and flush emerge caches.
415 root = emerge.settings["ROOT"]
416 vardb = emerge.trees[root]["vartree"].dbapi
David James0bdc5de2011-05-12 16:22:26 -0700417 if "--pretend" not in emerge.opts:
418 vardb.counter_tick()
David Jamesdeebd692011-05-09 17:02:52 -0700419 vardb.flush_cache()
420
David James386ccd12011-05-04 20:17:42 -0700421 def GenDependencyTree(self):
David Jamesfcb70ef2011-02-02 16:02:30 -0800422 """Get dependency tree info from emerge.
423
David Jamesfcb70ef2011-02-02 16:02:30 -0800424 Returns:
425 Dependency tree
426 """
427 start = time.time()
428
429 emerge = self.emerge
430
431 # Create a list of packages to merge
432 packages = set(emerge.cmdline_packages[:])
David Jamesfcb70ef2011-02-02 16:02:30 -0800433
434 # Tell emerge to be quiet. We print plenty of info ourselves so we don't
435 # need any extra output from portage.
436 portage.util.noiselimit = -1
437
438 # My favorite feature: The silent spinner. It doesn't spin. Ever.
439 # I'd disable the colors by default too, but they look kind of cool.
440 emerge.spinner = stdout_spinner()
441 emerge.spinner.update = emerge.spinner.update_quiet
442
443 if "--quiet" not in emerge.opts:
444 print "Calculating deps..."
445
446 self.CreateDepgraph(emerge, packages)
447 depgraph = emerge.depgraph
448
449 # Build our own tree from the emerge digraph.
450 deps_tree = {}
451 digraph = depgraph._dynamic_config.digraph
David James3f778802011-08-25 19:31:45 -0700452 root = emerge.settings["ROOT"]
453 final_db = depgraph._dynamic_config.mydbapi[root]
David Jamesfcb70ef2011-02-02 16:02:30 -0800454 for node, node_deps in digraph.nodes.items():
455 # Calculate dependency packages that need to be installed first. Each
456 # child on the digraph is a dependency. The "operation" field specifies
457 # what we're doing (e.g. merge, uninstall, etc.). The "priorities" array
458 # contains the type of dependency (e.g. build, runtime, runtime_post,
459 # etc.)
460 #
David Jamesfcb70ef2011-02-02 16:02:30 -0800461 # Portage refers to the identifiers for packages as a CPV. This acronym
462 # stands for Component/Path/Version.
463 #
464 # Here's an example CPV: chromeos-base/power_manager-0.0.1-r1
465 # Split up, this CPV would be:
466 # C -- Component: chromeos-base
467 # P -- Path: power_manager
468 # V -- Version: 0.0.1-r1
469 #
470 # We just refer to CPVs as packages here because it's easier.
471 deps = {}
472 for child, priorities in node_deps[0].items():
David James3f778802011-08-25 19:31:45 -0700473 if isinstance(child, Package) and child.root == root:
474 cpv = str(child.cpv)
475 action = str(child.operation)
476
477 # If we're uninstalling a package, check whether Portage is
478 # installing a replacement. If so, just depend on the installation
479 # of the new package, because the old package will automatically
480 # be uninstalled at that time.
481 if action == "uninstall":
482 for pkg in final_db.match_pkgs(child.slot_atom):
483 cpv = str(pkg.cpv)
484 action = "merge"
485 break
486
487 deps[cpv] = dict(action=action,
488 deptypes=[str(x) for x in priorities],
489 deps={})
David Jamesfcb70ef2011-02-02 16:02:30 -0800490
491 # We've built our list of deps, so we can add our package to the tree.
David James3f778802011-08-25 19:31:45 -0700492 if isinstance(node, Package) and node.root == root:
David Jamesfcb70ef2011-02-02 16:02:30 -0800493 deps_tree[str(node.cpv)] = dict(action=str(node.operation),
494 deps=deps)
495
David Jamesfcb70ef2011-02-02 16:02:30 -0800496 # Ask portage for its install plan, so that we can only throw out
David James386ccd12011-05-04 20:17:42 -0700497 # dependencies that portage throws out.
David Jamesfcb70ef2011-02-02 16:02:30 -0800498 deps_info = {}
499 for pkg in depgraph.altlist():
500 if isinstance(pkg, Package):
David James3f778802011-08-25 19:31:45 -0700501 assert pkg.root == root
David Jamesfcb70ef2011-02-02 16:02:30 -0800502 self.package_db[pkg.cpv] = pkg
503
David Jamesfcb70ef2011-02-02 16:02:30 -0800504 # Save off info about the package
David James386ccd12011-05-04 20:17:42 -0700505 deps_info[str(pkg.cpv)] = {"idx": len(deps_info)}
David Jamesfcb70ef2011-02-02 16:02:30 -0800506
507 seconds = time.time() - start
508 if "--quiet" not in emerge.opts:
509 print "Deps calculated in %dm%.1fs" % (seconds / 60, seconds % 60)
510
511 return deps_tree, deps_info
512
513 def PrintTree(self, deps, depth=""):
514 """Print the deps we have seen in the emerge output.
515
516 Args:
517 deps: Dependency tree structure.
518 depth: Allows printing the tree recursively, with indentation.
519 """
520 for entry in sorted(deps):
521 action = deps[entry]["action"]
522 print "%s %s (%s)" % (depth, entry, action)
523 self.PrintTree(deps[entry]["deps"], depth=depth + " ")
524
David James386ccd12011-05-04 20:17:42 -0700525 def GenDependencyGraph(self, deps_tree, deps_info):
David Jamesfcb70ef2011-02-02 16:02:30 -0800526 """Generate a doubly linked dependency graph.
527
528 Args:
529 deps_tree: Dependency tree structure.
530 deps_info: More details on the dependencies.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500531
David Jamesfcb70ef2011-02-02 16:02:30 -0800532 Returns:
533 Deps graph in the form of a dict of packages, with each package
534 specifying a "needs" list and "provides" list.
535 """
536 emerge = self.emerge
David Jamesfcb70ef2011-02-02 16:02:30 -0800537
David Jamesfcb70ef2011-02-02 16:02:30 -0800538 # deps_map is the actual dependency graph.
539 #
540 # Each package specifies a "needs" list and a "provides" list. The "needs"
541 # list indicates which packages we depend on. The "provides" list
542 # indicates the reverse dependencies -- what packages need us.
543 #
544 # We also provide some other information in the dependency graph:
545 # - action: What we're planning on doing with this package. Generally,
546 # "merge", "nomerge", or "uninstall"
David Jamesfcb70ef2011-02-02 16:02:30 -0800547 deps_map = {}
548
549 def ReverseTree(packages):
550 """Convert tree to digraph.
551
552 Take the tree of package -> requirements and reverse it to a digraph of
553 buildable packages -> packages they unblock.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500554
David Jamesfcb70ef2011-02-02 16:02:30 -0800555 Args:
556 packages: Tree(s) of dependencies.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500557
David Jamesfcb70ef2011-02-02 16:02:30 -0800558 Returns:
559 Unsanitized digraph.
560 """
David James8c7e5e32011-06-28 11:26:03 -0700561 binpkg_phases = set(["setup", "preinst", "postinst"])
David James3f778802011-08-25 19:31:45 -0700562 needed_dep_types = set(["blocker", "buildtime", "runtime"])
David Jamesfcb70ef2011-02-02 16:02:30 -0800563 for pkg in packages:
564
565 # Create an entry for the package
566 action = packages[pkg]["action"]
David James8c7e5e32011-06-28 11:26:03 -0700567 default_pkg = {"needs": {}, "provides": set(), "action": action,
568 "nodeps": False, "binary": False}
David Jamesfcb70ef2011-02-02 16:02:30 -0800569 this_pkg = deps_map.setdefault(pkg, default_pkg)
570
David James8c7e5e32011-06-28 11:26:03 -0700571 if pkg in deps_info:
572 this_pkg["idx"] = deps_info[pkg]["idx"]
573
574 # If a package doesn't have any defined phases that might use the
575 # dependent packages (i.e. pkg_setup, pkg_preinst, or pkg_postinst),
576 # we can install this package before its deps are ready.
577 emerge_pkg = self.package_db.get(pkg)
578 if emerge_pkg and emerge_pkg.type_name == "binary":
579 this_pkg["binary"] = True
Mike Frysinger91d7da92013-02-19 15:53:46 -0500580 if 0 <= vercmp(portage.VERSION, "2.1.11.50"):
581 defined_phases = emerge_pkg.defined_phases
582 else:
583 defined_phases = emerge_pkg.metadata.defined_phases
David James8c7e5e32011-06-28 11:26:03 -0700584 defined_binpkg_phases = binpkg_phases.intersection(defined_phases)
585 if not defined_binpkg_phases:
586 this_pkg["nodeps"] = True
587
David Jamesfcb70ef2011-02-02 16:02:30 -0800588 # Create entries for dependencies of this package first.
589 ReverseTree(packages[pkg]["deps"])
590
591 # Add dependencies to this package.
592 for dep, dep_item in packages[pkg]["deps"].iteritems():
David James8c7e5e32011-06-28 11:26:03 -0700593 # We only need to enforce strict ordering of dependencies if the
David James3f778802011-08-25 19:31:45 -0700594 # dependency is a blocker, or is a buildtime or runtime dependency.
595 # (I.e., ignored, optional, and runtime_post dependencies don't
596 # depend on ordering.)
David James8c7e5e32011-06-28 11:26:03 -0700597 dep_types = dep_item["deptypes"]
598 if needed_dep_types.intersection(dep_types):
599 deps_map[dep]["provides"].add(pkg)
600 this_pkg["needs"][dep] = "/".join(dep_types)
David Jamesfcb70ef2011-02-02 16:02:30 -0800601
David James3f778802011-08-25 19:31:45 -0700602 # If there's a blocker, Portage may need to move files from one
603 # package to another, which requires editing the CONTENTS files of
604 # both packages. To avoid race conditions while editing this file,
605 # the two packages must not be installed in parallel, so we can't
606 # safely ignore dependencies. See http://crosbug.com/19328
607 if "blocker" in dep_types:
608 this_pkg["nodeps"] = False
609
David Jamesfcb70ef2011-02-02 16:02:30 -0800610 def FindCycles():
611 """Find cycles in the dependency tree.
612
613 Returns:
614 A dict mapping cyclic packages to a dict of the deps that cause
615 cycles. For each dep that causes cycles, it returns an example
616 traversal of the graph that shows the cycle.
617 """
618
619 def FindCyclesAtNode(pkg, cycles, unresolved, resolved):
620 """Find cycles in cyclic dependencies starting at specified package.
621
622 Args:
623 pkg: Package identifier.
624 cycles: A dict mapping cyclic packages to a dict of the deps that
625 cause cycles. For each dep that causes cycles, it returns an
626 example traversal of the graph that shows the cycle.
627 unresolved: Nodes that have been visited but are not fully processed.
628 resolved: Nodes that have been visited and are fully processed.
629 """
630 pkg_cycles = cycles.get(pkg)
631 if pkg in resolved and not pkg_cycles:
632 # If we already looked at this package, and found no cyclic
633 # dependencies, we can stop now.
634 return
635 unresolved.append(pkg)
636 for dep in deps_map[pkg]["needs"]:
637 if dep in unresolved:
638 idx = unresolved.index(dep)
639 mycycle = unresolved[idx:] + [dep]
David James321490a2012-12-17 12:05:56 -0800640 for i in xrange(len(mycycle) - 1):
David Jamesfcb70ef2011-02-02 16:02:30 -0800641 pkg1, pkg2 = mycycle[i], mycycle[i+1]
642 cycles.setdefault(pkg1, {}).setdefault(pkg2, mycycle)
643 elif not pkg_cycles or dep not in pkg_cycles:
644 # Looks like we haven't seen this edge before.
645 FindCyclesAtNode(dep, cycles, unresolved, resolved)
646 unresolved.pop()
647 resolved.add(pkg)
648
649 cycles, unresolved, resolved = {}, [], set()
650 for pkg in deps_map:
651 FindCyclesAtNode(pkg, cycles, unresolved, resolved)
652 return cycles
653
David James386ccd12011-05-04 20:17:42 -0700654 def RemoveUnusedPackages():
David Jamesfcb70ef2011-02-02 16:02:30 -0800655 """Remove installed packages, propagating dependencies."""
David Jamesfcb70ef2011-02-02 16:02:30 -0800656 # Schedule packages that aren't on the install list for removal
657 rm_pkgs = set(deps_map.keys()) - set(deps_info.keys())
658
David Jamesfcb70ef2011-02-02 16:02:30 -0800659 # Remove the packages we don't want, simplifying the graph and making
660 # it easier for us to crack cycles.
661 for pkg in sorted(rm_pkgs):
662 this_pkg = deps_map[pkg]
663 needs = this_pkg["needs"]
664 provides = this_pkg["provides"]
665 for dep in needs:
666 dep_provides = deps_map[dep]["provides"]
667 dep_provides.update(provides)
668 dep_provides.discard(pkg)
669 dep_provides.discard(dep)
670 for target in provides:
671 target_needs = deps_map[target]["needs"]
672 target_needs.update(needs)
673 target_needs.pop(pkg, None)
674 target_needs.pop(target, None)
675 del deps_map[pkg]
676
677 def PrintCycleBreak(basedep, dep, mycycle):
678 """Print details about a cycle that we are planning on breaking.
679
Mike Frysinger02e1e072013-11-10 22:11:34 -0500680 We are breaking a cycle where dep needs basedep. mycycle is an
681 example cycle which contains dep -> basedep.
682 """
David Jamesfcb70ef2011-02-02 16:02:30 -0800683
David Jamesfcb70ef2011-02-02 16:02:30 -0800684 needs = deps_map[dep]["needs"]
685 depinfo = needs.get(basedep, "deleted")
David Jamesfcb70ef2011-02-02 16:02:30 -0800686
David James3f778802011-08-25 19:31:45 -0700687 # It's OK to swap install order for blockers, as long as the two
688 # packages aren't installed in parallel. If there is a cycle, then
689 # we know the packages depend on each other already, so we can drop the
690 # blocker safely without printing a warning.
691 if depinfo == "blocker":
692 return
693
David Jamesfcb70ef2011-02-02 16:02:30 -0800694 # Notify the user that we're breaking a cycle.
695 print "Breaking %s -> %s (%s)" % (dep, basedep, depinfo)
696
697 # Show cycle.
David James321490a2012-12-17 12:05:56 -0800698 for i in xrange(len(mycycle) - 1):
David Jamesfcb70ef2011-02-02 16:02:30 -0800699 pkg1, pkg2 = mycycle[i], mycycle[i+1]
700 needs = deps_map[pkg1]["needs"]
701 depinfo = needs.get(pkg2, "deleted")
702 if pkg1 == dep and pkg2 == basedep:
703 depinfo = depinfo + ", deleting"
704 print " %s -> %s (%s)" % (pkg1, pkg2, depinfo)
705
706 def SanitizeTree():
707 """Remove circular dependencies.
708
709 We prune all dependencies involved in cycles that go against the emerge
710 ordering. This has a nice property: we're guaranteed to merge
711 dependencies in the same order that portage does.
712
713 Because we don't treat any dependencies as "soft" unless they're killed
714 by a cycle, we pay attention to a larger number of dependencies when
715 merging. This hurts performance a bit, but helps reliability.
716 """
717 start = time.time()
718 cycles = FindCycles()
719 while cycles:
720 for dep, mycycles in cycles.iteritems():
721 for basedep, mycycle in mycycles.iteritems():
722 if deps_info[basedep]["idx"] >= deps_info[dep]["idx"]:
Matt Tennant08797302011-10-17 16:18:45 -0700723 if "--quiet" not in emerge.opts:
724 PrintCycleBreak(basedep, dep, mycycle)
David Jamesfcb70ef2011-02-02 16:02:30 -0800725 del deps_map[dep]["needs"][basedep]
726 deps_map[basedep]["provides"].remove(dep)
727 cycles = FindCycles()
728 seconds = time.time() - start
729 if "--quiet" not in emerge.opts and seconds >= 0.1:
730 print "Tree sanitized in %dm%.1fs" % (seconds / 60, seconds % 60)
731
David James8c7e5e32011-06-28 11:26:03 -0700732 def FindRecursiveProvides(pkg, seen):
733 """Find all nodes that require a particular package.
734
735 Assumes that graph is acyclic.
736
737 Args:
738 pkg: Package identifier.
739 seen: Nodes that have been visited so far.
740 """
741 if pkg in seen:
742 return
743 seen.add(pkg)
744 info = deps_map[pkg]
745 info["tprovides"] = info["provides"].copy()
746 for dep in info["provides"]:
747 FindRecursiveProvides(dep, seen)
748 info["tprovides"].update(deps_map[dep]["tprovides"])
749
David Jamesa22906f2011-05-04 19:53:26 -0700750 ReverseTree(deps_tree)
David Jamesa22906f2011-05-04 19:53:26 -0700751
David James386ccd12011-05-04 20:17:42 -0700752 # We need to remove unused packages so that we can use the dependency
753 # ordering of the install process to show us what cycles to crack.
754 RemoveUnusedPackages()
David Jamesfcb70ef2011-02-02 16:02:30 -0800755 SanitizeTree()
David James8c7e5e32011-06-28 11:26:03 -0700756 seen = set()
757 for pkg in deps_map:
758 FindRecursiveProvides(pkg, seen)
David Jamesfcb70ef2011-02-02 16:02:30 -0800759 return deps_map
760
761 def PrintInstallPlan(self, deps_map):
762 """Print an emerge-style install plan.
763
764 The install plan lists what packages we're installing, in order.
765 It's useful for understanding what parallel_emerge is doing.
766
767 Args:
768 deps_map: The dependency graph.
769 """
770
771 def InstallPlanAtNode(target, deps_map):
772 nodes = []
773 nodes.append(target)
774 for dep in deps_map[target]["provides"]:
775 del deps_map[dep]["needs"][target]
776 if not deps_map[dep]["needs"]:
777 nodes.extend(InstallPlanAtNode(dep, deps_map))
778 return nodes
779
780 deps_map = copy.deepcopy(deps_map)
781 install_plan = []
782 plan = set()
783 for target, info in deps_map.iteritems():
784 if not info["needs"] and target not in plan:
785 for item in InstallPlanAtNode(target, deps_map):
786 plan.add(item)
787 install_plan.append(self.package_db[item])
788
789 for pkg in plan:
790 del deps_map[pkg]
791
792 if deps_map:
793 print "Cyclic dependencies:", " ".join(deps_map)
794 PrintDepsMap(deps_map)
795 sys.exit(1)
796
797 self.emerge.depgraph.display(install_plan)
798
799
800def PrintDepsMap(deps_map):
801 """Print dependency graph, for each package list it's prerequisites."""
802 for i in sorted(deps_map):
803 print "%s: (%s) needs" % (i, deps_map[i]["action"])
804 needs = deps_map[i]["needs"]
805 for j in sorted(needs):
806 print " %s" % (j)
807 if not needs:
808 print " no dependencies"
809
810
811class EmergeJobState(object):
812 __slots__ = ["done", "filename", "last_notify_timestamp", "last_output_seek",
813 "last_output_timestamp", "pkgname", "retcode", "start_timestamp",
Thiago Goncalesf4acc422013-07-17 10:26:35 -0700814 "target", "fetch_only", "unpack_only"]
David Jamesfcb70ef2011-02-02 16:02:30 -0800815
816 def __init__(self, target, pkgname, done, filename, start_timestamp,
Thiago Goncalesf4acc422013-07-17 10:26:35 -0700817 retcode=None, fetch_only=False, unpack_only=False):
David Jamesfcb70ef2011-02-02 16:02:30 -0800818
819 # The full name of the target we're building (e.g.
820 # chromeos-base/chromeos-0.0.1-r60)
821 self.target = target
822
823 # The short name of the target we're building (e.g. chromeos-0.0.1-r60)
824 self.pkgname = pkgname
825
826 # Whether the job is done. (True if the job is done; false otherwise.)
827 self.done = done
828
829 # The filename where output is currently stored.
830 self.filename = filename
831
832 # The timestamp of the last time we printed the name of the log file. We
833 # print this at the beginning of the job, so this starts at
834 # start_timestamp.
835 self.last_notify_timestamp = start_timestamp
836
837 # The location (in bytes) of the end of the last complete line we printed.
838 # This starts off at zero. We use this to jump to the right place when we
839 # print output from the same ebuild multiple times.
840 self.last_output_seek = 0
841
842 # The timestamp of the last time we printed output. Since we haven't
843 # printed output yet, this starts at zero.
844 self.last_output_timestamp = 0
845
846 # The return code of our job, if the job is actually finished.
847 self.retcode = retcode
848
Brian Harring0be85c62012-03-17 19:52:12 -0700849 # Was this just a fetch job?
850 self.fetch_only = fetch_only
851
David Jamesfcb70ef2011-02-02 16:02:30 -0800852 # The timestamp when our job started.
853 self.start_timestamp = start_timestamp
854
Thiago Goncalesf4acc422013-07-17 10:26:35 -0700855 # No emerge, only unpack packages.
856 self.unpack_only = unpack_only
857
David Jamesfcb70ef2011-02-02 16:02:30 -0800858
David James321490a2012-12-17 12:05:56 -0800859def KillHandler(_signum, _frame):
David James7358d032011-05-19 10:40:03 -0700860 # Kill self and all subprocesses.
861 os.killpg(0, signal.SIGKILL)
862
David Jamesfcb70ef2011-02-02 16:02:30 -0800863def SetupWorkerSignals():
David James321490a2012-12-17 12:05:56 -0800864 def ExitHandler(_signum, _frame):
David James7358d032011-05-19 10:40:03 -0700865 # Set KILLED flag.
866 KILLED.set()
David James13cead42011-05-18 16:22:01 -0700867
David James7358d032011-05-19 10:40:03 -0700868 # Remove our signal handlers so we don't get called recursively.
869 signal.signal(signal.SIGINT, KillHandler)
870 signal.signal(signal.SIGTERM, KillHandler)
David Jamesfcb70ef2011-02-02 16:02:30 -0800871
872 # Ensure that we exit quietly and cleanly, if possible, when we receive
873 # SIGTERM or SIGINT signals. By default, when the user hits CTRL-C, all
874 # of the child processes will print details about KeyboardInterrupt
875 # exceptions, which isn't very helpful.
876 signal.signal(signal.SIGINT, ExitHandler)
877 signal.signal(signal.SIGTERM, ExitHandler)
878
David James6b29d052012-11-02 10:27:27 -0700879def EmergeProcess(output, *args, **kwargs):
David James1ed3e252011-10-05 20:26:15 -0700880 """Merge a package in a subprocess.
881
882 Args:
David James1ed3e252011-10-05 20:26:15 -0700883 output: Temporary file to write output.
David James6b29d052012-11-02 10:27:27 -0700884 *args: Arguments to pass to Scheduler constructor.
885 **kwargs: Keyword arguments to pass to Scheduler constructor.
David James1ed3e252011-10-05 20:26:15 -0700886
887 Returns:
888 The exit code returned by the subprocess.
889 """
890 pid = os.fork()
891 if pid == 0:
892 try:
893 # Sanity checks.
Mike Frysingerf02736e2013-11-08 15:27:00 -0500894 if sys.stdout.fileno() != 1:
895 raise Exception("sys.stdout.fileno() != 1")
896 if sys.stderr.fileno() != 2:
897 raise Exception("sys.stderr.fileno() != 2")
David James1ed3e252011-10-05 20:26:15 -0700898
899 # - Redirect 1 (stdout) and 2 (stderr) at our temporary file.
900 # - Redirect 0 to point at sys.stdin. In this case, sys.stdin
901 # points at a file reading os.devnull, because multiprocessing mucks
902 # with sys.stdin.
903 # - Leave the sys.stdin and output filehandles alone.
904 fd_pipes = {0: sys.stdin.fileno(),
905 1: output.fileno(),
906 2: output.fileno(),
907 sys.stdin.fileno(): sys.stdin.fileno(),
908 output.fileno(): output.fileno()}
David Jamesa249eef2013-07-19 14:03:45 -0700909 if 0 <= vercmp(portage.VERSION, "2.1.11.50"):
910 portage.process._setup_pipes(fd_pipes, close_fds=False)
911 else:
912 portage.process._setup_pipes(fd_pipes)
David James1ed3e252011-10-05 20:26:15 -0700913
914 # Portage doesn't like when sys.stdin.fileno() != 0, so point sys.stdin
915 # at the filehandle we just created in _setup_pipes.
916 if sys.stdin.fileno() != 0:
David James6b29d052012-11-02 10:27:27 -0700917 sys.__stdin__ = sys.stdin = os.fdopen(0, "r")
918
919 scheduler = Scheduler(*args, **kwargs)
920
921 # Enable blocker handling even though we're in --nodeps mode. This
922 # allows us to unmerge the blocker after we've merged the replacement.
923 scheduler._opts_ignore_blockers = frozenset()
David James1ed3e252011-10-05 20:26:15 -0700924
925 # Actually do the merge.
926 retval = scheduler.merge()
927
928 # We catch all exceptions here (including SystemExit, KeyboardInterrupt,
929 # etc) so as to ensure that we don't confuse the multiprocessing module,
930 # which expects that all forked children exit with os._exit().
David James321490a2012-12-17 12:05:56 -0800931 # pylint: disable=W0702
David James1ed3e252011-10-05 20:26:15 -0700932 except:
933 traceback.print_exc(file=output)
934 retval = 1
935 sys.stdout.flush()
936 sys.stderr.flush()
937 output.flush()
938 os._exit(retval)
939 else:
940 # Return the exit code of the subprocess.
941 return os.waitpid(pid, 0)[1]
David Jamesfcb70ef2011-02-02 16:02:30 -0800942
Thiago Goncalesf4acc422013-07-17 10:26:35 -0700943
944def UnpackPackage(pkg_state):
945 """Unpacks package described by pkg_state.
946
947 Args:
948 pkg_state: EmergeJobState object describing target.
949
950 Returns:
951 Exit code returned by subprocess.
952 """
953 pkgdir = os.environ.get("PKGDIR",
954 os.path.join(os.environ["SYSROOT"], "packages"))
955 root = os.environ.get("ROOT", os.environ["SYSROOT"])
956 path = os.path.join(pkgdir, pkg_state.target + ".tbz2")
957 comp = cros_build_lib.FindCompressor(cros_build_lib.COMP_BZIP2)
958 cmd = [comp, "-dc"]
959 if comp.endswith("pbzip2"):
960 cmd.append("--ignore-trailing-garbage=1")
961 cmd.append(path)
962
963 result = cros_build_lib.RunCommand(cmd, cwd=root, stdout_to_pipe=True,
964 print_cmd=False, error_code_ok=True)
965
966 # If we were not successful, return now and don't attempt untar.
967 if result.returncode:
968 return result.returncode
969
970 cmd = ["sudo", "tar", "-xf", "-", "-C", root]
971 result = cros_build_lib.RunCommand(cmd, cwd=root, input=result.output,
972 print_cmd=False, error_code_ok=True)
973
974 return result.returncode
975
976
977def EmergeWorker(task_queue, job_queue, emerge, package_db, fetch_only=False,
978 unpack_only=False):
David Jamesfcb70ef2011-02-02 16:02:30 -0800979 """This worker emerges any packages given to it on the task_queue.
980
981 Args:
982 task_queue: The queue of tasks for this worker to do.
983 job_queue: The queue of results from the worker.
984 emerge: An EmergeData() object.
985 package_db: A dict, mapping package ids to portage Package objects.
Brian Harring0be85c62012-03-17 19:52:12 -0700986 fetch_only: A bool, indicating if we should just fetch the target.
Thiago Goncalesf4acc422013-07-17 10:26:35 -0700987 unpack_only: A bool, indicating if we should just unpack the target.
David Jamesfcb70ef2011-02-02 16:02:30 -0800988
989 It expects package identifiers to be passed to it via task_queue. When
990 a task is started, it pushes the (target, filename) to the started_queue.
991 The output is stored in filename. When a merge starts or finishes, we push
992 EmergeJobState objects to the job_queue.
993 """
994
995 SetupWorkerSignals()
996 settings, trees, mtimedb = emerge.settings, emerge.trees, emerge.mtimedb
David Jamesdeebd692011-05-09 17:02:52 -0700997
998 # Disable flushing of caches to save on I/O.
David James7a1ea4b2011-10-13 15:06:41 -0700999 root = emerge.settings["ROOT"]
1000 vardb = emerge.trees[root]["vartree"].dbapi
1001 vardb._flush_cache_enabled = False
Brian Harring0be85c62012-03-17 19:52:12 -07001002 bindb = emerge.trees[root]["bintree"].dbapi
1003 # Might be a set, might be a list, might be None; no clue, just use shallow
1004 # copy to ensure we can roll it back.
1005 original_remotepkgs = copy.copy(bindb.bintree._remotepkgs)
David Jamesdeebd692011-05-09 17:02:52 -07001006
David Jamesfcb70ef2011-02-02 16:02:30 -08001007 opts, spinner = emerge.opts, emerge.spinner
1008 opts["--nodeps"] = True
Brian Harring0be85c62012-03-17 19:52:12 -07001009 if fetch_only:
1010 opts["--fetchonly"] = True
1011
David Jamesfcb70ef2011-02-02 16:02:30 -08001012 while True:
1013 # Wait for a new item to show up on the queue. This is a blocking wait,
1014 # so if there's nothing to do, we just sit here.
Brian Harring0be85c62012-03-17 19:52:12 -07001015 pkg_state = task_queue.get()
1016 if pkg_state is None:
David Jamesfcb70ef2011-02-02 16:02:30 -08001017 # If target is None, this means that the main thread wants us to quit.
1018 # The other workers need to exit too, so we'll push the message back on
1019 # to the queue so they'll get it too.
Brian Harring0be85c62012-03-17 19:52:12 -07001020 task_queue.put(None)
David Jamesfcb70ef2011-02-02 16:02:30 -08001021 return
David James7358d032011-05-19 10:40:03 -07001022 if KILLED.is_set():
1023 return
1024
Brian Harring0be85c62012-03-17 19:52:12 -07001025 target = pkg_state.target
1026
David Jamesfcb70ef2011-02-02 16:02:30 -08001027 db_pkg = package_db[target]
Brian Harring0be85c62012-03-17 19:52:12 -07001028
1029 if db_pkg.type_name == "binary":
1030 if not fetch_only and pkg_state.fetched_successfully:
1031 # Ensure portage doesn't think our pkg is remote- else it'll force
1032 # a redownload of it (even if the on-disk file is fine). In-memory
1033 # caching basically, implemented dumbly.
1034 bindb.bintree._remotepkgs = None
1035 else:
1036 bindb.bintree_remotepkgs = original_remotepkgs
1037
David Jamesfcb70ef2011-02-02 16:02:30 -08001038 db_pkg.root_config = emerge.root_config
1039 install_list = [db_pkg]
1040 pkgname = db_pkg.pf
1041 output = tempfile.NamedTemporaryFile(prefix=pkgname + "-", delete=False)
David James01b1e0f2012-06-07 17:18:05 -07001042 os.chmod(output.name, 644)
David Jamesfcb70ef2011-02-02 16:02:30 -08001043 start_timestamp = time.time()
Brian Harring0be85c62012-03-17 19:52:12 -07001044 job = EmergeJobState(target, pkgname, False, output.name, start_timestamp,
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001045 fetch_only=fetch_only, unpack_only=unpack_only)
David Jamesfcb70ef2011-02-02 16:02:30 -08001046 job_queue.put(job)
1047 if "--pretend" in opts:
1048 retcode = 0
1049 else:
David Jamesfcb70ef2011-02-02 16:02:30 -08001050 try:
David James386ccd12011-05-04 20:17:42 -07001051 emerge.scheduler_graph.mergelist = install_list
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001052 if unpack_only:
1053 retcode = UnpackPackage(pkg_state)
1054 else:
1055 retcode = EmergeProcess(output, settings, trees, mtimedb, opts,
1056 spinner, favorites=emerge.favorites,
1057 graph_config=emerge.scheduler_graph)
David Jamesfcb70ef2011-02-02 16:02:30 -08001058 except Exception:
1059 traceback.print_exc(file=output)
1060 retcode = 1
David James1ed3e252011-10-05 20:26:15 -07001061 output.close()
David Jamesfcb70ef2011-02-02 16:02:30 -08001062
David James7358d032011-05-19 10:40:03 -07001063 if KILLED.is_set():
1064 return
1065
David Jamesfcb70ef2011-02-02 16:02:30 -08001066 job = EmergeJobState(target, pkgname, True, output.name, start_timestamp,
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001067 retcode, fetch_only=fetch_only,
1068 unpack_only=unpack_only)
David Jamesfcb70ef2011-02-02 16:02:30 -08001069 job_queue.put(job)
1070
1071
1072class LinePrinter(object):
1073 """Helper object to print a single line."""
1074
1075 def __init__(self, line):
1076 self.line = line
1077
David James321490a2012-12-17 12:05:56 -08001078 def Print(self, _seek_locations):
David Jamesfcb70ef2011-02-02 16:02:30 -08001079 print self.line
1080
1081
1082class JobPrinter(object):
1083 """Helper object to print output of a job."""
1084
1085 def __init__(self, job, unlink=False):
1086 """Print output of job.
1087
Mike Frysinger02e1e072013-11-10 22:11:34 -05001088 If unlink is True, unlink the job output file when done.
1089 """
David Jamesfcb70ef2011-02-02 16:02:30 -08001090 self.current_time = time.time()
1091 self.job = job
1092 self.unlink = unlink
1093
1094 def Print(self, seek_locations):
1095
1096 job = self.job
1097
1098 # Calculate how long the job has been running.
1099 seconds = self.current_time - job.start_timestamp
1100
1101 # Note that we've printed out the job so far.
1102 job.last_output_timestamp = self.current_time
1103
1104 # Note that we're starting the job
1105 info = "job %s (%dm%.1fs)" % (job.pkgname, seconds / 60, seconds % 60)
1106 last_output_seek = seek_locations.get(job.filename, 0)
1107 if last_output_seek:
1108 print "=== Continue output for %s ===" % info
1109 else:
1110 print "=== Start output for %s ===" % info
1111
1112 # Print actual output from job
1113 f = codecs.open(job.filename, encoding='utf-8', errors='replace')
1114 f.seek(last_output_seek)
1115 prefix = job.pkgname + ":"
1116 for line in f:
1117
1118 # Save off our position in the file
1119 if line and line[-1] == "\n":
1120 last_output_seek = f.tell()
1121 line = line[:-1]
1122
1123 # Print our line
1124 print prefix, line.encode('utf-8', 'replace')
1125 f.close()
1126
1127 # Save our last spot in the file so that we don't print out the same
1128 # location twice.
1129 seek_locations[job.filename] = last_output_seek
1130
1131 # Note end of output section
1132 if job.done:
1133 print "=== Complete: %s ===" % info
1134 else:
1135 print "=== Still running: %s ===" % info
1136
1137 if self.unlink:
1138 os.unlink(job.filename)
1139
1140
1141def PrintWorker(queue):
1142 """A worker that prints stuff to the screen as requested."""
1143
David James321490a2012-12-17 12:05:56 -08001144 def ExitHandler(_signum, _frame):
David James7358d032011-05-19 10:40:03 -07001145 # Set KILLED flag.
1146 KILLED.set()
1147
David Jamesfcb70ef2011-02-02 16:02:30 -08001148 # Switch to default signal handlers so that we'll die after two signals.
David James7358d032011-05-19 10:40:03 -07001149 signal.signal(signal.SIGINT, KillHandler)
1150 signal.signal(signal.SIGTERM, KillHandler)
David Jamesfcb70ef2011-02-02 16:02:30 -08001151
1152 # Don't exit on the first SIGINT / SIGTERM, because the parent worker will
1153 # handle it and tell us when we need to exit.
1154 signal.signal(signal.SIGINT, ExitHandler)
1155 signal.signal(signal.SIGTERM, ExitHandler)
1156
1157 # seek_locations is a map indicating the position we are at in each file.
1158 # It starts off empty, but is set by the various Print jobs as we go along
1159 # to indicate where we left off in each file.
1160 seek_locations = {}
1161 while True:
1162 try:
1163 job = queue.get()
1164 if job:
1165 job.Print(seek_locations)
David Jamesbccf8eb2011-07-27 14:06:06 -07001166 sys.stdout.flush()
David Jamesfcb70ef2011-02-02 16:02:30 -08001167 else:
1168 break
1169 except IOError as ex:
1170 if ex.errno == errno.EINTR:
1171 # Looks like we received a signal. Keep printing.
1172 continue
1173 raise
1174
Brian Harring867e2362012-03-17 04:05:17 -07001175
Brian Harring0be85c62012-03-17 19:52:12 -07001176class TargetState(object):
Brian Harring867e2362012-03-17 04:05:17 -07001177
Brian Harring0be85c62012-03-17 19:52:12 -07001178 __slots__ = ("target", "info", "score", "prefetched", "fetched_successfully")
Brian Harring867e2362012-03-17 04:05:17 -07001179
David James321490a2012-12-17 12:05:56 -08001180 def __init__(self, target, info):
Brian Harring867e2362012-03-17 04:05:17 -07001181 self.target, self.info = target, info
Brian Harring0be85c62012-03-17 19:52:12 -07001182 self.fetched_successfully = False
1183 self.prefetched = False
David James321490a2012-12-17 12:05:56 -08001184 self.score = None
Brian Harring867e2362012-03-17 04:05:17 -07001185 self.update_score()
1186
1187 def __cmp__(self, other):
1188 return cmp(self.score, other.score)
1189
1190 def update_score(self):
1191 self.score = (
1192 -len(self.info["tprovides"]),
Brian Harring0be85c62012-03-17 19:52:12 -07001193 len(self.info["needs"]),
Brian Harring11c5eeb2012-03-18 11:02:39 -07001194 not self.info["binary"],
Brian Harring867e2362012-03-17 04:05:17 -07001195 -len(self.info["provides"]),
1196 self.info["idx"],
1197 self.target,
1198 )
1199
1200
1201class ScoredHeap(object):
1202
Brian Harring0be85c62012-03-17 19:52:12 -07001203 __slots__ = ("heap", "_heap_set")
1204
Brian Harring867e2362012-03-17 04:05:17 -07001205 def __init__(self, initial=()):
Brian Harring0be85c62012-03-17 19:52:12 -07001206 self.heap = list()
1207 self._heap_set = set()
1208 if initial:
1209 self.multi_put(initial)
Brian Harring867e2362012-03-17 04:05:17 -07001210
1211 def get(self):
Brian Harring0be85c62012-03-17 19:52:12 -07001212 item = heapq.heappop(self.heap)
1213 self._heap_set.remove(item.target)
1214 return item
Brian Harring867e2362012-03-17 04:05:17 -07001215
Brian Harring0be85c62012-03-17 19:52:12 -07001216 def put(self, item):
1217 if not isinstance(item, TargetState):
1218 raise ValueError("Item %r isn't a TargetState" % (item,))
1219 heapq.heappush(self.heap, item)
1220 self._heap_set.add(item.target)
Brian Harring867e2362012-03-17 04:05:17 -07001221
Brian Harring0be85c62012-03-17 19:52:12 -07001222 def multi_put(self, sequence):
1223 sequence = list(sequence)
1224 self.heap.extend(sequence)
1225 self._heap_set.update(x.target for x in sequence)
Brian Harring867e2362012-03-17 04:05:17 -07001226 self.sort()
1227
David James5c9996d2012-03-24 10:50:46 -07001228 def sort(self):
1229 heapq.heapify(self.heap)
1230
Brian Harring0be85c62012-03-17 19:52:12 -07001231 def __contains__(self, target):
1232 return target in self._heap_set
1233
1234 def __nonzero__(self):
1235 return bool(self.heap)
1236
Brian Harring867e2362012-03-17 04:05:17 -07001237 def __len__(self):
1238 return len(self.heap)
1239
1240
David Jamesfcb70ef2011-02-02 16:02:30 -08001241class EmergeQueue(object):
1242 """Class to schedule emerge jobs according to a dependency graph."""
1243
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001244 def __init__(self, deps_map, emerge, package_db, show_output, unpack_only):
David Jamesfcb70ef2011-02-02 16:02:30 -08001245 # Store the dependency graph.
1246 self._deps_map = deps_map
Brian Harring0be85c62012-03-17 19:52:12 -07001247 self._state_map = {}
David Jamesfcb70ef2011-02-02 16:02:30 -08001248 # Initialize the running queue to empty
Brian Harring0be85c62012-03-17 19:52:12 -07001249 self._build_jobs = {}
1250 self._build_ready = ScoredHeap()
1251 self._fetch_jobs = {}
1252 self._fetch_ready = ScoredHeap()
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001253 self._unpack_jobs = {}
1254 self._unpack_ready = ScoredHeap()
David Jamesfcb70ef2011-02-02 16:02:30 -08001255 # List of total package installs represented in deps_map.
1256 install_jobs = [x for x in deps_map if deps_map[x]["action"] == "merge"]
1257 self._total_jobs = len(install_jobs)
1258 self._show_output = show_output
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001259 self._unpack_only = unpack_only
David Jamesfcb70ef2011-02-02 16:02:30 -08001260
1261 if "--pretend" in emerge.opts:
1262 print "Skipping merge because of --pretend mode."
1263 sys.exit(0)
1264
David James7358d032011-05-19 10:40:03 -07001265 # Set a process group so we can easily terminate all children.
1266 os.setsid()
1267
David Jamesfcb70ef2011-02-02 16:02:30 -08001268 # Setup scheduler graph object. This is used by the child processes
1269 # to help schedule jobs.
1270 emerge.scheduler_graph = emerge.depgraph.schedulerGraph()
1271
1272 # Calculate how many jobs we can run in parallel. We don't want to pass
1273 # the --jobs flag over to emerge itself, because that'll tell emerge to
1274 # hide its output, and said output is quite useful for debugging hung
1275 # jobs.
1276 procs = min(self._total_jobs,
1277 emerge.opts.pop("--jobs", multiprocessing.cpu_count()))
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001278 self._build_procs = self._unpack_procs = self._fetch_procs = max(1, procs)
David James8c7e5e32011-06-28 11:26:03 -07001279 self._load_avg = emerge.opts.pop("--load-average", None)
David Jamesfcb70ef2011-02-02 16:02:30 -08001280 self._job_queue = multiprocessing.Queue()
1281 self._print_queue = multiprocessing.Queue()
Brian Harring0be85c62012-03-17 19:52:12 -07001282
1283 self._fetch_queue = multiprocessing.Queue()
1284 args = (self._fetch_queue, self._job_queue, emerge, package_db, True)
1285 self._fetch_pool = multiprocessing.Pool(self._fetch_procs, EmergeWorker,
1286 args)
1287
1288 self._build_queue = multiprocessing.Queue()
1289 args = (self._build_queue, self._job_queue, emerge, package_db)
1290 self._build_pool = multiprocessing.Pool(self._build_procs, EmergeWorker,
1291 args)
1292
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001293 if self._unpack_only:
1294 # Unpack pool only required on unpack_only jobs.
1295 self._unpack_queue = multiprocessing.Queue()
1296 args = (self._unpack_queue, self._job_queue, emerge, package_db, False,
1297 True)
1298 self._unpack_pool = multiprocessing.Pool(self._unpack_procs, EmergeWorker,
1299 args)
1300
David Jamesfcb70ef2011-02-02 16:02:30 -08001301 self._print_worker = multiprocessing.Process(target=PrintWorker,
1302 args=[self._print_queue])
1303 self._print_worker.start()
1304
1305 # Initialize the failed queue to empty.
1306 self._retry_queue = []
1307 self._failed = set()
1308
David Jamesfcb70ef2011-02-02 16:02:30 -08001309 # Setup an exit handler so that we print nice messages if we are
1310 # terminated.
1311 self._SetupExitHandler()
1312
1313 # Schedule our jobs.
Brian Harring0be85c62012-03-17 19:52:12 -07001314 self._state_map.update(
1315 (pkg, TargetState(pkg, data)) for pkg, data in deps_map.iteritems())
1316 self._fetch_ready.multi_put(self._state_map.itervalues())
David Jamesfcb70ef2011-02-02 16:02:30 -08001317
1318 def _SetupExitHandler(self):
1319
David James321490a2012-12-17 12:05:56 -08001320 def ExitHandler(signum, _frame):
David James7358d032011-05-19 10:40:03 -07001321 # Set KILLED flag.
1322 KILLED.set()
David Jamesfcb70ef2011-02-02 16:02:30 -08001323
1324 # Kill our signal handlers so we don't get called recursively
David James7358d032011-05-19 10:40:03 -07001325 signal.signal(signal.SIGINT, KillHandler)
1326 signal.signal(signal.SIGTERM, KillHandler)
David Jamesfcb70ef2011-02-02 16:02:30 -08001327
1328 # Print our current job status
Brian Harring0be85c62012-03-17 19:52:12 -07001329 for job in self._build_jobs.itervalues():
David Jamesfcb70ef2011-02-02 16:02:30 -08001330 if job:
1331 self._print_queue.put(JobPrinter(job, unlink=True))
1332
1333 # Notify the user that we are exiting
1334 self._Print("Exiting on signal %s" % signum)
David James7358d032011-05-19 10:40:03 -07001335 self._print_queue.put(None)
1336 self._print_worker.join()
David Jamesfcb70ef2011-02-02 16:02:30 -08001337
1338 # Kill child threads, then exit.
David James7358d032011-05-19 10:40:03 -07001339 os.killpg(0, signal.SIGKILL)
David Jamesfcb70ef2011-02-02 16:02:30 -08001340 sys.exit(1)
1341
1342 # Print out job status when we are killed
1343 signal.signal(signal.SIGINT, ExitHandler)
1344 signal.signal(signal.SIGTERM, ExitHandler)
1345
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001346 def _ScheduleUnpack(self, pkg_state):
1347 self._unpack_jobs[pkg_state.target] = None
1348 self._unpack_queue.put(pkg_state)
1349
Brian Harring0be85c62012-03-17 19:52:12 -07001350 def _Schedule(self, pkg_state):
David Jamesfcb70ef2011-02-02 16:02:30 -08001351 # We maintain a tree of all deps, if this doesn't need
David James8c7e5e32011-06-28 11:26:03 -07001352 # to be installed just free up its children and continue.
David Jamesfcb70ef2011-02-02 16:02:30 -08001353 # It is possible to reinstall deps of deps, without reinstalling
1354 # first level deps, like so:
1355 # chromeos (merge) -> eselect (nomerge) -> python (merge)
Brian Harring0be85c62012-03-17 19:52:12 -07001356 this_pkg = pkg_state.info
1357 target = pkg_state.target
1358 if pkg_state.info is not None:
1359 if this_pkg["action"] == "nomerge":
1360 self._Finish(target)
1361 elif target not in self._build_jobs:
1362 # Kick off the build if it's marked to be built.
1363 self._build_jobs[target] = None
1364 self._build_queue.put(pkg_state)
1365 return True
David Jamesfcb70ef2011-02-02 16:02:30 -08001366
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001367 def _ScheduleLoop(self, unpack_only=False):
1368 if unpack_only:
1369 ready_queue = self._unpack_ready
1370 jobs_queue = self._unpack_jobs
1371 procs = self._unpack_procs
1372 else:
1373 ready_queue = self._build_ready
1374 jobs_queue = self._build_jobs
1375 procs = self._build_procs
1376
David James8c7e5e32011-06-28 11:26:03 -07001377 # If the current load exceeds our desired load average, don't schedule
1378 # more than one job.
1379 if self._load_avg and os.getloadavg()[0] > self._load_avg:
1380 needed_jobs = 1
1381 else:
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001382 needed_jobs = procs
David James8c7e5e32011-06-28 11:26:03 -07001383
1384 # Schedule more jobs.
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001385 while ready_queue and len(jobs_queue) < needed_jobs:
1386 state = ready_queue.get()
1387 if unpack_only:
1388 self._ScheduleUnpack(state)
1389 else:
1390 if state.target not in self._failed:
1391 self._Schedule(state)
David Jamesfcb70ef2011-02-02 16:02:30 -08001392
1393 def _Print(self, line):
1394 """Print a single line."""
1395 self._print_queue.put(LinePrinter(line))
1396
1397 def _Status(self):
1398 """Print status."""
1399 current_time = time.time()
1400 no_output = True
1401
1402 # Print interim output every minute if --show-output is used. Otherwise,
1403 # print notifications about running packages every 2 minutes, and print
1404 # full output for jobs that have been running for 60 minutes or more.
1405 if self._show_output:
1406 interval = 60
1407 notify_interval = 0
1408 else:
1409 interval = 60 * 60
1410 notify_interval = 60 * 2
David James321490a2012-12-17 12:05:56 -08001411 for job in self._build_jobs.itervalues():
David Jamesfcb70ef2011-02-02 16:02:30 -08001412 if job:
1413 last_timestamp = max(job.start_timestamp, job.last_output_timestamp)
1414 if last_timestamp + interval < current_time:
1415 self._print_queue.put(JobPrinter(job))
1416 job.last_output_timestamp = current_time
1417 no_output = False
1418 elif (notify_interval and
1419 job.last_notify_timestamp + notify_interval < current_time):
1420 job_seconds = current_time - job.start_timestamp
1421 args = (job.pkgname, job_seconds / 60, job_seconds % 60, job.filename)
1422 info = "Still building %s (%dm%.1fs). Logs in %s" % args
1423 job.last_notify_timestamp = current_time
1424 self._Print(info)
1425 no_output = False
1426
1427 # If we haven't printed any messages yet, print a general status message
1428 # here.
1429 if no_output:
1430 seconds = current_time - GLOBAL_START
Brian Harring0be85c62012-03-17 19:52:12 -07001431 fjobs, fready = len(self._fetch_jobs), len(self._fetch_ready)
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001432 ujobs, uready = len(self._unpack_jobs), len(self._unpack_ready)
Brian Harring0be85c62012-03-17 19:52:12 -07001433 bjobs, bready = len(self._build_jobs), len(self._build_ready)
1434 retries = len(self._retry_queue)
1435 pending = max(0, len(self._deps_map) - fjobs - bjobs)
1436 line = "Pending %s/%s, " % (pending, self._total_jobs)
1437 if fjobs or fready:
1438 line += "Fetching %s/%s, " % (fjobs, fready + fjobs)
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001439 if ujobs or uready:
1440 line += "Unpacking %s/%s, " % (ujobs, uready + ujobs)
Brian Harring0be85c62012-03-17 19:52:12 -07001441 if bjobs or bready or retries:
1442 line += "Building %s/%s, " % (bjobs, bready + bjobs)
1443 if retries:
1444 line += "Retrying %s, " % (retries,)
David James8c7e5e32011-06-28 11:26:03 -07001445 load = " ".join(str(x) for x in os.getloadavg())
Brian Harring0be85c62012-03-17 19:52:12 -07001446 line += ("[Time %dm%.1fs Load %s]" % (seconds/60, seconds %60, load))
1447 self._Print(line)
David Jamesfcb70ef2011-02-02 16:02:30 -08001448
1449 def _Finish(self, target):
David James8c7e5e32011-06-28 11:26:03 -07001450 """Mark a target as completed and unblock dependencies."""
1451 this_pkg = self._deps_map[target]
1452 if this_pkg["needs"] and this_pkg["nodeps"]:
1453 # We got installed, but our deps have not been installed yet. Dependent
1454 # packages should only be installed when our needs have been fully met.
1455 this_pkg["action"] = "nomerge"
1456 else:
David James8c7e5e32011-06-28 11:26:03 -07001457 for dep in this_pkg["provides"]:
1458 dep_pkg = self._deps_map[dep]
Brian Harring0be85c62012-03-17 19:52:12 -07001459 state = self._state_map[dep]
David James8c7e5e32011-06-28 11:26:03 -07001460 del dep_pkg["needs"][target]
Brian Harring0be85c62012-03-17 19:52:12 -07001461 state.update_score()
1462 if not state.prefetched:
1463 if dep in self._fetch_ready:
1464 # If it's not currently being fetched, update the prioritization
1465 self._fetch_ready.sort()
1466 elif not dep_pkg["needs"]:
David James8c7e5e32011-06-28 11:26:03 -07001467 if dep_pkg["nodeps"] and dep_pkg["action"] == "nomerge":
1468 self._Finish(dep)
1469 else:
Brian Harring0be85c62012-03-17 19:52:12 -07001470 self._build_ready.put(self._state_map[dep])
David James8c7e5e32011-06-28 11:26:03 -07001471 self._deps_map.pop(target)
David Jamesfcb70ef2011-02-02 16:02:30 -08001472
1473 def _Retry(self):
David James8c7e5e32011-06-28 11:26:03 -07001474 while self._retry_queue:
Brian Harring0be85c62012-03-17 19:52:12 -07001475 state = self._retry_queue.pop(0)
1476 if self._Schedule(state):
1477 self._Print("Retrying emerge of %s." % state.target)
David James8c7e5e32011-06-28 11:26:03 -07001478 break
David Jamesfcb70ef2011-02-02 16:02:30 -08001479
Brian Harringa43f5952012-04-12 01:19:34 -07001480 def _Shutdown(self):
David Jamesfcb70ef2011-02-02 16:02:30 -08001481 # Tell emerge workers to exit. They all exit when 'None' is pushed
1482 # to the queue.
Brian Harring0be85c62012-03-17 19:52:12 -07001483
Brian Harringa43f5952012-04-12 01:19:34 -07001484 # Shutdown the workers first; then jobs (which is how they feed things back)
1485 # then finally the print queue.
Brian Harring0be85c62012-03-17 19:52:12 -07001486
Brian Harringa43f5952012-04-12 01:19:34 -07001487 def _stop(queue, pool):
1488 if pool is None:
1489 return
1490 try:
1491 queue.put(None)
1492 pool.close()
1493 pool.join()
1494 finally:
1495 pool.terminate()
Brian Harring0be85c62012-03-17 19:52:12 -07001496
Brian Harringa43f5952012-04-12 01:19:34 -07001497 _stop(self._fetch_queue, self._fetch_pool)
1498 self._fetch_queue = self._fetch_pool = None
Brian Harring0be85c62012-03-17 19:52:12 -07001499
Brian Harringa43f5952012-04-12 01:19:34 -07001500 _stop(self._build_queue, self._build_pool)
1501 self._build_queue = self._build_pool = None
1502
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001503 if self._unpack_only:
1504 _stop(self._unpack_queue, self._unpack_pool)
1505 self._unpack_queue = self._unpack_pool = None
1506
Brian Harringa43f5952012-04-12 01:19:34 -07001507 if self._job_queue is not None:
1508 self._job_queue.close()
1509 self._job_queue = None
David Jamesfcb70ef2011-02-02 16:02:30 -08001510
1511 # Now that our workers are finished, we can kill the print queue.
Brian Harringa43f5952012-04-12 01:19:34 -07001512 if self._print_worker is not None:
1513 try:
1514 self._print_queue.put(None)
1515 self._print_queue.close()
1516 self._print_worker.join()
1517 finally:
1518 self._print_worker.terminate()
1519 self._print_queue = self._print_worker = None
David Jamesfcb70ef2011-02-02 16:02:30 -08001520
1521 def Run(self):
1522 """Run through the scheduled ebuilds.
1523
1524 Keep running so long as we have uninstalled packages in the
1525 dependency graph to merge.
1526 """
Brian Harringa43f5952012-04-12 01:19:34 -07001527 if not self._deps_map:
1528 return
1529
Brian Harring0be85c62012-03-17 19:52:12 -07001530 # Start the fetchers.
1531 for _ in xrange(min(self._fetch_procs, len(self._fetch_ready))):
1532 state = self._fetch_ready.get()
1533 self._fetch_jobs[state.target] = None
1534 self._fetch_queue.put(state)
1535
1536 # Print an update, then get going.
1537 self._Status()
1538
David Jamese703d0f2012-01-12 16:27:45 -08001539 retried = set()
David Jamesfcb70ef2011-02-02 16:02:30 -08001540 while self._deps_map:
1541 # Check here that we are actually waiting for something.
Brian Harring0be85c62012-03-17 19:52:12 -07001542 if (self._build_queue.empty() and
David Jamesfcb70ef2011-02-02 16:02:30 -08001543 self._job_queue.empty() and
Brian Harring0be85c62012-03-17 19:52:12 -07001544 not self._fetch_jobs and
1545 not self._fetch_ready and
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001546 not self._unpack_jobs and
1547 not self._unpack_ready and
Brian Harring0be85c62012-03-17 19:52:12 -07001548 not self._build_jobs and
1549 not self._build_ready and
David Jamesfcb70ef2011-02-02 16:02:30 -08001550 self._deps_map):
1551 # If we have failed on a package, retry it now.
1552 if self._retry_queue:
1553 self._Retry()
1554 else:
David Jamesfcb70ef2011-02-02 16:02:30 -08001555 # Tell the user why we're exiting.
1556 if self._failed:
Mike Frysingerf2ff9172012-11-01 18:47:41 -04001557 print 'Packages failed:\n\t%s' % '\n\t'.join(self._failed)
David James0eae23e2012-07-03 15:04:25 -07001558 status_file = os.environ.get("PARALLEL_EMERGE_STATUS_FILE")
1559 if status_file:
David James321490a2012-12-17 12:05:56 -08001560 failed_pkgs = set(portage.versions.cpv_getkey(x)
1561 for x in self._failed)
David James0eae23e2012-07-03 15:04:25 -07001562 with open(status_file, "a") as f:
1563 f.write("%s\n" % " ".join(failed_pkgs))
David Jamesfcb70ef2011-02-02 16:02:30 -08001564 else:
1565 print "Deadlock! Circular dependencies!"
1566 sys.exit(1)
1567
David James321490a2012-12-17 12:05:56 -08001568 for _ in xrange(12):
David Jamesa74289a2011-08-12 10:41:24 -07001569 try:
1570 job = self._job_queue.get(timeout=5)
1571 break
1572 except Queue.Empty:
1573 # Check if any more jobs can be scheduled.
1574 self._ScheduleLoop()
1575 else:
Brian Harring706747c2012-03-16 03:04:31 -07001576 # Print an update every 60 seconds.
David Jamesfcb70ef2011-02-02 16:02:30 -08001577 self._Status()
1578 continue
1579
1580 target = job.target
1581
Brian Harring0be85c62012-03-17 19:52:12 -07001582 if job.fetch_only:
1583 if not job.done:
1584 self._fetch_jobs[job.target] = job
1585 else:
1586 state = self._state_map[job.target]
1587 state.prefetched = True
1588 state.fetched_successfully = (job.retcode == 0)
1589 del self._fetch_jobs[job.target]
1590 self._Print("Fetched %s in %2.2fs"
1591 % (target, time.time() - job.start_timestamp))
1592
1593 if self._show_output or job.retcode != 0:
1594 self._print_queue.put(JobPrinter(job, unlink=True))
1595 else:
1596 os.unlink(job.filename)
1597 # Failure or not, let build work with it next.
1598 if not self._deps_map[job.target]["needs"]:
1599 self._build_ready.put(state)
1600 self._ScheduleLoop()
1601
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001602 if self._unpack_only and job.retcode == 0:
1603 self._unpack_ready.put(state)
1604 self._ScheduleLoop(unpack_only=True)
1605
Brian Harring0be85c62012-03-17 19:52:12 -07001606 if self._fetch_ready:
1607 state = self._fetch_ready.get()
1608 self._fetch_queue.put(state)
1609 self._fetch_jobs[state.target] = None
1610 else:
1611 # Minor optimization; shut down fetchers early since we know
1612 # the queue is empty.
1613 self._fetch_queue.put(None)
1614 continue
1615
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001616 if job.unpack_only:
1617 if not job.done:
1618 self._unpack_jobs[target] = job
1619 else:
1620 del self._unpack_jobs[target]
1621 self._Print("Unpacked %s in %2.2fs"
1622 % (target, time.time() - job.start_timestamp))
1623 if self._show_output or job.retcode != 0:
1624 self._print_queue.put(JobPrinter(job, unlink=True))
1625 else:
1626 os.unlink(job.filename)
1627 if self._unpack_ready:
1628 state = self._unpack_ready.get()
1629 self._unpack_queue.put(state)
1630 self._unpack_jobs[state.target] = None
1631 continue
1632
David Jamesfcb70ef2011-02-02 16:02:30 -08001633 if not job.done:
Brian Harring0be85c62012-03-17 19:52:12 -07001634 self._build_jobs[target] = job
David Jamesfcb70ef2011-02-02 16:02:30 -08001635 self._Print("Started %s (logged in %s)" % (target, job.filename))
1636 continue
1637
1638 # Print output of job
1639 if self._show_output or job.retcode != 0:
1640 self._print_queue.put(JobPrinter(job, unlink=True))
1641 else:
1642 os.unlink(job.filename)
Brian Harring0be85c62012-03-17 19:52:12 -07001643 del self._build_jobs[target]
David Jamesfcb70ef2011-02-02 16:02:30 -08001644
1645 seconds = time.time() - job.start_timestamp
1646 details = "%s (in %dm%.1fs)" % (target, seconds / 60, seconds % 60)
David James32420cc2011-08-25 21:32:46 -07001647 previously_failed = target in self._failed
David Jamesfcb70ef2011-02-02 16:02:30 -08001648
1649 # Complain if necessary.
1650 if job.retcode != 0:
1651 # Handle job failure.
David James32420cc2011-08-25 21:32:46 -07001652 if previously_failed:
David Jamesfcb70ef2011-02-02 16:02:30 -08001653 # If this job has failed previously, give up.
1654 self._Print("Failed %s. Your build has failed." % details)
1655 else:
1656 # Queue up this build to try again after a long while.
David Jamese703d0f2012-01-12 16:27:45 -08001657 retried.add(target)
Brian Harring0be85c62012-03-17 19:52:12 -07001658 self._retry_queue.append(self._state_map[target])
David Jamesfcb70ef2011-02-02 16:02:30 -08001659 self._failed.add(target)
1660 self._Print("Failed %s, retrying later." % details)
1661 else:
David James32420cc2011-08-25 21:32:46 -07001662 if previously_failed:
1663 # Remove target from list of failed packages.
1664 self._failed.remove(target)
1665
1666 self._Print("Completed %s" % details)
1667
1668 # Mark as completed and unblock waiting ebuilds.
1669 self._Finish(target)
1670
1671 if previously_failed and self._retry_queue:
David Jamesfcb70ef2011-02-02 16:02:30 -08001672 # If we have successfully retried a failed package, and there
1673 # are more failed packages, try the next one. We will only have
1674 # one retrying package actively running at a time.
1675 self._Retry()
1676
David Jamesfcb70ef2011-02-02 16:02:30 -08001677
David James8c7e5e32011-06-28 11:26:03 -07001678 # Schedule pending jobs and print an update.
1679 self._ScheduleLoop()
1680 self._Status()
David Jamesfcb70ef2011-02-02 16:02:30 -08001681
David Jamese703d0f2012-01-12 16:27:45 -08001682 # If packages were retried, output a warning.
1683 if retried:
1684 self._Print("")
1685 self._Print("WARNING: The following packages failed the first time,")
1686 self._Print("but succeeded upon retry. This might indicate incorrect")
1687 self._Print("dependencies.")
1688 for pkg in retried:
1689 self._Print(" %s" % pkg)
1690 self._Print("@@@STEP_WARNINGS@@@")
1691 self._Print("")
1692
David Jamesfcb70ef2011-02-02 16:02:30 -08001693 # Tell child threads to exit.
1694 self._Print("Merge complete")
David Jamesfcb70ef2011-02-02 16:02:30 -08001695
1696
Brian Harring30675052012-02-29 12:18:22 -08001697def main(argv):
Brian Harring8294d652012-05-23 02:20:52 -07001698 try:
1699 return real_main(argv)
1700 finally:
1701 # Work around multiprocessing sucking and not cleaning up after itself.
1702 # http://bugs.python.org/issue4106;
1703 # Step one; ensure GC is ran *prior* to the VM starting shutdown.
1704 gc.collect()
1705 # Step two; go looking for those threads and try to manually reap
1706 # them if we can.
1707 for x in threading.enumerate():
1708 # Filter on the name, and ident; if ident is None, the thread
1709 # wasn't started.
1710 if x.name == 'QueueFeederThread' and x.ident is not None:
1711 x.join(1)
David Jamesfcb70ef2011-02-02 16:02:30 -08001712
Brian Harring8294d652012-05-23 02:20:52 -07001713
1714def real_main(argv):
Brian Harring30675052012-02-29 12:18:22 -08001715 parallel_emerge_args = argv[:]
David Jamesfcb70ef2011-02-02 16:02:30 -08001716 deps = DepGraphGenerator()
Brian Harring30675052012-02-29 12:18:22 -08001717 deps.Initialize(parallel_emerge_args)
David Jamesfcb70ef2011-02-02 16:02:30 -08001718 emerge = deps.emerge
1719
1720 if emerge.action is not None:
Brian Harring30675052012-02-29 12:18:22 -08001721 argv = deps.ParseParallelEmergeArgs(argv)
Brian Harring8294d652012-05-23 02:20:52 -07001722 return emerge_main(argv)
David Jamesfcb70ef2011-02-02 16:02:30 -08001723 elif not emerge.cmdline_packages:
1724 Usage()
Brian Harring8294d652012-05-23 02:20:52 -07001725 return 1
David Jamesfcb70ef2011-02-02 16:02:30 -08001726
1727 # Unless we're in pretend mode, there's not much point running without
1728 # root access. We need to be able to install packages.
1729 #
1730 # NOTE: Even if you're running --pretend, it's a good idea to run
1731 # parallel_emerge with root access so that portage can write to the
1732 # dependency cache. This is important for performance.
David James321490a2012-12-17 12:05:56 -08001733 if "--pretend" not in emerge.opts and portage.data.secpass < 2:
David Jamesfcb70ef2011-02-02 16:02:30 -08001734 print "parallel_emerge: superuser access is required."
Brian Harring8294d652012-05-23 02:20:52 -07001735 return 1
David Jamesfcb70ef2011-02-02 16:02:30 -08001736
1737 if "--quiet" not in emerge.opts:
1738 cmdline_packages = " ".join(emerge.cmdline_packages)
David Jamesfcb70ef2011-02-02 16:02:30 -08001739 print "Starting fast-emerge."
1740 print " Building package %s on %s" % (cmdline_packages,
1741 deps.board or "root")
David Jamesfcb70ef2011-02-02 16:02:30 -08001742
David James386ccd12011-05-04 20:17:42 -07001743 deps_tree, deps_info = deps.GenDependencyTree()
David Jamesfcb70ef2011-02-02 16:02:30 -08001744
1745 # You want me to be verbose? I'll give you two trees! Twice as much value.
1746 if "--tree" in emerge.opts and "--verbose" in emerge.opts:
1747 deps.PrintTree(deps_tree)
1748
David James386ccd12011-05-04 20:17:42 -07001749 deps_graph = deps.GenDependencyGraph(deps_tree, deps_info)
David Jamesfcb70ef2011-02-02 16:02:30 -08001750
1751 # OK, time to print out our progress so far.
1752 deps.PrintInstallPlan(deps_graph)
1753 if "--tree" in emerge.opts:
1754 PrintDepsMap(deps_graph)
1755
1756 # Are we upgrading portage? If so, and there are more packages to merge,
1757 # schedule a restart of parallel_emerge to merge the rest. This ensures that
1758 # we pick up all updates to portage settings before merging any more
1759 # packages.
1760 portage_upgrade = False
1761 root = emerge.settings["ROOT"]
1762 final_db = emerge.depgraph._dynamic_config.mydbapi[root]
1763 if root == "/":
1764 for db_pkg in final_db.match_pkgs("sys-apps/portage"):
1765 portage_pkg = deps_graph.get(db_pkg.cpv)
David James0ff16f22012-11-02 14:18:07 -07001766 if portage_pkg:
David Jamesfcb70ef2011-02-02 16:02:30 -08001767 portage_upgrade = True
1768 if "--quiet" not in emerge.opts:
1769 print "Upgrading portage first, then restarting..."
1770
David James0ff16f22012-11-02 14:18:07 -07001771 # Upgrade Portage first, then the rest of the packages.
1772 #
1773 # In order to grant the child permission to run setsid, we need to run sudo
1774 # again. We preserve SUDO_USER here in case an ebuild depends on it.
1775 if portage_upgrade:
1776 # Calculate what arguments to use when re-invoking.
1777 args = ["sudo", "-E", "SUDO_USER=%s" % os.environ.get("SUDO_USER", "")]
1778 args += [os.path.abspath(sys.argv[0])] + parallel_emerge_args
1779 args += ["--exclude=sys-apps/portage"]
1780
1781 # First upgrade Portage.
1782 passthrough_args = ("--quiet", "--pretend", "--verbose")
1783 emerge_args = [k for k in emerge.opts if k in passthrough_args]
1784 ret = emerge_main(emerge_args + ["portage"])
1785 if ret != 0:
1786 return ret
1787
1788 # Now upgrade the rest.
1789 os.execvp(args[0], args)
1790
David Jamesfcb70ef2011-02-02 16:02:30 -08001791 # Run the queued emerges.
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001792 scheduler = EmergeQueue(deps_graph, emerge, deps.package_db, deps.show_output,
1793 deps.unpack_only)
Brian Harringa43f5952012-04-12 01:19:34 -07001794 try:
1795 scheduler.Run()
1796 finally:
1797 scheduler._Shutdown()
David James97ce8902011-08-16 09:51:05 -07001798 scheduler = None
David Jamesfcb70ef2011-02-02 16:02:30 -08001799
Mike Frysingerd20a6e22012-10-04 19:01:10 -04001800 clean_logs(emerge.settings)
1801
David Jamesfcb70ef2011-02-02 16:02:30 -08001802 print "Done"
Brian Harring8294d652012-05-23 02:20:52 -07001803 return 0