| # 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 |