blob: 1743084cfebd786f9de38d5cf9060e5faf0143fd [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 Jamesfcb70ef2011-02-02 16:02:30 -080090def Usage():
91 """Print usage."""
92 print "Usage:"
David James386ccd12011-05-04 20:17:42 -070093 print " ./parallel_emerge [--board=BOARD] [--workon=PKGS]"
David Jamesfcb70ef2011-02-02 16:02:30 -080094 print " [--rebuild] [emerge args] package"
95 print
96 print "Packages specified as workon packages are always built from source."
David Jamesfcb70ef2011-02-02 16:02:30 -080097 print
98 print "The --workon argument is mainly useful when you want to build and"
99 print "install packages that you are working on unconditionally, but do not"
100 print "to have to rev the package to indicate you want to build it from"
101 print "source. The build_packages script will automatically supply the"
102 print "workon argument to emerge, ensuring that packages selected using"
103 print "cros-workon are rebuilt."
104 print
105 print "The --rebuild option rebuilds packages whenever their dependencies"
106 print "are changed. This ensures that your build is correct."
107 sys.exit(1)
108
109
David Jamesfcb70ef2011-02-02 16:02:30 -0800110# Global start time
111GLOBAL_START = time.time()
112
David James7358d032011-05-19 10:40:03 -0700113# Whether process has been killed by a signal.
114KILLED = multiprocessing.Event()
115
David Jamesfcb70ef2011-02-02 16:02:30 -0800116
117class EmergeData(object):
118 """This simple struct holds various emerge variables.
119
120 This struct helps us easily pass emerge variables around as a unit.
121 These variables are used for calculating dependencies and installing
122 packages.
123 """
124
David Jamesbf1e3442011-05-28 07:44:20 -0700125 __slots__ = ["action", "cmdline_packages", "depgraph", "favorites",
126 "mtimedb", "opts", "root_config", "scheduler_graph",
127 "settings", "spinner", "trees"]
David Jamesfcb70ef2011-02-02 16:02:30 -0800128
129 def __init__(self):
130 # The action the user requested. If the user is installing packages, this
131 # is None. If the user is doing anything other than installing packages,
132 # this will contain the action name, which will map exactly to the
133 # long-form name of the associated emerge option.
134 #
135 # Example: If you call parallel_emerge --unmerge package, the action name
136 # will be "unmerge"
137 self.action = None
138
139 # The list of packages the user passed on the command-line.
140 self.cmdline_packages = None
141
142 # The emerge dependency graph. It'll contain all the packages involved in
143 # this merge, along with their versions.
144 self.depgraph = None
145
David Jamesbf1e3442011-05-28 07:44:20 -0700146 # The list of candidates to add to the world file.
147 self.favorites = None
148
David Jamesfcb70ef2011-02-02 16:02:30 -0800149 # A dict of the options passed to emerge. This dict has been cleaned up
150 # a bit by parse_opts, so that it's a bit easier for the emerge code to
151 # look at the options.
152 #
153 # Emerge takes a few shortcuts in its cleanup process to make parsing of
154 # the options dict easier. For example, if you pass in "--usepkg=n", the
155 # "--usepkg" flag is just left out of the dictionary altogether. Because
156 # --usepkg=n is the default, this makes parsing easier, because emerge
157 # can just assume that if "--usepkg" is in the dictionary, it's enabled.
158 #
159 # These cleanup processes aren't applied to all options. For example, the
160 # --with-bdeps flag is passed in as-is. For a full list of the cleanups
161 # applied by emerge, see the parse_opts function in the _emerge.main
162 # package.
163 self.opts = None
164
165 # A dictionary used by portage to maintain global state. This state is
166 # loaded from disk when portage starts up, and saved to disk whenever we
167 # call mtimedb.commit().
168 #
169 # This database contains information about global updates (i.e., what
170 # version of portage we have) and what we're currently doing. Portage
171 # saves what it is currently doing in this database so that it can be
172 # resumed when you call it with the --resume option.
173 #
174 # parallel_emerge does not save what it is currently doing in the mtimedb,
175 # so we do not support the --resume option.
176 self.mtimedb = None
177
178 # The portage configuration for our current root. This contains the portage
179 # settings (see below) and the three portage trees for our current root.
180 # (The three portage trees are explained below, in the documentation for
181 # the "trees" member.)
182 self.root_config = None
183
184 # The scheduler graph is used by emerge to calculate what packages to
185 # install. We don't actually install any deps, so this isn't really used,
186 # but we pass it in to the Scheduler object anyway.
187 self.scheduler_graph = None
188
189 # Portage settings for our current session. Most of these settings are set
190 # in make.conf inside our current install root.
191 self.settings = None
192
193 # The spinner, which spews stuff to stdout to indicate that portage is
194 # doing something. We maintain our own spinner, so we set the portage
195 # spinner to "silent" mode.
196 self.spinner = None
197
198 # The portage trees. There are separate portage trees for each root. To get
199 # the portage tree for the current root, you can look in self.trees[root],
200 # where root = self.settings["ROOT"].
201 #
202 # In each root, there are three trees: vartree, porttree, and bintree.
203 # - vartree: A database of the currently-installed packages.
204 # - porttree: A database of ebuilds, that can be used to build packages.
205 # - bintree: A database of binary packages.
206 self.trees = None
207
208
209class DepGraphGenerator(object):
210 """Grab dependency information about packages from portage.
211
212 Typical usage:
213 deps = DepGraphGenerator()
214 deps.Initialize(sys.argv[1:])
215 deps_tree, deps_info = deps.GenDependencyTree()
216 deps_graph = deps.GenDependencyGraph(deps_tree, deps_info)
217 deps.PrintTree(deps_tree)
218 PrintDepsMap(deps_graph)
219 """
220
David James386ccd12011-05-04 20:17:42 -0700221 __slots__ = ["board", "emerge", "package_db", "show_output"]
David Jamesfcb70ef2011-02-02 16:02:30 -0800222
223 def __init__(self):
224 self.board = None
225 self.emerge = EmergeData()
David Jamesfcb70ef2011-02-02 16:02:30 -0800226 self.package_db = {}
David Jamesfcb70ef2011-02-02 16:02:30 -0800227 self.show_output = False
David Jamesfcb70ef2011-02-02 16:02:30 -0800228
229 def ParseParallelEmergeArgs(self, argv):
230 """Read the parallel emerge arguments from the command-line.
231
232 We need to be compatible with emerge arg format. We scrape arguments that
233 are specific to parallel_emerge, and pass through the rest directly to
234 emerge.
235 Args:
236 argv: arguments list
237 Returns:
238 Arguments that don't belong to parallel_emerge
239 """
240 emerge_args = []
241 for arg in argv:
242 # Specifically match arguments that are specific to parallel_emerge, and
243 # pass through the rest.
244 if arg.startswith("--board="):
245 self.board = arg.replace("--board=", "")
246 elif arg.startswith("--workon="):
247 workon_str = arg.replace("--workon=", "")
David James7a1ea4b2011-10-13 15:06:41 -0700248 emerge_args.append("--reinstall-atoms=%s" % workon_str)
249 emerge_args.append("--usepkg-exclude=%s" % workon_str)
David Jamesfcb70ef2011-02-02 16:02:30 -0800250 elif arg.startswith("--force-remote-binary="):
251 force_remote_binary = arg.replace("--force-remote-binary=", "")
David James7a1ea4b2011-10-13 15:06:41 -0700252 emerge_args.append("--useoldpkg-atoms=%s" % force_remote_binary)
David Jamesfcb70ef2011-02-02 16:02:30 -0800253 elif arg == "--show-output":
254 self.show_output = True
David James386ccd12011-05-04 20:17:42 -0700255 elif arg == "--rebuild":
David James7a1ea4b2011-10-13 15:06:41 -0700256 emerge_args.append("--rebuild-if-unbuilt")
David Jamesfcb70ef2011-02-02 16:02:30 -0800257 else:
258 # Not one of our options, so pass through to emerge.
259 emerge_args.append(arg)
260
David James386ccd12011-05-04 20:17:42 -0700261 # These packages take a really long time to build, so, for expediency, we
262 # are blacklisting them from automatic rebuilds because one of their
263 # dependencies needs to be recompiled.
264 for pkg in ("chromeos-base/chromeos-chrome", "media-plugins/o3d",
265 "dev-java/icedtea"):
David James7a1ea4b2011-10-13 15:06:41 -0700266 emerge_args.append("--rebuild-exclude=%s" % pkg)
David Jamesfcb70ef2011-02-02 16:02:30 -0800267
268 return emerge_args
269
270 def Initialize(self, args):
271 """Initializer. Parses arguments and sets up portage state."""
272
273 # Parse and strip out args that are just intended for parallel_emerge.
274 emerge_args = self.ParseParallelEmergeArgs(args)
275
276 # Setup various environment variables based on our current board. These
277 # variables are normally setup inside emerge-${BOARD}, but since we don't
278 # call that script, we have to set it up here. These variables serve to
279 # point our tools at /build/BOARD and to setup cross compiles to the
280 # appropriate board as configured in toolchain.conf.
281 if self.board:
282 os.environ["PORTAGE_CONFIGROOT"] = "/build/" + self.board
283 os.environ["PORTAGE_SYSROOT"] = "/build/" + self.board
284 os.environ["SYSROOT"] = "/build/" + self.board
285 srcroot = "%s/../../src" % os.path.dirname(os.path.realpath(__file__))
286 # Strip the variant out of the board name to look for the toolchain. This
287 # is similar to what setup_board does.
288 board_no_variant = self.board.split('_')[0]
289 public_toolchain_path = ("%s/overlays/overlay-%s/toolchain.conf" %
290 (srcroot, board_no_variant))
291 private_toolchain_path = (
292 "%s/private-overlays/overlay-%s-private/toolchain.conf" %
293 (srcroot, board_no_variant))
294 if os.path.isfile(public_toolchain_path):
295 toolchain_path = public_toolchain_path
296 elif os.path.isfile(private_toolchain_path):
297 toolchain_path = private_toolchain_path
298 else:
299 print "Not able to locate toolchain.conf in board overlays"
300 sys.exit(1)
301
302 f = open(toolchain_path)
303 os.environ["CHOST"] = f.readline().strip()
304 f.close()
305
306 # Although CHROMEOS_ROOT isn't specific to boards, it's normally setup
307 # inside emerge-${BOARD}, so we set it up here for compatibility. It
308 # will be going away soon as we migrate to CROS_WORKON_SRCROOT.
309 os.environ.setdefault("CHROMEOS_ROOT", os.environ["HOME"] + "/trunk")
310
311 # Turn off interactive delays
312 os.environ["EBEEP_IGNORE"] = "1"
313 os.environ["EPAUSE_IGNORE"] = "1"
314 os.environ["UNMERGE_DELAY"] = "0"
315
316 # Parse the emerge options.
David Jamesea3ca332011-05-26 11:48:29 -0700317 action, opts, cmdline_packages = parse_opts(emerge_args, silent=True)
David Jamesfcb70ef2011-02-02 16:02:30 -0800318
319 # Set environment variables based on options. Portage normally sets these
320 # environment variables in emerge_main, but we can't use that function,
321 # because it also does a bunch of other stuff that we don't want.
322 # TODO(davidjames): Patch portage to move this logic into a function we can
323 # reuse here.
324 if "--debug" in opts:
325 os.environ["PORTAGE_DEBUG"] = "1"
326 if "--config-root" in opts:
327 os.environ["PORTAGE_CONFIGROOT"] = opts["--config-root"]
328 if "--root" in opts:
329 os.environ["ROOT"] = opts["--root"]
330 if "--accept-properties" in opts:
331 os.environ["ACCEPT_PROPERTIES"] = opts["--accept-properties"]
332
333 # Portage has two flags for doing collision protection: collision-protect
334 # and protect-owned. The protect-owned feature is enabled by default and
335 # is quite useful: it checks to make sure that we don't have multiple
336 # packages that own the same file. The collision-protect feature is more
337 # strict, and less useful: it fails if it finds a conflicting file, even
338 # if that file was created by an earlier ebuild that failed to install.
339 #
340 # We want to disable collision-protect here because we don't handle
341 # failures during the merge step very well. Sometimes we leave old files
342 # lying around and they cause problems, so for now we disable the flag.
343 # TODO(davidjames): Look for a better solution.
344 features = os.environ.get("FEATURES", "") + " -collision-protect"
345
David Jamesdeebd692011-05-09 17:02:52 -0700346 # Install packages in parallel.
347 features = features + " parallel-install"
348
David Jamesfcb70ef2011-02-02 16:02:30 -0800349 # If we're installing packages to the board, and we're not using the
350 # official flag, we can enable the following optimizations:
351 # 1) Don't lock during install step. This allows multiple packages to be
352 # installed at once. This is safe because our board packages do not
353 # muck with each other during the post-install step.
354 # 2) Don't update the environment until the end of the build. This is
355 # safe because board packages don't need to run during the build --
356 # they're cross-compiled, so our CPU architecture doesn't support them
357 # anyway.
358 if self.board and os.environ.get("CHROMEOS_OFFICIAL") != "1":
359 os.environ.setdefault("PORTAGE_LOCKS", "false")
David Jamesdeebd692011-05-09 17:02:52 -0700360 features = features + " -ebuild-locks no-env-update"
David Jamesfcb70ef2011-02-02 16:02:30 -0800361
362 os.environ["FEATURES"] = features
363
364 # Now that we've setup the necessary environment variables, we can load the
365 # emerge config from disk.
366 settings, trees, mtimedb = load_emerge_config()
367
David Jamesea3ca332011-05-26 11:48:29 -0700368 # Add in EMERGE_DEFAULT_OPTS, if specified.
369 tmpcmdline = []
370 if "--ignore-default-opts" not in opts:
371 tmpcmdline.extend(settings["EMERGE_DEFAULT_OPTS"].split())
372 tmpcmdline.extend(emerge_args)
373 action, opts, cmdline_packages = parse_opts(tmpcmdline)
374
375 # If we're installing to the board, we want the --root-deps option so that
376 # portage will install the build dependencies to that location as well.
377 if self.board:
378 opts.setdefault("--root-deps", True)
379
David Jamesfcb70ef2011-02-02 16:02:30 -0800380 # Check whether our portage tree is out of date. Typically, this happens
381 # when you're setting up a new portage tree, such as in setup_board and
382 # make_chroot. In that case, portage applies a bunch of global updates
383 # here. Once the updates are finished, we need to commit any changes
384 # that the global update made to our mtimedb, and reload the config.
385 #
386 # Portage normally handles this logic in emerge_main, but again, we can't
387 # use that function here.
388 if _global_updates(trees, mtimedb["updates"]):
389 mtimedb.commit()
390 settings, trees, mtimedb = load_emerge_config(trees=trees)
391
392 # Setup implied options. Portage normally handles this logic in
393 # emerge_main.
394 if "--buildpkgonly" in opts or "buildpkg" in settings.features:
395 opts.setdefault("--buildpkg", True)
396 if "--getbinpkgonly" in opts:
397 opts.setdefault("--usepkgonly", True)
398 opts.setdefault("--getbinpkg", True)
399 if "getbinpkg" in settings.features:
400 # Per emerge_main, FEATURES=getbinpkg overrides --getbinpkg=n
401 opts["--getbinpkg"] = True
402 if "--getbinpkg" in opts or "--usepkgonly" in opts:
403 opts.setdefault("--usepkg", True)
404 if "--fetch-all-uri" in opts:
405 opts.setdefault("--fetchonly", True)
406 if "--skipfirst" in opts:
407 opts.setdefault("--resume", True)
408 if "--buildpkgonly" in opts:
409 # --buildpkgonly will not merge anything, so it overrides all binary
410 # package options.
411 for opt in ("--getbinpkg", "--getbinpkgonly",
412 "--usepkg", "--usepkgonly"):
413 opts.pop(opt, None)
414 if (settings.get("PORTAGE_DEBUG", "") == "1" and
415 "python-trace" in settings.features):
416 portage.debug.set_trace(True)
417
418 # Complain about unsupported options
David James386ccd12011-05-04 20:17:42 -0700419 for opt in ("--ask", "--ask-enter-invalid", "--resume", "--skipfirst"):
David Jamesfcb70ef2011-02-02 16:02:30 -0800420 if opt in opts:
421 print "%s is not supported by parallel_emerge" % opt
422 sys.exit(1)
423
424 # Make emerge specific adjustments to the config (e.g. colors!)
425 adjust_configs(opts, trees)
426
427 # Save our configuration so far in the emerge object
428 emerge = self.emerge
429 emerge.action, emerge.opts = action, opts
430 emerge.settings, emerge.trees, emerge.mtimedb = settings, trees, mtimedb
431 emerge.cmdline_packages = cmdline_packages
432 root = settings["ROOT"]
433 emerge.root_config = trees[root]["root_config"]
434
David James386ccd12011-05-04 20:17:42 -0700435 if "--usepkg" in opts:
David Jamesfcb70ef2011-02-02 16:02:30 -0800436 emerge.trees[root]["bintree"].populate("--getbinpkg" in opts)
437
David Jamesfcb70ef2011-02-02 16:02:30 -0800438 def CreateDepgraph(self, emerge, packages):
439 """Create an emerge depgraph object."""
440 # Setup emerge options.
441 emerge_opts = emerge.opts.copy()
442
David James386ccd12011-05-04 20:17:42 -0700443 # Ask portage to build a dependency graph. with the options we specified
444 # above.
David Jamesfcb70ef2011-02-02 16:02:30 -0800445 params = create_depgraph_params(emerge_opts, emerge.action)
David Jamesbf1e3442011-05-28 07:44:20 -0700446 success, depgraph, favorites = backtrack_depgraph(
David James386ccd12011-05-04 20:17:42 -0700447 emerge.settings, emerge.trees, emerge_opts, params, emerge.action,
448 packages, emerge.spinner)
449 emerge.depgraph = depgraph
David Jamesfcb70ef2011-02-02 16:02:30 -0800450
David James386ccd12011-05-04 20:17:42 -0700451 # Is it impossible to honor the user's request? Bail!
452 if not success:
453 depgraph.display_problems()
454 sys.exit(1)
David Jamesfcb70ef2011-02-02 16:02:30 -0800455
456 emerge.depgraph = depgraph
David Jamesbf1e3442011-05-28 07:44:20 -0700457 emerge.favorites = favorites
David Jamesfcb70ef2011-02-02 16:02:30 -0800458
David Jamesdeebd692011-05-09 17:02:52 -0700459 # Prime and flush emerge caches.
460 root = emerge.settings["ROOT"]
461 vardb = emerge.trees[root]["vartree"].dbapi
David James0bdc5de2011-05-12 16:22:26 -0700462 if "--pretend" not in emerge.opts:
463 vardb.counter_tick()
David Jamesdeebd692011-05-09 17:02:52 -0700464 vardb.flush_cache()
465
David James386ccd12011-05-04 20:17:42 -0700466 def GenDependencyTree(self):
David Jamesfcb70ef2011-02-02 16:02:30 -0800467 """Get dependency tree info from emerge.
468
David Jamesfcb70ef2011-02-02 16:02:30 -0800469 Returns:
470 Dependency tree
471 """
472 start = time.time()
473
474 emerge = self.emerge
475
476 # Create a list of packages to merge
477 packages = set(emerge.cmdline_packages[:])
David Jamesfcb70ef2011-02-02 16:02:30 -0800478
479 # Tell emerge to be quiet. We print plenty of info ourselves so we don't
480 # need any extra output from portage.
481 portage.util.noiselimit = -1
482
483 # My favorite feature: The silent spinner. It doesn't spin. Ever.
484 # I'd disable the colors by default too, but they look kind of cool.
485 emerge.spinner = stdout_spinner()
486 emerge.spinner.update = emerge.spinner.update_quiet
487
488 if "--quiet" not in emerge.opts:
489 print "Calculating deps..."
490
491 self.CreateDepgraph(emerge, packages)
492 depgraph = emerge.depgraph
493
494 # Build our own tree from the emerge digraph.
495 deps_tree = {}
496 digraph = depgraph._dynamic_config.digraph
David James3f778802011-08-25 19:31:45 -0700497 root = emerge.settings["ROOT"]
498 final_db = depgraph._dynamic_config.mydbapi[root]
David Jamesfcb70ef2011-02-02 16:02:30 -0800499 for node, node_deps in digraph.nodes.items():
500 # Calculate dependency packages that need to be installed first. Each
501 # child on the digraph is a dependency. The "operation" field specifies
502 # what we're doing (e.g. merge, uninstall, etc.). The "priorities" array
503 # contains the type of dependency (e.g. build, runtime, runtime_post,
504 # etc.)
505 #
David Jamesfcb70ef2011-02-02 16:02:30 -0800506 # Portage refers to the identifiers for packages as a CPV. This acronym
507 # stands for Component/Path/Version.
508 #
509 # Here's an example CPV: chromeos-base/power_manager-0.0.1-r1
510 # Split up, this CPV would be:
511 # C -- Component: chromeos-base
512 # P -- Path: power_manager
513 # V -- Version: 0.0.1-r1
514 #
515 # We just refer to CPVs as packages here because it's easier.
516 deps = {}
517 for child, priorities in node_deps[0].items():
David James3f778802011-08-25 19:31:45 -0700518 if isinstance(child, Package) and child.root == root:
519 cpv = str(child.cpv)
520 action = str(child.operation)
521
522 # If we're uninstalling a package, check whether Portage is
523 # installing a replacement. If so, just depend on the installation
524 # of the new package, because the old package will automatically
525 # be uninstalled at that time.
526 if action == "uninstall":
527 for pkg in final_db.match_pkgs(child.slot_atom):
528 cpv = str(pkg.cpv)
529 action = "merge"
530 break
531
532 deps[cpv] = dict(action=action,
533 deptypes=[str(x) for x in priorities],
534 deps={})
David Jamesfcb70ef2011-02-02 16:02:30 -0800535
536 # We've built our list of deps, so we can add our package to the tree.
David James3f778802011-08-25 19:31:45 -0700537 if isinstance(node, Package) and node.root == root:
David Jamesfcb70ef2011-02-02 16:02:30 -0800538 deps_tree[str(node.cpv)] = dict(action=str(node.operation),
539 deps=deps)
540
David Jamesfcb70ef2011-02-02 16:02:30 -0800541 # Ask portage for its install plan, so that we can only throw out
David James386ccd12011-05-04 20:17:42 -0700542 # dependencies that portage throws out.
David Jamesfcb70ef2011-02-02 16:02:30 -0800543 deps_info = {}
544 for pkg in depgraph.altlist():
545 if isinstance(pkg, Package):
David James3f778802011-08-25 19:31:45 -0700546 assert pkg.root == root
David Jamesfcb70ef2011-02-02 16:02:30 -0800547 self.package_db[pkg.cpv] = pkg
548
David Jamesfcb70ef2011-02-02 16:02:30 -0800549 # Save off info about the package
David James386ccd12011-05-04 20:17:42 -0700550 deps_info[str(pkg.cpv)] = {"idx": len(deps_info)}
David Jamesfcb70ef2011-02-02 16:02:30 -0800551
552 seconds = time.time() - start
553 if "--quiet" not in emerge.opts:
554 print "Deps calculated in %dm%.1fs" % (seconds / 60, seconds % 60)
555
556 return deps_tree, deps_info
557
558 def PrintTree(self, deps, depth=""):
559 """Print the deps we have seen in the emerge output.
560
561 Args:
562 deps: Dependency tree structure.
563 depth: Allows printing the tree recursively, with indentation.
564 """
565 for entry in sorted(deps):
566 action = deps[entry]["action"]
567 print "%s %s (%s)" % (depth, entry, action)
568 self.PrintTree(deps[entry]["deps"], depth=depth + " ")
569
David James386ccd12011-05-04 20:17:42 -0700570 def GenDependencyGraph(self, deps_tree, deps_info):
David Jamesfcb70ef2011-02-02 16:02:30 -0800571 """Generate a doubly linked dependency graph.
572
573 Args:
574 deps_tree: Dependency tree structure.
575 deps_info: More details on the dependencies.
576 Returns:
577 Deps graph in the form of a dict of packages, with each package
578 specifying a "needs" list and "provides" list.
579 """
580 emerge = self.emerge
581 root = emerge.settings["ROOT"]
582
David Jamesfcb70ef2011-02-02 16:02:30 -0800583 # deps_map is the actual dependency graph.
584 #
585 # Each package specifies a "needs" list and a "provides" list. The "needs"
586 # list indicates which packages we depend on. The "provides" list
587 # indicates the reverse dependencies -- what packages need us.
588 #
589 # We also provide some other information in the dependency graph:
590 # - action: What we're planning on doing with this package. Generally,
591 # "merge", "nomerge", or "uninstall"
David Jamesfcb70ef2011-02-02 16:02:30 -0800592 deps_map = {}
593
594 def ReverseTree(packages):
595 """Convert tree to digraph.
596
597 Take the tree of package -> requirements and reverse it to a digraph of
598 buildable packages -> packages they unblock.
599 Args:
600 packages: Tree(s) of dependencies.
601 Returns:
602 Unsanitized digraph.
603 """
David James8c7e5e32011-06-28 11:26:03 -0700604 binpkg_phases = set(["setup", "preinst", "postinst"])
David James3f778802011-08-25 19:31:45 -0700605 needed_dep_types = set(["blocker", "buildtime", "runtime"])
David Jamesfcb70ef2011-02-02 16:02:30 -0800606 for pkg in packages:
607
608 # Create an entry for the package
609 action = packages[pkg]["action"]
David James8c7e5e32011-06-28 11:26:03 -0700610 default_pkg = {"needs": {}, "provides": set(), "action": action,
611 "nodeps": False, "binary": False}
David Jamesfcb70ef2011-02-02 16:02:30 -0800612 this_pkg = deps_map.setdefault(pkg, default_pkg)
613
David James8c7e5e32011-06-28 11:26:03 -0700614 if pkg in deps_info:
615 this_pkg["idx"] = deps_info[pkg]["idx"]
616
617 # If a package doesn't have any defined phases that might use the
618 # dependent packages (i.e. pkg_setup, pkg_preinst, or pkg_postinst),
619 # we can install this package before its deps are ready.
620 emerge_pkg = self.package_db.get(pkg)
621 if emerge_pkg and emerge_pkg.type_name == "binary":
622 this_pkg["binary"] = True
623 defined_phases = emerge_pkg.metadata.defined_phases
624 defined_binpkg_phases = binpkg_phases.intersection(defined_phases)
625 if not defined_binpkg_phases:
626 this_pkg["nodeps"] = True
627
David Jamesfcb70ef2011-02-02 16:02:30 -0800628 # Create entries for dependencies of this package first.
629 ReverseTree(packages[pkg]["deps"])
630
631 # Add dependencies to this package.
632 for dep, dep_item in packages[pkg]["deps"].iteritems():
David James8c7e5e32011-06-28 11:26:03 -0700633 # We only need to enforce strict ordering of dependencies if the
David James3f778802011-08-25 19:31:45 -0700634 # dependency is a blocker, or is a buildtime or runtime dependency.
635 # (I.e., ignored, optional, and runtime_post dependencies don't
636 # depend on ordering.)
David James8c7e5e32011-06-28 11:26:03 -0700637 dep_types = dep_item["deptypes"]
638 if needed_dep_types.intersection(dep_types):
639 deps_map[dep]["provides"].add(pkg)
640 this_pkg["needs"][dep] = "/".join(dep_types)
David Jamesfcb70ef2011-02-02 16:02:30 -0800641
David James3f778802011-08-25 19:31:45 -0700642 # If there's a blocker, Portage may need to move files from one
643 # package to another, which requires editing the CONTENTS files of
644 # both packages. To avoid race conditions while editing this file,
645 # the two packages must not be installed in parallel, so we can't
646 # safely ignore dependencies. See http://crosbug.com/19328
647 if "blocker" in dep_types:
648 this_pkg["nodeps"] = False
649
David Jamesfcb70ef2011-02-02 16:02:30 -0800650 def FindCycles():
651 """Find cycles in the dependency tree.
652
653 Returns:
654 A dict mapping cyclic packages to a dict of the deps that cause
655 cycles. For each dep that causes cycles, it returns an example
656 traversal of the graph that shows the cycle.
657 """
658
659 def FindCyclesAtNode(pkg, cycles, unresolved, resolved):
660 """Find cycles in cyclic dependencies starting at specified package.
661
662 Args:
663 pkg: Package identifier.
664 cycles: A dict mapping cyclic packages to a dict of the deps that
665 cause cycles. For each dep that causes cycles, it returns an
666 example traversal of the graph that shows the cycle.
667 unresolved: Nodes that have been visited but are not fully processed.
668 resolved: Nodes that have been visited and are fully processed.
669 """
670 pkg_cycles = cycles.get(pkg)
671 if pkg in resolved and not pkg_cycles:
672 # If we already looked at this package, and found no cyclic
673 # dependencies, we can stop now.
674 return
675 unresolved.append(pkg)
676 for dep in deps_map[pkg]["needs"]:
677 if dep in unresolved:
678 idx = unresolved.index(dep)
679 mycycle = unresolved[idx:] + [dep]
680 for i in range(len(mycycle) - 1):
681 pkg1, pkg2 = mycycle[i], mycycle[i+1]
682 cycles.setdefault(pkg1, {}).setdefault(pkg2, mycycle)
683 elif not pkg_cycles or dep not in pkg_cycles:
684 # Looks like we haven't seen this edge before.
685 FindCyclesAtNode(dep, cycles, unresolved, resolved)
686 unresolved.pop()
687 resolved.add(pkg)
688
689 cycles, unresolved, resolved = {}, [], set()
690 for pkg in deps_map:
691 FindCyclesAtNode(pkg, cycles, unresolved, resolved)
692 return cycles
693
David James386ccd12011-05-04 20:17:42 -0700694 def RemoveUnusedPackages():
David Jamesfcb70ef2011-02-02 16:02:30 -0800695 """Remove installed packages, propagating dependencies."""
David Jamesfcb70ef2011-02-02 16:02:30 -0800696 # Schedule packages that aren't on the install list for removal
697 rm_pkgs = set(deps_map.keys()) - set(deps_info.keys())
698
David Jamesfcb70ef2011-02-02 16:02:30 -0800699 # Remove the packages we don't want, simplifying the graph and making
700 # it easier for us to crack cycles.
701 for pkg in sorted(rm_pkgs):
702 this_pkg = deps_map[pkg]
703 needs = this_pkg["needs"]
704 provides = this_pkg["provides"]
705 for dep in needs:
706 dep_provides = deps_map[dep]["provides"]
707 dep_provides.update(provides)
708 dep_provides.discard(pkg)
709 dep_provides.discard(dep)
710 for target in provides:
711 target_needs = deps_map[target]["needs"]
712 target_needs.update(needs)
713 target_needs.pop(pkg, None)
714 target_needs.pop(target, None)
715 del deps_map[pkg]
716
717 def PrintCycleBreak(basedep, dep, mycycle):
718 """Print details about a cycle that we are planning on breaking.
719
720 We are breaking a cycle where dep needs basedep. mycycle is an
721 example cycle which contains dep -> basedep."""
722
David Jamesfcb70ef2011-02-02 16:02:30 -0800723 needs = deps_map[dep]["needs"]
724 depinfo = needs.get(basedep, "deleted")
David Jamesfcb70ef2011-02-02 16:02:30 -0800725
David James3f778802011-08-25 19:31:45 -0700726 # It's OK to swap install order for blockers, as long as the two
727 # packages aren't installed in parallel. If there is a cycle, then
728 # we know the packages depend on each other already, so we can drop the
729 # blocker safely without printing a warning.
730 if depinfo == "blocker":
731 return
732
David Jamesfcb70ef2011-02-02 16:02:30 -0800733 # Notify the user that we're breaking a cycle.
734 print "Breaking %s -> %s (%s)" % (dep, basedep, depinfo)
735
736 # Show cycle.
737 for i in range(len(mycycle) - 1):
738 pkg1, pkg2 = mycycle[i], mycycle[i+1]
739 needs = deps_map[pkg1]["needs"]
740 depinfo = needs.get(pkg2, "deleted")
741 if pkg1 == dep and pkg2 == basedep:
742 depinfo = depinfo + ", deleting"
743 print " %s -> %s (%s)" % (pkg1, pkg2, depinfo)
744
745 def SanitizeTree():
746 """Remove circular dependencies.
747
748 We prune all dependencies involved in cycles that go against the emerge
749 ordering. This has a nice property: we're guaranteed to merge
750 dependencies in the same order that portage does.
751
752 Because we don't treat any dependencies as "soft" unless they're killed
753 by a cycle, we pay attention to a larger number of dependencies when
754 merging. This hurts performance a bit, but helps reliability.
755 """
756 start = time.time()
757 cycles = FindCycles()
758 while cycles:
759 for dep, mycycles in cycles.iteritems():
760 for basedep, mycycle in mycycles.iteritems():
761 if deps_info[basedep]["idx"] >= deps_info[dep]["idx"]:
Matt Tennant08797302011-10-17 16:18:45 -0700762 if "--quiet" not in emerge.opts:
763 PrintCycleBreak(basedep, dep, mycycle)
David Jamesfcb70ef2011-02-02 16:02:30 -0800764 del deps_map[dep]["needs"][basedep]
765 deps_map[basedep]["provides"].remove(dep)
766 cycles = FindCycles()
767 seconds = time.time() - start
768 if "--quiet" not in emerge.opts and seconds >= 0.1:
769 print "Tree sanitized in %dm%.1fs" % (seconds / 60, seconds % 60)
770
David James8c7e5e32011-06-28 11:26:03 -0700771 def FindRecursiveProvides(pkg, seen):
772 """Find all nodes that require a particular package.
773
774 Assumes that graph is acyclic.
775
776 Args:
777 pkg: Package identifier.
778 seen: Nodes that have been visited so far.
779 """
780 if pkg in seen:
781 return
782 seen.add(pkg)
783 info = deps_map[pkg]
784 info["tprovides"] = info["provides"].copy()
785 for dep in info["provides"]:
786 FindRecursiveProvides(dep, seen)
787 info["tprovides"].update(deps_map[dep]["tprovides"])
788
David Jamesa22906f2011-05-04 19:53:26 -0700789 ReverseTree(deps_tree)
David Jamesa22906f2011-05-04 19:53:26 -0700790
David James386ccd12011-05-04 20:17:42 -0700791 # We need to remove unused packages so that we can use the dependency
792 # ordering of the install process to show us what cycles to crack.
793 RemoveUnusedPackages()
David Jamesfcb70ef2011-02-02 16:02:30 -0800794 SanitizeTree()
David James8c7e5e32011-06-28 11:26:03 -0700795 seen = set()
796 for pkg in deps_map:
797 FindRecursiveProvides(pkg, seen)
David Jamesfcb70ef2011-02-02 16:02:30 -0800798 return deps_map
799
800 def PrintInstallPlan(self, deps_map):
801 """Print an emerge-style install plan.
802
803 The install plan lists what packages we're installing, in order.
804 It's useful for understanding what parallel_emerge is doing.
805
806 Args:
807 deps_map: The dependency graph.
808 """
809
810 def InstallPlanAtNode(target, deps_map):
811 nodes = []
812 nodes.append(target)
813 for dep in deps_map[target]["provides"]:
814 del deps_map[dep]["needs"][target]
815 if not deps_map[dep]["needs"]:
816 nodes.extend(InstallPlanAtNode(dep, deps_map))
817 return nodes
818
819 deps_map = copy.deepcopy(deps_map)
820 install_plan = []
821 plan = set()
822 for target, info in deps_map.iteritems():
823 if not info["needs"] and target not in plan:
824 for item in InstallPlanAtNode(target, deps_map):
825 plan.add(item)
826 install_plan.append(self.package_db[item])
827
828 for pkg in plan:
829 del deps_map[pkg]
830
831 if deps_map:
832 print "Cyclic dependencies:", " ".join(deps_map)
833 PrintDepsMap(deps_map)
834 sys.exit(1)
835
836 self.emerge.depgraph.display(install_plan)
837
838
839def PrintDepsMap(deps_map):
840 """Print dependency graph, for each package list it's prerequisites."""
841 for i in sorted(deps_map):
842 print "%s: (%s) needs" % (i, deps_map[i]["action"])
843 needs = deps_map[i]["needs"]
844 for j in sorted(needs):
845 print " %s" % (j)
846 if not needs:
847 print " no dependencies"
848
849
850class EmergeJobState(object):
851 __slots__ = ["done", "filename", "last_notify_timestamp", "last_output_seek",
852 "last_output_timestamp", "pkgname", "retcode", "start_timestamp",
853 "target"]
854
855 def __init__(self, target, pkgname, done, filename, start_timestamp,
856 retcode=None):
857
858 # The full name of the target we're building (e.g.
859 # chromeos-base/chromeos-0.0.1-r60)
860 self.target = target
861
862 # The short name of the target we're building (e.g. chromeos-0.0.1-r60)
863 self.pkgname = pkgname
864
865 # Whether the job is done. (True if the job is done; false otherwise.)
866 self.done = done
867
868 # The filename where output is currently stored.
869 self.filename = filename
870
871 # The timestamp of the last time we printed the name of the log file. We
872 # print this at the beginning of the job, so this starts at
873 # start_timestamp.
874 self.last_notify_timestamp = start_timestamp
875
876 # The location (in bytes) of the end of the last complete line we printed.
877 # This starts off at zero. We use this to jump to the right place when we
878 # print output from the same ebuild multiple times.
879 self.last_output_seek = 0
880
881 # The timestamp of the last time we printed output. Since we haven't
882 # printed output yet, this starts at zero.
883 self.last_output_timestamp = 0
884
885 # The return code of our job, if the job is actually finished.
886 self.retcode = retcode
887
888 # The timestamp when our job started.
889 self.start_timestamp = start_timestamp
890
891
David James7358d032011-05-19 10:40:03 -0700892def KillHandler(signum, frame):
893 # Kill self and all subprocesses.
894 os.killpg(0, signal.SIGKILL)
895
David Jamesfcb70ef2011-02-02 16:02:30 -0800896def SetupWorkerSignals():
897 def ExitHandler(signum, frame):
David James7358d032011-05-19 10:40:03 -0700898 # Set KILLED flag.
899 KILLED.set()
David James13cead42011-05-18 16:22:01 -0700900
David James7358d032011-05-19 10:40:03 -0700901 # Remove our signal handlers so we don't get called recursively.
902 signal.signal(signal.SIGINT, KillHandler)
903 signal.signal(signal.SIGTERM, KillHandler)
David Jamesfcb70ef2011-02-02 16:02:30 -0800904
905 # Ensure that we exit quietly and cleanly, if possible, when we receive
906 # SIGTERM or SIGINT signals. By default, when the user hits CTRL-C, all
907 # of the child processes will print details about KeyboardInterrupt
908 # exceptions, which isn't very helpful.
909 signal.signal(signal.SIGINT, ExitHandler)
910 signal.signal(signal.SIGTERM, ExitHandler)
911
David James1ed3e252011-10-05 20:26:15 -0700912def EmergeProcess(scheduler, output):
913 """Merge a package in a subprocess.
914
915 Args:
916 scheduler: Scheduler object.
917 output: Temporary file to write output.
918
919 Returns:
920 The exit code returned by the subprocess.
921 """
922 pid = os.fork()
923 if pid == 0:
924 try:
925 # Sanity checks.
926 if sys.stdout.fileno() != 1: raise Exception("sys.stdout.fileno() != 1")
927 if sys.stderr.fileno() != 2: raise Exception("sys.stderr.fileno() != 2")
928
929 # - Redirect 1 (stdout) and 2 (stderr) at our temporary file.
930 # - Redirect 0 to point at sys.stdin. In this case, sys.stdin
931 # points at a file reading os.devnull, because multiprocessing mucks
932 # with sys.stdin.
933 # - Leave the sys.stdin and output filehandles alone.
934 fd_pipes = {0: sys.stdin.fileno(),
935 1: output.fileno(),
936 2: output.fileno(),
937 sys.stdin.fileno(): sys.stdin.fileno(),
938 output.fileno(): output.fileno()}
939 portage.process._setup_pipes(fd_pipes)
940
941 # Portage doesn't like when sys.stdin.fileno() != 0, so point sys.stdin
942 # at the filehandle we just created in _setup_pipes.
943 if sys.stdin.fileno() != 0:
944 sys.stdin = os.fdopen(0, "r")
945
946 # Actually do the merge.
947 retval = scheduler.merge()
948
949 # We catch all exceptions here (including SystemExit, KeyboardInterrupt,
950 # etc) so as to ensure that we don't confuse the multiprocessing module,
951 # which expects that all forked children exit with os._exit().
952 except:
953 traceback.print_exc(file=output)
954 retval = 1
955 sys.stdout.flush()
956 sys.stderr.flush()
957 output.flush()
958 os._exit(retval)
959 else:
960 # Return the exit code of the subprocess.
961 return os.waitpid(pid, 0)[1]
David Jamesfcb70ef2011-02-02 16:02:30 -0800962
963def EmergeWorker(task_queue, job_queue, emerge, package_db):
964 """This worker emerges any packages given to it on the task_queue.
965
966 Args:
967 task_queue: The queue of tasks for this worker to do.
968 job_queue: The queue of results from the worker.
969 emerge: An EmergeData() object.
970 package_db: A dict, mapping package ids to portage Package objects.
971
972 It expects package identifiers to be passed to it via task_queue. When
973 a task is started, it pushes the (target, filename) to the started_queue.
974 The output is stored in filename. When a merge starts or finishes, we push
975 EmergeJobState objects to the job_queue.
976 """
977
978 SetupWorkerSignals()
979 settings, trees, mtimedb = emerge.settings, emerge.trees, emerge.mtimedb
David Jamesdeebd692011-05-09 17:02:52 -0700980
981 # Disable flushing of caches to save on I/O.
David James7a1ea4b2011-10-13 15:06:41 -0700982 root = emerge.settings["ROOT"]
983 vardb = emerge.trees[root]["vartree"].dbapi
984 vardb._flush_cache_enabled = False
David Jamesdeebd692011-05-09 17:02:52 -0700985
David Jamesfcb70ef2011-02-02 16:02:30 -0800986 opts, spinner = emerge.opts, emerge.spinner
987 opts["--nodeps"] = True
David Jamesfcb70ef2011-02-02 16:02:30 -0800988 while True:
989 # Wait for a new item to show up on the queue. This is a blocking wait,
990 # so if there's nothing to do, we just sit here.
991 target = task_queue.get()
992 if not target:
993 # If target is None, this means that the main thread wants us to quit.
994 # The other workers need to exit too, so we'll push the message back on
995 # to the queue so they'll get it too.
996 task_queue.put(target)
997 return
David James7358d032011-05-19 10:40:03 -0700998 if KILLED.is_set():
999 return
1000
David Jamesfcb70ef2011-02-02 16:02:30 -08001001 db_pkg = package_db[target]
1002 db_pkg.root_config = emerge.root_config
1003 install_list = [db_pkg]
1004 pkgname = db_pkg.pf
1005 output = tempfile.NamedTemporaryFile(prefix=pkgname + "-", delete=False)
1006 start_timestamp = time.time()
1007 job = EmergeJobState(target, pkgname, False, output.name, start_timestamp)
1008 job_queue.put(job)
1009 if "--pretend" in opts:
1010 retcode = 0
1011 else:
David Jamesfcb70ef2011-02-02 16:02:30 -08001012 try:
David James386ccd12011-05-04 20:17:42 -07001013 emerge.scheduler_graph.mergelist = install_list
1014 scheduler = Scheduler(settings, trees, mtimedb, opts, spinner,
David Jamesbf1e3442011-05-28 07:44:20 -07001015 favorites=emerge.favorites, graph_config=emerge.scheduler_graph)
David Jamesace2e212011-07-13 11:47:39 -07001016
1017 # Enable blocker handling even though we're in --nodeps mode. This
1018 # allows us to unmerge the blocker after we've merged the replacement.
1019 scheduler._opts_ignore_blockers = frozenset()
1020
David James1ed3e252011-10-05 20:26:15 -07001021 retcode = EmergeProcess(scheduler, output)
David Jamesfcb70ef2011-02-02 16:02:30 -08001022 except Exception:
1023 traceback.print_exc(file=output)
1024 retcode = 1
David James1ed3e252011-10-05 20:26:15 -07001025 output.close()
David Jamesfcb70ef2011-02-02 16:02:30 -08001026
David James7358d032011-05-19 10:40:03 -07001027 if KILLED.is_set():
1028 return
1029
David Jamesfcb70ef2011-02-02 16:02:30 -08001030 job = EmergeJobState(target, pkgname, True, output.name, start_timestamp,
1031 retcode)
1032 job_queue.put(job)
1033
1034
1035class LinePrinter(object):
1036 """Helper object to print a single line."""
1037
1038 def __init__(self, line):
1039 self.line = line
1040
1041 def Print(self, seek_locations):
1042 print self.line
1043
1044
1045class JobPrinter(object):
1046 """Helper object to print output of a job."""
1047
1048 def __init__(self, job, unlink=False):
1049 """Print output of job.
1050
1051 If unlink is True, unlink the job output file when done."""
1052 self.current_time = time.time()
1053 self.job = job
1054 self.unlink = unlink
1055
1056 def Print(self, seek_locations):
1057
1058 job = self.job
1059
1060 # Calculate how long the job has been running.
1061 seconds = self.current_time - job.start_timestamp
1062
1063 # Note that we've printed out the job so far.
1064 job.last_output_timestamp = self.current_time
1065
1066 # Note that we're starting the job
1067 info = "job %s (%dm%.1fs)" % (job.pkgname, seconds / 60, seconds % 60)
1068 last_output_seek = seek_locations.get(job.filename, 0)
1069 if last_output_seek:
1070 print "=== Continue output for %s ===" % info
1071 else:
1072 print "=== Start output for %s ===" % info
1073
1074 # Print actual output from job
1075 f = codecs.open(job.filename, encoding='utf-8', errors='replace')
1076 f.seek(last_output_seek)
1077 prefix = job.pkgname + ":"
1078 for line in f:
1079
1080 # Save off our position in the file
1081 if line and line[-1] == "\n":
1082 last_output_seek = f.tell()
1083 line = line[:-1]
1084
1085 # Print our line
1086 print prefix, line.encode('utf-8', 'replace')
1087 f.close()
1088
1089 # Save our last spot in the file so that we don't print out the same
1090 # location twice.
1091 seek_locations[job.filename] = last_output_seek
1092
1093 # Note end of output section
1094 if job.done:
1095 print "=== Complete: %s ===" % info
1096 else:
1097 print "=== Still running: %s ===" % info
1098
1099 if self.unlink:
1100 os.unlink(job.filename)
1101
1102
1103def PrintWorker(queue):
1104 """A worker that prints stuff to the screen as requested."""
1105
1106 def ExitHandler(signum, frame):
David James7358d032011-05-19 10:40:03 -07001107 # Set KILLED flag.
1108 KILLED.set()
1109
David Jamesfcb70ef2011-02-02 16:02:30 -08001110 # Switch to default signal handlers so that we'll die after two signals.
David James7358d032011-05-19 10:40:03 -07001111 signal.signal(signal.SIGINT, KillHandler)
1112 signal.signal(signal.SIGTERM, KillHandler)
David Jamesfcb70ef2011-02-02 16:02:30 -08001113
1114 # Don't exit on the first SIGINT / SIGTERM, because the parent worker will
1115 # handle it and tell us when we need to exit.
1116 signal.signal(signal.SIGINT, ExitHandler)
1117 signal.signal(signal.SIGTERM, ExitHandler)
1118
1119 # seek_locations is a map indicating the position we are at in each file.
1120 # It starts off empty, but is set by the various Print jobs as we go along
1121 # to indicate where we left off in each file.
1122 seek_locations = {}
1123 while True:
1124 try:
1125 job = queue.get()
1126 if job:
1127 job.Print(seek_locations)
David Jamesbccf8eb2011-07-27 14:06:06 -07001128 sys.stdout.flush()
David Jamesfcb70ef2011-02-02 16:02:30 -08001129 else:
1130 break
1131 except IOError as ex:
1132 if ex.errno == errno.EINTR:
1133 # Looks like we received a signal. Keep printing.
1134 continue
1135 raise
1136
David Jamesfcb70ef2011-02-02 16:02:30 -08001137class EmergeQueue(object):
1138 """Class to schedule emerge jobs according to a dependency graph."""
1139
1140 def __init__(self, deps_map, emerge, package_db, show_output):
1141 # Store the dependency graph.
1142 self._deps_map = deps_map
1143 # Initialize the running queue to empty
1144 self._jobs = {}
David James8c7e5e32011-06-28 11:26:03 -07001145 self._ready = []
David Jamesfcb70ef2011-02-02 16:02:30 -08001146 # List of total package installs represented in deps_map.
1147 install_jobs = [x for x in deps_map if deps_map[x]["action"] == "merge"]
1148 self._total_jobs = len(install_jobs)
1149 self._show_output = show_output
1150
1151 if "--pretend" in emerge.opts:
1152 print "Skipping merge because of --pretend mode."
1153 sys.exit(0)
1154
David James7358d032011-05-19 10:40:03 -07001155 # Set a process group so we can easily terminate all children.
1156 os.setsid()
1157
David Jamesfcb70ef2011-02-02 16:02:30 -08001158 # Setup scheduler graph object. This is used by the child processes
1159 # to help schedule jobs.
1160 emerge.scheduler_graph = emerge.depgraph.schedulerGraph()
1161
1162 # Calculate how many jobs we can run in parallel. We don't want to pass
1163 # the --jobs flag over to emerge itself, because that'll tell emerge to
1164 # hide its output, and said output is quite useful for debugging hung
1165 # jobs.
1166 procs = min(self._total_jobs,
1167 emerge.opts.pop("--jobs", multiprocessing.cpu_count()))
David James8c7e5e32011-06-28 11:26:03 -07001168 self._load_avg = emerge.opts.pop("--load-average", None)
David Jamesfcb70ef2011-02-02 16:02:30 -08001169 self._emerge_queue = multiprocessing.Queue()
1170 self._job_queue = multiprocessing.Queue()
1171 self._print_queue = multiprocessing.Queue()
1172 args = (self._emerge_queue, self._job_queue, emerge, package_db)
1173 self._pool = multiprocessing.Pool(procs, EmergeWorker, args)
1174 self._print_worker = multiprocessing.Process(target=PrintWorker,
1175 args=[self._print_queue])
1176 self._print_worker.start()
1177
1178 # Initialize the failed queue to empty.
1179 self._retry_queue = []
1180 self._failed = set()
1181
David Jamesfcb70ef2011-02-02 16:02:30 -08001182 # Setup an exit handler so that we print nice messages if we are
1183 # terminated.
1184 self._SetupExitHandler()
1185
1186 # Schedule our jobs.
1187 for target, info in deps_map.items():
David James8c7e5e32011-06-28 11:26:03 -07001188 if info["nodeps"] or not info["needs"]:
1189 score = (-len(info["tprovides"]), info["binary"], info["idx"])
1190 self._ready.append((score, target))
1191 heapq.heapify(self._ready)
1192 self._procs = procs
1193 self._ScheduleLoop()
1194
1195 # Print an update.
1196 self._Status()
David Jamesfcb70ef2011-02-02 16:02:30 -08001197
1198 def _SetupExitHandler(self):
1199
1200 def ExitHandler(signum, frame):
David James7358d032011-05-19 10:40:03 -07001201 # Set KILLED flag.
1202 KILLED.set()
David Jamesfcb70ef2011-02-02 16:02:30 -08001203
1204 # Kill our signal handlers so we don't get called recursively
David James7358d032011-05-19 10:40:03 -07001205 signal.signal(signal.SIGINT, KillHandler)
1206 signal.signal(signal.SIGTERM, KillHandler)
David Jamesfcb70ef2011-02-02 16:02:30 -08001207
1208 # Print our current job status
1209 for target, job in self._jobs.iteritems():
1210 if job:
1211 self._print_queue.put(JobPrinter(job, unlink=True))
1212
1213 # Notify the user that we are exiting
1214 self._Print("Exiting on signal %s" % signum)
David James7358d032011-05-19 10:40:03 -07001215 self._print_queue.put(None)
1216 self._print_worker.join()
David Jamesfcb70ef2011-02-02 16:02:30 -08001217
1218 # Kill child threads, then exit.
David James7358d032011-05-19 10:40:03 -07001219 os.killpg(0, signal.SIGKILL)
David Jamesfcb70ef2011-02-02 16:02:30 -08001220 sys.exit(1)
1221
1222 # Print out job status when we are killed
1223 signal.signal(signal.SIGINT, ExitHandler)
1224 signal.signal(signal.SIGTERM, ExitHandler)
1225
1226 def _Schedule(self, target):
1227 # We maintain a tree of all deps, if this doesn't need
David James8c7e5e32011-06-28 11:26:03 -07001228 # to be installed just free up its children and continue.
David Jamesfcb70ef2011-02-02 16:02:30 -08001229 # It is possible to reinstall deps of deps, without reinstalling
1230 # first level deps, like so:
1231 # chromeos (merge) -> eselect (nomerge) -> python (merge)
David James8c7e5e32011-06-28 11:26:03 -07001232 this_pkg = self._deps_map.get(target)
1233 if this_pkg is None:
David James386ccd12011-05-04 20:17:42 -07001234 pass
David James8c7e5e32011-06-28 11:26:03 -07001235 elif this_pkg["action"] == "nomerge":
David Jamesfcb70ef2011-02-02 16:02:30 -08001236 self._Finish(target)
David Jamesd20a6d92011-04-26 16:11:59 -07001237 elif target not in self._jobs:
David Jamesfcb70ef2011-02-02 16:02:30 -08001238 # Kick off the build if it's marked to be built.
1239 self._jobs[target] = None
1240 self._emerge_queue.put(target)
David James8c7e5e32011-06-28 11:26:03 -07001241 return True
David Jamesfcb70ef2011-02-02 16:02:30 -08001242
David James8c7e5e32011-06-28 11:26:03 -07001243 def _ScheduleLoop(self):
1244 # If the current load exceeds our desired load average, don't schedule
1245 # more than one job.
1246 if self._load_avg and os.getloadavg()[0] > self._load_avg:
1247 needed_jobs = 1
1248 else:
1249 needed_jobs = self._procs
1250
1251 # Schedule more jobs.
1252 while self._ready and len(self._jobs) < needed_jobs:
1253 score, pkg = heapq.heappop(self._ready)
David James32420cc2011-08-25 21:32:46 -07001254 if pkg not in self._failed:
1255 self._Schedule(pkg)
David Jamesfcb70ef2011-02-02 16:02:30 -08001256
1257 def _Print(self, line):
1258 """Print a single line."""
1259 self._print_queue.put(LinePrinter(line))
1260
1261 def _Status(self):
1262 """Print status."""
1263 current_time = time.time()
1264 no_output = True
1265
1266 # Print interim output every minute if --show-output is used. Otherwise,
1267 # print notifications about running packages every 2 minutes, and print
1268 # full output for jobs that have been running for 60 minutes or more.
1269 if self._show_output:
1270 interval = 60
1271 notify_interval = 0
1272 else:
1273 interval = 60 * 60
1274 notify_interval = 60 * 2
1275 for target, job in self._jobs.iteritems():
1276 if job:
1277 last_timestamp = max(job.start_timestamp, job.last_output_timestamp)
1278 if last_timestamp + interval < current_time:
1279 self._print_queue.put(JobPrinter(job))
1280 job.last_output_timestamp = current_time
1281 no_output = False
1282 elif (notify_interval and
1283 job.last_notify_timestamp + notify_interval < current_time):
1284 job_seconds = current_time - job.start_timestamp
1285 args = (job.pkgname, job_seconds / 60, job_seconds % 60, job.filename)
1286 info = "Still building %s (%dm%.1fs). Logs in %s" % args
1287 job.last_notify_timestamp = current_time
1288 self._Print(info)
1289 no_output = False
1290
1291 # If we haven't printed any messages yet, print a general status message
1292 # here.
1293 if no_output:
1294 seconds = current_time - GLOBAL_START
1295 line = ("Pending %s, Ready %s, Running %s, Retrying %s, Total %s "
1296 "[Time %dm%.1fs Load %s]")
David James8c7e5e32011-06-28 11:26:03 -07001297 load = " ".join(str(x) for x in os.getloadavg())
1298 self._Print(line % (len(self._deps_map), len(self._ready),
1299 len(self._jobs), len(self._retry_queue),
1300 self._total_jobs, seconds / 60, seconds % 60, load))
David Jamesfcb70ef2011-02-02 16:02:30 -08001301
1302 def _Finish(self, target):
David James8c7e5e32011-06-28 11:26:03 -07001303 """Mark a target as completed and unblock dependencies."""
1304 this_pkg = self._deps_map[target]
1305 if this_pkg["needs"] and this_pkg["nodeps"]:
1306 # We got installed, but our deps have not been installed yet. Dependent
1307 # packages should only be installed when our needs have been fully met.
1308 this_pkg["action"] = "nomerge"
1309 else:
1310 finish = []
1311 for dep in this_pkg["provides"]:
1312 dep_pkg = self._deps_map[dep]
1313 del dep_pkg["needs"][target]
1314 if not dep_pkg["needs"]:
1315 if dep_pkg["nodeps"] and dep_pkg["action"] == "nomerge":
1316 self._Finish(dep)
1317 else:
1318 score = (-len(dep_pkg["tprovides"]), dep_pkg["binary"],
1319 dep_pkg["idx"])
1320 heapq.heappush(self._ready, (score, dep))
1321 self._deps_map.pop(target)
David Jamesfcb70ef2011-02-02 16:02:30 -08001322
1323 def _Retry(self):
David James8c7e5e32011-06-28 11:26:03 -07001324 while self._retry_queue:
David Jamesfcb70ef2011-02-02 16:02:30 -08001325 target = self._retry_queue.pop(0)
David James8c7e5e32011-06-28 11:26:03 -07001326 if self._Schedule(target):
1327 self._Print("Retrying emerge of %s." % target)
1328 break
David Jamesfcb70ef2011-02-02 16:02:30 -08001329
1330 def _Exit(self):
1331 # Tell emerge workers to exit. They all exit when 'None' is pushed
1332 # to the queue.
1333 self._emerge_queue.put(None)
1334 self._pool.close()
1335 self._pool.join()
David James97ce8902011-08-16 09:51:05 -07001336 self._emerge_queue.close()
1337 self._emerge_queue = None
David Jamesfcb70ef2011-02-02 16:02:30 -08001338
1339 # Now that our workers are finished, we can kill the print queue.
1340 self._print_queue.put(None)
1341 self._print_worker.join()
David James97ce8902011-08-16 09:51:05 -07001342 self._print_queue.close()
1343 self._print_queue = None
1344 self._job_queue.close()
1345 self._job_queue = None
David Jamesfcb70ef2011-02-02 16:02:30 -08001346
1347 def Run(self):
1348 """Run through the scheduled ebuilds.
1349
1350 Keep running so long as we have uninstalled packages in the
1351 dependency graph to merge.
1352 """
1353 while self._deps_map:
1354 # Check here that we are actually waiting for something.
1355 if (self._emerge_queue.empty() and
1356 self._job_queue.empty() and
1357 not self._jobs and
David James8c7e5e32011-06-28 11:26:03 -07001358 not self._ready and
David Jamesfcb70ef2011-02-02 16:02:30 -08001359 self._deps_map):
1360 # If we have failed on a package, retry it now.
1361 if self._retry_queue:
1362 self._Retry()
1363 else:
1364 # Tell child threads to exit.
1365 self._Exit()
1366
1367 # The dependency map is helpful for debugging failures.
1368 PrintDepsMap(self._deps_map)
1369
1370 # Tell the user why we're exiting.
1371 if self._failed:
1372 print "Packages failed: %s" % " ,".join(self._failed)
1373 else:
1374 print "Deadlock! Circular dependencies!"
1375 sys.exit(1)
1376
David Jamesa74289a2011-08-12 10:41:24 -07001377 for i in range(3):
1378 try:
1379 job = self._job_queue.get(timeout=5)
1380 break
1381 except Queue.Empty:
1382 # Check if any more jobs can be scheduled.
1383 self._ScheduleLoop()
1384 else:
1385 # Print an update every 15 seconds.
David Jamesfcb70ef2011-02-02 16:02:30 -08001386 self._Status()
1387 continue
1388
1389 target = job.target
1390
1391 if not job.done:
1392 self._jobs[target] = job
1393 self._Print("Started %s (logged in %s)" % (target, job.filename))
1394 continue
1395
1396 # Print output of job
1397 if self._show_output or job.retcode != 0:
1398 self._print_queue.put(JobPrinter(job, unlink=True))
1399 else:
1400 os.unlink(job.filename)
1401 del self._jobs[target]
1402
1403 seconds = time.time() - job.start_timestamp
1404 details = "%s (in %dm%.1fs)" % (target, seconds / 60, seconds % 60)
David James32420cc2011-08-25 21:32:46 -07001405 previously_failed = target in self._failed
David Jamesfcb70ef2011-02-02 16:02:30 -08001406
1407 # Complain if necessary.
1408 if job.retcode != 0:
1409 # Handle job failure.
David James32420cc2011-08-25 21:32:46 -07001410 if previously_failed:
David Jamesfcb70ef2011-02-02 16:02:30 -08001411 # If this job has failed previously, give up.
1412 self._Print("Failed %s. Your build has failed." % details)
1413 else:
1414 # Queue up this build to try again after a long while.
1415 self._retry_queue.append(target)
1416 self._failed.add(target)
1417 self._Print("Failed %s, retrying later." % details)
1418 else:
David James32420cc2011-08-25 21:32:46 -07001419 if previously_failed:
1420 # Remove target from list of failed packages.
1421 self._failed.remove(target)
1422
1423 self._Print("Completed %s" % details)
1424
1425 # Mark as completed and unblock waiting ebuilds.
1426 self._Finish(target)
1427
1428 if previously_failed and self._retry_queue:
David Jamesfcb70ef2011-02-02 16:02:30 -08001429 # If we have successfully retried a failed package, and there
1430 # are more failed packages, try the next one. We will only have
1431 # one retrying package actively running at a time.
1432 self._Retry()
1433
David Jamesfcb70ef2011-02-02 16:02:30 -08001434
David James8c7e5e32011-06-28 11:26:03 -07001435 # Schedule pending jobs and print an update.
1436 self._ScheduleLoop()
1437 self._Status()
David Jamesfcb70ef2011-02-02 16:02:30 -08001438
1439 # Tell child threads to exit.
1440 self._Print("Merge complete")
1441 self._Exit()
1442
1443
1444def main():
1445
David James57437532011-05-06 15:51:21 -07001446 parallel_emerge_args = sys.argv[:]
David Jamesfcb70ef2011-02-02 16:02:30 -08001447 deps = DepGraphGenerator()
David James57437532011-05-06 15:51:21 -07001448 deps.Initialize(parallel_emerge_args[1:])
David Jamesfcb70ef2011-02-02 16:02:30 -08001449 emerge = deps.emerge
1450
1451 if emerge.action is not None:
1452 sys.argv = deps.ParseParallelEmergeArgs(sys.argv)
1453 sys.exit(emerge_main())
1454 elif not emerge.cmdline_packages:
1455 Usage()
1456 sys.exit(1)
1457
1458 # Unless we're in pretend mode, there's not much point running without
1459 # root access. We need to be able to install packages.
1460 #
1461 # NOTE: Even if you're running --pretend, it's a good idea to run
1462 # parallel_emerge with root access so that portage can write to the
1463 # dependency cache. This is important for performance.
1464 if "--pretend" not in emerge.opts and portage.secpass < 2:
1465 print "parallel_emerge: superuser access is required."
1466 sys.exit(1)
1467
1468 if "--quiet" not in emerge.opts:
1469 cmdline_packages = " ".join(emerge.cmdline_packages)
David Jamesfcb70ef2011-02-02 16:02:30 -08001470 print "Starting fast-emerge."
1471 print " Building package %s on %s" % (cmdline_packages,
1472 deps.board or "root")
David Jamesfcb70ef2011-02-02 16:02:30 -08001473
David James386ccd12011-05-04 20:17:42 -07001474 deps_tree, deps_info = deps.GenDependencyTree()
David Jamesfcb70ef2011-02-02 16:02:30 -08001475
1476 # You want me to be verbose? I'll give you two trees! Twice as much value.
1477 if "--tree" in emerge.opts and "--verbose" in emerge.opts:
1478 deps.PrintTree(deps_tree)
1479
David James386ccd12011-05-04 20:17:42 -07001480 deps_graph = deps.GenDependencyGraph(deps_tree, deps_info)
David Jamesfcb70ef2011-02-02 16:02:30 -08001481
1482 # OK, time to print out our progress so far.
1483 deps.PrintInstallPlan(deps_graph)
1484 if "--tree" in emerge.opts:
1485 PrintDepsMap(deps_graph)
1486
1487 # Are we upgrading portage? If so, and there are more packages to merge,
1488 # schedule a restart of parallel_emerge to merge the rest. This ensures that
1489 # we pick up all updates to portage settings before merging any more
1490 # packages.
1491 portage_upgrade = False
1492 root = emerge.settings["ROOT"]
1493 final_db = emerge.depgraph._dynamic_config.mydbapi[root]
1494 if root == "/":
1495 for db_pkg in final_db.match_pkgs("sys-apps/portage"):
1496 portage_pkg = deps_graph.get(db_pkg.cpv)
1497 if portage_pkg and len(deps_graph) > 1:
1498 portage_pkg["needs"].clear()
1499 portage_pkg["provides"].clear()
1500 deps_graph = { str(db_pkg.cpv): portage_pkg }
1501 portage_upgrade = True
1502 if "--quiet" not in emerge.opts:
1503 print "Upgrading portage first, then restarting..."
1504
1505 # Run the queued emerges.
1506 scheduler = EmergeQueue(deps_graph, emerge, deps.package_db, deps.show_output)
1507 scheduler.Run()
David James97ce8902011-08-16 09:51:05 -07001508 scheduler = None
David Jamesfcb70ef2011-02-02 16:02:30 -08001509
David Jamesfcb70ef2011-02-02 16:02:30 -08001510 # Update environment (library cache, symlinks, etc.)
1511 if deps.board and "--pretend" not in emerge.opts:
Brian Harring1e3fefc2011-10-03 12:09:19 -07001512 # Turn off env-update suppression used above for disabling
1513 # env-update during merging.
1514 os.environ["FEATURES"] += " -no-env-update"
1515 # Also kick the existing settings should they be reused...
1516 if hasattr(portage, 'settings'):
1517 portage.settings.unlock()
1518 portage.settings.features.discard('no-env-update')
David Jamesfcb70ef2011-02-02 16:02:30 -08001519 portage.env_update()
1520
1521 # If we already upgraded portage, we don't need to do so again. But we do
1522 # need to upgrade the rest of the packages. So we'll go ahead and do that.
David Jamesebc3ae02011-05-21 20:46:10 -07001523 #
1524 # In order to grant the child permission to run setsid, we need to run sudo
1525 # again. We preserve SUDO_USER here in case an ebuild depends on it.
David Jamesfcb70ef2011-02-02 16:02:30 -08001526 if portage_upgrade:
David Jamesebc3ae02011-05-21 20:46:10 -07001527 sudo = ["sudo", "-E", "SUDO_USER=%s" % os.environ.get("SUDO_USER", "")]
1528 args = sudo + parallel_emerge_args + ["--exclude=sys-apps/portage"]
1529 os.execvp("sudo", args)
David Jamesfcb70ef2011-02-02 16:02:30 -08001530
1531 print "Done"
1532 sys.exit(0)
1533
1534if __name__ == "__main__":
1535 main()