aboutsummaryrefslogtreecommitdiff
blob: 765d2411ce69e9b4ac439ad6b95445741d3b363f (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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# R overlay -- dep res channels
# Copyright 2006-2012 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2

import logging

from roverlay.depres.depenv        import DepEnv
from roverlay.depres.communication import DependencyResolverChannel

COMLINK = logging.getLogger ( "depres.com" )

class EbuildJobChannel ( DependencyResolverChannel ):
	"""The EbuildJobChannel is an interface to the dependency resolver used
	in EbuildJobs.
	It can be used to insert dependency strings, trigger dep resolution,
	poll until done and collect the dependencies afterwards.

	Note that this channel has a strict control flow:
	  add deps, then satisfy_request(): collect/lookup
	"""

	def __init__ ( self, name=None, logger=None ):
		"""EbuildJobChannel

		arguments:
		* name -- name of this channel, optional
		* logger --
		"""
		super ( EbuildJobChannel, self ) . __init__ ( main_resolver=None )

		# this is the number of resolved deps so far, should only be modified
		# in the join()-method
		self._depdone = 0

		# set of portage packages (resolved deps)
		#  this is None unless all deps have been successfully resolved
		self._collected_deps = None

		# used to communicate with the resolver
		self._depres_queue   = None

		# the list of dependency lookups assigned to this channel
		## todo: could remove this list
		self.dep_env_list = list ()

		_logger = logger if hasattr ( logger, 'log' ) else COMLINK
		if name:
			self.name   = name
			self.logger = _logger.getChild ( 'channel.' + name )
		else:
			self.logger = _logger.getChild ( 'channel' )
	# --- end of __init__ (...) ---

	def close ( self ):
		if self._depdone >= 0:
			# else already closed

			super ( EbuildJobChannel, self ) . close()
			del self.dep_env_list, self._collected_deps, self._depres_queue
			del self.logger

			self._depdone = -1
			self._collected_deps = None
	# --- end of close (...) ---

	def set_resolver ( self, resolver, channel_queue, **extra ):
		""" comment todo """
		if self._depres_master is None:
			self._depres_master = resolver
			self._depres_queue  = channel_queue
		else:
			raise Exception ( "channel already bound to a resolver." )
	# --- end of set_resolver (...) ---

	def add_dependency ( self, dep_str ):
		"""Adds a dependency string that should be looked up.
		This channel will create a "dependency environment" for it and enqueues
		that in the dep resolver.

		arguments:
		* dep_str --

		raises: Exception if this channel is done

		returns: None (implicit)
		"""
		if self._depdone:
			raise Exception (
				"This channel is 'done', it doesn't accept new dependencies."
			)
		else:
			dep_env = DepEnv ( dep_str )
			self.dep_env_list.append ( dep_env )
			self._depres_master.enqueue ( dep_env, self.ident )

	# --- end of add_dependency (...) ---

	def add_dependencies ( self, dep_list ):
		"""Adds dependency strings to this channel. See add_dependency (...)
		for details.

		arguments:
		* dep_list --

		raises: passes Exception if this channel is done

		returns: None (implicit)
		"""
		for dep_str in dep_list: self.add_dependency ( dep_str )
	# --- end of add_dependencies (...) ---

	def lookup ( self, dep_str ):
		"""Looks up a specific dep str. Use the (faster) collect_dependencies()
		method for getting all dependencies if order doesn't matter.

		! It assumes that dep_str is unique in this channel.

		arguments:
		* dep_str -- to be looked up

		raises: Exception if dep_str not in the dep env list.
		"""
		# it's no requirement that this channel is done when calling this method
		for de in self.dep_env_list:
			if de.dep_str == dep_str:
				return de.get_result() [1]

		raise Exception (
			"bad usage: %s not in channel's dep env list!" % dep_str
		)
	# --- end of lookup (...) ---

	def collect_dependencies ( self ):
		"""Returns a list that contains all resolved deps,
		including ignored deps that resolve to None.

		You have to call satisfy_request(...) before using this method.

		raises: Exception
		"""
		if not self._collected_deps is None:
			self.logger.debug (
				"returning collected deps: %s." % self._collected_deps
			)
			return self._collected_deps
		raise Exception ( "cannot do that" )
	# --- end of collect_dependencies (...) ---

	def satisfy_request ( self, close_if_unresolvable=True ):
		"""Tells to the dependency resolver to run.
		It blocks until this channel is done, which means that either all
		deps are resolved or one is unresolvable.

		arguments:
		* close_if_unresolvable -- close the channel if one dep is unresolvable
		                           this seems reasonable and defaults to True

		Returns the list of resolved dependencies if all could be resolved,
		else None.
		"""

		# using a set allows easy difference() operations between
		# DEPEND/RDEPEND/.. later, seewave requires sci-libs/fftw
		# in both DEPEND and RDEPEND for example
		dep_collected = set()
		satisfiable   = True


		def handle_queue_item ( dep_env ):
			self._depdone += 1
			if dep_env.is_resolved():
				### and dep_env in self.dep_env_list
				# successfully resolved
				dep_collected.add ( dep_env.get_result() [1] )
				self._depres_queue.task_done()
				return True
			else:
				# failed
				self._depres_queue.task_done()
				return False
		# --- end of handle_queue_item (...) ---

		while self._depdone < len ( self.dep_env_list ) and satisfiable:
			# tell the resolver to start
			self._depres_master.start()

			# wait for one result at least
			satisfiable = handle_queue_item ( self._depres_queue.get() )

			while not self._depres_queue.empty() and satisfiable:
				satisfiable = handle_queue_item ( self._depres_queue.get_nowait() )
		# --- end while

		if satisfiable:
			self._collected_deps = dep_collected
			return self._collected_deps
		else:
			if close_if_unresolvable: self.close()
			return None


	# --- end of join (...) ---