aboutsummaryrefslogtreecommitdiff
blob: 7e4bbea26d275d8a475b72f314fa0ae2f3e80621 (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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
# R Overlay -- <comment TODO>
# Copyright 2006-2012 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2

import threading
import logging
import shutil
import os

from roverlay import config, util

from roverlay.overlay.category import Category

DEFAULT_USE_DESC = '\n'.join ( [
	'byte-compile - enable byte compiling',
	'R_suggests - install recommended packages'
] )

class Overlay ( object ):

	def __init__ (
		self,
		name=None,
		logger=None,
		directory=None,
		default_category=None,
		eclass_files=None
	):

		# not setting any default values here (currently)

		if name is None:
			self.name = config.get_or_fail ( 'OVERLAY.name' )
		else:
			self.name = name

		if logger is None:
			#self.logger = logging.getLogger ( self.name )
			self.logger = logging.getLogger ( 'overlay' )
		else:
			#self.logger = logger.getChild ( self.name )
			self.logger = logger.getChild ( 'overlay' )

		if directory is None:
			self.physical_location = config.get_or_fail ( 'OVERLAY.dir' )
		else:
			self.physical_location = directory

		if default_category is None:
			self.default_category = config.get_or_fail ( 'OVERLAY.category' )
		else:
			self.default_category = default_category

		self.eclass_files = eclass_files

		#
		self._profiles_dir = os.path.join ( self.physical_location, 'profiles' )

		self._catlock        = threading.Lock()
		self._categories     = dict()
		self._default_header = config.get ( 'EBUILD.default_header', None )
	# --- end of __init__ (...) ---

	def _get_category ( self, category ):
		"""Returns a reference to the given category. Creates it if necessary.

		arguments:
		* category -- category identifier as string
		"""
		if not category in self._categories:
			self._catlock.acquire()
			if not category in self._categories:
				self._categories [category] = Category (
					category,
					self.logger,
					None if self.physical_location is None else \
						os.path.join ( self.physical_location, category )
				)
			self._catlock.release()

		return self._categories [category]
	# --- end of _get_category (...) ---

	def add ( self, package_info, category=None ):
		"""Adds a package to this overlay.

		arguments:
		* package_info -- PackageInfo of the package to add
		* category     -- category where the pkg should be put in, defaults to
		                   self.default_category

		returns: None (implicit)
		"""
		self._get_category (
			self.default_category if category is None else category
		) . add ( package_info )
	# --- end of add (...) ---

	def show ( self, **show_kw ):
		"""Presents the ebuilds/metadata stored in this overlay.

		arguments:
		* **show_kw -- ignored! (keywords for package.PackageDir.show(...))

		returns: None (implicit)
		"""
		for cat in self._categories.values():
			cat.show ( default_header=self._default_header )
	# --- end of show (...) ---

	def write ( self, **write_kw ):
		"""Writes the overlay to its physical location (filesystem), including
		metadata and Manifest files.
		TODO include Manifest generation in package.py

		arguments:
		* **write_kw -- ignored! (keywords for package.PackageDir.write(...))

		returns: None (implicit)

		raises: !! TODO

		TODO/FIXME/DOC: This is not thread-safe, it's expected to be called
		when ebuild creation is done.
		"""
		# writing profiles/ here, rewriting categories/ later
		self._init_overlay ( reimport_eclass=True, make_profiles_dir=True )

		for cat in self._categories.values():
			if cat.physical_location and not cat.empty():
				util.dodir ( cat.physical_location )
				cat.write ( default_header=self._default_header )

		self._write_categories ( only_active=True )
	# --- end of write (...) ---

	def write_incremental ( self, **write_kw ):
		"""Writes all ebuilds that have been added since the last
		write_incremental call.
		TODO:
		* This could be useful to save some mem by removing already written
		package infos.
		* This has to be thread safe
		"""
		raise Exception ( "method stub" )
	# --- end of write_incremental (...) ---

	def generate_metadata ( self, **metadata_kw ):
		"""Tells the overlay's categories to create metadata.
		You don't have to call this before write()/show() unless you want to use
		special metadata options.

		arguments:
		* **metadata_kw -- keywords for package.PackageDir.generate_metadata(...)

		returns: None (implicit)
		"""
		for cat in self._categories.values():
			cat.generate_metadata ( **metadata_kw )
	# --- end of generate_metadata (...) ---

	def generate_manifest ( self, **manifest_kw ):
		"""Generates Manifest files for all ebuilds in this overlay that exist
		physically/in filesystem.
		Manifest files are automatically created when calling write().

		arguments:
		* **manifest_kw -- see PackageDir.generate_manifest(...)

		returns: None (implicit)
		"""
		for cat in self._categories.values():
			cat.generate_manifest ( **manifest_kw )
	# --- end of generate_manifest (...) ---

	def _write_profiles_dir ( self, only_active_categories=True ):
		"""Creates and updates the profiles/ dir.

		arguments:
		* only_active_categories -- if True: do not list categories without
		                                      ebuilds in profiles/categories
		"""
		# profiles/
		util.dodir ( self._profiles_dir )
		self._write_repo_name()
		self._write_categories ( only_active=only_active_categories )
		self._write_usedesc()
	# --- end of _write_profiles_dir (...) ---

	def _write_profiles_file ( self, filename, to_write ):
		"""Writes a file in profiles/.

		arguments:
		* filename -- name of the file to write (including file extension)
		* to_write -- string to write (don't forget newline at the end)
		"""
		fh = None
		try:
			fh = open ( os.path.join ( self._profiles_dir, filename ), 'w' )
			if to_write:
				# else touch file
				fh.write ( to_write )
		except IOError as e:
			self.logger.exception ( e )
			raise
		finally:
			if fh: fh.close()
	# --- end of _write_profiles_file (...) ---

	def _write_repo_name ( self ):
		"""Writes profiles/repo_name."""
		self._write_profiles_file ( 'repo_name', self.name + '\n' )
	# --- end of _write_repo_name (...) ---

	def _write_categories ( self, only_active=True ):
		"""Writes profiles/categories.

		arguments:
		* only_active -- exclude categories without ebuilds
		"""
		cats = None
		if only_active:
			cats = [
				name for name, category
					in self._categories.items() if not category.empty()
			]
		else:
			cats = list ( self._categories.keys() )

		if cats:
			self._write_profiles_file (
				'categories',
				'\n'.join ( cats ) + '\n'
			)
	# --- end of _write_categories (...) ---

	def _write_usedesc ( self ):
		"""Writes profiles/use.desc."""
		# TODO: config entry
		use_desc = config.get (
			'OVERLAY.use_desc',
			fallback_value=DEFAULT_USE_DESC
		)
		if use_desc:
			self._write_profiles_file ( 'use.desc', use_desc + '\n' )
	# --- end of _write_usedesc (...) ---

	def _import_eclass ( self, reimport_eclass ):

		if self.eclass_files:
			# import eclass files
			eclass_dir = os.path.join ( self.physical_location, 'eclass' )
			try:
				util.dodir ( eclass_dir )

				for eclass in self.eclass_files:
					src  = eclass
					dest = None
					if isinstance ( eclass, str ):
						dest = os.path.basename ( eclass )
					else:
						# list-like specification ( src, destname )
						src  = eclass [0]
						dest = eclass [1]

					dest = os.path.join ( eclass_dir, dest )

					if reimport_eclass or not os.path.isfile ( dest ):
						shutil.copyfile ( src, dest )


			except Exception as e:
				#self.logger.exception ( e ) TODO try-catch blocks
				self.logger.critical ( "Cannot import eclass files!" )
				raise
	# --- end of _import_eclass (...) ---

	def _init_overlay ( self, reimport_eclass=False, make_profiles_dir=False ):
		"""Initializes the overlay at its physical/filesystem location.

		arguments:
		* reimport_eclass   -- whether to copy existing eclass files
		                         again (True) or not
		* make_profiles_dir -- if True: create the profiles/ dir now

		raises:
		* Exception if no physical location assigned
		* <TODO> passes IOError,...
		"""
		if self.physical_location is None:
			raise Exception ( "no directory assigned." )

		try:
			# mkdir overlay root
			os.makedirs ( self.physical_location, exist_ok=True ) # raises?

			self._import_eclass ( reimport_eclass )

			if make_profiles_dir:
				self._write_profiles_dir ( only_active_categories=False )

		except IOError as e:

			self.logger.exception ( e )
			self.logger.critical ( "^failed to init overlay" )
			raise