blob: d47e4fe47efa72ca5ad6d612bf06b1a09596dc07 [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
459 # Is it impossible to honor the user's request? Bail!
460 if not success:
461 depgraph.display_problems()
462 sys.exit(1)
463
David Jamesdeebd692011-05-09 17:02:52 -0700464 # Prime and flush emerge caches.
465 root = emerge.settings["ROOT"]
466 vardb = emerge.trees[root]["vartree"].dbapi
David James0bdc5de2011-05-12 16:22:26 -0700467 if "--pretend" not in emerge.opts:
468 vardb.counter_tick()
David Jamesdeebd692011-05-09 17:02:52 -0700469 vardb.flush_cache()
470
David James386ccd12011-05-04 20:17:42 -0700471 def GenDependencyTree(self):
David Jamesfcb70ef2011-02-02 16:02:30 -0800472 """Get dependency tree info from emerge.
473
David Jamesfcb70ef2011-02-02 16:02:30 -0800474 Returns:
475 Dependency tree
476 """
477 start = time.time()
478
479 emerge = self.emerge
480
481 # Create a list of packages to merge
482 packages = set(emerge.cmdline_packages[:])
David Jamesfcb70ef2011-02-02 16:02:30 -0800483
484 # Tell emerge to be quiet. We print plenty of info ourselves so we don't
485 # need any extra output from portage.
486 portage.util.noiselimit = -1
487
488 # My favorite feature: The silent spinner. It doesn't spin. Ever.
489 # I'd disable the colors by default too, but they look kind of cool.
490 emerge.spinner = stdout_spinner()
491 emerge.spinner.update = emerge.spinner.update_quiet
492
493 if "--quiet" not in emerge.opts:
494 print "Calculating deps..."
495
496 self.CreateDepgraph(emerge, packages)
497 depgraph = emerge.depgraph
498
499 # Build our own tree from the emerge digraph.
500 deps_tree = {}
501 digraph = depgraph._dynamic_config.digraph
David James3f778802011-08-25 19:31:45 -0700502 root = emerge.settings["ROOT"]
503 final_db = depgraph._dynamic_config.mydbapi[root]
David Jamesfcb70ef2011-02-02 16:02:30 -0800504 for node, node_deps in digraph.nodes.items():
505 # Calculate dependency packages that need to be installed first. Each
506 # child on the digraph is a dependency. The "operation" field specifies
507 # what we're doing (e.g. merge, uninstall, etc.). The "priorities" array
508 # contains the type of dependency (e.g. build, runtime, runtime_post,
509 # etc.)
510 #
David Jamesfcb70ef2011-02-02 16:02:30 -0800511 # Portage refers to the identifiers for packages as a CPV. This acronym
512 # stands for Component/Path/Version.
513 #
514 # Here's an example CPV: chromeos-base/power_manager-0.0.1-r1
515 # Split up, this CPV would be:
516 # C -- Component: chromeos-base
517 # P -- Path: power_manager
518 # V -- Version: 0.0.1-r1
519 #
520 # We just refer to CPVs as packages here because it's easier.
521 deps = {}
522 for child, priorities in node_deps[0].items():
David James3f778802011-08-25 19:31:45 -0700523 if isinstance(child, Package) and child.root == root:
524 cpv = str(child.cpv)
525 action = str(child.operation)
526
527 # If we're uninstalling a package, check whether Portage is
528 # installing a replacement. If so, just depend on the installation
529 # of the new package, because the old package will automatically
530 # be uninstalled at that time.
531 if action == "uninstall":
532 for pkg in final_db.match_pkgs(child.slot_atom):
533 cpv = str(pkg.cpv)
534 action = "merge"
535 break
536
537 deps[cpv] = dict(action=action,
538 deptypes=[str(x) for x in priorities],
539 deps={})
David Jamesfcb70ef2011-02-02 16:02:30 -0800540
541 # We've built our list of deps, so we can add our package to the tree.
David James3f778802011-08-25 19:31:45 -0700542 if isinstance(node, Package) and node.root == root:
David Jamesfcb70ef2011-02-02 16:02:30 -0800543 deps_tree[str(node.cpv)] = dict(action=str(node.operation),
544 deps=deps)
545
David Jamesfcb70ef2011-02-02 16:02:30 -0800546 # Ask portage for its install plan, so that we can only throw out
David James386ccd12011-05-04 20:17:42 -0700547 # dependencies that portage throws out.
David Jamesfcb70ef2011-02-02 16:02:30 -0800548 deps_info = {}
549 for pkg in depgraph.altlist():
550 if isinstance(pkg, Package):
David James3f778802011-08-25 19:31:45 -0700551 assert pkg.root == root
David Jamesfcb70ef2011-02-02 16:02:30 -0800552 self.package_db[pkg.cpv] = pkg
553
David Jamesfcb70ef2011-02-02 16:02:30 -0800554 # Save off info about the package
David James386ccd12011-05-04 20:17:42 -0700555 deps_info[str(pkg.cpv)] = {"idx": len(deps_info)}
David Jamesfcb70ef2011-02-02 16:02:30 -0800556
557 seconds = time.time() - start
558 if "--quiet" not in emerge.opts:
559 print "Deps calculated in %dm%.1fs" % (seconds / 60, seconds % 60)
560
561 return deps_tree, deps_info
562
563 def PrintTree(self, deps, depth=""):
564 """Print the deps we have seen in the emerge output.
565
566 Args:
567 deps: Dependency tree structure.
568 depth: Allows printing the tree recursively, with indentation.
569 """
570 for entry in sorted(deps):
571 action = deps[entry]["action"]
572 print "%s %s (%s)" % (depth, entry, action)
573 self.PrintTree(deps[entry]["deps"], depth=depth + " ")
574
David James386ccd12011-05-04 20:17:42 -0700575 def GenDependencyGraph(self, deps_tree, deps_info):
David Jamesfcb70ef2011-02-02 16:02:30 -0800576 """Generate a doubly linked dependency graph.
577
578 Args:
579 deps_tree: Dependency tree structure.
580 deps_info: More details on the dependencies.
581 Returns:
582 Deps graph in the form of a dict of packages, with each package
583 specifying a "needs" list and "provides" list.
584 """
585 emerge = self.emerge
586 root = emerge.settings["ROOT"]
587
David Jamesfcb70ef2011-02-02 16:02:30 -0800588 # deps_map is the actual dependency graph.
589 #
590 # Each package specifies a "needs" list and a "provides" list. The "needs"
591 # list indicates which packages we depend on. The "provides" list
592 # indicates the reverse dependencies -- what packages need us.
593 #
594 # We also provide some other information in the dependency graph:
595 # - action: What we're planning on doing with this package. Generally,
596 # "merge", "nomerge", or "uninstall"
David Jamesfcb70ef2011-02-02 16:02:30 -0800597 deps_map = {}
598
599 def ReverseTree(packages):
600 """Convert tree to digraph.
601
602 Take the tree of package -> requirements and reverse it to a digraph of
603 buildable packages -> packages they unblock.
604 Args:
605 packages: Tree(s) of dependencies.
606 Returns:
607 Unsanitized digraph.
608 """
David James8c7e5e32011-06-28 11:26:03 -0700609 binpkg_phases = set(["setup", "preinst", "postinst"])
David James3f778802011-08-25 19:31:45 -0700610 needed_dep_types = set(["blocker", "buildtime", "runtime"])
David Jamesfcb70ef2011-02-02 16:02:30 -0800611 for pkg in packages:
612
613 # Create an entry for the package
614 action = packages[pkg]["action"]
David James8c7e5e32011-06-28 11:26:03 -0700615 default_pkg = {"needs": {}, "provides": set(), "action": action,
616 "nodeps": False, "binary": False}
David Jamesfcb70ef2011-02-02 16:02:30 -0800617 this_pkg = deps_map.setdefault(pkg, default_pkg)
618
David James8c7e5e32011-06-28 11:26:03 -0700619 if pkg in deps_info:
620 this_pkg["idx"] = deps_info[pkg]["idx"]
621
622 # If a package doesn't have any defined phases that might use the
623 # dependent packages (i.e. pkg_setup, pkg_preinst, or pkg_postinst),
624 # we can install this package before its deps are ready.
625 emerge_pkg = self.package_db.get(pkg)
626 if emerge_pkg and emerge_pkg.type_name == "binary":
627 this_pkg["binary"] = True
628 defined_phases = emerge_pkg.metadata.defined_phases
629 defined_binpkg_phases = binpkg_phases.intersection(defined_phases)
630 if not defined_binpkg_phases:
631 this_pkg["nodeps"] = True
632
David Jamesfcb70ef2011-02-02 16:02:30 -0800633 # Create entries for dependencies of this package first.
634 ReverseTree(packages[pkg]["deps"])
635
636 # Add dependencies to this package.
637 for dep, dep_item in packages[pkg]["deps"].iteritems():
David James8c7e5e32011-06-28 11:26:03 -0700638 # We only need to enforce strict ordering of dependencies if the
David James3f778802011-08-25 19:31:45 -0700639 # dependency is a blocker, or is a buildtime or runtime dependency.
640 # (I.e., ignored, optional, and runtime_post dependencies don't
641 # depend on ordering.)
David James8c7e5e32011-06-28 11:26:03 -0700642 dep_types = dep_item["deptypes"]
643 if needed_dep_types.intersection(dep_types):
644 deps_map[dep]["provides"].add(pkg)
645 this_pkg["needs"][dep] = "/".join(dep_types)
David Jamesfcb70ef2011-02-02 16:02:30 -0800646
David James3f778802011-08-25 19:31:45 -0700647 # If there's a blocker, Portage may need to move files from one
648 # package to another, which requires editing the CONTENTS files of
649 # both packages. To avoid race conditions while editing this file,
650 # the two packages must not be installed in parallel, so we can't
651 # safely ignore dependencies. See http://crosbug.com/19328
652 if "blocker" in dep_types:
653 this_pkg["nodeps"] = False
654
David Jamesfcb70ef2011-02-02 16:02:30 -0800655 def FindCycles():
656 """Find cycles in the dependency tree.
657
658 Returns:
659 A dict mapping cyclic packages to a dict of the deps that cause
660 cycles. For each dep that causes cycles, it returns an example
661 traversal of the graph that shows the cycle.
662 """
663
664 def FindCyclesAtNode(pkg, cycles, unresolved, resolved):
665 """Find cycles in cyclic dependencies starting at specified package.
666
667 Args:
668 pkg: Package identifier.
669 cycles: A dict mapping cyclic packages to a dict of the deps that
670 cause cycles. For each dep that causes cycles, it returns an
671 example traversal of the graph that shows the cycle.
672 unresolved: Nodes that have been visited but are not fully processed.
673 resolved: Nodes that have been visited and are fully processed.
674 """
675 pkg_cycles = cycles.get(pkg)
676 if pkg in resolved and not pkg_cycles:
677 # If we already looked at this package, and found no cyclic
678 # dependencies, we can stop now.
679 return
680 unresolved.append(pkg)
681 for dep in deps_map[pkg]["needs"]:
682 if dep in unresolved:
683 idx = unresolved.index(dep)
684 mycycle = unresolved[idx:] + [dep]
685 for i in range(len(mycycle) - 1):
686 pkg1, pkg2 = mycycle[i], mycycle[i+1]
687 cycles.setdefault(pkg1, {}).setdefault(pkg2, mycycle)
688 elif not pkg_cycles or dep not in pkg_cycles:
689 # Looks like we haven't seen this edge before.
690 FindCyclesAtNode(dep, cycles, unresolved, resolved)
691 unresolved.pop()
692 resolved.add(pkg)
693
694 cycles, unresolved, resolved = {}, [], set()
695 for pkg in deps_map:
696 FindCyclesAtNode(pkg, cycles, unresolved, resolved)
697 return cycles
698
David James386ccd12011-05-04 20:17:42 -0700699 def RemoveUnusedPackages():
David Jamesfcb70ef2011-02-02 16:02:30 -0800700 """Remove installed packages, propagating dependencies."""
David Jamesfcb70ef2011-02-02 16:02:30 -0800701 # Schedule packages that aren't on the install list for removal
702 rm_pkgs = set(deps_map.keys()) - set(deps_info.keys())
703
David Jamesfcb70ef2011-02-02 16:02:30 -0800704 # Remove the packages we don't want, simplifying the graph and making
705 # it easier for us to crack cycles.
706 for pkg in sorted(rm_pkgs):
707 this_pkg = deps_map[pkg]
708 needs = this_pkg["needs"]
709 provides = this_pkg["provides"]
710 for dep in needs:
711 dep_provides = deps_map[dep]["provides"]
712 dep_provides.update(provides)
713 dep_provides.discard(pkg)
714 dep_provides.discard(dep)
715 for target in provides:
716 target_needs = deps_map[target]["needs"]
717 target_needs.update(needs)
718 target_needs.pop(pkg, None)
719 target_needs.pop(target, None)
720 del deps_map[pkg]
721
722 def PrintCycleBreak(basedep, dep, mycycle):
723 """Print details about a cycle that we are planning on breaking.
724
725 We are breaking a cycle where dep needs basedep. mycycle is an
726 example cycle which contains dep -> basedep."""
727
David Jamesfcb70ef2011-02-02 16:02:30 -0800728 needs = deps_map[dep]["needs"]
729 depinfo = needs.get(basedep, "deleted")
David Jamesfcb70ef2011-02-02 16:02:30 -0800730
David James3f778802011-08-25 19:31:45 -0700731 # It's OK to swap install order for blockers, as long as the two
732 # packages aren't installed in parallel. If there is a cycle, then
733 # we know the packages depend on each other already, so we can drop the
734 # blocker safely without printing a warning.
735 if depinfo == "blocker":
736 return
737
David Jamesfcb70ef2011-02-02 16:02:30 -0800738 # Notify the user that we're breaking a cycle.
739 print "Breaking %s -> %s (%s)" % (dep, basedep, depinfo)
740
741 # Show cycle.
742 for i in range(len(mycycle) - 1):
743 pkg1, pkg2 = mycycle[i], mycycle[i+1]
744 needs = deps_map[pkg1]["needs"]
745 depinfo = needs.get(pkg2, "deleted")
746 if pkg1 == dep and pkg2 == basedep:
747 depinfo = depinfo + ", deleting"
748 print " %s -> %s (%s)" % (pkg1, pkg2, depinfo)
749
750 def SanitizeTree():
751 """Remove circular dependencies.
752
753 We prune all dependencies involved in cycles that go against the emerge
754 ordering. This has a nice property: we're guaranteed to merge
755 dependencies in the same order that portage does.
756
757 Because we don't treat any dependencies as "soft" unless they're killed
758 by a cycle, we pay attention to a larger number of dependencies when
759 merging. This hurts performance a bit, but helps reliability.
760 """
761 start = time.time()
762 cycles = FindCycles()
763 while cycles:
764 for dep, mycycles in cycles.iteritems():
765 for basedep, mycycle in mycycles.iteritems():
766 if deps_info[basedep]["idx"] >= deps_info[dep]["idx"]:
767 PrintCycleBreak(basedep, dep, mycycle)
768 del deps_map[dep]["needs"][basedep]
769 deps_map[basedep]["provides"].remove(dep)
770 cycles = FindCycles()
771 seconds = time.time() - start
772 if "--quiet" not in emerge.opts and seconds >= 0.1:
773 print "Tree sanitized in %dm%.1fs" % (seconds / 60, seconds % 60)
774
David James8c7e5e32011-06-28 11:26:03 -0700775 def FindRecursiveProvides(pkg, seen):
776 """Find all nodes that require a particular package.
777
778 Assumes that graph is acyclic.
779
780 Args:
781 pkg: Package identifier.
782 seen: Nodes that have been visited so far.
783 """
784 if pkg in seen:
785 return
786 seen.add(pkg)
787 info = deps_map[pkg]
788 info["tprovides"] = info["provides"].copy()
789 for dep in info["provides"]:
790 FindRecursiveProvides(dep, seen)
791 info["tprovides"].update(deps_map[dep]["tprovides"])
792
David Jamesa22906f2011-05-04 19:53:26 -0700793 ReverseTree(deps_tree)
David Jamesa22906f2011-05-04 19:53:26 -0700794
David James386ccd12011-05-04 20:17:42 -0700795 # We need to remove unused packages so that we can use the dependency
796 # ordering of the install process to show us what cycles to crack.
797 RemoveUnusedPackages()
David Jamesfcb70ef2011-02-02 16:02:30 -0800798 SanitizeTree()
David James8c7e5e32011-06-28 11:26:03 -0700799 seen = set()
800 for pkg in deps_map:
801 FindRecursiveProvides(pkg, seen)
David Jamesfcb70ef2011-02-02 16:02:30 -0800802 return deps_map
803
804 def PrintInstallPlan(self, deps_map):
805 """Print an emerge-style install plan.
806
807 The install plan lists what packages we're installing, in order.
808 It's useful for understanding what parallel_emerge is doing.
809
810 Args:
811 deps_map: The dependency graph.
812 """
813
814 def InstallPlanAtNode(target, deps_map):
815 nodes = []
816 nodes.append(target)
817 for dep in deps_map[target]["provides"]:
818 del deps_map[dep]["needs"][target]
819 if not deps_map[dep]["needs"]:
820 nodes.extend(InstallPlanAtNode(dep, deps_map))
821 return nodes
822
823 deps_map = copy.deepcopy(deps_map)
824 install_plan = []
825 plan = set()
826 for target, info in deps_map.iteritems():
827 if not info["needs"] and target not in plan:
828 for item in InstallPlanAtNode(target, deps_map):
829 plan.add(item)
830 install_plan.append(self.package_db[item])
831
832 for pkg in plan:
833 del deps_map[pkg]
834
835 if deps_map:
836 print "Cyclic dependencies:", " ".join(deps_map)
837 PrintDepsMap(deps_map)
838 sys.exit(1)
839
840 self.emerge.depgraph.display(install_plan)
841
842
843def PrintDepsMap(deps_map):
844 """Print dependency graph, for each package list it's prerequisites."""
845 for i in sorted(deps_map):
846 print "%s: (%s) needs" % (i, deps_map[i]["action"])
847 needs = deps_map[i]["needs"]
848 for j in sorted(needs):
849 print " %s" % (j)
850 if not needs:
851 print " no dependencies"
852
853
854class EmergeJobState(object):
855 __slots__ = ["done", "filename", "last_notify_timestamp", "last_output_seek",
856 "last_output_timestamp", "pkgname", "retcode", "start_timestamp",
857 "target"]
858
859 def __init__(self, target, pkgname, done, filename, start_timestamp,
860 retcode=None):
861
862 # The full name of the target we're building (e.g.
863 # chromeos-base/chromeos-0.0.1-r60)
864 self.target = target
865
866 # The short name of the target we're building (e.g. chromeos-0.0.1-r60)
867 self.pkgname = pkgname
868
869 # Whether the job is done. (True if the job is done; false otherwise.)
870 self.done = done
871
872 # The filename where output is currently stored.
873 self.filename = filename
874
875 # The timestamp of the last time we printed the name of the log file. We
876 # print this at the beginning of the job, so this starts at
877 # start_timestamp.
878 self.last_notify_timestamp = start_timestamp
879
880 # The location (in bytes) of the end of the last complete line we printed.
881 # This starts off at zero. We use this to jump to the right place when we
882 # print output from the same ebuild multiple times.
883 self.last_output_seek = 0
884
885 # The timestamp of the last time we printed output. Since we haven't
886 # printed output yet, this starts at zero.
887 self.last_output_timestamp = 0
888
889 # The return code of our job, if the job is actually finished.
890 self.retcode = retcode
891
892 # The timestamp when our job started.
893 self.start_timestamp = start_timestamp
894
895
David James7358d032011-05-19 10:40:03 -0700896def KillHandler(signum, frame):
897 # Kill self and all subprocesses.
898 os.killpg(0, signal.SIGKILL)
899
David Jamesfcb70ef2011-02-02 16:02:30 -0800900def SetupWorkerSignals():
901 def ExitHandler(signum, frame):
David James7358d032011-05-19 10:40:03 -0700902 # Set KILLED flag.
903 KILLED.set()
David James13cead42011-05-18 16:22:01 -0700904
David James7358d032011-05-19 10:40:03 -0700905 # Remove our signal handlers so we don't get called recursively.
906 signal.signal(signal.SIGINT, KillHandler)
907 signal.signal(signal.SIGTERM, KillHandler)
David Jamesfcb70ef2011-02-02 16:02:30 -0800908
909 # Ensure that we exit quietly and cleanly, if possible, when we receive
910 # SIGTERM or SIGINT signals. By default, when the user hits CTRL-C, all
911 # of the child processes will print details about KeyboardInterrupt
912 # exceptions, which isn't very helpful.
913 signal.signal(signal.SIGINT, ExitHandler)
914 signal.signal(signal.SIGTERM, ExitHandler)
915
David James1ed3e252011-10-05 20:26:15 -0700916def EmergeProcess(scheduler, output):
917 """Merge a package in a subprocess.
918
919 Args:
920 scheduler: Scheduler object.
921 output: Temporary file to write output.
922
923 Returns:
924 The exit code returned by the subprocess.
925 """
926 pid = os.fork()
927 if pid == 0:
928 try:
929 # Sanity checks.
930 if sys.stdout.fileno() != 1: raise Exception("sys.stdout.fileno() != 1")
931 if sys.stderr.fileno() != 2: raise Exception("sys.stderr.fileno() != 2")
932
933 # - Redirect 1 (stdout) and 2 (stderr) at our temporary file.
934 # - Redirect 0 to point at sys.stdin. In this case, sys.stdin
935 # points at a file reading os.devnull, because multiprocessing mucks
936 # with sys.stdin.
937 # - Leave the sys.stdin and output filehandles alone.
938 fd_pipes = {0: sys.stdin.fileno(),
939 1: output.fileno(),
940 2: output.fileno(),
941 sys.stdin.fileno(): sys.stdin.fileno(),
942 output.fileno(): output.fileno()}
943 portage.process._setup_pipes(fd_pipes)
944
945 # Portage doesn't like when sys.stdin.fileno() != 0, so point sys.stdin
946 # at the filehandle we just created in _setup_pipes.
947 if sys.stdin.fileno() != 0:
948 sys.stdin = os.fdopen(0, "r")
949
950 # Actually do the merge.
951 retval = scheduler.merge()
952
953 # We catch all exceptions here (including SystemExit, KeyboardInterrupt,
954 # etc) so as to ensure that we don't confuse the multiprocessing module,
955 # which expects that all forked children exit with os._exit().
956 except:
957 traceback.print_exc(file=output)
958 retval = 1
959 sys.stdout.flush()
960 sys.stderr.flush()
961 output.flush()
962 os._exit(retval)
963 else:
964 # Return the exit code of the subprocess.
965 return os.waitpid(pid, 0)[1]
David Jamesfcb70ef2011-02-02 16:02:30 -0800966
967def EmergeWorker(task_queue, job_queue, emerge, package_db):
968 """This worker emerges any packages given to it on the task_queue.
969
970 Args:
971 task_queue: The queue of tasks for this worker to do.
972 job_queue: The queue of results from the worker.
973 emerge: An EmergeData() object.
974 package_db: A dict, mapping package ids to portage Package objects.
975
976 It expects package identifiers to be passed to it via task_queue. When
977 a task is started, it pushes the (target, filename) to the started_queue.
978 The output is stored in filename. When a merge starts or finishes, we push
979 EmergeJobState objects to the job_queue.
980 """
981
982 SetupWorkerSignals()
983 settings, trees, mtimedb = emerge.settings, emerge.trees, emerge.mtimedb
David Jamesdeebd692011-05-09 17:02:52 -0700984
985 # Disable flushing of caches to save on I/O.
David James7a1ea4b2011-10-13 15:06:41 -0700986 root = emerge.settings["ROOT"]
987 vardb = emerge.trees[root]["vartree"].dbapi
988 vardb._flush_cache_enabled = False
David Jamesdeebd692011-05-09 17:02:52 -0700989
David Jamesfcb70ef2011-02-02 16:02:30 -0800990 opts, spinner = emerge.opts, emerge.spinner
991 opts["--nodeps"] = True
David Jamesfcb70ef2011-02-02 16:02:30 -0800992 while True:
993 # Wait for a new item to show up on the queue. This is a blocking wait,
994 # so if there's nothing to do, we just sit here.
995 target = task_queue.get()
996 if not target:
997 # If target is None, this means that the main thread wants us to quit.
998 # The other workers need to exit too, so we'll push the message back on
999 # to the queue so they'll get it too.
1000 task_queue.put(target)
1001 return
David James7358d032011-05-19 10:40:03 -07001002 if KILLED.is_set():
1003 return
1004
David Jamesfcb70ef2011-02-02 16:02:30 -08001005 db_pkg = package_db[target]
1006 db_pkg.root_config = emerge.root_config
1007 install_list = [db_pkg]
1008 pkgname = db_pkg.pf
1009 output = tempfile.NamedTemporaryFile(prefix=pkgname + "-", delete=False)
1010 start_timestamp = time.time()
1011 job = EmergeJobState(target, pkgname, False, output.name, start_timestamp)
1012 job_queue.put(job)
1013 if "--pretend" in opts:
1014 retcode = 0
1015 else:
David Jamesfcb70ef2011-02-02 16:02:30 -08001016 try:
David James386ccd12011-05-04 20:17:42 -07001017 emerge.scheduler_graph.mergelist = install_list
1018 scheduler = Scheduler(settings, trees, mtimedb, opts, spinner,
David Jamesbf1e3442011-05-28 07:44:20 -07001019 favorites=emerge.favorites, graph_config=emerge.scheduler_graph)
David Jamesace2e212011-07-13 11:47:39 -07001020
1021 # Enable blocker handling even though we're in --nodeps mode. This
1022 # allows us to unmerge the blocker after we've merged the replacement.
1023 scheduler._opts_ignore_blockers = frozenset()
1024
David James1ed3e252011-10-05 20:26:15 -07001025 retcode = EmergeProcess(scheduler, output)
David Jamesfcb70ef2011-02-02 16:02:30 -08001026 except Exception:
1027 traceback.print_exc(file=output)
1028 retcode = 1
David James1ed3e252011-10-05 20:26:15 -07001029 output.close()
David Jamesfcb70ef2011-02-02 16:02:30 -08001030
David James7358d032011-05-19 10:40:03 -07001031 if KILLED.is_set():
1032 return
1033
David Jamesfcb70ef2011-02-02 16:02:30 -08001034 job = EmergeJobState(target, pkgname, True, output.name, start_timestamp,
1035 retcode)
1036 job_queue.put(job)
1037
1038
1039class LinePrinter(object):
1040 """Helper object to print a single line."""
1041
1042 def __init__(self, line):
1043 self.line = line
1044
1045 def Print(self, seek_locations):
1046 print self.line
1047
1048
1049class JobPrinter(object):
1050 """Helper object to print output of a job."""
1051
1052 def __init__(self, job, unlink=False):
1053 """Print output of job.
1054
1055 If unlink is True, unlink the job output file when done."""
1056 self.current_time = time.time()
1057 self.job = job
1058 self.unlink = unlink
1059
1060 def Print(self, seek_locations):
1061
1062 job = self.job
1063
1064 # Calculate how long the job has been running.
1065 seconds = self.current_time - job.start_timestamp
1066
1067 # Note that we've printed out the job so far.
1068 job.last_output_timestamp = self.current_time
1069
1070 # Note that we're starting the job
1071 info = "job %s (%dm%.1fs)" % (job.pkgname, seconds / 60, seconds % 60)
1072 last_output_seek = seek_locations.get(job.filename, 0)
1073 if last_output_seek:
1074 print "=== Continue output for %s ===" % info
1075 else:
1076 print "=== Start output for %s ===" % info
1077
1078 # Print actual output from job
1079 f = codecs.open(job.filename, encoding='utf-8', errors='replace')
1080 f.seek(last_output_seek)
1081 prefix = job.pkgname + ":"
1082 for line in f:
1083
1084 # Save off our position in the file
1085 if line and line[-1] == "\n":
1086 last_output_seek = f.tell()
1087 line = line[:-1]
1088
1089 # Print our line
1090 print prefix, line.encode('utf-8', 'replace')
1091 f.close()
1092
1093 # Save our last spot in the file so that we don't print out the same
1094 # location twice.
1095 seek_locations[job.filename] = last_output_seek
1096
1097 # Note end of output section
1098 if job.done:
1099 print "=== Complete: %s ===" % info
1100 else:
1101 print "=== Still running: %s ===" % info
1102
1103 if self.unlink:
1104 os.unlink(job.filename)
1105
1106
1107def PrintWorker(queue):
1108 """A worker that prints stuff to the screen as requested."""
1109
1110 def ExitHandler(signum, frame):
David James7358d032011-05-19 10:40:03 -07001111 # Set KILLED flag.
1112 KILLED.set()
1113
David Jamesfcb70ef2011-02-02 16:02:30 -08001114 # Switch to default signal handlers so that we'll die after two signals.
David James7358d032011-05-19 10:40:03 -07001115 signal.signal(signal.SIGINT, KillHandler)
1116 signal.signal(signal.SIGTERM, KillHandler)
David Jamesfcb70ef2011-02-02 16:02:30 -08001117
1118 # Don't exit on the first SIGINT / SIGTERM, because the parent worker will
1119 # handle it and tell us when we need to exit.
1120 signal.signal(signal.SIGINT, ExitHandler)
1121 signal.signal(signal.SIGTERM, ExitHandler)
1122
1123 # seek_locations is a map indicating the position we are at in each file.
1124 # It starts off empty, but is set by the various Print jobs as we go along
1125 # to indicate where we left off in each file.
1126 seek_locations = {}
1127 while True:
1128 try:
1129 job = queue.get()
1130 if job:
1131 job.Print(seek_locations)
David Jamesbccf8eb2011-07-27 14:06:06 -07001132 sys.stdout.flush()
David Jamesfcb70ef2011-02-02 16:02:30 -08001133 else:
1134 break
1135 except IOError as ex:
1136 if ex.errno == errno.EINTR:
1137 # Looks like we received a signal. Keep printing.
1138 continue
1139 raise
1140
David Jamesfcb70ef2011-02-02 16:02:30 -08001141class EmergeQueue(object):
1142 """Class to schedule emerge jobs according to a dependency graph."""
1143
1144 def __init__(self, deps_map, emerge, package_db, show_output):
1145 # Store the dependency graph.
1146 self._deps_map = deps_map
1147 # Initialize the running queue to empty
1148 self._jobs = {}
David James8c7e5e32011-06-28 11:26:03 -07001149 self._ready = []
David Jamesfcb70ef2011-02-02 16:02:30 -08001150 # List of total package installs represented in deps_map.
1151 install_jobs = [x for x in deps_map if deps_map[x]["action"] == "merge"]
1152 self._total_jobs = len(install_jobs)
1153 self._show_output = show_output
1154
1155 if "--pretend" in emerge.opts:
1156 print "Skipping merge because of --pretend mode."
1157 sys.exit(0)
1158
David James7358d032011-05-19 10:40:03 -07001159 # Set a process group so we can easily terminate all children.
1160 os.setsid()
1161
David Jamesfcb70ef2011-02-02 16:02:30 -08001162 # Setup scheduler graph object. This is used by the child processes
1163 # to help schedule jobs.
1164 emerge.scheduler_graph = emerge.depgraph.schedulerGraph()
1165
1166 # Calculate how many jobs we can run in parallel. We don't want to pass
1167 # the --jobs flag over to emerge itself, because that'll tell emerge to
1168 # hide its output, and said output is quite useful for debugging hung
1169 # jobs.
1170 procs = min(self._total_jobs,
1171 emerge.opts.pop("--jobs", multiprocessing.cpu_count()))
David James8c7e5e32011-06-28 11:26:03 -07001172 self._load_avg = emerge.opts.pop("--load-average", None)
David Jamesfcb70ef2011-02-02 16:02:30 -08001173 self._emerge_queue = multiprocessing.Queue()
1174 self._job_queue = multiprocessing.Queue()
1175 self._print_queue = multiprocessing.Queue()
1176 args = (self._emerge_queue, self._job_queue, emerge, package_db)
1177 self._pool = multiprocessing.Pool(procs, EmergeWorker, args)
1178 self._print_worker = multiprocessing.Process(target=PrintWorker,
1179 args=[self._print_queue])
1180 self._print_worker.start()
1181
1182 # Initialize the failed queue to empty.
1183 self._retry_queue = []
1184 self._failed = set()
1185
David Jamesfcb70ef2011-02-02 16:02:30 -08001186 # Setup an exit handler so that we print nice messages if we are
1187 # terminated.
1188 self._SetupExitHandler()
1189
1190 # Schedule our jobs.
1191 for target, info in deps_map.items():
David James8c7e5e32011-06-28 11:26:03 -07001192 if info["nodeps"] or not info["needs"]:
1193 score = (-len(info["tprovides"]), info["binary"], info["idx"])
1194 self._ready.append((score, target))
1195 heapq.heapify(self._ready)
1196 self._procs = procs
1197 self._ScheduleLoop()
1198
1199 # Print an update.
1200 self._Status()
David Jamesfcb70ef2011-02-02 16:02:30 -08001201
1202 def _SetupExitHandler(self):
1203
1204 def ExitHandler(signum, frame):
David James7358d032011-05-19 10:40:03 -07001205 # Set KILLED flag.
1206 KILLED.set()
David Jamesfcb70ef2011-02-02 16:02:30 -08001207
1208 # Kill our signal handlers so we don't get called recursively
David James7358d032011-05-19 10:40:03 -07001209 signal.signal(signal.SIGINT, KillHandler)
1210 signal.signal(signal.SIGTERM, KillHandler)
David Jamesfcb70ef2011-02-02 16:02:30 -08001211
1212 # Print our current job status
1213 for target, job in self._jobs.iteritems():
1214 if job:
1215 self._print_queue.put(JobPrinter(job, unlink=True))
1216
1217 # Notify the user that we are exiting
1218 self._Print("Exiting on signal %s" % signum)
David James7358d032011-05-19 10:40:03 -07001219 self._print_queue.put(None)
1220 self._print_worker.join()
David Jamesfcb70ef2011-02-02 16:02:30 -08001221
1222 # Kill child threads, then exit.
David James7358d032011-05-19 10:40:03 -07001223 os.killpg(0, signal.SIGKILL)
David Jamesfcb70ef2011-02-02 16:02:30 -08001224 sys.exit(1)
1225
1226 # Print out job status when we are killed
1227 signal.signal(signal.SIGINT, ExitHandler)
1228 signal.signal(signal.SIGTERM, ExitHandler)
1229
1230 def _Schedule(self, target):
1231 # We maintain a tree of all deps, if this doesn't need
David James8c7e5e32011-06-28 11:26:03 -07001232 # to be installed just free up its children and continue.
David Jamesfcb70ef2011-02-02 16:02:30 -08001233 # It is possible to reinstall deps of deps, without reinstalling
1234 # first level deps, like so:
1235 # chromeos (merge) -> eselect (nomerge) -> python (merge)
David James8c7e5e32011-06-28 11:26:03 -07001236 this_pkg = self._deps_map.get(target)
1237 if this_pkg is None:
David James386ccd12011-05-04 20:17:42 -07001238 pass
David James8c7e5e32011-06-28 11:26:03 -07001239 elif this_pkg["action"] == "nomerge":
David Jamesfcb70ef2011-02-02 16:02:30 -08001240 self._Finish(target)
David Jamesd20a6d92011-04-26 16:11:59 -07001241 elif target not in self._jobs:
David Jamesfcb70ef2011-02-02 16:02:30 -08001242 # Kick off the build if it's marked to be built.
1243 self._jobs[target] = None
1244 self._emerge_queue.put(target)
David James8c7e5e32011-06-28 11:26:03 -07001245 return True
David Jamesfcb70ef2011-02-02 16:02:30 -08001246
David James8c7e5e32011-06-28 11:26:03 -07001247 def _ScheduleLoop(self):
1248 # If the current load exceeds our desired load average, don't schedule
1249 # more than one job.
1250 if self._load_avg and os.getloadavg()[0] > self._load_avg:
1251 needed_jobs = 1
1252 else:
1253 needed_jobs = self._procs
1254
1255 # Schedule more jobs.
1256 while self._ready and len(self._jobs) < needed_jobs:
1257 score, pkg = heapq.heappop(self._ready)
David James32420cc2011-08-25 21:32:46 -07001258 if pkg not in self._failed:
1259 self._Schedule(pkg)
David Jamesfcb70ef2011-02-02 16:02:30 -08001260
1261 def _Print(self, line):
1262 """Print a single line."""
1263 self._print_queue.put(LinePrinter(line))
1264
1265 def _Status(self):
1266 """Print status."""
1267 current_time = time.time()
1268 no_output = True
1269
1270 # Print interim output every minute if --show-output is used. Otherwise,
1271 # print notifications about running packages every 2 minutes, and print
1272 # full output for jobs that have been running for 60 minutes or more.
1273 if self._show_output:
1274 interval = 60
1275 notify_interval = 0
1276 else:
1277 interval = 60 * 60
1278 notify_interval = 60 * 2
1279 for target, job in self._jobs.iteritems():
1280 if job:
1281 last_timestamp = max(job.start_timestamp, job.last_output_timestamp)
1282 if last_timestamp + interval < current_time:
1283 self._print_queue.put(JobPrinter(job))
1284 job.last_output_timestamp = current_time
1285 no_output = False
1286 elif (notify_interval and
1287 job.last_notify_timestamp + notify_interval < current_time):
1288 job_seconds = current_time - job.start_timestamp
1289 args = (job.pkgname, job_seconds / 60, job_seconds % 60, job.filename)
1290 info = "Still building %s (%dm%.1fs). Logs in %s" % args
1291 job.last_notify_timestamp = current_time
1292 self._Print(info)
1293 no_output = False
1294
1295 # If we haven't printed any messages yet, print a general status message
1296 # here.
1297 if no_output:
1298 seconds = current_time - GLOBAL_START
1299 line = ("Pending %s, Ready %s, Running %s, Retrying %s, Total %s "
1300 "[Time %dm%.1fs Load %s]")
David James8c7e5e32011-06-28 11:26:03 -07001301 load = " ".join(str(x) for x in os.getloadavg())
1302 self._Print(line % (len(self._deps_map), len(self._ready),
1303 len(self._jobs), len(self._retry_queue),
1304 self._total_jobs, seconds / 60, seconds % 60, load))
David Jamesfcb70ef2011-02-02 16:02:30 -08001305
1306 def _Finish(self, target):
David James8c7e5e32011-06-28 11:26:03 -07001307 """Mark a target as completed and unblock dependencies."""
1308 this_pkg = self._deps_map[target]
1309 if this_pkg["needs"] and this_pkg["nodeps"]:
1310 # We got installed, but our deps have not been installed yet. Dependent
1311 # packages should only be installed when our needs have been fully met.
1312 this_pkg["action"] = "nomerge"
1313 else:
1314 finish = []
1315 for dep in this_pkg["provides"]:
1316 dep_pkg = self._deps_map[dep]
1317 del dep_pkg["needs"][target]
1318 if not dep_pkg["needs"]:
1319 if dep_pkg["nodeps"] and dep_pkg["action"] == "nomerge":
1320 self._Finish(dep)
1321 else:
1322 score = (-len(dep_pkg["tprovides"]), dep_pkg["binary"],
1323 dep_pkg["idx"])
1324 heapq.heappush(self._ready, (score, dep))
1325 self._deps_map.pop(target)
David Jamesfcb70ef2011-02-02 16:02:30 -08001326
1327 def _Retry(self):
David James8c7e5e32011-06-28 11:26:03 -07001328 while self._retry_queue:
David Jamesfcb70ef2011-02-02 16:02:30 -08001329 target = self._retry_queue.pop(0)
David James8c7e5e32011-06-28 11:26:03 -07001330 if self._Schedule(target):
1331 self._Print("Retrying emerge of %s." % target)
1332 break
David Jamesfcb70ef2011-02-02 16:02:30 -08001333
1334 def _Exit(self):
1335 # Tell emerge workers to exit. They all exit when 'None' is pushed
1336 # to the queue.
1337 self._emerge_queue.put(None)
1338 self._pool.close()
1339 self._pool.join()
David James97ce8902011-08-16 09:51:05 -07001340 self._emerge_queue.close()
1341 self._emerge_queue = None
David Jamesfcb70ef2011-02-02 16:02:30 -08001342
1343 # Now that our workers are finished, we can kill the print queue.
1344 self._print_queue.put(None)
1345 self._print_worker.join()
David James97ce8902011-08-16 09:51:05 -07001346 self._print_queue.close()
1347 self._print_queue = None
1348 self._job_queue.close()
1349 self._job_queue = None
David Jamesfcb70ef2011-02-02 16:02:30 -08001350
1351 def Run(self):
1352 """Run through the scheduled ebuilds.
1353
1354 Keep running so long as we have uninstalled packages in the
1355 dependency graph to merge.
1356 """
1357 while self._deps_map:
1358 # Check here that we are actually waiting for something.
1359 if (self._emerge_queue.empty() and
1360 self._job_queue.empty() and
1361 not self._jobs and
David James8c7e5e32011-06-28 11:26:03 -07001362 not self._ready and
David Jamesfcb70ef2011-02-02 16:02:30 -08001363 self._deps_map):
1364 # If we have failed on a package, retry it now.
1365 if self._retry_queue:
1366 self._Retry()
1367 else:
1368 # Tell child threads to exit.
1369 self._Exit()
1370
1371 # The dependency map is helpful for debugging failures.
1372 PrintDepsMap(self._deps_map)
1373
1374 # Tell the user why we're exiting.
1375 if self._failed:
1376 print "Packages failed: %s" % " ,".join(self._failed)
1377 else:
1378 print "Deadlock! Circular dependencies!"
1379 sys.exit(1)
1380
David Jamesa74289a2011-08-12 10:41:24 -07001381 for i in range(3):
1382 try:
1383 job = self._job_queue.get(timeout=5)
1384 break
1385 except Queue.Empty:
1386 # Check if any more jobs can be scheduled.
1387 self._ScheduleLoop()
1388 else:
1389 # Print an update every 15 seconds.
David Jamesfcb70ef2011-02-02 16:02:30 -08001390 self._Status()
1391 continue
1392
1393 target = job.target
1394
1395 if not job.done:
1396 self._jobs[target] = job
1397 self._Print("Started %s (logged in %s)" % (target, job.filename))
1398 continue
1399
1400 # Print output of job
1401 if self._show_output or job.retcode != 0:
1402 self._print_queue.put(JobPrinter(job, unlink=True))
1403 else:
1404 os.unlink(job.filename)
1405 del self._jobs[target]
1406
1407 seconds = time.time() - job.start_timestamp
1408 details = "%s (in %dm%.1fs)" % (target, seconds / 60, seconds % 60)
David James32420cc2011-08-25 21:32:46 -07001409 previously_failed = target in self._failed
David Jamesfcb70ef2011-02-02 16:02:30 -08001410
1411 # Complain if necessary.
1412 if job.retcode != 0:
1413 # Handle job failure.
David James32420cc2011-08-25 21:32:46 -07001414 if previously_failed:
David Jamesfcb70ef2011-02-02 16:02:30 -08001415 # If this job has failed previously, give up.
1416 self._Print("Failed %s. Your build has failed." % details)
1417 else:
1418 # Queue up this build to try again after a long while.
1419 self._retry_queue.append(target)
1420 self._failed.add(target)
1421 self._Print("Failed %s, retrying later." % details)
1422 else:
David James32420cc2011-08-25 21:32:46 -07001423 if previously_failed:
1424 # Remove target from list of failed packages.
1425 self._failed.remove(target)
1426
1427 self._Print("Completed %s" % details)
1428
1429 # Mark as completed and unblock waiting ebuilds.
1430 self._Finish(target)
1431
1432 if previously_failed and self._retry_queue:
David Jamesfcb70ef2011-02-02 16:02:30 -08001433 # If we have successfully retried a failed package, and there
1434 # are more failed packages, try the next one. We will only have
1435 # one retrying package actively running at a time.
1436 self._Retry()
1437
David Jamesfcb70ef2011-02-02 16:02:30 -08001438
David James8c7e5e32011-06-28 11:26:03 -07001439 # Schedule pending jobs and print an update.
1440 self._ScheduleLoop()
1441 self._Status()
David Jamesfcb70ef2011-02-02 16:02:30 -08001442
1443 # Tell child threads to exit.
1444 self._Print("Merge complete")
1445 self._Exit()
1446
1447
1448def main():
1449
David James57437532011-05-06 15:51:21 -07001450 parallel_emerge_args = sys.argv[:]
David Jamesfcb70ef2011-02-02 16:02:30 -08001451 deps = DepGraphGenerator()
David James57437532011-05-06 15:51:21 -07001452 deps.Initialize(parallel_emerge_args[1:])
David Jamesfcb70ef2011-02-02 16:02:30 -08001453 emerge = deps.emerge
1454
1455 if emerge.action is not None:
1456 sys.argv = deps.ParseParallelEmergeArgs(sys.argv)
1457 sys.exit(emerge_main())
1458 elif not emerge.cmdline_packages:
1459 Usage()
1460 sys.exit(1)
1461
1462 # Unless we're in pretend mode, there's not much point running without
1463 # root access. We need to be able to install packages.
1464 #
1465 # NOTE: Even if you're running --pretend, it's a good idea to run
1466 # parallel_emerge with root access so that portage can write to the
1467 # dependency cache. This is important for performance.
1468 if "--pretend" not in emerge.opts and portage.secpass < 2:
1469 print "parallel_emerge: superuser access is required."
1470 sys.exit(1)
1471
1472 if "--quiet" not in emerge.opts:
1473 cmdline_packages = " ".join(emerge.cmdline_packages)
David Jamesfcb70ef2011-02-02 16:02:30 -08001474 print "Starting fast-emerge."
1475 print " Building package %s on %s" % (cmdline_packages,
1476 deps.board or "root")
David Jamesfcb70ef2011-02-02 16:02:30 -08001477
David James386ccd12011-05-04 20:17:42 -07001478 deps_tree, deps_info = deps.GenDependencyTree()
David Jamesfcb70ef2011-02-02 16:02:30 -08001479
1480 # You want me to be verbose? I'll give you two trees! Twice as much value.
1481 if "--tree" in emerge.opts and "--verbose" in emerge.opts:
1482 deps.PrintTree(deps_tree)
1483
David James386ccd12011-05-04 20:17:42 -07001484 deps_graph = deps.GenDependencyGraph(deps_tree, deps_info)
David Jamesfcb70ef2011-02-02 16:02:30 -08001485
1486 # OK, time to print out our progress so far.
1487 deps.PrintInstallPlan(deps_graph)
1488 if "--tree" in emerge.opts:
1489 PrintDepsMap(deps_graph)
1490
1491 # Are we upgrading portage? If so, and there are more packages to merge,
1492 # schedule a restart of parallel_emerge to merge the rest. This ensures that
1493 # we pick up all updates to portage settings before merging any more
1494 # packages.
1495 portage_upgrade = False
1496 root = emerge.settings["ROOT"]
1497 final_db = emerge.depgraph._dynamic_config.mydbapi[root]
1498 if root == "/":
1499 for db_pkg in final_db.match_pkgs("sys-apps/portage"):
1500 portage_pkg = deps_graph.get(db_pkg.cpv)
1501 if portage_pkg and len(deps_graph) > 1:
1502 portage_pkg["needs"].clear()
1503 portage_pkg["provides"].clear()
1504 deps_graph = { str(db_pkg.cpv): portage_pkg }
1505 portage_upgrade = True
1506 if "--quiet" not in emerge.opts:
1507 print "Upgrading portage first, then restarting..."
1508
1509 # Run the queued emerges.
1510 scheduler = EmergeQueue(deps_graph, emerge, deps.package_db, deps.show_output)
1511 scheduler.Run()
David James97ce8902011-08-16 09:51:05 -07001512 scheduler = None
David Jamesfcb70ef2011-02-02 16:02:30 -08001513
David Jamesfcb70ef2011-02-02 16:02:30 -08001514 # Update environment (library cache, symlinks, etc.)
1515 if deps.board and "--pretend" not in emerge.opts:
Brian Harring1e3fefc2011-10-03 12:09:19 -07001516 # Turn off env-update suppression used above for disabling
1517 # env-update during merging.
1518 os.environ["FEATURES"] += " -no-env-update"
1519 # Also kick the existing settings should they be reused...
1520 if hasattr(portage, 'settings'):
1521 portage.settings.unlock()
1522 portage.settings.features.discard('no-env-update')
David Jamesfcb70ef2011-02-02 16:02:30 -08001523 portage.env_update()
1524
1525 # If we already upgraded portage, we don't need to do so again. But we do
1526 # need to upgrade the rest of the packages. So we'll go ahead and do that.
David Jamesebc3ae02011-05-21 20:46:10 -07001527 #
1528 # In order to grant the child permission to run setsid, we need to run sudo
1529 # again. We preserve SUDO_USER here in case an ebuild depends on it.
David Jamesfcb70ef2011-02-02 16:02:30 -08001530 if portage_upgrade:
David Jamesebc3ae02011-05-21 20:46:10 -07001531 sudo = ["sudo", "-E", "SUDO_USER=%s" % os.environ.get("SUDO_USER", "")]
1532 args = sudo + parallel_emerge_args + ["--exclude=sys-apps/portage"]
1533 os.execvp("sudo", args)
David Jamesfcb70ef2011-02-02 16:02:30 -08001534
1535 print "Done"
1536 sys.exit(0)
1537
1538if __name__ == "__main__":
1539 main()