blob: 27e9d3d3469524e885a62d195c9bf99e6b566884 [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.
Mike Frysinger27e21b72018-07-12 14:20:21 -040031 # pylint: disable=import-error
Mike Frysinger1ae28092013-10-17 17:17:22 -040032 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.
Mike Frysinger27e21b72018-07-12 14:20:21 -040080# pylint: disable=import-error
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
Mike Frysinger27e21b72018-07-12 14:20:21 -040094# pylint: enable=import-error
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",
Gregory Meinke68de2412018-08-30 07:15:54 -0600231 "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
Chris McDonaldb6fa8122019-07-08 10:45:56 -0600240 self.max_retries = int(os.environ.get('PARALLEL_EMERGE_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 = {}
Mike Frysinger27e21b72018-07-12 14:20:21 -0400482 # pylint: disable=protected-access
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
Mike Frysingerdcad4e02018-08-03 16:20:02 -0400655 # safely ignore dependencies. See https://crbug.com/202428.
David James3f778802011-08-25 19:31:45 -0700656 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 Frysinger27e21b72018-07-12 14:20:21 -0400980 # pylint: disable=protected-access
Mike Frysinger66652ec2014-04-24 11:42:25 -0400981 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().
Mike Frysinger27e21b72018-07-12 14:20:21 -04001003 # pylint: disable=bare-except
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()
Mike Frysinger27e21b72018-07-12 14:20:21 -04001010 # pylint: disable=protected-access
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.
Mike Frysinger27e21b72018-07-12 14:20:21 -04001090 # pylint: disable=protected-access
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)
Mike Frysinger0444f4c2018-08-03 15:12:46 -04001129 os.chmod(output.name, 0o644)
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
Mike Frysinger28f76682019-07-02 17:54:38 -04001281 def __lt__(self, other):
1282 return self.score < other.score
1283
1284 def __le__(self, other):
1285 return self.score <= other.score
1286
1287 def __eq__(self, other):
1288 return self.score == other.score
1289
1290 def __ne__(self, other):
1291 return self.score != other.score
1292
1293 def __gt__(self, other):
1294 return self.score > other.score
1295
1296 def __ge__(self, other):
1297 return self.score >= other.score
Brian Harring867e2362012-03-17 04:05:17 -07001298
1299 def update_score(self):
1300 self.score = (
1301 -len(self.info["tprovides"]),
Brian Harring0be85c62012-03-17 19:52:12 -07001302 len(self.info["needs"]),
Brian Harring11c5eeb2012-03-18 11:02:39 -07001303 not self.info["binary"],
Brian Harring867e2362012-03-17 04:05:17 -07001304 -len(self.info["provides"]),
1305 self.info["idx"],
1306 self.target,
1307 )
1308
1309
1310class ScoredHeap(object):
Don Garrett25f309a2014-03-19 14:02:12 -07001311 """Implementation of a general purpose scored heap."""
Brian Harring867e2362012-03-17 04:05:17 -07001312
Brian Harring0be85c62012-03-17 19:52:12 -07001313 __slots__ = ("heap", "_heap_set")
1314
Brian Harring867e2362012-03-17 04:05:17 -07001315 def __init__(self, initial=()):
Brian Harring0be85c62012-03-17 19:52:12 -07001316 self.heap = list()
1317 self._heap_set = set()
1318 if initial:
1319 self.multi_put(initial)
Brian Harring867e2362012-03-17 04:05:17 -07001320
1321 def get(self):
Brian Harring0be85c62012-03-17 19:52:12 -07001322 item = heapq.heappop(self.heap)
1323 self._heap_set.remove(item.target)
1324 return item
Brian Harring867e2362012-03-17 04:05:17 -07001325
Brian Harring0be85c62012-03-17 19:52:12 -07001326 def put(self, item):
1327 if not isinstance(item, TargetState):
1328 raise ValueError("Item %r isn't a TargetState" % (item,))
1329 heapq.heappush(self.heap, item)
1330 self._heap_set.add(item.target)
Brian Harring867e2362012-03-17 04:05:17 -07001331
Brian Harring0be85c62012-03-17 19:52:12 -07001332 def multi_put(self, sequence):
1333 sequence = list(sequence)
1334 self.heap.extend(sequence)
1335 self._heap_set.update(x.target for x in sequence)
Brian Harring867e2362012-03-17 04:05:17 -07001336 self.sort()
1337
David James5c9996d2012-03-24 10:50:46 -07001338 def sort(self):
1339 heapq.heapify(self.heap)
1340
Brian Harring0be85c62012-03-17 19:52:12 -07001341 def __contains__(self, target):
1342 return target in self._heap_set
1343
Mike Frysinger31517e72019-07-03 16:29:56 -04001344 def __bool__(self):
Brian Harring0be85c62012-03-17 19:52:12 -07001345 return bool(self.heap)
1346
Mike Frysinger31517e72019-07-03 16:29:56 -04001347 # Python 2 glue.
1348 __nonzero__ = __bool__
1349
Brian Harring867e2362012-03-17 04:05:17 -07001350 def __len__(self):
1351 return len(self.heap)
1352
1353
David Jamesfcb70ef2011-02-02 16:02:30 -08001354class EmergeQueue(object):
1355 """Class to schedule emerge jobs according to a dependency graph."""
1356
Bertrand SIMONNET411945d2015-05-20 17:23:28 -07001357 def __init__(self, deps_map, emerge, package_db, show_output, unpack_only,
1358 max_retries):
David Jamesfcb70ef2011-02-02 16:02:30 -08001359 # Store the dependency graph.
1360 self._deps_map = deps_map
Brian Harring0be85c62012-03-17 19:52:12 -07001361 self._state_map = {}
David Jamesfcb70ef2011-02-02 16:02:30 -08001362 # Initialize the running queue to empty
Brian Harring0be85c62012-03-17 19:52:12 -07001363 self._build_jobs = {}
1364 self._build_ready = ScoredHeap()
1365 self._fetch_jobs = {}
1366 self._fetch_ready = ScoredHeap()
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001367 self._unpack_jobs = {}
1368 self._unpack_ready = ScoredHeap()
David Jamesfcb70ef2011-02-02 16:02:30 -08001369 # List of total package installs represented in deps_map.
1370 install_jobs = [x for x in deps_map if deps_map[x]["action"] == "merge"]
1371 self._total_jobs = len(install_jobs)
1372 self._show_output = show_output
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001373 self._unpack_only = unpack_only
Bertrand SIMONNET411945d2015-05-20 17:23:28 -07001374 self._max_retries = max_retries
David Jamesfcb70ef2011-02-02 16:02:30 -08001375
1376 if "--pretend" in emerge.opts:
Mike Frysinger383367e2014-09-16 15:06:17 -04001377 print("Skipping merge because of --pretend mode.")
David Jamesfcb70ef2011-02-02 16:02:30 -08001378 sys.exit(0)
1379
David Jamesaaf49e42014-04-24 09:40:05 -07001380 # Set up a session so we can easily terminate all children.
1381 self._SetupSession()
David James7358d032011-05-19 10:40:03 -07001382
David Jamesfcb70ef2011-02-02 16:02:30 -08001383 # Setup scheduler graph object. This is used by the child processes
1384 # to help schedule jobs.
1385 emerge.scheduler_graph = emerge.depgraph.schedulerGraph()
1386
1387 # Calculate how many jobs we can run in parallel. We don't want to pass
1388 # the --jobs flag over to emerge itself, because that'll tell emerge to
1389 # hide its output, and said output is quite useful for debugging hung
1390 # jobs.
1391 procs = min(self._total_jobs,
1392 emerge.opts.pop("--jobs", multiprocessing.cpu_count()))
Nam T. Nguyenf7098b32015-05-08 11:04:48 -07001393 self._build_procs = self._unpack_procs = max(1, procs)
1394 # Fetch is IO bound, we can use more processes.
1395 self._fetch_procs = max(4, procs)
David James8c7e5e32011-06-28 11:26:03 -07001396 self._load_avg = emerge.opts.pop("--load-average", None)
David Jamesfcb70ef2011-02-02 16:02:30 -08001397 self._job_queue = multiprocessing.Queue()
1398 self._print_queue = multiprocessing.Queue()
Brian Harring0be85c62012-03-17 19:52:12 -07001399
Allen Webb6ecafca2018-12-13 11:29:11 -08001400 # Portage 2.3.49 spawns a process to find the default value of _lock_fn
1401 # which isn't allowed inside a Pool() worker process because they are
1402 # marked as daemons which are not allowed to have children. This fix makes
1403 # sure the default value is set prior to spawning the children.
1404 try:
1405 portage.locks._get_lock_fn()
1406 except AttributeError:
1407 pass
1408
Brian Harring0be85c62012-03-17 19:52:12 -07001409 self._fetch_queue = multiprocessing.Queue()
1410 args = (self._fetch_queue, self._job_queue, emerge, package_db, True)
1411 self._fetch_pool = multiprocessing.Pool(self._fetch_procs, EmergeWorker,
1412 args)
1413
1414 self._build_queue = multiprocessing.Queue()
1415 args = (self._build_queue, self._job_queue, emerge, package_db)
1416 self._build_pool = multiprocessing.Pool(self._build_procs, EmergeWorker,
1417 args)
1418
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001419 if self._unpack_only:
1420 # Unpack pool only required on unpack_only jobs.
1421 self._unpack_queue = multiprocessing.Queue()
1422 args = (self._unpack_queue, self._job_queue, emerge, package_db, False,
1423 True)
1424 self._unpack_pool = multiprocessing.Pool(self._unpack_procs, EmergeWorker,
1425 args)
1426
David Jamesfcb70ef2011-02-02 16:02:30 -08001427 self._print_worker = multiprocessing.Process(target=PrintWorker,
1428 args=[self._print_queue])
1429 self._print_worker.start()
1430
1431 # Initialize the failed queue to empty.
1432 self._retry_queue = []
Bertrand SIMONNET411945d2015-05-20 17:23:28 -07001433 self._failed_count = dict()
David Jamesfcb70ef2011-02-02 16:02:30 -08001434
David Jamesfcb70ef2011-02-02 16:02:30 -08001435 # Setup an exit handler so that we print nice messages if we are
1436 # terminated.
1437 self._SetupExitHandler()
1438
1439 # Schedule our jobs.
Brian Harring0be85c62012-03-17 19:52:12 -07001440 self._state_map.update(
1441 (pkg, TargetState(pkg, data)) for pkg, data in deps_map.iteritems())
1442 self._fetch_ready.multi_put(self._state_map.itervalues())
David Jamesfcb70ef2011-02-02 16:02:30 -08001443
David Jamesaaf49e42014-04-24 09:40:05 -07001444 def _SetupSession(self):
1445 """Set up a session so we can easily terminate all children."""
1446 # When we call os.setsid(), this sets up a session / process group for this
1447 # process and all children. These session groups are needed so that we can
1448 # easily kill all children (including processes launched by emerge) before
1449 # we exit.
1450 #
1451 # One unfortunate side effect of os.setsid() is that it blocks CTRL-C from
1452 # being received. To work around this, we only call os.setsid() in a forked
1453 # process, so that the parent can still watch for CTRL-C. The parent will
1454 # just sit around, watching for signals and propagating them to the child,
1455 # until the child exits.
1456 #
1457 # TODO(davidjames): It would be nice if we could replace this with cgroups.
1458 pid = os.fork()
1459 if pid == 0:
1460 os.setsid()
1461 else:
Mike Frysingerd74fe4a2014-04-24 11:43:38 -04001462 proctitle.settitle('SessionManager')
1463
David Jamesaaf49e42014-04-24 09:40:05 -07001464 def PropagateToChildren(signum, _frame):
1465 # Just propagate the signals down to the child. We'll exit when the
1466 # child does.
1467 try:
1468 os.kill(pid, signum)
1469 except OSError as ex:
1470 if ex.errno != errno.ESRCH:
1471 raise
1472 signal.signal(signal.SIGINT, PropagateToChildren)
1473 signal.signal(signal.SIGTERM, PropagateToChildren)
1474
1475 def StopGroup(_signum, _frame):
1476 # When we get stopped, stop the children.
1477 try:
1478 os.killpg(pid, signal.SIGSTOP)
1479 os.kill(0, signal.SIGSTOP)
1480 except OSError as ex:
1481 if ex.errno != errno.ESRCH:
1482 raise
1483 signal.signal(signal.SIGTSTP, StopGroup)
1484
1485 def ContinueGroup(_signum, _frame):
1486 # Launch the children again after being stopped.
1487 try:
1488 os.killpg(pid, signal.SIGCONT)
1489 except OSError as ex:
1490 if ex.errno != errno.ESRCH:
1491 raise
1492 signal.signal(signal.SIGCONT, ContinueGroup)
1493
1494 # Loop until the children exit. We exit with os._exit to be sure we
1495 # don't run any finalizers (those will be run by the child process.)
Mike Frysinger27e21b72018-07-12 14:20:21 -04001496 # pylint: disable=protected-access
David Jamesaaf49e42014-04-24 09:40:05 -07001497 while True:
1498 try:
1499 # Wait for the process to exit. When it does, exit with the return
1500 # value of the subprocess.
Mike Frysingere2d8f0d2014-11-01 13:09:26 -04001501 os._exit(process_util.GetExitStatus(os.waitpid(pid, 0)[1]))
David Jamesaaf49e42014-04-24 09:40:05 -07001502 except OSError as ex:
1503 if ex.errno == errno.EINTR:
1504 continue
1505 traceback.print_exc()
1506 os._exit(1)
1507 except BaseException:
1508 traceback.print_exc()
1509 os._exit(1)
1510
David Jamesfcb70ef2011-02-02 16:02:30 -08001511 def _SetupExitHandler(self):
1512
David James321490a2012-12-17 12:05:56 -08001513 def ExitHandler(signum, _frame):
David James7358d032011-05-19 10:40:03 -07001514 # Set KILLED flag.
1515 KILLED.set()
David Jamesfcb70ef2011-02-02 16:02:30 -08001516
1517 # Kill our signal handlers so we don't get called recursively
David James7358d032011-05-19 10:40:03 -07001518 signal.signal(signal.SIGINT, KillHandler)
1519 signal.signal(signal.SIGTERM, KillHandler)
David Jamesfcb70ef2011-02-02 16:02:30 -08001520
1521 # Print our current job status
Brian Harring0be85c62012-03-17 19:52:12 -07001522 for job in self._build_jobs.itervalues():
David Jamesfcb70ef2011-02-02 16:02:30 -08001523 if job:
1524 self._print_queue.put(JobPrinter(job, unlink=True))
1525
1526 # Notify the user that we are exiting
1527 self._Print("Exiting on signal %s" % signum)
David James7358d032011-05-19 10:40:03 -07001528 self._print_queue.put(None)
1529 self._print_worker.join()
David Jamesfcb70ef2011-02-02 16:02:30 -08001530
1531 # Kill child threads, then exit.
David James7358d032011-05-19 10:40:03 -07001532 os.killpg(0, signal.SIGKILL)
David Jamesfcb70ef2011-02-02 16:02:30 -08001533 sys.exit(1)
1534
1535 # Print out job status when we are killed
1536 signal.signal(signal.SIGINT, ExitHandler)
1537 signal.signal(signal.SIGTERM, ExitHandler)
1538
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001539 def _ScheduleUnpack(self, pkg_state):
1540 self._unpack_jobs[pkg_state.target] = None
1541 self._unpack_queue.put(pkg_state)
1542
Brian Harring0be85c62012-03-17 19:52:12 -07001543 def _Schedule(self, pkg_state):
David Jamesfcb70ef2011-02-02 16:02:30 -08001544 # We maintain a tree of all deps, if this doesn't need
David James8c7e5e32011-06-28 11:26:03 -07001545 # to be installed just free up its children and continue.
David Jamesfcb70ef2011-02-02 16:02:30 -08001546 # It is possible to reinstall deps of deps, without reinstalling
1547 # first level deps, like so:
Mike Frysingerfd969312014-04-02 22:16:42 -04001548 # virtual/target-os (merge) -> eselect (nomerge) -> python (merge)
Brian Harring0be85c62012-03-17 19:52:12 -07001549 this_pkg = pkg_state.info
1550 target = pkg_state.target
1551 if pkg_state.info is not None:
1552 if this_pkg["action"] == "nomerge":
1553 self._Finish(target)
1554 elif target not in self._build_jobs:
1555 # Kick off the build if it's marked to be built.
1556 self._build_jobs[target] = None
1557 self._build_queue.put(pkg_state)
1558 return True
David Jamesfcb70ef2011-02-02 16:02:30 -08001559
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001560 def _ScheduleLoop(self, unpack_only=False):
1561 if unpack_only:
1562 ready_queue = self._unpack_ready
1563 jobs_queue = self._unpack_jobs
1564 procs = self._unpack_procs
1565 else:
1566 ready_queue = self._build_ready
1567 jobs_queue = self._build_jobs
1568 procs = self._build_procs
1569
David James8c7e5e32011-06-28 11:26:03 -07001570 # If the current load exceeds our desired load average, don't schedule
1571 # more than one job.
1572 if self._load_avg and os.getloadavg()[0] > self._load_avg:
1573 needed_jobs = 1
1574 else:
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001575 needed_jobs = procs
David James8c7e5e32011-06-28 11:26:03 -07001576
1577 # Schedule more jobs.
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001578 while ready_queue and len(jobs_queue) < needed_jobs:
1579 state = ready_queue.get()
1580 if unpack_only:
1581 self._ScheduleUnpack(state)
1582 else:
Bertrand SIMONNET411945d2015-05-20 17:23:28 -07001583 if state.target not in self._failed_count:
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001584 self._Schedule(state)
David Jamesfcb70ef2011-02-02 16:02:30 -08001585
1586 def _Print(self, line):
1587 """Print a single line."""
1588 self._print_queue.put(LinePrinter(line))
1589
1590 def _Status(self):
1591 """Print status."""
1592 current_time = time.time()
Aviv Keshet3b381682015-11-12 13:15:06 -08001593 current_time_struct = time.localtime(current_time)
David Jamesfcb70ef2011-02-02 16:02:30 -08001594 no_output = True
1595
1596 # Print interim output every minute if --show-output is used. Otherwise,
1597 # print notifications about running packages every 2 minutes, and print
1598 # full output for jobs that have been running for 60 minutes or more.
1599 if self._show_output:
1600 interval = 60
1601 notify_interval = 0
1602 else:
1603 interval = 60 * 60
1604 notify_interval = 60 * 2
David James321490a2012-12-17 12:05:56 -08001605 for job in self._build_jobs.itervalues():
David Jamesfcb70ef2011-02-02 16:02:30 -08001606 if job:
1607 last_timestamp = max(job.start_timestamp, job.last_output_timestamp)
1608 if last_timestamp + interval < current_time:
1609 self._print_queue.put(JobPrinter(job))
1610 job.last_output_timestamp = current_time
1611 no_output = False
1612 elif (notify_interval and
1613 job.last_notify_timestamp + notify_interval < current_time):
1614 job_seconds = current_time - job.start_timestamp
1615 args = (job.pkgname, job_seconds / 60, job_seconds % 60, job.filename)
1616 info = "Still building %s (%dm%.1fs). Logs in %s" % args
1617 job.last_notify_timestamp = current_time
1618 self._Print(info)
1619 no_output = False
1620
1621 # If we haven't printed any messages yet, print a general status message
1622 # here.
1623 if no_output:
1624 seconds = current_time - GLOBAL_START
Brian Harring0be85c62012-03-17 19:52:12 -07001625 fjobs, fready = len(self._fetch_jobs), len(self._fetch_ready)
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001626 ujobs, uready = len(self._unpack_jobs), len(self._unpack_ready)
Brian Harring0be85c62012-03-17 19:52:12 -07001627 bjobs, bready = len(self._build_jobs), len(self._build_ready)
1628 retries = len(self._retry_queue)
1629 pending = max(0, len(self._deps_map) - fjobs - bjobs)
1630 line = "Pending %s/%s, " % (pending, self._total_jobs)
1631 if fjobs or fready:
1632 line += "Fetching %s/%s, " % (fjobs, fready + fjobs)
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001633 if ujobs or uready:
1634 line += "Unpacking %s/%s, " % (ujobs, uready + ujobs)
Brian Harring0be85c62012-03-17 19:52:12 -07001635 if bjobs or bready or retries:
1636 line += "Building %s/%s, " % (bjobs, bready + bjobs)
1637 if retries:
1638 line += "Retrying %s, " % (retries,)
Mike Frysingerd6e2df02014-11-26 02:55:04 -05001639 load = " ".join(str(x) for x in os.getloadavg())
Aviv Keshet3b381682015-11-12 13:15:06 -08001640 line += ("[Time %s | Elapsed %dm%.1fs | Load %s]" % (
1641 time.strftime('%H:%M:%S', current_time_struct), seconds / 60,
1642 seconds % 60, load))
Brian Harring0be85c62012-03-17 19:52:12 -07001643 self._Print(line)
David Jamesfcb70ef2011-02-02 16:02:30 -08001644
1645 def _Finish(self, target):
David James8c7e5e32011-06-28 11:26:03 -07001646 """Mark a target as completed and unblock dependencies."""
1647 this_pkg = self._deps_map[target]
1648 if this_pkg["needs"] and this_pkg["nodeps"]:
1649 # We got installed, but our deps have not been installed yet. Dependent
1650 # packages should only be installed when our needs have been fully met.
1651 this_pkg["action"] = "nomerge"
1652 else:
David James8c7e5e32011-06-28 11:26:03 -07001653 for dep in this_pkg["provides"]:
1654 dep_pkg = self._deps_map[dep]
Brian Harring0be85c62012-03-17 19:52:12 -07001655 state = self._state_map[dep]
David James8c7e5e32011-06-28 11:26:03 -07001656 del dep_pkg["needs"][target]
Brian Harring0be85c62012-03-17 19:52:12 -07001657 state.update_score()
1658 if not state.prefetched:
1659 if dep in self._fetch_ready:
1660 # If it's not currently being fetched, update the prioritization
1661 self._fetch_ready.sort()
1662 elif not dep_pkg["needs"]:
David James8c7e5e32011-06-28 11:26:03 -07001663 if dep_pkg["nodeps"] and dep_pkg["action"] == "nomerge":
1664 self._Finish(dep)
1665 else:
Brian Harring0be85c62012-03-17 19:52:12 -07001666 self._build_ready.put(self._state_map[dep])
David James8c7e5e32011-06-28 11:26:03 -07001667 self._deps_map.pop(target)
David Jamesfcb70ef2011-02-02 16:02:30 -08001668
1669 def _Retry(self):
David James8c7e5e32011-06-28 11:26:03 -07001670 while self._retry_queue:
Brian Harring0be85c62012-03-17 19:52:12 -07001671 state = self._retry_queue.pop(0)
1672 if self._Schedule(state):
1673 self._Print("Retrying emerge of %s." % state.target)
David James8c7e5e32011-06-28 11:26:03 -07001674 break
David Jamesfcb70ef2011-02-02 16:02:30 -08001675
Brian Harringa43f5952012-04-12 01:19:34 -07001676 def _Shutdown(self):
David Jamesfcb70ef2011-02-02 16:02:30 -08001677 # Tell emerge workers to exit. They all exit when 'None' is pushed
1678 # to the queue.
Brian Harring0be85c62012-03-17 19:52:12 -07001679
Brian Harringa43f5952012-04-12 01:19:34 -07001680 # Shutdown the workers first; then jobs (which is how they feed things back)
1681 # then finally the print queue.
Brian Harring0be85c62012-03-17 19:52:12 -07001682
Brian Harringa43f5952012-04-12 01:19:34 -07001683 def _stop(queue, pool):
1684 if pool is None:
1685 return
1686 try:
1687 queue.put(None)
1688 pool.close()
1689 pool.join()
1690 finally:
1691 pool.terminate()
Brian Harring0be85c62012-03-17 19:52:12 -07001692
Brian Harringa43f5952012-04-12 01:19:34 -07001693 _stop(self._fetch_queue, self._fetch_pool)
1694 self._fetch_queue = self._fetch_pool = None
Brian Harring0be85c62012-03-17 19:52:12 -07001695
Brian Harringa43f5952012-04-12 01:19:34 -07001696 _stop(self._build_queue, self._build_pool)
1697 self._build_queue = self._build_pool = None
1698
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001699 if self._unpack_only:
1700 _stop(self._unpack_queue, self._unpack_pool)
1701 self._unpack_queue = self._unpack_pool = None
1702
Brian Harringa43f5952012-04-12 01:19:34 -07001703 if self._job_queue is not None:
1704 self._job_queue.close()
1705 self._job_queue = None
David Jamesfcb70ef2011-02-02 16:02:30 -08001706
1707 # Now that our workers are finished, we can kill the print queue.
Brian Harringa43f5952012-04-12 01:19:34 -07001708 if self._print_worker is not None:
1709 try:
1710 self._print_queue.put(None)
1711 self._print_queue.close()
1712 self._print_worker.join()
1713 finally:
1714 self._print_worker.terminate()
1715 self._print_queue = self._print_worker = None
David Jamesfcb70ef2011-02-02 16:02:30 -08001716
1717 def Run(self):
1718 """Run through the scheduled ebuilds.
1719
1720 Keep running so long as we have uninstalled packages in the
1721 dependency graph to merge.
1722 """
Brian Harringa43f5952012-04-12 01:19:34 -07001723 if not self._deps_map:
1724 return
1725
Brian Harring0be85c62012-03-17 19:52:12 -07001726 # Start the fetchers.
1727 for _ in xrange(min(self._fetch_procs, len(self._fetch_ready))):
1728 state = self._fetch_ready.get()
1729 self._fetch_jobs[state.target] = None
1730 self._fetch_queue.put(state)
1731
1732 # Print an update, then get going.
1733 self._Status()
1734
David Jamesfcb70ef2011-02-02 16:02:30 -08001735 while self._deps_map:
1736 # Check here that we are actually waiting for something.
Brian Harring0be85c62012-03-17 19:52:12 -07001737 if (self._build_queue.empty() and
David Jamesfcb70ef2011-02-02 16:02:30 -08001738 self._job_queue.empty() and
Brian Harring0be85c62012-03-17 19:52:12 -07001739 not self._fetch_jobs and
1740 not self._fetch_ready and
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001741 not self._unpack_jobs and
1742 not self._unpack_ready and
Brian Harring0be85c62012-03-17 19:52:12 -07001743 not self._build_jobs and
1744 not self._build_ready and
David Jamesfcb70ef2011-02-02 16:02:30 -08001745 self._deps_map):
1746 # If we have failed on a package, retry it now.
1747 if self._retry_queue:
1748 self._Retry()
1749 else:
David Jamesfcb70ef2011-02-02 16:02:30 -08001750 # Tell the user why we're exiting.
Bertrand SIMONNET411945d2015-05-20 17:23:28 -07001751 if self._failed_count:
1752 print('Packages failed:\n\t%s' %
1753 '\n\t'.join(self._failed_count.iterkeys()))
David James0eae23e2012-07-03 15:04:25 -07001754 status_file = os.environ.get("PARALLEL_EMERGE_STATUS_FILE")
1755 if status_file:
David James321490a2012-12-17 12:05:56 -08001756 failed_pkgs = set(portage.versions.cpv_getkey(x)
Bertrand SIMONNET411945d2015-05-20 17:23:28 -07001757 for x in self._failed_count.iterkeys())
David James0eae23e2012-07-03 15:04:25 -07001758 with open(status_file, "a") as f:
1759 f.write("%s\n" % " ".join(failed_pkgs))
David Jamesfcb70ef2011-02-02 16:02:30 -08001760 else:
Mike Frysinger383367e2014-09-16 15:06:17 -04001761 print("Deadlock! Circular dependencies!")
David Jamesfcb70ef2011-02-02 16:02:30 -08001762 sys.exit(1)
1763
David James321490a2012-12-17 12:05:56 -08001764 for _ in xrange(12):
David Jamesa74289a2011-08-12 10:41:24 -07001765 try:
1766 job = self._job_queue.get(timeout=5)
1767 break
1768 except Queue.Empty:
1769 # Check if any more jobs can be scheduled.
1770 self._ScheduleLoop()
1771 else:
Brian Harring706747c2012-03-16 03:04:31 -07001772 # Print an update every 60 seconds.
David Jamesfcb70ef2011-02-02 16:02:30 -08001773 self._Status()
1774 continue
1775
1776 target = job.target
1777
Brian Harring0be85c62012-03-17 19:52:12 -07001778 if job.fetch_only:
1779 if not job.done:
1780 self._fetch_jobs[job.target] = job
1781 else:
1782 state = self._state_map[job.target]
1783 state.prefetched = True
1784 state.fetched_successfully = (job.retcode == 0)
1785 del self._fetch_jobs[job.target]
1786 self._Print("Fetched %s in %2.2fs"
1787 % (target, time.time() - job.start_timestamp))
1788
1789 if self._show_output or job.retcode != 0:
1790 self._print_queue.put(JobPrinter(job, unlink=True))
1791 else:
1792 os.unlink(job.filename)
1793 # Failure or not, let build work with it next.
1794 if not self._deps_map[job.target]["needs"]:
1795 self._build_ready.put(state)
1796 self._ScheduleLoop()
1797
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001798 if self._unpack_only and job.retcode == 0:
1799 self._unpack_ready.put(state)
1800 self._ScheduleLoop(unpack_only=True)
1801
Brian Harring0be85c62012-03-17 19:52:12 -07001802 if self._fetch_ready:
1803 state = self._fetch_ready.get()
1804 self._fetch_queue.put(state)
1805 self._fetch_jobs[state.target] = None
1806 else:
1807 # Minor optimization; shut down fetchers early since we know
1808 # the queue is empty.
1809 self._fetch_queue.put(None)
1810 continue
1811
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001812 if job.unpack_only:
1813 if not job.done:
1814 self._unpack_jobs[target] = job
1815 else:
1816 del self._unpack_jobs[target]
1817 self._Print("Unpacked %s in %2.2fs"
1818 % (target, time.time() - job.start_timestamp))
1819 if self._show_output or job.retcode != 0:
1820 self._print_queue.put(JobPrinter(job, unlink=True))
1821 else:
1822 os.unlink(job.filename)
1823 if self._unpack_ready:
1824 state = self._unpack_ready.get()
1825 self._unpack_queue.put(state)
1826 self._unpack_jobs[state.target] = None
1827 continue
1828
David Jamesfcb70ef2011-02-02 16:02:30 -08001829 if not job.done:
Brian Harring0be85c62012-03-17 19:52:12 -07001830 self._build_jobs[target] = job
David Jamesfcb70ef2011-02-02 16:02:30 -08001831 self._Print("Started %s (logged in %s)" % (target, job.filename))
1832 continue
1833
1834 # Print output of job
1835 if self._show_output or job.retcode != 0:
1836 self._print_queue.put(JobPrinter(job, unlink=True))
1837 else:
1838 os.unlink(job.filename)
Brian Harring0be85c62012-03-17 19:52:12 -07001839 del self._build_jobs[target]
David Jamesfcb70ef2011-02-02 16:02:30 -08001840
1841 seconds = time.time() - job.start_timestamp
1842 details = "%s (in %dm%.1fs)" % (target, seconds / 60, seconds % 60)
1843
1844 # Complain if necessary.
1845 if job.retcode != 0:
1846 # Handle job failure.
Bertrand SIMONNET411945d2015-05-20 17:23:28 -07001847 failed_count = self._failed_count.get(target, 0)
1848 if failed_count >= self._max_retries:
1849 # If this job has failed and can't be retried, give up.
David Jamesfcb70ef2011-02-02 16:02:30 -08001850 self._Print("Failed %s. Your build has failed." % details)
1851 else:
1852 # Queue up this build to try again after a long while.
Brian Harring0be85c62012-03-17 19:52:12 -07001853 self._retry_queue.append(self._state_map[target])
Bertrand SIMONNET411945d2015-05-20 17:23:28 -07001854 self._failed_count[target] = failed_count + 1
David Jamesfcb70ef2011-02-02 16:02:30 -08001855 self._Print("Failed %s, retrying later." % details)
1856 else:
David James32420cc2011-08-25 21:32:46 -07001857 self._Print("Completed %s" % details)
1858
1859 # Mark as completed and unblock waiting ebuilds.
1860 self._Finish(target)
1861
Bertrand SIMONNET411945d2015-05-20 17:23:28 -07001862 if target in self._failed_count and self._retry_queue:
David Jamesfcb70ef2011-02-02 16:02:30 -08001863 # If we have successfully retried a failed package, and there
1864 # are more failed packages, try the next one. We will only have
1865 # one retrying package actively running at a time.
1866 self._Retry()
1867
David Jamesfcb70ef2011-02-02 16:02:30 -08001868
David James8c7e5e32011-06-28 11:26:03 -07001869 # Schedule pending jobs and print an update.
1870 self._ScheduleLoop()
1871 self._Status()
David Jamesfcb70ef2011-02-02 16:02:30 -08001872
David Jamese703d0f2012-01-12 16:27:45 -08001873 # If packages were retried, output a warning.
Bertrand SIMONNET411945d2015-05-20 17:23:28 -07001874 if self._failed_count:
David Jamese703d0f2012-01-12 16:27:45 -08001875 self._Print("")
Bertrand SIMONNET411945d2015-05-20 17:23:28 -07001876 self._Print("WARNING: The following packages failed once or more,")
David Jamese703d0f2012-01-12 16:27:45 -08001877 self._Print("but succeeded upon retry. This might indicate incorrect")
1878 self._Print("dependencies.")
Bertrand SIMONNET411945d2015-05-20 17:23:28 -07001879 for pkg in self._failed_count.iterkeys():
David Jamese703d0f2012-01-12 16:27:45 -08001880 self._Print(" %s" % pkg)
1881 self._Print("@@@STEP_WARNINGS@@@")
1882 self._Print("")
1883
David Jamesfcb70ef2011-02-02 16:02:30 -08001884 # Tell child threads to exit.
1885 self._Print("Merge complete")
David Jamesfcb70ef2011-02-02 16:02:30 -08001886
1887
Brian Harring30675052012-02-29 12:18:22 -08001888def main(argv):
Brian Harring8294d652012-05-23 02:20:52 -07001889 try:
1890 return real_main(argv)
1891 finally:
1892 # Work around multiprocessing sucking and not cleaning up after itself.
1893 # http://bugs.python.org/issue4106;
1894 # Step one; ensure GC is ran *prior* to the VM starting shutdown.
1895 gc.collect()
1896 # Step two; go looking for those threads and try to manually reap
1897 # them if we can.
1898 for x in threading.enumerate():
1899 # Filter on the name, and ident; if ident is None, the thread
1900 # wasn't started.
1901 if x.name == 'QueueFeederThread' and x.ident is not None:
1902 x.join(1)
David Jamesfcb70ef2011-02-02 16:02:30 -08001903
Brian Harring8294d652012-05-23 02:20:52 -07001904
1905def real_main(argv):
Brian Harring30675052012-02-29 12:18:22 -08001906 parallel_emerge_args = argv[:]
David Jamesfcb70ef2011-02-02 16:02:30 -08001907 deps = DepGraphGenerator()
Brian Harring30675052012-02-29 12:18:22 -08001908 deps.Initialize(parallel_emerge_args)
David Jamesfcb70ef2011-02-02 16:02:30 -08001909 emerge = deps.emerge
1910
1911 if emerge.action is not None:
Brian Harring30675052012-02-29 12:18:22 -08001912 argv = deps.ParseParallelEmergeArgs(argv)
Brian Harring8294d652012-05-23 02:20:52 -07001913 return emerge_main(argv)
David Jamesfcb70ef2011-02-02 16:02:30 -08001914 elif not emerge.cmdline_packages:
1915 Usage()
Brian Harring8294d652012-05-23 02:20:52 -07001916 return 1
David Jamesfcb70ef2011-02-02 16:02:30 -08001917
1918 # Unless we're in pretend mode, there's not much point running without
1919 # root access. We need to be able to install packages.
1920 #
1921 # NOTE: Even if you're running --pretend, it's a good idea to run
1922 # parallel_emerge with root access so that portage can write to the
1923 # dependency cache. This is important for performance.
David James321490a2012-12-17 12:05:56 -08001924 if "--pretend" not in emerge.opts and portage.data.secpass < 2:
Mike Frysinger383367e2014-09-16 15:06:17 -04001925 print("parallel_emerge: superuser access is required.")
Brian Harring8294d652012-05-23 02:20:52 -07001926 return 1
David Jamesfcb70ef2011-02-02 16:02:30 -08001927
1928 if "--quiet" not in emerge.opts:
1929 cmdline_packages = " ".join(emerge.cmdline_packages)
Mike Frysinger383367e2014-09-16 15:06:17 -04001930 print("Starting fast-emerge.")
1931 print(" Building package %s on %s" % (cmdline_packages,
Gilad Arnold05f94b02015-05-22 10:41:05 -07001932 deps.sysroot or "root"))
David Jamesfcb70ef2011-02-02 16:02:30 -08001933
David James386ccd12011-05-04 20:17:42 -07001934 deps_tree, deps_info = deps.GenDependencyTree()
David Jamesfcb70ef2011-02-02 16:02:30 -08001935
1936 # You want me to be verbose? I'll give you two trees! Twice as much value.
1937 if "--tree" in emerge.opts and "--verbose" in emerge.opts:
1938 deps.PrintTree(deps_tree)
1939
David James386ccd12011-05-04 20:17:42 -07001940 deps_graph = deps.GenDependencyGraph(deps_tree, deps_info)
David Jamesfcb70ef2011-02-02 16:02:30 -08001941
1942 # OK, time to print out our progress so far.
1943 deps.PrintInstallPlan(deps_graph)
1944 if "--tree" in emerge.opts:
1945 PrintDepsMap(deps_graph)
1946
1947 # Are we upgrading portage? If so, and there are more packages to merge,
1948 # schedule a restart of parallel_emerge to merge the rest. This ensures that
1949 # we pick up all updates to portage settings before merging any more
1950 # packages.
1951 portage_upgrade = False
1952 root = emerge.settings["ROOT"]
Mike Frysinger27e21b72018-07-12 14:20:21 -04001953 # pylint: disable=protected-access
David Jamesfcb70ef2011-02-02 16:02:30 -08001954 if root == "/":
Gilad Arnoldcead28a2015-05-22 10:45:02 -07001955 final_db = emerge.depgraph._dynamic_config._filtered_trees[root]['graph_db']
Mike Frysinger3fb56ef2018-01-05 19:00:04 -05001956 for db_pkg in final_db.cp_list("sys-apps/portage"):
David Jamesfcb70ef2011-02-02 16:02:30 -08001957 portage_pkg = deps_graph.get(db_pkg.cpv)
David James0ff16f22012-11-02 14:18:07 -07001958 if portage_pkg:
David Jamesfcb70ef2011-02-02 16:02:30 -08001959 portage_upgrade = True
1960 if "--quiet" not in emerge.opts:
Mike Frysinger383367e2014-09-16 15:06:17 -04001961 print("Upgrading portage first, then restarting...")
David Jamesfcb70ef2011-02-02 16:02:30 -08001962
David James0ff16f22012-11-02 14:18:07 -07001963 # Upgrade Portage first, then the rest of the packages.
1964 #
1965 # In order to grant the child permission to run setsid, we need to run sudo
1966 # again. We preserve SUDO_USER here in case an ebuild depends on it.
1967 if portage_upgrade:
1968 # Calculate what arguments to use when re-invoking.
1969 args = ["sudo", "-E", "SUDO_USER=%s" % os.environ.get("SUDO_USER", "")]
1970 args += [os.path.abspath(sys.argv[0])] + parallel_emerge_args
1971 args += ["--exclude=sys-apps/portage"]
1972
1973 # First upgrade Portage.
1974 passthrough_args = ("--quiet", "--pretend", "--verbose")
1975 emerge_args = [k for k in emerge.opts if k in passthrough_args]
1976 ret = emerge_main(emerge_args + ["portage"])
1977 if ret != 0:
1978 return ret
1979
1980 # Now upgrade the rest.
1981 os.execvp(args[0], args)
1982
Bertrand SIMONNETc03c8ee2014-12-10 17:02:55 -08001983 # Attempt to solve crbug.com/433482
1984 # The file descriptor error appears only when getting userpriv_groups
1985 # (lazily generated). Loading userpriv_groups here will reduce the number of
1986 # calls from few hundreds to one.
1987 portage.data._get_global('userpriv_groups')
1988
David Jamesfcb70ef2011-02-02 16:02:30 -08001989 # Run the queued emerges.
Thiago Goncalesf4acc422013-07-17 10:26:35 -07001990 scheduler = EmergeQueue(deps_graph, emerge, deps.package_db, deps.show_output,
Bertrand SIMONNET411945d2015-05-20 17:23:28 -07001991 deps.unpack_only, deps.max_retries)
Brian Harringa43f5952012-04-12 01:19:34 -07001992 try:
1993 scheduler.Run()
1994 finally:
Mike Frysinger27e21b72018-07-12 14:20:21 -04001995 # pylint: disable=protected-access
Brian Harringa43f5952012-04-12 01:19:34 -07001996 scheduler._Shutdown()
David James97ce8902011-08-16 09:51:05 -07001997 scheduler = None
David Jamesfcb70ef2011-02-02 16:02:30 -08001998
Mike Frysingerd20a6e22012-10-04 19:01:10 -04001999 clean_logs(emerge.settings)
2000
Mike Frysinger383367e2014-09-16 15:06:17 -04002001 print("Done")
Brian Harring8294d652012-05-23 02:20:52 -07002002 return 0