aboutsummaryrefslogtreecommitdiff
blob: f1c49f041207f423103ad3817c87a17a71fb6cb8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# Copyright 1999-2020 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2

from _emerge.AsynchronousLock import AsynchronousLock

import portage
from portage import os
from portage.exception import PortageException
from portage.util.futures import asyncio
from portage.util.futures.compat_coroutine import coroutine
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

	@coroutine
	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

		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 = AsynchronousLock(path=catdir, scheduler=self.scheduler)
		builddir_lock = AsynchronousLock(path=dir_path, scheduler=self.scheduler)
		try:
			yield catdir_lock.async_start()
			yield catdir_lock.async_wait()

			self._assert_lock(catdir_lock)

			try:
				portage.util.ensure_dirs(catdir,
					gid=portage.portage_gid,
					mode=0o70, mask=0)
			except PortageException:
				if not os.path.isdir(catdir):
					raise

			yield builddir_lock.async_start()
			yield builddir_lock.async_wait()
		except asyncio.CancelledError:
			if catdir_lock.poll() is None:
				catdir_lock.cancel()
			if builddir_lock.poll() is None:
				builddir_lock.cancel()
			raise

		try:
			self._assert_lock(builddir_lock)
		except AssertionError:
			yield catdir_lock.async_unlock()
			raise

		self._lock_obj = builddir_lock
		self.locked = True
		self.settings['PORTAGE_BUILDDIR_LOCKED'] = '1'
		yield catdir_lock.async_unlock()

	@coroutine
	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
		"""
		if self._lock_obj is not None:
			yield self._lock_obj.async_unlock()

			self._lock_obj = None
			self.locked = False
			self.settings.pop('PORTAGE_BUILDDIR_LOCKED', None)
			catdir_lock = AsynchronousLock(
				path=self._catdir, scheduler=self.scheduler)
			try:
				yield catdir_lock.async_start()
				yield catdir_lock.async_wait()
			except asyncio.CancelledError:
				if catdir_lock.poll() is None:
					catdir_lock.cancel()
				raise

			if catdir_lock.returncode == os.EX_OK:
				try:
					os.rmdir(self._catdir)
				except OSError:
					pass
				yield catdir_lock.async_unlock()

	class AlreadyLocked(portage.exception.PortageException):
		pass