blob: 6570a034a2353e1ab345db24e36040626f0f8cfb [file] [log] [blame]
David Jamesfcb70ef2011-02-02 16:02:30 -08001#!/usr/bin/python2.6
2# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
3# 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
12Basic operation:
13 Runs 'emerge -p --debug' to display dependencies, and stores a
14 dependency graph. All non-blocked packages are launched in parallel,
15 as 'emerge --nodeps package' with any blocked packages being emerged
16 immediately upon deps being met.
17
18 For this to work effectively, /usr/lib/portage/pym/portage/locks.py
19 must be stubbed out, preventing portage from slowing itself with
20 unneccesary locking, as this script ensures that emerge is run in such
21 a way that common resources are never in conflict. This is controlled
22 by an environment variable PORTAGE_LOCKS set in parallel emerge
23 subprocesses.
24
25 Parallel Emerge unlocks two things during operation, here's what you
26 must do to keep this safe:
27 * Storage dir containing binary packages. - Don't emerge new
28 packages while installing the existing ones.
29 * Portage database - You must not examine deps while modifying the
30 database. Therefore you may only parallelize "-p" read only access,
31 or "--nodeps" write only access.
32 Caveats:
33 * Some ebuild packages have incorrectly specified deps, and running
34 them in parallel is more likely to bring out these failures.
35 * Some ebuilds (especially the build part) have complex dependencies
36 that are not captured well by this script (it may be necessary to
37 install an old package to build, but then install a newer version
38 of the same package for a runtime dep).
39"""
40
41import codecs
42import copy
43import errno
David James8c7e5e32011-06-28 11:26:03 -070044import heapq
David Jamesfcb70ef2011-02-02 16:02:30 -080045import multiprocessing
46import os
47import Queue
David Jamesfcb70ef2011-02-02 16:02:30 -080048import signal
49import sys
50import tempfile
51import time
52import traceback
David Jamesfcb70ef2011-02-02 16:02:30 -080053
54# If PORTAGE_USERNAME isn't specified, scrape it from the $HOME variable. On
55# Chromium OS, the default "portage" user doesn't have the necessary
56# permissions. It'd be easier if we could default to $USERNAME, but $USERNAME
57# is "root" here because we get called through sudo.
58#
59# We need to set this before importing any portage modules, because portage
60# looks up "PORTAGE_USERNAME" at import time.
61#
62# NOTE: .bashrc sets PORTAGE_USERNAME = $USERNAME, so most people won't
63# encounter this case unless they have an old chroot or blow away the
64# environment by running sudo without the -E specifier.
65if "PORTAGE_USERNAME" not in os.environ:
66 homedir = os.environ.get("HOME")
67 if homedir:
68 os.environ["PORTAGE_USERNAME"] = os.path.basename(homedir)
69
70# Portage doesn't expose dependency trees in its public API, so we have to
71# make use of some private APIs here. These modules are found under
72# /usr/lib/portage/pym/.
73#
74# TODO(davidjames): Update Portage to expose public APIs for these features.
75from _emerge.actions import adjust_configs
76from _emerge.actions import load_emerge_config
77from _emerge.create_depgraph_params import create_depgraph_params
David James386ccd12011-05-04 20:17:42 -070078from _emerge.depgraph import backtrack_depgraph
David Jamesfcb70ef2011-02-02 16:02:30 -080079from _emerge.main import emerge_main
80from _emerge.main import parse_opts
81from _emerge.Package import Package
82from _emerge.Scheduler import Scheduler
83from _emerge.SetArg import SetArg
84from _emerge.stdout_spinner import stdout_spinner
David James386ccd12011-05-04 20:17:42 -070085from portage._global_updates import _global_updates
86from portage.versions import vercmp
David Jamesfcb70ef2011-02-02 16:02:30 -080087import portage
88import portage.debug
David Jamesfcb70ef2011-02-02 16:02:30 -080089
David James386ccd12011-05-04 20:17:42 -070090NEW_PORTAGE = (0 <= vercmp(portage.VERSION, "2.1.9.46-r2"))
David Jamesfcb70ef2011-02-02 16:02:30 -080091
92def Usage():
93 """Print usage."""
94 print "Usage:"
David James386ccd12011-05-04 20:17:42 -070095 print " ./parallel_emerge [--board=BOARD] [--workon=PKGS]"
David Jamesfcb70ef2011-02-02 16:02:30 -080096 print " [--rebuild] [emerge args] package"
97 print
98 print "Packages specified as workon packages are always built from source."
David Jamesfcb70ef2011-02-02 16:02:30 -080099 print
100 print "The --workon argument is mainly useful when you want to build and"
101 print "install packages that you are working on unconditionally, but do not"
102 print "to have to rev the package to indicate you want to build it from"
103 print "source. The build_packages script will automatically supply the"
104 print "workon argument to emerge, ensuring that packages selected using"
105 print "cros-workon are rebuilt."
106 print
107 print "The --rebuild option rebuilds packages whenever their dependencies"
108 print "are changed. This ensures that your build is correct."
109 sys.exit(1)
110
111
David Jamesfcb70ef2011-02-02 16:02:30 -0800112# Global start time
113GLOBAL_START = time.time()
114
David James7358d032011-05-19 10:40:03 -0700115# Whether process has been killed by a signal.
116KILLED = multiprocessing.Event()
117
David Jamesfcb70ef2011-02-02 16:02:30 -0800118
119class EmergeData(object):
120 """This simple struct holds various emerge variables.
121
122 This struct helps us easily pass emerge variables around as a unit.
123 These variables are used for calculating dependencies and installing
124 packages.
125 """
126
David Jamesbf1e3442011-05-28 07:44:20 -0700127 __slots__ = ["action", "cmdline_packages", "depgraph", "favorites",
128 "mtimedb", "opts", "root_config", "scheduler_graph",
129 "settings", "spinner", "trees"]
David Jamesfcb70ef2011-02-02 16:02:30 -0800130
131 def __init__(self):
132 # The action the user requested. If the user is installing packages, this
133 # is None. If the user is doing anything other than installing packages,
134 # this will contain the action name, which will map exactly to the
135 # long-form name of the associated emerge option.
136 #
137 # Example: If you call parallel_emerge --unmerge package, the action name
138 # will be "unmerge"
139 self.action = None
140
141 # The list of packages the user passed on the command-line.
142 self.cmdline_packages = None
143
144 # The emerge dependency graph. It'll contain all the packages involved in
145 # this merge, along with their versions.
146 self.depgraph = None
147
David Jamesbf1e3442011-05-28 07:44:20 -0700148 # The list of candidates to add to the world file.
149 self.favorites = None
150
David Jamesfcb70ef2011-02-02 16:02:30 -0800151 # A dict of the options passed to emerge. This dict has been cleaned up
152 # a bit by parse_opts, so that it's a bit easier for the emerge code to
153 # look at the options.
154 #
155 # Emerge takes a few shortcuts in its cleanup process to make parsing of
156 # the options dict easier. For example, if you pass in "--usepkg=n", the
157 # "--usepkg" flag is just left out of the dictionary altogether. Because
158 # --usepkg=n is the default, this makes parsing easier, because emerge
159 # can just assume that if "--usepkg" is in the dictionary, it's enabled.
160 #
161 # These cleanup processes aren't applied to all options. For example, the
162 # --with-bdeps flag is passed in as-is. For a full list of the cleanups
163 # applied by emerge, see the parse_opts function in the _emerge.main
164 # package.
165 self.opts = None
166
167 # A dictionary used by portage to maintain global state. This state is
168 # loaded from disk when portage starts up, and saved to disk whenever we
169 # call mtimedb.commit().
170 #
171 # This database contains information about global updates (i.e., what
172 # version of portage we have) and what we're currently doing. Portage
173 # saves what it is currently doing in this database so that it can be
174 # resumed when you call it with the --resume option.
175 #
176 # parallel_emerge does not save what it is currently doing in the mtimedb,
177 # so we do not support the --resume option.
178 self.mtimedb = None
179
180 # The portage configuration for our current root. This contains the portage
181 # settings (see below) and the three portage trees for our current root.
182 # (The three portage trees are explained below, in the documentation for
183 # the "trees" member.)
184 self.root_config = None
185
186 # The scheduler graph is used by emerge to calculate what packages to
187 # install. We don't actually install any deps, so this isn't really used,
188 # but we pass it in to the Scheduler object anyway.
189 self.scheduler_graph = None
190
191 # Portage settings for our current session. Most of these settings are set
192 # in make.conf inside our current install root.
193 self.settings = None
194
195 # The spinner, which spews stuff to stdout to indicate that portage is
196 # doing something. We maintain our own spinner, so we set the portage
197 # spinner to "silent" mode.
198 self.spinner = None
199
200 # The portage trees. There are separate portage trees for each root. To get
201 # the portage tree for the current root, you can look in self.trees[root],
202 # where root = self.settings["ROOT"].
203 #
204 # In each root, there are three trees: vartree, porttree, and bintree.
205 # - vartree: A database of the currently-installed packages.
206 # - porttree: A database of ebuilds, that can be used to build packages.
207 # - bintree: A database of binary packages.
208 self.trees = None
209
210
211class DepGraphGenerator(object):
212 """Grab dependency information about packages from portage.
213
214 Typical usage:
215 deps = DepGraphGenerator()
216 deps.Initialize(sys.argv[1:])
217 deps_tree, deps_info = deps.GenDependencyTree()
218 deps_graph = deps.GenDependencyGraph(deps_tree, deps_info)
219 deps.PrintTree(deps_tree)
220 PrintDepsMap(deps_graph)
221 """
222
David James386ccd12011-05-04 20:17:42 -0700223 __slots__ = ["board", "emerge", "package_db", "show_output"]
David Jamesfcb70ef2011-02-02 16:02:30 -0800224
225 def __init__(self):
226 self.board = None
227 self.emerge = EmergeData()
David Jamesfcb70ef2011-02-02 16:02:30 -0800228 self.package_db = {}
David Jamesfcb70ef2011-02-02 16:02:30 -0800229 self.show_output = False
David Jamesfcb70ef2011-02-02 16:02:30 -0800230
231 def ParseParallelEmergeArgs(self, argv):
232 """Read the parallel emerge arguments from the command-line.
233
234 We need to be compatible with emerge arg format. We scrape arguments that
235 are specific to parallel_emerge, and pass through the rest directly to
236 emerge.
237 Args:
238 argv: arguments list
239 Returns:
240 Arguments that don't belong to parallel_emerge
241 """
242 emerge_args = []
243 for arg in argv:
244 # Specifically match arguments that are specific to parallel_emerge, and
245 # pass through the rest.
246 if arg.startswith("--board="):
247 self.board = arg.replace("--board=", "")
248 elif arg.startswith("--workon="):
249 workon_str = arg.replace("--workon=", "")
David James386ccd12011-05-04 20:17:42 -0700250 if NEW_PORTAGE:
251 emerge_args.append("--reinstall-atoms=%s" % workon_str)
252 emerge_args.append("--usepkg-exclude=%s" % workon_str)
David Jamesfcb70ef2011-02-02 16:02:30 -0800253 elif arg.startswith("--force-remote-binary="):
254 force_remote_binary = arg.replace("--force-remote-binary=", "")
David James386ccd12011-05-04 20:17:42 -0700255 if NEW_PORTAGE:
256 emerge_args.append("--useoldpkg-atoms=%s" % force_remote_binary)
David Jamesfcb70ef2011-02-02 16:02:30 -0800257 elif arg == "--show-output":
258 self.show_output = True
David James386ccd12011-05-04 20:17:42 -0700259 elif arg == "--rebuild":
260 if NEW_PORTAGE:
261 emerge_args.append("--rebuild-if-unbuilt")
David Jamesfcb70ef2011-02-02 16:02:30 -0800262 else:
263 # Not one of our options, so pass through to emerge.
264 emerge_args.append(arg)
265
David James386ccd12011-05-04 20:17:42 -0700266 # These packages take a really long time to build, so, for expediency, we
267 # are blacklisting them from automatic rebuilds because one of their
268 # dependencies needs to be recompiled.
269 for pkg in ("chromeos-base/chromeos-chrome", "media-plugins/o3d",
270 "dev-java/icedtea"):
271 if NEW_PORTAGE:
272 emerge_args.append("--rebuild-exclude=%s" % pkg)
David Jamesfcb70ef2011-02-02 16:02:30 -0800273
274 return emerge_args
275
276 def Initialize(self, args):
277 """Initializer. Parses arguments and sets up portage state."""
278
279 # Parse and strip out args that are just intended for parallel_emerge.
280 emerge_args = self.ParseParallelEmergeArgs(args)
281
282 # Setup various environment variables based on our current board. These
283 # variables are normally setup inside emerge-${BOARD}, but since we don't
284 # call that script, we have to set it up here. These variables serve to
285 # point our tools at /build/BOARD and to setup cross compiles to the
286 # appropriate board as configured in toolchain.conf.
287 if self.board:
288 os.environ["PORTAGE_CONFIGROOT"] = "/build/" + self.board
289 os.environ["PORTAGE_SYSROOT"] = "/build/" + self.board
290 os.environ["SYSROOT"] = "/build/" + self.board
291 srcroot = "%s/../../src" % os.path.dirname(os.path.realpath(__file__))
292 # Strip the variant out of the board name to look for the toolchain. This
293 # is similar to what setup_board does.
294 board_no_variant = self.board.split('_')[0]
295 public_toolchain_path = ("%s/overlays/overlay-%s/toolchain.conf" %
296 (srcroot, board_no_variant))
297 private_toolchain_path = (
298 "%s/private-overlays/overlay-%s-private/toolchain.conf" %
299 (srcroot, board_no_variant))
300 if os.path.isfile(public_toolchain_path):
301 toolchain_path = public_toolchain_path
302 elif os.path.isfile(private_toolchain_path):
303 toolchain_path = private_toolchain_path
304 else:
305 print "Not able to locate toolchain.conf in board overlays"
306 sys.exit(1)
307
308 f = open(toolchain_path)
309 os.environ["CHOST"] = f.readline().strip()
310 f.close()
311
312 # Although CHROMEOS_ROOT isn't specific to boards, it's normally setup
313 # inside emerge-${BOARD}, so we set it up here for compatibility. It
314 # will be going away soon as we migrate to CROS_WORKON_SRCROOT.
315 os.environ.setdefault("CHROMEOS_ROOT", os.environ["HOME"] + "/trunk")
316
317 # Turn off interactive delays
318 os.environ["EBEEP_IGNORE"] = "1"
319 os.environ["EPAUSE_IGNORE"] = "1"
320 os.environ["UNMERGE_DELAY"] = "0"
321
322 # Parse the emerge options.
David Jamesea3ca332011-05-26 11:48:29 -0700323 action, opts, cmdline_packages = parse_opts(emerge_args, silent=True)
David Jamesfcb70ef2011-02-02 16:02:30 -0800324
325 # Set environment variables based on options. Portage normally sets these
326 # environment variables in emerge_main, but we can't use that function,
327 # because it also does a bunch of other stuff that we don't want.
328 # TODO(davidjames): Patch portage to move this logic into a function we can
329 # reuse here.
330 if "--debug" in opts:
331 os.environ["PORTAGE_DEBUG"] = "1"
332 if "--config-root" in opts:
333 os.environ["PORTAGE_CONFIGROOT"] = opts["--config-root"]
334 if "--root" in opts:
335 os.environ["ROOT"] = opts["--root"]
336 if "--accept-properties" in opts:
337 os.environ["ACCEPT_PROPERTIES"] = opts["--accept-properties"]
338
339 # Portage has two flags for doing collision protection: collision-protect
340 # and protect-owned. The protect-owned feature is enabled by default and
341 # is quite useful: it checks to make sure that we don't have multiple
342 # packages that own the same file. The collision-protect feature is more
343 # strict, and less useful: it fails if it finds a conflicting file, even
344 # if that file was created by an earlier ebuild that failed to install.
345 #
346 # We want to disable collision-protect here because we don't handle
347 # failures during the merge step very well. Sometimes we leave old files
348 # lying around and they cause problems, so for now we disable the flag.
349 # TODO(davidjames): Look for a better solution.
350 features = os.environ.get("FEATURES", "") + " -collision-protect"
351
David Jamesdeebd692011-05-09 17:02:52 -0700352 # Install packages in parallel.
353 features = features + " parallel-install"
354
David Jamesfcb70ef2011-02-02 16:02:30 -0800355 # If we're installing packages to the board, and we're not using the
356 # official flag, we can enable the following optimizations:
357 # 1) Don't lock during install step. This allows multiple packages to be
358 # installed at once. This is safe because our board packages do not
359 # muck with each other during the post-install step.
360 # 2) Don't update the environment until the end of the build. This is
361 # safe because board packages don't need to run during the build --
362 # they're cross-compiled, so our CPU architecture doesn't support them
363 # anyway.
364 if self.board and os.environ.get("CHROMEOS_OFFICIAL") != "1":
365 os.environ.setdefault("PORTAGE_LOCKS", "false")
David Jamesdeebd692011-05-09 17:02:52 -0700366 features = features + " -ebuild-locks no-env-update"
David Jamesfcb70ef2011-02-02 16:02:30 -0800367
368 os.environ["FEATURES"] = features
369
370 # Now that we've setup the necessary environment variables, we can load the
371 # emerge config from disk.
372 settings, trees, mtimedb = load_emerge_config()
373
David Jamesea3ca332011-05-26 11:48:29 -0700374 # Add in EMERGE_DEFAULT_OPTS, if specified.
375 tmpcmdline = []
376 if "--ignore-default-opts" not in opts:
377 tmpcmdline.extend(settings["EMERGE_DEFAULT_OPTS"].split())
378 tmpcmdline.extend(emerge_args)
379 action, opts, cmdline_packages = parse_opts(tmpcmdline)
380
381 # If we're installing to the board, we want the --root-deps option so that
382 # portage will install the build dependencies to that location as well.
383 if self.board:
384 opts.setdefault("--root-deps", True)
385
David Jamesfcb70ef2011-02-02 16:02:30 -0800386 # Check whether our portage tree is out of date. Typically, this happens
387 # when you're setting up a new portage tree, such as in setup_board and
388 # make_chroot. In that case, portage applies a bunch of global updates
389 # here. Once the updates are finished, we need to commit any changes
390 # that the global update made to our mtimedb, and reload the config.
391 #
392 # Portage normally handles this logic in emerge_main, but again, we can't
393 # use that function here.
394 if _global_updates(trees, mtimedb["updates"]):
395 mtimedb.commit()
396 settings, trees, mtimedb = load_emerge_config(trees=trees)
397
398 # Setup implied options. Portage normally handles this logic in
399 # emerge_main.
400 if "--buildpkgonly" in opts or "buildpkg" in settings.features:
401 opts.setdefault("--buildpkg", True)
402 if "--getbinpkgonly" in opts:
403 opts.setdefault("--usepkgonly", True)
404 opts.setdefault("--getbinpkg", True)
405 if "getbinpkg" in settings.features:
406 # Per emerge_main, FEATURES=getbinpkg overrides --getbinpkg=n
407 opts["--getbinpkg"] = True
408 if "--getbinpkg" in opts or "--usepkgonly" in opts:
409 opts.setdefault("--usepkg", True)
410 if "--fetch-all-uri" in opts:
411 opts.setdefault("--fetchonly", True)
412 if "--skipfirst" in opts:
413 opts.setdefault("--resume", True)
414 if "--buildpkgonly" in opts:
415 # --buildpkgonly will not merge anything, so it overrides all binary
416 # package options.
417 for opt in ("--getbinpkg", "--getbinpkgonly",
418 "--usepkg", "--usepkgonly"):
419 opts.pop(opt, None)
420 if (settings.get("PORTAGE_DEBUG", "") == "1" and
421 "python-trace" in settings.features):
422 portage.debug.set_trace(True)
423
424 # Complain about unsupported options
David James386ccd12011-05-04 20:17:42 -0700425 for opt in ("--ask", "--ask-enter-invalid", "--resume", "--skipfirst"):
David Jamesfcb70ef2011-02-02 16:02:30 -0800426 if opt in opts:
427 print "%s is not supported by parallel_emerge" % opt
428 sys.exit(1)
429
430 # Make emerge specific adjustments to the config (e.g. colors!)
431 adjust_configs(opts, trees)
432
433 # Save our configuration so far in the emerge object
434 emerge = self.emerge
435 emerge.action, emerge.opts = action, opts
436 emerge.settings, emerge.trees, emerge.mtimedb = settings, trees, mtimedb
437 emerge.cmdline_packages = cmdline_packages
438 root = settings["ROOT"]
439 emerge.root_config = trees[root]["root_config"]
440
David James386ccd12011-05-04 20:17:42 -0700441 if "--usepkg" in opts:
David Jamesfcb70ef2011-02-02 16:02:30 -0800442 emerge.trees[root]["bintree"].populate("--getbinpkg" in opts)
443
David Jamesfcb70ef2011-02-02 16:02:30 -0800444 def CreateDepgraph(self, emerge, packages):
445 """Create an emerge depgraph object."""
446 # Setup emerge options.
447 emerge_opts = emerge.opts.copy()
448
David James386ccd12011-05-04 20:17:42 -0700449 # Ask portage to build a dependency graph. with the options we specified
450 # above.
David Jamesfcb70ef2011-02-02 16:02:30 -0800451 params = create_depgraph_params(emerge_opts, emerge.action)
David Jamesbf1e3442011-05-28 07:44:20 -0700452 success, depgraph, favorites = backtrack_depgraph(
David James386ccd12011-05-04 20:17:42 -0700453 emerge.settings, emerge.trees, emerge_opts, params, emerge.action,
454 packages, emerge.spinner)
455 emerge.depgraph = depgraph
David Jamesfcb70ef2011-02-02 16:02:30 -0800456
David James386ccd12011-05-04 20:17:42 -0700457 # Is it impossible to honor the user's request? Bail!
458 if not success:
459 depgraph.display_problems()
460 sys.exit(1)
David Jamesfcb70ef2011-02-02 16:02:30 -0800461
462 emerge.depgraph = depgraph
David Jamesbf1e3442011-05-28 07:44:20 -0700463 emerge.favorites = favorites
David Jamesfcb70ef2011-02-02 16:02:30 -0800464
465 # Is it impossible to honor the user's request? Bail!
466 if not success:
467 depgraph.display_problems()
468 sys.exit(1)
469
David Jamesdeebd692011-05-09 17:02:52 -0700470 # Prime and flush emerge caches.
471 root = emerge.settings["ROOT"]
472 vardb = emerge.trees[root]["vartree"].dbapi
David James0bdc5de2011-05-12 16:22:26 -0700473 if "--pretend" not in emerge.opts:
474 vardb.counter_tick()
David Jamesdeebd692011-05-09 17:02:52 -0700475 vardb.flush_cache()
476
David James386ccd12011-05-04 20:17:42 -0700477 def GenDependencyTree(self):
David Jamesfcb70ef2011-02-02 16:02:30 -0800478 """Get dependency tree info from emerge.
479
David Jamesfcb70ef2011-02-02 16:02:30 -0800480 Returns:
481 Dependency tree
482 """
483 start = time.time()
484
485 emerge = self.emerge
486
487 # Create a list of packages to merge
488 packages = set(emerge.cmdline_packages[:])
David Jamesfcb70ef2011-02-02 16:02:30 -0800489
490 # Tell emerge to be quiet. We print plenty of info ourselves so we don't
491 # need any extra output from portage.
492 portage.util.noiselimit = -1
493
494 # My favorite feature: The silent spinner. It doesn't spin. Ever.
495 # I'd disable the colors by default too, but they look kind of cool.
496 emerge.spinner = stdout_spinner()
497 emerge.spinner.update = emerge.spinner.update_quiet
498
499 if "--quiet" not in emerge.opts:
500 print "Calculating deps..."
501
502 self.CreateDepgraph(emerge, packages)
503 depgraph = emerge.depgraph
504
505 # Build our own tree from the emerge digraph.
506 deps_tree = {}
507 digraph = depgraph._dynamic_config.digraph
David James3f778802011-08-25 19:31:45 -0700508 root = emerge.settings["ROOT"]
509 final_db = depgraph._dynamic_config.mydbapi[root]
David Jamesfcb70ef2011-02-02 16:02:30 -0800510 for node, node_deps in digraph.nodes.items():
511 # Calculate dependency packages that need to be installed first. Each
512 # child on the digraph is a dependency. The "operation" field specifies
513 # what we're doing (e.g. merge, uninstall, etc.). The "priorities" array
514 # contains the type of dependency (e.g. build, runtime, runtime_post,
515 # etc.)
516 #
David Jamesfcb70ef2011-02-02 16:02:30 -0800517 # Portage refers to the identifiers for packages as a CPV. This acronym
518 # stands for Component/Path/Version.
519 #
520 # Here's an example CPV: chromeos-base/power_manager-0.0.1-r1
521 # Split up, this CPV would be:
522 # C -- Component: chromeos-base
523 # P -- Path: power_manager
524 # V -- Version: 0.0.1-r1
525 #
526 # We just refer to CPVs as packages here because it's easier.
527 deps = {}
528 for child, priorities in node_deps[0].items():
David James3f778802011-08-25 19:31:45 -0700529 if isinstance(child, Package) and child.root == root:
530 cpv = str(child.cpv)
531 action = str(child.operation)
532
533 # If we're uninstalling a package, check whether Portage is
534 # installing a replacement. If so, just depend on the installation
535 # of the new package, because the old package will automatically
536 # be uninstalled at that time.
537 if action == "uninstall":
538 for pkg in final_db.match_pkgs(child.slot_atom):
539 cpv = str(pkg.cpv)
540 action = "merge"
541 break
542
543 deps[cpv] = dict(action=action,
544 deptypes=[str(x) for x in priorities],
545 deps={})
David Jamesfcb70ef2011-02-02 16:02:30 -0800546
547 # We've built our list of deps, so we can add our package to the tree.
David James3f778802011-08-25 19:31:45 -0700548 if isinstance(node, Package) and node.root == root:
David Jamesfcb70ef2011-02-02 16:02:30 -0800549 deps_tree[str(node.cpv)] = dict(action=str(node.operation),
550 deps=deps)
551
David Jamesfcb70ef2011-02-02 16:02:30 -0800552 # Ask portage for its install plan, so that we can only throw out
David James386ccd12011-05-04 20:17:42 -0700553 # dependencies that portage throws out.
David Jamesfcb70ef2011-02-02 16:02:30 -0800554 deps_info = {}
555 for pkg in depgraph.altlist():
556 if isinstance(pkg, Package):
David James3f778802011-08-25 19:31:45 -0700557 assert pkg.root == root
David Jamesfcb70ef2011-02-02 16:02:30 -0800558 self.package_db[pkg.cpv] = pkg
559
David Jamesfcb70ef2011-02-02 16:02:30 -0800560 # Save off info about the package
David James386ccd12011-05-04 20:17:42 -0700561 deps_info[str(pkg.cpv)] = {"idx": len(deps_info)}
David Jamesfcb70ef2011-02-02 16:02:30 -0800562
563 seconds = time.time() - start
564 if "--quiet" not in emerge.opts:
565 print "Deps calculated in %dm%.1fs" % (seconds / 60, seconds % 60)
566
567 return deps_tree, deps_info
568
569 def PrintTree(self, deps, depth=""):
570 """Print the deps we have seen in the emerge output.
571
572 Args:
573 deps: Dependency tree structure.
574 depth: Allows printing the tree recursively, with indentation.
575 """
576 for entry in sorted(deps):
577 action = deps[entry]["action"]
578 print "%s %s (%s)" % (depth, entry, action)
579 self.PrintTree(deps[entry]["deps"], depth=depth + " ")
580
David James386ccd12011-05-04 20:17:42 -0700581 def GenDependencyGraph(self, deps_tree, deps_info):
David Jamesfcb70ef2011-02-02 16:02:30 -0800582 """Generate a doubly linked dependency graph.
583
584 Args:
585 deps_tree: Dependency tree structure.
586 deps_info: More details on the dependencies.
587 Returns:
588 Deps graph in the form of a dict of packages, with each package
589 specifying a "needs" list and "provides" list.
590 """
591 emerge = self.emerge
592 root = emerge.settings["ROOT"]
593
David Jamesfcb70ef2011-02-02 16:02:30 -0800594 # deps_map is the actual dependency graph.
595 #
596 # Each package specifies a "needs" list and a "provides" list. The "needs"
597 # list indicates which packages we depend on. The "provides" list
598 # indicates the reverse dependencies -- what packages need us.
599 #
600 # We also provide some other information in the dependency graph:
601 # - action: What we're planning on doing with this package. Generally,
602 # "merge", "nomerge", or "uninstall"
David Jamesfcb70ef2011-02-02 16:02:30 -0800603 deps_map = {}
604
605 def ReverseTree(packages):
606 """Convert tree to digraph.
607
608 Take the tree of package -> requirements and reverse it to a digraph of
609 buildable packages -> packages they unblock.
610 Args:
611 packages: Tree(s) of dependencies.
612 Returns:
613 Unsanitized digraph.
614 """
David James8c7e5e32011-06-28 11:26:03 -0700615 binpkg_phases = set(["setup", "preinst", "postinst"])
David James3f778802011-08-25 19:31:45 -0700616 needed_dep_types = set(["blocker", "buildtime", "runtime"])
David Jamesfcb70ef2011-02-02 16:02:30 -0800617 for pkg in packages:
618
619 # Create an entry for the package
620 action = packages[pkg]["action"]
David James8c7e5e32011-06-28 11:26:03 -0700621 default_pkg = {"needs": {}, "provides": set(), "action": action,
622 "nodeps": False, "binary": False}
David Jamesfcb70ef2011-02-02 16:02:30 -0800623 this_pkg = deps_map.setdefault(pkg, default_pkg)
624
David James8c7e5e32011-06-28 11:26:03 -0700625 if pkg in deps_info:
626 this_pkg["idx"] = deps_info[pkg]["idx"]
627
628 # If a package doesn't have any defined phases that might use the
629 # dependent packages (i.e. pkg_setup, pkg_preinst, or pkg_postinst),
630 # we can install this package before its deps are ready.
631 emerge_pkg = self.package_db.get(pkg)
632 if emerge_pkg and emerge_pkg.type_name == "binary":
633 this_pkg["binary"] = True
634 defined_phases = emerge_pkg.metadata.defined_phases
635 defined_binpkg_phases = binpkg_phases.intersection(defined_phases)
636 if not defined_binpkg_phases:
637 this_pkg["nodeps"] = True
638
David Jamesfcb70ef2011-02-02 16:02:30 -0800639 # Create entries for dependencies of this package first.
640 ReverseTree(packages[pkg]["deps"])
641
642 # Add dependencies to this package.
643 for dep, dep_item in packages[pkg]["deps"].iteritems():
David James8c7e5e32011-06-28 11:26:03 -0700644 # We only need to enforce strict ordering of dependencies if the
David James3f778802011-08-25 19:31:45 -0700645 # dependency is a blocker, or is a buildtime or runtime dependency.
646 # (I.e., ignored, optional, and runtime_post dependencies don't
647 # depend on ordering.)
David James8c7e5e32011-06-28 11:26:03 -0700648 dep_types = dep_item["deptypes"]
649 if needed_dep_types.intersection(dep_types):
650 deps_map[dep]["provides"].add(pkg)
651 this_pkg["needs"][dep] = "/".join(dep_types)
David Jamesfcb70ef2011-02-02 16:02:30 -0800652
David James3f778802011-08-25 19:31:45 -0700653 # If there's a blocker, Portage may need to move files from one
654 # package to another, which requires editing the CONTENTS files of
655 # both packages. To avoid race conditions while editing this file,
656 # the two packages must not be installed in parallel, so we can't
657 # safely ignore dependencies. See http://crosbug.com/19328
658 if "blocker" in dep_types:
659 this_pkg["nodeps"] = False
660
David Jamesfcb70ef2011-02-02 16:02:30 -0800661 def FindCycles():
662 """Find cycles in the dependency tree.
663
664 Returns:
665 A dict mapping cyclic packages to a dict of the deps that cause
666 cycles. For each dep that causes cycles, it returns an example
667 traversal of the graph that shows the cycle.
668 """
669
670 def FindCyclesAtNode(pkg, cycles, unresolved, resolved):
671 """Find cycles in cyclic dependencies starting at specified package.
672
673 Args:
674 pkg: Package identifier.
675 cycles: A dict mapping cyclic packages to a dict of the deps that
676 cause cycles. For each dep that causes cycles, it returns an
677 example traversal of the graph that shows the cycle.
678 unresolved: Nodes that have been visited but are not fully processed.
679 resolved: Nodes that have been visited and are fully processed.
680 """
681 pkg_cycles = cycles.get(pkg)
682 if pkg in resolved and not pkg_cycles:
683 # If we already looked at this package, and found no cyclic
684 # dependencies, we can stop now.
685 return
686 unresolved.append(pkg)
687 for dep in deps_map[pkg]["needs"]:
688 if dep in unresolved:
689 idx = unresolved.index(dep)
690 mycycle = unresolved[idx:] + [dep]
691 for i in range(len(mycycle) - 1):
692 pkg1, pkg2 = mycycle[i], mycycle[i+1]
693 cycles.setdefault(pkg1, {}).setdefault(pkg2, mycycle)
694 elif not pkg_cycles or dep not in pkg_cycles:
695 # Looks like we haven't seen this edge before.
696 FindCyclesAtNode(dep, cycles, unresolved, resolved)
697 unresolved.pop()
698 resolved.add(pkg)
699
700 cycles, unresolved, resolved = {}, [], set()
701 for pkg in deps_map:
702 FindCyclesAtNode(pkg, cycles, unresolved, resolved)
703 return cycles
704
David James386ccd12011-05-04 20:17:42 -0700705 def RemoveUnusedPackages():
David Jamesfcb70ef2011-02-02 16:02:30 -0800706 """Remove installed packages, propagating dependencies."""
David Jamesfcb70ef2011-02-02 16:02:30 -0800707 # Schedule packages that aren't on the install list for removal
708 rm_pkgs = set(deps_map.keys()) - set(deps_info.keys())
709
David Jamesfcb70ef2011-02-02 16:02:30 -0800710 # Remove the packages we don't want, simplifying the graph and making
711 # it easier for us to crack cycles.
712 for pkg in sorted(rm_pkgs):
713 this_pkg = deps_map[pkg]
714 needs = this_pkg["needs"]
715 provides = this_pkg["provides"]
716 for dep in needs:
717 dep_provides = deps_map[dep]["provides"]
718 dep_provides.update(provides)
719 dep_provides.discard(pkg)
720 dep_provides.discard(dep)
721 for target in provides:
722 target_needs = deps_map[target]["needs"]
723 target_needs.update(needs)
724 target_needs.pop(pkg, None)
725 target_needs.pop(target, None)
726 del deps_map[pkg]
727
728 def PrintCycleBreak(basedep, dep, mycycle):
729 """Print details about a cycle that we are planning on breaking.
730
731 We are breaking a cycle where dep needs basedep. mycycle is an
732 example cycle which contains dep -> basedep."""
733
David Jamesfcb70ef2011-02-02 16:02:30 -0800734 needs = deps_map[dep]["needs"]
735 depinfo = needs.get(basedep, "deleted")
David Jamesfcb70ef2011-02-02 16:02:30 -0800736
David James3f778802011-08-25 19:31:45 -0700737 # It's OK to swap install order for blockers, as long as the two
738 # packages aren't installed in parallel. If there is a cycle, then
739 # we know the packages depend on each other already, so we can drop the
740 # blocker safely without printing a warning.
741 if depinfo == "blocker":
742 return
743
David Jamesfcb70ef2011-02-02 16:02:30 -0800744 # Notify the user that we're breaking a cycle.
745 print "Breaking %s -> %s (%s)" % (dep, basedep, depinfo)
746
747 # Show cycle.
748 for i in range(len(mycycle) - 1):
749 pkg1, pkg2 = mycycle[i], mycycle[i+1]
750 needs = deps_map[pkg1]["needs"]
751 depinfo = needs.get(pkg2, "deleted")
752 if pkg1 == dep and pkg2 == basedep:
753 depinfo = depinfo + ", deleting"
754 print " %s -> %s (%s)" % (pkg1, pkg2, depinfo)
755
756 def SanitizeTree():
757 """Remove circular dependencies.
758
759 We prune all dependencies involved in cycles that go against the emerge
760 ordering. This has a nice property: we're guaranteed to merge
761 dependencies in the same order that portage does.
762
763 Because we don't treat any dependencies as "soft" unless they're killed
764 by a cycle, we pay attention to a larger number of dependencies when
765 merging. This hurts performance a bit, but helps reliability.
766 """
767 start = time.time()
768 cycles = FindCycles()
769 while cycles:
770 for dep, mycycles in cycles.iteritems():
771 for basedep, mycycle in mycycles.iteritems():
772 if deps_info[basedep]["idx"] >= deps_info[dep]["idx"]:
773 PrintCycleBreak(basedep, dep, mycycle)
774 del deps_map[dep]["needs"][basedep]
775 deps_map[basedep]["provides"].remove(dep)
776 cycles = FindCycles()
777 seconds = time.time() - start
778 if "--quiet" not in emerge.opts and seconds >= 0.1:
779 print "Tree sanitized in %dm%.1fs" % (seconds / 60, seconds % 60)
780
David James8c7e5e32011-06-28 11:26:03 -0700781 def FindRecursiveProvides(pkg, seen):
782 """Find all nodes that require a particular package.
783
784 Assumes that graph is acyclic.
785
786 Args:
787 pkg: Package identifier.
788 seen: Nodes that have been visited so far.
789 """
790 if pkg in seen:
791 return
792 seen.add(pkg)
793 info = deps_map[pkg]
794 info["tprovides"] = info["provides"].copy()
795 for dep in info["provides"]:
796 FindRecursiveProvides(dep, seen)
797 info["tprovides"].update(deps_map[dep]["tprovides"])
798
David Jamesa22906f2011-05-04 19:53:26 -0700799 ReverseTree(deps_tree)
David Jamesa22906f2011-05-04 19:53:26 -0700800
David James386ccd12011-05-04 20:17:42 -0700801 # We need to remove unused packages so that we can use the dependency
802 # ordering of the install process to show us what cycles to crack.
803 RemoveUnusedPackages()
David Jamesfcb70ef2011-02-02 16:02:30 -0800804 SanitizeTree()
David James8c7e5e32011-06-28 11:26:03 -0700805 seen = set()
806 for pkg in deps_map:
807 FindRecursiveProvides(pkg, seen)
David Jamesfcb70ef2011-02-02 16:02:30 -0800808 return deps_map
809
810 def PrintInstallPlan(self, deps_map):
811 """Print an emerge-style install plan.
812
813 The install plan lists what packages we're installing, in order.
814 It's useful for understanding what parallel_emerge is doing.
815
816 Args:
817 deps_map: The dependency graph.
818 """
819
820 def InstallPlanAtNode(target, deps_map):
821 nodes = []
822 nodes.append(target)
823 for dep in deps_map[target]["provides"]:
824 del deps_map[dep]["needs"][target]
825 if not deps_map[dep]["needs"]:
826 nodes.extend(InstallPlanAtNode(dep, deps_map))
827 return nodes
828
829 deps_map = copy.deepcopy(deps_map)
830 install_plan = []
831 plan = set()
832 for target, info in deps_map.iteritems():
833 if not info["needs"] and target not in plan:
834 for item in InstallPlanAtNode(target, deps_map):
835 plan.add(item)
836 install_plan.append(self.package_db[item])
837
838 for pkg in plan:
839 del deps_map[pkg]
840
841 if deps_map:
842 print "Cyclic dependencies:", " ".join(deps_map)
843 PrintDepsMap(deps_map)
844 sys.exit(1)
845
846 self.emerge.depgraph.display(install_plan)
847
848
849def PrintDepsMap(deps_map):
850 """Print dependency graph, for each package list it's prerequisites."""
851 for i in sorted(deps_map):
852 print "%s: (%s) needs" % (i, deps_map[i]["action"])
853 needs = deps_map[i]["needs"]
854 for j in sorted(needs):
855 print " %s" % (j)
856 if not needs:
857 print " no dependencies"
858
859
860class EmergeJobState(object):
861 __slots__ = ["done", "filename", "last_notify_timestamp", "last_output_seek",
862 "last_output_timestamp", "pkgname", "retcode", "start_timestamp",
863 "target"]
864
865 def __init__(self, target, pkgname, done, filename, start_timestamp,
866 retcode=None):
867
868 # The full name of the target we're building (e.g.
869 # chromeos-base/chromeos-0.0.1-r60)
870 self.target = target
871
872 # The short name of the target we're building (e.g. chromeos-0.0.1-r60)
873 self.pkgname = pkgname
874
875 # Whether the job is done. (True if the job is done; false otherwise.)
876 self.done = done
877
878 # The filename where output is currently stored.
879 self.filename = filename
880
881 # The timestamp of the last time we printed the name of the log file. We
882 # print this at the beginning of the job, so this starts at
883 # start_timestamp.
884 self.last_notify_timestamp = start_timestamp
885
886 # The location (in bytes) of the end of the last complete line we printed.
887 # This starts off at zero. We use this to jump to the right place when we
888 # print output from the same ebuild multiple times.
889 self.last_output_seek = 0
890
891 # The timestamp of the last time we printed output. Since we haven't
892 # printed output yet, this starts at zero.
893 self.last_output_timestamp = 0
894
895 # The return code of our job, if the job is actually finished.
896 self.retcode = retcode
897
898 # The timestamp when our job started.
899 self.start_timestamp = start_timestamp
900
901
David James7358d032011-05-19 10:40:03 -0700902def KillHandler(signum, frame):
903 # Kill self and all subprocesses.
904 os.killpg(0, signal.SIGKILL)
905
David Jamesfcb70ef2011-02-02 16:02:30 -0800906def SetupWorkerSignals():
907 def ExitHandler(signum, frame):
David James7358d032011-05-19 10:40:03 -0700908 # Set KILLED flag.
909 KILLED.set()
David James13cead42011-05-18 16:22:01 -0700910
David James7358d032011-05-19 10:40:03 -0700911 # Remove our signal handlers so we don't get called recursively.
912 signal.signal(signal.SIGINT, KillHandler)
913 signal.signal(signal.SIGTERM, KillHandler)
David Jamesfcb70ef2011-02-02 16:02:30 -0800914
915 # Ensure that we exit quietly and cleanly, if possible, when we receive
916 # SIGTERM or SIGINT signals. By default, when the user hits CTRL-C, all
917 # of the child processes will print details about KeyboardInterrupt
918 # exceptions, which isn't very helpful.
919 signal.signal(signal.SIGINT, ExitHandler)
920 signal.signal(signal.SIGTERM, ExitHandler)
921
David James1ed3e252011-10-05 20:26:15 -0700922def EmergeProcess(scheduler, output):
923 """Merge a package in a subprocess.
924
925 Args:
926 scheduler: Scheduler object.
927 output: Temporary file to write output.
928
929 Returns:
930 The exit code returned by the subprocess.
931 """
932 pid = os.fork()
933 if pid == 0:
934 try:
935 # Sanity checks.
936 if sys.stdout.fileno() != 1: raise Exception("sys.stdout.fileno() != 1")
937 if sys.stderr.fileno() != 2: raise Exception("sys.stderr.fileno() != 2")
938
939 # - Redirect 1 (stdout) and 2 (stderr) at our temporary file.
940 # - Redirect 0 to point at sys.stdin. In this case, sys.stdin
941 # points at a file reading os.devnull, because multiprocessing mucks
942 # with sys.stdin.
943 # - Leave the sys.stdin and output filehandles alone.
944 fd_pipes = {0: sys.stdin.fileno(),
945 1: output.fileno(),
946 2: output.fileno(),
947 sys.stdin.fileno(): sys.stdin.fileno(),
948 output.fileno(): output.fileno()}
949 portage.process._setup_pipes(fd_pipes)
950
951 # Portage doesn't like when sys.stdin.fileno() != 0, so point sys.stdin
952 # at the filehandle we just created in _setup_pipes.
953 if sys.stdin.fileno() != 0:
954 sys.stdin = os.fdopen(0, "r")
955
956 # Actually do the merge.
957 retval = scheduler.merge()
958
959 # We catch all exceptions here (including SystemExit, KeyboardInterrupt,
960 # etc) so as to ensure that we don't confuse the multiprocessing module,
961 # which expects that all forked children exit with os._exit().
962 except:
963 traceback.print_exc(file=output)
964 retval = 1
965 sys.stdout.flush()
966 sys.stderr.flush()
967 output.flush()
968 os._exit(retval)
969 else:
970 # Return the exit code of the subprocess.
971 return os.waitpid(pid, 0)[1]
David Jamesfcb70ef2011-02-02 16:02:30 -0800972
973def EmergeWorker(task_queue, job_queue, emerge, package_db):
974 """This worker emerges any packages given to it on the task_queue.
975
976 Args:
977 task_queue: The queue of tasks for this worker to do.
978 job_queue: The queue of results from the worker.
979 emerge: An EmergeData() object.
980 package_db: A dict, mapping package ids to portage Package objects.
981
982 It expects package identifiers to be passed to it via task_queue. When
983 a task is started, it pushes the (target, filename) to the started_queue.
984 The output is stored in filename. When a merge starts or finishes, we push
985 EmergeJobState objects to the job_queue.
986 """
987
988 SetupWorkerSignals()
989 settings, trees, mtimedb = emerge.settings, emerge.trees, emerge.mtimedb
David Jamesdeebd692011-05-09 17:02:52 -0700990
991 # Disable flushing of caches to save on I/O.
992 if 0 <= vercmp(portage.VERSION, "2.1.9.48"):
993 root = emerge.settings["ROOT"]
994 vardb = emerge.trees[root]["vartree"].dbapi
995 vardb._flush_cache_enabled = False
996
David Jamesfcb70ef2011-02-02 16:02:30 -0800997 opts, spinner = emerge.opts, emerge.spinner
998 opts["--nodeps"] = True
David Jamesfcb70ef2011-02-02 16:02:30 -0800999 while True:
1000 # Wait for a new item to show up on the queue. This is a blocking wait,
1001 # so if there's nothing to do, we just sit here.
1002 target = task_queue.get()
1003 if not target:
1004 # If target is None, this means that the main thread wants us to quit.
1005 # The other workers need to exit too, so we'll push the message back on
1006 # to the queue so they'll get it too.
1007 task_queue.put(target)
1008 return
David James7358d032011-05-19 10:40:03 -07001009 if KILLED.is_set():
1010 return
1011
David Jamesfcb70ef2011-02-02 16:02:30 -08001012 db_pkg = package_db[target]
1013 db_pkg.root_config = emerge.root_config
1014 install_list = [db_pkg]
1015 pkgname = db_pkg.pf
1016 output = tempfile.NamedTemporaryFile(prefix=pkgname + "-", delete=False)
1017 start_timestamp = time.time()
1018 job = EmergeJobState(target, pkgname, False, output.name, start_timestamp)
1019 job_queue.put(job)
1020 if "--pretend" in opts:
1021 retcode = 0
1022 else:
David Jamesfcb70ef2011-02-02 16:02:30 -08001023 try:
David James386ccd12011-05-04 20:17:42 -07001024 emerge.scheduler_graph.mergelist = install_list
1025 scheduler = Scheduler(settings, trees, mtimedb, opts, spinner,
David Jamesbf1e3442011-05-28 07:44:20 -07001026 favorites=emerge.favorites, graph_config=emerge.scheduler_graph)
David Jamesace2e212011-07-13 11:47:39 -07001027
1028 # Enable blocker handling even though we're in --nodeps mode. This
1029 # allows us to unmerge the blocker after we've merged the replacement.
1030 scheduler._opts_ignore_blockers = frozenset()
1031
David James1ed3e252011-10-05 20:26:15 -07001032 retcode = EmergeProcess(scheduler, output)
David Jamesfcb70ef2011-02-02 16:02:30 -08001033 except Exception:
1034 traceback.print_exc(file=output)
1035 retcode = 1
David James1ed3e252011-10-05 20:26:15 -07001036 output.close()
David Jamesfcb70ef2011-02-02 16:02:30 -08001037
David James7358d032011-05-19 10:40:03 -07001038 if KILLED.is_set():
1039 return
1040
David Jamesfcb70ef2011-02-02 16:02:30 -08001041 job = EmergeJobState(target, pkgname, True, output.name, start_timestamp,
1042 retcode)
1043 job_queue.put(job)
1044
1045
1046class LinePrinter(object):
1047 """Helper object to print a single line."""
1048
1049 def __init__(self, line):
1050 self.line = line
1051
1052 def Print(self, seek_locations):
1053 print self.line
1054
1055
1056class JobPrinter(object):
1057 """Helper object to print output of a job."""
1058
1059 def __init__(self, job, unlink=False):
1060 """Print output of job.
1061
1062 If unlink is True, unlink the job output file when done."""
1063 self.current_time = time.time()
1064 self.job = job
1065 self.unlink = unlink
1066
1067 def Print(self, seek_locations):
1068
1069 job = self.job
1070
1071 # Calculate how long the job has been running.
1072 seconds = self.current_time - job.start_timestamp
1073
1074 # Note that we've printed out the job so far.
1075 job.last_output_timestamp = self.current_time
1076
1077 # Note that we're starting the job
1078 info = "job %s (%dm%.1fs)" % (job.pkgname, seconds / 60, seconds % 60)
1079 last_output_seek = seek_locations.get(job.filename, 0)
1080 if last_output_seek:
1081 print "=== Continue output for %s ===" % info
1082 else:
1083 print "=== Start output for %s ===" % info
1084
1085 # Print actual output from job
1086 f = codecs.open(job.filename, encoding='utf-8', errors='replace')
1087 f.seek(last_output_seek)
1088 prefix = job.pkgname + ":"
1089 for line in f:
1090
1091 # Save off our position in the file
1092 if line and line[-1] == "\n":
1093 last_output_seek = f.tell()
1094 line = line[:-1]
1095
1096 # Print our line
1097 print prefix, line.encode('utf-8', 'replace')
1098 f.close()
1099
1100 # Save our last spot in the file so that we don't print out the same
1101 # location twice.
1102 seek_locations[job.filename] = last_output_seek
1103
1104 # Note end of output section
1105 if job.done:
1106 print "=== Complete: %s ===" % info
1107 else:
1108 print "=== Still running: %s ===" % info
1109
1110 if self.unlink:
1111 os.unlink(job.filename)
1112
1113
1114def PrintWorker(queue):
1115 """A worker that prints stuff to the screen as requested."""
1116
1117 def ExitHandler(signum, frame):
David James7358d032011-05-19 10:40:03 -07001118 # Set KILLED flag.
1119 KILLED.set()
1120
David Jamesfcb70ef2011-02-02 16:02:30 -08001121 # Switch to default signal handlers so that we'll die after two signals.
David James7358d032011-05-19 10:40:03 -07001122 signal.signal(signal.SIGINT, KillHandler)
1123 signal.signal(signal.SIGTERM, KillHandler)
David Jamesfcb70ef2011-02-02 16:02:30 -08001124
1125 # Don't exit on the first SIGINT / SIGTERM, because the parent worker will
1126 # handle it and tell us when we need to exit.
1127 signal.signal(signal.SIGINT, ExitHandler)
1128 signal.signal(signal.SIGTERM, ExitHandler)
1129
1130 # seek_locations is a map indicating the position we are at in each file.
1131 # It starts off empty, but is set by the various Print jobs as we go along
1132 # to indicate where we left off in each file.
1133 seek_locations = {}
1134 while True:
1135 try:
1136 job = queue.get()
1137 if job:
1138 job.Print(seek_locations)
David Jamesbccf8eb2011-07-27 14:06:06 -07001139 sys.stdout.flush()
David Jamesfcb70ef2011-02-02 16:02:30 -08001140 else:
1141 break
1142 except IOError as ex:
1143 if ex.errno == errno.EINTR:
1144 # Looks like we received a signal. Keep printing.
1145 continue
1146 raise
1147
David Jamesfcb70ef2011-02-02 16:02:30 -08001148class EmergeQueue(object):
1149 """Class to schedule emerge jobs according to a dependency graph."""
1150
1151 def __init__(self, deps_map, emerge, package_db, show_output):
1152 # Store the dependency graph.
1153 self._deps_map = deps_map
1154 # Initialize the running queue to empty
1155 self._jobs = {}
David James8c7e5e32011-06-28 11:26:03 -07001156 self._ready = []
David Jamesfcb70ef2011-02-02 16:02:30 -08001157 # List of total package installs represented in deps_map.
1158 install_jobs = [x for x in deps_map if deps_map[x]["action"] == "merge"]
1159 self._total_jobs = len(install_jobs)
1160 self._show_output = show_output
1161
1162 if "--pretend" in emerge.opts:
1163 print "Skipping merge because of --pretend mode."
1164 sys.exit(0)
1165
David James7358d032011-05-19 10:40:03 -07001166 # Set a process group so we can easily terminate all children.
1167 os.setsid()
1168
David Jamesfcb70ef2011-02-02 16:02:30 -08001169 # Setup scheduler graph object. This is used by the child processes
1170 # to help schedule jobs.
1171 emerge.scheduler_graph = emerge.depgraph.schedulerGraph()
1172
1173 # Calculate how many jobs we can run in parallel. We don't want to pass
1174 # the --jobs flag over to emerge itself, because that'll tell emerge to
1175 # hide its output, and said output is quite useful for debugging hung
1176 # jobs.
1177 procs = min(self._total_jobs,
1178 emerge.opts.pop("--jobs", multiprocessing.cpu_count()))
David James8c7e5e32011-06-28 11:26:03 -07001179 self._load_avg = emerge.opts.pop("--load-average", None)
David Jamesfcb70ef2011-02-02 16:02:30 -08001180 self._emerge_queue = multiprocessing.Queue()
1181 self._job_queue = multiprocessing.Queue()
1182 self._print_queue = multiprocessing.Queue()
1183 args = (self._emerge_queue, self._job_queue, emerge, package_db)
1184 self._pool = multiprocessing.Pool(procs, EmergeWorker, args)
1185 self._print_worker = multiprocessing.Process(target=PrintWorker,
1186 args=[self._print_queue])
1187 self._print_worker.start()
1188
1189 # Initialize the failed queue to empty.
1190 self._retry_queue = []
1191 self._failed = set()
1192
David Jamesfcb70ef2011-02-02 16:02:30 -08001193 # Setup an exit handler so that we print nice messages if we are
1194 # terminated.
1195 self._SetupExitHandler()
1196
1197 # Schedule our jobs.
1198 for target, info in deps_map.items():
David James8c7e5e32011-06-28 11:26:03 -07001199 if info["nodeps"] or not info["needs"]:
1200 score = (-len(info["tprovides"]), info["binary"], info["idx"])
1201 self._ready.append((score, target))
1202 heapq.heapify(self._ready)
1203 self._procs = procs
1204 self._ScheduleLoop()
1205
1206 # Print an update.
1207 self._Status()
David Jamesfcb70ef2011-02-02 16:02:30 -08001208
1209 def _SetupExitHandler(self):
1210
1211 def ExitHandler(signum, frame):
David James7358d032011-05-19 10:40:03 -07001212 # Set KILLED flag.
1213 KILLED.set()
David Jamesfcb70ef2011-02-02 16:02:30 -08001214
1215 # Kill our signal handlers so we don't get called recursively
David James7358d032011-05-19 10:40:03 -07001216 signal.signal(signal.SIGINT, KillHandler)
1217 signal.signal(signal.SIGTERM, KillHandler)
David Jamesfcb70ef2011-02-02 16:02:30 -08001218
1219 # Print our current job status
1220 for target, job in self._jobs.iteritems():
1221 if job:
1222 self._print_queue.put(JobPrinter(job, unlink=True))
1223
1224 # Notify the user that we are exiting
1225 self._Print("Exiting on signal %s" % signum)
David James7358d032011-05-19 10:40:03 -07001226 self._print_queue.put(None)
1227 self._print_worker.join()
David Jamesfcb70ef2011-02-02 16:02:30 -08001228
1229 # Kill child threads, then exit.
David James7358d032011-05-19 10:40:03 -07001230 os.killpg(0, signal.SIGKILL)
David Jamesfcb70ef2011-02-02 16:02:30 -08001231 sys.exit(1)
1232
1233 # Print out job status when we are killed
1234 signal.signal(signal.SIGINT, ExitHandler)
1235 signal.signal(signal.SIGTERM, ExitHandler)
1236
1237 def _Schedule(self, target):
1238 # We maintain a tree of all deps, if this doesn't need
David James8c7e5e32011-06-28 11:26:03 -07001239 # to be installed just free up its children and continue.
David Jamesfcb70ef2011-02-02 16:02:30 -08001240 # It is possible to reinstall deps of deps, without reinstalling
1241 # first level deps, like so:
1242 # chromeos (merge) -> eselect (nomerge) -> python (merge)
David James8c7e5e32011-06-28 11:26:03 -07001243 this_pkg = self._deps_map.get(target)
1244 if this_pkg is None:
David James386ccd12011-05-04 20:17:42 -07001245 pass
David James8c7e5e32011-06-28 11:26:03 -07001246 elif this_pkg["action"] == "nomerge":
David Jamesfcb70ef2011-02-02 16:02:30 -08001247 self._Finish(target)
David Jamesd20a6d92011-04-26 16:11:59 -07001248 elif target not in self._jobs:
David Jamesfcb70ef2011-02-02 16:02:30 -08001249 # Kick off the build if it's marked to be built.
1250 self._jobs[target] = None
1251 self._emerge_queue.put(target)
David James8c7e5e32011-06-28 11:26:03 -07001252 return True
David Jamesfcb70ef2011-02-02 16:02:30 -08001253
David James8c7e5e32011-06-28 11:26:03 -07001254 def _ScheduleLoop(self):
1255 # If the current load exceeds our desired load average, don't schedule
1256 # more than one job.
1257 if self._load_avg and os.getloadavg()[0] > self._load_avg:
1258 needed_jobs = 1
1259 else:
1260 needed_jobs = self._procs
1261
1262 # Schedule more jobs.
1263 while self._ready and len(self._jobs) < needed_jobs:
1264 score, pkg = heapq.heappop(self._ready)
David James32420cc2011-08-25 21:32:46 -07001265 if pkg not in self._failed:
1266 self._Schedule(pkg)
David Jamesfcb70ef2011-02-02 16:02:30 -08001267
1268 def _Print(self, line):
1269 """Print a single line."""
1270 self._print_queue.put(LinePrinter(line))
1271
1272 def _Status(self):
1273 """Print status."""
1274 current_time = time.time()
1275 no_output = True
1276
1277 # Print interim output every minute if --show-output is used. Otherwise,
1278 # print notifications about running packages every 2 minutes, and print
1279 # full output for jobs that have been running for 60 minutes or more.
1280 if self._show_output:
1281 interval = 60
1282 notify_interval = 0
1283 else:
1284 interval = 60 * 60
1285 notify_interval = 60 * 2
1286 for target, job in self._jobs.iteritems():
1287 if job:
1288 last_timestamp = max(job.start_timestamp, job.last_output_timestamp)
1289 if last_timestamp + interval < current_time:
1290 self._print_queue.put(JobPrinter(job))
1291 job.last_output_timestamp = current_time
1292 no_output = False
1293 elif (notify_interval and
1294 job.last_notify_timestamp + notify_interval < current_time):
1295 job_seconds = current_time - job.start_timestamp
1296 args = (job.pkgname, job_seconds / 60, job_seconds % 60, job.filename)
1297 info = "Still building %s (%dm%.1fs). Logs in %s" % args
1298 job.last_notify_timestamp = current_time
1299 self._Print(info)
1300 no_output = False
1301
1302 # If we haven't printed any messages yet, print a general status message
1303 # here.
1304 if no_output:
1305 seconds = current_time - GLOBAL_START
1306 line = ("Pending %s, Ready %s, Running %s, Retrying %s, Total %s "
1307 "[Time %dm%.1fs Load %s]")
David James8c7e5e32011-06-28 11:26:03 -07001308 load = " ".join(str(x) for x in os.getloadavg())
1309 self._Print(line % (len(self._deps_map), len(self._ready),
1310 len(self._jobs), len(self._retry_queue),
1311 self._total_jobs, seconds / 60, seconds % 60, load))
David Jamesfcb70ef2011-02-02 16:02:30 -08001312
1313 def _Finish(self, target):
David James8c7e5e32011-06-28 11:26:03 -07001314 """Mark a target as completed and unblock dependencies."""
1315 this_pkg = self._deps_map[target]
1316 if this_pkg["needs"] and this_pkg["nodeps"]:
1317 # We got installed, but our deps have not been installed yet. Dependent
1318 # packages should only be installed when our needs have been fully met.
1319 this_pkg["action"] = "nomerge"
1320 else:
1321 finish = []
1322 for dep in this_pkg["provides"]:
1323 dep_pkg = self._deps_map[dep]
1324 del dep_pkg["needs"][target]
1325 if not dep_pkg["needs"]:
1326 if dep_pkg["nodeps"] and dep_pkg["action"] == "nomerge":
1327 self._Finish(dep)
1328 else:
1329 score = (-len(dep_pkg["tprovides"]), dep_pkg["binary"],
1330 dep_pkg["idx"])
1331 heapq.heappush(self._ready, (score, dep))
1332 self._deps_map.pop(target)
David Jamesfcb70ef2011-02-02 16:02:30 -08001333
1334 def _Retry(self):
David James8c7e5e32011-06-28 11:26:03 -07001335 while self._retry_queue:
David Jamesfcb70ef2011-02-02 16:02:30 -08001336 target = self._retry_queue.pop(0)
David James8c7e5e32011-06-28 11:26:03 -07001337 if self._Schedule(target):
1338 self._Print("Retrying emerge of %s." % target)
1339 break
David Jamesfcb70ef2011-02-02 16:02:30 -08001340
1341 def _Exit(self):
1342 # Tell emerge workers to exit. They all exit when 'None' is pushed
1343 # to the queue.
1344 self._emerge_queue.put(None)
1345 self._pool.close()
1346 self._pool.join()
David James97ce8902011-08-16 09:51:05 -07001347 self._emerge_queue.close()
1348 self._emerge_queue = None
David Jamesfcb70ef2011-02-02 16:02:30 -08001349
1350 # Now that our workers are finished, we can kill the print queue.
1351 self._print_queue.put(None)
1352 self._print_worker.join()
David James97ce8902011-08-16 09:51:05 -07001353 self._print_queue.close()
1354 self._print_queue = None
1355 self._job_queue.close()
1356 self._job_queue = None
David Jamesfcb70ef2011-02-02 16:02:30 -08001357
1358 def Run(self):
1359 """Run through the scheduled ebuilds.
1360
1361 Keep running so long as we have uninstalled packages in the
1362 dependency graph to merge.
1363 """
1364 while self._deps_map:
1365 # Check here that we are actually waiting for something.
1366 if (self._emerge_queue.empty() and
1367 self._job_queue.empty() and
1368 not self._jobs and
David James8c7e5e32011-06-28 11:26:03 -07001369 not self._ready and
David Jamesfcb70ef2011-02-02 16:02:30 -08001370 self._deps_map):
1371 # If we have failed on a package, retry it now.
1372 if self._retry_queue:
1373 self._Retry()
1374 else:
1375 # Tell child threads to exit.
1376 self._Exit()
1377
1378 # The dependency map is helpful for debugging failures.
1379 PrintDepsMap(self._deps_map)
1380
1381 # Tell the user why we're exiting.
1382 if self._failed:
1383 print "Packages failed: %s" % " ,".join(self._failed)
1384 else:
1385 print "Deadlock! Circular dependencies!"
1386 sys.exit(1)
1387
David Jamesa74289a2011-08-12 10:41:24 -07001388 for i in range(3):
1389 try:
1390 job = self._job_queue.get(timeout=5)
1391 break
1392 except Queue.Empty:
1393 # Check if any more jobs can be scheduled.
1394 self._ScheduleLoop()
1395 else:
1396 # Print an update every 15 seconds.
David Jamesfcb70ef2011-02-02 16:02:30 -08001397 self._Status()
1398 continue
1399
1400 target = job.target
1401
1402 if not job.done:
1403 self._jobs[target] = job
1404 self._Print("Started %s (logged in %s)" % (target, job.filename))
1405 continue
1406
1407 # Print output of job
1408 if self._show_output or job.retcode != 0:
1409 self._print_queue.put(JobPrinter(job, unlink=True))
1410 else:
1411 os.unlink(job.filename)
1412 del self._jobs[target]
1413
1414 seconds = time.time() - job.start_timestamp
1415 details = "%s (in %dm%.1fs)" % (target, seconds / 60, seconds % 60)
David James32420cc2011-08-25 21:32:46 -07001416 previously_failed = target in self._failed
David Jamesfcb70ef2011-02-02 16:02:30 -08001417
1418 # Complain if necessary.
1419 if job.retcode != 0:
1420 # Handle job failure.
David James32420cc2011-08-25 21:32:46 -07001421 if previously_failed:
David Jamesfcb70ef2011-02-02 16:02:30 -08001422 # If this job has failed previously, give up.
1423 self._Print("Failed %s. Your build has failed." % details)
1424 else:
1425 # Queue up this build to try again after a long while.
1426 self._retry_queue.append(target)
1427 self._failed.add(target)
1428 self._Print("Failed %s, retrying later." % details)
1429 else:
David James32420cc2011-08-25 21:32:46 -07001430 if previously_failed:
1431 # Remove target from list of failed packages.
1432 self._failed.remove(target)
1433
1434 self._Print("Completed %s" % details)
1435
1436 # Mark as completed and unblock waiting ebuilds.
1437 self._Finish(target)
1438
1439 if previously_failed and self._retry_queue:
David Jamesfcb70ef2011-02-02 16:02:30 -08001440 # If we have successfully retried a failed package, and there
1441 # are more failed packages, try the next one. We will only have
1442 # one retrying package actively running at a time.
1443 self._Retry()
1444
David Jamesfcb70ef2011-02-02 16:02:30 -08001445
David James8c7e5e32011-06-28 11:26:03 -07001446 # Schedule pending jobs and print an update.
1447 self._ScheduleLoop()
1448 self._Status()
David Jamesfcb70ef2011-02-02 16:02:30 -08001449
1450 # Tell child threads to exit.
1451 self._Print("Merge complete")
1452 self._Exit()
1453
1454
1455def main():
1456
David James57437532011-05-06 15:51:21 -07001457 parallel_emerge_args = sys.argv[:]
David Jamesfcb70ef2011-02-02 16:02:30 -08001458 deps = DepGraphGenerator()
David James57437532011-05-06 15:51:21 -07001459 deps.Initialize(parallel_emerge_args[1:])
David Jamesfcb70ef2011-02-02 16:02:30 -08001460 emerge = deps.emerge
1461
1462 if emerge.action is not None:
1463 sys.argv = deps.ParseParallelEmergeArgs(sys.argv)
1464 sys.exit(emerge_main())
1465 elif not emerge.cmdline_packages:
1466 Usage()
1467 sys.exit(1)
1468
1469 # Unless we're in pretend mode, there's not much point running without
1470 # root access. We need to be able to install packages.
1471 #
1472 # NOTE: Even if you're running --pretend, it's a good idea to run
1473 # parallel_emerge with root access so that portage can write to the
1474 # dependency cache. This is important for performance.
1475 if "--pretend" not in emerge.opts and portage.secpass < 2:
1476 print "parallel_emerge: superuser access is required."
1477 sys.exit(1)
1478
1479 if "--quiet" not in emerge.opts:
1480 cmdline_packages = " ".join(emerge.cmdline_packages)
David Jamesfcb70ef2011-02-02 16:02:30 -08001481 print "Starting fast-emerge."
1482 print " Building package %s on %s" % (cmdline_packages,
1483 deps.board or "root")
David Jamesfcb70ef2011-02-02 16:02:30 -08001484
David James386ccd12011-05-04 20:17:42 -07001485 deps_tree, deps_info = deps.GenDependencyTree()
David Jamesfcb70ef2011-02-02 16:02:30 -08001486
1487 # You want me to be verbose? I'll give you two trees! Twice as much value.
1488 if "--tree" in emerge.opts and "--verbose" in emerge.opts:
1489 deps.PrintTree(deps_tree)
1490
David James386ccd12011-05-04 20:17:42 -07001491 deps_graph = deps.GenDependencyGraph(deps_tree, deps_info)
David Jamesfcb70ef2011-02-02 16:02:30 -08001492
1493 # OK, time to print out our progress so far.
1494 deps.PrintInstallPlan(deps_graph)
1495 if "--tree" in emerge.opts:
1496 PrintDepsMap(deps_graph)
1497
1498 # Are we upgrading portage? If so, and there are more packages to merge,
1499 # schedule a restart of parallel_emerge to merge the rest. This ensures that
1500 # we pick up all updates to portage settings before merging any more
1501 # packages.
1502 portage_upgrade = False
1503 root = emerge.settings["ROOT"]
1504 final_db = emerge.depgraph._dynamic_config.mydbapi[root]
1505 if root == "/":
1506 for db_pkg in final_db.match_pkgs("sys-apps/portage"):
1507 portage_pkg = deps_graph.get(db_pkg.cpv)
1508 if portage_pkg and len(deps_graph) > 1:
1509 portage_pkg["needs"].clear()
1510 portage_pkg["provides"].clear()
1511 deps_graph = { str(db_pkg.cpv): portage_pkg }
1512 portage_upgrade = True
1513 if "--quiet" not in emerge.opts:
1514 print "Upgrading portage first, then restarting..."
1515
1516 # Run the queued emerges.
1517 scheduler = EmergeQueue(deps_graph, emerge, deps.package_db, deps.show_output)
1518 scheduler.Run()
David James97ce8902011-08-16 09:51:05 -07001519 scheduler = None
David Jamesfcb70ef2011-02-02 16:02:30 -08001520
David Jamesfcb70ef2011-02-02 16:02:30 -08001521 # Update environment (library cache, symlinks, etc.)
1522 if deps.board and "--pretend" not in emerge.opts:
Brian Harring1e3fefc2011-10-03 12:09:19 -07001523 # Turn off env-update suppression used above for disabling
1524 # env-update during merging.
1525 os.environ["FEATURES"] += " -no-env-update"
1526 # Also kick the existing settings should they be reused...
1527 if hasattr(portage, 'settings'):
1528 portage.settings.unlock()
1529 portage.settings.features.discard('no-env-update')
David Jamesfcb70ef2011-02-02 16:02:30 -08001530 portage.env_update()
1531
1532 # If we already upgraded portage, we don't need to do so again. But we do
1533 # need to upgrade the rest of the packages. So we'll go ahead and do that.
David Jamesebc3ae02011-05-21 20:46:10 -07001534 #
1535 # In order to grant the child permission to run setsid, we need to run sudo
1536 # again. We preserve SUDO_USER here in case an ebuild depends on it.
David Jamesfcb70ef2011-02-02 16:02:30 -08001537 if portage_upgrade:
David Jamesebc3ae02011-05-21 20:46:10 -07001538 sudo = ["sudo", "-E", "SUDO_USER=%s" % os.environ.get("SUDO_USER", "")]
1539 args = sudo + parallel_emerge_args + ["--exclude=sys-apps/portage"]
1540 os.execvp("sudo", args)
David Jamesfcb70ef2011-02-02 16:02:30 -08001541
1542 print "Done"
1543 sys.exit(0)
1544
1545if __name__ == "__main__":
1546 main()