blob: fc77974875bc08eb57e0f0e900add4eb2e394028 [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.
229 Args:
230 argv: arguments list
231 Returns:
232 Arguments that don't belong to parallel_emerge
233 """
234 emerge_args = []
235 for arg in argv:
236 # Specifically match arguments that are specific to parallel_emerge, and
237 # pass through the rest.
238 if arg.startswith("--board="):
239 self.board = arg.replace("--board=", "")
240 elif arg.startswith("--workon="):
241 workon_str = arg.replace("--workon=", "")
David James7a1ea4b2011-10-13 15:06:41 -0700242 emerge_args.append("--reinstall-atoms=%s" % workon_str)
243 emerge_args.append("--usepkg-exclude=%s" % workon_str)
David Jamesfcb70ef2011-02-02 16:02:30 -0800244 elif arg.startswith("--force-remote-binary="):
245 force_remote_binary = arg.replace("--force-remote-binary=", "")
David James7a1ea4b2011-10-13 15:06:41 -0700246 emerge_args.append("--useoldpkg-atoms=%s" % force_remote_binary)
David Jamesfcb70ef2011-02-02 16:02:30 -0800247 elif arg == "--show-output":
248 self.show_output = True
David James386ccd12011-05-04 20:17:42 -0700249 elif arg == "--rebuild":
David James7a1ea4b2011-10-13 15:06:41 -0700250 emerge_args.append("--rebuild-if-unbuilt")
Thiago Goncalesf4acc422013-07-17 10:26:35 -0700251 elif arg == "--unpackonly":
252 emerge_args.append("--fetchonly")
253 self.unpack_only = True
David Jamesfcb70ef2011-02-02 16:02:30 -0800254 else:
255 # Not one of our options, so pass through to emerge.
256 emerge_args.append(arg)
257
David James386ccd12011-05-04 20:17:42 -0700258 # These packages take a really long time to build, so, for expediency, we
259 # are blacklisting them from automatic rebuilds because one of their
260 # dependencies needs to be recompiled.
261 for pkg in ("chromeos-base/chromeos-chrome", "media-plugins/o3d",
262 "dev-java/icedtea"):
David James7a1ea4b2011-10-13 15:06:41 -0700263 emerge_args.append("--rebuild-exclude=%s" % pkg)
David Jamesfcb70ef2011-02-02 16:02:30 -0800264
265 return emerge_args
266
267 def Initialize(self, args):
268 """Initializer. Parses arguments and sets up portage state."""
269
270 # Parse and strip out args that are just intended for parallel_emerge.
271 emerge_args = self.ParseParallelEmergeArgs(args)
272
273 # Setup various environment variables based on our current board. These
274 # variables are normally setup inside emerge-${BOARD}, but since we don't
275 # call that script, we have to set it up here. These variables serve to
276 # point our tools at /build/BOARD and to setup cross compiles to the
277 # appropriate board as configured in toolchain.conf.
278 if self.board:
279 os.environ["PORTAGE_CONFIGROOT"] = "/build/" + self.board
280 os.environ["PORTAGE_SYSROOT"] = "/build/" + self.board
281 os.environ["SYSROOT"] = "/build/" + self.board
David Jamesfcb70ef2011-02-02 16:02:30 -0800282
283 # Although CHROMEOS_ROOT isn't specific to boards, it's normally setup
284 # inside emerge-${BOARD}, so we set it up here for compatibility. It
285 # will be going away soon as we migrate to CROS_WORKON_SRCROOT.
286 os.environ.setdefault("CHROMEOS_ROOT", os.environ["HOME"] + "/trunk")
287
288 # Turn off interactive delays
289 os.environ["EBEEP_IGNORE"] = "1"
290 os.environ["EPAUSE_IGNORE"] = "1"
Mike Frysinger0a647fc2012-08-06 14:36:05 -0400291 os.environ["CLEAN_DELAY"] = "0"
David Jamesfcb70ef2011-02-02 16:02:30 -0800292
293 # Parse the emerge options.
David Jamesea3ca332011-05-26 11:48:29 -0700294 action, opts, cmdline_packages = parse_opts(emerge_args, silent=True)
David Jamesfcb70ef2011-02-02 16:02:30 -0800295
296 # Set environment variables based on options. Portage normally sets these
297 # environment variables in emerge_main, but we can't use that function,
298 # because it also does a bunch of other stuff that we don't want.
299 # TODO(davidjames): Patch portage to move this logic into a function we can
300 # reuse here.
301 if "--debug" in opts:
302 os.environ["PORTAGE_DEBUG"] = "1"
303 if "--config-root" in opts:
304 os.environ["PORTAGE_CONFIGROOT"] = opts["--config-root"]
305 if "--root" in opts:
306 os.environ["ROOT"] = opts["--root"]
307 if "--accept-properties" in opts:
308 os.environ["ACCEPT_PROPERTIES"] = opts["--accept-properties"]
309
David Jamesfcb70ef2011-02-02 16:02:30 -0800310 # If we're installing packages to the board, and we're not using the
David James927a56d2012-04-03 11:26:39 -0700311 # official flag, we can disable vardb locks. This is safe because we
312 # only run up to one instance of parallel_emerge in parallel.
David Jamesfcb70ef2011-02-02 16:02:30 -0800313 if self.board and os.environ.get("CHROMEOS_OFFICIAL") != "1":
314 os.environ.setdefault("PORTAGE_LOCKS", "false")
David Jamesfcb70ef2011-02-02 16:02:30 -0800315
316 # Now that we've setup the necessary environment variables, we can load the
317 # emerge config from disk.
318 settings, trees, mtimedb = load_emerge_config()
319
David Jamesea3ca332011-05-26 11:48:29 -0700320 # Add in EMERGE_DEFAULT_OPTS, if specified.
321 tmpcmdline = []
322 if "--ignore-default-opts" not in opts:
323 tmpcmdline.extend(settings["EMERGE_DEFAULT_OPTS"].split())
324 tmpcmdline.extend(emerge_args)
325 action, opts, cmdline_packages = parse_opts(tmpcmdline)
326
327 # If we're installing to the board, we want the --root-deps option so that
328 # portage will install the build dependencies to that location as well.
329 if self.board:
330 opts.setdefault("--root-deps", True)
331
David Jamesfcb70ef2011-02-02 16:02:30 -0800332 # Check whether our portage tree is out of date. Typically, this happens
333 # when you're setting up a new portage tree, such as in setup_board and
334 # make_chroot. In that case, portage applies a bunch of global updates
335 # here. Once the updates are finished, we need to commit any changes
336 # that the global update made to our mtimedb, and reload the config.
337 #
338 # Portage normally handles this logic in emerge_main, but again, we can't
339 # use that function here.
340 if _global_updates(trees, mtimedb["updates"]):
341 mtimedb.commit()
342 settings, trees, mtimedb = load_emerge_config(trees=trees)
343
344 # Setup implied options. Portage normally handles this logic in
345 # emerge_main.
346 if "--buildpkgonly" in opts or "buildpkg" in settings.features:
347 opts.setdefault("--buildpkg", True)
348 if "--getbinpkgonly" in opts:
349 opts.setdefault("--usepkgonly", True)
350 opts.setdefault("--getbinpkg", True)
351 if "getbinpkg" in settings.features:
352 # Per emerge_main, FEATURES=getbinpkg overrides --getbinpkg=n
353 opts["--getbinpkg"] = True
354 if "--getbinpkg" in opts or "--usepkgonly" in opts:
355 opts.setdefault("--usepkg", True)
356 if "--fetch-all-uri" in opts:
357 opts.setdefault("--fetchonly", True)
358 if "--skipfirst" in opts:
359 opts.setdefault("--resume", True)
360 if "--buildpkgonly" in opts:
361 # --buildpkgonly will not merge anything, so it overrides all binary
362 # package options.
363 for opt in ("--getbinpkg", "--getbinpkgonly",
364 "--usepkg", "--usepkgonly"):
365 opts.pop(opt, None)
366 if (settings.get("PORTAGE_DEBUG", "") == "1" and
367 "python-trace" in settings.features):
368 portage.debug.set_trace(True)
369
370 # Complain about unsupported options
David James386ccd12011-05-04 20:17:42 -0700371 for opt in ("--ask", "--ask-enter-invalid", "--resume", "--skipfirst"):
David Jamesfcb70ef2011-02-02 16:02:30 -0800372 if opt in opts:
373 print "%s is not supported by parallel_emerge" % opt
374 sys.exit(1)
375
376 # Make emerge specific adjustments to the config (e.g. colors!)
377 adjust_configs(opts, trees)
378
379 # Save our configuration so far in the emerge object
380 emerge = self.emerge
381 emerge.action, emerge.opts = action, opts
382 emerge.settings, emerge.trees, emerge.mtimedb = settings, trees, mtimedb
383 emerge.cmdline_packages = cmdline_packages
384 root = settings["ROOT"]
385 emerge.root_config = trees[root]["root_config"]
386
David James386ccd12011-05-04 20:17:42 -0700387 if "--usepkg" in opts:
David Jamesfcb70ef2011-02-02 16:02:30 -0800388 emerge.trees[root]["bintree"].populate("--getbinpkg" in opts)
389
David Jamesfcb70ef2011-02-02 16:02:30 -0800390 def CreateDepgraph(self, emerge, packages):
391 """Create an emerge depgraph object."""
392 # Setup emerge options.
393 emerge_opts = emerge.opts.copy()
394
David James386ccd12011-05-04 20:17:42 -0700395 # Ask portage to build a dependency graph. with the options we specified
396 # above.
David Jamesfcb70ef2011-02-02 16:02:30 -0800397 params = create_depgraph_params(emerge_opts, emerge.action)
David Jamesbf1e3442011-05-28 07:44:20 -0700398 success, depgraph, favorites = backtrack_depgraph(
David James386ccd12011-05-04 20:17:42 -0700399 emerge.settings, emerge.trees, emerge_opts, params, emerge.action,
400 packages, emerge.spinner)
401 emerge.depgraph = depgraph
David Jamesfcb70ef2011-02-02 16:02:30 -0800402
David James386ccd12011-05-04 20:17:42 -0700403 # Is it impossible to honor the user's request? Bail!
404 if not success:
405 depgraph.display_problems()
406 sys.exit(1)
David Jamesfcb70ef2011-02-02 16:02:30 -0800407
408 emerge.depgraph = depgraph
David Jamesbf1e3442011-05-28 07:44:20 -0700409 emerge.favorites = favorites
David Jamesfcb70ef2011-02-02 16:02:30 -0800410
David Jamesdeebd692011-05-09 17:02:52 -0700411 # Prime and flush emerge caches.
412 root = emerge.settings["ROOT"]
413 vardb = emerge.trees[root]["vartree"].dbapi
David James0bdc5de2011-05-12 16:22:26 -0700414 if "--pretend" not in emerge.opts:
415 vardb.counter_tick()
David Jamesdeebd692011-05-09 17:02:52 -0700416 vardb.flush_cache()
417
David James386ccd12011-05-04 20:17:42 -0700418 def GenDependencyTree(self):
David Jamesfcb70ef2011-02-02 16:02:30 -0800419 """Get dependency tree info from emerge.
420
David Jamesfcb70ef2011-02-02 16:02:30 -0800421 Returns:
422 Dependency tree
423 """
424 start = time.time()
425
426 emerge = self.emerge
427
428 # Create a list of packages to merge
429 packages = set(emerge.cmdline_packages[:])
David Jamesfcb70ef2011-02-02 16:02:30 -0800430
431 # Tell emerge to be quiet. We print plenty of info ourselves so we don't
432 # need any extra output from portage.
433 portage.util.noiselimit = -1
434
435 # My favorite feature: The silent spinner. It doesn't spin. Ever.
436 # I'd disable the colors by default too, but they look kind of cool.
437 emerge.spinner = stdout_spinner()
438 emerge.spinner.update = emerge.spinner.update_quiet
439
440 if "--quiet" not in emerge.opts:
441 print "Calculating deps..."
442
443 self.CreateDepgraph(emerge, packages)
444 depgraph = emerge.depgraph
445
446 # Build our own tree from the emerge digraph.
447 deps_tree = {}
448 digraph = depgraph._dynamic_config.digraph
David James3f778802011-08-25 19:31:45 -0700449 root = emerge.settings["ROOT"]
450 final_db = depgraph._dynamic_config.mydbapi[root]
David Jamesfcb70ef2011-02-02 16:02:30 -0800451 for node, node_deps in digraph.nodes.items():
452 # Calculate dependency packages that need to be installed first. Each
453 # child on the digraph is a dependency. The "operation" field specifies
454 # what we're doing (e.g. merge, uninstall, etc.). The "priorities" array
455 # contains the type of dependency (e.g. build, runtime, runtime_post,
456 # etc.)
457 #
David Jamesfcb70ef2011-02-02 16:02:30 -0800458 # Portage refers to the identifiers for packages as a CPV. This acronym
459 # stands for Component/Path/Version.
460 #
461 # Here's an example CPV: chromeos-base/power_manager-0.0.1-r1
462 # Split up, this CPV would be:
463 # C -- Component: chromeos-base
464 # P -- Path: power_manager
465 # V -- Version: 0.0.1-r1
466 #
467 # We just refer to CPVs as packages here because it's easier.
468 deps = {}
469 for child, priorities in node_deps[0].items():
David James3f778802011-08-25 19:31:45 -0700470 if isinstance(child, Package) and child.root == root:
471 cpv = str(child.cpv)
472 action = str(child.operation)
473
474 # If we're uninstalling a package, check whether Portage is
475 # installing a replacement. If so, just depend on the installation
476 # of the new package, because the old package will automatically
477 # be uninstalled at that time.
478 if action == "uninstall":
479 for pkg in final_db.match_pkgs(child.slot_atom):
480 cpv = str(pkg.cpv)
481 action = "merge"
482 break
483
484 deps[cpv] = dict(action=action,
485 deptypes=[str(x) for x in priorities],
486 deps={})
David Jamesfcb70ef2011-02-02 16:02:30 -0800487
488 # We've built our list of deps, so we can add our package to the tree.
David James3f778802011-08-25 19:31:45 -0700489 if isinstance(node, Package) and node.root == root:
David Jamesfcb70ef2011-02-02 16:02:30 -0800490 deps_tree[str(node.cpv)] = dict(action=str(node.operation),
491 deps=deps)
492
David Jamesfcb70ef2011-02-02 16:02:30 -0800493 # Ask portage for its install plan, so that we can only throw out
David James386ccd12011-05-04 20:17:42 -0700494 # dependencies that portage throws out.
David Jamesfcb70ef2011-02-02 16:02:30 -0800495 deps_info = {}
496 for pkg in depgraph.altlist():
497 if isinstance(pkg, Package):
David James3f778802011-08-25 19:31:45 -0700498 assert pkg.root == root
David Jamesfcb70ef2011-02-02 16:02:30 -0800499 self.package_db[pkg.cpv] = pkg
500
David Jamesfcb70ef2011-02-02 16:02:30 -0800501 # Save off info about the package
David James386ccd12011-05-04 20:17:42 -0700502 deps_info[str(pkg.cpv)] = {"idx": len(deps_info)}
David Jamesfcb70ef2011-02-02 16:02:30 -0800503
504 seconds = time.time() - start
505 if "--quiet" not in emerge.opts:
506 print "Deps calculated in %dm%.1fs" % (seconds / 60, seconds % 60)
507
508 return deps_tree, deps_info
509
510 def PrintTree(self, deps, depth=""):
511 """Print the deps we have seen in the emerge output.
512
513 Args:
514 deps: Dependency tree structure.
515 depth: Allows printing the tree recursively, with indentation.
516 """
517 for entry in sorted(deps):
518 action = deps[entry]["action"]
519 print "%s %s (%s)" % (depth, entry, action)
520 self.PrintTree(deps[entry]["deps"], depth=depth + " ")
521
David James386ccd12011-05-04 20:17:42 -0700522 def GenDependencyGraph(self, deps_tree, deps_info):
David Jamesfcb70ef2011-02-02 16:02:30 -0800523 """Generate a doubly linked dependency graph.
524
525 Args:
526 deps_tree: Dependency tree structure.
527 deps_info: More details on the dependencies.
528 Returns:
529 Deps graph in the form of a dict of packages, with each package
530 specifying a "needs" list and "provides" list.
531 """
532 emerge = self.emerge
David Jamesfcb70ef2011-02-02 16:02:30 -0800533
David Jamesfcb70ef2011-02-02 16:02:30 -0800534 # deps_map is the actual dependency graph.
535 #
536 # Each package specifies a "needs" list and a "provides" list. The "needs"
537 # list indicates which packages we depend on. The "provides" list
538 # indicates the reverse dependencies -- what packages need us.
539 #
540 # We also provide some other information in the dependency graph:
541 # - action: What we're planning on doing with this package. Generally,
542 # "merge", "nomerge", or "uninstall"
David Jamesfcb70ef2011-02-02 16:02:30 -0800543 deps_map = {}
544
545 def ReverseTree(packages):
546 """Convert tree to digraph.
547
548 Take the tree of package -> requirements and reverse it to a digraph of
549 buildable packages -> packages they unblock.
550 Args:
551 packages: Tree(s) of dependencies.
552 Returns:
553 Unsanitized digraph.
554 """
David James8c7e5e32011-06-28 11:26:03 -0700555 binpkg_phases = set(["setup", "preinst", "postinst"])
David James3f778802011-08-25 19:31:45 -0700556 needed_dep_types = set(["blocker", "buildtime", "runtime"])
David Jamesfcb70ef2011-02-02 16:02:30 -0800557 for pkg in packages:
558
559 # Create an entry for the package
560 action = packages[pkg]["action"]
David James8c7e5e32011-06-28 11:26:03 -0700561 default_pkg = {"needs": {}, "provides": set(), "action": action,
562 "nodeps": False, "binary": False}
David Jamesfcb70ef2011-02-02 16:02:30 -0800563 this_pkg = deps_map.setdefault(pkg, default_pkg)
564
David James8c7e5e32011-06-28 11:26:03 -0700565 if pkg in deps_info:
566 this_pkg["idx"] = deps_info[pkg]["idx"]
567
568 # If a package doesn't have any defined phases that might use the
569 # dependent packages (i.e. pkg_setup, pkg_preinst, or pkg_postinst),
570 # we can install this package before its deps are ready.
571 emerge_pkg = self.package_db.get(pkg)
572 if emerge_pkg and emerge_pkg.type_name == "binary":
573 this_pkg["binary"] = True
Mike Frysinger91d7da92013-02-19 15:53:46 -0500574 if 0 <= vercmp(portage.VERSION, "2.1.11.50"):
575 defined_phases = emerge_pkg.defined_phases
576 else:
577 defined_phases = emerge_pkg.metadata.defined_phases
David James8c7e5e32011-06-28 11:26:03 -0700578 defined_binpkg_phases = binpkg_phases.intersection(defined_phases)
579 if not defined_binpkg_phases:
580 this_pkg["nodeps"] = True
581
David Jamesfcb70ef2011-02-02 16:02:30 -0800582 # Create entries for dependencies of this package first.
583 ReverseTree(packages[pkg]["deps"])
584
585 # Add dependencies to this package.
586 for dep, dep_item in packages[pkg]["deps"].iteritems():
David James8c7e5e32011-06-28 11:26:03 -0700587 # We only need to enforce strict ordering of dependencies if the
David James3f778802011-08-25 19:31:45 -0700588 # dependency is a blocker, or is a buildtime or runtime dependency.
589 # (I.e., ignored, optional, and runtime_post dependencies don't
590 # depend on ordering.)
David James8c7e5e32011-06-28 11:26:03 -0700591 dep_types = dep_item["deptypes"]
592 if needed_dep_types.intersection(dep_types):
593 deps_map[dep]["provides"].add(pkg)
594 this_pkg["needs"][dep] = "/".join(dep_types)
David Jamesfcb70ef2011-02-02 16:02:30 -0800595
David James3f778802011-08-25 19:31:45 -0700596 # If there's a blocker, Portage may need to move files from one
597 # package to another, which requires editing the CONTENTS files of
598 # both packages. To avoid race conditions while editing this file,
599 # the two packages must not be installed in parallel, so we can't
600 # safely ignore dependencies. See http://crosbug.com/19328
601 if "blocker" in dep_types:
602 this_pkg["nodeps"] = False
603
David Jamesfcb70ef2011-02-02 16:02:30 -0800604 def FindCycles():
605 """Find cycles in the dependency tree.
606
607 Returns:
608 A dict mapping cyclic packages to a dict of the deps that cause
609 cycles. For each dep that causes cycles, it returns an example
610 traversal of the graph that shows the cycle.
611 """
612
613 def FindCyclesAtNode(pkg, cycles, unresolved, resolved):
614 """Find cycles in cyclic dependencies starting at specified package.
615
616 Args:
617 pkg: Package identifier.
618 cycles: A dict mapping cyclic packages to a dict of the deps that
619 cause cycles. For each dep that causes cycles, it returns an
620 example traversal of the graph that shows the cycle.
621 unresolved: Nodes that have been visited but are not fully processed.
622 resolved: Nodes that have been visited and are fully processed.
623 """
624 pkg_cycles = cycles.get(pkg)
625 if pkg in resolved and not pkg_cycles:
626 # If we already looked at this package, and found no cyclic
627 # dependencies, we can stop now.
628 return
629 unresolved.append(pkg)
630 for dep in deps_map[pkg]["needs"]:
631 if dep in unresolved:
632 idx = unresolved.index(dep)
633 mycycle = unresolved[idx:] + [dep]
David James321490a2012-12-17 12:05:56 -0800634 for i in xrange(len(mycycle) - 1):
David Jamesfcb70ef2011-02-02 16:02:30 -0800635 pkg1, pkg2 = mycycle[i], mycycle[i+1]
636 cycles.setdefault(pkg1, {}).setdefault(pkg2, mycycle)
637 elif not pkg_cycles or dep not in pkg_cycles:
638 # Looks like we haven't seen this edge before.
639 FindCyclesAtNode(dep, cycles, unresolved, resolved)
640 unresolved.pop()
641 resolved.add(pkg)
642
643 cycles, unresolved, resolved = {}, [], set()
644 for pkg in deps_map:
645 FindCyclesAtNode(pkg, cycles, unresolved, resolved)
646 return cycles
647
David James386ccd12011-05-04 20:17:42 -0700648 def RemoveUnusedPackages():
David Jamesfcb70ef2011-02-02 16:02:30 -0800649 """Remove installed packages, propagating dependencies."""
David Jamesfcb70ef2011-02-02 16:02:30 -0800650 # Schedule packages that aren't on the install list for removal
651 rm_pkgs = set(deps_map.keys()) - set(deps_info.keys())
652
David Jamesfcb70ef2011-02-02 16:02:30 -0800653 # Remove the packages we don't want, simplifying the graph and making
654 # it easier for us to crack cycles.
655 for pkg in sorted(rm_pkgs):
656 this_pkg = deps_map[pkg]
657 needs = this_pkg["needs"]
658 provides = this_pkg["provides"]
659 for dep in needs:
660 dep_provides = deps_map[dep]["provides"]
661 dep_provides.update(provides)
662 dep_provides.discard(pkg)
663 dep_provides.discard(dep)
664 for target in provides:
665 target_needs = deps_map[target]["needs"]
666 target_needs.update(needs)
667 target_needs.pop(pkg, None)
668 target_needs.pop(target, None)
669 del deps_map[pkg]
670
671 def PrintCycleBreak(basedep, dep, mycycle):
672 """Print details about a cycle that we are planning on breaking.
673
Mike Frysinger02e1e072013-11-10 22:11:34 -0500674 We are breaking a cycle where dep needs basedep. mycycle is an
675 example cycle which contains dep -> basedep.
676 """
David Jamesfcb70ef2011-02-02 16:02:30 -0800677
David Jamesfcb70ef2011-02-02 16:02:30 -0800678 needs = deps_map[dep]["needs"]
679 depinfo = needs.get(basedep, "deleted")
David Jamesfcb70ef2011-02-02 16:02:30 -0800680
David James3f778802011-08-25 19:31:45 -0700681 # It's OK to swap install order for blockers, as long as the two
682 # packages aren't installed in parallel. If there is a cycle, then
683 # we know the packages depend on each other already, so we can drop the
684 # blocker safely without printing a warning.
685 if depinfo == "blocker":
686 return
687
David Jamesfcb70ef2011-02-02 16:02:30 -0800688 # Notify the user that we're breaking a cycle.
689 print "Breaking %s -> %s (%s)" % (dep, basedep, depinfo)
690
691 # Show cycle.
David James321490a2012-12-17 12:05:56 -0800692 for i in xrange(len(mycycle) - 1):
David Jamesfcb70ef2011-02-02 16:02:30 -0800693 pkg1, pkg2 = mycycle[i], mycycle[i+1]
694 needs = deps_map[pkg1]["needs"]
695 depinfo = needs.get(pkg2, "deleted")
696 if pkg1 == dep and pkg2 == basedep:
697 depinfo = depinfo + ", deleting"
698 print " %s -> %s (%s)" % (pkg1, pkg2, depinfo)
699
700 def SanitizeTree():
701 """Remove circular dependencies.
702
703 We prune all dependencies involved in cycles that go against the emerge
704 ordering. This has a nice property: we're guaranteed to merge
705 dependencies in the same order that portage does.
706
707 Because we don't treat any dependencies as "soft" unless they're killed
708 by a cycle, we pay attention to a larger number of dependencies when
709 merging. This hurts performance a bit, but helps reliability.
710 """
711 start = time.time()
712 cycles = FindCycles()
713 while cycles:
714 for dep, mycycles in cycles.iteritems():
715 for basedep, mycycle in mycycles.iteritems():
716 if deps_info[basedep]["idx"] >= deps_info[dep]["idx"]:
Matt Tennant08797302011-10-17 16:18:45 -0700717 if "--quiet" not in emerge.opts:
718 PrintCycleBreak(basedep, dep, mycycle)
David Jamesfcb70ef2011-02-02 16:02:30 -0800719 del deps_map[dep]["needs"][basedep]
720 deps_map[basedep]["provides"].remove(dep)
721 cycles = FindCycles()
722 seconds = time.time() - start
723 if "--quiet" not in emerge.opts and seconds >= 0.1:
724 print "Tree sanitized in %dm%.1fs" % (seconds / 60, seconds % 60)
725
David James8c7e5e32011-06-28 11:26:03 -0700726 def FindRecursiveProvides(pkg, seen):
727 """Find all nodes that require a particular package.
728
729 Assumes that graph is acyclic.
730
731 Args:
732 pkg: Package identifier.
733 seen: Nodes that have been visited so far.
734 """
735 if pkg in seen:
736 return
737 seen.add(pkg)
738 info = deps_map[pkg]
739 info["tprovides"] = info["provides"].copy()
740 for dep in info["provides"]:
741 FindRecursiveProvides(dep, seen)
742 info["tprovides"].update(deps_map[dep]["tprovides"])
743
David Jamesa22906f2011-05-04 19:53:26 -0700744 ReverseTree(deps_tree)
David Jamesa22906f2011-05-04 19:53:26 -0700745
David James386ccd12011-05-04 20:17:42 -0700746 # We need to remove unused packages so that we can use the dependency
747 # ordering of the install process to show us what cycles to crack.
748 RemoveUnusedPackages()
David Jamesfcb70ef2011-02-02 16:02:30 -0800749 SanitizeTree()
David James8c7e5e32011-06-28 11:26:03 -0700750 seen = set()
751 for pkg in deps_map:
752 FindRecursiveProvides(pkg, seen)
David Jamesfcb70ef2011-02-02 16:02:30 -0800753 return deps_map
754
755 def PrintInstallPlan(self, deps_map):
756 """Print an emerge-style install plan.
757
758 The install plan lists what packages we're installing, in order.
759 It's useful for understanding what parallel_emerge is doing.
760
761 Args:
762 deps_map: The dependency graph.
763 """
764
765 def InstallPlanAtNode(target, deps_map):
766 nodes = []
767 nodes.append(target)
768 for dep in deps_map[target]["provides"]:
769 del deps_map[dep]["needs"][target]
770 if not deps_map[dep]["needs"]:
771 nodes.extend(InstallPlanAtNode(dep, deps_map))
772 return nodes
773
774 deps_map = copy.deepcopy(deps_map)
775 install_plan = []
776 plan = set()
777 for target, info in deps_map.iteritems():
778 if not info["needs"] and target not in plan:
779 for item in InstallPlanAtNode(target, deps_map):
780 plan.add(item)
781 install_plan.append(self.package_db[item])
782
783 for pkg in plan:
784 del deps_map[pkg]
785
786 if deps_map:
787 print "Cyclic dependencies:", " ".join(deps_map)
788 PrintDepsMap(deps_map)
789 sys.exit(1)
790
791 self.emerge.depgraph.display(install_plan)
792
793
794def PrintDepsMap(deps_map):
795 """Print dependency graph, for each package list it's prerequisites."""
796 for i in sorted(deps_map):
797 print "%s: (%s) needs" % (i, deps_map[i]["action"])
798 needs = deps_map[i]["needs"]
799 for j in sorted(needs):
800 print " %s" % (j)
801 if not needs:
802 print " no dependencies"
803
804
805class EmergeJobState(object):
806 __slots__ = ["done", "filename", "last_notify_timestamp", "last_output_seek",
807 "last_output_timestamp", "pkgname", "retcode", "start_timestamp",
Thiago Goncalesf4acc422013-07-17 10:26:35 -0700808 "target", "fetch_only", "unpack_only"]
David Jamesfcb70ef2011-02-02 16:02:30 -0800809
810 def __init__(self, target, pkgname, done, filename, start_timestamp,
Thiago Goncalesf4acc422013-07-17 10:26:35 -0700811 retcode=None, fetch_only=False, unpack_only=False):
David Jamesfcb70ef2011-02-02 16:02:30 -0800812
813 # The full name of the target we're building (e.g.
814 # chromeos-base/chromeos-0.0.1-r60)
815 self.target = target
816
817 # The short name of the target we're building (e.g. chromeos-0.0.1-r60)
818 self.pkgname = pkgname
819
820 # Whether the job is done. (True if the job is done; false otherwise.)
821 self.done = done
822
823 # The filename where output is currently stored.
824 self.filename = filename
825
826 # The timestamp of the last time we printed the name of the log file. We
827 # print this at the beginning of the job, so this starts at
828 # start_timestamp.
829 self.last_notify_timestamp = start_timestamp
830
831 # The location (in bytes) of the end of the last complete line we printed.
832 # This starts off at zero. We use this to jump to the right place when we
833 # print output from the same ebuild multiple times.
834 self.last_output_seek = 0
835
836 # The timestamp of the last time we printed output. Since we haven't
837 # printed output yet, this starts at zero.
838 self.last_output_timestamp = 0
839
840 # The return code of our job, if the job is actually finished.
841 self.retcode = retcode
842
Brian Harring0be85c62012-03-17 19:52:12 -0700843 # Was this just a fetch job?
844 self.fetch_only = fetch_only
845
David Jamesfcb70ef2011-02-02 16:02:30 -0800846 # The timestamp when our job started.
847 self.start_timestamp = start_timestamp
848
Thiago Goncalesf4acc422013-07-17 10:26:35 -0700849 # No emerge, only unpack packages.
850 self.unpack_only = unpack_only
851
David Jamesfcb70ef2011-02-02 16:02:30 -0800852
David James321490a2012-12-17 12:05:56 -0800853def KillHandler(_signum, _frame):
David James7358d032011-05-19 10:40:03 -0700854 # Kill self and all subprocesses.
855 os.killpg(0, signal.SIGKILL)
856
David Jamesfcb70ef2011-02-02 16:02:30 -0800857def SetupWorkerSignals():
David James321490a2012-12-17 12:05:56 -0800858 def ExitHandler(_signum, _frame):
David James7358d032011-05-19 10:40:03 -0700859 # Set KILLED flag.
860 KILLED.set()
David James13cead42011-05-18 16:22:01 -0700861
David James7358d032011-05-19 10:40:03 -0700862 # Remove our signal handlers so we don't get called recursively.
863 signal.signal(signal.SIGINT, KillHandler)
864 signal.signal(signal.SIGTERM, KillHandler)
David Jamesfcb70ef2011-02-02 16:02:30 -0800865
866 # Ensure that we exit quietly and cleanly, if possible, when we receive
867 # SIGTERM or SIGINT signals. By default, when the user hits CTRL-C, all
868 # of the child processes will print details about KeyboardInterrupt
869 # exceptions, which isn't very helpful.
870 signal.signal(signal.SIGINT, ExitHandler)
871 signal.signal(signal.SIGTERM, ExitHandler)
872
David James6b29d052012-11-02 10:27:27 -0700873def EmergeProcess(output, *args, **kwargs):
David James1ed3e252011-10-05 20:26:15 -0700874 """Merge a package in a subprocess.
875
876 Args:
David James1ed3e252011-10-05 20:26:15 -0700877 output: Temporary file to write output.
David James6b29d052012-11-02 10:27:27 -0700878 *args: Arguments to pass to Scheduler constructor.
879 **kwargs: Keyword arguments to pass to Scheduler constructor.
David James1ed3e252011-10-05 20:26:15 -0700880
881 Returns:
882 The exit code returned by the subprocess.
883 """
884 pid = os.fork()
885 if pid == 0:
886 try:
887 # Sanity checks.
Mike Frysingerf02736e2013-11-08 15:27:00 -0500888 if sys.stdout.fileno() != 1:
889 raise Exception("sys.stdout.fileno() != 1")
890 if sys.stderr.fileno() != 2:
891 raise Exception("sys.stderr.fileno() != 2")
David James1ed3e252011-10-05 20:26:15 -0700892
893 # - Redirect 1 (stdout) and 2 (stderr) at our temporary file.
894 # - Redirect 0 to point at sys.stdin. In this case, sys.stdin
895 # points at a file reading os.devnull, because multiprocessing mucks
896 # with sys.stdin.
897 # - Leave the sys.stdin and output filehandles alone.
898 fd_pipes = {0: sys.stdin.fileno(),
899 1: output.fileno(),
900 2: output.fileno(),
901 sys.stdin.fileno(): sys.stdin.fileno(),
902 output.fileno(): output.fileno()}
David Jamesa249eef2013-07-19 14:03:45 -0700903 if 0 <= vercmp(portage.VERSION, "2.1.11.50"):
904 portage.process._setup_pipes(fd_pipes, close_fds=False)
905 else:
906 portage.process._setup_pipes(fd_pipes)
David James1ed3e252011-10-05 20:26:15 -0700907
908 # Portage doesn't like when sys.stdin.fileno() != 0, so point sys.stdin
909 # at the filehandle we just created in _setup_pipes.
910 if sys.stdin.fileno() != 0:
David James6b29d052012-11-02 10:27:27 -0700911 sys.__stdin__ = sys.stdin = os.fdopen(0, "r")
912
913 scheduler = Scheduler(*args, **kwargs)
914
915 # Enable blocker handling even though we're in --nodeps mode. This
916 # allows us to unmerge the blocker after we've merged the replacement.
917 scheduler._opts_ignore_blockers = frozenset()
David James1ed3e252011-10-05 20:26:15 -0700918
919 # Actually do the merge.
920 retval = scheduler.merge()
921
922 # We catch all exceptions here (including SystemExit, KeyboardInterrupt,
923 # etc) so as to ensure that we don't confuse the multiprocessing module,
924 # which expects that all forked children exit with os._exit().
David James321490a2012-12-17 12:05:56 -0800925 # pylint: disable=W0702
David James1ed3e252011-10-05 20:26:15 -0700926 except:
927 traceback.print_exc(file=output)
928 retval = 1
929 sys.stdout.flush()
930 sys.stderr.flush()
931 output.flush()
932 os._exit(retval)
933 else:
934 # Return the exit code of the subprocess.
935 return os.waitpid(pid, 0)[1]
David Jamesfcb70ef2011-02-02 16:02:30 -0800936
Thiago Goncalesf4acc422013-07-17 10:26:35 -0700937
938def UnpackPackage(pkg_state):
939 """Unpacks package described by pkg_state.
940
941 Args:
942 pkg_state: EmergeJobState object describing target.
943
944 Returns:
945 Exit code returned by subprocess.
946 """
947 pkgdir = os.environ.get("PKGDIR",
948 os.path.join(os.environ["SYSROOT"], "packages"))
949 root = os.environ.get("ROOT", os.environ["SYSROOT"])
950 path = os.path.join(pkgdir, pkg_state.target + ".tbz2")
951 comp = cros_build_lib.FindCompressor(cros_build_lib.COMP_BZIP2)
952 cmd = [comp, "-dc"]
953 if comp.endswith("pbzip2"):
954 cmd.append("--ignore-trailing-garbage=1")
955 cmd.append(path)
956
957 result = cros_build_lib.RunCommand(cmd, cwd=root, stdout_to_pipe=True,
958 print_cmd=False, error_code_ok=True)
959
960 # If we were not successful, return now and don't attempt untar.
961 if result.returncode:
962 return result.returncode
963
964 cmd = ["sudo", "tar", "-xf", "-", "-C", root]
965 result = cros_build_lib.RunCommand(cmd, cwd=root, input=result.output,
966 print_cmd=False, error_code_ok=True)
967
968 return result.returncode
969
970
971def EmergeWorker(task_queue, job_queue, emerge, package_db, fetch_only=False,
972 unpack_only=False):
David Jamesfcb70ef2011-02-02 16:02:30 -0800973 """This worker emerges any packages given to it on the task_queue.
974
975 Args:
976 task_queue: The queue of tasks for this worker to do.
977 job_queue: The queue of results from the worker.
978 emerge: An EmergeData() object.
979 package_db: A dict, mapping package ids to portage Package objects.
Brian Harring0be85c62012-03-17 19:52:12 -0700980 fetch_only: A bool, indicating if we should just fetch the target.
Thiago Goncalesf4acc422013-07-17 10:26:35 -0700981 unpack_only: A bool, indicating if we should just unpack the target.
David Jamesfcb70ef2011-02-02 16:02:30 -0800982
983 It expects package identifiers to be passed to it via task_queue. When
984 a task is started, it pushes the (target, filename) to the started_queue.
985 The output is stored in filename. When a merge starts or finishes, we push
986 EmergeJobState objects to the job_queue.
987 """
988
989 SetupWorkerSignals()
990 settings, trees, mtimedb = emerge.settings, emerge.trees, emerge.mtimedb
David Jamesdeebd692011-05-09 17:02:52 -0700991
992 # Disable flushing of caches to save on I/O.
David James7a1ea4b2011-10-13 15:06:41 -0700993 root = emerge.settings["ROOT"]
994 vardb = emerge.trees[root]["vartree"].dbapi
995 vardb._flush_cache_enabled = False
Brian Harring0be85c62012-03-17 19:52:12 -0700996 bindb = emerge.trees[root]["bintree"].dbapi
997 # Might be a set, might be a list, might be None; no clue, just use shallow
998 # copy to ensure we can roll it back.
999 original_remotepkgs = copy.copy(bindb.bintree._remotepkgs)
David Jamesdeebd692011-05-09 17:02:52 -07001000
David Jamesfcb70ef2011-02-02 16:02:30 -08001001 opts, spinner = emerge.opts, emerge.spinner
1002 opts["--nodeps"] = True
Brian Harring0be85c62012-03-17 19:52:12 -07001003 if fetch_only:
1004 opts["--fetchonly"] = True
1005
David Jamesfcb70ef2011-02-02 16:02:30 -08001006 while True:
1007 # Wait for a new item to show up on the queue. This is a blocking wait,
1008 # so if there's nothing to do, we just sit here.
Brian Harring0be85c62012-03-17 19:52:12 -07001009 pkg_state = task_queue.get()
1010 if pkg_state is None:
David Jamesfcb70ef2011-02-02 16:02:30 -08001011 # If target is None, this means that the main thread wants us to quit.
1012 # The other workers need to exit too, so we'll push the message back on
1013 # to the queue so they'll get it too.
Brian Harring0be85c62012-03-17 19:52:12 -07001014 task_queue.put(None)
David Jamesfcb70ef2011-02-02 16:02:30 -08001015 return
David James7358d032011-05-19 10:40:03 -07001016 if KILLED.is_set():
1017 return
1018
Brian Harring0be85c62012-03-17 19:52:12 -07001019 target = pkg_state.target
1020
David Jamesfcb70ef2011-02-02 16:02:30 -08001021 db_pkg = package_db[target]
Brian Harring0be85c62012-03-17 19:52:12 -07001022
1023 if db_pkg.type_name == "binary":
1024 if not fetch_only and pkg_state.fetched_successfully:
1025 # Ensure portage doesn't think our pkg is remote- else it'll force
1026 # a redownload of it (even if the on-disk file is fine). In-memory
1027 # caching basically, implemented dumbly.
1028 bindb.bintree._remotepkgs = None
1029 else:
1030 bindb.bintree_remotepkgs = original_remotepkgs
1031
David Jamesfcb70ef2011-02-02 16:02:30 -08001032 db_pkg.root_config = emerge.root_config
1033 install_list = [db_pkg]
1034 pkgname = db_pkg.pf
1035 output = tempfile.NamedTemporaryFile(prefix=pkgname + "-", delete=False)
David James01b1e0f2012-06-07 17:18:05 -07001036 os.chmod(output.name, 644)
David Jamesfcb70ef2011-02-02 16:02:30 -08001037 start_timestamp = time.time()
Brian Harring0be85c62012-03-17 19:52:12 -07001038 job = EmergeJobState(target, pkgname, False, output.name, start_timestamp,
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001039 fetch_only=fetch_only, unpack_only=unpack_only)
David Jamesfcb70ef2011-02-02 16:02:30 -08001040 job_queue.put(job)
1041 if "--pretend" in opts:
1042 retcode = 0
1043 else:
David Jamesfcb70ef2011-02-02 16:02:30 -08001044 try:
David James386ccd12011-05-04 20:17:42 -07001045 emerge.scheduler_graph.mergelist = install_list
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001046 if unpack_only:
1047 retcode = UnpackPackage(pkg_state)
1048 else:
1049 retcode = EmergeProcess(output, settings, trees, mtimedb, opts,
1050 spinner, favorites=emerge.favorites,
1051 graph_config=emerge.scheduler_graph)
David Jamesfcb70ef2011-02-02 16:02:30 -08001052 except Exception:
1053 traceback.print_exc(file=output)
1054 retcode = 1
David James1ed3e252011-10-05 20:26:15 -07001055 output.close()
David Jamesfcb70ef2011-02-02 16:02:30 -08001056
David James7358d032011-05-19 10:40:03 -07001057 if KILLED.is_set():
1058 return
1059
David Jamesfcb70ef2011-02-02 16:02:30 -08001060 job = EmergeJobState(target, pkgname, True, output.name, start_timestamp,
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001061 retcode, fetch_only=fetch_only,
1062 unpack_only=unpack_only)
David Jamesfcb70ef2011-02-02 16:02:30 -08001063 job_queue.put(job)
1064
1065
1066class LinePrinter(object):
1067 """Helper object to print a single line."""
1068
1069 def __init__(self, line):
1070 self.line = line
1071
David James321490a2012-12-17 12:05:56 -08001072 def Print(self, _seek_locations):
David Jamesfcb70ef2011-02-02 16:02:30 -08001073 print self.line
1074
1075
1076class JobPrinter(object):
1077 """Helper object to print output of a job."""
1078
1079 def __init__(self, job, unlink=False):
1080 """Print output of job.
1081
Mike Frysinger02e1e072013-11-10 22:11:34 -05001082 If unlink is True, unlink the job output file when done.
1083 """
David Jamesfcb70ef2011-02-02 16:02:30 -08001084 self.current_time = time.time()
1085 self.job = job
1086 self.unlink = unlink
1087
1088 def Print(self, seek_locations):
1089
1090 job = self.job
1091
1092 # Calculate how long the job has been running.
1093 seconds = self.current_time - job.start_timestamp
1094
1095 # Note that we've printed out the job so far.
1096 job.last_output_timestamp = self.current_time
1097
1098 # Note that we're starting the job
1099 info = "job %s (%dm%.1fs)" % (job.pkgname, seconds / 60, seconds % 60)
1100 last_output_seek = seek_locations.get(job.filename, 0)
1101 if last_output_seek:
1102 print "=== Continue output for %s ===" % info
1103 else:
1104 print "=== Start output for %s ===" % info
1105
1106 # Print actual output from job
1107 f = codecs.open(job.filename, encoding='utf-8', errors='replace')
1108 f.seek(last_output_seek)
1109 prefix = job.pkgname + ":"
1110 for line in f:
1111
1112 # Save off our position in the file
1113 if line and line[-1] == "\n":
1114 last_output_seek = f.tell()
1115 line = line[:-1]
1116
1117 # Print our line
1118 print prefix, line.encode('utf-8', 'replace')
1119 f.close()
1120
1121 # Save our last spot in the file so that we don't print out the same
1122 # location twice.
1123 seek_locations[job.filename] = last_output_seek
1124
1125 # Note end of output section
1126 if job.done:
1127 print "=== Complete: %s ===" % info
1128 else:
1129 print "=== Still running: %s ===" % info
1130
1131 if self.unlink:
1132 os.unlink(job.filename)
1133
1134
1135def PrintWorker(queue):
1136 """A worker that prints stuff to the screen as requested."""
1137
David James321490a2012-12-17 12:05:56 -08001138 def ExitHandler(_signum, _frame):
David James7358d032011-05-19 10:40:03 -07001139 # Set KILLED flag.
1140 KILLED.set()
1141
David Jamesfcb70ef2011-02-02 16:02:30 -08001142 # Switch to default signal handlers so that we'll die after two signals.
David James7358d032011-05-19 10:40:03 -07001143 signal.signal(signal.SIGINT, KillHandler)
1144 signal.signal(signal.SIGTERM, KillHandler)
David Jamesfcb70ef2011-02-02 16:02:30 -08001145
1146 # Don't exit on the first SIGINT / SIGTERM, because the parent worker will
1147 # handle it and tell us when we need to exit.
1148 signal.signal(signal.SIGINT, ExitHandler)
1149 signal.signal(signal.SIGTERM, ExitHandler)
1150
1151 # seek_locations is a map indicating the position we are at in each file.
1152 # It starts off empty, but is set by the various Print jobs as we go along
1153 # to indicate where we left off in each file.
1154 seek_locations = {}
1155 while True:
1156 try:
1157 job = queue.get()
1158 if job:
1159 job.Print(seek_locations)
David Jamesbccf8eb2011-07-27 14:06:06 -07001160 sys.stdout.flush()
David Jamesfcb70ef2011-02-02 16:02:30 -08001161 else:
1162 break
1163 except IOError as ex:
1164 if ex.errno == errno.EINTR:
1165 # Looks like we received a signal. Keep printing.
1166 continue
1167 raise
1168
Brian Harring867e2362012-03-17 04:05:17 -07001169
Brian Harring0be85c62012-03-17 19:52:12 -07001170class TargetState(object):
Brian Harring867e2362012-03-17 04:05:17 -07001171
Brian Harring0be85c62012-03-17 19:52:12 -07001172 __slots__ = ("target", "info", "score", "prefetched", "fetched_successfully")
Brian Harring867e2362012-03-17 04:05:17 -07001173
David James321490a2012-12-17 12:05:56 -08001174 def __init__(self, target, info):
Brian Harring867e2362012-03-17 04:05:17 -07001175 self.target, self.info = target, info
Brian Harring0be85c62012-03-17 19:52:12 -07001176 self.fetched_successfully = False
1177 self.prefetched = False
David James321490a2012-12-17 12:05:56 -08001178 self.score = None
Brian Harring867e2362012-03-17 04:05:17 -07001179 self.update_score()
1180
1181 def __cmp__(self, other):
1182 return cmp(self.score, other.score)
1183
1184 def update_score(self):
1185 self.score = (
1186 -len(self.info["tprovides"]),
Brian Harring0be85c62012-03-17 19:52:12 -07001187 len(self.info["needs"]),
Brian Harring11c5eeb2012-03-18 11:02:39 -07001188 not self.info["binary"],
Brian Harring867e2362012-03-17 04:05:17 -07001189 -len(self.info["provides"]),
1190 self.info["idx"],
1191 self.target,
1192 )
1193
1194
1195class ScoredHeap(object):
1196
Brian Harring0be85c62012-03-17 19:52:12 -07001197 __slots__ = ("heap", "_heap_set")
1198
Brian Harring867e2362012-03-17 04:05:17 -07001199 def __init__(self, initial=()):
Brian Harring0be85c62012-03-17 19:52:12 -07001200 self.heap = list()
1201 self._heap_set = set()
1202 if initial:
1203 self.multi_put(initial)
Brian Harring867e2362012-03-17 04:05:17 -07001204
1205 def get(self):
Brian Harring0be85c62012-03-17 19:52:12 -07001206 item = heapq.heappop(self.heap)
1207 self._heap_set.remove(item.target)
1208 return item
Brian Harring867e2362012-03-17 04:05:17 -07001209
Brian Harring0be85c62012-03-17 19:52:12 -07001210 def put(self, item):
1211 if not isinstance(item, TargetState):
1212 raise ValueError("Item %r isn't a TargetState" % (item,))
1213 heapq.heappush(self.heap, item)
1214 self._heap_set.add(item.target)
Brian Harring867e2362012-03-17 04:05:17 -07001215
Brian Harring0be85c62012-03-17 19:52:12 -07001216 def multi_put(self, sequence):
1217 sequence = list(sequence)
1218 self.heap.extend(sequence)
1219 self._heap_set.update(x.target for x in sequence)
Brian Harring867e2362012-03-17 04:05:17 -07001220 self.sort()
1221
David James5c9996d2012-03-24 10:50:46 -07001222 def sort(self):
1223 heapq.heapify(self.heap)
1224
Brian Harring0be85c62012-03-17 19:52:12 -07001225 def __contains__(self, target):
1226 return target in self._heap_set
1227
1228 def __nonzero__(self):
1229 return bool(self.heap)
1230
Brian Harring867e2362012-03-17 04:05:17 -07001231 def __len__(self):
1232 return len(self.heap)
1233
1234
David Jamesfcb70ef2011-02-02 16:02:30 -08001235class EmergeQueue(object):
1236 """Class to schedule emerge jobs according to a dependency graph."""
1237
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001238 def __init__(self, deps_map, emerge, package_db, show_output, unpack_only):
David Jamesfcb70ef2011-02-02 16:02:30 -08001239 # Store the dependency graph.
1240 self._deps_map = deps_map
Brian Harring0be85c62012-03-17 19:52:12 -07001241 self._state_map = {}
David Jamesfcb70ef2011-02-02 16:02:30 -08001242 # Initialize the running queue to empty
Brian Harring0be85c62012-03-17 19:52:12 -07001243 self._build_jobs = {}
1244 self._build_ready = ScoredHeap()
1245 self._fetch_jobs = {}
1246 self._fetch_ready = ScoredHeap()
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001247 self._unpack_jobs = {}
1248 self._unpack_ready = ScoredHeap()
David Jamesfcb70ef2011-02-02 16:02:30 -08001249 # List of total package installs represented in deps_map.
1250 install_jobs = [x for x in deps_map if deps_map[x]["action"] == "merge"]
1251 self._total_jobs = len(install_jobs)
1252 self._show_output = show_output
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001253 self._unpack_only = unpack_only
David Jamesfcb70ef2011-02-02 16:02:30 -08001254
1255 if "--pretend" in emerge.opts:
1256 print "Skipping merge because of --pretend mode."
1257 sys.exit(0)
1258
David James7358d032011-05-19 10:40:03 -07001259 # Set a process group so we can easily terminate all children.
1260 os.setsid()
1261
David Jamesfcb70ef2011-02-02 16:02:30 -08001262 # Setup scheduler graph object. This is used by the child processes
1263 # to help schedule jobs.
1264 emerge.scheduler_graph = emerge.depgraph.schedulerGraph()
1265
1266 # Calculate how many jobs we can run in parallel. We don't want to pass
1267 # the --jobs flag over to emerge itself, because that'll tell emerge to
1268 # hide its output, and said output is quite useful for debugging hung
1269 # jobs.
1270 procs = min(self._total_jobs,
1271 emerge.opts.pop("--jobs", multiprocessing.cpu_count()))
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001272 self._build_procs = self._unpack_procs = self._fetch_procs = max(1, procs)
David James8c7e5e32011-06-28 11:26:03 -07001273 self._load_avg = emerge.opts.pop("--load-average", None)
David Jamesfcb70ef2011-02-02 16:02:30 -08001274 self._job_queue = multiprocessing.Queue()
1275 self._print_queue = multiprocessing.Queue()
Brian Harring0be85c62012-03-17 19:52:12 -07001276
1277 self._fetch_queue = multiprocessing.Queue()
1278 args = (self._fetch_queue, self._job_queue, emerge, package_db, True)
1279 self._fetch_pool = multiprocessing.Pool(self._fetch_procs, EmergeWorker,
1280 args)
1281
1282 self._build_queue = multiprocessing.Queue()
1283 args = (self._build_queue, self._job_queue, emerge, package_db)
1284 self._build_pool = multiprocessing.Pool(self._build_procs, EmergeWorker,
1285 args)
1286
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001287 if self._unpack_only:
1288 # Unpack pool only required on unpack_only jobs.
1289 self._unpack_queue = multiprocessing.Queue()
1290 args = (self._unpack_queue, self._job_queue, emerge, package_db, False,
1291 True)
1292 self._unpack_pool = multiprocessing.Pool(self._unpack_procs, EmergeWorker,
1293 args)
1294
David Jamesfcb70ef2011-02-02 16:02:30 -08001295 self._print_worker = multiprocessing.Process(target=PrintWorker,
1296 args=[self._print_queue])
1297 self._print_worker.start()
1298
1299 # Initialize the failed queue to empty.
1300 self._retry_queue = []
1301 self._failed = set()
1302
David Jamesfcb70ef2011-02-02 16:02:30 -08001303 # Setup an exit handler so that we print nice messages if we are
1304 # terminated.
1305 self._SetupExitHandler()
1306
1307 # Schedule our jobs.
Brian Harring0be85c62012-03-17 19:52:12 -07001308 self._state_map.update(
1309 (pkg, TargetState(pkg, data)) for pkg, data in deps_map.iteritems())
1310 self._fetch_ready.multi_put(self._state_map.itervalues())
David Jamesfcb70ef2011-02-02 16:02:30 -08001311
1312 def _SetupExitHandler(self):
1313
David James321490a2012-12-17 12:05:56 -08001314 def ExitHandler(signum, _frame):
David James7358d032011-05-19 10:40:03 -07001315 # Set KILLED flag.
1316 KILLED.set()
David Jamesfcb70ef2011-02-02 16:02:30 -08001317
1318 # Kill our signal handlers so we don't get called recursively
David James7358d032011-05-19 10:40:03 -07001319 signal.signal(signal.SIGINT, KillHandler)
1320 signal.signal(signal.SIGTERM, KillHandler)
David Jamesfcb70ef2011-02-02 16:02:30 -08001321
1322 # Print our current job status
Brian Harring0be85c62012-03-17 19:52:12 -07001323 for job in self._build_jobs.itervalues():
David Jamesfcb70ef2011-02-02 16:02:30 -08001324 if job:
1325 self._print_queue.put(JobPrinter(job, unlink=True))
1326
1327 # Notify the user that we are exiting
1328 self._Print("Exiting on signal %s" % signum)
David James7358d032011-05-19 10:40:03 -07001329 self._print_queue.put(None)
1330 self._print_worker.join()
David Jamesfcb70ef2011-02-02 16:02:30 -08001331
1332 # Kill child threads, then exit.
David James7358d032011-05-19 10:40:03 -07001333 os.killpg(0, signal.SIGKILL)
David Jamesfcb70ef2011-02-02 16:02:30 -08001334 sys.exit(1)
1335
1336 # Print out job status when we are killed
1337 signal.signal(signal.SIGINT, ExitHandler)
1338 signal.signal(signal.SIGTERM, ExitHandler)
1339
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001340 def _ScheduleUnpack(self, pkg_state):
1341 self._unpack_jobs[pkg_state.target] = None
1342 self._unpack_queue.put(pkg_state)
1343
Brian Harring0be85c62012-03-17 19:52:12 -07001344 def _Schedule(self, pkg_state):
David Jamesfcb70ef2011-02-02 16:02:30 -08001345 # We maintain a tree of all deps, if this doesn't need
David James8c7e5e32011-06-28 11:26:03 -07001346 # to be installed just free up its children and continue.
David Jamesfcb70ef2011-02-02 16:02:30 -08001347 # It is possible to reinstall deps of deps, without reinstalling
1348 # first level deps, like so:
1349 # chromeos (merge) -> eselect (nomerge) -> python (merge)
Brian Harring0be85c62012-03-17 19:52:12 -07001350 this_pkg = pkg_state.info
1351 target = pkg_state.target
1352 if pkg_state.info is not None:
1353 if this_pkg["action"] == "nomerge":
1354 self._Finish(target)
1355 elif target not in self._build_jobs:
1356 # Kick off the build if it's marked to be built.
1357 self._build_jobs[target] = None
1358 self._build_queue.put(pkg_state)
1359 return True
David Jamesfcb70ef2011-02-02 16:02:30 -08001360
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001361 def _ScheduleLoop(self, unpack_only=False):
1362 if unpack_only:
1363 ready_queue = self._unpack_ready
1364 jobs_queue = self._unpack_jobs
1365 procs = self._unpack_procs
1366 else:
1367 ready_queue = self._build_ready
1368 jobs_queue = self._build_jobs
1369 procs = self._build_procs
1370
David James8c7e5e32011-06-28 11:26:03 -07001371 # If the current load exceeds our desired load average, don't schedule
1372 # more than one job.
1373 if self._load_avg and os.getloadavg()[0] > self._load_avg:
1374 needed_jobs = 1
1375 else:
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001376 needed_jobs = procs
David James8c7e5e32011-06-28 11:26:03 -07001377
1378 # Schedule more jobs.
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001379 while ready_queue and len(jobs_queue) < needed_jobs:
1380 state = ready_queue.get()
1381 if unpack_only:
1382 self._ScheduleUnpack(state)
1383 else:
1384 if state.target not in self._failed:
1385 self._Schedule(state)
David Jamesfcb70ef2011-02-02 16:02:30 -08001386
1387 def _Print(self, line):
1388 """Print a single line."""
1389 self._print_queue.put(LinePrinter(line))
1390
1391 def _Status(self):
1392 """Print status."""
1393 current_time = time.time()
1394 no_output = True
1395
1396 # Print interim output every minute if --show-output is used. Otherwise,
1397 # print notifications about running packages every 2 minutes, and print
1398 # full output for jobs that have been running for 60 minutes or more.
1399 if self._show_output:
1400 interval = 60
1401 notify_interval = 0
1402 else:
1403 interval = 60 * 60
1404 notify_interval = 60 * 2
David James321490a2012-12-17 12:05:56 -08001405 for job in self._build_jobs.itervalues():
David Jamesfcb70ef2011-02-02 16:02:30 -08001406 if job:
1407 last_timestamp = max(job.start_timestamp, job.last_output_timestamp)
1408 if last_timestamp + interval < current_time:
1409 self._print_queue.put(JobPrinter(job))
1410 job.last_output_timestamp = current_time
1411 no_output = False
1412 elif (notify_interval and
1413 job.last_notify_timestamp + notify_interval < current_time):
1414 job_seconds = current_time - job.start_timestamp
1415 args = (job.pkgname, job_seconds / 60, job_seconds % 60, job.filename)
1416 info = "Still building %s (%dm%.1fs). Logs in %s" % args
1417 job.last_notify_timestamp = current_time
1418 self._Print(info)
1419 no_output = False
1420
1421 # If we haven't printed any messages yet, print a general status message
1422 # here.
1423 if no_output:
1424 seconds = current_time - GLOBAL_START
Brian Harring0be85c62012-03-17 19:52:12 -07001425 fjobs, fready = len(self._fetch_jobs), len(self._fetch_ready)
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001426 ujobs, uready = len(self._unpack_jobs), len(self._unpack_ready)
Brian Harring0be85c62012-03-17 19:52:12 -07001427 bjobs, bready = len(self._build_jobs), len(self._build_ready)
1428 retries = len(self._retry_queue)
1429 pending = max(0, len(self._deps_map) - fjobs - bjobs)
1430 line = "Pending %s/%s, " % (pending, self._total_jobs)
1431 if fjobs or fready:
1432 line += "Fetching %s/%s, " % (fjobs, fready + fjobs)
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001433 if ujobs or uready:
1434 line += "Unpacking %s/%s, " % (ujobs, uready + ujobs)
Brian Harring0be85c62012-03-17 19:52:12 -07001435 if bjobs or bready or retries:
1436 line += "Building %s/%s, " % (bjobs, bready + bjobs)
1437 if retries:
1438 line += "Retrying %s, " % (retries,)
David James8c7e5e32011-06-28 11:26:03 -07001439 load = " ".join(str(x) for x in os.getloadavg())
Brian Harring0be85c62012-03-17 19:52:12 -07001440 line += ("[Time %dm%.1fs Load %s]" % (seconds/60, seconds %60, load))
1441 self._Print(line)
David Jamesfcb70ef2011-02-02 16:02:30 -08001442
1443 def _Finish(self, target):
David James8c7e5e32011-06-28 11:26:03 -07001444 """Mark a target as completed and unblock dependencies."""
1445 this_pkg = self._deps_map[target]
1446 if this_pkg["needs"] and this_pkg["nodeps"]:
1447 # We got installed, but our deps have not been installed yet. Dependent
1448 # packages should only be installed when our needs have been fully met.
1449 this_pkg["action"] = "nomerge"
1450 else:
David James8c7e5e32011-06-28 11:26:03 -07001451 for dep in this_pkg["provides"]:
1452 dep_pkg = self._deps_map[dep]
Brian Harring0be85c62012-03-17 19:52:12 -07001453 state = self._state_map[dep]
David James8c7e5e32011-06-28 11:26:03 -07001454 del dep_pkg["needs"][target]
Brian Harring0be85c62012-03-17 19:52:12 -07001455 state.update_score()
1456 if not state.prefetched:
1457 if dep in self._fetch_ready:
1458 # If it's not currently being fetched, update the prioritization
1459 self._fetch_ready.sort()
1460 elif not dep_pkg["needs"]:
David James8c7e5e32011-06-28 11:26:03 -07001461 if dep_pkg["nodeps"] and dep_pkg["action"] == "nomerge":
1462 self._Finish(dep)
1463 else:
Brian Harring0be85c62012-03-17 19:52:12 -07001464 self._build_ready.put(self._state_map[dep])
David James8c7e5e32011-06-28 11:26:03 -07001465 self._deps_map.pop(target)
David Jamesfcb70ef2011-02-02 16:02:30 -08001466
1467 def _Retry(self):
David James8c7e5e32011-06-28 11:26:03 -07001468 while self._retry_queue:
Brian Harring0be85c62012-03-17 19:52:12 -07001469 state = self._retry_queue.pop(0)
1470 if self._Schedule(state):
1471 self._Print("Retrying emerge of %s." % state.target)
David James8c7e5e32011-06-28 11:26:03 -07001472 break
David Jamesfcb70ef2011-02-02 16:02:30 -08001473
Brian Harringa43f5952012-04-12 01:19:34 -07001474 def _Shutdown(self):
David Jamesfcb70ef2011-02-02 16:02:30 -08001475 # Tell emerge workers to exit. They all exit when 'None' is pushed
1476 # to the queue.
Brian Harring0be85c62012-03-17 19:52:12 -07001477
Brian Harringa43f5952012-04-12 01:19:34 -07001478 # Shutdown the workers first; then jobs (which is how they feed things back)
1479 # then finally the print queue.
Brian Harring0be85c62012-03-17 19:52:12 -07001480
Brian Harringa43f5952012-04-12 01:19:34 -07001481 def _stop(queue, pool):
1482 if pool is None:
1483 return
1484 try:
1485 queue.put(None)
1486 pool.close()
1487 pool.join()
1488 finally:
1489 pool.terminate()
Brian Harring0be85c62012-03-17 19:52:12 -07001490
Brian Harringa43f5952012-04-12 01:19:34 -07001491 _stop(self._fetch_queue, self._fetch_pool)
1492 self._fetch_queue = self._fetch_pool = None
Brian Harring0be85c62012-03-17 19:52:12 -07001493
Brian Harringa43f5952012-04-12 01:19:34 -07001494 _stop(self._build_queue, self._build_pool)
1495 self._build_queue = self._build_pool = None
1496
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001497 if self._unpack_only:
1498 _stop(self._unpack_queue, self._unpack_pool)
1499 self._unpack_queue = self._unpack_pool = None
1500
Brian Harringa43f5952012-04-12 01:19:34 -07001501 if self._job_queue is not None:
1502 self._job_queue.close()
1503 self._job_queue = None
David Jamesfcb70ef2011-02-02 16:02:30 -08001504
1505 # Now that our workers are finished, we can kill the print queue.
Brian Harringa43f5952012-04-12 01:19:34 -07001506 if self._print_worker is not None:
1507 try:
1508 self._print_queue.put(None)
1509 self._print_queue.close()
1510 self._print_worker.join()
1511 finally:
1512 self._print_worker.terminate()
1513 self._print_queue = self._print_worker = None
David Jamesfcb70ef2011-02-02 16:02:30 -08001514
1515 def Run(self):
1516 """Run through the scheduled ebuilds.
1517
1518 Keep running so long as we have uninstalled packages in the
1519 dependency graph to merge.
1520 """
Brian Harringa43f5952012-04-12 01:19:34 -07001521 if not self._deps_map:
1522 return
1523
Brian Harring0be85c62012-03-17 19:52:12 -07001524 # Start the fetchers.
1525 for _ in xrange(min(self._fetch_procs, len(self._fetch_ready))):
1526 state = self._fetch_ready.get()
1527 self._fetch_jobs[state.target] = None
1528 self._fetch_queue.put(state)
1529
1530 # Print an update, then get going.
1531 self._Status()
1532
David Jamese703d0f2012-01-12 16:27:45 -08001533 retried = set()
David Jamesfcb70ef2011-02-02 16:02:30 -08001534 while self._deps_map:
1535 # Check here that we are actually waiting for something.
Brian Harring0be85c62012-03-17 19:52:12 -07001536 if (self._build_queue.empty() and
David Jamesfcb70ef2011-02-02 16:02:30 -08001537 self._job_queue.empty() and
Brian Harring0be85c62012-03-17 19:52:12 -07001538 not self._fetch_jobs and
1539 not self._fetch_ready and
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001540 not self._unpack_jobs and
1541 not self._unpack_ready and
Brian Harring0be85c62012-03-17 19:52:12 -07001542 not self._build_jobs and
1543 not self._build_ready and
David Jamesfcb70ef2011-02-02 16:02:30 -08001544 self._deps_map):
1545 # If we have failed on a package, retry it now.
1546 if self._retry_queue:
1547 self._Retry()
1548 else:
David Jamesfcb70ef2011-02-02 16:02:30 -08001549 # Tell the user why we're exiting.
1550 if self._failed:
Mike Frysingerf2ff9172012-11-01 18:47:41 -04001551 print 'Packages failed:\n\t%s' % '\n\t'.join(self._failed)
David James0eae23e2012-07-03 15:04:25 -07001552 status_file = os.environ.get("PARALLEL_EMERGE_STATUS_FILE")
1553 if status_file:
David James321490a2012-12-17 12:05:56 -08001554 failed_pkgs = set(portage.versions.cpv_getkey(x)
1555 for x in self._failed)
David James0eae23e2012-07-03 15:04:25 -07001556 with open(status_file, "a") as f:
1557 f.write("%s\n" % " ".join(failed_pkgs))
David Jamesfcb70ef2011-02-02 16:02:30 -08001558 else:
1559 print "Deadlock! Circular dependencies!"
1560 sys.exit(1)
1561
David James321490a2012-12-17 12:05:56 -08001562 for _ in xrange(12):
David Jamesa74289a2011-08-12 10:41:24 -07001563 try:
1564 job = self._job_queue.get(timeout=5)
1565 break
1566 except Queue.Empty:
1567 # Check if any more jobs can be scheduled.
1568 self._ScheduleLoop()
1569 else:
Brian Harring706747c2012-03-16 03:04:31 -07001570 # Print an update every 60 seconds.
David Jamesfcb70ef2011-02-02 16:02:30 -08001571 self._Status()
1572 continue
1573
1574 target = job.target
1575
Brian Harring0be85c62012-03-17 19:52:12 -07001576 if job.fetch_only:
1577 if not job.done:
1578 self._fetch_jobs[job.target] = job
1579 else:
1580 state = self._state_map[job.target]
1581 state.prefetched = True
1582 state.fetched_successfully = (job.retcode == 0)
1583 del self._fetch_jobs[job.target]
1584 self._Print("Fetched %s in %2.2fs"
1585 % (target, time.time() - job.start_timestamp))
1586
1587 if self._show_output or job.retcode != 0:
1588 self._print_queue.put(JobPrinter(job, unlink=True))
1589 else:
1590 os.unlink(job.filename)
1591 # Failure or not, let build work with it next.
1592 if not self._deps_map[job.target]["needs"]:
1593 self._build_ready.put(state)
1594 self._ScheduleLoop()
1595
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001596 if self._unpack_only and job.retcode == 0:
1597 self._unpack_ready.put(state)
1598 self._ScheduleLoop(unpack_only=True)
1599
Brian Harring0be85c62012-03-17 19:52:12 -07001600 if self._fetch_ready:
1601 state = self._fetch_ready.get()
1602 self._fetch_queue.put(state)
1603 self._fetch_jobs[state.target] = None
1604 else:
1605 # Minor optimization; shut down fetchers early since we know
1606 # the queue is empty.
1607 self._fetch_queue.put(None)
1608 continue
1609
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001610 if job.unpack_only:
1611 if not job.done:
1612 self._unpack_jobs[target] = job
1613 else:
1614 del self._unpack_jobs[target]
1615 self._Print("Unpacked %s in %2.2fs"
1616 % (target, time.time() - job.start_timestamp))
1617 if self._show_output or job.retcode != 0:
1618 self._print_queue.put(JobPrinter(job, unlink=True))
1619 else:
1620 os.unlink(job.filename)
1621 if self._unpack_ready:
1622 state = self._unpack_ready.get()
1623 self._unpack_queue.put(state)
1624 self._unpack_jobs[state.target] = None
1625 continue
1626
David Jamesfcb70ef2011-02-02 16:02:30 -08001627 if not job.done:
Brian Harring0be85c62012-03-17 19:52:12 -07001628 self._build_jobs[target] = job
David Jamesfcb70ef2011-02-02 16:02:30 -08001629 self._Print("Started %s (logged in %s)" % (target, job.filename))
1630 continue
1631
1632 # Print output of job
1633 if self._show_output or job.retcode != 0:
1634 self._print_queue.put(JobPrinter(job, unlink=True))
1635 else:
1636 os.unlink(job.filename)
Brian Harring0be85c62012-03-17 19:52:12 -07001637 del self._build_jobs[target]
David Jamesfcb70ef2011-02-02 16:02:30 -08001638
1639 seconds = time.time() - job.start_timestamp
1640 details = "%s (in %dm%.1fs)" % (target, seconds / 60, seconds % 60)
David James32420cc2011-08-25 21:32:46 -07001641 previously_failed = target in self._failed
David Jamesfcb70ef2011-02-02 16:02:30 -08001642
1643 # Complain if necessary.
1644 if job.retcode != 0:
1645 # Handle job failure.
David James32420cc2011-08-25 21:32:46 -07001646 if previously_failed:
David Jamesfcb70ef2011-02-02 16:02:30 -08001647 # If this job has failed previously, give up.
1648 self._Print("Failed %s. Your build has failed." % details)
1649 else:
1650 # Queue up this build to try again after a long while.
David Jamese703d0f2012-01-12 16:27:45 -08001651 retried.add(target)
Brian Harring0be85c62012-03-17 19:52:12 -07001652 self._retry_queue.append(self._state_map[target])
David Jamesfcb70ef2011-02-02 16:02:30 -08001653 self._failed.add(target)
1654 self._Print("Failed %s, retrying later." % details)
1655 else:
David James32420cc2011-08-25 21:32:46 -07001656 if previously_failed:
1657 # Remove target from list of failed packages.
1658 self._failed.remove(target)
1659
1660 self._Print("Completed %s" % details)
1661
1662 # Mark as completed and unblock waiting ebuilds.
1663 self._Finish(target)
1664
1665 if previously_failed and self._retry_queue:
David Jamesfcb70ef2011-02-02 16:02:30 -08001666 # If we have successfully retried a failed package, and there
1667 # are more failed packages, try the next one. We will only have
1668 # one retrying package actively running at a time.
1669 self._Retry()
1670
David Jamesfcb70ef2011-02-02 16:02:30 -08001671
David James8c7e5e32011-06-28 11:26:03 -07001672 # Schedule pending jobs and print an update.
1673 self._ScheduleLoop()
1674 self._Status()
David Jamesfcb70ef2011-02-02 16:02:30 -08001675
David Jamese703d0f2012-01-12 16:27:45 -08001676 # If packages were retried, output a warning.
1677 if retried:
1678 self._Print("")
1679 self._Print("WARNING: The following packages failed the first time,")
1680 self._Print("but succeeded upon retry. This might indicate incorrect")
1681 self._Print("dependencies.")
1682 for pkg in retried:
1683 self._Print(" %s" % pkg)
1684 self._Print("@@@STEP_WARNINGS@@@")
1685 self._Print("")
1686
David Jamesfcb70ef2011-02-02 16:02:30 -08001687 # Tell child threads to exit.
1688 self._Print("Merge complete")
David Jamesfcb70ef2011-02-02 16:02:30 -08001689
1690
Brian Harring30675052012-02-29 12:18:22 -08001691def main(argv):
Brian Harring8294d652012-05-23 02:20:52 -07001692 try:
1693 return real_main(argv)
1694 finally:
1695 # Work around multiprocessing sucking and not cleaning up after itself.
1696 # http://bugs.python.org/issue4106;
1697 # Step one; ensure GC is ran *prior* to the VM starting shutdown.
1698 gc.collect()
1699 # Step two; go looking for those threads and try to manually reap
1700 # them if we can.
1701 for x in threading.enumerate():
1702 # Filter on the name, and ident; if ident is None, the thread
1703 # wasn't started.
1704 if x.name == 'QueueFeederThread' and x.ident is not None:
1705 x.join(1)
David Jamesfcb70ef2011-02-02 16:02:30 -08001706
Brian Harring8294d652012-05-23 02:20:52 -07001707
1708def real_main(argv):
Brian Harring30675052012-02-29 12:18:22 -08001709 parallel_emerge_args = argv[:]
David Jamesfcb70ef2011-02-02 16:02:30 -08001710 deps = DepGraphGenerator()
Brian Harring30675052012-02-29 12:18:22 -08001711 deps.Initialize(parallel_emerge_args)
David Jamesfcb70ef2011-02-02 16:02:30 -08001712 emerge = deps.emerge
1713
1714 if emerge.action is not None:
Brian Harring30675052012-02-29 12:18:22 -08001715 argv = deps.ParseParallelEmergeArgs(argv)
Brian Harring8294d652012-05-23 02:20:52 -07001716 return emerge_main(argv)
David Jamesfcb70ef2011-02-02 16:02:30 -08001717 elif not emerge.cmdline_packages:
1718 Usage()
Brian Harring8294d652012-05-23 02:20:52 -07001719 return 1
David Jamesfcb70ef2011-02-02 16:02:30 -08001720
1721 # Unless we're in pretend mode, there's not much point running without
1722 # root access. We need to be able to install packages.
1723 #
1724 # NOTE: Even if you're running --pretend, it's a good idea to run
1725 # parallel_emerge with root access so that portage can write to the
1726 # dependency cache. This is important for performance.
David James321490a2012-12-17 12:05:56 -08001727 if "--pretend" not in emerge.opts and portage.data.secpass < 2:
David Jamesfcb70ef2011-02-02 16:02:30 -08001728 print "parallel_emerge: superuser access is required."
Brian Harring8294d652012-05-23 02:20:52 -07001729 return 1
David Jamesfcb70ef2011-02-02 16:02:30 -08001730
1731 if "--quiet" not in emerge.opts:
1732 cmdline_packages = " ".join(emerge.cmdline_packages)
David Jamesfcb70ef2011-02-02 16:02:30 -08001733 print "Starting fast-emerge."
1734 print " Building package %s on %s" % (cmdline_packages,
1735 deps.board or "root")
David Jamesfcb70ef2011-02-02 16:02:30 -08001736
David James386ccd12011-05-04 20:17:42 -07001737 deps_tree, deps_info = deps.GenDependencyTree()
David Jamesfcb70ef2011-02-02 16:02:30 -08001738
1739 # You want me to be verbose? I'll give you two trees! Twice as much value.
1740 if "--tree" in emerge.opts and "--verbose" in emerge.opts:
1741 deps.PrintTree(deps_tree)
1742
David James386ccd12011-05-04 20:17:42 -07001743 deps_graph = deps.GenDependencyGraph(deps_tree, deps_info)
David Jamesfcb70ef2011-02-02 16:02:30 -08001744
1745 # OK, time to print out our progress so far.
1746 deps.PrintInstallPlan(deps_graph)
1747 if "--tree" in emerge.opts:
1748 PrintDepsMap(deps_graph)
1749
1750 # Are we upgrading portage? If so, and there are more packages to merge,
1751 # schedule a restart of parallel_emerge to merge the rest. This ensures that
1752 # we pick up all updates to portage settings before merging any more
1753 # packages.
1754 portage_upgrade = False
1755 root = emerge.settings["ROOT"]
1756 final_db = emerge.depgraph._dynamic_config.mydbapi[root]
1757 if root == "/":
1758 for db_pkg in final_db.match_pkgs("sys-apps/portage"):
1759 portage_pkg = deps_graph.get(db_pkg.cpv)
David James0ff16f22012-11-02 14:18:07 -07001760 if portage_pkg:
David Jamesfcb70ef2011-02-02 16:02:30 -08001761 portage_upgrade = True
1762 if "--quiet" not in emerge.opts:
1763 print "Upgrading portage first, then restarting..."
1764
David James0ff16f22012-11-02 14:18:07 -07001765 # Upgrade Portage first, then the rest of the packages.
1766 #
1767 # In order to grant the child permission to run setsid, we need to run sudo
1768 # again. We preserve SUDO_USER here in case an ebuild depends on it.
1769 if portage_upgrade:
1770 # Calculate what arguments to use when re-invoking.
1771 args = ["sudo", "-E", "SUDO_USER=%s" % os.environ.get("SUDO_USER", "")]
1772 args += [os.path.abspath(sys.argv[0])] + parallel_emerge_args
1773 args += ["--exclude=sys-apps/portage"]
1774
1775 # First upgrade Portage.
1776 passthrough_args = ("--quiet", "--pretend", "--verbose")
1777 emerge_args = [k for k in emerge.opts if k in passthrough_args]
1778 ret = emerge_main(emerge_args + ["portage"])
1779 if ret != 0:
1780 return ret
1781
1782 # Now upgrade the rest.
1783 os.execvp(args[0], args)
1784
David Jamesfcb70ef2011-02-02 16:02:30 -08001785 # Run the queued emerges.
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001786 scheduler = EmergeQueue(deps_graph, emerge, deps.package_db, deps.show_output,
1787 deps.unpack_only)
Brian Harringa43f5952012-04-12 01:19:34 -07001788 try:
1789 scheduler.Run()
1790 finally:
1791 scheduler._Shutdown()
David James97ce8902011-08-16 09:51:05 -07001792 scheduler = None
David Jamesfcb70ef2011-02-02 16:02:30 -08001793
Mike Frysingerd20a6e22012-10-04 19:01:10 -04001794 clean_logs(emerge.settings)
1795
David Jamesfcb70ef2011-02-02 16:02:30 -08001796 print "Done"
Brian Harring8294d652012-05-23 02:20:52 -07001797 return 0