blob: 7791ec23651a3a78bf8dc7ec7172597ec771557c [file] [log] [blame]
# Copyright 1999-2018 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
import functools
import _emerge.emergelog
from _emerge.EbuildPhase import EbuildPhase
from _emerge.BinpkgFetcher import BinpkgFetcher
from _emerge.BinpkgEnvExtractor import BinpkgEnvExtractor
from _emerge.BinpkgExtractorAsync import BinpkgExtractorAsync
from _emerge.CompositeTask import CompositeTask
from _emerge.BinpkgVerifier import BinpkgVerifier
from _emerge.EbuildMerge import EbuildMerge
from _emerge.EbuildBuildDir import EbuildBuildDir
from _emerge.SpawnProcess import SpawnProcess
from portage.eapi import eapi_exports_replace_vars
from portage.util import ensure_dirs
from portage.util._async.AsyncTaskFuture import AsyncTaskFuture
import portage
from portage import os
from portage import shutil
from portage import _encodings
from portage import _unicode_decode
from portage import _unicode_encode
import io
import logging
class Binpkg(CompositeTask):
__slots__ = ("find_blockers",
"ldpath_mtimes", "logger", "opts",
"pkg", "pkg_count", "prefetcher", "settings", "world_atom") + \
("_bintree", "_build_dir", "_build_prefix",
"_ebuild_path", "_fetched_pkg",
"_image_dir", "_infloc", "_pkg_path", "_tree", "_verify")
def _writemsg_level(self, msg, level=0, noiselevel=0):
self.scheduler.output(msg, level=level, noiselevel=noiselevel,
log_path=self.settings.get("PORTAGE_LOG_FILE"))
def _start(self):
pkg = self.pkg
settings = self.settings
settings.setcpv(pkg)
self._tree = "bintree"
self._bintree = self.pkg.root_config.trees[self._tree]
self._verify = not self.opts.pretend
# Use realpath like doebuild_environment() does, since we assert
# that this path is literally identical to PORTAGE_BUILDDIR.
dir_path = os.path.join(os.path.realpath(settings["PORTAGE_TMPDIR"]),
"portage", pkg.category, pkg.pf)
self._image_dir = os.path.join(dir_path, "image")
self._infloc = os.path.join(dir_path, "build-info")
self._ebuild_path = os.path.join(self._infloc, pkg.pf + ".ebuild")
settings["EBUILD"] = self._ebuild_path
portage.doebuild_environment(self._ebuild_path, 'setup',
settings=self.settings, db=self._bintree.dbapi)
if dir_path != self.settings['PORTAGE_BUILDDIR']:
raise AssertionError("'%s' != '%s'" % \
(dir_path, self.settings['PORTAGE_BUILDDIR']))
self._build_dir = EbuildBuildDir(
scheduler=self.scheduler, settings=settings)
settings.configdict["pkg"]["EMERGE_FROM"] = "binary"
settings.configdict["pkg"]["MERGE_TYPE"] = "binary"
if eapi_exports_replace_vars(settings["EAPI"]):
vardb = self.pkg.root_config.trees["vartree"].dbapi
settings["REPLACING_VERSIONS"] = " ".join(
set(portage.versions.cpv_getversion(x) \
for x in vardb.match(self.pkg.slot_atom) + \
vardb.match('='+self.pkg.cpv)))
# The prefetcher has already completed or it
# could be running now. If it's running now,
# wait for it to complete since it holds
# a lock on the file being fetched. The
# portage.locks functions are only designed
# to work between separate processes. Since
# the lock is held by the current process,
# use the scheduler and fetcher methods to
# synchronize with the fetcher.
prefetcher = self.prefetcher
if prefetcher is None:
pass
elif prefetcher.isAlive() and \
prefetcher.poll() is None:
if not self.background:
fetch_log = os.path.join(
_emerge.emergelog._emerge_log_dir, 'emerge-fetch.log')
msg = (
'Fetching in the background:',
prefetcher.pkg_path,
'To view fetch progress, run in another terminal:',
'tail -f %s' % fetch_log,
)
out = portage.output.EOutput()
for l in msg:
out.einfo(l)
self._current_task = prefetcher
prefetcher.addExitListener(self._prefetch_exit)
return
self._prefetch_exit(prefetcher)
def _prefetch_exit(self, prefetcher):
if self._was_cancelled():
self.wait()
return
if not (self.opts.pretend or self.opts.fetchonly):
self._start_task(
AsyncTaskFuture(future=self._build_dir.async_lock()),
self._start_fetcher)
else:
self._start_fetcher()
def _start_fetcher(self, lock_task=None):
if lock_task is not None:
self._assert_current(lock_task)
if lock_task.cancelled:
self._default_final_exit(lock_task)
return
lock_task.future.result()
# Initialize PORTAGE_LOG_FILE (clean_log won't work without it).
portage.prepare_build_dirs(self.settings["ROOT"], self.settings, 1)
# If necessary, discard old log so that we don't
# append to it.
self._build_dir.clean_log()
pkg = self.pkg
pkg_count = self.pkg_count
fetcher = BinpkgFetcher(background=self.background,
logfile=self.settings.get("PORTAGE_LOG_FILE"), pkg=self.pkg,
pretend=self.opts.pretend, scheduler=self.scheduler)
if self.opts.getbinpkg and self._bintree.isremote(pkg.cpv):
msg = " --- (%s of %s) Fetching Binary (%s::%s)" %\
(pkg_count.curval, pkg_count.maxval, pkg.cpv,
fetcher.pkg_path)
short_msg = "emerge: (%s of %s) %s Fetch" % \
(pkg_count.curval, pkg_count.maxval, pkg.cpv)
self.logger.log(msg, short_msg=short_msg)
# Allow the Scheduler's fetch queue to control the
# number of concurrent fetchers.
fetcher.addExitListener(self._fetcher_exit)
self._task_queued(fetcher)
self.scheduler.fetch.schedule(fetcher)
return
self._fetcher_exit(fetcher)
def _fetcher_exit(self, fetcher):
# The fetcher only has a returncode when
# --getbinpkg is enabled.
if fetcher.returncode is not None:
self._fetched_pkg = fetcher.pkg_path
if self._default_exit(fetcher) != os.EX_OK:
self._async_unlock_builddir(returncode=self.returncode)
return
if self.opts.pretend:
self._current_task = None
self.returncode = os.EX_OK
self.wait()
return
verifier = None
if self._verify:
if self._fetched_pkg:
path = self._fetched_pkg
else:
path = self.pkg.root_config.trees["bintree"].getname(
self.pkg.cpv)
logfile = self.settings.get("PORTAGE_LOG_FILE")
verifier = BinpkgVerifier(background=self.background,
logfile=logfile, pkg=self.pkg, scheduler=self.scheduler,
_pkg_path=path)
self._start_task(verifier, self._verifier_exit)
return
self._verifier_exit(verifier)
def _verifier_exit(self, verifier):
if verifier is not None and \
self._default_exit(verifier) != os.EX_OK:
self._async_unlock_builddir(returncode=self.returncode)
return
logger = self.logger
pkg = self.pkg
pkg_count = self.pkg_count
if self._fetched_pkg:
pkg_path = self._bintree.getname(
self._bintree.inject(pkg.cpv,
filename=self._fetched_pkg),
allocate_new=False)
else:
pkg_path = self.pkg.root_config.trees["bintree"].getname(
self.pkg.cpv)
# This gives bashrc users an opportunity to do various things
# such as remove binary packages after they're installed.
self.settings["PORTAGE_BINPKG_FILE"] = pkg_path
self._pkg_path = pkg_path
logfile = self.settings.get("PORTAGE_LOG_FILE")
if logfile is not None and os.path.isfile(logfile):
# Remove fetch log after successful fetch.
try:
os.unlink(logfile)
except OSError:
pass
if self.opts.fetchonly:
self._current_task = None
self.returncode = os.EX_OK
self.wait()
return
msg = " === (%s of %s) Merging Binary (%s::%s)" % \
(pkg_count.curval, pkg_count.maxval, pkg.cpv, pkg_path)
short_msg = "emerge: (%s of %s) %s Merge Binary" % \
(pkg_count.curval, pkg_count.maxval, pkg.cpv)
logger.log(msg, short_msg=short_msg)
phase = "clean"
settings = self.settings
ebuild_phase = EbuildPhase(background=self.background,
phase=phase, scheduler=self.scheduler,
settings=settings)
self._start_task(ebuild_phase, self._clean_exit)
def _clean_exit(self, clean_phase):
if self._default_exit(clean_phase) != os.EX_OK:
self._async_unlock_builddir(returncode=self.returncode)
return
dir_path = self.settings['PORTAGE_BUILDDIR']
infloc = self._infloc
pkg = self.pkg
pkg_path = self._pkg_path
dir_mode = 0o755
for mydir in (dir_path, self._image_dir, infloc):
portage.util.ensure_dirs(mydir, uid=portage.data.portage_uid,
gid=portage.data.portage_gid, mode=dir_mode)
# This initializes PORTAGE_LOG_FILE.
portage.prepare_build_dirs(self.settings["ROOT"], self.settings, 1)
self._writemsg_level(">>> Extracting info\n")
pkg_xpak = portage.xpak.tbz2(self._pkg_path)
check_missing_metadata = ("CATEGORY", "PF")
missing_metadata = set()
for k in check_missing_metadata:
v = pkg_xpak.getfile(_unicode_encode(k,
encoding=_encodings['repo.content']))
if not v:
missing_metadata.add(k)
pkg_xpak.unpackinfo(infloc)
for k in missing_metadata:
if k == "CATEGORY":
v = pkg.category
elif k == "PF":
v = pkg.pf
else:
continue
f = io.open(_unicode_encode(os.path.join(infloc, k),
encoding=_encodings['fs'], errors='strict'),
mode='w', encoding=_encodings['content'],
errors='backslashreplace')
try:
f.write(_unicode_decode(v + "\n"))
finally:
f.close()
# Store the md5sum in the vdb.
f = io.open(_unicode_encode(os.path.join(infloc, 'BINPKGMD5'),
encoding=_encodings['fs'], errors='strict'),
mode='w', encoding=_encodings['content'], errors='strict')
try:
f.write(_unicode_decode(
str(portage.checksum.perform_md5(pkg_path)) + "\n"))
finally:
f.close()
env_extractor = BinpkgEnvExtractor(background=self.background,
scheduler=self.scheduler, settings=self.settings)
self._start_task(env_extractor, self._env_extractor_exit)
def _env_extractor_exit(self, env_extractor):
if self._default_exit(env_extractor) != os.EX_OK:
self._async_unlock_builddir(returncode=self.returncode)
return
setup_phase = EbuildPhase(background=self.background,
phase="setup", scheduler=self.scheduler,
settings=self.settings)
setup_phase.addExitListener(self._setup_exit)
self._task_queued(setup_phase)
self.scheduler.scheduleSetup(setup_phase)
def _setup_exit(self, setup_phase):
if self._default_exit(setup_phase) != os.EX_OK:
self._async_unlock_builddir(returncode=self.returncode)
return
extractor = BinpkgExtractorAsync(background=self.background,
env=self.settings.environ(),
features=self.settings.features,
image_dir=self._image_dir,
pkg=self.pkg, pkg_path=self._pkg_path,
logfile=self.settings.get("PORTAGE_LOG_FILE"),
scheduler=self.scheduler)
self._writemsg_level(">>> Extracting %s\n" % self.pkg.cpv)
self._start_task(extractor, self._extractor_exit)
def _extractor_exit(self, extractor):
if self._default_exit(extractor) != os.EX_OK:
self._writemsg_level("!!! Error Extracting '%s'\n" % \
self._pkg_path, noiselevel=-1, level=logging.ERROR)
self._async_unlock_builddir(returncode=self.returncode)
return
try:
with io.open(_unicode_encode(os.path.join(self._infloc, "EPREFIX"),
encoding=_encodings['fs'], errors='strict'), mode='r',
encoding=_encodings['repo.content'], errors='replace') as f:
self._build_prefix = f.read().rstrip('\n')
except IOError:
self._build_prefix = ""
if self._build_prefix == self.settings["EPREFIX"]:
ensure_dirs(self.settings["ED"])
self._current_task = None
self.returncode = os.EX_OK
self.wait()
return
env = self.settings.environ()
env["PYTHONPATH"] = self.settings["PORTAGE_PYTHONPATH"]
chpathtool = SpawnProcess(
args=[portage._python_interpreter,
os.path.join(self.settings["PORTAGE_BIN_PATH"], "chpathtool.py"),
self.settings["D"], self._build_prefix, self.settings["EPREFIX"]],
background=self.background, env=env,
scheduler=self.scheduler,
logfile=self.settings.get('PORTAGE_LOG_FILE'))
self._writemsg_level(">>> Adjusting Prefix to %s\n" % self.settings["EPREFIX"])
self._start_task(chpathtool, self._chpathtool_exit)
def _chpathtool_exit(self, chpathtool):
if self._final_exit(chpathtool) != os.EX_OK:
self._writemsg_level("!!! Error Adjusting Prefix to %s\n" %
(self.settings["EPREFIX"],),
noiselevel=-1, level=logging.ERROR)
self._async_unlock_builddir(returncode=self.returncode)
return
# We want to install in "our" prefix, not the binary one
with io.open(_unicode_encode(os.path.join(self._infloc, "EPREFIX"),
encoding=_encodings['fs'], errors='strict'), mode='w',
encoding=_encodings['repo.content'], errors='strict') as f:
f.write(self.settings["EPREFIX"] + "\n")
# Move the files to the correct location for merge.
image_tmp_dir = os.path.join(
self.settings["PORTAGE_BUILDDIR"], "image_tmp")
build_d = os.path.join(self.settings["D"],
self._build_prefix.lstrip(os.sep))
if not os.path.isdir(build_d):
# Assume this is a virtual package or something.
shutil.rmtree(self._image_dir)
ensure_dirs(self.settings["ED"])
else:
os.rename(build_d, image_tmp_dir)
shutil.rmtree(self._image_dir)
ensure_dirs(os.path.dirname(self.settings["ED"].rstrip(os.sep)))
os.rename(image_tmp_dir, self.settings["ED"])
self.wait()
def _async_unlock_builddir(self, returncode=None):
"""
Release the lock asynchronously, and if a returncode parameter
is given then set self.returncode and notify exit listeners.
"""
if self.opts.pretend or self.opts.fetchonly:
if returncode is not None:
self.returncode = returncode
self._async_wait()
return
if returncode is not None:
# The returncode will be set after unlock is complete.
self.returncode = None
portage.elog.elog_process(self.pkg.cpv, self.settings)
self._start_task(
AsyncTaskFuture(future=self._build_dir.async_unlock()),
functools.partial(self._unlock_builddir_exit, returncode=returncode))
def _unlock_builddir_exit(self, unlock_task, returncode=None):
self._assert_current(unlock_task)
if unlock_task.cancelled and returncode is not None:
self._default_final_exit(unlock_task)
return
# Normally, async_unlock should not raise an exception here.
unlock_task.future.cancelled() or unlock_task.future.result()
if returncode is not None:
self.returncode = returncode
self._async_wait()
def create_install_task(self):
task = EbuildMerge(exit_hook=self._install_exit,
find_blockers=self.find_blockers,
ldpath_mtimes=self.ldpath_mtimes, logger=self.logger,
pkg=self.pkg, pkg_count=self.pkg_count,
pkg_path=self._pkg_path, scheduler=self.scheduler,
settings=self.settings, tree=self._tree,
world_atom=self.world_atom)
return task
def _install_exit(self, task):
"""
@returns: Future, result is the returncode from an
EbuildBuildDir.async_unlock() task
"""
self.settings.pop("PORTAGE_BINPKG_FILE", None)
if task.returncode == os.EX_OK and \
'binpkg-logs' not in self.settings.features and \
self.settings.get("PORTAGE_LOG_FILE"):
try:
os.unlink(self.settings["PORTAGE_LOG_FILE"])
except OSError:
pass
self._async_unlock_builddir()
if self._current_task is None:
result = self.scheduler.create_future()
self.scheduler.call_soon(result.set_result, os.EX_OK)
else:
result = self._current_task.async_wait()
return result