blob: d4e7d5aedbd7c9aecf0c02f9e8d0fadf55f5d666 [file] [log] [blame]
Mike Frysingere58c0e22017-10-04 15:43:30 -04001# -*- coding: utf-8 -*-
Mike Frysinger0a647fc2012-08-06 14:36:05 -04002# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
David Jamesfcb70ef2011-02-02 16:02:30 -08003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Program to run emerge in parallel, for significant speedup.
7
8Usage:
David James386ccd12011-05-04 20:17:42 -07009 ./parallel_emerge [--board=BOARD] [--workon=PKGS]
David Jamesfcb70ef2011-02-02 16:02:30 -080010 [--force-remote-binary=PKGS] [emerge args] package
11
David James78b6cd92012-04-02 21:36:12 -070012This script runs multiple emerge processes in parallel, using appropriate
13Portage APIs. It is faster than standard emerge because it has a
14multiprocess model instead of an asynchronous model.
David Jamesfcb70ef2011-02-02 16:02:30 -080015"""
16
Mike Frysinger383367e2014-09-16 15:06:17 -040017from __future__ import print_function
18
David Jamesfcb70ef2011-02-02 16:02:30 -080019import codecs
20import copy
21import errno
Brian Harring8294d652012-05-23 02:20:52 -070022import gc
David James8c7e5e32011-06-28 11:26:03 -070023import heapq
David Jamesfcb70ef2011-02-02 16:02:30 -080024import multiprocessing
25import os
Mike Frysinger1ae28092013-10-17 17:17:22 -040026try:
27 import Queue
28except ImportError:
29 # Python-3 renamed to "queue". We still use Queue to avoid collisions
30 # with naming variables as "queue". Maybe we'll transition at some point.
31 # pylint: disable=F0401
32 import queue as Queue
David Jamesfcb70ef2011-02-02 16:02:30 -080033import signal
Bertrand SIMONNET19d789e2014-12-09 13:36:31 -080034import subprocess
David Jamesfcb70ef2011-02-02 16:02:30 -080035import sys
36import tempfile
Brian Harring8294d652012-05-23 02:20:52 -070037import threading
David Jamesfcb70ef2011-02-02 16:02:30 -080038import time
39import traceback
David Jamesfcb70ef2011-02-02 16:02:30 -080040
Thiago Goncalesf4acc422013-07-17 10:26:35 -070041from chromite.lib import cros_build_lib
Chris Ching5fcbd622016-11-28 09:22:15 -070042from chromite.lib import cros_event
Chris Chingb8eba812017-06-22 09:54:48 -060043from chromite.lib import portage_util
Mike Frysingere2d8f0d2014-11-01 13:09:26 -040044from chromite.lib import process_util
Mike Frysingerd74fe4a2014-04-24 11:43:38 -040045from chromite.lib import proctitle
Thiago Goncalesf4acc422013-07-17 10:26:35 -070046
David Jamesfcb70ef2011-02-02 16:02:30 -080047# If PORTAGE_USERNAME isn't specified, scrape it from the $HOME variable. On
48# Chromium OS, the default "portage" user doesn't have the necessary
49# permissions. It'd be easier if we could default to $USERNAME, but $USERNAME
50# is "root" here because we get called through sudo.
51#
52# We need to set this before importing any portage modules, because portage
53# looks up "PORTAGE_USERNAME" at import time.
54#
55# NOTE: .bashrc sets PORTAGE_USERNAME = $USERNAME, so most people won't
56# encounter this case unless they have an old chroot or blow away the
57# environment by running sudo without the -E specifier.
58if "PORTAGE_USERNAME" not in os.environ:
59 homedir = os.environ.get("HOME")
60 if homedir:
61 os.environ["PORTAGE_USERNAME"] = os.path.basename(homedir)
62
Bertrand SIMONNET19d789e2014-12-09 13:36:31 -080063# Wrap Popen with a lock to ensure no two Popen are executed simultaneously in
64# the same process.
65# Two Popen call at the same time might be the cause for crbug.com/433482.
66_popen_lock = threading.Lock()
67_old_popen = subprocess.Popen
68
69def _LockedPopen(*args, **kwargs):
70 with _popen_lock:
71 return _old_popen(*args, **kwargs)
72
73subprocess.Popen = _LockedPopen
74
David Jamesfcb70ef2011-02-02 16:02:30 -080075# Portage doesn't expose dependency trees in its public API, so we have to
76# make use of some private APIs here. These modules are found under
77# /usr/lib/portage/pym/.
78#
79# TODO(davidjames): Update Portage to expose public APIs for these features.
Don Garrett25f309a2014-03-19 14:02:12 -070080# pylint: disable=F0401
David Jamesfcb70ef2011-02-02 16:02:30 -080081from _emerge.actions import adjust_configs
82from _emerge.actions import load_emerge_config
83from _emerge.create_depgraph_params import create_depgraph_params
David James386ccd12011-05-04 20:17:42 -070084from _emerge.depgraph import backtrack_depgraph
David Jamesfcb70ef2011-02-02 16:02:30 -080085from _emerge.main import emerge_main
86from _emerge.main import parse_opts
87from _emerge.Package import Package
Bertrand SIMONNETa15b5072014-10-23 15:27:52 -070088from _emerge.post_emerge import clean_logs
David Jamesfcb70ef2011-02-02 16:02:30 -080089from _emerge.Scheduler import Scheduler
David Jamesfcb70ef2011-02-02 16:02:30 -080090from _emerge.stdout_spinner import stdout_spinner
David James386ccd12011-05-04 20:17:42 -070091from portage._global_updates import _global_updates
David Jamesfcb70ef2011-02-02 16:02:30 -080092import portage
93import portage.debug
Don Garrettf8bf7842014-03-20 17:03:42 -070094# pylint: enable=F0401
Mike Frysinger91d7da92013-02-19 15:53:46 -050095
David Jamesfcb70ef2011-02-02 16:02:30 -080096
David Jamesfcb70ef2011-02-02 16:02:30 -080097def Usage():
98 """Print usage."""
Mike Frysinger383367e2014-09-16 15:06:17 -040099 print("Usage:")
Chris Ching5fcbd622016-11-28 09:22:15 -0700100 print(" ./parallel_emerge [--board=BOARD] [--workon=PKGS] [--rebuild]")
101 print(" [--eventlogfile=FILE] [emerge args] package")
Mike Frysinger383367e2014-09-16 15:06:17 -0400102 print()
103 print("Packages specified as workon packages are always built from source.")
104 print()
105 print("The --workon argument is mainly useful when you want to build and")
106 print("install packages that you are working on unconditionally, but do not")
107 print("to have to rev the package to indicate you want to build it from")
108 print("source. The build_packages script will automatically supply the")
109 print("workon argument to emerge, ensuring that packages selected using")
110 print("cros-workon are rebuilt.")
111 print()
112 print("The --rebuild option rebuilds packages whenever their dependencies")
113 print("are changed. This ensures that your build is correct.")
Chris Ching5fcbd622016-11-28 09:22:15 -0700114 print()
115 print("The --eventlogfile writes events to the given file. File is")
116 print("is overwritten if it exists.")
David Jamesfcb70ef2011-02-02 16:02:30 -0800117
118
David Jamesfcb70ef2011-02-02 16:02:30 -0800119# Global start time
120GLOBAL_START = time.time()
121
David James7358d032011-05-19 10:40:03 -0700122# Whether process has been killed by a signal.
123KILLED = multiprocessing.Event()
124
David Jamesfcb70ef2011-02-02 16:02:30 -0800125
126class EmergeData(object):
127 """This simple struct holds various emerge variables.
128
129 This struct helps us easily pass emerge variables around as a unit.
130 These variables are used for calculating dependencies and installing
131 packages.
132 """
133
David Jamesbf1e3442011-05-28 07:44:20 -0700134 __slots__ = ["action", "cmdline_packages", "depgraph", "favorites",
135 "mtimedb", "opts", "root_config", "scheduler_graph",
136 "settings", "spinner", "trees"]
David Jamesfcb70ef2011-02-02 16:02:30 -0800137
138 def __init__(self):
139 # The action the user requested. If the user is installing packages, this
140 # is None. If the user is doing anything other than installing packages,
141 # this will contain the action name, which will map exactly to the
142 # long-form name of the associated emerge option.
143 #
144 # Example: If you call parallel_emerge --unmerge package, the action name
145 # will be "unmerge"
146 self.action = None
147
148 # The list of packages the user passed on the command-line.
149 self.cmdline_packages = None
150
151 # The emerge dependency graph. It'll contain all the packages involved in
152 # this merge, along with their versions.
153 self.depgraph = None
154
David Jamesbf1e3442011-05-28 07:44:20 -0700155 # The list of candidates to add to the world file.
156 self.favorites = None
157
David Jamesfcb70ef2011-02-02 16:02:30 -0800158 # A dict of the options passed to emerge. This dict has been cleaned up
159 # a bit by parse_opts, so that it's a bit easier for the emerge code to
160 # look at the options.
161 #
162 # Emerge takes a few shortcuts in its cleanup process to make parsing of
163 # the options dict easier. For example, if you pass in "--usepkg=n", the
164 # "--usepkg" flag is just left out of the dictionary altogether. Because
165 # --usepkg=n is the default, this makes parsing easier, because emerge
166 # can just assume that if "--usepkg" is in the dictionary, it's enabled.
167 #
168 # These cleanup processes aren't applied to all options. For example, the
169 # --with-bdeps flag is passed in as-is. For a full list of the cleanups
170 # applied by emerge, see the parse_opts function in the _emerge.main
171 # package.
172 self.opts = None
173
174 # A dictionary used by portage to maintain global state. This state is
175 # loaded from disk when portage starts up, and saved to disk whenever we
176 # call mtimedb.commit().
177 #
178 # This database contains information about global updates (i.e., what
179 # version of portage we have) and what we're currently doing. Portage
180 # saves what it is currently doing in this database so that it can be
181 # resumed when you call it with the --resume option.
182 #
183 # parallel_emerge does not save what it is currently doing in the mtimedb,
184 # so we do not support the --resume option.
185 self.mtimedb = None
186
187 # The portage configuration for our current root. This contains the portage
188 # settings (see below) and the three portage trees for our current root.
189 # (The three portage trees are explained below, in the documentation for
190 # the "trees" member.)
191 self.root_config = None
192
193 # The scheduler graph is used by emerge to calculate what packages to
194 # install. We don't actually install any deps, so this isn't really used,
195 # but we pass it in to the Scheduler object anyway.
196 self.scheduler_graph = None
197
198 # Portage settings for our current session. Most of these settings are set
199 # in make.conf inside our current install root.
200 self.settings = None
201
202 # The spinner, which spews stuff to stdout to indicate that portage is
203 # doing something. We maintain our own spinner, so we set the portage
204 # spinner to "silent" mode.
205 self.spinner = None
206
207 # The portage trees. There are separate portage trees for each root. To get
208 # the portage tree for the current root, you can look in self.trees[root],
209 # where root = self.settings["ROOT"].
210 #
211 # In each root, there are three trees: vartree, porttree, and bintree.
212 # - vartree: A database of the currently-installed packages.
213 # - porttree: A database of ebuilds, that can be used to build packages.
214 # - bintree: A database of binary packages.
215 self.trees = None
216
217
218class DepGraphGenerator(object):
219 """Grab dependency information about packages from portage.
220
221 Typical usage:
222 deps = DepGraphGenerator()
223 deps.Initialize(sys.argv[1:])
224 deps_tree, deps_info = deps.GenDependencyTree()
225 deps_graph = deps.GenDependencyGraph(deps_tree, deps_info)
226 deps.PrintTree(deps_tree)
227 PrintDepsMap(deps_graph)
228 """
229
Bertrand SIMONNET0625e1a2015-04-07 11:41:16 -0700230 __slots__ = ["board", "emerge", "package_db", "show_output", "sysroot",
Bertrand SIMONNET411945d2015-05-20 17:23:28 -0700231 "unpack_only", "max_retries"]
David Jamesfcb70ef2011-02-02 16:02:30 -0800232
233 def __init__(self):
234 self.board = None
235 self.emerge = EmergeData()
David Jamesfcb70ef2011-02-02 16:02:30 -0800236 self.package_db = {}
David Jamesfcb70ef2011-02-02 16:02:30 -0800237 self.show_output = False
Bertrand SIMONNET0625e1a2015-04-07 11:41:16 -0700238 self.sysroot = None
Thiago Goncalesf4acc422013-07-17 10:26:35 -0700239 self.unpack_only = False
Bertrand SIMONNET411945d2015-05-20 17:23:28 -0700240 self.max_retries = 1
David Jamesfcb70ef2011-02-02 16:02:30 -0800241
242 def ParseParallelEmergeArgs(self, argv):
243 """Read the parallel emerge arguments from the command-line.
244
245 We need to be compatible with emerge arg format. We scrape arguments that
246 are specific to parallel_emerge, and pass through the rest directly to
247 emerge.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500248
David Jamesfcb70ef2011-02-02 16:02:30 -0800249 Args:
250 argv: arguments list
Mike Frysinger1a736a82013-12-12 01:50:59 -0500251
David Jamesfcb70ef2011-02-02 16:02:30 -0800252 Returns:
253 Arguments that don't belong to parallel_emerge
254 """
255 emerge_args = []
256 for arg in argv:
257 # Specifically match arguments that are specific to parallel_emerge, and
258 # pass through the rest.
259 if arg.startswith("--board="):
260 self.board = arg.replace("--board=", "")
Bertrand SIMONNET0625e1a2015-04-07 11:41:16 -0700261 elif arg.startswith("--sysroot="):
262 self.sysroot = arg.replace("--sysroot=", "")
David Jamesfcb70ef2011-02-02 16:02:30 -0800263 elif arg.startswith("--workon="):
264 workon_str = arg.replace("--workon=", "")
David James7a1ea4b2011-10-13 15:06:41 -0700265 emerge_args.append("--reinstall-atoms=%s" % workon_str)
266 emerge_args.append("--usepkg-exclude=%s" % workon_str)
David Jamesfcb70ef2011-02-02 16:02:30 -0800267 elif arg.startswith("--force-remote-binary="):
268 force_remote_binary = arg.replace("--force-remote-binary=", "")
David James7a1ea4b2011-10-13 15:06:41 -0700269 emerge_args.append("--useoldpkg-atoms=%s" % force_remote_binary)
Bertrand SIMONNET411945d2015-05-20 17:23:28 -0700270 elif arg.startswith("--retries="):
271 self.max_retries = int(arg.replace("--retries=", ""))
David Jamesfcb70ef2011-02-02 16:02:30 -0800272 elif arg == "--show-output":
273 self.show_output = True
David James386ccd12011-05-04 20:17:42 -0700274 elif arg == "--rebuild":
David James7a1ea4b2011-10-13 15:06:41 -0700275 emerge_args.append("--rebuild-if-unbuilt")
Thiago Goncalesf4acc422013-07-17 10:26:35 -0700276 elif arg == "--unpackonly":
277 emerge_args.append("--fetchonly")
278 self.unpack_only = True
Chris Ching5fcbd622016-11-28 09:22:15 -0700279 elif arg.startswith("--eventlogfile="):
280 log_file_name = arg.replace("--eventlogfile=", "")
Chris Ching4a2ebd62017-04-26 16:30:05 -0600281 event_logger = cros_event.getEventFileLogger(log_file_name)
282 event_logger.setKind('ParallelEmerge')
283 cros_event.setEventLogger(event_logger)
David Jamesfcb70ef2011-02-02 16:02:30 -0800284 else:
285 # Not one of our options, so pass through to emerge.
286 emerge_args.append(arg)
287
David James386ccd12011-05-04 20:17:42 -0700288 # These packages take a really long time to build, so, for expediency, we
289 # are blacklisting them from automatic rebuilds because one of their
290 # dependencies needs to be recompiled.
Mike Frysinger5c2a9052014-04-15 15:52:04 -0400291 for pkg in ("chromeos-base/chromeos-chrome",):
David James7a1ea4b2011-10-13 15:06:41 -0700292 emerge_args.append("--rebuild-exclude=%s" % pkg)
David Jamesfcb70ef2011-02-02 16:02:30 -0800293
294 return emerge_args
295
296 def Initialize(self, args):
297 """Initializer. Parses arguments and sets up portage state."""
298
299 # Parse and strip out args that are just intended for parallel_emerge.
300 emerge_args = self.ParseParallelEmergeArgs(args)
301
Bertrand SIMONNET0625e1a2015-04-07 11:41:16 -0700302 if self.sysroot and self.board:
303 cros_build_lib.Die("--sysroot and --board are incompatible.")
304
David Jamesfcb70ef2011-02-02 16:02:30 -0800305 # Setup various environment variables based on our current board. These
306 # variables are normally setup inside emerge-${BOARD}, but since we don't
307 # call that script, we have to set it up here. These variables serve to
308 # point our tools at /build/BOARD and to setup cross compiles to the
309 # appropriate board as configured in toolchain.conf.
310 if self.board:
Bertrand SIMONNET0625e1a2015-04-07 11:41:16 -0700311 self.sysroot = os.environ.get('SYSROOT',
312 cros_build_lib.GetSysroot(self.board))
313
314 if self.sysroot:
315 os.environ["PORTAGE_CONFIGROOT"] = self.sysroot
316 os.environ["SYSROOT"] = self.sysroot
David Jamesfcb70ef2011-02-02 16:02:30 -0800317
David Jamesfcb70ef2011-02-02 16:02:30 -0800318 # Turn off interactive delays
319 os.environ["EBEEP_IGNORE"] = "1"
320 os.environ["EPAUSE_IGNORE"] = "1"
Mike Frysinger0a647fc2012-08-06 14:36:05 -0400321 os.environ["CLEAN_DELAY"] = "0"
David Jamesfcb70ef2011-02-02 16:02:30 -0800322
323 # Parse the emerge options.
David Jamesea3ca332011-05-26 11:48:29 -0700324 action, opts, cmdline_packages = parse_opts(emerge_args, silent=True)
David Jamesfcb70ef2011-02-02 16:02:30 -0800325
326 # Set environment variables based on options. Portage normally sets these
327 # environment variables in emerge_main, but we can't use that function,
328 # because it also does a bunch of other stuff that we don't want.
329 # TODO(davidjames): Patch portage to move this logic into a function we can
330 # reuse here.
331 if "--debug" in opts:
332 os.environ["PORTAGE_DEBUG"] = "1"
333 if "--config-root" in opts:
334 os.environ["PORTAGE_CONFIGROOT"] = opts["--config-root"]
335 if "--root" in opts:
336 os.environ["ROOT"] = opts["--root"]
337 if "--accept-properties" in opts:
338 os.environ["ACCEPT_PROPERTIES"] = opts["--accept-properties"]
339
David James88d780c2014-02-05 13:03:29 -0800340 # If we're installing packages to the board, we can disable vardb locks.
341 # This is safe because we only run up to one instance of parallel_emerge in
342 # parallel.
343 # TODO(davidjames): Enable this for the host too.
Bertrand SIMONNET0625e1a2015-04-07 11:41:16 -0700344 if self.sysroot:
David Jamesfcb70ef2011-02-02 16:02:30 -0800345 os.environ.setdefault("PORTAGE_LOCKS", "false")
David Jamesfcb70ef2011-02-02 16:02:30 -0800346
347 # Now that we've setup the necessary environment variables, we can load the
348 # emerge config from disk.
Gilad Arnold94758762015-05-22 12:23:23 -0700349 # pylint: disable=unpacking-non-sequence
David Jamesfcb70ef2011-02-02 16:02:30 -0800350 settings, trees, mtimedb = load_emerge_config()
351
David Jamesea3ca332011-05-26 11:48:29 -0700352 # Add in EMERGE_DEFAULT_OPTS, if specified.
353 tmpcmdline = []
354 if "--ignore-default-opts" not in opts:
355 tmpcmdline.extend(settings["EMERGE_DEFAULT_OPTS"].split())
356 tmpcmdline.extend(emerge_args)
357 action, opts, cmdline_packages = parse_opts(tmpcmdline)
358
359 # If we're installing to the board, we want the --root-deps option so that
360 # portage will install the build dependencies to that location as well.
Bertrand SIMONNET0625e1a2015-04-07 11:41:16 -0700361 if self.sysroot:
David Jamesea3ca332011-05-26 11:48:29 -0700362 opts.setdefault("--root-deps", True)
363
David Jamesfcb70ef2011-02-02 16:02:30 -0800364 # Check whether our portage tree is out of date. Typically, this happens
365 # when you're setting up a new portage tree, such as in setup_board and
366 # make_chroot. In that case, portage applies a bunch of global updates
367 # here. Once the updates are finished, we need to commit any changes
368 # that the global update made to our mtimedb, and reload the config.
369 #
370 # Portage normally handles this logic in emerge_main, but again, we can't
371 # use that function here.
372 if _global_updates(trees, mtimedb["updates"]):
373 mtimedb.commit()
Gilad Arnold94758762015-05-22 12:23:23 -0700374 # pylint: disable=unpacking-non-sequence
David Jamesfcb70ef2011-02-02 16:02:30 -0800375 settings, trees, mtimedb = load_emerge_config(trees=trees)
376
377 # Setup implied options. Portage normally handles this logic in
378 # emerge_main.
379 if "--buildpkgonly" in opts or "buildpkg" in settings.features:
380 opts.setdefault("--buildpkg", True)
381 if "--getbinpkgonly" in opts:
382 opts.setdefault("--usepkgonly", True)
383 opts.setdefault("--getbinpkg", True)
384 if "getbinpkg" in settings.features:
385 # Per emerge_main, FEATURES=getbinpkg overrides --getbinpkg=n
386 opts["--getbinpkg"] = True
387 if "--getbinpkg" in opts or "--usepkgonly" in opts:
388 opts.setdefault("--usepkg", True)
389 if "--fetch-all-uri" in opts:
390 opts.setdefault("--fetchonly", True)
391 if "--skipfirst" in opts:
392 opts.setdefault("--resume", True)
393 if "--buildpkgonly" in opts:
394 # --buildpkgonly will not merge anything, so it overrides all binary
395 # package options.
396 for opt in ("--getbinpkg", "--getbinpkgonly",
397 "--usepkg", "--usepkgonly"):
398 opts.pop(opt, None)
399 if (settings.get("PORTAGE_DEBUG", "") == "1" and
400 "python-trace" in settings.features):
401 portage.debug.set_trace(True)
402
403 # Complain about unsupported options
David James386ccd12011-05-04 20:17:42 -0700404 for opt in ("--ask", "--ask-enter-invalid", "--resume", "--skipfirst"):
David Jamesfcb70ef2011-02-02 16:02:30 -0800405 if opt in opts:
Mike Frysinger383367e2014-09-16 15:06:17 -0400406 print("%s is not supported by parallel_emerge" % opt)
David Jamesfcb70ef2011-02-02 16:02:30 -0800407 sys.exit(1)
408
409 # Make emerge specific adjustments to the config (e.g. colors!)
410 adjust_configs(opts, trees)
411
412 # Save our configuration so far in the emerge object
413 emerge = self.emerge
414 emerge.action, emerge.opts = action, opts
415 emerge.settings, emerge.trees, emerge.mtimedb = settings, trees, mtimedb
416 emerge.cmdline_packages = cmdline_packages
417 root = settings["ROOT"]
418 emerge.root_config = trees[root]["root_config"]
419
David James386ccd12011-05-04 20:17:42 -0700420 if "--usepkg" in opts:
David Jamesfcb70ef2011-02-02 16:02:30 -0800421 emerge.trees[root]["bintree"].populate("--getbinpkg" in opts)
422
David Jamesfcb70ef2011-02-02 16:02:30 -0800423 def CreateDepgraph(self, emerge, packages):
424 """Create an emerge depgraph object."""
425 # Setup emerge options.
426 emerge_opts = emerge.opts.copy()
427
David James386ccd12011-05-04 20:17:42 -0700428 # Ask portage to build a dependency graph. with the options we specified
429 # above.
David Jamesfcb70ef2011-02-02 16:02:30 -0800430 params = create_depgraph_params(emerge_opts, emerge.action)
David Jamesbf1e3442011-05-28 07:44:20 -0700431 success, depgraph, favorites = backtrack_depgraph(
David James386ccd12011-05-04 20:17:42 -0700432 emerge.settings, emerge.trees, emerge_opts, params, emerge.action,
433 packages, emerge.spinner)
434 emerge.depgraph = depgraph
David Jamesfcb70ef2011-02-02 16:02:30 -0800435
David James386ccd12011-05-04 20:17:42 -0700436 # Is it impossible to honor the user's request? Bail!
437 if not success:
438 depgraph.display_problems()
439 sys.exit(1)
David Jamesfcb70ef2011-02-02 16:02:30 -0800440
441 emerge.depgraph = depgraph
David Jamesbf1e3442011-05-28 07:44:20 -0700442 emerge.favorites = favorites
David Jamesfcb70ef2011-02-02 16:02:30 -0800443
David Jamesdeebd692011-05-09 17:02:52 -0700444 # Prime and flush emerge caches.
445 root = emerge.settings["ROOT"]
446 vardb = emerge.trees[root]["vartree"].dbapi
David James0bdc5de2011-05-12 16:22:26 -0700447 if "--pretend" not in emerge.opts:
448 vardb.counter_tick()
David Jamesdeebd692011-05-09 17:02:52 -0700449 vardb.flush_cache()
450
David James386ccd12011-05-04 20:17:42 -0700451 def GenDependencyTree(self):
David Jamesfcb70ef2011-02-02 16:02:30 -0800452 """Get dependency tree info from emerge.
453
David Jamesfcb70ef2011-02-02 16:02:30 -0800454 Returns:
455 Dependency tree
456 """
457 start = time.time()
458
459 emerge = self.emerge
460
461 # Create a list of packages to merge
462 packages = set(emerge.cmdline_packages[:])
David Jamesfcb70ef2011-02-02 16:02:30 -0800463
464 # Tell emerge to be quiet. We print plenty of info ourselves so we don't
465 # need any extra output from portage.
466 portage.util.noiselimit = -1
467
468 # My favorite feature: The silent spinner. It doesn't spin. Ever.
469 # I'd disable the colors by default too, but they look kind of cool.
470 emerge.spinner = stdout_spinner()
471 emerge.spinner.update = emerge.spinner.update_quiet
472
473 if "--quiet" not in emerge.opts:
Mike Frysinger383367e2014-09-16 15:06:17 -0400474 print("Calculating deps...")
David Jamesfcb70ef2011-02-02 16:02:30 -0800475
Chris Ching4a2ebd62017-04-26 16:30:05 -0600476 with cros_event.newEvent(task_name="GenerateDepTree"):
Chris Ching5fcbd622016-11-28 09:22:15 -0700477 self.CreateDepgraph(emerge, packages)
478 depgraph = emerge.depgraph
David Jamesfcb70ef2011-02-02 16:02:30 -0800479
480 # Build our own tree from the emerge digraph.
481 deps_tree = {}
Don Garrett25f309a2014-03-19 14:02:12 -0700482 # pylint: disable=W0212
David Jamesfcb70ef2011-02-02 16:02:30 -0800483 digraph = depgraph._dynamic_config.digraph
David James3f778802011-08-25 19:31:45 -0700484 root = emerge.settings["ROOT"]
Bertrand SIMONNETa15b5072014-10-23 15:27:52 -0700485 final_db = depgraph._dynamic_config._filtered_trees[root]['graph_db']
David Jamesfcb70ef2011-02-02 16:02:30 -0800486 for node, node_deps in digraph.nodes.items():
487 # Calculate dependency packages that need to be installed first. Each
488 # child on the digraph is a dependency. The "operation" field specifies
489 # what we're doing (e.g. merge, uninstall, etc.). The "priorities" array
490 # contains the type of dependency (e.g. build, runtime, runtime_post,
491 # etc.)
492 #
David Jamesfcb70ef2011-02-02 16:02:30 -0800493 # Portage refers to the identifiers for packages as a CPV. This acronym
494 # stands for Component/Path/Version.
495 #
496 # Here's an example CPV: chromeos-base/power_manager-0.0.1-r1
497 # Split up, this CPV would be:
498 # C -- Component: chromeos-base
499 # P -- Path: power_manager
500 # V -- Version: 0.0.1-r1
501 #
502 # We just refer to CPVs as packages here because it's easier.
503 deps = {}
504 for child, priorities in node_deps[0].items():
David James3f778802011-08-25 19:31:45 -0700505 if isinstance(child, Package) and child.root == root:
506 cpv = str(child.cpv)
507 action = str(child.operation)
508
509 # If we're uninstalling a package, check whether Portage is
510 # installing a replacement. If so, just depend on the installation
511 # of the new package, because the old package will automatically
512 # be uninstalled at that time.
513 if action == "uninstall":
514 for pkg in final_db.match_pkgs(child.slot_atom):
515 cpv = str(pkg.cpv)
516 action = "merge"
517 break
518
519 deps[cpv] = dict(action=action,
520 deptypes=[str(x) for x in priorities],
521 deps={})
David Jamesfcb70ef2011-02-02 16:02:30 -0800522
523 # We've built our list of deps, so we can add our package to the tree.
David James3f778802011-08-25 19:31:45 -0700524 if isinstance(node, Package) and node.root == root:
David Jamesfcb70ef2011-02-02 16:02:30 -0800525 deps_tree[str(node.cpv)] = dict(action=str(node.operation),
526 deps=deps)
527
David Jamesfcb70ef2011-02-02 16:02:30 -0800528 # Ask portage for its install plan, so that we can only throw out
David James386ccd12011-05-04 20:17:42 -0700529 # dependencies that portage throws out.
David Jamesfcb70ef2011-02-02 16:02:30 -0800530 deps_info = {}
531 for pkg in depgraph.altlist():
532 if isinstance(pkg, Package):
David James3f778802011-08-25 19:31:45 -0700533 assert pkg.root == root
David Jamesfcb70ef2011-02-02 16:02:30 -0800534 self.package_db[pkg.cpv] = pkg
535
David Jamesfcb70ef2011-02-02 16:02:30 -0800536 # Save off info about the package
David James386ccd12011-05-04 20:17:42 -0700537 deps_info[str(pkg.cpv)] = {"idx": len(deps_info)}
David Jamesfcb70ef2011-02-02 16:02:30 -0800538
539 seconds = time.time() - start
540 if "--quiet" not in emerge.opts:
Mike Frysinger383367e2014-09-16 15:06:17 -0400541 print("Deps calculated in %dm%.1fs" % (seconds / 60, seconds % 60))
David Jamesfcb70ef2011-02-02 16:02:30 -0800542
543 return deps_tree, deps_info
544
545 def PrintTree(self, deps, depth=""):
546 """Print the deps we have seen in the emerge output.
547
548 Args:
Mike Frysinger6f3c48e2015-05-06 02:38:51 -0400549 deps: Dependency tree structure.
550 depth: Allows printing the tree recursively, with indentation.
David Jamesfcb70ef2011-02-02 16:02:30 -0800551 """
552 for entry in sorted(deps):
553 action = deps[entry]["action"]
Mike Frysinger383367e2014-09-16 15:06:17 -0400554 print("%s %s (%s)" % (depth, entry, action))
David Jamesfcb70ef2011-02-02 16:02:30 -0800555 self.PrintTree(deps[entry]["deps"], depth=depth + " ")
556
David James386ccd12011-05-04 20:17:42 -0700557 def GenDependencyGraph(self, deps_tree, deps_info):
David Jamesfcb70ef2011-02-02 16:02:30 -0800558 """Generate a doubly linked dependency graph.
559
560 Args:
561 deps_tree: Dependency tree structure.
562 deps_info: More details on the dependencies.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500563
David Jamesfcb70ef2011-02-02 16:02:30 -0800564 Returns:
565 Deps graph in the form of a dict of packages, with each package
566 specifying a "needs" list and "provides" list.
567 """
568 emerge = self.emerge
David Jamesfcb70ef2011-02-02 16:02:30 -0800569
David Jamesfcb70ef2011-02-02 16:02:30 -0800570 # deps_map is the actual dependency graph.
571 #
572 # Each package specifies a "needs" list and a "provides" list. The "needs"
573 # list indicates which packages we depend on. The "provides" list
574 # indicates the reverse dependencies -- what packages need us.
575 #
576 # We also provide some other information in the dependency graph:
577 # - action: What we're planning on doing with this package. Generally,
578 # "merge", "nomerge", or "uninstall"
David Jamesfcb70ef2011-02-02 16:02:30 -0800579 deps_map = {}
580
581 def ReverseTree(packages):
582 """Convert tree to digraph.
583
584 Take the tree of package -> requirements and reverse it to a digraph of
585 buildable packages -> packages they unblock.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500586
David Jamesfcb70ef2011-02-02 16:02:30 -0800587 Args:
588 packages: Tree(s) of dependencies.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500589
David Jamesfcb70ef2011-02-02 16:02:30 -0800590 Returns:
591 Unsanitized digraph.
592 """
David James8c7e5e32011-06-28 11:26:03 -0700593 binpkg_phases = set(["setup", "preinst", "postinst"])
David Jamese5e1c0a2014-09-29 17:19:41 -0700594 needed_dep_types = set(["blocker", "buildtime", "buildtime_slot_op",
595 "runtime", "runtime_slot_op"])
Benjamin Gordon670b6972017-08-29 13:43:56 -0600596 ignored_dep_types = set(["ignored", "runtime_post", "soft"])
597
598 # There's a bug in the Portage library where it always returns 'optional'
599 # and never 'buildtime' for the digraph while --usepkg is enabled; even
600 # when the package is being rebuilt. To work around this, we treat
601 # 'optional' as needed when we are using --usepkg. See crbug.com/756240 .
602 if "--usepkg" in self.emerge.opts:
603 needed_dep_types.add("optional")
604 else:
605 ignored_dep_types.add("optional")
606
David Jamese5e1c0a2014-09-29 17:19:41 -0700607 all_dep_types = ignored_dep_types | needed_dep_types
David Jamesfcb70ef2011-02-02 16:02:30 -0800608 for pkg in packages:
609
610 # Create an entry for the package
611 action = packages[pkg]["action"]
David James8c7e5e32011-06-28 11:26:03 -0700612 default_pkg = {"needs": {}, "provides": set(), "action": action,
613 "nodeps": False, "binary": False}
David Jamesfcb70ef2011-02-02 16:02:30 -0800614 this_pkg = deps_map.setdefault(pkg, default_pkg)
615
David James8c7e5e32011-06-28 11:26:03 -0700616 if pkg in deps_info:
617 this_pkg["idx"] = deps_info[pkg]["idx"]
618
619 # If a package doesn't have any defined phases that might use the
620 # dependent packages (i.e. pkg_setup, pkg_preinst, or pkg_postinst),
621 # we can install this package before its deps are ready.
622 emerge_pkg = self.package_db.get(pkg)
623 if emerge_pkg and emerge_pkg.type_name == "binary":
624 this_pkg["binary"] = True
Mike Frysinger66652ec2014-04-24 11:42:25 -0400625 defined_phases = emerge_pkg.defined_phases
David James8c7e5e32011-06-28 11:26:03 -0700626 defined_binpkg_phases = binpkg_phases.intersection(defined_phases)
627 if not defined_binpkg_phases:
628 this_pkg["nodeps"] = True
629
David Jamesfcb70ef2011-02-02 16:02:30 -0800630 # Create entries for dependencies of this package first.
631 ReverseTree(packages[pkg]["deps"])
632
633 # Add dependencies to this package.
634 for dep, dep_item in packages[pkg]["deps"].iteritems():
David James8c7e5e32011-06-28 11:26:03 -0700635 # We only need to enforce strict ordering of dependencies if the
David James3f778802011-08-25 19:31:45 -0700636 # dependency is a blocker, or is a buildtime or runtime dependency.
637 # (I.e., ignored, optional, and runtime_post dependencies don't
638 # depend on ordering.)
David James8c7e5e32011-06-28 11:26:03 -0700639 dep_types = dep_item["deptypes"]
640 if needed_dep_types.intersection(dep_types):
641 deps_map[dep]["provides"].add(pkg)
642 this_pkg["needs"][dep] = "/".join(dep_types)
David Jamesfcb70ef2011-02-02 16:02:30 -0800643
David Jamese5e1c0a2014-09-29 17:19:41 -0700644 # Verify we processed all appropriate dependency types.
645 unknown_dep_types = set(dep_types) - all_dep_types
646 if unknown_dep_types:
647 print("Unknown dependency types found:")
648 print(" %s -> %s (%s)" % (pkg, dep, "/".join(unknown_dep_types)))
649 sys.exit(1)
650
David James3f778802011-08-25 19:31:45 -0700651 # If there's a blocker, Portage may need to move files from one
652 # package to another, which requires editing the CONTENTS files of
653 # both packages. To avoid race conditions while editing this file,
654 # the two packages must not be installed in parallel, so we can't
655 # safely ignore dependencies. See http://crosbug.com/19328
656 if "blocker" in dep_types:
657 this_pkg["nodeps"] = False
658
David Jamesfcb70ef2011-02-02 16:02:30 -0800659 def FindCycles():
660 """Find cycles in the dependency tree.
661
662 Returns:
663 A dict mapping cyclic packages to a dict of the deps that cause
664 cycles. For each dep that causes cycles, it returns an example
665 traversal of the graph that shows the cycle.
666 """
667
668 def FindCyclesAtNode(pkg, cycles, unresolved, resolved):
669 """Find cycles in cyclic dependencies starting at specified package.
670
671 Args:
672 pkg: Package identifier.
673 cycles: A dict mapping cyclic packages to a dict of the deps that
674 cause cycles. For each dep that causes cycles, it returns an
675 example traversal of the graph that shows the cycle.
676 unresolved: Nodes that have been visited but are not fully processed.
677 resolved: Nodes that have been visited and are fully processed.
678 """
679 pkg_cycles = cycles.get(pkg)
680 if pkg in resolved and not pkg_cycles:
681 # If we already looked at this package, and found no cyclic
682 # dependencies, we can stop now.
683 return
684 unresolved.append(pkg)
685 for dep in deps_map[pkg]["needs"]:
686 if dep in unresolved:
687 idx = unresolved.index(dep)
688 mycycle = unresolved[idx:] + [dep]
David James321490a2012-12-17 12:05:56 -0800689 for i in xrange(len(mycycle) - 1):
David Jamesfcb70ef2011-02-02 16:02:30 -0800690 pkg1, pkg2 = mycycle[i], mycycle[i+1]
691 cycles.setdefault(pkg1, {}).setdefault(pkg2, mycycle)
692 elif not pkg_cycles or dep not in pkg_cycles:
693 # Looks like we haven't seen this edge before.
694 FindCyclesAtNode(dep, cycles, unresolved, resolved)
695 unresolved.pop()
696 resolved.add(pkg)
697
698 cycles, unresolved, resolved = {}, [], set()
699 for pkg in deps_map:
700 FindCyclesAtNode(pkg, cycles, unresolved, resolved)
701 return cycles
702
David James386ccd12011-05-04 20:17:42 -0700703 def RemoveUnusedPackages():
David Jamesfcb70ef2011-02-02 16:02:30 -0800704 """Remove installed packages, propagating dependencies."""
David Jamesfcb70ef2011-02-02 16:02:30 -0800705 # Schedule packages that aren't on the install list for removal
706 rm_pkgs = set(deps_map.keys()) - set(deps_info.keys())
707
David Jamesfcb70ef2011-02-02 16:02:30 -0800708 # Remove the packages we don't want, simplifying the graph and making
709 # it easier for us to crack cycles.
710 for pkg in sorted(rm_pkgs):
711 this_pkg = deps_map[pkg]
712 needs = this_pkg["needs"]
713 provides = this_pkg["provides"]
714 for dep in needs:
715 dep_provides = deps_map[dep]["provides"]
716 dep_provides.update(provides)
717 dep_provides.discard(pkg)
718 dep_provides.discard(dep)
719 for target in provides:
720 target_needs = deps_map[target]["needs"]
721 target_needs.update(needs)
722 target_needs.pop(pkg, None)
723 target_needs.pop(target, None)
724 del deps_map[pkg]
725
726 def PrintCycleBreak(basedep, dep, mycycle):
727 """Print details about a cycle that we are planning on breaking.
728
Mike Frysinger02e1e072013-11-10 22:11:34 -0500729 We are breaking a cycle where dep needs basedep. mycycle is an
730 example cycle which contains dep -> basedep.
731 """
David Jamesfcb70ef2011-02-02 16:02:30 -0800732
David Jamesfcb70ef2011-02-02 16:02:30 -0800733 needs = deps_map[dep]["needs"]
734 depinfo = needs.get(basedep, "deleted")
David Jamesfcb70ef2011-02-02 16:02:30 -0800735
David James3f778802011-08-25 19:31:45 -0700736 # It's OK to swap install order for blockers, as long as the two
737 # packages aren't installed in parallel. If there is a cycle, then
738 # we know the packages depend on each other already, so we can drop the
739 # blocker safely without printing a warning.
740 if depinfo == "blocker":
741 return
742
David Jamesfcb70ef2011-02-02 16:02:30 -0800743 # Notify the user that we're breaking a cycle.
Mike Frysinger383367e2014-09-16 15:06:17 -0400744 print("Breaking %s -> %s (%s)" % (dep, basedep, depinfo))
David Jamesfcb70ef2011-02-02 16:02:30 -0800745
746 # Show cycle.
David James321490a2012-12-17 12:05:56 -0800747 for i in xrange(len(mycycle) - 1):
David Jamesfcb70ef2011-02-02 16:02:30 -0800748 pkg1, pkg2 = mycycle[i], mycycle[i+1]
749 needs = deps_map[pkg1]["needs"]
750 depinfo = needs.get(pkg2, "deleted")
751 if pkg1 == dep and pkg2 == basedep:
752 depinfo = depinfo + ", deleting"
Mike Frysinger383367e2014-09-16 15:06:17 -0400753 print(" %s -> %s (%s)" % (pkg1, pkg2, depinfo))
David Jamesfcb70ef2011-02-02 16:02:30 -0800754
755 def SanitizeTree():
756 """Remove circular dependencies.
757
758 We prune all dependencies involved in cycles that go against the emerge
759 ordering. This has a nice property: we're guaranteed to merge
760 dependencies in the same order that portage does.
761
762 Because we don't treat any dependencies as "soft" unless they're killed
763 by a cycle, we pay attention to a larger number of dependencies when
764 merging. This hurts performance a bit, but helps reliability.
765 """
766 start = time.time()
767 cycles = FindCycles()
768 while cycles:
769 for dep, mycycles in cycles.iteritems():
770 for basedep, mycycle in mycycles.iteritems():
771 if deps_info[basedep]["idx"] >= deps_info[dep]["idx"]:
Matt Tennant08797302011-10-17 16:18:45 -0700772 if "--quiet" not in emerge.opts:
773 PrintCycleBreak(basedep, dep, mycycle)
David Jamesfcb70ef2011-02-02 16:02:30 -0800774 del deps_map[dep]["needs"][basedep]
775 deps_map[basedep]["provides"].remove(dep)
776 cycles = FindCycles()
777 seconds = time.time() - start
778 if "--quiet" not in emerge.opts and seconds >= 0.1:
Mike Frysinger383367e2014-09-16 15:06:17 -0400779 print("Tree sanitized in %dm%.1fs" % (seconds / 60, seconds % 60))
David Jamesfcb70ef2011-02-02 16:02:30 -0800780
David James8c7e5e32011-06-28 11:26:03 -0700781 def FindRecursiveProvides(pkg, seen):
782 """Find all nodes that require a particular package.
783
784 Assumes that graph is acyclic.
785
786 Args:
787 pkg: Package identifier.
788 seen: Nodes that have been visited so far.
789 """
790 if pkg in seen:
791 return
792 seen.add(pkg)
793 info = deps_map[pkg]
794 info["tprovides"] = info["provides"].copy()
795 for dep in info["provides"]:
796 FindRecursiveProvides(dep, seen)
797 info["tprovides"].update(deps_map[dep]["tprovides"])
798
David Jamesa22906f2011-05-04 19:53:26 -0700799 ReverseTree(deps_tree)
David Jamesa22906f2011-05-04 19:53:26 -0700800
David James386ccd12011-05-04 20:17:42 -0700801 # We need to remove unused packages so that we can use the dependency
802 # ordering of the install process to show us what cycles to crack.
803 RemoveUnusedPackages()
David Jamesfcb70ef2011-02-02 16:02:30 -0800804 SanitizeTree()
David James8c7e5e32011-06-28 11:26:03 -0700805 seen = set()
806 for pkg in deps_map:
807 FindRecursiveProvides(pkg, seen)
David Jamesfcb70ef2011-02-02 16:02:30 -0800808 return deps_map
809
810 def PrintInstallPlan(self, deps_map):
811 """Print an emerge-style install plan.
812
813 The install plan lists what packages we're installing, in order.
814 It's useful for understanding what parallel_emerge is doing.
815
816 Args:
817 deps_map: The dependency graph.
818 """
819
820 def InstallPlanAtNode(target, deps_map):
821 nodes = []
822 nodes.append(target)
823 for dep in deps_map[target]["provides"]:
824 del deps_map[dep]["needs"][target]
825 if not deps_map[dep]["needs"]:
826 nodes.extend(InstallPlanAtNode(dep, deps_map))
827 return nodes
828
829 deps_map = copy.deepcopy(deps_map)
830 install_plan = []
831 plan = set()
832 for target, info in deps_map.iteritems():
833 if not info["needs"] and target not in plan:
834 for item in InstallPlanAtNode(target, deps_map):
835 plan.add(item)
836 install_plan.append(self.package_db[item])
837
838 for pkg in plan:
839 del deps_map[pkg]
840
841 if deps_map:
Mike Frysinger383367e2014-09-16 15:06:17 -0400842 print("Cyclic dependencies:", " ".join(deps_map))
David Jamesfcb70ef2011-02-02 16:02:30 -0800843 PrintDepsMap(deps_map)
844 sys.exit(1)
845
846 self.emerge.depgraph.display(install_plan)
847
848
849def PrintDepsMap(deps_map):
850 """Print dependency graph, for each package list it's prerequisites."""
851 for i in sorted(deps_map):
Mike Frysinger383367e2014-09-16 15:06:17 -0400852 print("%s: (%s) needs" % (i, deps_map[i]["action"]))
David Jamesfcb70ef2011-02-02 16:02:30 -0800853 needs = deps_map[i]["needs"]
854 for j in sorted(needs):
Mike Frysinger383367e2014-09-16 15:06:17 -0400855 print(" %s" % (j))
David Jamesfcb70ef2011-02-02 16:02:30 -0800856 if not needs:
Mike Frysinger383367e2014-09-16 15:06:17 -0400857 print(" no dependencies")
David Jamesfcb70ef2011-02-02 16:02:30 -0800858
859
860class EmergeJobState(object):
Don Garrett25f309a2014-03-19 14:02:12 -0700861 """Structure describing the EmergeJobState."""
862
David Jamesfcb70ef2011-02-02 16:02:30 -0800863 __slots__ = ["done", "filename", "last_notify_timestamp", "last_output_seek",
864 "last_output_timestamp", "pkgname", "retcode", "start_timestamp",
Chris Ching73486ab2017-04-26 18:02:37 -0600865 "target", "try_count", "fetch_only", "unpack_only"]
David Jamesfcb70ef2011-02-02 16:02:30 -0800866
867 def __init__(self, target, pkgname, done, filename, start_timestamp,
Chris Ching73486ab2017-04-26 18:02:37 -0600868 retcode=None, fetch_only=False, try_count=0, unpack_only=False):
David Jamesfcb70ef2011-02-02 16:02:30 -0800869
870 # The full name of the target we're building (e.g.
Mike Frysingerfd969312014-04-02 22:16:42 -0400871 # virtual/target-os-1-r60)
David Jamesfcb70ef2011-02-02 16:02:30 -0800872 self.target = target
873
Mike Frysingerfd969312014-04-02 22:16:42 -0400874 # The short name of the target we're building (e.g. target-os-1-r60)
David Jamesfcb70ef2011-02-02 16:02:30 -0800875 self.pkgname = pkgname
876
877 # Whether the job is done. (True if the job is done; false otherwise.)
878 self.done = done
879
880 # The filename where output is currently stored.
881 self.filename = filename
882
883 # The timestamp of the last time we printed the name of the log file. We
884 # print this at the beginning of the job, so this starts at
885 # start_timestamp.
886 self.last_notify_timestamp = start_timestamp
887
888 # The location (in bytes) of the end of the last complete line we printed.
889 # This starts off at zero. We use this to jump to the right place when we
890 # print output from the same ebuild multiple times.
891 self.last_output_seek = 0
892
893 # The timestamp of the last time we printed output. Since we haven't
894 # printed output yet, this starts at zero.
895 self.last_output_timestamp = 0
896
897 # The return code of our job, if the job is actually finished.
898 self.retcode = retcode
899
Chris Ching73486ab2017-04-26 18:02:37 -0600900 # Number of tries for this job
901 self.try_count = try_count
902
Brian Harring0be85c62012-03-17 19:52:12 -0700903 # Was this just a fetch job?
904 self.fetch_only = fetch_only
905
David Jamesfcb70ef2011-02-02 16:02:30 -0800906 # The timestamp when our job started.
907 self.start_timestamp = start_timestamp
908
Thiago Goncalesf4acc422013-07-17 10:26:35 -0700909 # No emerge, only unpack packages.
910 self.unpack_only = unpack_only
911
David Jamesfcb70ef2011-02-02 16:02:30 -0800912
David James321490a2012-12-17 12:05:56 -0800913def KillHandler(_signum, _frame):
David James7358d032011-05-19 10:40:03 -0700914 # Kill self and all subprocesses.
915 os.killpg(0, signal.SIGKILL)
916
Mike Frysingercc838832014-05-24 13:10:30 -0400917
David Jamesfcb70ef2011-02-02 16:02:30 -0800918def SetupWorkerSignals():
David James321490a2012-12-17 12:05:56 -0800919 def ExitHandler(_signum, _frame):
David James7358d032011-05-19 10:40:03 -0700920 # Set KILLED flag.
921 KILLED.set()
David James13cead42011-05-18 16:22:01 -0700922
David James7358d032011-05-19 10:40:03 -0700923 # Remove our signal handlers so we don't get called recursively.
924 signal.signal(signal.SIGINT, KillHandler)
925 signal.signal(signal.SIGTERM, KillHandler)
David Jamesfcb70ef2011-02-02 16:02:30 -0800926
927 # Ensure that we exit quietly and cleanly, if possible, when we receive
928 # SIGTERM or SIGINT signals. By default, when the user hits CTRL-C, all
929 # of the child processes will print details about KeyboardInterrupt
930 # exceptions, which isn't very helpful.
931 signal.signal(signal.SIGINT, ExitHandler)
932 signal.signal(signal.SIGTERM, ExitHandler)
933
Mike Frysingerd74fe4a2014-04-24 11:43:38 -0400934
Chris Ching73486ab2017-04-26 18:02:37 -0600935def EmergeProcess(output, job_state, *args, **kwargs):
David James1ed3e252011-10-05 20:26:15 -0700936 """Merge a package in a subprocess.
937
938 Args:
David James1ed3e252011-10-05 20:26:15 -0700939 output: Temporary file to write output.
Chris Ching73486ab2017-04-26 18:02:37 -0600940 job_state: Stored state of package
David James6b29d052012-11-02 10:27:27 -0700941 *args: Arguments to pass to Scheduler constructor.
942 **kwargs: Keyword arguments to pass to Scheduler constructor.
David James1ed3e252011-10-05 20:26:15 -0700943
944 Returns:
945 The exit code returned by the subprocess.
946 """
Chris Chingb8eba812017-06-22 09:54:48 -0600947
Chris Ching73486ab2017-04-26 18:02:37 -0600948 target = job_state.target
949
950 job_state.try_count += 1
951
Chris Chingb8eba812017-06-22 09:54:48 -0600952 cpv = portage_util.SplitCPV(target)
Chris Ching73486ab2017-04-26 18:02:37 -0600953
Chris Ching4a2ebd62017-04-26 16:30:05 -0600954 event = cros_event.newEvent(task_name="EmergePackage",
Chris Chingb8eba812017-06-22 09:54:48 -0600955 name=cpv.package,
956 category=cpv.category,
Chris Ching73486ab2017-04-26 18:02:37 -0600957 version=cpv.version,
958 try_count=job_state.try_count)
David James1ed3e252011-10-05 20:26:15 -0700959 pid = os.fork()
960 if pid == 0:
961 try:
Mike Frysingerd74fe4a2014-04-24 11:43:38 -0400962 proctitle.settitle('EmergeProcess', target)
963
David James1ed3e252011-10-05 20:26:15 -0700964 # Sanity checks.
Mike Frysingerf02736e2013-11-08 15:27:00 -0500965 if sys.stdout.fileno() != 1:
966 raise Exception("sys.stdout.fileno() != 1")
967 if sys.stderr.fileno() != 2:
968 raise Exception("sys.stderr.fileno() != 2")
David James1ed3e252011-10-05 20:26:15 -0700969
970 # - Redirect 1 (stdout) and 2 (stderr) at our temporary file.
971 # - Redirect 0 to point at sys.stdin. In this case, sys.stdin
972 # points at a file reading os.devnull, because multiprocessing mucks
973 # with sys.stdin.
974 # - Leave the sys.stdin and output filehandles alone.
975 fd_pipes = {0: sys.stdin.fileno(),
976 1: output.fileno(),
977 2: output.fileno(),
978 sys.stdin.fileno(): sys.stdin.fileno(),
979 output.fileno(): output.fileno()}
Mike Frysinger66652ec2014-04-24 11:42:25 -0400980 # pylint: disable=W0212
981 portage.process._setup_pipes(fd_pipes, close_fds=False)
David James1ed3e252011-10-05 20:26:15 -0700982
983 # Portage doesn't like when sys.stdin.fileno() != 0, so point sys.stdin
984 # at the filehandle we just created in _setup_pipes.
985 if sys.stdin.fileno() != 0:
David James6b29d052012-11-02 10:27:27 -0700986 sys.__stdin__ = sys.stdin = os.fdopen(0, "r")
987
988 scheduler = Scheduler(*args, **kwargs)
989
990 # Enable blocker handling even though we're in --nodeps mode. This
991 # allows us to unmerge the blocker after we've merged the replacement.
992 scheduler._opts_ignore_blockers = frozenset()
David James1ed3e252011-10-05 20:26:15 -0700993
994 # Actually do the merge.
Chris Ching5fcbd622016-11-28 09:22:15 -0700995 with event:
Chris Ching73486ab2017-04-26 18:02:37 -0600996 job_state.retcode = scheduler.merge()
997 if job_state.retcode != 0:
Chris Ching5fcbd622016-11-28 09:22:15 -0700998 event.fail(message="non-zero value returned")
David James1ed3e252011-10-05 20:26:15 -0700999
1000 # We catch all exceptions here (including SystemExit, KeyboardInterrupt,
1001 # etc) so as to ensure that we don't confuse the multiprocessing module,
1002 # which expects that all forked children exit with os._exit().
David James321490a2012-12-17 12:05:56 -08001003 # pylint: disable=W0702
David James1ed3e252011-10-05 20:26:15 -07001004 except:
1005 traceback.print_exc(file=output)
Chris Ching73486ab2017-04-26 18:02:37 -06001006 job_state.retcode = 1
David James1ed3e252011-10-05 20:26:15 -07001007 sys.stdout.flush()
1008 sys.stderr.flush()
1009 output.flush()
Don Garrett25f309a2014-03-19 14:02:12 -07001010 # pylint: disable=W0212
Chris Ching73486ab2017-04-26 18:02:37 -06001011 os._exit(job_state.retcode)
David James1ed3e252011-10-05 20:26:15 -07001012 else:
1013 # Return the exit code of the subprocess.
1014 return os.waitpid(pid, 0)[1]
David Jamesfcb70ef2011-02-02 16:02:30 -08001015
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001016
1017def UnpackPackage(pkg_state):
1018 """Unpacks package described by pkg_state.
1019
1020 Args:
1021 pkg_state: EmergeJobState object describing target.
1022
1023 Returns:
1024 Exit code returned by subprocess.
1025 """
1026 pkgdir = os.environ.get("PKGDIR",
1027 os.path.join(os.environ["SYSROOT"], "packages"))
1028 root = os.environ.get("ROOT", os.environ["SYSROOT"])
1029 path = os.path.join(pkgdir, pkg_state.target + ".tbz2")
1030 comp = cros_build_lib.FindCompressor(cros_build_lib.COMP_BZIP2)
1031 cmd = [comp, "-dc"]
1032 if comp.endswith("pbzip2"):
1033 cmd.append("--ignore-trailing-garbage=1")
1034 cmd.append(path)
1035
Chris Ching4a2ebd62017-04-26 16:30:05 -06001036 with cros_event.newEvent(task_name="UnpackPackage", **pkg_state) as event:
Chris Ching5fcbd622016-11-28 09:22:15 -07001037 result = cros_build_lib.RunCommand(cmd, cwd=root, stdout_to_pipe=True,
1038 print_cmd=False, error_code_ok=True)
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001039
Chris Ching5fcbd622016-11-28 09:22:15 -07001040 # If we were not successful, return now and don't attempt untar.
1041 if result.returncode != 0:
1042 event.fail("error compressing: returned {}".format(result.returncode))
1043 return result.returncode
1044
1045 cmd = ["sudo", "tar", "-xf", "-", "-C", root]
1046
1047 result = cros_build_lib.RunCommand(cmd, cwd=root, input=result.output,
1048 print_cmd=False, error_code_ok=True)
1049 if result.returncode != 0:
1050 event.fail("error extracting:returned {}".format(result.returncode))
1051
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001052 return result.returncode
1053
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001054
1055def EmergeWorker(task_queue, job_queue, emerge, package_db, fetch_only=False,
1056 unpack_only=False):
David Jamesfcb70ef2011-02-02 16:02:30 -08001057 """This worker emerges any packages given to it on the task_queue.
1058
1059 Args:
1060 task_queue: The queue of tasks for this worker to do.
1061 job_queue: The queue of results from the worker.
1062 emerge: An EmergeData() object.
1063 package_db: A dict, mapping package ids to portage Package objects.
Brian Harring0be85c62012-03-17 19:52:12 -07001064 fetch_only: A bool, indicating if we should just fetch the target.
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001065 unpack_only: A bool, indicating if we should just unpack the target.
David Jamesfcb70ef2011-02-02 16:02:30 -08001066
1067 It expects package identifiers to be passed to it via task_queue. When
1068 a task is started, it pushes the (target, filename) to the started_queue.
1069 The output is stored in filename. When a merge starts or finishes, we push
1070 EmergeJobState objects to the job_queue.
1071 """
Mike Frysingerd74fe4a2014-04-24 11:43:38 -04001072 if fetch_only:
1073 mode = 'fetch'
1074 elif unpack_only:
1075 mode = 'unpack'
1076 else:
1077 mode = 'emerge'
1078 proctitle.settitle('EmergeWorker', mode, '[idle]')
David Jamesfcb70ef2011-02-02 16:02:30 -08001079
1080 SetupWorkerSignals()
1081 settings, trees, mtimedb = emerge.settings, emerge.trees, emerge.mtimedb
David Jamesdeebd692011-05-09 17:02:52 -07001082
1083 # Disable flushing of caches to save on I/O.
David James7a1ea4b2011-10-13 15:06:41 -07001084 root = emerge.settings["ROOT"]
1085 vardb = emerge.trees[root]["vartree"].dbapi
Mike Frysingere56debd2014-11-19 01:54:36 -05001086 vardb._flush_cache_enabled = False # pylint: disable=protected-access
Brian Harring0be85c62012-03-17 19:52:12 -07001087 bindb = emerge.trees[root]["bintree"].dbapi
1088 # Might be a set, might be a list, might be None; no clue, just use shallow
1089 # copy to ensure we can roll it back.
Don Garrett25f309a2014-03-19 14:02:12 -07001090 # pylint: disable=W0212
Brian Harring0be85c62012-03-17 19:52:12 -07001091 original_remotepkgs = copy.copy(bindb.bintree._remotepkgs)
David Jamesdeebd692011-05-09 17:02:52 -07001092
David Jamesfcb70ef2011-02-02 16:02:30 -08001093 opts, spinner = emerge.opts, emerge.spinner
1094 opts["--nodeps"] = True
Brian Harring0be85c62012-03-17 19:52:12 -07001095 if fetch_only:
1096 opts["--fetchonly"] = True
1097
David Jamesfcb70ef2011-02-02 16:02:30 -08001098 while True:
1099 # Wait for a new item to show up on the queue. This is a blocking wait,
1100 # so if there's nothing to do, we just sit here.
Brian Harring0be85c62012-03-17 19:52:12 -07001101 pkg_state = task_queue.get()
1102 if pkg_state is None:
David Jamesfcb70ef2011-02-02 16:02:30 -08001103 # If target is None, this means that the main thread wants us to quit.
1104 # The other workers need to exit too, so we'll push the message back on
1105 # to the queue so they'll get it too.
Brian Harring0be85c62012-03-17 19:52:12 -07001106 task_queue.put(None)
David Jamesfcb70ef2011-02-02 16:02:30 -08001107 return
David James7358d032011-05-19 10:40:03 -07001108 if KILLED.is_set():
1109 return
1110
Brian Harring0be85c62012-03-17 19:52:12 -07001111 target = pkg_state.target
Mike Frysingerd74fe4a2014-04-24 11:43:38 -04001112 proctitle.settitle('EmergeWorker', mode, target)
Brian Harring0be85c62012-03-17 19:52:12 -07001113
David Jamesfcb70ef2011-02-02 16:02:30 -08001114 db_pkg = package_db[target]
Brian Harring0be85c62012-03-17 19:52:12 -07001115
1116 if db_pkg.type_name == "binary":
1117 if not fetch_only and pkg_state.fetched_successfully:
1118 # Ensure portage doesn't think our pkg is remote- else it'll force
1119 # a redownload of it (even if the on-disk file is fine). In-memory
1120 # caching basically, implemented dumbly.
1121 bindb.bintree._remotepkgs = None
1122 else:
1123 bindb.bintree_remotepkgs = original_remotepkgs
1124
David Jamesfcb70ef2011-02-02 16:02:30 -08001125 db_pkg.root_config = emerge.root_config
1126 install_list = [db_pkg]
1127 pkgname = db_pkg.pf
1128 output = tempfile.NamedTemporaryFile(prefix=pkgname + "-", delete=False)
David James01b1e0f2012-06-07 17:18:05 -07001129 os.chmod(output.name, 644)
David Jamesfcb70ef2011-02-02 16:02:30 -08001130 start_timestamp = time.time()
Brian Harring0be85c62012-03-17 19:52:12 -07001131 job = EmergeJobState(target, pkgname, False, output.name, start_timestamp,
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001132 fetch_only=fetch_only, unpack_only=unpack_only)
David Jamesfcb70ef2011-02-02 16:02:30 -08001133 job_queue.put(job)
1134 if "--pretend" in opts:
Chris Ching73486ab2017-04-26 18:02:37 -06001135 job.retcode = 0
David Jamesfcb70ef2011-02-02 16:02:30 -08001136 else:
David Jamesfcb70ef2011-02-02 16:02:30 -08001137 try:
David James386ccd12011-05-04 20:17:42 -07001138 emerge.scheduler_graph.mergelist = install_list
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001139 if unpack_only:
Chris Ching73486ab2017-04-26 18:02:37 -06001140 job.retcode = UnpackPackage(pkg_state)
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001141 else:
Chris Ching73486ab2017-04-26 18:02:37 -06001142 job.retcode = EmergeProcess(output, job, settings, trees, mtimedb,
1143 opts, spinner,
1144 favorites=emerge.favorites,
1145 graph_config=emerge.scheduler_graph)
David Jamesfcb70ef2011-02-02 16:02:30 -08001146 except Exception:
1147 traceback.print_exc(file=output)
Chris Ching73486ab2017-04-26 18:02:37 -06001148 job.retcode = 1
David James1ed3e252011-10-05 20:26:15 -07001149 output.close()
David Jamesfcb70ef2011-02-02 16:02:30 -08001150
David James7358d032011-05-19 10:40:03 -07001151 if KILLED.is_set():
1152 return
1153
David Jamesfcb70ef2011-02-02 16:02:30 -08001154 job = EmergeJobState(target, pkgname, True, output.name, start_timestamp,
Chris Ching73486ab2017-04-26 18:02:37 -06001155 job.retcode, fetch_only=fetch_only,
1156 try_count=job.try_count, unpack_only=unpack_only)
David Jamesfcb70ef2011-02-02 16:02:30 -08001157 job_queue.put(job)
1158
Mike Frysingerd74fe4a2014-04-24 11:43:38 -04001159 # Set the title back to idle as the multiprocess pool won't destroy us;
1160 # when another job comes up, it'll re-use this process.
1161 proctitle.settitle('EmergeWorker', mode, '[idle]')
1162
David Jamesfcb70ef2011-02-02 16:02:30 -08001163
1164class LinePrinter(object):
1165 """Helper object to print a single line."""
1166
1167 def __init__(self, line):
1168 self.line = line
1169
David James321490a2012-12-17 12:05:56 -08001170 def Print(self, _seek_locations):
Mike Frysinger383367e2014-09-16 15:06:17 -04001171 print(self.line)
David Jamesfcb70ef2011-02-02 16:02:30 -08001172
1173
1174class JobPrinter(object):
1175 """Helper object to print output of a job."""
1176
1177 def __init__(self, job, unlink=False):
1178 """Print output of job.
1179
Mike Frysinger02e1e072013-11-10 22:11:34 -05001180 If unlink is True, unlink the job output file when done.
1181 """
David Jamesfcb70ef2011-02-02 16:02:30 -08001182 self.current_time = time.time()
1183 self.job = job
1184 self.unlink = unlink
1185
1186 def Print(self, seek_locations):
1187
1188 job = self.job
1189
1190 # Calculate how long the job has been running.
1191 seconds = self.current_time - job.start_timestamp
1192
1193 # Note that we've printed out the job so far.
1194 job.last_output_timestamp = self.current_time
1195
1196 # Note that we're starting the job
1197 info = "job %s (%dm%.1fs)" % (job.pkgname, seconds / 60, seconds % 60)
1198 last_output_seek = seek_locations.get(job.filename, 0)
1199 if last_output_seek:
Mike Frysinger383367e2014-09-16 15:06:17 -04001200 print("=== Continue output for %s ===" % info)
David Jamesfcb70ef2011-02-02 16:02:30 -08001201 else:
Mike Frysinger383367e2014-09-16 15:06:17 -04001202 print("=== Start output for %s ===" % info)
David Jamesfcb70ef2011-02-02 16:02:30 -08001203
1204 # Print actual output from job
1205 f = codecs.open(job.filename, encoding='utf-8', errors='replace')
1206 f.seek(last_output_seek)
1207 prefix = job.pkgname + ":"
1208 for line in f:
1209
1210 # Save off our position in the file
1211 if line and line[-1] == "\n":
1212 last_output_seek = f.tell()
1213 line = line[:-1]
1214
1215 # Print our line
Mike Frysinger383367e2014-09-16 15:06:17 -04001216 print(prefix, line.encode('utf-8', 'replace'))
David Jamesfcb70ef2011-02-02 16:02:30 -08001217 f.close()
1218
1219 # Save our last spot in the file so that we don't print out the same
1220 # location twice.
1221 seek_locations[job.filename] = last_output_seek
1222
1223 # Note end of output section
1224 if job.done:
Mike Frysinger383367e2014-09-16 15:06:17 -04001225 print("=== Complete: %s ===" % info)
David Jamesfcb70ef2011-02-02 16:02:30 -08001226 else:
Mike Frysinger383367e2014-09-16 15:06:17 -04001227 print("=== Still running: %s ===" % info)
David Jamesfcb70ef2011-02-02 16:02:30 -08001228
1229 if self.unlink:
1230 os.unlink(job.filename)
1231
1232
1233def PrintWorker(queue):
1234 """A worker that prints stuff to the screen as requested."""
Mike Frysingerd74fe4a2014-04-24 11:43:38 -04001235 proctitle.settitle('PrintWorker')
David Jamesfcb70ef2011-02-02 16:02:30 -08001236
David James321490a2012-12-17 12:05:56 -08001237 def ExitHandler(_signum, _frame):
David James7358d032011-05-19 10:40:03 -07001238 # Set KILLED flag.
1239 KILLED.set()
1240
David Jamesfcb70ef2011-02-02 16:02:30 -08001241 # Switch to default signal handlers so that we'll die after two signals.
David James7358d032011-05-19 10:40:03 -07001242 signal.signal(signal.SIGINT, KillHandler)
1243 signal.signal(signal.SIGTERM, KillHandler)
David Jamesfcb70ef2011-02-02 16:02:30 -08001244
1245 # Don't exit on the first SIGINT / SIGTERM, because the parent worker will
1246 # handle it and tell us when we need to exit.
1247 signal.signal(signal.SIGINT, ExitHandler)
1248 signal.signal(signal.SIGTERM, ExitHandler)
1249
1250 # seek_locations is a map indicating the position we are at in each file.
1251 # It starts off empty, but is set by the various Print jobs as we go along
1252 # to indicate where we left off in each file.
1253 seek_locations = {}
1254 while True:
1255 try:
1256 job = queue.get()
1257 if job:
1258 job.Print(seek_locations)
David Jamesbccf8eb2011-07-27 14:06:06 -07001259 sys.stdout.flush()
David Jamesfcb70ef2011-02-02 16:02:30 -08001260 else:
1261 break
1262 except IOError as ex:
1263 if ex.errno == errno.EINTR:
1264 # Looks like we received a signal. Keep printing.
1265 continue
1266 raise
1267
Brian Harring867e2362012-03-17 04:05:17 -07001268
Brian Harring0be85c62012-03-17 19:52:12 -07001269class TargetState(object):
Chris Ching5fcbd622016-11-28 09:22:15 -07001270 """Structure describing the TargetState."""
Brian Harring867e2362012-03-17 04:05:17 -07001271
Brian Harring0be85c62012-03-17 19:52:12 -07001272 __slots__ = ("target", "info", "score", "prefetched", "fetched_successfully")
Brian Harring867e2362012-03-17 04:05:17 -07001273
David James321490a2012-12-17 12:05:56 -08001274 def __init__(self, target, info):
Brian Harring867e2362012-03-17 04:05:17 -07001275 self.target, self.info = target, info
Brian Harring0be85c62012-03-17 19:52:12 -07001276 self.fetched_successfully = False
1277 self.prefetched = False
David James321490a2012-12-17 12:05:56 -08001278 self.score = None
Brian Harring867e2362012-03-17 04:05:17 -07001279 self.update_score()
1280
1281 def __cmp__(self, other):
1282 return cmp(self.score, other.score)
1283
1284 def update_score(self):
1285 self.score = (
1286 -len(self.info["tprovides"]),
Brian Harring0be85c62012-03-17 19:52:12 -07001287 len(self.info["needs"]),
Brian Harring11c5eeb2012-03-18 11:02:39 -07001288 not self.info["binary"],
Brian Harring867e2362012-03-17 04:05:17 -07001289 -len(self.info["provides"]),
1290 self.info["idx"],
1291 self.target,
1292 )
1293
1294
1295class ScoredHeap(object):
Don Garrett25f309a2014-03-19 14:02:12 -07001296 """Implementation of a general purpose scored heap."""
Brian Harring867e2362012-03-17 04:05:17 -07001297
Brian Harring0be85c62012-03-17 19:52:12 -07001298 __slots__ = ("heap", "_heap_set")
1299
Brian Harring867e2362012-03-17 04:05:17 -07001300 def __init__(self, initial=()):
Brian Harring0be85c62012-03-17 19:52:12 -07001301 self.heap = list()
1302 self._heap_set = set()
1303 if initial:
1304 self.multi_put(initial)
Brian Harring867e2362012-03-17 04:05:17 -07001305
1306 def get(self):
Brian Harring0be85c62012-03-17 19:52:12 -07001307 item = heapq.heappop(self.heap)
1308 self._heap_set.remove(item.target)
1309 return item
Brian Harring867e2362012-03-17 04:05:17 -07001310
Brian Harring0be85c62012-03-17 19:52:12 -07001311 def put(self, item):
1312 if not isinstance(item, TargetState):
1313 raise ValueError("Item %r isn't a TargetState" % (item,))
1314 heapq.heappush(self.heap, item)
1315 self._heap_set.add(item.target)
Brian Harring867e2362012-03-17 04:05:17 -07001316
Brian Harring0be85c62012-03-17 19:52:12 -07001317 def multi_put(self, sequence):
1318 sequence = list(sequence)
1319 self.heap.extend(sequence)
1320 self._heap_set.update(x.target for x in sequence)
Brian Harring867e2362012-03-17 04:05:17 -07001321 self.sort()
1322
David James5c9996d2012-03-24 10:50:46 -07001323 def sort(self):
1324 heapq.heapify(self.heap)
1325
Brian Harring0be85c62012-03-17 19:52:12 -07001326 def __contains__(self, target):
1327 return target in self._heap_set
1328
1329 def __nonzero__(self):
1330 return bool(self.heap)
1331
Brian Harring867e2362012-03-17 04:05:17 -07001332 def __len__(self):
1333 return len(self.heap)
1334
1335
David Jamesfcb70ef2011-02-02 16:02:30 -08001336class EmergeQueue(object):
1337 """Class to schedule emerge jobs according to a dependency graph."""
1338
Bertrand SIMONNET411945d2015-05-20 17:23:28 -07001339 def __init__(self, deps_map, emerge, package_db, show_output, unpack_only,
1340 max_retries):
David Jamesfcb70ef2011-02-02 16:02:30 -08001341 # Store the dependency graph.
1342 self._deps_map = deps_map
Brian Harring0be85c62012-03-17 19:52:12 -07001343 self._state_map = {}
David Jamesfcb70ef2011-02-02 16:02:30 -08001344 # Initialize the running queue to empty
Brian Harring0be85c62012-03-17 19:52:12 -07001345 self._build_jobs = {}
1346 self._build_ready = ScoredHeap()
1347 self._fetch_jobs = {}
1348 self._fetch_ready = ScoredHeap()
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001349 self._unpack_jobs = {}
1350 self._unpack_ready = ScoredHeap()
David Jamesfcb70ef2011-02-02 16:02:30 -08001351 # List of total package installs represented in deps_map.
1352 install_jobs = [x for x in deps_map if deps_map[x]["action"] == "merge"]
1353 self._total_jobs = len(install_jobs)
1354 self._show_output = show_output
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001355 self._unpack_only = unpack_only
Bertrand SIMONNET411945d2015-05-20 17:23:28 -07001356 self._max_retries = max_retries
David Jamesfcb70ef2011-02-02 16:02:30 -08001357
1358 if "--pretend" in emerge.opts:
Mike Frysinger383367e2014-09-16 15:06:17 -04001359 print("Skipping merge because of --pretend mode.")
David Jamesfcb70ef2011-02-02 16:02:30 -08001360 sys.exit(0)
1361
David Jamesaaf49e42014-04-24 09:40:05 -07001362 # Set up a session so we can easily terminate all children.
1363 self._SetupSession()
David James7358d032011-05-19 10:40:03 -07001364
David Jamesfcb70ef2011-02-02 16:02:30 -08001365 # Setup scheduler graph object. This is used by the child processes
1366 # to help schedule jobs.
1367 emerge.scheduler_graph = emerge.depgraph.schedulerGraph()
1368
1369 # Calculate how many jobs we can run in parallel. We don't want to pass
1370 # the --jobs flag over to emerge itself, because that'll tell emerge to
1371 # hide its output, and said output is quite useful for debugging hung
1372 # jobs.
1373 procs = min(self._total_jobs,
1374 emerge.opts.pop("--jobs", multiprocessing.cpu_count()))
Nam T. Nguyenf7098b32015-05-08 11:04:48 -07001375 self._build_procs = self._unpack_procs = max(1, procs)
1376 # Fetch is IO bound, we can use more processes.
1377 self._fetch_procs = max(4, procs)
David James8c7e5e32011-06-28 11:26:03 -07001378 self._load_avg = emerge.opts.pop("--load-average", None)
David Jamesfcb70ef2011-02-02 16:02:30 -08001379 self._job_queue = multiprocessing.Queue()
1380 self._print_queue = multiprocessing.Queue()
Brian Harring0be85c62012-03-17 19:52:12 -07001381
1382 self._fetch_queue = multiprocessing.Queue()
1383 args = (self._fetch_queue, self._job_queue, emerge, package_db, True)
1384 self._fetch_pool = multiprocessing.Pool(self._fetch_procs, EmergeWorker,
1385 args)
1386
1387 self._build_queue = multiprocessing.Queue()
1388 args = (self._build_queue, self._job_queue, emerge, package_db)
1389 self._build_pool = multiprocessing.Pool(self._build_procs, EmergeWorker,
1390 args)
1391
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001392 if self._unpack_only:
1393 # Unpack pool only required on unpack_only jobs.
1394 self._unpack_queue = multiprocessing.Queue()
1395 args = (self._unpack_queue, self._job_queue, emerge, package_db, False,
1396 True)
1397 self._unpack_pool = multiprocessing.Pool(self._unpack_procs, EmergeWorker,
1398 args)
1399
David Jamesfcb70ef2011-02-02 16:02:30 -08001400 self._print_worker = multiprocessing.Process(target=PrintWorker,
1401 args=[self._print_queue])
1402 self._print_worker.start()
1403
1404 # Initialize the failed queue to empty.
1405 self._retry_queue = []
Bertrand SIMONNET411945d2015-05-20 17:23:28 -07001406 self._failed_count = dict()
David Jamesfcb70ef2011-02-02 16:02:30 -08001407
David Jamesfcb70ef2011-02-02 16:02:30 -08001408 # Setup an exit handler so that we print nice messages if we are
1409 # terminated.
1410 self._SetupExitHandler()
1411
1412 # Schedule our jobs.
Brian Harring0be85c62012-03-17 19:52:12 -07001413 self._state_map.update(
1414 (pkg, TargetState(pkg, data)) for pkg, data in deps_map.iteritems())
1415 self._fetch_ready.multi_put(self._state_map.itervalues())
David Jamesfcb70ef2011-02-02 16:02:30 -08001416
David Jamesaaf49e42014-04-24 09:40:05 -07001417 def _SetupSession(self):
1418 """Set up a session so we can easily terminate all children."""
1419 # When we call os.setsid(), this sets up a session / process group for this
1420 # process and all children. These session groups are needed so that we can
1421 # easily kill all children (including processes launched by emerge) before
1422 # we exit.
1423 #
1424 # One unfortunate side effect of os.setsid() is that it blocks CTRL-C from
1425 # being received. To work around this, we only call os.setsid() in a forked
1426 # process, so that the parent can still watch for CTRL-C. The parent will
1427 # just sit around, watching for signals and propagating them to the child,
1428 # until the child exits.
1429 #
1430 # TODO(davidjames): It would be nice if we could replace this with cgroups.
1431 pid = os.fork()
1432 if pid == 0:
1433 os.setsid()
1434 else:
Mike Frysingerd74fe4a2014-04-24 11:43:38 -04001435 proctitle.settitle('SessionManager')
1436
David Jamesaaf49e42014-04-24 09:40:05 -07001437 def PropagateToChildren(signum, _frame):
1438 # Just propagate the signals down to the child. We'll exit when the
1439 # child does.
1440 try:
1441 os.kill(pid, signum)
1442 except OSError as ex:
1443 if ex.errno != errno.ESRCH:
1444 raise
1445 signal.signal(signal.SIGINT, PropagateToChildren)
1446 signal.signal(signal.SIGTERM, PropagateToChildren)
1447
1448 def StopGroup(_signum, _frame):
1449 # When we get stopped, stop the children.
1450 try:
1451 os.killpg(pid, signal.SIGSTOP)
1452 os.kill(0, signal.SIGSTOP)
1453 except OSError as ex:
1454 if ex.errno != errno.ESRCH:
1455 raise
1456 signal.signal(signal.SIGTSTP, StopGroup)
1457
1458 def ContinueGroup(_signum, _frame):
1459 # Launch the children again after being stopped.
1460 try:
1461 os.killpg(pid, signal.SIGCONT)
1462 except OSError as ex:
1463 if ex.errno != errno.ESRCH:
1464 raise
1465 signal.signal(signal.SIGCONT, ContinueGroup)
1466
1467 # Loop until the children exit. We exit with os._exit to be sure we
1468 # don't run any finalizers (those will be run by the child process.)
1469 # pylint: disable=W0212
1470 while True:
1471 try:
1472 # Wait for the process to exit. When it does, exit with the return
1473 # value of the subprocess.
Mike Frysingere2d8f0d2014-11-01 13:09:26 -04001474 os._exit(process_util.GetExitStatus(os.waitpid(pid, 0)[1]))
David Jamesaaf49e42014-04-24 09:40:05 -07001475 except OSError as ex:
1476 if ex.errno == errno.EINTR:
1477 continue
1478 traceback.print_exc()
1479 os._exit(1)
1480 except BaseException:
1481 traceback.print_exc()
1482 os._exit(1)
1483
David Jamesfcb70ef2011-02-02 16:02:30 -08001484 def _SetupExitHandler(self):
1485
David James321490a2012-12-17 12:05:56 -08001486 def ExitHandler(signum, _frame):
David James7358d032011-05-19 10:40:03 -07001487 # Set KILLED flag.
1488 KILLED.set()
David Jamesfcb70ef2011-02-02 16:02:30 -08001489
1490 # Kill our signal handlers so we don't get called recursively
David James7358d032011-05-19 10:40:03 -07001491 signal.signal(signal.SIGINT, KillHandler)
1492 signal.signal(signal.SIGTERM, KillHandler)
David Jamesfcb70ef2011-02-02 16:02:30 -08001493
1494 # Print our current job status
Brian Harring0be85c62012-03-17 19:52:12 -07001495 for job in self._build_jobs.itervalues():
David Jamesfcb70ef2011-02-02 16:02:30 -08001496 if job:
1497 self._print_queue.put(JobPrinter(job, unlink=True))
1498
1499 # Notify the user that we are exiting
1500 self._Print("Exiting on signal %s" % signum)
David James7358d032011-05-19 10:40:03 -07001501 self._print_queue.put(None)
1502 self._print_worker.join()
David Jamesfcb70ef2011-02-02 16:02:30 -08001503
1504 # Kill child threads, then exit.
David James7358d032011-05-19 10:40:03 -07001505 os.killpg(0, signal.SIGKILL)
David Jamesfcb70ef2011-02-02 16:02:30 -08001506 sys.exit(1)
1507
1508 # Print out job status when we are killed
1509 signal.signal(signal.SIGINT, ExitHandler)
1510 signal.signal(signal.SIGTERM, ExitHandler)
1511
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001512 def _ScheduleUnpack(self, pkg_state):
1513 self._unpack_jobs[pkg_state.target] = None
1514 self._unpack_queue.put(pkg_state)
1515
Brian Harring0be85c62012-03-17 19:52:12 -07001516 def _Schedule(self, pkg_state):
David Jamesfcb70ef2011-02-02 16:02:30 -08001517 # We maintain a tree of all deps, if this doesn't need
David James8c7e5e32011-06-28 11:26:03 -07001518 # to be installed just free up its children and continue.
David Jamesfcb70ef2011-02-02 16:02:30 -08001519 # It is possible to reinstall deps of deps, without reinstalling
1520 # first level deps, like so:
Mike Frysingerfd969312014-04-02 22:16:42 -04001521 # virtual/target-os (merge) -> eselect (nomerge) -> python (merge)
Brian Harring0be85c62012-03-17 19:52:12 -07001522 this_pkg = pkg_state.info
1523 target = pkg_state.target
1524 if pkg_state.info is not None:
1525 if this_pkg["action"] == "nomerge":
1526 self._Finish(target)
1527 elif target not in self._build_jobs:
1528 # Kick off the build if it's marked to be built.
1529 self._build_jobs[target] = None
1530 self._build_queue.put(pkg_state)
1531 return True
David Jamesfcb70ef2011-02-02 16:02:30 -08001532
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001533 def _ScheduleLoop(self, unpack_only=False):
1534 if unpack_only:
1535 ready_queue = self._unpack_ready
1536 jobs_queue = self._unpack_jobs
1537 procs = self._unpack_procs
1538 else:
1539 ready_queue = self._build_ready
1540 jobs_queue = self._build_jobs
1541 procs = self._build_procs
1542
David James8c7e5e32011-06-28 11:26:03 -07001543 # If the current load exceeds our desired load average, don't schedule
1544 # more than one job.
1545 if self._load_avg and os.getloadavg()[0] > self._load_avg:
1546 needed_jobs = 1
1547 else:
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001548 needed_jobs = procs
David James8c7e5e32011-06-28 11:26:03 -07001549
1550 # Schedule more jobs.
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001551 while ready_queue and len(jobs_queue) < needed_jobs:
1552 state = ready_queue.get()
1553 if unpack_only:
1554 self._ScheduleUnpack(state)
1555 else:
Bertrand SIMONNET411945d2015-05-20 17:23:28 -07001556 if state.target not in self._failed_count:
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001557 self._Schedule(state)
David Jamesfcb70ef2011-02-02 16:02:30 -08001558
1559 def _Print(self, line):
1560 """Print a single line."""
1561 self._print_queue.put(LinePrinter(line))
1562
1563 def _Status(self):
1564 """Print status."""
1565 current_time = time.time()
Aviv Keshet3b381682015-11-12 13:15:06 -08001566 current_time_struct = time.localtime(current_time)
David Jamesfcb70ef2011-02-02 16:02:30 -08001567 no_output = True
1568
1569 # Print interim output every minute if --show-output is used. Otherwise,
1570 # print notifications about running packages every 2 minutes, and print
1571 # full output for jobs that have been running for 60 minutes or more.
1572 if self._show_output:
1573 interval = 60
1574 notify_interval = 0
1575 else:
1576 interval = 60 * 60
1577 notify_interval = 60 * 2
David James321490a2012-12-17 12:05:56 -08001578 for job in self._build_jobs.itervalues():
David Jamesfcb70ef2011-02-02 16:02:30 -08001579 if job:
1580 last_timestamp = max(job.start_timestamp, job.last_output_timestamp)
1581 if last_timestamp + interval < current_time:
1582 self._print_queue.put(JobPrinter(job))
1583 job.last_output_timestamp = current_time
1584 no_output = False
1585 elif (notify_interval and
1586 job.last_notify_timestamp + notify_interval < current_time):
1587 job_seconds = current_time - job.start_timestamp
1588 args = (job.pkgname, job_seconds / 60, job_seconds % 60, job.filename)
1589 info = "Still building %s (%dm%.1fs). Logs in %s" % args
1590 job.last_notify_timestamp = current_time
1591 self._Print(info)
1592 no_output = False
1593
1594 # If we haven't printed any messages yet, print a general status message
1595 # here.
1596 if no_output:
1597 seconds = current_time - GLOBAL_START
Brian Harring0be85c62012-03-17 19:52:12 -07001598 fjobs, fready = len(self._fetch_jobs), len(self._fetch_ready)
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001599 ujobs, uready = len(self._unpack_jobs), len(self._unpack_ready)
Brian Harring0be85c62012-03-17 19:52:12 -07001600 bjobs, bready = len(self._build_jobs), len(self._build_ready)
1601 retries = len(self._retry_queue)
1602 pending = max(0, len(self._deps_map) - fjobs - bjobs)
1603 line = "Pending %s/%s, " % (pending, self._total_jobs)
1604 if fjobs or fready:
1605 line += "Fetching %s/%s, " % (fjobs, fready + fjobs)
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001606 if ujobs or uready:
1607 line += "Unpacking %s/%s, " % (ujobs, uready + ujobs)
Brian Harring0be85c62012-03-17 19:52:12 -07001608 if bjobs or bready or retries:
1609 line += "Building %s/%s, " % (bjobs, bready + bjobs)
1610 if retries:
1611 line += "Retrying %s, " % (retries,)
Mike Frysingerd6e2df02014-11-26 02:55:04 -05001612 load = " ".join(str(x) for x in os.getloadavg())
Aviv Keshet3b381682015-11-12 13:15:06 -08001613 line += ("[Time %s | Elapsed %dm%.1fs | Load %s]" % (
1614 time.strftime('%H:%M:%S', current_time_struct), seconds / 60,
1615 seconds % 60, load))
Brian Harring0be85c62012-03-17 19:52:12 -07001616 self._Print(line)
David Jamesfcb70ef2011-02-02 16:02:30 -08001617
1618 def _Finish(self, target):
David James8c7e5e32011-06-28 11:26:03 -07001619 """Mark a target as completed and unblock dependencies."""
1620 this_pkg = self._deps_map[target]
1621 if this_pkg["needs"] and this_pkg["nodeps"]:
1622 # We got installed, but our deps have not been installed yet. Dependent
1623 # packages should only be installed when our needs have been fully met.
1624 this_pkg["action"] = "nomerge"
1625 else:
David James8c7e5e32011-06-28 11:26:03 -07001626 for dep in this_pkg["provides"]:
1627 dep_pkg = self._deps_map[dep]
Brian Harring0be85c62012-03-17 19:52:12 -07001628 state = self._state_map[dep]
David James8c7e5e32011-06-28 11:26:03 -07001629 del dep_pkg["needs"][target]
Brian Harring0be85c62012-03-17 19:52:12 -07001630 state.update_score()
1631 if not state.prefetched:
1632 if dep in self._fetch_ready:
1633 # If it's not currently being fetched, update the prioritization
1634 self._fetch_ready.sort()
1635 elif not dep_pkg["needs"]:
David James8c7e5e32011-06-28 11:26:03 -07001636 if dep_pkg["nodeps"] and dep_pkg["action"] == "nomerge":
1637 self._Finish(dep)
1638 else:
Brian Harring0be85c62012-03-17 19:52:12 -07001639 self._build_ready.put(self._state_map[dep])
David James8c7e5e32011-06-28 11:26:03 -07001640 self._deps_map.pop(target)
David Jamesfcb70ef2011-02-02 16:02:30 -08001641
1642 def _Retry(self):
David James8c7e5e32011-06-28 11:26:03 -07001643 while self._retry_queue:
Brian Harring0be85c62012-03-17 19:52:12 -07001644 state = self._retry_queue.pop(0)
1645 if self._Schedule(state):
1646 self._Print("Retrying emerge of %s." % state.target)
David James8c7e5e32011-06-28 11:26:03 -07001647 break
David Jamesfcb70ef2011-02-02 16:02:30 -08001648
Brian Harringa43f5952012-04-12 01:19:34 -07001649 def _Shutdown(self):
David Jamesfcb70ef2011-02-02 16:02:30 -08001650 # Tell emerge workers to exit. They all exit when 'None' is pushed
1651 # to the queue.
Brian Harring0be85c62012-03-17 19:52:12 -07001652
Brian Harringa43f5952012-04-12 01:19:34 -07001653 # Shutdown the workers first; then jobs (which is how they feed things back)
1654 # then finally the print queue.
Brian Harring0be85c62012-03-17 19:52:12 -07001655
Brian Harringa43f5952012-04-12 01:19:34 -07001656 def _stop(queue, pool):
1657 if pool is None:
1658 return
1659 try:
1660 queue.put(None)
1661 pool.close()
1662 pool.join()
1663 finally:
1664 pool.terminate()
Brian Harring0be85c62012-03-17 19:52:12 -07001665
Brian Harringa43f5952012-04-12 01:19:34 -07001666 _stop(self._fetch_queue, self._fetch_pool)
1667 self._fetch_queue = self._fetch_pool = None
Brian Harring0be85c62012-03-17 19:52:12 -07001668
Brian Harringa43f5952012-04-12 01:19:34 -07001669 _stop(self._build_queue, self._build_pool)
1670 self._build_queue = self._build_pool = None
1671
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001672 if self._unpack_only:
1673 _stop(self._unpack_queue, self._unpack_pool)
1674 self._unpack_queue = self._unpack_pool = None
1675
Brian Harringa43f5952012-04-12 01:19:34 -07001676 if self._job_queue is not None:
1677 self._job_queue.close()
1678 self._job_queue = None
David Jamesfcb70ef2011-02-02 16:02:30 -08001679
1680 # Now that our workers are finished, we can kill the print queue.
Brian Harringa43f5952012-04-12 01:19:34 -07001681 if self._print_worker is not None:
1682 try:
1683 self._print_queue.put(None)
1684 self._print_queue.close()
1685 self._print_worker.join()
1686 finally:
1687 self._print_worker.terminate()
1688 self._print_queue = self._print_worker = None
David Jamesfcb70ef2011-02-02 16:02:30 -08001689
1690 def Run(self):
1691 """Run through the scheduled ebuilds.
1692
1693 Keep running so long as we have uninstalled packages in the
1694 dependency graph to merge.
1695 """
Brian Harringa43f5952012-04-12 01:19:34 -07001696 if not self._deps_map:
1697 return
1698
Brian Harring0be85c62012-03-17 19:52:12 -07001699 # Start the fetchers.
1700 for _ in xrange(min(self._fetch_procs, len(self._fetch_ready))):
1701 state = self._fetch_ready.get()
1702 self._fetch_jobs[state.target] = None
1703 self._fetch_queue.put(state)
1704
1705 # Print an update, then get going.
1706 self._Status()
1707
David Jamesfcb70ef2011-02-02 16:02:30 -08001708 while self._deps_map:
1709 # Check here that we are actually waiting for something.
Brian Harring0be85c62012-03-17 19:52:12 -07001710 if (self._build_queue.empty() and
David Jamesfcb70ef2011-02-02 16:02:30 -08001711 self._job_queue.empty() and
Brian Harring0be85c62012-03-17 19:52:12 -07001712 not self._fetch_jobs and
1713 not self._fetch_ready and
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001714 not self._unpack_jobs and
1715 not self._unpack_ready and
Brian Harring0be85c62012-03-17 19:52:12 -07001716 not self._build_jobs and
1717 not self._build_ready and
David Jamesfcb70ef2011-02-02 16:02:30 -08001718 self._deps_map):
1719 # If we have failed on a package, retry it now.
1720 if self._retry_queue:
1721 self._Retry()
1722 else:
David Jamesfcb70ef2011-02-02 16:02:30 -08001723 # Tell the user why we're exiting.
Bertrand SIMONNET411945d2015-05-20 17:23:28 -07001724 if self._failed_count:
1725 print('Packages failed:\n\t%s' %
1726 '\n\t'.join(self._failed_count.iterkeys()))
David James0eae23e2012-07-03 15:04:25 -07001727 status_file = os.environ.get("PARALLEL_EMERGE_STATUS_FILE")
1728 if status_file:
David James321490a2012-12-17 12:05:56 -08001729 failed_pkgs = set(portage.versions.cpv_getkey(x)
Bertrand SIMONNET411945d2015-05-20 17:23:28 -07001730 for x in self._failed_count.iterkeys())
David James0eae23e2012-07-03 15:04:25 -07001731 with open(status_file, "a") as f:
1732 f.write("%s\n" % " ".join(failed_pkgs))
David Jamesfcb70ef2011-02-02 16:02:30 -08001733 else:
Mike Frysinger383367e2014-09-16 15:06:17 -04001734 print("Deadlock! Circular dependencies!")
David Jamesfcb70ef2011-02-02 16:02:30 -08001735 sys.exit(1)
1736
David James321490a2012-12-17 12:05:56 -08001737 for _ in xrange(12):
David Jamesa74289a2011-08-12 10:41:24 -07001738 try:
1739 job = self._job_queue.get(timeout=5)
1740 break
1741 except Queue.Empty:
1742 # Check if any more jobs can be scheduled.
1743 self._ScheduleLoop()
1744 else:
Brian Harring706747c2012-03-16 03:04:31 -07001745 # Print an update every 60 seconds.
David Jamesfcb70ef2011-02-02 16:02:30 -08001746 self._Status()
1747 continue
1748
1749 target = job.target
1750
Brian Harring0be85c62012-03-17 19:52:12 -07001751 if job.fetch_only:
1752 if not job.done:
1753 self._fetch_jobs[job.target] = job
1754 else:
1755 state = self._state_map[job.target]
1756 state.prefetched = True
1757 state.fetched_successfully = (job.retcode == 0)
1758 del self._fetch_jobs[job.target]
1759 self._Print("Fetched %s in %2.2fs"
1760 % (target, time.time() - job.start_timestamp))
1761
1762 if self._show_output or job.retcode != 0:
1763 self._print_queue.put(JobPrinter(job, unlink=True))
1764 else:
1765 os.unlink(job.filename)
1766 # Failure or not, let build work with it next.
1767 if not self._deps_map[job.target]["needs"]:
1768 self._build_ready.put(state)
1769 self._ScheduleLoop()
1770
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001771 if self._unpack_only and job.retcode == 0:
1772 self._unpack_ready.put(state)
1773 self._ScheduleLoop(unpack_only=True)
1774
Brian Harring0be85c62012-03-17 19:52:12 -07001775 if self._fetch_ready:
1776 state = self._fetch_ready.get()
1777 self._fetch_queue.put(state)
1778 self._fetch_jobs[state.target] = None
1779 else:
1780 # Minor optimization; shut down fetchers early since we know
1781 # the queue is empty.
1782 self._fetch_queue.put(None)
1783 continue
1784
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001785 if job.unpack_only:
1786 if not job.done:
1787 self._unpack_jobs[target] = job
1788 else:
1789 del self._unpack_jobs[target]
1790 self._Print("Unpacked %s in %2.2fs"
1791 % (target, time.time() - job.start_timestamp))
1792 if self._show_output or job.retcode != 0:
1793 self._print_queue.put(JobPrinter(job, unlink=True))
1794 else:
1795 os.unlink(job.filename)
1796 if self._unpack_ready:
1797 state = self._unpack_ready.get()
1798 self._unpack_queue.put(state)
1799 self._unpack_jobs[state.target] = None
1800 continue
1801
David Jamesfcb70ef2011-02-02 16:02:30 -08001802 if not job.done:
Brian Harring0be85c62012-03-17 19:52:12 -07001803 self._build_jobs[target] = job
David Jamesfcb70ef2011-02-02 16:02:30 -08001804 self._Print("Started %s (logged in %s)" % (target, job.filename))
1805 continue
1806
1807 # Print output of job
1808 if self._show_output or job.retcode != 0:
1809 self._print_queue.put(JobPrinter(job, unlink=True))
1810 else:
1811 os.unlink(job.filename)
Brian Harring0be85c62012-03-17 19:52:12 -07001812 del self._build_jobs[target]
David Jamesfcb70ef2011-02-02 16:02:30 -08001813
1814 seconds = time.time() - job.start_timestamp
1815 details = "%s (in %dm%.1fs)" % (target, seconds / 60, seconds % 60)
1816
1817 # Complain if necessary.
1818 if job.retcode != 0:
1819 # Handle job failure.
Bertrand SIMONNET411945d2015-05-20 17:23:28 -07001820 failed_count = self._failed_count.get(target, 0)
1821 if failed_count >= self._max_retries:
1822 # If this job has failed and can't be retried, give up.
David Jamesfcb70ef2011-02-02 16:02:30 -08001823 self._Print("Failed %s. Your build has failed." % details)
1824 else:
1825 # Queue up this build to try again after a long while.
Brian Harring0be85c62012-03-17 19:52:12 -07001826 self._retry_queue.append(self._state_map[target])
Bertrand SIMONNET411945d2015-05-20 17:23:28 -07001827 self._failed_count[target] = failed_count + 1
David Jamesfcb70ef2011-02-02 16:02:30 -08001828 self._Print("Failed %s, retrying later." % details)
1829 else:
David James32420cc2011-08-25 21:32:46 -07001830 self._Print("Completed %s" % details)
1831
1832 # Mark as completed and unblock waiting ebuilds.
1833 self._Finish(target)
1834
Bertrand SIMONNET411945d2015-05-20 17:23:28 -07001835 if target in self._failed_count and self._retry_queue:
David Jamesfcb70ef2011-02-02 16:02:30 -08001836 # If we have successfully retried a failed package, and there
1837 # are more failed packages, try the next one. We will only have
1838 # one retrying package actively running at a time.
1839 self._Retry()
1840
David Jamesfcb70ef2011-02-02 16:02:30 -08001841
David James8c7e5e32011-06-28 11:26:03 -07001842 # Schedule pending jobs and print an update.
1843 self._ScheduleLoop()
1844 self._Status()
David Jamesfcb70ef2011-02-02 16:02:30 -08001845
David Jamese703d0f2012-01-12 16:27:45 -08001846 # If packages were retried, output a warning.
Bertrand SIMONNET411945d2015-05-20 17:23:28 -07001847 if self._failed_count:
David Jamese703d0f2012-01-12 16:27:45 -08001848 self._Print("")
Bertrand SIMONNET411945d2015-05-20 17:23:28 -07001849 self._Print("WARNING: The following packages failed once or more,")
David Jamese703d0f2012-01-12 16:27:45 -08001850 self._Print("but succeeded upon retry. This might indicate incorrect")
1851 self._Print("dependencies.")
Bertrand SIMONNET411945d2015-05-20 17:23:28 -07001852 for pkg in self._failed_count.iterkeys():
David Jamese703d0f2012-01-12 16:27:45 -08001853 self._Print(" %s" % pkg)
1854 self._Print("@@@STEP_WARNINGS@@@")
1855 self._Print("")
1856
David Jamesfcb70ef2011-02-02 16:02:30 -08001857 # Tell child threads to exit.
1858 self._Print("Merge complete")
David Jamesfcb70ef2011-02-02 16:02:30 -08001859
1860
Brian Harring30675052012-02-29 12:18:22 -08001861def main(argv):
Brian Harring8294d652012-05-23 02:20:52 -07001862 try:
1863 return real_main(argv)
1864 finally:
1865 # Work around multiprocessing sucking and not cleaning up after itself.
1866 # http://bugs.python.org/issue4106;
1867 # Step one; ensure GC is ran *prior* to the VM starting shutdown.
1868 gc.collect()
1869 # Step two; go looking for those threads and try to manually reap
1870 # them if we can.
1871 for x in threading.enumerate():
1872 # Filter on the name, and ident; if ident is None, the thread
1873 # wasn't started.
1874 if x.name == 'QueueFeederThread' and x.ident is not None:
1875 x.join(1)
David Jamesfcb70ef2011-02-02 16:02:30 -08001876
Brian Harring8294d652012-05-23 02:20:52 -07001877
1878def real_main(argv):
Brian Harring30675052012-02-29 12:18:22 -08001879 parallel_emerge_args = argv[:]
David Jamesfcb70ef2011-02-02 16:02:30 -08001880 deps = DepGraphGenerator()
Brian Harring30675052012-02-29 12:18:22 -08001881 deps.Initialize(parallel_emerge_args)
David Jamesfcb70ef2011-02-02 16:02:30 -08001882 emerge = deps.emerge
1883
1884 if emerge.action is not None:
Brian Harring30675052012-02-29 12:18:22 -08001885 argv = deps.ParseParallelEmergeArgs(argv)
Brian Harring8294d652012-05-23 02:20:52 -07001886 return emerge_main(argv)
David Jamesfcb70ef2011-02-02 16:02:30 -08001887 elif not emerge.cmdline_packages:
1888 Usage()
Brian Harring8294d652012-05-23 02:20:52 -07001889 return 1
David Jamesfcb70ef2011-02-02 16:02:30 -08001890
1891 # Unless we're in pretend mode, there's not much point running without
1892 # root access. We need to be able to install packages.
1893 #
1894 # NOTE: Even if you're running --pretend, it's a good idea to run
1895 # parallel_emerge with root access so that portage can write to the
1896 # dependency cache. This is important for performance.
David James321490a2012-12-17 12:05:56 -08001897 if "--pretend" not in emerge.opts and portage.data.secpass < 2:
Mike Frysinger383367e2014-09-16 15:06:17 -04001898 print("parallel_emerge: superuser access is required.")
Brian Harring8294d652012-05-23 02:20:52 -07001899 return 1
David Jamesfcb70ef2011-02-02 16:02:30 -08001900
1901 if "--quiet" not in emerge.opts:
1902 cmdline_packages = " ".join(emerge.cmdline_packages)
Mike Frysinger383367e2014-09-16 15:06:17 -04001903 print("Starting fast-emerge.")
1904 print(" Building package %s on %s" % (cmdline_packages,
Gilad Arnold05f94b02015-05-22 10:41:05 -07001905 deps.sysroot or "root"))
David Jamesfcb70ef2011-02-02 16:02:30 -08001906
David James386ccd12011-05-04 20:17:42 -07001907 deps_tree, deps_info = deps.GenDependencyTree()
David Jamesfcb70ef2011-02-02 16:02:30 -08001908
1909 # You want me to be verbose? I'll give you two trees! Twice as much value.
1910 if "--tree" in emerge.opts and "--verbose" in emerge.opts:
1911 deps.PrintTree(deps_tree)
1912
David James386ccd12011-05-04 20:17:42 -07001913 deps_graph = deps.GenDependencyGraph(deps_tree, deps_info)
David Jamesfcb70ef2011-02-02 16:02:30 -08001914
1915 # OK, time to print out our progress so far.
1916 deps.PrintInstallPlan(deps_graph)
1917 if "--tree" in emerge.opts:
1918 PrintDepsMap(deps_graph)
1919
1920 # Are we upgrading portage? If so, and there are more packages to merge,
1921 # schedule a restart of parallel_emerge to merge the rest. This ensures that
1922 # we pick up all updates to portage settings before merging any more
1923 # packages.
1924 portage_upgrade = False
1925 root = emerge.settings["ROOT"]
Don Garrett25f309a2014-03-19 14:02:12 -07001926 # pylint: disable=W0212
David Jamesfcb70ef2011-02-02 16:02:30 -08001927 if root == "/":
Gilad Arnoldcead28a2015-05-22 10:45:02 -07001928 final_db = emerge.depgraph._dynamic_config._filtered_trees[root]['graph_db']
Mike Frysinger3fb56ef2018-01-05 19:00:04 -05001929 for db_pkg in final_db.cp_list("sys-apps/portage"):
David Jamesfcb70ef2011-02-02 16:02:30 -08001930 portage_pkg = deps_graph.get(db_pkg.cpv)
David James0ff16f22012-11-02 14:18:07 -07001931 if portage_pkg:
David Jamesfcb70ef2011-02-02 16:02:30 -08001932 portage_upgrade = True
1933 if "--quiet" not in emerge.opts:
Mike Frysinger383367e2014-09-16 15:06:17 -04001934 print("Upgrading portage first, then restarting...")
David Jamesfcb70ef2011-02-02 16:02:30 -08001935
David James0ff16f22012-11-02 14:18:07 -07001936 # Upgrade Portage first, then the rest of the packages.
1937 #
1938 # In order to grant the child permission to run setsid, we need to run sudo
1939 # again. We preserve SUDO_USER here in case an ebuild depends on it.
1940 if portage_upgrade:
1941 # Calculate what arguments to use when re-invoking.
1942 args = ["sudo", "-E", "SUDO_USER=%s" % os.environ.get("SUDO_USER", "")]
1943 args += [os.path.abspath(sys.argv[0])] + parallel_emerge_args
1944 args += ["--exclude=sys-apps/portage"]
1945
1946 # First upgrade Portage.
1947 passthrough_args = ("--quiet", "--pretend", "--verbose")
1948 emerge_args = [k for k in emerge.opts if k in passthrough_args]
1949 ret = emerge_main(emerge_args + ["portage"])
1950 if ret != 0:
1951 return ret
1952
1953 # Now upgrade the rest.
1954 os.execvp(args[0], args)
1955
Bertrand SIMONNETc03c8ee2014-12-10 17:02:55 -08001956 # Attempt to solve crbug.com/433482
1957 # The file descriptor error appears only when getting userpriv_groups
1958 # (lazily generated). Loading userpriv_groups here will reduce the number of
1959 # calls from few hundreds to one.
1960 portage.data._get_global('userpriv_groups')
1961
David Jamesfcb70ef2011-02-02 16:02:30 -08001962 # Run the queued emerges.
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001963 scheduler = EmergeQueue(deps_graph, emerge, deps.package_db, deps.show_output,
Bertrand SIMONNET411945d2015-05-20 17:23:28 -07001964 deps.unpack_only, deps.max_retries)
Brian Harringa43f5952012-04-12 01:19:34 -07001965 try:
1966 scheduler.Run()
1967 finally:
Don Garrett25f309a2014-03-19 14:02:12 -07001968 # pylint: disable=W0212
Brian Harringa43f5952012-04-12 01:19:34 -07001969 scheduler._Shutdown()
David James97ce8902011-08-16 09:51:05 -07001970 scheduler = None
David Jamesfcb70ef2011-02-02 16:02:30 -08001971
Mike Frysingerd20a6e22012-10-04 19:01:10 -04001972 clean_logs(emerge.settings)
1973
Mike Frysinger383367e2014-09-16 15:06:17 -04001974 print("Done")
Brian Harring8294d652012-05-23 02:20:52 -07001975 return 0