blob: 41e569ae3fb8e93bb4997c63305201815a96640f [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 Jamesa22906f2011-05-04 19:53:26 -07009 ./parallel_emerge [--board=BOARD] [--workon=PKGS] [--no-workon-deps]
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
44import multiprocessing
45import os
46import Queue
David Jamesa22906f2011-05-04 19:53:26 -070047import shlex
David Jamesfcb70ef2011-02-02 16:02:30 -080048import signal
49import sys
50import tempfile
51import time
52import traceback
David Jamesa22906f2011-05-04 19:53:26 -070053import urllib2
54import urlparse
David Jamesfcb70ef2011-02-02 16:02:30 -080055
56# If PORTAGE_USERNAME isn't specified, scrape it from the $HOME variable. On
57# Chromium OS, the default "portage" user doesn't have the necessary
58# permissions. It'd be easier if we could default to $USERNAME, but $USERNAME
59# is "root" here because we get called through sudo.
60#
61# We need to set this before importing any portage modules, because portage
62# looks up "PORTAGE_USERNAME" at import time.
63#
64# NOTE: .bashrc sets PORTAGE_USERNAME = $USERNAME, so most people won't
65# encounter this case unless they have an old chroot or blow away the
66# environment by running sudo without the -E specifier.
67if "PORTAGE_USERNAME" not in os.environ:
68 homedir = os.environ.get("HOME")
69 if homedir:
70 os.environ["PORTAGE_USERNAME"] = os.path.basename(homedir)
71
72# Portage doesn't expose dependency trees in its public API, so we have to
73# make use of some private APIs here. These modules are found under
74# /usr/lib/portage/pym/.
75#
76# TODO(davidjames): Update Portage to expose public APIs for these features.
77from _emerge.actions import adjust_configs
78from _emerge.actions import load_emerge_config
79from _emerge.create_depgraph_params import create_depgraph_params
David Jamesa22906f2011-05-04 19:53:26 -070080from _emerge.depgraph import depgraph as emerge_depgraph
81from _emerge.depgraph import _frozen_depgraph_config
David Jamesfcb70ef2011-02-02 16:02:30 -080082from _emerge.main import emerge_main
83from _emerge.main import parse_opts
84from _emerge.Package import Package
85from _emerge.Scheduler import Scheduler
86from _emerge.SetArg import SetArg
87from _emerge.stdout_spinner import stdout_spinner
88import portage
89import portage.debug
David Jamesa22906f2011-05-04 19:53:26 -070090import portage.versions
David Jamesfcb70ef2011-02-02 16:02:30 -080091
David Jamesa22906f2011-05-04 19:53:26 -070092new_portage = not portage.VERSION.startswith("2.1.7.")
93if new_portage:
94 from portage._global_updates import _global_updates
95else:
96 from portage import _global_updates
David Jamesfcb70ef2011-02-02 16:02:30 -080097
98def Usage():
99 """Print usage."""
100 print "Usage:"
David Jamesa22906f2011-05-04 19:53:26 -0700101 print " ./parallel_emerge [--board=BOARD] [--workon=PKGS] [--no-workon-deps]"
David Jamesfcb70ef2011-02-02 16:02:30 -0800102 print " [--rebuild] [emerge args] package"
103 print
104 print "Packages specified as workon packages are always built from source."
David Jamesa22906f2011-05-04 19:53:26 -0700105 print "Unless --no-workon-deps is specified, packages that depend on these"
106 print "packages are also built from source."
David Jamesfcb70ef2011-02-02 16:02:30 -0800107 print
108 print "The --workon argument is mainly useful when you want to build and"
109 print "install packages that you are working on unconditionally, but do not"
110 print "to have to rev the package to indicate you want to build it from"
111 print "source. The build_packages script will automatically supply the"
112 print "workon argument to emerge, ensuring that packages selected using"
113 print "cros-workon are rebuilt."
114 print
115 print "The --rebuild option rebuilds packages whenever their dependencies"
116 print "are changed. This ensures that your build is correct."
117 sys.exit(1)
118
119
David Jamesa22906f2011-05-04 19:53:26 -0700120# These are dependencies that are not specified in the package,
121# but will prevent the package from installing.
122secret_deps = {}
123
David Jamesfcb70ef2011-02-02 16:02:30 -0800124# Global start time
125GLOBAL_START = time.time()
126
127
128class EmergeData(object):
129 """This simple struct holds various emerge variables.
130
131 This struct helps us easily pass emerge variables around as a unit.
132 These variables are used for calculating dependencies and installing
133 packages.
134 """
135
136 __slots__ = ["action", "cmdline_packages", "depgraph", "mtimedb", "opts",
137 "root_config", "scheduler_graph", "settings", "spinner",
138 "trees"]
139
140 def __init__(self):
141 # The action the user requested. If the user is installing packages, this
142 # is None. If the user is doing anything other than installing packages,
143 # this will contain the action name, which will map exactly to the
144 # long-form name of the associated emerge option.
145 #
146 # Example: If you call parallel_emerge --unmerge package, the action name
147 # will be "unmerge"
148 self.action = None
149
150 # The list of packages the user passed on the command-line.
151 self.cmdline_packages = None
152
153 # The emerge dependency graph. It'll contain all the packages involved in
154 # this merge, along with their versions.
155 self.depgraph = None
156
157 # A dict of the options passed to emerge. This dict has been cleaned up
158 # a bit by parse_opts, so that it's a bit easier for the emerge code to
159 # look at the options.
160 #
161 # Emerge takes a few shortcuts in its cleanup process to make parsing of
162 # the options dict easier. For example, if you pass in "--usepkg=n", the
163 # "--usepkg" flag is just left out of the dictionary altogether. Because
164 # --usepkg=n is the default, this makes parsing easier, because emerge
165 # can just assume that if "--usepkg" is in the dictionary, it's enabled.
166 #
167 # These cleanup processes aren't applied to all options. For example, the
168 # --with-bdeps flag is passed in as-is. For a full list of the cleanups
169 # applied by emerge, see the parse_opts function in the _emerge.main
170 # package.
171 self.opts = None
172
173 # A dictionary used by portage to maintain global state. This state is
174 # loaded from disk when portage starts up, and saved to disk whenever we
175 # call mtimedb.commit().
176 #
177 # This database contains information about global updates (i.e., what
178 # version of portage we have) and what we're currently doing. Portage
179 # saves what it is currently doing in this database so that it can be
180 # resumed when you call it with the --resume option.
181 #
182 # parallel_emerge does not save what it is currently doing in the mtimedb,
183 # so we do not support the --resume option.
184 self.mtimedb = None
185
186 # The portage configuration for our current root. This contains the portage
187 # settings (see below) and the three portage trees for our current root.
188 # (The three portage trees are explained below, in the documentation for
189 # the "trees" member.)
190 self.root_config = None
191
192 # The scheduler graph is used by emerge to calculate what packages to
193 # install. We don't actually install any deps, so this isn't really used,
194 # but we pass it in to the Scheduler object anyway.
195 self.scheduler_graph = None
196
197 # Portage settings for our current session. Most of these settings are set
198 # in make.conf inside our current install root.
199 self.settings = None
200
201 # The spinner, which spews stuff to stdout to indicate that portage is
202 # doing something. We maintain our own spinner, so we set the portage
203 # spinner to "silent" mode.
204 self.spinner = None
205
206 # The portage trees. There are separate portage trees for each root. To get
207 # the portage tree for the current root, you can look in self.trees[root],
208 # where root = self.settings["ROOT"].
209 #
210 # In each root, there are three trees: vartree, porttree, and bintree.
211 # - vartree: A database of the currently-installed packages.
212 # - porttree: A database of ebuilds, that can be used to build packages.
213 # - bintree: A database of binary packages.
214 self.trees = None
215
216
217class DepGraphGenerator(object):
218 """Grab dependency information about packages from portage.
219
220 Typical usage:
221 deps = DepGraphGenerator()
222 deps.Initialize(sys.argv[1:])
223 deps_tree, deps_info = deps.GenDependencyTree()
224 deps_graph = deps.GenDependencyGraph(deps_tree, deps_info)
225 deps.PrintTree(deps_tree)
226 PrintDepsMap(deps_graph)
227 """
228
David Jamesa22906f2011-05-04 19:53:26 -0700229 __slots__ = ["board", "emerge", "mandatory_source", "no_workon_deps",
230 "nomerge", "package_db", "rebuild", "show_output",
231 "force_remote_binary", "forced_remote_binary_packages"]
David Jamesfcb70ef2011-02-02 16:02:30 -0800232
233 def __init__(self):
234 self.board = None
235 self.emerge = EmergeData()
David Jamesa22906f2011-05-04 19:53:26 -0700236 self.mandatory_source = set()
237 self.no_workon_deps = False
238 self.nomerge = set()
David Jamesfcb70ef2011-02-02 16:02:30 -0800239 self.package_db = {}
David Jamesa22906f2011-05-04 19:53:26 -0700240 self.rebuild = False
David Jamesfcb70ef2011-02-02 16:02:30 -0800241 self.show_output = False
David Jamesa22906f2011-05-04 19:53:26 -0700242 self.force_remote_binary = set()
243 self.forced_remote_binary_packages = set()
David Jamesfcb70ef2011-02-02 16:02:30 -0800244
245 def ParseParallelEmergeArgs(self, argv):
246 """Read the parallel emerge arguments from the command-line.
247
248 We need to be compatible with emerge arg format. We scrape arguments that
249 are specific to parallel_emerge, and pass through the rest directly to
250 emerge.
251 Args:
252 argv: arguments list
253 Returns:
254 Arguments that don't belong to parallel_emerge
255 """
256 emerge_args = []
257 for arg in argv:
258 # Specifically match arguments that are specific to parallel_emerge, and
259 # pass through the rest.
260 if arg.startswith("--board="):
261 self.board = arg.replace("--board=", "")
262 elif arg.startswith("--workon="):
263 workon_str = arg.replace("--workon=", "")
David Jamesa22906f2011-05-04 19:53:26 -0700264 package_list = shlex.split(" ".join(shlex.split(workon_str)))
265 self.mandatory_source.update(package_list)
David Jamesfcb70ef2011-02-02 16:02:30 -0800266 elif arg.startswith("--force-remote-binary="):
267 force_remote_binary = arg.replace("--force-remote-binary=", "")
David Jamesa22906f2011-05-04 19:53:26 -0700268 force_remote_binary = \
269 shlex.split(" ".join(shlex.split(force_remote_binary)))
270 self.force_remote_binary.update(force_remote_binary)
271 elif arg.startswith("--nomerge="):
272 nomerge_str = arg.replace("--nomerge=", "")
273 package_list = shlex.split(" ".join(shlex.split(nomerge_str)))
274 self.nomerge.update(package_list)
275 elif arg == "--no-workon-deps":
276 self.no_workon_deps = True
277 elif arg == "--rebuild":
278 self.rebuild = True
David Jamesfcb70ef2011-02-02 16:02:30 -0800279 elif arg == "--show-output":
280 self.show_output = True
281 else:
282 # Not one of our options, so pass through to emerge.
283 emerge_args.append(arg)
284
David Jamesa22906f2011-05-04 19:53:26 -0700285 if self.rebuild:
286 if self.no_workon_deps:
287 print "--rebuild is not compatible with --no-workon-deps"
288 sys.exit(1)
David Jamesfcb70ef2011-02-02 16:02:30 -0800289
290 return emerge_args
291
292 def Initialize(self, args):
293 """Initializer. Parses arguments and sets up portage state."""
294
295 # Parse and strip out args that are just intended for parallel_emerge.
296 emerge_args = self.ParseParallelEmergeArgs(args)
297
298 # Setup various environment variables based on our current board. These
299 # variables are normally setup inside emerge-${BOARD}, but since we don't
300 # call that script, we have to set it up here. These variables serve to
301 # point our tools at /build/BOARD and to setup cross compiles to the
302 # appropriate board as configured in toolchain.conf.
303 if self.board:
304 os.environ["PORTAGE_CONFIGROOT"] = "/build/" + self.board
305 os.environ["PORTAGE_SYSROOT"] = "/build/" + self.board
306 os.environ["SYSROOT"] = "/build/" + self.board
307 srcroot = "%s/../../src" % os.path.dirname(os.path.realpath(__file__))
308 # Strip the variant out of the board name to look for the toolchain. This
309 # is similar to what setup_board does.
310 board_no_variant = self.board.split('_')[0]
311 public_toolchain_path = ("%s/overlays/overlay-%s/toolchain.conf" %
312 (srcroot, board_no_variant))
313 private_toolchain_path = (
314 "%s/private-overlays/overlay-%s-private/toolchain.conf" %
315 (srcroot, board_no_variant))
316 if os.path.isfile(public_toolchain_path):
317 toolchain_path = public_toolchain_path
318 elif os.path.isfile(private_toolchain_path):
319 toolchain_path = private_toolchain_path
320 else:
321 print "Not able to locate toolchain.conf in board overlays"
322 sys.exit(1)
323
324 f = open(toolchain_path)
325 os.environ["CHOST"] = f.readline().strip()
326 f.close()
327
328 # Although CHROMEOS_ROOT isn't specific to boards, it's normally setup
329 # inside emerge-${BOARD}, so we set it up here for compatibility. It
330 # will be going away soon as we migrate to CROS_WORKON_SRCROOT.
331 os.environ.setdefault("CHROMEOS_ROOT", os.environ["HOME"] + "/trunk")
332
333 # Turn off interactive delays
334 os.environ["EBEEP_IGNORE"] = "1"
335 os.environ["EPAUSE_IGNORE"] = "1"
336 os.environ["UNMERGE_DELAY"] = "0"
337
338 # Parse the emerge options.
339 action, opts, cmdline_packages = parse_opts(emerge_args)
340
341 # If we're installing to the board, we want the --root-deps option so that
342 # portage will install the build dependencies to that location as well.
343 if self.board:
344 opts.setdefault("--root-deps", True)
345
346 # Set environment variables based on options. Portage normally sets these
347 # environment variables in emerge_main, but we can't use that function,
348 # because it also does a bunch of other stuff that we don't want.
349 # TODO(davidjames): Patch portage to move this logic into a function we can
350 # reuse here.
351 if "--debug" in opts:
352 os.environ["PORTAGE_DEBUG"] = "1"
353 if "--config-root" in opts:
354 os.environ["PORTAGE_CONFIGROOT"] = opts["--config-root"]
355 if "--root" in opts:
356 os.environ["ROOT"] = opts["--root"]
357 if "--accept-properties" in opts:
358 os.environ["ACCEPT_PROPERTIES"] = opts["--accept-properties"]
359
360 # Portage has two flags for doing collision protection: collision-protect
361 # and protect-owned. The protect-owned feature is enabled by default and
362 # is quite useful: it checks to make sure that we don't have multiple
363 # packages that own the same file. The collision-protect feature is more
364 # strict, and less useful: it fails if it finds a conflicting file, even
365 # if that file was created by an earlier ebuild that failed to install.
366 #
367 # We want to disable collision-protect here because we don't handle
368 # failures during the merge step very well. Sometimes we leave old files
369 # lying around and they cause problems, so for now we disable the flag.
370 # TODO(davidjames): Look for a better solution.
371 features = os.environ.get("FEATURES", "") + " -collision-protect"
372
373 # If we're installing packages to the board, and we're not using the
374 # official flag, we can enable the following optimizations:
375 # 1) Don't lock during install step. This allows multiple packages to be
376 # installed at once. This is safe because our board packages do not
377 # muck with each other during the post-install step.
378 # 2) Don't update the environment until the end of the build. This is
379 # safe because board packages don't need to run during the build --
380 # they're cross-compiled, so our CPU architecture doesn't support them
381 # anyway.
382 if self.board and os.environ.get("CHROMEOS_OFFICIAL") != "1":
383 os.environ.setdefault("PORTAGE_LOCKS", "false")
384 features = features + " no-env-update"
385
386 os.environ["FEATURES"] = features
387
388 # Now that we've setup the necessary environment variables, we can load the
389 # emerge config from disk.
390 settings, trees, mtimedb = load_emerge_config()
391
392 # Check whether our portage tree is out of date. Typically, this happens
393 # when you're setting up a new portage tree, such as in setup_board and
394 # make_chroot. In that case, portage applies a bunch of global updates
395 # here. Once the updates are finished, we need to commit any changes
396 # that the global update made to our mtimedb, and reload the config.
397 #
398 # Portage normally handles this logic in emerge_main, but again, we can't
399 # use that function here.
400 if _global_updates(trees, mtimedb["updates"]):
401 mtimedb.commit()
402 settings, trees, mtimedb = load_emerge_config(trees=trees)
403
404 # Setup implied options. Portage normally handles this logic in
405 # emerge_main.
406 if "--buildpkgonly" in opts or "buildpkg" in settings.features:
407 opts.setdefault("--buildpkg", True)
408 if "--getbinpkgonly" in opts:
409 opts.setdefault("--usepkgonly", True)
410 opts.setdefault("--getbinpkg", True)
411 if "getbinpkg" in settings.features:
412 # Per emerge_main, FEATURES=getbinpkg overrides --getbinpkg=n
413 opts["--getbinpkg"] = True
414 if "--getbinpkg" in opts or "--usepkgonly" in opts:
415 opts.setdefault("--usepkg", True)
416 if "--fetch-all-uri" in opts:
417 opts.setdefault("--fetchonly", True)
418 if "--skipfirst" in opts:
419 opts.setdefault("--resume", True)
420 if "--buildpkgonly" in opts:
421 # --buildpkgonly will not merge anything, so it overrides all binary
422 # package options.
423 for opt in ("--getbinpkg", "--getbinpkgonly",
424 "--usepkg", "--usepkgonly"):
425 opts.pop(opt, None)
426 if (settings.get("PORTAGE_DEBUG", "") == "1" and
427 "python-trace" in settings.features):
428 portage.debug.set_trace(True)
429
430 # Complain about unsupported options
David Jamesa22906f2011-05-04 19:53:26 -0700431 for opt in ("--ask", "--ask-enter-invalid", "--complete-graph",
432 "--resume", "--skipfirst"):
David Jamesfcb70ef2011-02-02 16:02:30 -0800433 if opt in opts:
434 print "%s is not supported by parallel_emerge" % opt
435 sys.exit(1)
436
437 # Make emerge specific adjustments to the config (e.g. colors!)
438 adjust_configs(opts, trees)
439
440 # Save our configuration so far in the emerge object
441 emerge = self.emerge
442 emerge.action, emerge.opts = action, opts
443 emerge.settings, emerge.trees, emerge.mtimedb = settings, trees, mtimedb
444 emerge.cmdline_packages = cmdline_packages
445 root = settings["ROOT"]
446 emerge.root_config = trees[root]["root_config"]
447
David Jamesa22906f2011-05-04 19:53:26 -0700448 if new_portage and "--usepkg" in opts:
David Jamesfcb70ef2011-02-02 16:02:30 -0800449 emerge.trees[root]["bintree"].populate("--getbinpkg" in opts)
450
David Jamesa22906f2011-05-04 19:53:26 -0700451 def CheckUseFlags(self, pkgsettings, cur_pkg, new_pkg):
452 """Are the use flags in cur_pkg up to date?
453
454 Return True if use flags are up to date; return false otherwise."""
455
456 # cur_use: The set of flags that were enabled when the package was
457 # first installed.
458 # cur_iuse: The set of flags that affected the specified package
459 # when it was first installed.
460 #
461 # The intersection of cur_use and cur_iuse provides the set of
462 # flags that were enabled and affected the specified package.
463 cur_use = cur_pkg.use.enabled
464 cur_iuse = cur_pkg.iuse.all
465
466 # Check whether this package is already installed with the right use
467 # flags.
468 #
469 # now_use: The set of flags (special and non-special) that are now
470 # enabled for the specified package.
471 # now_iuse: The set of non-special flags that affect the specified
472 # package.
473 now_use = new_pkg.use.enabled
474 now_iuse = new_pkg.iuse.all
475
476 # Tell portage we want to lookup the flags for the specified package
477 # in package.use.{mask,force}
478 pkgsettings.setcpv(new_pkg.cpv)
479
480 # Grab the set of flags that are requested for the given package.
481 # This includes flags that don't affect the package, and includes
482 # all sources of flags (e.g. USE environment variable, make.conf,
483 # make.defaults, package.use.{mask,force}, etc.).
484 #
485 # This is used by portage in the _reinstall_for_flags function below.
486 forced_flags = set(pkgsettings.useforce).union(pkgsettings.usemask)
487
488 depgraph = self.emerge.depgraph
489 flags = depgraph._reinstall_for_flags(forced_flags, cur_use,
490 cur_iuse, now_use, now_iuse)
491 return not flags
492
David Jamesfcb70ef2011-02-02 16:02:30 -0800493 def CreateDepgraph(self, emerge, packages):
494 """Create an emerge depgraph object."""
495 # Setup emerge options.
496 emerge_opts = emerge.opts.copy()
497
David Jamesa22906f2011-05-04 19:53:26 -0700498 # Enable --emptytree so that we get the full tree, which we need for
499 # dependency analysis. By default, with this option, emerge optimizes
500 # the graph by removing uninstall instructions from the graph. By
501 # specifying --tree as well, we tell emerge that it's not safe to remove
502 # uninstall instructions because we're planning on analyzing the output.
503 emerge_opts["--tree"] = True
504 emerge_opts["--emptytree"] = True
505
506 # Set up parameters.
David Jamesfcb70ef2011-02-02 16:02:30 -0800507 params = create_depgraph_params(emerge_opts, emerge.action)
David Jamesa22906f2011-05-04 19:53:26 -0700508 frozen_config = _frozen_depgraph_config(emerge.settings, emerge.trees,
509 emerge_opts, emerge.spinner)
510 backtrack_max = emerge_opts.get('--backtrack', 5)
511 backtrack_parameters = {}
512 allow_backtracking = backtrack_max > 0
David Jamesfcb70ef2011-02-02 16:02:30 -0800513
David Jamesa22906f2011-05-04 19:53:26 -0700514 # Try up to backtrack_max times to create a working depgraph. Each time we
515 # run into a conflict, mask the offending package and try again.
516 # TODO(davidjames): When Portage supports --force-remote-binary directly,
517 # switch back to using the backtrack_depgraph function.
518 for i in range(backtrack_max + 2):
519 # Create a depgraph object.
520 depgraph = emerge_depgraph(emerge.settings, emerge.trees, emerge_opts,
521 params, emerge.spinner, frozen_config=frozen_config,
522 allow_backtracking=allow_backtracking,
523 **backtrack_parameters)
524
525 if i == 0:
526 for cpv in self.forced_remote_binary_packages:
527 # If --force-remote-binary was specified, we want to use this package
528 # regardless of its use flags. Unfortunately, Portage doesn't support
529 # ignoring use flags for just one package. To convince Portage to
530 # install the package, we trick Portage into thinking the package has
531 # the right use flags.
532 # TODO(davidjames): Update Portage to support --force-remote-binary
533 # directly, so that this hack isn't necessary.
534 pkg = depgraph._pkg(cpv, "binary", emerge.root_config)
535 pkgsettings = frozen_config.pkgsettings[pkg.root]
536 pkgsettings.setcpv(pkg)
537 pkg.use.enabled = pkgsettings["PORTAGE_USE"].split()
538
539 # Select the packages we want.
540 success, favorites = depgraph.select_files(packages)
541 if success:
542 break
543 elif depgraph.need_restart() and i < backtrack_max:
544 # Looks like we found some packages that can't be installed due to
545 # conflicts. Try again, masking out the conflicting packages.
546 if new_portage:
547 backtrack_parameters = depgraph.get_backtrack_parameters()
548 else:
549 backtrack_parameters = {
550 'runtime_pkg_mask': depgraph.get_runtime_pkg_mask()
551 }
552 elif allow_backtracking and i > 0:
553 # Looks like we can't solve the graph. Stop backtracking and report an
554 # error message.
555 backtrack_parameters.pop('runtime_pkg_mask', None)
556 allow_backtracking = False
557 else:
558 break
559
560 # Delete the --tree option, because we don't really want to display a
561 # tree. We just wanted to get emerge to leave uninstall instructions on
562 # the graph. Later, when we display the graph, we'll want standard-looking
563 # output, so removing the --tree option is important.
564 frozen_config.myopts.pop("--tree", None)
David Jamesfcb70ef2011-02-02 16:02:30 -0800565
566 emerge.depgraph = depgraph
567
568 # Is it impossible to honor the user's request? Bail!
569 if not success:
570 depgraph.display_problems()
571 sys.exit(1)
572
David Jamesa22906f2011-05-04 19:53:26 -0700573 def GenDependencyTree(self, remote_pkgs):
David Jamesfcb70ef2011-02-02 16:02:30 -0800574 """Get dependency tree info from emerge.
575
David Jamesa22906f2011-05-04 19:53:26 -0700576 TODO(): Update cros_extract_deps to also use this code.
David Jamesfcb70ef2011-02-02 16:02:30 -0800577 Returns:
578 Dependency tree
579 """
580 start = time.time()
581
582 emerge = self.emerge
583
584 # Create a list of packages to merge
585 packages = set(emerge.cmdline_packages[:])
David Jamesa22906f2011-05-04 19:53:26 -0700586 if self.mandatory_source:
587 packages.update(self.mandatory_source)
588 if self.force_remote_binary:
589 forced_pkgs = {}
590 for pkg in remote_pkgs:
591 category, pkgname, _, _ = portage.catpkgsplit(pkg)
592 full_pkgname = "%s/%s" % (category, pkgname)
593 if (pkgname in self.force_remote_binary or
594 full_pkgname in self.force_remote_binary):
595 forced_pkgs.setdefault(full_pkgname, []).append(pkg)
596
597 # Add forced binary packages to the dependency list. This is necessary
598 # to ensure that the install plan contains the right package.
599 #
600 # Putting the forced binary package at the beginning of the list is an
601 # optimization that helps avoid unnecessary backtracking (e.g., if
602 # Portage first selects the wrong version, and then backtracks later, it
603 # takes a bit longer and uses up an unnecessary backtrack iteration.)
604 packages = list(packages)
605 for pkgs in forced_pkgs.values():
606 forced_package = portage.versions.best(pkgs)
607 packages.insert(0, "=%s" % forced_package)
608 self.forced_remote_binary_packages.add(forced_package)
David Jamesfcb70ef2011-02-02 16:02:30 -0800609
610 # Tell emerge to be quiet. We print plenty of info ourselves so we don't
611 # need any extra output from portage.
612 portage.util.noiselimit = -1
613
614 # My favorite feature: The silent spinner. It doesn't spin. Ever.
615 # I'd disable the colors by default too, but they look kind of cool.
616 emerge.spinner = stdout_spinner()
617 emerge.spinner.update = emerge.spinner.update_quiet
618
619 if "--quiet" not in emerge.opts:
620 print "Calculating deps..."
621
622 self.CreateDepgraph(emerge, packages)
623 depgraph = emerge.depgraph
624
625 # Build our own tree from the emerge digraph.
626 deps_tree = {}
627 digraph = depgraph._dynamic_config.digraph
628 for node, node_deps in digraph.nodes.items():
629 # Calculate dependency packages that need to be installed first. Each
630 # child on the digraph is a dependency. The "operation" field specifies
631 # what we're doing (e.g. merge, uninstall, etc.). The "priorities" array
632 # contains the type of dependency (e.g. build, runtime, runtime_post,
633 # etc.)
634 #
635 # Emerge itself actually treats some dependencies as "soft" dependencies
636 # and sometimes ignores them. We don't do that -- we honor all
637 # dependencies unless we're forced to prune them because they're cyclic.
638 #
639 # Portage refers to the identifiers for packages as a CPV. This acronym
640 # stands for Component/Path/Version.
641 #
642 # Here's an example CPV: chromeos-base/power_manager-0.0.1-r1
643 # Split up, this CPV would be:
644 # C -- Component: chromeos-base
645 # P -- Path: power_manager
646 # V -- Version: 0.0.1-r1
647 #
648 # We just refer to CPVs as packages here because it's easier.
649 deps = {}
650 for child, priorities in node_deps[0].items():
651 if isinstance(child, SetArg): continue
652 deps[str(child.cpv)] = dict(action=str(child.operation),
653 deptype=str(priorities[-1]),
654 deps={})
655
656 # We've built our list of deps, so we can add our package to the tree.
657 if isinstance(node, Package):
658 deps_tree[str(node.cpv)] = dict(action=str(node.operation),
659 deps=deps)
660
David Jamesa22906f2011-05-04 19:53:26 -0700661 emptytree = "--emptytree" in emerge.opts
662
David Jamesfcb70ef2011-02-02 16:02:30 -0800663 # Ask portage for its install plan, so that we can only throw out
David Jamesa22906f2011-05-04 19:53:26 -0700664 # dependencies that portage throws out. Also, keep track of the old
665 # versions of packages that we're either upgrading or replacing.
666 #
667 # The "vardb" is the database of installed packages.
668 root = emerge.settings["ROOT"]
669 frozen_config = depgraph._frozen_config
670 vardb = frozen_config.trees[root]["vartree"].dbapi
671 pkgsettings = frozen_config.pkgsettings[root]
672
David Jamesfcb70ef2011-02-02 16:02:30 -0800673 deps_info = {}
674 for pkg in depgraph.altlist():
675 if isinstance(pkg, Package):
David Jamesa22906f2011-05-04 19:53:26 -0700676 # If we're not using --force-remote-binary, check what flags are being
677 # used by the real package.
678 if pkg.operation != "uninstall" and "--usepkgonly" not in emerge.opts:
679 try:
680 pkg = emerge.depgraph._pkg(pkg.cpv, "ebuild", emerge.root_config)
681 except portage.exception.PackageNotFound:
682 # This is a --force-remote-binary package.
683 pass
David Jamesfcb70ef2011-02-02 16:02:30 -0800684 self.package_db[pkg.cpv] = pkg
685
David Jamesa22906f2011-05-04 19:53:26 -0700686 # If we're not in emptytree mode, and we're going to replace a package
687 # that is already installed, then this operation is possibly optional.
688 # ("--selective" mode is handled later, in RemoveInstalledPackages())
689 optional = False
690 if pkg.operation != "uninstall" and not emptytree:
691 for vardb_pkg in vardb.match_pkgs(pkg.cpv):
692 if self.CheckUseFlags(pkgsettings, vardb_pkg, pkg):
693 optional = True
694 break
695
David Jamesfcb70ef2011-02-02 16:02:30 -0800696 # Save off info about the package
David Jamesa22906f2011-05-04 19:53:26 -0700697 deps_info[str(pkg.cpv)] = {"idx": len(deps_info),
698 "optional": optional}
David Jamesfcb70ef2011-02-02 16:02:30 -0800699
700 seconds = time.time() - start
701 if "--quiet" not in emerge.opts:
702 print "Deps calculated in %dm%.1fs" % (seconds / 60, seconds % 60)
703
704 return deps_tree, deps_info
705
706 def PrintTree(self, deps, depth=""):
707 """Print the deps we have seen in the emerge output.
708
709 Args:
710 deps: Dependency tree structure.
711 depth: Allows printing the tree recursively, with indentation.
712 """
713 for entry in sorted(deps):
714 action = deps[entry]["action"]
715 print "%s %s (%s)" % (depth, entry, action)
716 self.PrintTree(deps[entry]["deps"], depth=depth + " ")
717
David Jamesa22906f2011-05-04 19:53:26 -0700718 def RemotePackageDatabase(self):
719 """Grab the latest binary package database from the prebuilt server.
720
721 We need to know the modification times of the prebuilt packages so that we
722 know when it is OK to use these packages and when we should rebuild them
723 instead.
724
725 Returns:
726 A dict mapping package identifiers to modification times.
727 """
728 root = self.emerge.settings["ROOT"]
729 bindb = self.emerge.trees[root]["bintree"].dbapi
730 prebuilt_pkgs = {}
731 for pkg in bindb.cpv_all():
732 prebuilt_pkgs[pkg] = bindb.aux_get(pkg, ["BUILD_TIME"])[0]
733 return prebuilt_pkgs
734
735 def GenDependencyGraph(self, deps_tree, deps_info, remote_pkgs):
David Jamesfcb70ef2011-02-02 16:02:30 -0800736 """Generate a doubly linked dependency graph.
737
738 Args:
739 deps_tree: Dependency tree structure.
740 deps_info: More details on the dependencies.
741 Returns:
742 Deps graph in the form of a dict of packages, with each package
743 specifying a "needs" list and "provides" list.
744 """
745 emerge = self.emerge
746 root = emerge.settings["ROOT"]
747
David Jamesa22906f2011-05-04 19:53:26 -0700748 # It's useful to know what packages will actually end up on the
749 # system at some point. Packages in final_db are either already
750 # installed, or will be installed by the time we're done.
751 final_db = emerge.depgraph._dynamic_config.mydbapi[root]
752
753 # final_pkgs is a set of the packages we found in the final_db. These
754 # packages are either already installed, or will be installed by the time
755 # we're done. It's populated in BuildFinalPackageSet()
756 final_pkgs = set()
757
758 # These packages take a really long time to build, so, for expediency, we
759 # are blacklisting them from automatic rebuilds because one of their
760 # dependencies needs to be recompiled.
761 rebuild_blacklist = set()
762 for pkg in ("chromeos-base/chromeos-chrome", "media-plugins/o3d",
763 "dev-java/icedtea"):
764 for match in final_db.match_pkgs(pkg):
765 rebuild_blacklist.add(str(match.cpv))
766
David Jamesfcb70ef2011-02-02 16:02:30 -0800767 # deps_map is the actual dependency graph.
768 #
769 # Each package specifies a "needs" list and a "provides" list. The "needs"
770 # list indicates which packages we depend on. The "provides" list
771 # indicates the reverse dependencies -- what packages need us.
772 #
773 # We also provide some other information in the dependency graph:
774 # - action: What we're planning on doing with this package. Generally,
775 # "merge", "nomerge", or "uninstall"
David Jamesa22906f2011-05-04 19:53:26 -0700776 # - mandatory_source:
777 # If true, indicates that this package must be compiled from source.
778 # We set this for "workon" packages, and for packages where the
779 # binaries are known to be out of date.
780 # - mandatory:
781 # If true, indicates that this package must be installed. We don't care
782 # whether it's binary or source, unless the mandatory_source flag is
783 # also set.
784 # - force_remote_binary:
785 # If true, indicates that we want to update to the latest remote prebuilt
786 # of this package. Packages that depend on this package should be built
787 # from source.
788 #
David Jamesfcb70ef2011-02-02 16:02:30 -0800789 deps_map = {}
790
791 def ReverseTree(packages):
792 """Convert tree to digraph.
793
794 Take the tree of package -> requirements and reverse it to a digraph of
795 buildable packages -> packages they unblock.
796 Args:
797 packages: Tree(s) of dependencies.
798 Returns:
799 Unsanitized digraph.
800 """
801 for pkg in packages:
802
803 # Create an entry for the package
804 action = packages[pkg]["action"]
David Jamesa22906f2011-05-04 19:53:26 -0700805 default_pkg = {"needs": {}, "provides": set(), "action": action,
806 "mandatory_source": False, "mandatory": False,
807 "force_remote_binary": False}
David Jamesfcb70ef2011-02-02 16:02:30 -0800808 this_pkg = deps_map.setdefault(pkg, default_pkg)
809
810 # Create entries for dependencies of this package first.
811 ReverseTree(packages[pkg]["deps"])
812
813 # Add dependencies to this package.
814 for dep, dep_item in packages[pkg]["deps"].iteritems():
815 dep_pkg = deps_map[dep]
816 dep_type = dep_item["deptype"]
817 if dep_type != "runtime_post":
818 dep_pkg["provides"].add(pkg)
819 this_pkg["needs"][dep] = dep_type
820
David Jamesa22906f2011-05-04 19:53:26 -0700821 def BuildFinalPackageSet():
822 # If this package is installed, or will get installed, add it to
823 # final_pkgs
824 for pkg in deps_map:
825 for match in final_db.match_pkgs(pkg):
826 if match.cpv in deps_info:
827 final_pkgs.add(str(match.cpv))
828
David Jamesfcb70ef2011-02-02 16:02:30 -0800829 def FindCycles():
830 """Find cycles in the dependency tree.
831
832 Returns:
833 A dict mapping cyclic packages to a dict of the deps that cause
834 cycles. For each dep that causes cycles, it returns an example
835 traversal of the graph that shows the cycle.
836 """
837
838 def FindCyclesAtNode(pkg, cycles, unresolved, resolved):
839 """Find cycles in cyclic dependencies starting at specified package.
840
841 Args:
842 pkg: Package identifier.
843 cycles: A dict mapping cyclic packages to a dict of the deps that
844 cause cycles. For each dep that causes cycles, it returns an
845 example traversal of the graph that shows the cycle.
846 unresolved: Nodes that have been visited but are not fully processed.
847 resolved: Nodes that have been visited and are fully processed.
848 """
849 pkg_cycles = cycles.get(pkg)
850 if pkg in resolved and not pkg_cycles:
851 # If we already looked at this package, and found no cyclic
852 # dependencies, we can stop now.
853 return
854 unresolved.append(pkg)
855 for dep in deps_map[pkg]["needs"]:
856 if dep in unresolved:
857 idx = unresolved.index(dep)
858 mycycle = unresolved[idx:] + [dep]
859 for i in range(len(mycycle) - 1):
860 pkg1, pkg2 = mycycle[i], mycycle[i+1]
861 cycles.setdefault(pkg1, {}).setdefault(pkg2, mycycle)
862 elif not pkg_cycles or dep not in pkg_cycles:
863 # Looks like we haven't seen this edge before.
864 FindCyclesAtNode(dep, cycles, unresolved, resolved)
865 unresolved.pop()
866 resolved.add(pkg)
867
868 cycles, unresolved, resolved = {}, [], set()
869 for pkg in deps_map:
870 FindCyclesAtNode(pkg, cycles, unresolved, resolved)
871 return cycles
872
David Jamesa22906f2011-05-04 19:53:26 -0700873 def RemoveInstalledPackages():
David Jamesfcb70ef2011-02-02 16:02:30 -0800874 """Remove installed packages, propagating dependencies."""
David Jamesa22906f2011-05-04 19:53:26 -0700875
876 # If we're in non-selective mode, the packages specified on the command
877 # line are generally mandatory.
878 #
879 # There are a few exceptions to this rule:
880 # 1. If the package isn't getting installed because it's in
881 # package.provided, it's not mandatory.
882 # 2. If the package isn't getting installed because we're in --onlydeps
883 # mode, it's not mandatory either.
884 if "--selective" in emerge.opts:
885 selective = emerge.opts["--selective"] != "n"
886 else:
887 selective = ("--noreplace" in emerge.opts or
888 "--update" in emerge.opts or
889 "--newuse" in emerge.opts or
890 "--reinstall" in emerge.opts)
891 onlydeps = "--onlydeps" in emerge.opts
892 if not selective:
893 for pkg in emerge.cmdline_packages:
894 # If the package specified on the command-line is in our install
895 # list, mark it as non-optional.
896 found = False
897 for db_pkg in final_db.match_pkgs(pkg):
898 this_pkg = deps_info.get(db_pkg.cpv)
899 if this_pkg:
900 found = True
901 this_pkg["optional"] = False
902
903 # We didn't find the package in our final db. If we're not in
904 # --onlydeps mode, this likely means that the package was specified
905 # in package.provided.
906 if not found and not onlydeps and "--verbose" in emerge.opts:
907 print "Skipping %s (is it in package.provided?)" % pkg
908
David Jamesfcb70ef2011-02-02 16:02:30 -0800909 # Schedule packages that aren't on the install list for removal
910 rm_pkgs = set(deps_map.keys()) - set(deps_info.keys())
911
David Jamesa22906f2011-05-04 19:53:26 -0700912 # Schedule optional packages for removal
913 for pkg, info in deps_info.items():
914 if info["optional"]:
915 rm_pkgs.add(pkg)
916
917 # Schedule nomerge packages for removal
918 for pkg in self.nomerge:
919 for db_pkg in final_db.match_pkgs(pkg):
920 if db_pkg.cpv in deps_map:
921 rm_pkgs.add(str(db_pkg.cpv))
922
David Jamesfcb70ef2011-02-02 16:02:30 -0800923 # Remove the packages we don't want, simplifying the graph and making
924 # it easier for us to crack cycles.
925 for pkg in sorted(rm_pkgs):
926 this_pkg = deps_map[pkg]
927 needs = this_pkg["needs"]
928 provides = this_pkg["provides"]
929 for dep in needs:
930 dep_provides = deps_map[dep]["provides"]
931 dep_provides.update(provides)
932 dep_provides.discard(pkg)
933 dep_provides.discard(dep)
934 for target in provides:
935 target_needs = deps_map[target]["needs"]
936 target_needs.update(needs)
937 target_needs.pop(pkg, None)
938 target_needs.pop(target, None)
939 del deps_map[pkg]
940
941 def PrintCycleBreak(basedep, dep, mycycle):
942 """Print details about a cycle that we are planning on breaking.
943
944 We are breaking a cycle where dep needs basedep. mycycle is an
945 example cycle which contains dep -> basedep."""
946
947 # If it's an optional dependency, there's no need to spam the user with
948 # warning messages.
949 needs = deps_map[dep]["needs"]
950 depinfo = needs.get(basedep, "deleted")
951 if depinfo == "optional":
952 return
953
954 # Notify the user that we're breaking a cycle.
955 print "Breaking %s -> %s (%s)" % (dep, basedep, depinfo)
956
957 # Show cycle.
958 for i in range(len(mycycle) - 1):
959 pkg1, pkg2 = mycycle[i], mycycle[i+1]
960 needs = deps_map[pkg1]["needs"]
961 depinfo = needs.get(pkg2, "deleted")
962 if pkg1 == dep and pkg2 == basedep:
963 depinfo = depinfo + ", deleting"
964 print " %s -> %s (%s)" % (pkg1, pkg2, depinfo)
965
966 def SanitizeTree():
967 """Remove circular dependencies.
968
969 We prune all dependencies involved in cycles that go against the emerge
970 ordering. This has a nice property: we're guaranteed to merge
971 dependencies in the same order that portage does.
972
973 Because we don't treat any dependencies as "soft" unless they're killed
974 by a cycle, we pay attention to a larger number of dependencies when
975 merging. This hurts performance a bit, but helps reliability.
976 """
977 start = time.time()
978 cycles = FindCycles()
979 while cycles:
980 for dep, mycycles in cycles.iteritems():
981 for basedep, mycycle in mycycles.iteritems():
982 if deps_info[basedep]["idx"] >= deps_info[dep]["idx"]:
983 PrintCycleBreak(basedep, dep, mycycle)
984 del deps_map[dep]["needs"][basedep]
985 deps_map[basedep]["provides"].remove(dep)
986 cycles = FindCycles()
987 seconds = time.time() - start
988 if "--quiet" not in emerge.opts and seconds >= 0.1:
989 print "Tree sanitized in %dm%.1fs" % (seconds / 60, seconds % 60)
990
David Jamesa22906f2011-05-04 19:53:26 -0700991 def AddSecretDeps():
992 """Find these tagged packages and add extra dependencies.
David Jamesfcb70ef2011-02-02 16:02:30 -0800993
David Jamesa22906f2011-05-04 19:53:26 -0700994 For debugging dependency problems.
995 """
996 for bad in secret_deps:
997 needed = secret_deps[bad]
998 bad_pkg = None
999 needed_pkg = None
1000 for dep in deps_map:
1001 if dep.find(bad) != -1:
1002 bad_pkg = dep
1003 if dep.find(needed) != -1:
1004 needed_pkg = dep
1005 if bad_pkg and needed_pkg:
1006 deps_map[needed_pkg]["provides"].add(bad_pkg)
1007 deps_map[bad_pkg]["needs"][needed_pkg] = "secret"
1008
1009 def MergeChildren(pkg, merge_type):
1010 """Merge this package and all packages it provides."""
1011
1012 this_pkg = deps_map[pkg]
1013 if (this_pkg[merge_type] or pkg not in final_pkgs):
1014 return
1015
1016 # Mark this package as non-optional
1017 deps_info[pkg]["optional"] = False
1018 this_pkg[merge_type] = True
1019 for w in this_pkg["provides"].difference(rebuild_blacklist):
1020 MergeChildren(w, merge_type)
1021
1022 if this_pkg["action"] == "nomerge":
1023 this_pkg["action"] = "merge"
1024
1025 def LocalPackageDatabase():
1026 """Get the modification times of the packages in the local database.
1027
1028 We need to know the modification times of the local packages so that we
1029 know when they need to be rebuilt.
1030
1031 Returns:
1032 A dict mapping package identifiers to modification times.
1033 """
1034 vardb = emerge.trees[root]["vartree"].dbapi
1035 local_pkgs = {}
1036 for pkg in vardb.cpv_all():
1037 local_pkgs[pkg] = vardb.aux_get(pkg, ["BUILD_TIME"])[0]
1038 return local_pkgs
1039
1040 def AutoRebuildDeps(local_pkgs, remote_pkgs, cycles):
1041 """Recursively rebuild packages when necessary using modification times.
1042
1043 If you've modified a package, it's a good idea to rebuild all the packages
1044 that depend on it from source. This function looks for any packages which
1045 depend on packages that have been modified and ensures that they get
1046 rebuilt.
1047
1048 Args:
1049 local_pkgs: Modification times from the local database.
1050 remote_pkgs: Modification times from the prebuilt server.
1051 cycles: Dictionary returned from FindCycles()
1052
1053 Returns:
1054 The set of packages we marked as needing to be merged.
1055 """
1056
1057 def PrebuiltsReady(pkg, pkg_db, cache):
1058 """Check whether the prebuilts are ready for pkg and all deps.
1059
1060 Args:
1061 pkg: The specified package.
1062 pkg_db: The package DB to use.
1063 cache: A dict, where the results are stored.
1064
1065 Returns:
1066 True iff the prebuilts are ready for pkg and all deps.
1067 """
1068 if pkg in cache:
1069 return cache[pkg]
1070 if pkg not in pkg_db and pkg not in self.forced_remote_binary_packages:
1071 cache[pkg] = False
1072 else:
1073 cache[pkg] = True
1074 for dep in deps_map[pkg]["needs"]:
1075 if not PrebuiltsReady(dep, pkg_db, cache):
1076 cache[pkg] = False
1077 break
1078 return cache[pkg]
1079
1080 def LastModifiedWithDeps(pkg, pkg_db, cache):
1081 """Calculate the last modified time of a package and its dependencies.
1082
1083 This function looks at all the packages needed by the specified package
1084 and checks the most recent modification time of all of those packages.
1085 If the dependencies of a package were modified more recently than the
1086 package itself, then we know the package needs to be rebuilt.
1087
1088 Args:
1089 pkg: The specified package.
1090 pkg_db: The package DB to use.
1091 cache: A dict, where the last modified times are stored.
1092
1093 Returns:
1094 The last modified time of the specified package and its dependencies.
1095 """
1096 if pkg in cache:
1097 return cache[pkg]
1098
1099 cache[pkg] = pkg_db.get(pkg, 0)
1100 for dep in deps_map[pkg]["needs"]:
1101 t = LastModifiedWithDeps(dep, pkg_db, cache)
1102 cache[pkg] = max(cache[pkg], t)
1103 return cache[pkg]
1104
1105 # For every package that's getting updated in our local cache (binary
1106 # or source), make sure we also update the children. If a package is
1107 # built from source, all children must also be built from source.
1108 local_ready_cache, remote_ready_cache = {}, {}
1109 local_mtime_cache, remote_mtime_cache = {}, {}
1110 for pkg in final_pkgs.difference(rebuild_blacklist):
1111 # If all the necessary local packages are ready, and their
1112 # modification times are in sync, we don't need to do anything here.
1113 local_mtime = LastModifiedWithDeps(pkg, local_pkgs, local_mtime_cache)
1114 local_ready = PrebuiltsReady(pkg, local_pkgs, local_ready_cache)
1115 if (not local_ready or local_pkgs.get(pkg, 0) < local_mtime and
1116 pkg not in cycles):
1117 # OK, at least one package is missing from the local cache or is
1118 # outdated. This means we're going to have to install the package
1119 # and all dependencies.
1120 #
1121 # If all the necessary remote packages are ready, and they're at
1122 # least as new as our local packages, we can install them.
1123 # Otherwise, we need to build from source.
1124 remote_mtime = LastModifiedWithDeps(pkg, remote_pkgs,
1125 remote_mtime_cache)
1126 remote_ready = PrebuiltsReady(pkg, remote_pkgs, remote_ready_cache)
1127 if remote_ready and (local_mtime <= remote_mtime or pkg in cycles):
1128 MergeChildren(pkg, "mandatory")
1129 else:
1130 MergeChildren(pkg, "mandatory_source")
1131
1132 def UsePrebuiltPackages(remote_pkgs):
1133 """Update packages that can use prebuilts to do so."""
1134 start = time.time()
1135
1136 # Build list of prebuilt packages.
1137 prebuilt_pkgs = {}
1138 for pkg, info in deps_map.iteritems():
1139 if info and info["action"] == "merge":
1140 if (not info["force_remote_binary"] and info["mandatory_source"] or
1141 "--usepkgonly" not in emerge.opts and pkg not in remote_pkgs):
1142 continue
1143
1144 db_pkg = emerge.depgraph._pkg(pkg, "binary", emerge.root_config)
1145 if info["force_remote_binary"]:
1146 # Undo our earlier hacks to the use flags so that the use flags
1147 # display correctly.
1148 db_pkg.use.enabled = db_pkg.metadata["USE"].split()
1149 prebuilt_pkgs[pkg] = db_pkg
1150
1151 # Calculate what packages need to be rebuilt due to changes in use flags.
1152 pkgsettings = emerge.depgraph._frozen_config.pkgsettings[root]
1153 for pkg, db_pkg in prebuilt_pkgs.iteritems():
1154 if not self.CheckUseFlags(pkgsettings, db_pkg, self.package_db[pkg]):
1155 MergeChildren(pkg, "mandatory_source")
1156
1157 # Convert eligible packages to binaries.
1158 for pkg, info in deps_map.iteritems():
1159 if info and info["action"] == "merge" and pkg in prebuilt_pkgs:
1160 if not info["mandatory_source"] or info["force_remote_binary"]:
1161 self.package_db[pkg] = prebuilt_pkgs[pkg]
1162
1163 seconds = time.time() - start
1164 if "--quiet" not in emerge.opts:
1165 print "Prebuilt DB populated in %dm%.1fs" % (seconds / 60, seconds % 60)
1166
1167 return prebuilt_pkgs
1168
1169 ReverseTree(deps_tree)
1170 BuildFinalPackageSet()
1171 AddSecretDeps()
1172
1173 # Mark that we want to use remote binaries only for a particular package.
1174 vardb = emerge.depgraph._frozen_config.trees[root]["vartree"].dbapi
1175 for pkg in self.force_remote_binary:
1176 for db_pkg in final_db.match_pkgs(pkg):
1177 match = deps_map.get(str(db_pkg.cpv))
1178 if match:
1179 match["force_remote_binary"] = True
1180
1181 rebuild_blacklist.add(str(db_pkg.cpv))
1182 if not vardb.match_pkgs(db_pkg.cpv):
1183 MergeChildren(str(db_pkg.cpv), "mandatory")
1184
1185 if self.no_workon_deps:
1186 for pkg in self.mandatory_source.copy():
1187 for db_pkg in final_db.match_pkgs(pkg):
1188 deps_map[str(db_pkg.cpv)]["mandatory_source"] = True
1189 else:
1190 for pkg in self.mandatory_source.copy():
1191 for db_pkg in final_db.match_pkgs(pkg):
1192 MergeChildren(str(db_pkg.cpv), "mandatory_source")
1193
1194 cycles = FindCycles()
1195 if self.rebuild:
1196 local_pkgs = LocalPackageDatabase()
1197 AutoRebuildDeps(local_pkgs, remote_pkgs, cycles)
1198
1199 # We need to remove installed packages so that we can use the dependency
1200 # ordering of the install process to show us what cycles to crack. Once
1201 # we've done that, we also need to recalculate our list of cycles so that
1202 # we don't include the installed packages in our cycles.
1203 RemoveInstalledPackages()
David Jamesfcb70ef2011-02-02 16:02:30 -08001204 SanitizeTree()
David Jamesa22906f2011-05-04 19:53:26 -07001205 if deps_map:
1206 if "--usepkg" in emerge.opts:
1207 UsePrebuiltPackages(remote_pkgs)
David Jamesfcb70ef2011-02-02 16:02:30 -08001208 return deps_map
1209
1210 def PrintInstallPlan(self, deps_map):
1211 """Print an emerge-style install plan.
1212
1213 The install plan lists what packages we're installing, in order.
1214 It's useful for understanding what parallel_emerge is doing.
1215
1216 Args:
1217 deps_map: The dependency graph.
1218 """
1219
1220 def InstallPlanAtNode(target, deps_map):
1221 nodes = []
1222 nodes.append(target)
1223 for dep in deps_map[target]["provides"]:
1224 del deps_map[dep]["needs"][target]
1225 if not deps_map[dep]["needs"]:
1226 nodes.extend(InstallPlanAtNode(dep, deps_map))
1227 return nodes
1228
1229 deps_map = copy.deepcopy(deps_map)
1230 install_plan = []
1231 plan = set()
1232 for target, info in deps_map.iteritems():
1233 if not info["needs"] and target not in plan:
1234 for item in InstallPlanAtNode(target, deps_map):
1235 plan.add(item)
1236 install_plan.append(self.package_db[item])
1237
1238 for pkg in plan:
1239 del deps_map[pkg]
1240
1241 if deps_map:
1242 print "Cyclic dependencies:", " ".join(deps_map)
1243 PrintDepsMap(deps_map)
1244 sys.exit(1)
1245
1246 self.emerge.depgraph.display(install_plan)
1247
1248
1249def PrintDepsMap(deps_map):
1250 """Print dependency graph, for each package list it's prerequisites."""
1251 for i in sorted(deps_map):
1252 print "%s: (%s) needs" % (i, deps_map[i]["action"])
1253 needs = deps_map[i]["needs"]
1254 for j in sorted(needs):
1255 print " %s" % (j)
1256 if not needs:
1257 print " no dependencies"
1258
1259
1260class EmergeJobState(object):
1261 __slots__ = ["done", "filename", "last_notify_timestamp", "last_output_seek",
1262 "last_output_timestamp", "pkgname", "retcode", "start_timestamp",
1263 "target"]
1264
1265 def __init__(self, target, pkgname, done, filename, start_timestamp,
1266 retcode=None):
1267
1268 # The full name of the target we're building (e.g.
1269 # chromeos-base/chromeos-0.0.1-r60)
1270 self.target = target
1271
1272 # The short name of the target we're building (e.g. chromeos-0.0.1-r60)
1273 self.pkgname = pkgname
1274
1275 # Whether the job is done. (True if the job is done; false otherwise.)
1276 self.done = done
1277
1278 # The filename where output is currently stored.
1279 self.filename = filename
1280
1281 # The timestamp of the last time we printed the name of the log file. We
1282 # print this at the beginning of the job, so this starts at
1283 # start_timestamp.
1284 self.last_notify_timestamp = start_timestamp
1285
1286 # The location (in bytes) of the end of the last complete line we printed.
1287 # This starts off at zero. We use this to jump to the right place when we
1288 # print output from the same ebuild multiple times.
1289 self.last_output_seek = 0
1290
1291 # The timestamp of the last time we printed output. Since we haven't
1292 # printed output yet, this starts at zero.
1293 self.last_output_timestamp = 0
1294
1295 # The return code of our job, if the job is actually finished.
1296 self.retcode = retcode
1297
1298 # The timestamp when our job started.
1299 self.start_timestamp = start_timestamp
1300
1301
1302def SetupWorkerSignals():
1303 def ExitHandler(signum, frame):
1304 # Remove our signal handlers so we don't get called recursively.
1305 signal.signal(signal.SIGINT, signal.SIG_DFL)
1306 signal.signal(signal.SIGTERM, signal.SIG_DFL)
1307
1308 # Try to exit cleanly
1309 sys.exit(1)
1310
1311 # Ensure that we exit quietly and cleanly, if possible, when we receive
1312 # SIGTERM or SIGINT signals. By default, when the user hits CTRL-C, all
1313 # of the child processes will print details about KeyboardInterrupt
1314 # exceptions, which isn't very helpful.
1315 signal.signal(signal.SIGINT, ExitHandler)
1316 signal.signal(signal.SIGTERM, ExitHandler)
1317
1318
1319def EmergeWorker(task_queue, job_queue, emerge, package_db):
1320 """This worker emerges any packages given to it on the task_queue.
1321
1322 Args:
1323 task_queue: The queue of tasks for this worker to do.
1324 job_queue: The queue of results from the worker.
1325 emerge: An EmergeData() object.
1326 package_db: A dict, mapping package ids to portage Package objects.
1327
1328 It expects package identifiers to be passed to it via task_queue. When
1329 a task is started, it pushes the (target, filename) to the started_queue.
1330 The output is stored in filename. When a merge starts or finishes, we push
1331 EmergeJobState objects to the job_queue.
1332 """
1333
1334 SetupWorkerSignals()
1335 settings, trees, mtimedb = emerge.settings, emerge.trees, emerge.mtimedb
1336 opts, spinner = emerge.opts, emerge.spinner
1337 opts["--nodeps"] = True
David Jamesa22906f2011-05-04 19:53:26 -07001338 if new_portage:
1339 # When Portage launches new processes, it goes on a rampage and closes all
1340 # open file descriptors. Ask Portage not to do that, as it breaks us.
1341 portage.process.get_open_fds = lambda: []
David Jamesfcb70ef2011-02-02 16:02:30 -08001342 while True:
1343 # Wait for a new item to show up on the queue. This is a blocking wait,
1344 # so if there's nothing to do, we just sit here.
1345 target = task_queue.get()
1346 if not target:
1347 # If target is None, this means that the main thread wants us to quit.
1348 # The other workers need to exit too, so we'll push the message back on
1349 # to the queue so they'll get it too.
1350 task_queue.put(target)
1351 return
1352 db_pkg = package_db[target]
1353 db_pkg.root_config = emerge.root_config
1354 install_list = [db_pkg]
1355 pkgname = db_pkg.pf
1356 output = tempfile.NamedTemporaryFile(prefix=pkgname + "-", delete=False)
1357 start_timestamp = time.time()
1358 job = EmergeJobState(target, pkgname, False, output.name, start_timestamp)
1359 job_queue.put(job)
1360 if "--pretend" in opts:
1361 retcode = 0
1362 else:
1363 save_stdout = sys.stdout
1364 save_stderr = sys.stderr
1365 try:
1366 sys.stdout = output
1367 sys.stderr = output
David Jamesa22906f2011-05-04 19:53:26 -07001368 if new_portage:
1369 emerge.scheduler_graph.mergelist = install_list
1370 scheduler = Scheduler(settings, trees, mtimedb, opts, spinner,
1371 favorites=[], graph_config=emerge.scheduler_graph)
1372 else:
1373 scheduler = Scheduler(settings, trees, mtimedb, opts, spinner,
1374 install_list, [], emerge.scheduler_graph)
David Jamesfcb70ef2011-02-02 16:02:30 -08001375 retcode = scheduler.merge()
1376 except Exception:
1377 traceback.print_exc(file=output)
1378 retcode = 1
1379 finally:
1380 sys.stdout = save_stdout
1381 sys.stderr = save_stderr
1382 output.close()
1383 if retcode is None:
1384 retcode = 0
1385
1386 job = EmergeJobState(target, pkgname, True, output.name, start_timestamp,
1387 retcode)
1388 job_queue.put(job)
1389
1390
1391class LinePrinter(object):
1392 """Helper object to print a single line."""
1393
1394 def __init__(self, line):
1395 self.line = line
1396
1397 def Print(self, seek_locations):
1398 print self.line
1399
1400
1401class JobPrinter(object):
1402 """Helper object to print output of a job."""
1403
1404 def __init__(self, job, unlink=False):
1405 """Print output of job.
1406
1407 If unlink is True, unlink the job output file when done."""
1408 self.current_time = time.time()
1409 self.job = job
1410 self.unlink = unlink
1411
1412 def Print(self, seek_locations):
1413
1414 job = self.job
1415
1416 # Calculate how long the job has been running.
1417 seconds = self.current_time - job.start_timestamp
1418
1419 # Note that we've printed out the job so far.
1420 job.last_output_timestamp = self.current_time
1421
1422 # Note that we're starting the job
1423 info = "job %s (%dm%.1fs)" % (job.pkgname, seconds / 60, seconds % 60)
1424 last_output_seek = seek_locations.get(job.filename, 0)
1425 if last_output_seek:
1426 print "=== Continue output for %s ===" % info
1427 else:
1428 print "=== Start output for %s ===" % info
1429
1430 # Print actual output from job
1431 f = codecs.open(job.filename, encoding='utf-8', errors='replace')
1432 f.seek(last_output_seek)
1433 prefix = job.pkgname + ":"
1434 for line in f:
1435
1436 # Save off our position in the file
1437 if line and line[-1] == "\n":
1438 last_output_seek = f.tell()
1439 line = line[:-1]
1440
1441 # Print our line
1442 print prefix, line.encode('utf-8', 'replace')
1443 f.close()
1444
1445 # Save our last spot in the file so that we don't print out the same
1446 # location twice.
1447 seek_locations[job.filename] = last_output_seek
1448
1449 # Note end of output section
1450 if job.done:
1451 print "=== Complete: %s ===" % info
1452 else:
1453 print "=== Still running: %s ===" % info
1454
1455 if self.unlink:
1456 os.unlink(job.filename)
1457
1458
1459def PrintWorker(queue):
1460 """A worker that prints stuff to the screen as requested."""
1461
1462 def ExitHandler(signum, frame):
1463 # Switch to default signal handlers so that we'll die after two signals.
1464 signal.signal(signal.SIGINT, signal.SIG_DFL)
1465 signal.signal(signal.SIGTERM, signal.SIG_DFL)
1466
1467 # Don't exit on the first SIGINT / SIGTERM, because the parent worker will
1468 # handle it and tell us when we need to exit.
1469 signal.signal(signal.SIGINT, ExitHandler)
1470 signal.signal(signal.SIGTERM, ExitHandler)
1471
1472 # seek_locations is a map indicating the position we are at in each file.
1473 # It starts off empty, but is set by the various Print jobs as we go along
1474 # to indicate where we left off in each file.
1475 seek_locations = {}
1476 while True:
1477 try:
1478 job = queue.get()
1479 if job:
1480 job.Print(seek_locations)
1481 else:
1482 break
1483 except IOError as ex:
1484 if ex.errno == errno.EINTR:
1485 # Looks like we received a signal. Keep printing.
1486 continue
1487 raise
1488
1489
1490class EmergeQueue(object):
1491 """Class to schedule emerge jobs according to a dependency graph."""
1492
1493 def __init__(self, deps_map, emerge, package_db, show_output):
1494 # Store the dependency graph.
1495 self._deps_map = deps_map
1496 # Initialize the running queue to empty
1497 self._jobs = {}
1498 # List of total package installs represented in deps_map.
1499 install_jobs = [x for x in deps_map if deps_map[x]["action"] == "merge"]
1500 self._total_jobs = len(install_jobs)
1501 self._show_output = show_output
1502
1503 if "--pretend" in emerge.opts:
1504 print "Skipping merge because of --pretend mode."
1505 sys.exit(0)
1506
1507 # Setup scheduler graph object. This is used by the child processes
1508 # to help schedule jobs.
1509 emerge.scheduler_graph = emerge.depgraph.schedulerGraph()
1510
1511 # Calculate how many jobs we can run in parallel. We don't want to pass
1512 # the --jobs flag over to emerge itself, because that'll tell emerge to
1513 # hide its output, and said output is quite useful for debugging hung
1514 # jobs.
1515 procs = min(self._total_jobs,
1516 emerge.opts.pop("--jobs", multiprocessing.cpu_count()))
1517 self._emerge_queue = multiprocessing.Queue()
1518 self._job_queue = multiprocessing.Queue()
1519 self._print_queue = multiprocessing.Queue()
1520 args = (self._emerge_queue, self._job_queue, emerge, package_db)
1521 self._pool = multiprocessing.Pool(procs, EmergeWorker, args)
1522 self._print_worker = multiprocessing.Process(target=PrintWorker,
1523 args=[self._print_queue])
1524 self._print_worker.start()
1525
1526 # Initialize the failed queue to empty.
1527 self._retry_queue = []
1528 self._failed = set()
1529
1530 # Print an update before we launch the merges.
1531 self._Status()
1532
1533 # Setup an exit handler so that we print nice messages if we are
1534 # terminated.
1535 self._SetupExitHandler()
1536
1537 # Schedule our jobs.
1538 for target, info in deps_map.items():
1539 if not info["needs"]:
1540 self._Schedule(target)
1541
1542 def _SetupExitHandler(self):
1543
1544 def ExitHandler(signum, frame):
1545
1546 # Kill our signal handlers so we don't get called recursively
1547 signal.signal(signal.SIGINT, signal.SIG_DFL)
1548 signal.signal(signal.SIGTERM, signal.SIG_DFL)
1549
1550 # Print our current job status
1551 for target, job in self._jobs.iteritems():
1552 if job:
1553 self._print_queue.put(JobPrinter(job, unlink=True))
1554
1555 # Notify the user that we are exiting
1556 self._Print("Exiting on signal %s" % signum)
1557
1558 # Kill child threads, then exit.
1559 self._Exit()
1560 sys.exit(1)
1561
1562 # Print out job status when we are killed
1563 signal.signal(signal.SIGINT, ExitHandler)
1564 signal.signal(signal.SIGTERM, ExitHandler)
1565
1566 def _Schedule(self, target):
1567 # We maintain a tree of all deps, if this doesn't need
1568 # to be installed just free up it's children and continue.
1569 # It is possible to reinstall deps of deps, without reinstalling
1570 # first level deps, like so:
1571 # chromeos (merge) -> eselect (nomerge) -> python (merge)
David Jamesa22906f2011-05-04 19:53:26 -07001572 if self._deps_map[target]["action"] == "nomerge":
David Jamesfcb70ef2011-02-02 16:02:30 -08001573 self._Finish(target)
David Jamesd20a6d92011-04-26 16:11:59 -07001574 elif target not in self._jobs:
David Jamesfcb70ef2011-02-02 16:02:30 -08001575 # Kick off the build if it's marked to be built.
1576 self._jobs[target] = None
1577 self._emerge_queue.put(target)
1578
1579 def _LoadAvg(self):
1580 loads = open("/proc/loadavg", "r").readline().split()[:3]
1581 return " ".join(loads)
1582
1583 def _Print(self, line):
1584 """Print a single line."""
1585 self._print_queue.put(LinePrinter(line))
1586
1587 def _Status(self):
1588 """Print status."""
1589 current_time = time.time()
1590 no_output = True
1591
1592 # Print interim output every minute if --show-output is used. Otherwise,
1593 # print notifications about running packages every 2 minutes, and print
1594 # full output for jobs that have been running for 60 minutes or more.
1595 if self._show_output:
1596 interval = 60
1597 notify_interval = 0
1598 else:
1599 interval = 60 * 60
1600 notify_interval = 60 * 2
1601 for target, job in self._jobs.iteritems():
1602 if job:
1603 last_timestamp = max(job.start_timestamp, job.last_output_timestamp)
1604 if last_timestamp + interval < current_time:
1605 self._print_queue.put(JobPrinter(job))
1606 job.last_output_timestamp = current_time
1607 no_output = False
1608 elif (notify_interval and
1609 job.last_notify_timestamp + notify_interval < current_time):
1610 job_seconds = current_time - job.start_timestamp
1611 args = (job.pkgname, job_seconds / 60, job_seconds % 60, job.filename)
1612 info = "Still building %s (%dm%.1fs). Logs in %s" % args
1613 job.last_notify_timestamp = current_time
1614 self._Print(info)
1615 no_output = False
1616
1617 # If we haven't printed any messages yet, print a general status message
1618 # here.
1619 if no_output:
1620 seconds = current_time - GLOBAL_START
1621 line = ("Pending %s, Ready %s, Running %s, Retrying %s, Total %s "
1622 "[Time %dm%.1fs Load %s]")
1623 qsize = self._emerge_queue.qsize()
1624 self._Print(line % (len(self._deps_map), qsize, len(self._jobs) - qsize,
1625 len(self._retry_queue), self._total_jobs,
1626 seconds / 60, seconds % 60, self._LoadAvg()))
1627
1628 def _Finish(self, target):
1629 """Mark a target as completed and unblock dependecies."""
1630 for dep in self._deps_map[target]["provides"]:
1631 del self._deps_map[dep]["needs"][target]
1632 if not self._deps_map[dep]["needs"]:
1633 self._Schedule(dep)
1634 self._deps_map.pop(target)
1635
1636 def _Retry(self):
1637 if self._retry_queue:
1638 target = self._retry_queue.pop(0)
1639 self._Schedule(target)
1640 self._Print("Retrying emerge of %s." % target)
1641
1642 def _Exit(self):
1643 # Tell emerge workers to exit. They all exit when 'None' is pushed
1644 # to the queue.
1645 self._emerge_queue.put(None)
1646 self._pool.close()
1647 self._pool.join()
1648
1649 # Now that our workers are finished, we can kill the print queue.
1650 self._print_queue.put(None)
1651 self._print_worker.join()
1652
1653 def Run(self):
1654 """Run through the scheduled ebuilds.
1655
1656 Keep running so long as we have uninstalled packages in the
1657 dependency graph to merge.
1658 """
1659 while self._deps_map:
1660 # Check here that we are actually waiting for something.
1661 if (self._emerge_queue.empty() and
1662 self._job_queue.empty() and
1663 not self._jobs and
1664 self._deps_map):
1665 # If we have failed on a package, retry it now.
1666 if self._retry_queue:
1667 self._Retry()
1668 else:
1669 # Tell child threads to exit.
1670 self._Exit()
1671
1672 # The dependency map is helpful for debugging failures.
1673 PrintDepsMap(self._deps_map)
1674
1675 # Tell the user why we're exiting.
1676 if self._failed:
1677 print "Packages failed: %s" % " ,".join(self._failed)
1678 else:
1679 print "Deadlock! Circular dependencies!"
1680 sys.exit(1)
1681
1682 try:
1683 job = self._job_queue.get(timeout=5)
1684 except Queue.Empty:
1685 # Print an update.
1686 self._Status()
1687 continue
1688
1689 target = job.target
1690
1691 if not job.done:
1692 self._jobs[target] = job
1693 self._Print("Started %s (logged in %s)" % (target, job.filename))
1694 continue
1695
1696 # Print output of job
1697 if self._show_output or job.retcode != 0:
1698 self._print_queue.put(JobPrinter(job, unlink=True))
1699 else:
1700 os.unlink(job.filename)
1701 del self._jobs[target]
1702
1703 seconds = time.time() - job.start_timestamp
1704 details = "%s (in %dm%.1fs)" % (target, seconds / 60, seconds % 60)
1705
1706 # Complain if necessary.
1707 if job.retcode != 0:
1708 # Handle job failure.
1709 if target in self._failed:
1710 # If this job has failed previously, give up.
1711 self._Print("Failed %s. Your build has failed." % details)
1712 else:
1713 # Queue up this build to try again after a long while.
1714 self._retry_queue.append(target)
1715 self._failed.add(target)
1716 self._Print("Failed %s, retrying later." % details)
1717 else:
1718 if target in self._failed and self._retry_queue:
1719 # If we have successfully retried a failed package, and there
1720 # are more failed packages, try the next one. We will only have
1721 # one retrying package actively running at a time.
1722 self._Retry()
1723
1724 self._Print("Completed %s" % details)
1725 # Mark as completed and unblock waiting ebuilds.
1726 self._Finish(target)
1727
1728 # Print an update.
1729 self._Status()
1730
1731 # Tell child threads to exit.
1732 self._Print("Merge complete")
1733 self._Exit()
1734
1735
1736def main():
1737
1738 deps = DepGraphGenerator()
1739 deps.Initialize(sys.argv[1:])
1740 emerge = deps.emerge
1741
1742 if emerge.action is not None:
1743 sys.argv = deps.ParseParallelEmergeArgs(sys.argv)
1744 sys.exit(emerge_main())
1745 elif not emerge.cmdline_packages:
1746 Usage()
1747 sys.exit(1)
1748
1749 # Unless we're in pretend mode, there's not much point running without
1750 # root access. We need to be able to install packages.
1751 #
1752 # NOTE: Even if you're running --pretend, it's a good idea to run
1753 # parallel_emerge with root access so that portage can write to the
1754 # dependency cache. This is important for performance.
1755 if "--pretend" not in emerge.opts and portage.secpass < 2:
1756 print "parallel_emerge: superuser access is required."
1757 sys.exit(1)
1758
1759 if "--quiet" not in emerge.opts:
1760 cmdline_packages = " ".join(emerge.cmdline_packages)
David Jamesa22906f2011-05-04 19:53:26 -07001761 nomerge_packages = " ".join(deps.nomerge)
David Jamesfcb70ef2011-02-02 16:02:30 -08001762 print "Starting fast-emerge."
1763 print " Building package %s on %s" % (cmdline_packages,
1764 deps.board or "root")
David Jamesa22906f2011-05-04 19:53:26 -07001765 if nomerge_packages:
1766 print " Skipping package %s on %s" % (nomerge_packages,
1767 deps.board or "root")
David Jamesfcb70ef2011-02-02 16:02:30 -08001768
David Jamesa22906f2011-05-04 19:53:26 -07001769 remote_pkgs = {}
1770 if "--getbinpkg" in emerge.opts:
1771 remote_pkgs = deps.RemotePackageDatabase()
1772
1773 deps_tree, deps_info = deps.GenDependencyTree(remote_pkgs)
David Jamesfcb70ef2011-02-02 16:02:30 -08001774
1775 # You want me to be verbose? I'll give you two trees! Twice as much value.
1776 if "--tree" in emerge.opts and "--verbose" in emerge.opts:
1777 deps.PrintTree(deps_tree)
1778
David Jamesa22906f2011-05-04 19:53:26 -07001779 deps_graph = deps.GenDependencyGraph(deps_tree, deps_info, remote_pkgs)
David Jamesfcb70ef2011-02-02 16:02:30 -08001780
1781 # OK, time to print out our progress so far.
1782 deps.PrintInstallPlan(deps_graph)
1783 if "--tree" in emerge.opts:
1784 PrintDepsMap(deps_graph)
1785
1786 # Are we upgrading portage? If so, and there are more packages to merge,
1787 # schedule a restart of parallel_emerge to merge the rest. This ensures that
1788 # we pick up all updates to portage settings before merging any more
1789 # packages.
1790 portage_upgrade = False
1791 root = emerge.settings["ROOT"]
1792 final_db = emerge.depgraph._dynamic_config.mydbapi[root]
1793 if root == "/":
1794 for db_pkg in final_db.match_pkgs("sys-apps/portage"):
1795 portage_pkg = deps_graph.get(db_pkg.cpv)
1796 if portage_pkg and len(deps_graph) > 1:
1797 portage_pkg["needs"].clear()
1798 portage_pkg["provides"].clear()
1799 deps_graph = { str(db_pkg.cpv): portage_pkg }
1800 portage_upgrade = True
1801 if "--quiet" not in emerge.opts:
1802 print "Upgrading portage first, then restarting..."
1803
1804 # Run the queued emerges.
1805 scheduler = EmergeQueue(deps_graph, emerge, deps.package_db, deps.show_output)
1806 scheduler.Run()
1807
1808 # Update world.
1809 if ("--oneshot" not in emerge.opts and
1810 "--pretend" not in emerge.opts):
1811 world_set = emerge.root_config.sets["selected"]
1812 new_world_pkgs = []
1813 for pkg in emerge.cmdline_packages:
1814 for db_pkg in final_db.match_pkgs(pkg):
1815 print "Adding %s to world" % db_pkg.cp
1816 new_world_pkgs.append(db_pkg.cp)
1817 if new_world_pkgs:
1818 world_set.update(new_world_pkgs)
1819
1820 # Update environment (library cache, symlinks, etc.)
1821 if deps.board and "--pretend" not in emerge.opts:
1822 portage.env_update()
1823
1824 # If we already upgraded portage, we don't need to do so again. But we do
1825 # need to upgrade the rest of the packages. So we'll go ahead and do that.
1826 if portage_upgrade:
David Jamesa22906f2011-05-04 19:53:26 -07001827 args = sys.argv[1:] + ["--nomerge=sys-apps/portage"]
David Jamesfcb70ef2011-02-02 16:02:30 -08001828 os.execvp(os.path.realpath(sys.argv[0]), args)
1829
1830 print "Done"
1831 sys.exit(0)
1832
1833if __name__ == "__main__":
1834 main()