aboutsummaryrefslogtreecommitdiffstats
path: root/pym/portage/eclass_cache.py
blob: cb2cf8a980892bd26aa8c0123421c84d9e6dd01a (plain) (blame)
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
# Copyright 2005-2011 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
# Author(s): Nicholas Carpaski (carpaski@gentoo.org), Brian Harring (ferringb@gentoo.org)

__all__ = ["cache"]

import stat
import sys
import operator
from portage.util import normalize_path
import errno
from portage.exception import FileNotFound, PermissionDenied
from portage import os
from portage import checksum

if sys.hexversion >= 0x3000000:
	long = int


class hashed_path(object):

	def __init__(self, location):
		self.location = location

	def __getattr__(self, attr):
		if attr == 'mtime':
			# use stat.ST_MTIME; accessing .st_mtime gets you a float
			# depending on the python version, and long(float) introduces
			# some rounding issues that aren't present for people using
			# the straight c api.
			# thus use the defacto python compatibility work around;
			# access via index, which guarantees you get the raw long.
			try:
				self.mtime = obj = os.stat(self.location)[stat.ST_MTIME]
			except OSError as e:
				if e.errno in (errno.ENOENT, errno.ESTALE):
					raise FileNotFound(self.location)
				elif e.errno == PermissionDenied.errno:
					raise PermissionDenied(self.location)
				raise
			return obj
		if not attr.islower():
			# we don't care to allow .mD5 as an alias for .md5
			raise AttributeError(attr)
		hashname = attr.upper()
		if hashname not in checksum.hashfunc_map:
			raise AttributeError(attr)
		val = checksum.perform_checksum(self.location, hashname)[0]
		setattr(self, attr, val)
		return val

	def __repr__(self):
		return "<portage.eclass_cache.hashed_path('%s')>" % (self.location,)

class cache(object):
	"""
	Maintains the cache information about eclasses used in ebuild.
	"""
	def __init__(self, porttree_root, overlays=[]):

		self.eclasses = {} # {"Name": hashed_path}
		self._eclass_locations = {}

		# screw with the porttree ordering, w/out having bash inherit match it, and I'll hurt you.
		# ~harring
		if porttree_root:
			self.porttree_root = porttree_root
			self.porttrees = [self.porttree_root] + overlays
			self.porttrees = tuple(map(normalize_path, self.porttrees))
			self._master_eclass_root = os.path.join(self.porttrees[0], "eclass")
			self.update_eclasses()
		else:
			self.porttree_root = None
			self.porttrees = ()
			self._master_eclass_root = None

	def copy(self):
		return self.__copy__()

	def __copy__(self):
		result = self.__class__(None)
		result.eclasses = self.eclasses.copy()
		result._eclass_locations = self._eclass_locations.copy()
		result.porttree_root = self.porttree_root
		result.porttrees = self.porttrees
		result._master_eclass_root = self._master_eclass_root
		return result

	def append(self, other):
		"""
		Append another instance to this instance. This will cause eclasses
		from the other instance to override any eclasses from this instance
		that have the same name.
		"""
		if not isinstance(other, self.__class__):
			raise TypeError(
				"expected type %s, got %s" % (self.__class__, type(other)))
		self.porttrees = self.porttrees + other.porttrees
		self.eclasses.update(other.eclasses)
		self._eclass_locations.update(other._eclass_locations)

	def update_eclasses(self):
		self.eclasses = {}
		self._eclass_locations = {}
		master_eclasses = {}
		eclass_len = len(".eclass")
		ignored_listdir_errnos = (errno.ENOENT, errno.ENOTDIR)
		for x in [normalize_path(os.path.join(y,"eclass")) for y in self.porttrees]:
			try:
				eclass_filenames = os.listdir(x)
			except OSError as e:
				if e.errno in ignored_listdir_errnos:
					del e
					continue
				elif e.errno == PermissionDenied.errno:
					raise PermissionDenied(x)
				raise
			for y in eclass_filenames:
				if not y.endswith(".eclass"):
					continue
				obj = hashed_path(os.path.join(x, y))
				obj.eclass_dir = x
				try:
					mtime = obj.mtime
				except FileNotFound:
					continue
				ys=y[:-eclass_len]
				if x == self._master_eclass_root:
					master_eclasses[ys] = mtime
					self.eclasses[ys] = obj
					self._eclass_locations[ys] = x
					continue

				master_mtime = master_eclasses.get(ys)
				if master_mtime is not None:
					if master_mtime == mtime:
						# It appears to be identical to the master,
						# so prefer the master entry.
						continue

				self.eclasses[ys] = obj
				self._eclass_locations[ys] = x

	def validate_and_rewrite_cache(self, ec_dict, chf_type, stores_paths):
		"""
		This will return an empty dict if the ec_dict parameter happens
		to be empty, therefore callers must take care to distinguish
		between empty dict and None return values.
		"""
		if not isinstance(ec_dict, dict):
			return None
		our_getter = operator.attrgetter(chf_type)
		cache_getter = lambda x:x
		if stores_paths:
			cache_getter = operator.itemgetter(1)
		d = {}
		for eclass, ec_data in ec_dict.items():
			cached_data = self.eclasses.get(eclass)
			if cached_data is None:
				return None
			if cache_getter(ec_data) != our_getter(cached_data):
				return None
			d[eclass] = cached_data
		return d

	def get_eclass_data(self, inherits):
		ec_dict = {}
		for x in inherits:
			ec_dict[x] = self.eclasses[x]

		return ec_dict