blob: c7dde69bd242250cdcf2e9750016b33d52b41297 [file] [log] [blame]
# Copyright 1999-2021 Gentoo Authors
# 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.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 = None
if self.opts.getbinpkg and self._bintree.isremote(pkg.cpv):
fetcher = BinpkgFetcher(
background=self.background,
logfile=self.settings.get("PORTAGE_LOG_FILE"),
pkg=self.pkg,
pretend=self.opts.pretend,
scheduler=self.scheduler,
)
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 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.
if pkg_path is not None:
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
self._start_task(
AsyncTaskFuture(future=self._unpack_metadata(loop=self.scheduler)),
self._unpack_metadata_exit,
)
async def _unpack_metadata(self, loop=None):
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")
await self._bintree.dbapi.unpack_metadata(
self.settings, infloc, loop=self.scheduler
)
check_missing_metadata = ("CATEGORY", "PF")
for k, v in zip(
check_missing_metadata,
self._bintree.dbapi.aux_get(self.pkg.cpv, check_missing_metadata),
):
if v:
continue
elif 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.
if pkg_path is not None:
(md5sum,) = self._bintree.dbapi.aux_get(self.pkg.cpv, ["MD5"])
if not md5sum:
md5sum = portage.checksum.perform_md5(pkg_path)
with io.open(
_unicode_encode(
os.path.join(infloc, "BINPKGMD5"),
encoding=_encodings["fs"],
errors="strict",
),
mode="w",
encoding=_encodings["content"],
errors="strict",
) as f:
f.write(_unicode_decode("{}\n".format(md5sum)))
env_extractor = BinpkgEnvExtractor(
background=self.background, scheduler=self.scheduler, settings=self.settings
)
env_extractor.start()
await env_extractor.async_wait()
if env_extractor.returncode != os.EX_OK:
raise portage.exception.PortageException(
"failed to extract environment for {}".format(self.pkg.cpv)
)
def _unpack_metadata_exit(self, unpack_metadata):
if self._default_exit(unpack_metadata) != os.EX_OK:
unpack_metadata.future.result()
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
self._writemsg_level(">>> Extracting %s\n" % self.pkg.cpv)
self._start_task(
AsyncTaskFuture(
future=self._bintree.dbapi.unpack_contents(
self.settings, self._image_dir, loop=self.scheduler
)
),
self._unpack_contents_exit,
)
def _unpack_contents_exit(self, unpack_contents):
if self._default_exit(unpack_contents) != os.EX_OK:
unpack_contents.future.result()
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)
).rstrip(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)
if build_d != self._image_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