blob: f536b1892881aa23105e3b9d8ac90e97177a05d0 [file] [log] [blame]
Mike Frysinger9f7e4ee2013-03-13 15:43:03 -04001#!/usr/bin/python
Mike Frysinger0a647fc2012-08-06 14:36:05 -04002# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
David Jamesfcb70ef2011-02-02 16:02:30 -08003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Program to run emerge in parallel, for significant speedup.
7
8Usage:
David James386ccd12011-05-04 20:17:42 -07009 ./parallel_emerge [--board=BOARD] [--workon=PKGS]
David Jamesfcb70ef2011-02-02 16:02:30 -080010 [--force-remote-binary=PKGS] [emerge args] package
11
David James78b6cd92012-04-02 21:36:12 -070012This script runs multiple emerge processes in parallel, using appropriate
13Portage APIs. It is faster than standard emerge because it has a
14multiprocess model instead of an asynchronous model.
David Jamesfcb70ef2011-02-02 16:02:30 -080015"""
16
17import codecs
18import copy
19import errno
Brian Harring8294d652012-05-23 02:20:52 -070020import gc
David James8c7e5e32011-06-28 11:26:03 -070021import heapq
David Jamesfcb70ef2011-02-02 16:02:30 -080022import multiprocessing
23import os
24import Queue
David Jamesfcb70ef2011-02-02 16:02:30 -080025import signal
26import sys
27import tempfile
Brian Harring8294d652012-05-23 02:20:52 -070028import threading
David Jamesfcb70ef2011-02-02 16:02:30 -080029import time
30import traceback
David Jamesfcb70ef2011-02-02 16:02:30 -080031
Thiago Goncalesf4acc422013-07-17 10:26:35 -070032from chromite.lib import cros_build_lib
33
David Jamesfcb70ef2011-02-02 16:02:30 -080034# If PORTAGE_USERNAME isn't specified, scrape it from the $HOME variable. On
35# Chromium OS, the default "portage" user doesn't have the necessary
36# permissions. It'd be easier if we could default to $USERNAME, but $USERNAME
37# is "root" here because we get called through sudo.
38#
39# We need to set this before importing any portage modules, because portage
40# looks up "PORTAGE_USERNAME" at import time.
41#
42# NOTE: .bashrc sets PORTAGE_USERNAME = $USERNAME, so most people won't
43# encounter this case unless they have an old chroot or blow away the
44# environment by running sudo without the -E specifier.
45if "PORTAGE_USERNAME" not in os.environ:
46 homedir = os.environ.get("HOME")
47 if homedir:
48 os.environ["PORTAGE_USERNAME"] = os.path.basename(homedir)
49
50# Portage doesn't expose dependency trees in its public API, so we have to
51# make use of some private APIs here. These modules are found under
52# /usr/lib/portage/pym/.
53#
54# TODO(davidjames): Update Portage to expose public APIs for these features.
David James321490a2012-12-17 12:05:56 -080055# pylint: disable=W0212
David Jamesfcb70ef2011-02-02 16:02:30 -080056from _emerge.actions import adjust_configs
57from _emerge.actions import load_emerge_config
58from _emerge.create_depgraph_params import create_depgraph_params
David James386ccd12011-05-04 20:17:42 -070059from _emerge.depgraph import backtrack_depgraph
Mike Frysinger901eaad2012-10-10 18:18:03 -040060try:
61 from _emerge.main import clean_logs
62except ImportError:
63 # Older portage versions did not provide clean_logs, so stub it.
64 # We need this if running in an older chroot that hasn't yet upgraded
65 # the portage version.
66 clean_logs = lambda x: None
David Jamesfcb70ef2011-02-02 16:02:30 -080067from _emerge.main import emerge_main
68from _emerge.main import parse_opts
69from _emerge.Package import Package
70from _emerge.Scheduler import Scheduler
David Jamesfcb70ef2011-02-02 16:02:30 -080071from _emerge.stdout_spinner import stdout_spinner
David James386ccd12011-05-04 20:17:42 -070072from portage._global_updates import _global_updates
David Jamesfcb70ef2011-02-02 16:02:30 -080073import portage
74import portage.debug
Mike Frysinger91d7da92013-02-19 15:53:46 -050075from portage.versions import vercmp
76
David Jamesfcb70ef2011-02-02 16:02:30 -080077
David Jamesfcb70ef2011-02-02 16:02:30 -080078def Usage():
79 """Print usage."""
80 print "Usage:"
David James386ccd12011-05-04 20:17:42 -070081 print " ./parallel_emerge [--board=BOARD] [--workon=PKGS]"
David Jamesfcb70ef2011-02-02 16:02:30 -080082 print " [--rebuild] [emerge args] package"
83 print
84 print "Packages specified as workon packages are always built from source."
David Jamesfcb70ef2011-02-02 16:02:30 -080085 print
86 print "The --workon argument is mainly useful when you want to build and"
87 print "install packages that you are working on unconditionally, but do not"
88 print "to have to rev the package to indicate you want to build it from"
89 print "source. The build_packages script will automatically supply the"
90 print "workon argument to emerge, ensuring that packages selected using"
91 print "cros-workon are rebuilt."
92 print
93 print "The --rebuild option rebuilds packages whenever their dependencies"
94 print "are changed. This ensures that your build is correct."
David Jamesfcb70ef2011-02-02 16:02:30 -080095
96
David Jamesfcb70ef2011-02-02 16:02:30 -080097# Global start time
98GLOBAL_START = time.time()
99
David James7358d032011-05-19 10:40:03 -0700100# Whether process has been killed by a signal.
101KILLED = multiprocessing.Event()
102
David Jamesfcb70ef2011-02-02 16:02:30 -0800103
104class EmergeData(object):
105 """This simple struct holds various emerge variables.
106
107 This struct helps us easily pass emerge variables around as a unit.
108 These variables are used for calculating dependencies and installing
109 packages.
110 """
111
David Jamesbf1e3442011-05-28 07:44:20 -0700112 __slots__ = ["action", "cmdline_packages", "depgraph", "favorites",
113 "mtimedb", "opts", "root_config", "scheduler_graph",
114 "settings", "spinner", "trees"]
David Jamesfcb70ef2011-02-02 16:02:30 -0800115
116 def __init__(self):
117 # The action the user requested. If the user is installing packages, this
118 # is None. If the user is doing anything other than installing packages,
119 # this will contain the action name, which will map exactly to the
120 # long-form name of the associated emerge option.
121 #
122 # Example: If you call parallel_emerge --unmerge package, the action name
123 # will be "unmerge"
124 self.action = None
125
126 # The list of packages the user passed on the command-line.
127 self.cmdline_packages = None
128
129 # The emerge dependency graph. It'll contain all the packages involved in
130 # this merge, along with their versions.
131 self.depgraph = None
132
David Jamesbf1e3442011-05-28 07:44:20 -0700133 # The list of candidates to add to the world file.
134 self.favorites = None
135
David Jamesfcb70ef2011-02-02 16:02:30 -0800136 # A dict of the options passed to emerge. This dict has been cleaned up
137 # a bit by parse_opts, so that it's a bit easier for the emerge code to
138 # look at the options.
139 #
140 # Emerge takes a few shortcuts in its cleanup process to make parsing of
141 # the options dict easier. For example, if you pass in "--usepkg=n", the
142 # "--usepkg" flag is just left out of the dictionary altogether. Because
143 # --usepkg=n is the default, this makes parsing easier, because emerge
144 # can just assume that if "--usepkg" is in the dictionary, it's enabled.
145 #
146 # These cleanup processes aren't applied to all options. For example, the
147 # --with-bdeps flag is passed in as-is. For a full list of the cleanups
148 # applied by emerge, see the parse_opts function in the _emerge.main
149 # package.
150 self.opts = None
151
152 # A dictionary used by portage to maintain global state. This state is
153 # loaded from disk when portage starts up, and saved to disk whenever we
154 # call mtimedb.commit().
155 #
156 # This database contains information about global updates (i.e., what
157 # version of portage we have) and what we're currently doing. Portage
158 # saves what it is currently doing in this database so that it can be
159 # resumed when you call it with the --resume option.
160 #
161 # parallel_emerge does not save what it is currently doing in the mtimedb,
162 # so we do not support the --resume option.
163 self.mtimedb = None
164
165 # The portage configuration for our current root. This contains the portage
166 # settings (see below) and the three portage trees for our current root.
167 # (The three portage trees are explained below, in the documentation for
168 # the "trees" member.)
169 self.root_config = None
170
171 # The scheduler graph is used by emerge to calculate what packages to
172 # install. We don't actually install any deps, so this isn't really used,
173 # but we pass it in to the Scheduler object anyway.
174 self.scheduler_graph = None
175
176 # Portage settings for our current session. Most of these settings are set
177 # in make.conf inside our current install root.
178 self.settings = None
179
180 # The spinner, which spews stuff to stdout to indicate that portage is
181 # doing something. We maintain our own spinner, so we set the portage
182 # spinner to "silent" mode.
183 self.spinner = None
184
185 # The portage trees. There are separate portage trees for each root. To get
186 # the portage tree for the current root, you can look in self.trees[root],
187 # where root = self.settings["ROOT"].
188 #
189 # In each root, there are three trees: vartree, porttree, and bintree.
190 # - vartree: A database of the currently-installed packages.
191 # - porttree: A database of ebuilds, that can be used to build packages.
192 # - bintree: A database of binary packages.
193 self.trees = None
194
195
196class DepGraphGenerator(object):
197 """Grab dependency information about packages from portage.
198
199 Typical usage:
200 deps = DepGraphGenerator()
201 deps.Initialize(sys.argv[1:])
202 deps_tree, deps_info = deps.GenDependencyTree()
203 deps_graph = deps.GenDependencyGraph(deps_tree, deps_info)
204 deps.PrintTree(deps_tree)
205 PrintDepsMap(deps_graph)
206 """
207
Thiago Goncalesf4acc422013-07-17 10:26:35 -0700208 __slots__ = ["board", "emerge", "package_db", "show_output", "unpack_only"]
David Jamesfcb70ef2011-02-02 16:02:30 -0800209
210 def __init__(self):
211 self.board = None
212 self.emerge = EmergeData()
David Jamesfcb70ef2011-02-02 16:02:30 -0800213 self.package_db = {}
David Jamesfcb70ef2011-02-02 16:02:30 -0800214 self.show_output = False
Thiago Goncalesf4acc422013-07-17 10:26:35 -0700215 self.unpack_only = False
David Jamesfcb70ef2011-02-02 16:02:30 -0800216
217 def ParseParallelEmergeArgs(self, argv):
218 """Read the parallel emerge arguments from the command-line.
219
220 We need to be compatible with emerge arg format. We scrape arguments that
221 are specific to parallel_emerge, and pass through the rest directly to
222 emerge.
223 Args:
224 argv: arguments list
225 Returns:
226 Arguments that don't belong to parallel_emerge
227 """
228 emerge_args = []
229 for arg in argv:
230 # Specifically match arguments that are specific to parallel_emerge, and
231 # pass through the rest.
232 if arg.startswith("--board="):
233 self.board = arg.replace("--board=", "")
234 elif arg.startswith("--workon="):
235 workon_str = arg.replace("--workon=", "")
David James7a1ea4b2011-10-13 15:06:41 -0700236 emerge_args.append("--reinstall-atoms=%s" % workon_str)
237 emerge_args.append("--usepkg-exclude=%s" % workon_str)
David Jamesfcb70ef2011-02-02 16:02:30 -0800238 elif arg.startswith("--force-remote-binary="):
239 force_remote_binary = arg.replace("--force-remote-binary=", "")
David James7a1ea4b2011-10-13 15:06:41 -0700240 emerge_args.append("--useoldpkg-atoms=%s" % force_remote_binary)
David Jamesfcb70ef2011-02-02 16:02:30 -0800241 elif arg == "--show-output":
242 self.show_output = True
David James386ccd12011-05-04 20:17:42 -0700243 elif arg == "--rebuild":
David James7a1ea4b2011-10-13 15:06:41 -0700244 emerge_args.append("--rebuild-if-unbuilt")
Thiago Goncalesf4acc422013-07-17 10:26:35 -0700245 elif arg == "--unpackonly":
246 emerge_args.append("--fetchonly")
247 self.unpack_only = True
David Jamesfcb70ef2011-02-02 16:02:30 -0800248 else:
249 # Not one of our options, so pass through to emerge.
250 emerge_args.append(arg)
251
David James386ccd12011-05-04 20:17:42 -0700252 # These packages take a really long time to build, so, for expediency, we
253 # are blacklisting them from automatic rebuilds because one of their
254 # dependencies needs to be recompiled.
255 for pkg in ("chromeos-base/chromeos-chrome", "media-plugins/o3d",
256 "dev-java/icedtea"):
David James7a1ea4b2011-10-13 15:06:41 -0700257 emerge_args.append("--rebuild-exclude=%s" % pkg)
David Jamesfcb70ef2011-02-02 16:02:30 -0800258
259 return emerge_args
260
261 def Initialize(self, args):
262 """Initializer. Parses arguments and sets up portage state."""
263
264 # Parse and strip out args that are just intended for parallel_emerge.
265 emerge_args = self.ParseParallelEmergeArgs(args)
266
267 # Setup various environment variables based on our current board. These
268 # variables are normally setup inside emerge-${BOARD}, but since we don't
269 # call that script, we have to set it up here. These variables serve to
270 # point our tools at /build/BOARD and to setup cross compiles to the
271 # appropriate board as configured in toolchain.conf.
272 if self.board:
273 os.environ["PORTAGE_CONFIGROOT"] = "/build/" + self.board
274 os.environ["PORTAGE_SYSROOT"] = "/build/" + self.board
275 os.environ["SYSROOT"] = "/build/" + self.board
David Jamesfcb70ef2011-02-02 16:02:30 -0800276
277 # Although CHROMEOS_ROOT isn't specific to boards, it's normally setup
278 # inside emerge-${BOARD}, so we set it up here for compatibility. It
279 # will be going away soon as we migrate to CROS_WORKON_SRCROOT.
280 os.environ.setdefault("CHROMEOS_ROOT", os.environ["HOME"] + "/trunk")
281
282 # Turn off interactive delays
283 os.environ["EBEEP_IGNORE"] = "1"
284 os.environ["EPAUSE_IGNORE"] = "1"
Mike Frysinger0a647fc2012-08-06 14:36:05 -0400285 os.environ["CLEAN_DELAY"] = "0"
David Jamesfcb70ef2011-02-02 16:02:30 -0800286
287 # Parse the emerge options.
David Jamesea3ca332011-05-26 11:48:29 -0700288 action, opts, cmdline_packages = parse_opts(emerge_args, silent=True)
David Jamesfcb70ef2011-02-02 16:02:30 -0800289
290 # Set environment variables based on options. Portage normally sets these
291 # environment variables in emerge_main, but we can't use that function,
292 # because it also does a bunch of other stuff that we don't want.
293 # TODO(davidjames): Patch portage to move this logic into a function we can
294 # reuse here.
295 if "--debug" in opts:
296 os.environ["PORTAGE_DEBUG"] = "1"
297 if "--config-root" in opts:
298 os.environ["PORTAGE_CONFIGROOT"] = opts["--config-root"]
299 if "--root" in opts:
300 os.environ["ROOT"] = opts["--root"]
301 if "--accept-properties" in opts:
302 os.environ["ACCEPT_PROPERTIES"] = opts["--accept-properties"]
303
David Jamesfcb70ef2011-02-02 16:02:30 -0800304 # If we're installing packages to the board, and we're not using the
David James927a56d2012-04-03 11:26:39 -0700305 # official flag, we can disable vardb locks. This is safe because we
306 # only run up to one instance of parallel_emerge in parallel.
David Jamesfcb70ef2011-02-02 16:02:30 -0800307 if self.board and os.environ.get("CHROMEOS_OFFICIAL") != "1":
308 os.environ.setdefault("PORTAGE_LOCKS", "false")
David Jamesfcb70ef2011-02-02 16:02:30 -0800309
310 # Now that we've setup the necessary environment variables, we can load the
311 # emerge config from disk.
312 settings, trees, mtimedb = load_emerge_config()
313
David Jamesea3ca332011-05-26 11:48:29 -0700314 # Add in EMERGE_DEFAULT_OPTS, if specified.
315 tmpcmdline = []
316 if "--ignore-default-opts" not in opts:
317 tmpcmdline.extend(settings["EMERGE_DEFAULT_OPTS"].split())
318 tmpcmdline.extend(emerge_args)
319 action, opts, cmdline_packages = parse_opts(tmpcmdline)
320
321 # If we're installing to the board, we want the --root-deps option so that
322 # portage will install the build dependencies to that location as well.
323 if self.board:
324 opts.setdefault("--root-deps", True)
325
David Jamesfcb70ef2011-02-02 16:02:30 -0800326 # Check whether our portage tree is out of date. Typically, this happens
327 # when you're setting up a new portage tree, such as in setup_board and
328 # make_chroot. In that case, portage applies a bunch of global updates
329 # here. Once the updates are finished, we need to commit any changes
330 # that the global update made to our mtimedb, and reload the config.
331 #
332 # Portage normally handles this logic in emerge_main, but again, we can't
333 # use that function here.
334 if _global_updates(trees, mtimedb["updates"]):
335 mtimedb.commit()
336 settings, trees, mtimedb = load_emerge_config(trees=trees)
337
338 # Setup implied options. Portage normally handles this logic in
339 # emerge_main.
340 if "--buildpkgonly" in opts or "buildpkg" in settings.features:
341 opts.setdefault("--buildpkg", True)
342 if "--getbinpkgonly" in opts:
343 opts.setdefault("--usepkgonly", True)
344 opts.setdefault("--getbinpkg", True)
345 if "getbinpkg" in settings.features:
346 # Per emerge_main, FEATURES=getbinpkg overrides --getbinpkg=n
347 opts["--getbinpkg"] = True
348 if "--getbinpkg" in opts or "--usepkgonly" in opts:
349 opts.setdefault("--usepkg", True)
350 if "--fetch-all-uri" in opts:
351 opts.setdefault("--fetchonly", True)
352 if "--skipfirst" in opts:
353 opts.setdefault("--resume", True)
354 if "--buildpkgonly" in opts:
355 # --buildpkgonly will not merge anything, so it overrides all binary
356 # package options.
357 for opt in ("--getbinpkg", "--getbinpkgonly",
358 "--usepkg", "--usepkgonly"):
359 opts.pop(opt, None)
360 if (settings.get("PORTAGE_DEBUG", "") == "1" and
361 "python-trace" in settings.features):
362 portage.debug.set_trace(True)
363
364 # Complain about unsupported options
David James386ccd12011-05-04 20:17:42 -0700365 for opt in ("--ask", "--ask-enter-invalid", "--resume", "--skipfirst"):
David Jamesfcb70ef2011-02-02 16:02:30 -0800366 if opt in opts:
367 print "%s is not supported by parallel_emerge" % opt
368 sys.exit(1)
369
370 # Make emerge specific adjustments to the config (e.g. colors!)
371 adjust_configs(opts, trees)
372
373 # Save our configuration so far in the emerge object
374 emerge = self.emerge
375 emerge.action, emerge.opts = action, opts
376 emerge.settings, emerge.trees, emerge.mtimedb = settings, trees, mtimedb
377 emerge.cmdline_packages = cmdline_packages
378 root = settings["ROOT"]
379 emerge.root_config = trees[root]["root_config"]
380
David James386ccd12011-05-04 20:17:42 -0700381 if "--usepkg" in opts:
David Jamesfcb70ef2011-02-02 16:02:30 -0800382 emerge.trees[root]["bintree"].populate("--getbinpkg" in opts)
383
David Jamesfcb70ef2011-02-02 16:02:30 -0800384 def CreateDepgraph(self, emerge, packages):
385 """Create an emerge depgraph object."""
386 # Setup emerge options.
387 emerge_opts = emerge.opts.copy()
388
David James386ccd12011-05-04 20:17:42 -0700389 # Ask portage to build a dependency graph. with the options we specified
390 # above.
David Jamesfcb70ef2011-02-02 16:02:30 -0800391 params = create_depgraph_params(emerge_opts, emerge.action)
David Jamesbf1e3442011-05-28 07:44:20 -0700392 success, depgraph, favorites = backtrack_depgraph(
David James386ccd12011-05-04 20:17:42 -0700393 emerge.settings, emerge.trees, emerge_opts, params, emerge.action,
394 packages, emerge.spinner)
395 emerge.depgraph = depgraph
David Jamesfcb70ef2011-02-02 16:02:30 -0800396
David James386ccd12011-05-04 20:17:42 -0700397 # Is it impossible to honor the user's request? Bail!
398 if not success:
399 depgraph.display_problems()
400 sys.exit(1)
David Jamesfcb70ef2011-02-02 16:02:30 -0800401
402 emerge.depgraph = depgraph
David Jamesbf1e3442011-05-28 07:44:20 -0700403 emerge.favorites = favorites
David Jamesfcb70ef2011-02-02 16:02:30 -0800404
David Jamesdeebd692011-05-09 17:02:52 -0700405 # Prime and flush emerge caches.
406 root = emerge.settings["ROOT"]
407 vardb = emerge.trees[root]["vartree"].dbapi
David James0bdc5de2011-05-12 16:22:26 -0700408 if "--pretend" not in emerge.opts:
409 vardb.counter_tick()
David Jamesdeebd692011-05-09 17:02:52 -0700410 vardb.flush_cache()
411
David James386ccd12011-05-04 20:17:42 -0700412 def GenDependencyTree(self):
David Jamesfcb70ef2011-02-02 16:02:30 -0800413 """Get dependency tree info from emerge.
414
David Jamesfcb70ef2011-02-02 16:02:30 -0800415 Returns:
416 Dependency tree
417 """
418 start = time.time()
419
420 emerge = self.emerge
421
422 # Create a list of packages to merge
423 packages = set(emerge.cmdline_packages[:])
David Jamesfcb70ef2011-02-02 16:02:30 -0800424
425 # Tell emerge to be quiet. We print plenty of info ourselves so we don't
426 # need any extra output from portage.
427 portage.util.noiselimit = -1
428
429 # My favorite feature: The silent spinner. It doesn't spin. Ever.
430 # I'd disable the colors by default too, but they look kind of cool.
431 emerge.spinner = stdout_spinner()
432 emerge.spinner.update = emerge.spinner.update_quiet
433
434 if "--quiet" not in emerge.opts:
435 print "Calculating deps..."
436
437 self.CreateDepgraph(emerge, packages)
438 depgraph = emerge.depgraph
439
440 # Build our own tree from the emerge digraph.
441 deps_tree = {}
442 digraph = depgraph._dynamic_config.digraph
David James3f778802011-08-25 19:31:45 -0700443 root = emerge.settings["ROOT"]
444 final_db = depgraph._dynamic_config.mydbapi[root]
David Jamesfcb70ef2011-02-02 16:02:30 -0800445 for node, node_deps in digraph.nodes.items():
446 # Calculate dependency packages that need to be installed first. Each
447 # child on the digraph is a dependency. The "operation" field specifies
448 # what we're doing (e.g. merge, uninstall, etc.). The "priorities" array
449 # contains the type of dependency (e.g. build, runtime, runtime_post,
450 # etc.)
451 #
David Jamesfcb70ef2011-02-02 16:02:30 -0800452 # Portage refers to the identifiers for packages as a CPV. This acronym
453 # stands for Component/Path/Version.
454 #
455 # Here's an example CPV: chromeos-base/power_manager-0.0.1-r1
456 # Split up, this CPV would be:
457 # C -- Component: chromeos-base
458 # P -- Path: power_manager
459 # V -- Version: 0.0.1-r1
460 #
461 # We just refer to CPVs as packages here because it's easier.
462 deps = {}
463 for child, priorities in node_deps[0].items():
David James3f778802011-08-25 19:31:45 -0700464 if isinstance(child, Package) and child.root == root:
465 cpv = str(child.cpv)
466 action = str(child.operation)
467
468 # If we're uninstalling a package, check whether Portage is
469 # installing a replacement. If so, just depend on the installation
470 # of the new package, because the old package will automatically
471 # be uninstalled at that time.
472 if action == "uninstall":
473 for pkg in final_db.match_pkgs(child.slot_atom):
474 cpv = str(pkg.cpv)
475 action = "merge"
476 break
477
478 deps[cpv] = dict(action=action,
479 deptypes=[str(x) for x in priorities],
480 deps={})
David Jamesfcb70ef2011-02-02 16:02:30 -0800481
482 # We've built our list of deps, so we can add our package to the tree.
David James3f778802011-08-25 19:31:45 -0700483 if isinstance(node, Package) and node.root == root:
David Jamesfcb70ef2011-02-02 16:02:30 -0800484 deps_tree[str(node.cpv)] = dict(action=str(node.operation),
485 deps=deps)
486
David Jamesfcb70ef2011-02-02 16:02:30 -0800487 # Ask portage for its install plan, so that we can only throw out
David James386ccd12011-05-04 20:17:42 -0700488 # dependencies that portage throws out.
David Jamesfcb70ef2011-02-02 16:02:30 -0800489 deps_info = {}
490 for pkg in depgraph.altlist():
491 if isinstance(pkg, Package):
David James3f778802011-08-25 19:31:45 -0700492 assert pkg.root == root
David Jamesfcb70ef2011-02-02 16:02:30 -0800493 self.package_db[pkg.cpv] = pkg
494
David Jamesfcb70ef2011-02-02 16:02:30 -0800495 # Save off info about the package
David James386ccd12011-05-04 20:17:42 -0700496 deps_info[str(pkg.cpv)] = {"idx": len(deps_info)}
David Jamesfcb70ef2011-02-02 16:02:30 -0800497
498 seconds = time.time() - start
499 if "--quiet" not in emerge.opts:
500 print "Deps calculated in %dm%.1fs" % (seconds / 60, seconds % 60)
501
502 return deps_tree, deps_info
503
504 def PrintTree(self, deps, depth=""):
505 """Print the deps we have seen in the emerge output.
506
507 Args:
508 deps: Dependency tree structure.
509 depth: Allows printing the tree recursively, with indentation.
510 """
511 for entry in sorted(deps):
512 action = deps[entry]["action"]
513 print "%s %s (%s)" % (depth, entry, action)
514 self.PrintTree(deps[entry]["deps"], depth=depth + " ")
515
David James386ccd12011-05-04 20:17:42 -0700516 def GenDependencyGraph(self, deps_tree, deps_info):
David Jamesfcb70ef2011-02-02 16:02:30 -0800517 """Generate a doubly linked dependency graph.
518
519 Args:
520 deps_tree: Dependency tree structure.
521 deps_info: More details on the dependencies.
522 Returns:
523 Deps graph in the form of a dict of packages, with each package
524 specifying a "needs" list and "provides" list.
525 """
526 emerge = self.emerge
David Jamesfcb70ef2011-02-02 16:02:30 -0800527
David Jamesfcb70ef2011-02-02 16:02:30 -0800528 # deps_map is the actual dependency graph.
529 #
530 # Each package specifies a "needs" list and a "provides" list. The "needs"
531 # list indicates which packages we depend on. The "provides" list
532 # indicates the reverse dependencies -- what packages need us.
533 #
534 # We also provide some other information in the dependency graph:
535 # - action: What we're planning on doing with this package. Generally,
536 # "merge", "nomerge", or "uninstall"
David Jamesfcb70ef2011-02-02 16:02:30 -0800537 deps_map = {}
538
539 def ReverseTree(packages):
540 """Convert tree to digraph.
541
542 Take the tree of package -> requirements and reverse it to a digraph of
543 buildable packages -> packages they unblock.
544 Args:
545 packages: Tree(s) of dependencies.
546 Returns:
547 Unsanitized digraph.
548 """
David James8c7e5e32011-06-28 11:26:03 -0700549 binpkg_phases = set(["setup", "preinst", "postinst"])
David James3f778802011-08-25 19:31:45 -0700550 needed_dep_types = set(["blocker", "buildtime", "runtime"])
David Jamesfcb70ef2011-02-02 16:02:30 -0800551 for pkg in packages:
552
553 # Create an entry for the package
554 action = packages[pkg]["action"]
David James8c7e5e32011-06-28 11:26:03 -0700555 default_pkg = {"needs": {}, "provides": set(), "action": action,
556 "nodeps": False, "binary": False}
David Jamesfcb70ef2011-02-02 16:02:30 -0800557 this_pkg = deps_map.setdefault(pkg, default_pkg)
558
David James8c7e5e32011-06-28 11:26:03 -0700559 if pkg in deps_info:
560 this_pkg["idx"] = deps_info[pkg]["idx"]
561
562 # If a package doesn't have any defined phases that might use the
563 # dependent packages (i.e. pkg_setup, pkg_preinst, or pkg_postinst),
564 # we can install this package before its deps are ready.
565 emerge_pkg = self.package_db.get(pkg)
566 if emerge_pkg and emerge_pkg.type_name == "binary":
567 this_pkg["binary"] = True
Mike Frysinger91d7da92013-02-19 15:53:46 -0500568 if 0 <= vercmp(portage.VERSION, "2.1.11.50"):
569 defined_phases = emerge_pkg.defined_phases
570 else:
571 defined_phases = emerge_pkg.metadata.defined_phases
David James8c7e5e32011-06-28 11:26:03 -0700572 defined_binpkg_phases = binpkg_phases.intersection(defined_phases)
573 if not defined_binpkg_phases:
574 this_pkg["nodeps"] = True
575
David Jamesfcb70ef2011-02-02 16:02:30 -0800576 # Create entries for dependencies of this package first.
577 ReverseTree(packages[pkg]["deps"])
578
579 # Add dependencies to this package.
580 for dep, dep_item in packages[pkg]["deps"].iteritems():
David James8c7e5e32011-06-28 11:26:03 -0700581 # We only need to enforce strict ordering of dependencies if the
David James3f778802011-08-25 19:31:45 -0700582 # dependency is a blocker, or is a buildtime or runtime dependency.
583 # (I.e., ignored, optional, and runtime_post dependencies don't
584 # depend on ordering.)
David James8c7e5e32011-06-28 11:26:03 -0700585 dep_types = dep_item["deptypes"]
586 if needed_dep_types.intersection(dep_types):
587 deps_map[dep]["provides"].add(pkg)
588 this_pkg["needs"][dep] = "/".join(dep_types)
David Jamesfcb70ef2011-02-02 16:02:30 -0800589
David James3f778802011-08-25 19:31:45 -0700590 # If there's a blocker, Portage may need to move files from one
591 # package to another, which requires editing the CONTENTS files of
592 # both packages. To avoid race conditions while editing this file,
593 # the two packages must not be installed in parallel, so we can't
594 # safely ignore dependencies. See http://crosbug.com/19328
595 if "blocker" in dep_types:
596 this_pkg["nodeps"] = False
597
David Jamesfcb70ef2011-02-02 16:02:30 -0800598 def FindCycles():
599 """Find cycles in the dependency tree.
600
601 Returns:
602 A dict mapping cyclic packages to a dict of the deps that cause
603 cycles. For each dep that causes cycles, it returns an example
604 traversal of the graph that shows the cycle.
605 """
606
607 def FindCyclesAtNode(pkg, cycles, unresolved, resolved):
608 """Find cycles in cyclic dependencies starting at specified package.
609
610 Args:
611 pkg: Package identifier.
612 cycles: A dict mapping cyclic packages to a dict of the deps that
613 cause cycles. For each dep that causes cycles, it returns an
614 example traversal of the graph that shows the cycle.
615 unresolved: Nodes that have been visited but are not fully processed.
616 resolved: Nodes that have been visited and are fully processed.
617 """
618 pkg_cycles = cycles.get(pkg)
619 if pkg in resolved and not pkg_cycles:
620 # If we already looked at this package, and found no cyclic
621 # dependencies, we can stop now.
622 return
623 unresolved.append(pkg)
624 for dep in deps_map[pkg]["needs"]:
625 if dep in unresolved:
626 idx = unresolved.index(dep)
627 mycycle = unresolved[idx:] + [dep]
David James321490a2012-12-17 12:05:56 -0800628 for i in xrange(len(mycycle) - 1):
David Jamesfcb70ef2011-02-02 16:02:30 -0800629 pkg1, pkg2 = mycycle[i], mycycle[i+1]
630 cycles.setdefault(pkg1, {}).setdefault(pkg2, mycycle)
631 elif not pkg_cycles or dep not in pkg_cycles:
632 # Looks like we haven't seen this edge before.
633 FindCyclesAtNode(dep, cycles, unresolved, resolved)
634 unresolved.pop()
635 resolved.add(pkg)
636
637 cycles, unresolved, resolved = {}, [], set()
638 for pkg in deps_map:
639 FindCyclesAtNode(pkg, cycles, unresolved, resolved)
640 return cycles
641
David James386ccd12011-05-04 20:17:42 -0700642 def RemoveUnusedPackages():
David Jamesfcb70ef2011-02-02 16:02:30 -0800643 """Remove installed packages, propagating dependencies."""
David Jamesfcb70ef2011-02-02 16:02:30 -0800644 # Schedule packages that aren't on the install list for removal
645 rm_pkgs = set(deps_map.keys()) - set(deps_info.keys())
646
David Jamesfcb70ef2011-02-02 16:02:30 -0800647 # Remove the packages we don't want, simplifying the graph and making
648 # it easier for us to crack cycles.
649 for pkg in sorted(rm_pkgs):
650 this_pkg = deps_map[pkg]
651 needs = this_pkg["needs"]
652 provides = this_pkg["provides"]
653 for dep in needs:
654 dep_provides = deps_map[dep]["provides"]
655 dep_provides.update(provides)
656 dep_provides.discard(pkg)
657 dep_provides.discard(dep)
658 for target in provides:
659 target_needs = deps_map[target]["needs"]
660 target_needs.update(needs)
661 target_needs.pop(pkg, None)
662 target_needs.pop(target, None)
663 del deps_map[pkg]
664
665 def PrintCycleBreak(basedep, dep, mycycle):
666 """Print details about a cycle that we are planning on breaking.
667
668 We are breaking a cycle where dep needs basedep. mycycle is an
669 example cycle which contains dep -> basedep."""
670
David Jamesfcb70ef2011-02-02 16:02:30 -0800671 needs = deps_map[dep]["needs"]
672 depinfo = needs.get(basedep, "deleted")
David Jamesfcb70ef2011-02-02 16:02:30 -0800673
David James3f778802011-08-25 19:31:45 -0700674 # It's OK to swap install order for blockers, as long as the two
675 # packages aren't installed in parallel. If there is a cycle, then
676 # we know the packages depend on each other already, so we can drop the
677 # blocker safely without printing a warning.
678 if depinfo == "blocker":
679 return
680
David Jamesfcb70ef2011-02-02 16:02:30 -0800681 # Notify the user that we're breaking a cycle.
682 print "Breaking %s -> %s (%s)" % (dep, basedep, depinfo)
683
684 # Show cycle.
David James321490a2012-12-17 12:05:56 -0800685 for i in xrange(len(mycycle) - 1):
David Jamesfcb70ef2011-02-02 16:02:30 -0800686 pkg1, pkg2 = mycycle[i], mycycle[i+1]
687 needs = deps_map[pkg1]["needs"]
688 depinfo = needs.get(pkg2, "deleted")
689 if pkg1 == dep and pkg2 == basedep:
690 depinfo = depinfo + ", deleting"
691 print " %s -> %s (%s)" % (pkg1, pkg2, depinfo)
692
693 def SanitizeTree():
694 """Remove circular dependencies.
695
696 We prune all dependencies involved in cycles that go against the emerge
697 ordering. This has a nice property: we're guaranteed to merge
698 dependencies in the same order that portage does.
699
700 Because we don't treat any dependencies as "soft" unless they're killed
701 by a cycle, we pay attention to a larger number of dependencies when
702 merging. This hurts performance a bit, but helps reliability.
703 """
704 start = time.time()
705 cycles = FindCycles()
706 while cycles:
707 for dep, mycycles in cycles.iteritems():
708 for basedep, mycycle in mycycles.iteritems():
709 if deps_info[basedep]["idx"] >= deps_info[dep]["idx"]:
Matt Tennant08797302011-10-17 16:18:45 -0700710 if "--quiet" not in emerge.opts:
711 PrintCycleBreak(basedep, dep, mycycle)
David Jamesfcb70ef2011-02-02 16:02:30 -0800712 del deps_map[dep]["needs"][basedep]
713 deps_map[basedep]["provides"].remove(dep)
714 cycles = FindCycles()
715 seconds = time.time() - start
716 if "--quiet" not in emerge.opts and seconds >= 0.1:
717 print "Tree sanitized in %dm%.1fs" % (seconds / 60, seconds % 60)
718
David James8c7e5e32011-06-28 11:26:03 -0700719 def FindRecursiveProvides(pkg, seen):
720 """Find all nodes that require a particular package.
721
722 Assumes that graph is acyclic.
723
724 Args:
725 pkg: Package identifier.
726 seen: Nodes that have been visited so far.
727 """
728 if pkg in seen:
729 return
730 seen.add(pkg)
731 info = deps_map[pkg]
732 info["tprovides"] = info["provides"].copy()
733 for dep in info["provides"]:
734 FindRecursiveProvides(dep, seen)
735 info["tprovides"].update(deps_map[dep]["tprovides"])
736
David Jamesa22906f2011-05-04 19:53:26 -0700737 ReverseTree(deps_tree)
David Jamesa22906f2011-05-04 19:53:26 -0700738
David James386ccd12011-05-04 20:17:42 -0700739 # We need to remove unused packages so that we can use the dependency
740 # ordering of the install process to show us what cycles to crack.
741 RemoveUnusedPackages()
David Jamesfcb70ef2011-02-02 16:02:30 -0800742 SanitizeTree()
David James8c7e5e32011-06-28 11:26:03 -0700743 seen = set()
744 for pkg in deps_map:
745 FindRecursiveProvides(pkg, seen)
David Jamesfcb70ef2011-02-02 16:02:30 -0800746 return deps_map
747
748 def PrintInstallPlan(self, deps_map):
749 """Print an emerge-style install plan.
750
751 The install plan lists what packages we're installing, in order.
752 It's useful for understanding what parallel_emerge is doing.
753
754 Args:
755 deps_map: The dependency graph.
756 """
757
758 def InstallPlanAtNode(target, deps_map):
759 nodes = []
760 nodes.append(target)
761 for dep in deps_map[target]["provides"]:
762 del deps_map[dep]["needs"][target]
763 if not deps_map[dep]["needs"]:
764 nodes.extend(InstallPlanAtNode(dep, deps_map))
765 return nodes
766
767 deps_map = copy.deepcopy(deps_map)
768 install_plan = []
769 plan = set()
770 for target, info in deps_map.iteritems():
771 if not info["needs"] and target not in plan:
772 for item in InstallPlanAtNode(target, deps_map):
773 plan.add(item)
774 install_plan.append(self.package_db[item])
775
776 for pkg in plan:
777 del deps_map[pkg]
778
779 if deps_map:
780 print "Cyclic dependencies:", " ".join(deps_map)
781 PrintDepsMap(deps_map)
782 sys.exit(1)
783
784 self.emerge.depgraph.display(install_plan)
785
786
787def PrintDepsMap(deps_map):
788 """Print dependency graph, for each package list it's prerequisites."""
789 for i in sorted(deps_map):
790 print "%s: (%s) needs" % (i, deps_map[i]["action"])
791 needs = deps_map[i]["needs"]
792 for j in sorted(needs):
793 print " %s" % (j)
794 if not needs:
795 print " no dependencies"
796
797
798class EmergeJobState(object):
799 __slots__ = ["done", "filename", "last_notify_timestamp", "last_output_seek",
800 "last_output_timestamp", "pkgname", "retcode", "start_timestamp",
Thiago Goncalesf4acc422013-07-17 10:26:35 -0700801 "target", "fetch_only", "unpack_only"]
David Jamesfcb70ef2011-02-02 16:02:30 -0800802
803 def __init__(self, target, pkgname, done, filename, start_timestamp,
Thiago Goncalesf4acc422013-07-17 10:26:35 -0700804 retcode=None, fetch_only=False, unpack_only=False):
David Jamesfcb70ef2011-02-02 16:02:30 -0800805
806 # The full name of the target we're building (e.g.
807 # chromeos-base/chromeos-0.0.1-r60)
808 self.target = target
809
810 # The short name of the target we're building (e.g. chromeos-0.0.1-r60)
811 self.pkgname = pkgname
812
813 # Whether the job is done. (True if the job is done; false otherwise.)
814 self.done = done
815
816 # The filename where output is currently stored.
817 self.filename = filename
818
819 # The timestamp of the last time we printed the name of the log file. We
820 # print this at the beginning of the job, so this starts at
821 # start_timestamp.
822 self.last_notify_timestamp = start_timestamp
823
824 # The location (in bytes) of the end of the last complete line we printed.
825 # This starts off at zero. We use this to jump to the right place when we
826 # print output from the same ebuild multiple times.
827 self.last_output_seek = 0
828
829 # The timestamp of the last time we printed output. Since we haven't
830 # printed output yet, this starts at zero.
831 self.last_output_timestamp = 0
832
833 # The return code of our job, if the job is actually finished.
834 self.retcode = retcode
835
Brian Harring0be85c62012-03-17 19:52:12 -0700836 # Was this just a fetch job?
837 self.fetch_only = fetch_only
838
David Jamesfcb70ef2011-02-02 16:02:30 -0800839 # The timestamp when our job started.
840 self.start_timestamp = start_timestamp
841
Thiago Goncalesf4acc422013-07-17 10:26:35 -0700842 # No emerge, only unpack packages.
843 self.unpack_only = unpack_only
844
David Jamesfcb70ef2011-02-02 16:02:30 -0800845
David James321490a2012-12-17 12:05:56 -0800846def KillHandler(_signum, _frame):
David James7358d032011-05-19 10:40:03 -0700847 # Kill self and all subprocesses.
848 os.killpg(0, signal.SIGKILL)
849
David Jamesfcb70ef2011-02-02 16:02:30 -0800850def SetupWorkerSignals():
David James321490a2012-12-17 12:05:56 -0800851 def ExitHandler(_signum, _frame):
David James7358d032011-05-19 10:40:03 -0700852 # Set KILLED flag.
853 KILLED.set()
David James13cead42011-05-18 16:22:01 -0700854
David James7358d032011-05-19 10:40:03 -0700855 # Remove our signal handlers so we don't get called recursively.
856 signal.signal(signal.SIGINT, KillHandler)
857 signal.signal(signal.SIGTERM, KillHandler)
David Jamesfcb70ef2011-02-02 16:02:30 -0800858
859 # Ensure that we exit quietly and cleanly, if possible, when we receive
860 # SIGTERM or SIGINT signals. By default, when the user hits CTRL-C, all
861 # of the child processes will print details about KeyboardInterrupt
862 # exceptions, which isn't very helpful.
863 signal.signal(signal.SIGINT, ExitHandler)
864 signal.signal(signal.SIGTERM, ExitHandler)
865
David James6b29d052012-11-02 10:27:27 -0700866def EmergeProcess(output, *args, **kwargs):
David James1ed3e252011-10-05 20:26:15 -0700867 """Merge a package in a subprocess.
868
869 Args:
David James1ed3e252011-10-05 20:26:15 -0700870 output: Temporary file to write output.
David James6b29d052012-11-02 10:27:27 -0700871 *args: Arguments to pass to Scheduler constructor.
872 **kwargs: Keyword arguments to pass to Scheduler constructor.
David James1ed3e252011-10-05 20:26:15 -0700873
874 Returns:
875 The exit code returned by the subprocess.
876 """
877 pid = os.fork()
878 if pid == 0:
879 try:
880 # Sanity checks.
881 if sys.stdout.fileno() != 1: raise Exception("sys.stdout.fileno() != 1")
882 if sys.stderr.fileno() != 2: raise Exception("sys.stderr.fileno() != 2")
883
884 # - Redirect 1 (stdout) and 2 (stderr) at our temporary file.
885 # - Redirect 0 to point at sys.stdin. In this case, sys.stdin
886 # points at a file reading os.devnull, because multiprocessing mucks
887 # with sys.stdin.
888 # - Leave the sys.stdin and output filehandles alone.
889 fd_pipes = {0: sys.stdin.fileno(),
890 1: output.fileno(),
891 2: output.fileno(),
892 sys.stdin.fileno(): sys.stdin.fileno(),
893 output.fileno(): output.fileno()}
David Jamesa249eef2013-07-19 14:03:45 -0700894 if 0 <= vercmp(portage.VERSION, "2.1.11.50"):
895 portage.process._setup_pipes(fd_pipes, close_fds=False)
896 else:
897 portage.process._setup_pipes(fd_pipes)
David James1ed3e252011-10-05 20:26:15 -0700898
899 # Portage doesn't like when sys.stdin.fileno() != 0, so point sys.stdin
900 # at the filehandle we just created in _setup_pipes.
901 if sys.stdin.fileno() != 0:
David James6b29d052012-11-02 10:27:27 -0700902 sys.__stdin__ = sys.stdin = os.fdopen(0, "r")
903
904 scheduler = Scheduler(*args, **kwargs)
905
906 # Enable blocker handling even though we're in --nodeps mode. This
907 # allows us to unmerge the blocker after we've merged the replacement.
908 scheduler._opts_ignore_blockers = frozenset()
David James1ed3e252011-10-05 20:26:15 -0700909
910 # Actually do the merge.
911 retval = scheduler.merge()
912
913 # We catch all exceptions here (including SystemExit, KeyboardInterrupt,
914 # etc) so as to ensure that we don't confuse the multiprocessing module,
915 # which expects that all forked children exit with os._exit().
David James321490a2012-12-17 12:05:56 -0800916 # pylint: disable=W0702
David James1ed3e252011-10-05 20:26:15 -0700917 except:
918 traceback.print_exc(file=output)
919 retval = 1
920 sys.stdout.flush()
921 sys.stderr.flush()
922 output.flush()
923 os._exit(retval)
924 else:
925 # Return the exit code of the subprocess.
926 return os.waitpid(pid, 0)[1]
David Jamesfcb70ef2011-02-02 16:02:30 -0800927
Thiago Goncalesf4acc422013-07-17 10:26:35 -0700928
929def UnpackPackage(pkg_state):
930 """Unpacks package described by pkg_state.
931
932 Args:
933 pkg_state: EmergeJobState object describing target.
934
935 Returns:
936 Exit code returned by subprocess.
937 """
938 pkgdir = os.environ.get("PKGDIR",
939 os.path.join(os.environ["SYSROOT"], "packages"))
940 root = os.environ.get("ROOT", os.environ["SYSROOT"])
941 path = os.path.join(pkgdir, pkg_state.target + ".tbz2")
942 comp = cros_build_lib.FindCompressor(cros_build_lib.COMP_BZIP2)
943 cmd = [comp, "-dc"]
944 if comp.endswith("pbzip2"):
945 cmd.append("--ignore-trailing-garbage=1")
946 cmd.append(path)
947
948 result = cros_build_lib.RunCommand(cmd, cwd=root, stdout_to_pipe=True,
949 print_cmd=False, error_code_ok=True)
950
951 # If we were not successful, return now and don't attempt untar.
952 if result.returncode:
953 return result.returncode
954
955 cmd = ["sudo", "tar", "-xf", "-", "-C", root]
956 result = cros_build_lib.RunCommand(cmd, cwd=root, input=result.output,
957 print_cmd=False, error_code_ok=True)
958
959 return result.returncode
960
961
962def EmergeWorker(task_queue, job_queue, emerge, package_db, fetch_only=False,
963 unpack_only=False):
David Jamesfcb70ef2011-02-02 16:02:30 -0800964 """This worker emerges any packages given to it on the task_queue.
965
966 Args:
967 task_queue: The queue of tasks for this worker to do.
968 job_queue: The queue of results from the worker.
969 emerge: An EmergeData() object.
970 package_db: A dict, mapping package ids to portage Package objects.
Brian Harring0be85c62012-03-17 19:52:12 -0700971 fetch_only: A bool, indicating if we should just fetch the target.
Thiago Goncalesf4acc422013-07-17 10:26:35 -0700972 unpack_only: A bool, indicating if we should just unpack the target.
David Jamesfcb70ef2011-02-02 16:02:30 -0800973
974 It expects package identifiers to be passed to it via task_queue. When
975 a task is started, it pushes the (target, filename) to the started_queue.
976 The output is stored in filename. When a merge starts or finishes, we push
977 EmergeJobState objects to the job_queue.
978 """
979
980 SetupWorkerSignals()
981 settings, trees, mtimedb = emerge.settings, emerge.trees, emerge.mtimedb
David Jamesdeebd692011-05-09 17:02:52 -0700982
983 # Disable flushing of caches to save on I/O.
David James7a1ea4b2011-10-13 15:06:41 -0700984 root = emerge.settings["ROOT"]
985 vardb = emerge.trees[root]["vartree"].dbapi
986 vardb._flush_cache_enabled = False
Brian Harring0be85c62012-03-17 19:52:12 -0700987 bindb = emerge.trees[root]["bintree"].dbapi
988 # Might be a set, might be a list, might be None; no clue, just use shallow
989 # copy to ensure we can roll it back.
990 original_remotepkgs = copy.copy(bindb.bintree._remotepkgs)
David Jamesdeebd692011-05-09 17:02:52 -0700991
David Jamesfcb70ef2011-02-02 16:02:30 -0800992 opts, spinner = emerge.opts, emerge.spinner
993 opts["--nodeps"] = True
Brian Harring0be85c62012-03-17 19:52:12 -0700994 if fetch_only:
995 opts["--fetchonly"] = True
996
David Jamesfcb70ef2011-02-02 16:02:30 -0800997 while True:
998 # Wait for a new item to show up on the queue. This is a blocking wait,
999 # so if there's nothing to do, we just sit here.
Brian Harring0be85c62012-03-17 19:52:12 -07001000 pkg_state = task_queue.get()
1001 if pkg_state is None:
David Jamesfcb70ef2011-02-02 16:02:30 -08001002 # If target is None, this means that the main thread wants us to quit.
1003 # The other workers need to exit too, so we'll push the message back on
1004 # to the queue so they'll get it too.
Brian Harring0be85c62012-03-17 19:52:12 -07001005 task_queue.put(None)
David Jamesfcb70ef2011-02-02 16:02:30 -08001006 return
David James7358d032011-05-19 10:40:03 -07001007 if KILLED.is_set():
1008 return
1009
Brian Harring0be85c62012-03-17 19:52:12 -07001010 target = pkg_state.target
1011
David Jamesfcb70ef2011-02-02 16:02:30 -08001012 db_pkg = package_db[target]
Brian Harring0be85c62012-03-17 19:52:12 -07001013
1014 if db_pkg.type_name == "binary":
1015 if not fetch_only and pkg_state.fetched_successfully:
1016 # Ensure portage doesn't think our pkg is remote- else it'll force
1017 # a redownload of it (even if the on-disk file is fine). In-memory
1018 # caching basically, implemented dumbly.
1019 bindb.bintree._remotepkgs = None
1020 else:
1021 bindb.bintree_remotepkgs = original_remotepkgs
1022
David Jamesfcb70ef2011-02-02 16:02:30 -08001023 db_pkg.root_config = emerge.root_config
1024 install_list = [db_pkg]
1025 pkgname = db_pkg.pf
1026 output = tempfile.NamedTemporaryFile(prefix=pkgname + "-", delete=False)
David James01b1e0f2012-06-07 17:18:05 -07001027 os.chmod(output.name, 644)
David Jamesfcb70ef2011-02-02 16:02:30 -08001028 start_timestamp = time.time()
Brian Harring0be85c62012-03-17 19:52:12 -07001029 job = EmergeJobState(target, pkgname, False, output.name, start_timestamp,
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001030 fetch_only=fetch_only, unpack_only=unpack_only)
David Jamesfcb70ef2011-02-02 16:02:30 -08001031 job_queue.put(job)
1032 if "--pretend" in opts:
1033 retcode = 0
1034 else:
David Jamesfcb70ef2011-02-02 16:02:30 -08001035 try:
David James386ccd12011-05-04 20:17:42 -07001036 emerge.scheduler_graph.mergelist = install_list
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001037 if unpack_only:
1038 retcode = UnpackPackage(pkg_state)
1039 else:
1040 retcode = EmergeProcess(output, settings, trees, mtimedb, opts,
1041 spinner, favorites=emerge.favorites,
1042 graph_config=emerge.scheduler_graph)
David Jamesfcb70ef2011-02-02 16:02:30 -08001043 except Exception:
1044 traceback.print_exc(file=output)
1045 retcode = 1
David James1ed3e252011-10-05 20:26:15 -07001046 output.close()
David Jamesfcb70ef2011-02-02 16:02:30 -08001047
David James7358d032011-05-19 10:40:03 -07001048 if KILLED.is_set():
1049 return
1050
David Jamesfcb70ef2011-02-02 16:02:30 -08001051 job = EmergeJobState(target, pkgname, True, output.name, start_timestamp,
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001052 retcode, fetch_only=fetch_only,
1053 unpack_only=unpack_only)
David Jamesfcb70ef2011-02-02 16:02:30 -08001054 job_queue.put(job)
1055
1056
1057class LinePrinter(object):
1058 """Helper object to print a single line."""
1059
1060 def __init__(self, line):
1061 self.line = line
1062
David James321490a2012-12-17 12:05:56 -08001063 def Print(self, _seek_locations):
David Jamesfcb70ef2011-02-02 16:02:30 -08001064 print self.line
1065
1066
1067class JobPrinter(object):
1068 """Helper object to print output of a job."""
1069
1070 def __init__(self, job, unlink=False):
1071 """Print output of job.
1072
1073 If unlink is True, unlink the job output file when done."""
1074 self.current_time = time.time()
1075 self.job = job
1076 self.unlink = unlink
1077
1078 def Print(self, seek_locations):
1079
1080 job = self.job
1081
1082 # Calculate how long the job has been running.
1083 seconds = self.current_time - job.start_timestamp
1084
1085 # Note that we've printed out the job so far.
1086 job.last_output_timestamp = self.current_time
1087
1088 # Note that we're starting the job
1089 info = "job %s (%dm%.1fs)" % (job.pkgname, seconds / 60, seconds % 60)
1090 last_output_seek = seek_locations.get(job.filename, 0)
1091 if last_output_seek:
1092 print "=== Continue output for %s ===" % info
1093 else:
1094 print "=== Start output for %s ===" % info
1095
1096 # Print actual output from job
1097 f = codecs.open(job.filename, encoding='utf-8', errors='replace')
1098 f.seek(last_output_seek)
1099 prefix = job.pkgname + ":"
1100 for line in f:
1101
1102 # Save off our position in the file
1103 if line and line[-1] == "\n":
1104 last_output_seek = f.tell()
1105 line = line[:-1]
1106
1107 # Print our line
1108 print prefix, line.encode('utf-8', 'replace')
1109 f.close()
1110
1111 # Save our last spot in the file so that we don't print out the same
1112 # location twice.
1113 seek_locations[job.filename] = last_output_seek
1114
1115 # Note end of output section
1116 if job.done:
1117 print "=== Complete: %s ===" % info
1118 else:
1119 print "=== Still running: %s ===" % info
1120
1121 if self.unlink:
1122 os.unlink(job.filename)
1123
1124
1125def PrintWorker(queue):
1126 """A worker that prints stuff to the screen as requested."""
1127
David James321490a2012-12-17 12:05:56 -08001128 def ExitHandler(_signum, _frame):
David James7358d032011-05-19 10:40:03 -07001129 # Set KILLED flag.
1130 KILLED.set()
1131
David Jamesfcb70ef2011-02-02 16:02:30 -08001132 # Switch to default signal handlers so that we'll die after two signals.
David James7358d032011-05-19 10:40:03 -07001133 signal.signal(signal.SIGINT, KillHandler)
1134 signal.signal(signal.SIGTERM, KillHandler)
David Jamesfcb70ef2011-02-02 16:02:30 -08001135
1136 # Don't exit on the first SIGINT / SIGTERM, because the parent worker will
1137 # handle it and tell us when we need to exit.
1138 signal.signal(signal.SIGINT, ExitHandler)
1139 signal.signal(signal.SIGTERM, ExitHandler)
1140
1141 # seek_locations is a map indicating the position we are at in each file.
1142 # It starts off empty, but is set by the various Print jobs as we go along
1143 # to indicate where we left off in each file.
1144 seek_locations = {}
1145 while True:
1146 try:
1147 job = queue.get()
1148 if job:
1149 job.Print(seek_locations)
David Jamesbccf8eb2011-07-27 14:06:06 -07001150 sys.stdout.flush()
David Jamesfcb70ef2011-02-02 16:02:30 -08001151 else:
1152 break
1153 except IOError as ex:
1154 if ex.errno == errno.EINTR:
1155 # Looks like we received a signal. Keep printing.
1156 continue
1157 raise
1158
Brian Harring867e2362012-03-17 04:05:17 -07001159
Brian Harring0be85c62012-03-17 19:52:12 -07001160class TargetState(object):
Brian Harring867e2362012-03-17 04:05:17 -07001161
Brian Harring0be85c62012-03-17 19:52:12 -07001162 __slots__ = ("target", "info", "score", "prefetched", "fetched_successfully")
Brian Harring867e2362012-03-17 04:05:17 -07001163
David James321490a2012-12-17 12:05:56 -08001164 def __init__(self, target, info):
Brian Harring867e2362012-03-17 04:05:17 -07001165 self.target, self.info = target, info
Brian Harring0be85c62012-03-17 19:52:12 -07001166 self.fetched_successfully = False
1167 self.prefetched = False
David James321490a2012-12-17 12:05:56 -08001168 self.score = None
Brian Harring867e2362012-03-17 04:05:17 -07001169 self.update_score()
1170
1171 def __cmp__(self, other):
1172 return cmp(self.score, other.score)
1173
1174 def update_score(self):
1175 self.score = (
1176 -len(self.info["tprovides"]),
Brian Harring0be85c62012-03-17 19:52:12 -07001177 len(self.info["needs"]),
Brian Harring11c5eeb2012-03-18 11:02:39 -07001178 not self.info["binary"],
Brian Harring867e2362012-03-17 04:05:17 -07001179 -len(self.info["provides"]),
1180 self.info["idx"],
1181 self.target,
1182 )
1183
1184
1185class ScoredHeap(object):
1186
Brian Harring0be85c62012-03-17 19:52:12 -07001187 __slots__ = ("heap", "_heap_set")
1188
Brian Harring867e2362012-03-17 04:05:17 -07001189 def __init__(self, initial=()):
Brian Harring0be85c62012-03-17 19:52:12 -07001190 self.heap = list()
1191 self._heap_set = set()
1192 if initial:
1193 self.multi_put(initial)
Brian Harring867e2362012-03-17 04:05:17 -07001194
1195 def get(self):
Brian Harring0be85c62012-03-17 19:52:12 -07001196 item = heapq.heappop(self.heap)
1197 self._heap_set.remove(item.target)
1198 return item
Brian Harring867e2362012-03-17 04:05:17 -07001199
Brian Harring0be85c62012-03-17 19:52:12 -07001200 def put(self, item):
1201 if not isinstance(item, TargetState):
1202 raise ValueError("Item %r isn't a TargetState" % (item,))
1203 heapq.heappush(self.heap, item)
1204 self._heap_set.add(item.target)
Brian Harring867e2362012-03-17 04:05:17 -07001205
Brian Harring0be85c62012-03-17 19:52:12 -07001206 def multi_put(self, sequence):
1207 sequence = list(sequence)
1208 self.heap.extend(sequence)
1209 self._heap_set.update(x.target for x in sequence)
Brian Harring867e2362012-03-17 04:05:17 -07001210 self.sort()
1211
David James5c9996d2012-03-24 10:50:46 -07001212 def sort(self):
1213 heapq.heapify(self.heap)
1214
Brian Harring0be85c62012-03-17 19:52:12 -07001215 def __contains__(self, target):
1216 return target in self._heap_set
1217
1218 def __nonzero__(self):
1219 return bool(self.heap)
1220
Brian Harring867e2362012-03-17 04:05:17 -07001221 def __len__(self):
1222 return len(self.heap)
1223
1224
David Jamesfcb70ef2011-02-02 16:02:30 -08001225class EmergeQueue(object):
1226 """Class to schedule emerge jobs according to a dependency graph."""
1227
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001228 def __init__(self, deps_map, emerge, package_db, show_output, unpack_only):
David Jamesfcb70ef2011-02-02 16:02:30 -08001229 # Store the dependency graph.
1230 self._deps_map = deps_map
Brian Harring0be85c62012-03-17 19:52:12 -07001231 self._state_map = {}
David Jamesfcb70ef2011-02-02 16:02:30 -08001232 # Initialize the running queue to empty
Brian Harring0be85c62012-03-17 19:52:12 -07001233 self._build_jobs = {}
1234 self._build_ready = ScoredHeap()
1235 self._fetch_jobs = {}
1236 self._fetch_ready = ScoredHeap()
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001237 self._unpack_jobs = {}
1238 self._unpack_ready = ScoredHeap()
David Jamesfcb70ef2011-02-02 16:02:30 -08001239 # List of total package installs represented in deps_map.
1240 install_jobs = [x for x in deps_map if deps_map[x]["action"] == "merge"]
1241 self._total_jobs = len(install_jobs)
1242 self._show_output = show_output
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001243 self._unpack_only = unpack_only
David Jamesfcb70ef2011-02-02 16:02:30 -08001244
1245 if "--pretend" in emerge.opts:
1246 print "Skipping merge because of --pretend mode."
1247 sys.exit(0)
1248
David James7358d032011-05-19 10:40:03 -07001249 # Set a process group so we can easily terminate all children.
1250 os.setsid()
1251
David Jamesfcb70ef2011-02-02 16:02:30 -08001252 # Setup scheduler graph object. This is used by the child processes
1253 # to help schedule jobs.
1254 emerge.scheduler_graph = emerge.depgraph.schedulerGraph()
1255
1256 # Calculate how many jobs we can run in parallel. We don't want to pass
1257 # the --jobs flag over to emerge itself, because that'll tell emerge to
1258 # hide its output, and said output is quite useful for debugging hung
1259 # jobs.
1260 procs = min(self._total_jobs,
1261 emerge.opts.pop("--jobs", multiprocessing.cpu_count()))
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001262 self._build_procs = self._unpack_procs = self._fetch_procs = max(1, procs)
David James8c7e5e32011-06-28 11:26:03 -07001263 self._load_avg = emerge.opts.pop("--load-average", None)
David Jamesfcb70ef2011-02-02 16:02:30 -08001264 self._job_queue = multiprocessing.Queue()
1265 self._print_queue = multiprocessing.Queue()
Brian Harring0be85c62012-03-17 19:52:12 -07001266
1267 self._fetch_queue = multiprocessing.Queue()
1268 args = (self._fetch_queue, self._job_queue, emerge, package_db, True)
1269 self._fetch_pool = multiprocessing.Pool(self._fetch_procs, EmergeWorker,
1270 args)
1271
1272 self._build_queue = multiprocessing.Queue()
1273 args = (self._build_queue, self._job_queue, emerge, package_db)
1274 self._build_pool = multiprocessing.Pool(self._build_procs, EmergeWorker,
1275 args)
1276
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001277 if self._unpack_only:
1278 # Unpack pool only required on unpack_only jobs.
1279 self._unpack_queue = multiprocessing.Queue()
1280 args = (self._unpack_queue, self._job_queue, emerge, package_db, False,
1281 True)
1282 self._unpack_pool = multiprocessing.Pool(self._unpack_procs, EmergeWorker,
1283 args)
1284
David Jamesfcb70ef2011-02-02 16:02:30 -08001285 self._print_worker = multiprocessing.Process(target=PrintWorker,
1286 args=[self._print_queue])
1287 self._print_worker.start()
1288
1289 # Initialize the failed queue to empty.
1290 self._retry_queue = []
1291 self._failed = set()
1292
David Jamesfcb70ef2011-02-02 16:02:30 -08001293 # Setup an exit handler so that we print nice messages if we are
1294 # terminated.
1295 self._SetupExitHandler()
1296
1297 # Schedule our jobs.
Brian Harring0be85c62012-03-17 19:52:12 -07001298 self._state_map.update(
1299 (pkg, TargetState(pkg, data)) for pkg, data in deps_map.iteritems())
1300 self._fetch_ready.multi_put(self._state_map.itervalues())
David Jamesfcb70ef2011-02-02 16:02:30 -08001301
1302 def _SetupExitHandler(self):
1303
David James321490a2012-12-17 12:05:56 -08001304 def ExitHandler(signum, _frame):
David James7358d032011-05-19 10:40:03 -07001305 # Set KILLED flag.
1306 KILLED.set()
David Jamesfcb70ef2011-02-02 16:02:30 -08001307
1308 # Kill our signal handlers so we don't get called recursively
David James7358d032011-05-19 10:40:03 -07001309 signal.signal(signal.SIGINT, KillHandler)
1310 signal.signal(signal.SIGTERM, KillHandler)
David Jamesfcb70ef2011-02-02 16:02:30 -08001311
1312 # Print our current job status
Brian Harring0be85c62012-03-17 19:52:12 -07001313 for job in self._build_jobs.itervalues():
David Jamesfcb70ef2011-02-02 16:02:30 -08001314 if job:
1315 self._print_queue.put(JobPrinter(job, unlink=True))
1316
1317 # Notify the user that we are exiting
1318 self._Print("Exiting on signal %s" % signum)
David James7358d032011-05-19 10:40:03 -07001319 self._print_queue.put(None)
1320 self._print_worker.join()
David Jamesfcb70ef2011-02-02 16:02:30 -08001321
1322 # Kill child threads, then exit.
David James7358d032011-05-19 10:40:03 -07001323 os.killpg(0, signal.SIGKILL)
David Jamesfcb70ef2011-02-02 16:02:30 -08001324 sys.exit(1)
1325
1326 # Print out job status when we are killed
1327 signal.signal(signal.SIGINT, ExitHandler)
1328 signal.signal(signal.SIGTERM, ExitHandler)
1329
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001330 def _ScheduleUnpack(self, pkg_state):
1331 self._unpack_jobs[pkg_state.target] = None
1332 self._unpack_queue.put(pkg_state)
1333
Brian Harring0be85c62012-03-17 19:52:12 -07001334 def _Schedule(self, pkg_state):
David Jamesfcb70ef2011-02-02 16:02:30 -08001335 # We maintain a tree of all deps, if this doesn't need
David James8c7e5e32011-06-28 11:26:03 -07001336 # to be installed just free up its children and continue.
David Jamesfcb70ef2011-02-02 16:02:30 -08001337 # It is possible to reinstall deps of deps, without reinstalling
1338 # first level deps, like so:
1339 # chromeos (merge) -> eselect (nomerge) -> python (merge)
Brian Harring0be85c62012-03-17 19:52:12 -07001340 this_pkg = pkg_state.info
1341 target = pkg_state.target
1342 if pkg_state.info is not None:
1343 if this_pkg["action"] == "nomerge":
1344 self._Finish(target)
1345 elif target not in self._build_jobs:
1346 # Kick off the build if it's marked to be built.
1347 self._build_jobs[target] = None
1348 self._build_queue.put(pkg_state)
1349 return True
David Jamesfcb70ef2011-02-02 16:02:30 -08001350
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001351 def _ScheduleLoop(self, unpack_only=False):
1352 if unpack_only:
1353 ready_queue = self._unpack_ready
1354 jobs_queue = self._unpack_jobs
1355 procs = self._unpack_procs
1356 else:
1357 ready_queue = self._build_ready
1358 jobs_queue = self._build_jobs
1359 procs = self._build_procs
1360
David James8c7e5e32011-06-28 11:26:03 -07001361 # If the current load exceeds our desired load average, don't schedule
1362 # more than one job.
1363 if self._load_avg and os.getloadavg()[0] > self._load_avg:
1364 needed_jobs = 1
1365 else:
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001366 needed_jobs = procs
David James8c7e5e32011-06-28 11:26:03 -07001367
1368 # Schedule more jobs.
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001369 while ready_queue and len(jobs_queue) < needed_jobs:
1370 state = ready_queue.get()
1371 if unpack_only:
1372 self._ScheduleUnpack(state)
1373 else:
1374 if state.target not in self._failed:
1375 self._Schedule(state)
David Jamesfcb70ef2011-02-02 16:02:30 -08001376
1377 def _Print(self, line):
1378 """Print a single line."""
1379 self._print_queue.put(LinePrinter(line))
1380
1381 def _Status(self):
1382 """Print status."""
1383 current_time = time.time()
1384 no_output = True
1385
1386 # Print interim output every minute if --show-output is used. Otherwise,
1387 # print notifications about running packages every 2 minutes, and print
1388 # full output for jobs that have been running for 60 minutes or more.
1389 if self._show_output:
1390 interval = 60
1391 notify_interval = 0
1392 else:
1393 interval = 60 * 60
1394 notify_interval = 60 * 2
David James321490a2012-12-17 12:05:56 -08001395 for job in self._build_jobs.itervalues():
David Jamesfcb70ef2011-02-02 16:02:30 -08001396 if job:
1397 last_timestamp = max(job.start_timestamp, job.last_output_timestamp)
1398 if last_timestamp + interval < current_time:
1399 self._print_queue.put(JobPrinter(job))
1400 job.last_output_timestamp = current_time
1401 no_output = False
1402 elif (notify_interval and
1403 job.last_notify_timestamp + notify_interval < current_time):
1404 job_seconds = current_time - job.start_timestamp
1405 args = (job.pkgname, job_seconds / 60, job_seconds % 60, job.filename)
1406 info = "Still building %s (%dm%.1fs). Logs in %s" % args
1407 job.last_notify_timestamp = current_time
1408 self._Print(info)
1409 no_output = False
1410
1411 # If we haven't printed any messages yet, print a general status message
1412 # here.
1413 if no_output:
1414 seconds = current_time - GLOBAL_START
Brian Harring0be85c62012-03-17 19:52:12 -07001415 fjobs, fready = len(self._fetch_jobs), len(self._fetch_ready)
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001416 ujobs, uready = len(self._unpack_jobs), len(self._unpack_ready)
Brian Harring0be85c62012-03-17 19:52:12 -07001417 bjobs, bready = len(self._build_jobs), len(self._build_ready)
1418 retries = len(self._retry_queue)
1419 pending = max(0, len(self._deps_map) - fjobs - bjobs)
1420 line = "Pending %s/%s, " % (pending, self._total_jobs)
1421 if fjobs or fready:
1422 line += "Fetching %s/%s, " % (fjobs, fready + fjobs)
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001423 if ujobs or uready:
1424 line += "Unpacking %s/%s, " % (ujobs, uready + ujobs)
Brian Harring0be85c62012-03-17 19:52:12 -07001425 if bjobs or bready or retries:
1426 line += "Building %s/%s, " % (bjobs, bready + bjobs)
1427 if retries:
1428 line += "Retrying %s, " % (retries,)
David James8c7e5e32011-06-28 11:26:03 -07001429 load = " ".join(str(x) for x in os.getloadavg())
Brian Harring0be85c62012-03-17 19:52:12 -07001430 line += ("[Time %dm%.1fs Load %s]" % (seconds/60, seconds %60, load))
1431 self._Print(line)
David Jamesfcb70ef2011-02-02 16:02:30 -08001432
1433 def _Finish(self, target):
David James8c7e5e32011-06-28 11:26:03 -07001434 """Mark a target as completed and unblock dependencies."""
1435 this_pkg = self._deps_map[target]
1436 if this_pkg["needs"] and this_pkg["nodeps"]:
1437 # We got installed, but our deps have not been installed yet. Dependent
1438 # packages should only be installed when our needs have been fully met.
1439 this_pkg["action"] = "nomerge"
1440 else:
David James8c7e5e32011-06-28 11:26:03 -07001441 for dep in this_pkg["provides"]:
1442 dep_pkg = self._deps_map[dep]
Brian Harring0be85c62012-03-17 19:52:12 -07001443 state = self._state_map[dep]
David James8c7e5e32011-06-28 11:26:03 -07001444 del dep_pkg["needs"][target]
Brian Harring0be85c62012-03-17 19:52:12 -07001445 state.update_score()
1446 if not state.prefetched:
1447 if dep in self._fetch_ready:
1448 # If it's not currently being fetched, update the prioritization
1449 self._fetch_ready.sort()
1450 elif not dep_pkg["needs"]:
David James8c7e5e32011-06-28 11:26:03 -07001451 if dep_pkg["nodeps"] and dep_pkg["action"] == "nomerge":
1452 self._Finish(dep)
1453 else:
Brian Harring0be85c62012-03-17 19:52:12 -07001454 self._build_ready.put(self._state_map[dep])
David James8c7e5e32011-06-28 11:26:03 -07001455 self._deps_map.pop(target)
David Jamesfcb70ef2011-02-02 16:02:30 -08001456
1457 def _Retry(self):
David James8c7e5e32011-06-28 11:26:03 -07001458 while self._retry_queue:
Brian Harring0be85c62012-03-17 19:52:12 -07001459 state = self._retry_queue.pop(0)
1460 if self._Schedule(state):
1461 self._Print("Retrying emerge of %s." % state.target)
David James8c7e5e32011-06-28 11:26:03 -07001462 break
David Jamesfcb70ef2011-02-02 16:02:30 -08001463
Brian Harringa43f5952012-04-12 01:19:34 -07001464 def _Shutdown(self):
David Jamesfcb70ef2011-02-02 16:02:30 -08001465 # Tell emerge workers to exit. They all exit when 'None' is pushed
1466 # to the queue.
Brian Harring0be85c62012-03-17 19:52:12 -07001467
Brian Harringa43f5952012-04-12 01:19:34 -07001468 # Shutdown the workers first; then jobs (which is how they feed things back)
1469 # then finally the print queue.
Brian Harring0be85c62012-03-17 19:52:12 -07001470
Brian Harringa43f5952012-04-12 01:19:34 -07001471 def _stop(queue, pool):
1472 if pool is None:
1473 return
1474 try:
1475 queue.put(None)
1476 pool.close()
1477 pool.join()
1478 finally:
1479 pool.terminate()
Brian Harring0be85c62012-03-17 19:52:12 -07001480
Brian Harringa43f5952012-04-12 01:19:34 -07001481 _stop(self._fetch_queue, self._fetch_pool)
1482 self._fetch_queue = self._fetch_pool = None
Brian Harring0be85c62012-03-17 19:52:12 -07001483
Brian Harringa43f5952012-04-12 01:19:34 -07001484 _stop(self._build_queue, self._build_pool)
1485 self._build_queue = self._build_pool = None
1486
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001487 if self._unpack_only:
1488 _stop(self._unpack_queue, self._unpack_pool)
1489 self._unpack_queue = self._unpack_pool = None
1490
Brian Harringa43f5952012-04-12 01:19:34 -07001491 if self._job_queue is not None:
1492 self._job_queue.close()
1493 self._job_queue = None
David Jamesfcb70ef2011-02-02 16:02:30 -08001494
1495 # Now that our workers are finished, we can kill the print queue.
Brian Harringa43f5952012-04-12 01:19:34 -07001496 if self._print_worker is not None:
1497 try:
1498 self._print_queue.put(None)
1499 self._print_queue.close()
1500 self._print_worker.join()
1501 finally:
1502 self._print_worker.terminate()
1503 self._print_queue = self._print_worker = None
David Jamesfcb70ef2011-02-02 16:02:30 -08001504
1505 def Run(self):
1506 """Run through the scheduled ebuilds.
1507
1508 Keep running so long as we have uninstalled packages in the
1509 dependency graph to merge.
1510 """
Brian Harringa43f5952012-04-12 01:19:34 -07001511 if not self._deps_map:
1512 return
1513
Brian Harring0be85c62012-03-17 19:52:12 -07001514 # Start the fetchers.
1515 for _ in xrange(min(self._fetch_procs, len(self._fetch_ready))):
1516 state = self._fetch_ready.get()
1517 self._fetch_jobs[state.target] = None
1518 self._fetch_queue.put(state)
1519
1520 # Print an update, then get going.
1521 self._Status()
1522
David Jamese703d0f2012-01-12 16:27:45 -08001523 retried = set()
David Jamesfcb70ef2011-02-02 16:02:30 -08001524 while self._deps_map:
1525 # Check here that we are actually waiting for something.
Brian Harring0be85c62012-03-17 19:52:12 -07001526 if (self._build_queue.empty() and
David Jamesfcb70ef2011-02-02 16:02:30 -08001527 self._job_queue.empty() and
Brian Harring0be85c62012-03-17 19:52:12 -07001528 not self._fetch_jobs and
1529 not self._fetch_ready and
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001530 not self._unpack_jobs and
1531 not self._unpack_ready and
Brian Harring0be85c62012-03-17 19:52:12 -07001532 not self._build_jobs and
1533 not self._build_ready and
David Jamesfcb70ef2011-02-02 16:02:30 -08001534 self._deps_map):
1535 # If we have failed on a package, retry it now.
1536 if self._retry_queue:
1537 self._Retry()
1538 else:
David Jamesfcb70ef2011-02-02 16:02:30 -08001539 # Tell the user why we're exiting.
1540 if self._failed:
Mike Frysingerf2ff9172012-11-01 18:47:41 -04001541 print 'Packages failed:\n\t%s' % '\n\t'.join(self._failed)
David James0eae23e2012-07-03 15:04:25 -07001542 status_file = os.environ.get("PARALLEL_EMERGE_STATUS_FILE")
1543 if status_file:
David James321490a2012-12-17 12:05:56 -08001544 failed_pkgs = set(portage.versions.cpv_getkey(x)
1545 for x in self._failed)
David James0eae23e2012-07-03 15:04:25 -07001546 with open(status_file, "a") as f:
1547 f.write("%s\n" % " ".join(failed_pkgs))
David Jamesfcb70ef2011-02-02 16:02:30 -08001548 else:
1549 print "Deadlock! Circular dependencies!"
1550 sys.exit(1)
1551
David James321490a2012-12-17 12:05:56 -08001552 for _ in xrange(12):
David Jamesa74289a2011-08-12 10:41:24 -07001553 try:
1554 job = self._job_queue.get(timeout=5)
1555 break
1556 except Queue.Empty:
1557 # Check if any more jobs can be scheduled.
1558 self._ScheduleLoop()
1559 else:
Brian Harring706747c2012-03-16 03:04:31 -07001560 # Print an update every 60 seconds.
David Jamesfcb70ef2011-02-02 16:02:30 -08001561 self._Status()
1562 continue
1563
1564 target = job.target
1565
Brian Harring0be85c62012-03-17 19:52:12 -07001566 if job.fetch_only:
1567 if not job.done:
1568 self._fetch_jobs[job.target] = job
1569 else:
1570 state = self._state_map[job.target]
1571 state.prefetched = True
1572 state.fetched_successfully = (job.retcode == 0)
1573 del self._fetch_jobs[job.target]
1574 self._Print("Fetched %s in %2.2fs"
1575 % (target, time.time() - job.start_timestamp))
1576
1577 if self._show_output or job.retcode != 0:
1578 self._print_queue.put(JobPrinter(job, unlink=True))
1579 else:
1580 os.unlink(job.filename)
1581 # Failure or not, let build work with it next.
1582 if not self._deps_map[job.target]["needs"]:
1583 self._build_ready.put(state)
1584 self._ScheduleLoop()
1585
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001586 if self._unpack_only and job.retcode == 0:
1587 self._unpack_ready.put(state)
1588 self._ScheduleLoop(unpack_only=True)
1589
Brian Harring0be85c62012-03-17 19:52:12 -07001590 if self._fetch_ready:
1591 state = self._fetch_ready.get()
1592 self._fetch_queue.put(state)
1593 self._fetch_jobs[state.target] = None
1594 else:
1595 # Minor optimization; shut down fetchers early since we know
1596 # the queue is empty.
1597 self._fetch_queue.put(None)
1598 continue
1599
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001600 if job.unpack_only:
1601 if not job.done:
1602 self._unpack_jobs[target] = job
1603 else:
1604 del self._unpack_jobs[target]
1605 self._Print("Unpacked %s in %2.2fs"
1606 % (target, time.time() - job.start_timestamp))
1607 if self._show_output or job.retcode != 0:
1608 self._print_queue.put(JobPrinter(job, unlink=True))
1609 else:
1610 os.unlink(job.filename)
1611 if self._unpack_ready:
1612 state = self._unpack_ready.get()
1613 self._unpack_queue.put(state)
1614 self._unpack_jobs[state.target] = None
1615 continue
1616
David Jamesfcb70ef2011-02-02 16:02:30 -08001617 if not job.done:
Brian Harring0be85c62012-03-17 19:52:12 -07001618 self._build_jobs[target] = job
David Jamesfcb70ef2011-02-02 16:02:30 -08001619 self._Print("Started %s (logged in %s)" % (target, job.filename))
1620 continue
1621
1622 # Print output of job
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)
Brian Harring0be85c62012-03-17 19:52:12 -07001627 del self._build_jobs[target]
David Jamesfcb70ef2011-02-02 16:02:30 -08001628
1629 seconds = time.time() - job.start_timestamp
1630 details = "%s (in %dm%.1fs)" % (target, seconds / 60, seconds % 60)
David James32420cc2011-08-25 21:32:46 -07001631 previously_failed = target in self._failed
David Jamesfcb70ef2011-02-02 16:02:30 -08001632
1633 # Complain if necessary.
1634 if job.retcode != 0:
1635 # Handle job failure.
David James32420cc2011-08-25 21:32:46 -07001636 if previously_failed:
David Jamesfcb70ef2011-02-02 16:02:30 -08001637 # If this job has failed previously, give up.
1638 self._Print("Failed %s. Your build has failed." % details)
1639 else:
1640 # Queue up this build to try again after a long while.
David Jamese703d0f2012-01-12 16:27:45 -08001641 retried.add(target)
Brian Harring0be85c62012-03-17 19:52:12 -07001642 self._retry_queue.append(self._state_map[target])
David Jamesfcb70ef2011-02-02 16:02:30 -08001643 self._failed.add(target)
1644 self._Print("Failed %s, retrying later." % details)
1645 else:
David James32420cc2011-08-25 21:32:46 -07001646 if previously_failed:
1647 # Remove target from list of failed packages.
1648 self._failed.remove(target)
1649
1650 self._Print("Completed %s" % details)
1651
1652 # Mark as completed and unblock waiting ebuilds.
1653 self._Finish(target)
1654
1655 if previously_failed and self._retry_queue:
David Jamesfcb70ef2011-02-02 16:02:30 -08001656 # If we have successfully retried a failed package, and there
1657 # are more failed packages, try the next one. We will only have
1658 # one retrying package actively running at a time.
1659 self._Retry()
1660
David Jamesfcb70ef2011-02-02 16:02:30 -08001661
David James8c7e5e32011-06-28 11:26:03 -07001662 # Schedule pending jobs and print an update.
1663 self._ScheduleLoop()
1664 self._Status()
David Jamesfcb70ef2011-02-02 16:02:30 -08001665
David Jamese703d0f2012-01-12 16:27:45 -08001666 # If packages were retried, output a warning.
1667 if retried:
1668 self._Print("")
1669 self._Print("WARNING: The following packages failed the first time,")
1670 self._Print("but succeeded upon retry. This might indicate incorrect")
1671 self._Print("dependencies.")
1672 for pkg in retried:
1673 self._Print(" %s" % pkg)
1674 self._Print("@@@STEP_WARNINGS@@@")
1675 self._Print("")
1676
David Jamesfcb70ef2011-02-02 16:02:30 -08001677 # Tell child threads to exit.
1678 self._Print("Merge complete")
David Jamesfcb70ef2011-02-02 16:02:30 -08001679
1680
Brian Harring30675052012-02-29 12:18:22 -08001681def main(argv):
Brian Harring8294d652012-05-23 02:20:52 -07001682 try:
1683 return real_main(argv)
1684 finally:
1685 # Work around multiprocessing sucking and not cleaning up after itself.
1686 # http://bugs.python.org/issue4106;
1687 # Step one; ensure GC is ran *prior* to the VM starting shutdown.
1688 gc.collect()
1689 # Step two; go looking for those threads and try to manually reap
1690 # them if we can.
1691 for x in threading.enumerate():
1692 # Filter on the name, and ident; if ident is None, the thread
1693 # wasn't started.
1694 if x.name == 'QueueFeederThread' and x.ident is not None:
1695 x.join(1)
David Jamesfcb70ef2011-02-02 16:02:30 -08001696
Brian Harring8294d652012-05-23 02:20:52 -07001697
1698def real_main(argv):
Brian Harring30675052012-02-29 12:18:22 -08001699 parallel_emerge_args = argv[:]
David Jamesfcb70ef2011-02-02 16:02:30 -08001700 deps = DepGraphGenerator()
Brian Harring30675052012-02-29 12:18:22 -08001701 deps.Initialize(parallel_emerge_args)
David Jamesfcb70ef2011-02-02 16:02:30 -08001702 emerge = deps.emerge
1703
1704 if emerge.action is not None:
Brian Harring30675052012-02-29 12:18:22 -08001705 argv = deps.ParseParallelEmergeArgs(argv)
Brian Harring8294d652012-05-23 02:20:52 -07001706 return emerge_main(argv)
David Jamesfcb70ef2011-02-02 16:02:30 -08001707 elif not emerge.cmdline_packages:
1708 Usage()
Brian Harring8294d652012-05-23 02:20:52 -07001709 return 1
David Jamesfcb70ef2011-02-02 16:02:30 -08001710
1711 # Unless we're in pretend mode, there's not much point running without
1712 # root access. We need to be able to install packages.
1713 #
1714 # NOTE: Even if you're running --pretend, it's a good idea to run
1715 # parallel_emerge with root access so that portage can write to the
1716 # dependency cache. This is important for performance.
David James321490a2012-12-17 12:05:56 -08001717 if "--pretend" not in emerge.opts and portage.data.secpass < 2:
David Jamesfcb70ef2011-02-02 16:02:30 -08001718 print "parallel_emerge: superuser access is required."
Brian Harring8294d652012-05-23 02:20:52 -07001719 return 1
David Jamesfcb70ef2011-02-02 16:02:30 -08001720
1721 if "--quiet" not in emerge.opts:
1722 cmdline_packages = " ".join(emerge.cmdline_packages)
David Jamesfcb70ef2011-02-02 16:02:30 -08001723 print "Starting fast-emerge."
1724 print " Building package %s on %s" % (cmdline_packages,
1725 deps.board or "root")
David Jamesfcb70ef2011-02-02 16:02:30 -08001726
David James386ccd12011-05-04 20:17:42 -07001727 deps_tree, deps_info = deps.GenDependencyTree()
David Jamesfcb70ef2011-02-02 16:02:30 -08001728
1729 # You want me to be verbose? I'll give you two trees! Twice as much value.
1730 if "--tree" in emerge.opts and "--verbose" in emerge.opts:
1731 deps.PrintTree(deps_tree)
1732
David James386ccd12011-05-04 20:17:42 -07001733 deps_graph = deps.GenDependencyGraph(deps_tree, deps_info)
David Jamesfcb70ef2011-02-02 16:02:30 -08001734
1735 # OK, time to print out our progress so far.
1736 deps.PrintInstallPlan(deps_graph)
1737 if "--tree" in emerge.opts:
1738 PrintDepsMap(deps_graph)
1739
1740 # Are we upgrading portage? If so, and there are more packages to merge,
1741 # schedule a restart of parallel_emerge to merge the rest. This ensures that
1742 # we pick up all updates to portage settings before merging any more
1743 # packages.
1744 portage_upgrade = False
1745 root = emerge.settings["ROOT"]
1746 final_db = emerge.depgraph._dynamic_config.mydbapi[root]
1747 if root == "/":
1748 for db_pkg in final_db.match_pkgs("sys-apps/portage"):
1749 portage_pkg = deps_graph.get(db_pkg.cpv)
David James0ff16f22012-11-02 14:18:07 -07001750 if portage_pkg:
David Jamesfcb70ef2011-02-02 16:02:30 -08001751 portage_upgrade = True
1752 if "--quiet" not in emerge.opts:
1753 print "Upgrading portage first, then restarting..."
1754
David James0ff16f22012-11-02 14:18:07 -07001755 # Upgrade Portage first, then the rest of the packages.
1756 #
1757 # In order to grant the child permission to run setsid, we need to run sudo
1758 # again. We preserve SUDO_USER here in case an ebuild depends on it.
1759 if portage_upgrade:
1760 # Calculate what arguments to use when re-invoking.
1761 args = ["sudo", "-E", "SUDO_USER=%s" % os.environ.get("SUDO_USER", "")]
1762 args += [os.path.abspath(sys.argv[0])] + parallel_emerge_args
1763 args += ["--exclude=sys-apps/portage"]
1764
1765 # First upgrade Portage.
1766 passthrough_args = ("--quiet", "--pretend", "--verbose")
1767 emerge_args = [k for k in emerge.opts if k in passthrough_args]
1768 ret = emerge_main(emerge_args + ["portage"])
1769 if ret != 0:
1770 return ret
1771
1772 # Now upgrade the rest.
1773 os.execvp(args[0], args)
1774
David Jamesfcb70ef2011-02-02 16:02:30 -08001775 # Run the queued emerges.
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001776 scheduler = EmergeQueue(deps_graph, emerge, deps.package_db, deps.show_output,
1777 deps.unpack_only)
Brian Harringa43f5952012-04-12 01:19:34 -07001778 try:
1779 scheduler.Run()
1780 finally:
1781 scheduler._Shutdown()
David James97ce8902011-08-16 09:51:05 -07001782 scheduler = None
David Jamesfcb70ef2011-02-02 16:02:30 -08001783
Mike Frysingerd20a6e22012-10-04 19:01:10 -04001784 clean_logs(emerge.settings)
1785
David Jamesfcb70ef2011-02-02 16:02:30 -08001786 print "Done"
Brian Harring8294d652012-05-23 02:20:52 -07001787 return 0