diff options
author | Michał Górny <mgorny@gentoo.org> | 2018-07-17 21:50:45 +0200 |
---|---|---|
committer | Zac Medico <zmedico@gentoo.org> | 2018-07-18 16:19:11 -0700 |
commit | bc0fa8d3795ed7e40aaa00f579bb2977897bce25 (patch) | |
tree | 2a62c721ee8dec47ddb564254e1cbd967577d1f0 /lib/_emerge/EbuildPhase.py | |
parent | EventLoop: raise TypeError for unexpected call_* keyword args (diff) | |
download | portage-bc0fa8d3795ed7e40aaa00f579bb2977897bce25.tar.gz portage-bc0fa8d3795ed7e40aaa00f579bb2977897bce25.tar.bz2 portage-bc0fa8d3795ed7e40aaa00f579bb2977897bce25.zip |
Rename pym→lib, for better distutils-r1 interoperability
Closes: https://github.com/gentoo/portage/pull/343
Diffstat (limited to 'lib/_emerge/EbuildPhase.py')
-rw-r--r-- | lib/_emerge/EbuildPhase.py | 439 |
1 files changed, 439 insertions, 0 deletions
diff --git a/lib/_emerge/EbuildPhase.py b/lib/_emerge/EbuildPhase.py new file mode 100644 index 000000000..4104cefa7 --- /dev/null +++ b/lib/_emerge/EbuildPhase.py @@ -0,0 +1,439 @@ +# Copyright 1999-2018 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 + +import functools +import gzip +import io +import sys +import tempfile + +from _emerge.AsynchronousLock import AsynchronousLock +from _emerge.BinpkgEnvExtractor import BinpkgEnvExtractor +from _emerge.MiscFunctionsProcess import MiscFunctionsProcess +from _emerge.EbuildProcess import EbuildProcess +from _emerge.CompositeTask import CompositeTask +from _emerge.PackagePhase import PackagePhase +from _emerge.TaskSequence import TaskSequence +from portage.package.ebuild.prepare_build_dirs import (_prepare_workdir, + _prepare_fake_distdir, _prepare_fake_filesdir) +from portage.util import writemsg +from portage.util._async.AsyncTaskFuture import AsyncTaskFuture + +try: + from portage.xml.metadata import MetaDataXML +except (SystemExit, KeyboardInterrupt): + raise +except (ImportError, SystemError, RuntimeError, Exception): + # broken or missing xml support + # https://bugs.python.org/issue14988 + MetaDataXML = None + +import portage +portage.proxy.lazyimport.lazyimport(globals(), + 'portage.elog:messages@elog_messages', + 'portage.package.ebuild.doebuild:_check_build_log,' + \ + '_post_phase_cmds,_post_phase_userpriv_perms,' + \ + '_post_src_install_soname_symlinks,' + \ + '_post_src_install_uid_fix,_postinst_bsdflags,' + \ + '_post_src_install_write_metadata,' + \ + '_preinst_bsdflags' +) +from portage import os +from portage import _encodings +from portage import _unicode_encode + +class EbuildPhase(CompositeTask): + + __slots__ = ("actionmap", "fd_pipes", "phase", "settings") + \ + ("_ebuild_lock",) + + # FEATURES displayed prior to setup phase + _features_display = ( + "ccache", "compressdebug", "distcc", "distcc-pump", "fakeroot", + "installsources", "keeptemp", "keepwork", "network-sandbox", + "network-sandbox-proxy", "nostrip", "preserve-libs", "sandbox", + "selinux", "sesandbox", "splitdebug", "suidctl", "test", + "userpriv", "usersandbox" + ) + + # Locked phases + _locked_phases = ("setup", "preinst", "postinst", "prerm", "postrm") + + def _start(self): + + need_builddir = self.phase not in EbuildProcess._phases_without_builddir + + if need_builddir: + phase_completed_file = os.path.join( + self.settings['PORTAGE_BUILDDIR'], + ".%sed" % self.phase.rstrip('e')) + if not os.path.exists(phase_completed_file): + # If the phase is really going to run then we want + # to eliminate any stale elog messages that may + # exist from a previous run. + try: + os.unlink(os.path.join(self.settings['T'], + 'logging', self.phase)) + except OSError: + pass + + if self.phase in ('nofetch', 'pretend', 'setup'): + + use = self.settings.get('PORTAGE_BUILT_USE') + if use is None: + use = self.settings['PORTAGE_USE'] + + maint_str = "" + upstr_str = "" + metadata_xml_path = os.path.join(os.path.dirname(self.settings['EBUILD']), "metadata.xml") + if MetaDataXML is not None and os.path.isfile(metadata_xml_path): + herds_path = os.path.join(self.settings['PORTDIR'], + 'metadata/herds.xml') + try: + metadata_xml = MetaDataXML(metadata_xml_path, herds_path) + maint_str = metadata_xml.format_maintainer_string() + upstr_str = metadata_xml.format_upstream_string() + except SyntaxError: + maint_str = "<invalid metadata.xml>" + + msg = [] + msg.append("Package: %s" % self.settings.mycpv) + if self.settings.get('PORTAGE_REPO_NAME'): + msg.append("Repository: %s" % self.settings['PORTAGE_REPO_NAME']) + if maint_str: + msg.append("Maintainer: %s" % maint_str) + if upstr_str: + msg.append("Upstream: %s" % upstr_str) + + msg.append("USE: %s" % use) + relevant_features = [] + enabled_features = self.settings.features + for x in self._features_display: + if x in enabled_features: + relevant_features.append(x) + if relevant_features: + msg.append("FEATURES: %s" % " ".join(relevant_features)) + + # Force background=True for this header since it's intended + # for the log and it doesn't necessarily need to be visible + # elsewhere. + self._elog('einfo', msg, background=True) + + if self.phase == 'package': + if 'PORTAGE_BINPKG_TMPFILE' not in self.settings: + self.settings['PORTAGE_BINPKG_TMPFILE'] = \ + os.path.join(self.settings['PKGDIR'], + self.settings['CATEGORY'], self.settings['PF']) + '.tbz2' + + if self.phase in ("pretend", "prerm"): + env_extractor = BinpkgEnvExtractor(background=self.background, + scheduler=self.scheduler, settings=self.settings) + if env_extractor.saved_env_exists(): + self._start_task(env_extractor, self._env_extractor_exit) + return + # If the environment.bz2 doesn't exist, then ebuild.sh will + # source the ebuild as a fallback. + + self._start_lock() + + def _env_extractor_exit(self, env_extractor): + if self._default_exit(env_extractor) != os.EX_OK: + self.wait() + return + + self._start_lock() + + def _start_lock(self): + if (self.phase in self._locked_phases and + "ebuild-locks" in self.settings.features): + eroot = self.settings["EROOT"] + lock_path = os.path.join(eroot, portage.VDB_PATH + "-ebuild") + if os.access(os.path.dirname(lock_path), os.W_OK): + self._ebuild_lock = AsynchronousLock(path=lock_path, + scheduler=self.scheduler) + self._start_task(self._ebuild_lock, self._lock_exit) + return + + self._start_ebuild() + + def _lock_exit(self, ebuild_lock): + if self._default_exit(ebuild_lock) != os.EX_OK: + self.wait() + return + self._start_ebuild() + + def _get_log_path(self): + # Don't open the log file during the clean phase since the + # open file can result in an nfs lock on $T/build.log which + # prevents the clean phase from removing $T. + logfile = None + if self.phase not in ("clean", "cleanrm") and \ + self.settings.get("PORTAGE_BACKGROUND") != "subprocess": + logfile = self.settings.get("PORTAGE_LOG_FILE") + return logfile + + def _start_ebuild(self): + if self.phase == "package": + self._start_task(PackagePhase(actionmap=self.actionmap, + background=self.background, fd_pipes=self.fd_pipes, + logfile=self._get_log_path(), scheduler=self.scheduler, + settings=self.settings), self._ebuild_exit) + return + + if self.phase == "unpack": + alist = self.settings.configdict["pkg"].get("A", "").split() + _prepare_fake_distdir(self.settings, alist) + _prepare_fake_filesdir(self.settings) + + fd_pipes = self.fd_pipes + if fd_pipes is None: + if not self.background and self.phase == 'nofetch': + # All the pkg_nofetch output goes to stderr since + # it's considered to be an error message. + fd_pipes = {1 : sys.__stderr__.fileno()} + + ebuild_process = EbuildProcess(actionmap=self.actionmap, + background=self.background, fd_pipes=fd_pipes, + logfile=self._get_log_path(), phase=self.phase, + scheduler=self.scheduler, settings=self.settings) + + self._start_task(ebuild_process, self._ebuild_exit) + + def _ebuild_exit(self, ebuild_process): + self._assert_current(ebuild_process) + if self._ebuild_lock is None: + self._ebuild_exit_unlocked(ebuild_process) + else: + self._start_task( + AsyncTaskFuture(future=self._ebuild_lock.async_unlock()), + functools.partial(self._ebuild_exit_unlocked, ebuild_process)) + + def _ebuild_exit_unlocked(self, ebuild_process, unlock_task=None): + if unlock_task is not None: + self._assert_current(unlock_task) + if unlock_task.cancelled: + self._default_final_exit(unlock_task) + return + + # Normally, async_unlock should not raise an exception here. + unlock_task.future.result() + + fail = False + if ebuild_process.returncode != os.EX_OK: + self.returncode = ebuild_process.returncode + if self.phase == "test" and \ + "test-fail-continue" in self.settings.features: + # mark test phase as complete (bug #452030) + try: + open(_unicode_encode(os.path.join( + self.settings["PORTAGE_BUILDDIR"], ".tested"), + encoding=_encodings['fs'], errors='strict'), + 'wb').close() + except OSError: + pass + else: + fail = True + + if not fail: + self.returncode = None + + logfile = self._get_log_path() + + if self.phase == "install": + out = io.StringIO() + _check_build_log(self.settings, out=out) + msg = out.getvalue() + self.scheduler.output(msg, log_path=logfile) + + if fail: + self._die_hooks() + return + + settings = self.settings + _post_phase_userpriv_perms(settings) + + if self.phase == "unpack": + # Bump WORKDIR timestamp, in case tar gave it a timestamp + # that will interfere with distfiles / WORKDIR timestamp + # comparisons as reported in bug #332217. Also, fix + # ownership since tar can change that too. + os.utime(settings["WORKDIR"], None) + _prepare_workdir(settings) + elif self.phase == "install": + out = io.StringIO() + _post_src_install_write_metadata(settings) + _post_src_install_uid_fix(settings, out) + msg = out.getvalue() + if msg: + self.scheduler.output(msg, log_path=logfile) + elif self.phase == "preinst": + _preinst_bsdflags(settings) + elif self.phase == "postinst": + _postinst_bsdflags(settings) + + post_phase_cmds = _post_phase_cmds.get(self.phase) + if post_phase_cmds is not None: + if logfile is not None and self.phase in ("install",): + # Log to a temporary file, since the code we are running + # reads PORTAGE_LOG_FILE for QA checks, and we want to + # avoid annoying "gzip: unexpected end of file" messages + # when FEATURES=compress-build-logs is enabled. + fd, logfile = tempfile.mkstemp() + os.close(fd) + post_phase = _PostPhaseCommands(background=self.background, + commands=post_phase_cmds, fd_pipes=self.fd_pipes, + logfile=logfile, phase=self.phase, scheduler=self.scheduler, + settings=settings) + self._start_task(post_phase, self._post_phase_exit) + return + + # this point is not reachable if there was a failure and + # we returned for die_hooks above, so returncode must + # indicate success (especially if ebuild_process.returncode + # is unsuccessful and test-fail-continue came into play) + self.returncode = os.EX_OK + self._current_task = None + self.wait() + + def _post_phase_exit(self, post_phase): + + self._assert_current(post_phase) + + log_path = None + if self.settings.get("PORTAGE_BACKGROUND") != "subprocess": + log_path = self.settings.get("PORTAGE_LOG_FILE") + + if post_phase.logfile is not None and \ + post_phase.logfile != log_path: + # We were logging to a temp file (see above), so append + # temp file to main log and remove temp file. + self._append_temp_log(post_phase.logfile, log_path) + + if self._final_exit(post_phase) != os.EX_OK: + writemsg("!!! post %s failed; exiting.\n" % self.phase, + noiselevel=-1) + self._die_hooks() + return + + if self.phase == "install": + out = io.StringIO() + _post_src_install_soname_symlinks(self.settings, out) + msg = out.getvalue() + if msg: + self.scheduler.output(msg, log_path=log_path) + + self._current_task = None + self.wait() + return + + def _append_temp_log(self, temp_log, log_path): + + temp_file = open(_unicode_encode(temp_log, + encoding=_encodings['fs'], errors='strict'), 'rb') + + log_file, log_file_real = self._open_log(log_path) + + for line in temp_file: + log_file.write(line) + + temp_file.close() + log_file.close() + if log_file_real is not log_file: + log_file_real.close() + os.unlink(temp_log) + + def _open_log(self, log_path): + + f = open(_unicode_encode(log_path, + encoding=_encodings['fs'], errors='strict'), + mode='ab') + f_real = f + + if log_path.endswith('.gz'): + f = gzip.GzipFile(filename='', mode='ab', fileobj=f) + + return (f, f_real) + + def _die_hooks(self): + self.returncode = None + phase = 'die_hooks' + die_hooks = MiscFunctionsProcess(background=self.background, + commands=[phase], phase=phase, logfile=self._get_log_path(), + fd_pipes=self.fd_pipes, scheduler=self.scheduler, + settings=self.settings) + self._start_task(die_hooks, self._die_hooks_exit) + + def _die_hooks_exit(self, die_hooks): + if self.phase != 'clean' and \ + 'noclean' not in self.settings.features and \ + 'fail-clean' in self.settings.features: + self._default_exit(die_hooks) + self._fail_clean() + return + self._final_exit(die_hooks) + self.returncode = 1 + self.wait() + + def _fail_clean(self): + self.returncode = None + portage.elog.elog_process(self.settings.mycpv, self.settings) + phase = "clean" + clean_phase = EbuildPhase(background=self.background, + fd_pipes=self.fd_pipes, phase=phase, scheduler=self.scheduler, + settings=self.settings) + self._start_task(clean_phase, self._fail_clean_exit) + return + + def _fail_clean_exit(self, clean_phase): + self._final_exit(clean_phase) + self.returncode = 1 + self.wait() + + def _elog(self, elog_funcname, lines, background=None): + if background is None: + background = self.background + out = io.StringIO() + phase = self.phase + elog_func = getattr(elog_messages, elog_funcname) + global_havecolor = portage.output.havecolor + try: + portage.output.havecolor = \ + self.settings.get('NOCOLOR', 'false').lower() in ('no', 'false') + for line in lines: + elog_func(line, phase=phase, key=self.settings.mycpv, out=out) + finally: + portage.output.havecolor = global_havecolor + msg = out.getvalue() + if msg: + log_path = None + if self.settings.get("PORTAGE_BACKGROUND") != "subprocess": + log_path = self.settings.get("PORTAGE_LOG_FILE") + self.scheduler.output(msg, log_path=log_path, + background=background) + + +class _PostPhaseCommands(CompositeTask): + + __slots__ = ("commands", "fd_pipes", "logfile", "phase", "settings") + + def _start(self): + if isinstance(self.commands, list): + cmds = [({}, self.commands)] + else: + cmds = list(self.commands) + + if 'selinux' not in self.settings.features: + cmds = [(kwargs, commands) for kwargs, commands in + cmds if not kwargs.get('selinux_only')] + + tasks = TaskSequence() + for kwargs, commands in cmds: + # Select args intended for MiscFunctionsProcess. + kwargs = dict((k, v) for k, v in kwargs.items() + if k in ('ld_preload_sandbox',)) + tasks.add(MiscFunctionsProcess(background=self.background, + commands=commands, fd_pipes=self.fd_pipes, + logfile=self.logfile, phase=self.phase, + scheduler=self.scheduler, settings=self.settings, **kwargs)) + + self._start_task(tasks, self._default_final_exit) |