blob: 78f98d2b5783b3f30de9cae9cf82c9d917d1dfbb [file] [log] [blame]
# Copyright 1999-2012 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
import functools
from _emerge.AsynchronousLock import AsynchronousLock
import portage
from portage import os
from portage.exception import PortageException
from portage.util.SlotObject import SlotObject
class EbuildBuildDir(SlotObject):
__slots__ = ("scheduler", "settings", "locked", "_catdir", "_lock_obj")
def __init__(self, **kwargs):
SlotObject.__init__(self, **kwargs)
self.locked = False
def _assert_lock(self, async_lock):
if async_lock.returncode != os.EX_OK:
# TODO: create a better way to propagate this error to the caller
raise AssertionError(
"AsynchronousLock failed with returncode %s" % (async_lock.returncode,)
)
def clean_log(self):
"""Discard existing log. The log will not be be discarded
in cases when it would not make sense, like when FEATURES=keepwork
is enabled."""
settings = self.settings
if "keepwork" in settings.features:
return
log_file = settings.get("PORTAGE_LOG_FILE")
if log_file is not None and os.path.isfile(log_file):
try:
os.unlink(log_file)
except OSError:
pass
def async_lock(self):
"""
Acquire the lock asynchronously. Notification is available
via the add_done_callback method of the returned Future instance.
This raises an AlreadyLocked exception if async_lock() is called
while a lock is already held. In order to avoid this, call
async_unlock() or check whether the "locked" attribute is True
or False before calling async_lock().
@returns: Future, result is None
"""
if self._lock_obj is not None:
raise self.AlreadyLocked((self._lock_obj,))
dir_path = self.settings.get("PORTAGE_BUILDDIR")
if not dir_path:
raise AssertionError("PORTAGE_BUILDDIR is unset")
catdir = os.path.dirname(dir_path)
self._catdir = catdir
catdir_lock = AsynchronousLock(path=catdir, scheduler=self.scheduler)
builddir_lock = AsynchronousLock(path=dir_path, scheduler=self.scheduler)
result = self.scheduler.create_future()
def catdir_locked(catdir_lock):
try:
self._assert_lock(catdir_lock)
except AssertionError as e:
result.set_exception(e)
return
try:
portage.util.ensure_dirs(
catdir, gid=portage.portage_gid, mode=0o70, mask=0
)
except PortageException as e:
if not os.path.isdir(catdir):
result.set_exception(e)
return
builddir_lock.addExitListener(builddir_locked)
builddir_lock.start()
def builddir_locked(builddir_lock):
try:
self._assert_lock(builddir_lock)
except AssertionError as e:
catdir_lock.async_unlock.add_done_callback(
functools.partial(catdir_unlocked, exception=e)
)
return
self._lock_obj = builddir_lock
self.locked = True
self.settings["PORTAGE_BUILDDIR_LOCKED"] = "1"
catdir_lock.async_unlock().add_done_callback(catdir_unlocked)
def catdir_unlocked(future, exception=None):
if not (exception is None and future.exception() is None):
result.set_exception(exception or future.exception())
else:
result.set_result(None)
try:
portage.util.ensure_dirs(
os.path.dirname(catdir), gid=portage.portage_gid, mode=0o70, mask=0
)
except PortageException:
if not os.path.isdir(os.path.dirname(catdir)):
raise
catdir_lock.addExitListener(catdir_locked)
catdir_lock.start()
return result
def async_unlock(self):
"""
Release the lock asynchronously. Release notification is available
via the add_done_callback method of the returned Future instance.
@returns: Future, result is None
"""
result = self.scheduler.create_future()
def builddir_unlocked(future):
if future.exception() is not None:
result.set_exception(future.exception())
else:
self._lock_obj = None
self.locked = False
self.settings.pop("PORTAGE_BUILDDIR_LOCKED", None)
catdir_lock = AsynchronousLock(
path=self._catdir, scheduler=self.scheduler
)
catdir_lock.addExitListener(catdir_locked)
catdir_lock.start()
def catdir_locked(catdir_lock):
if catdir_lock.wait() != os.EX_OK:
result.set_result(None)
else:
try:
os.rmdir(self._catdir)
except OSError:
pass
catdir_lock.async_unlock().add_done_callback(catdir_unlocked)
def catdir_unlocked(future):
if future.exception() is None:
result.set_result(None)
else:
result.set_exception(future.exception())
if self._lock_obj is None:
self.scheduler.call_soon(result.set_result, None)
else:
self._lock_obj.async_unlock().add_done_callback(builddir_unlocked)
return result
class AlreadyLocked(portage.exception.PortageException):
pass