Use isvalidatom to validate PROVIDE entries.
[portage.git] / pym / _emerge / Package.py
1 # Copyright 1999-2009 Gentoo Foundation
2 # Distributed under the terms of the GNU General Public License v2
3 # $Id$
4
5 import re
6 import sys
7 from itertools import chain
8 import portage
9 from portage.cache.mappings import slot_dict_class
10 from portage.dep import isvalidatom, paren_reduce, use_reduce, \
11         paren_normalize, paren_enclose, _slot_re
12 from _emerge.Task import Task
13
14 if sys.hexversion >= 0x3000000:
15         basestring = str
16         long = int
17
18 class Package(Task):
19
20         __hash__ = Task.__hash__
21         __slots__ = ("built", "cpv", "depth",
22                 "installed", "metadata", "onlydeps", "operation",
23                 "root_config", "type_name",
24                 "category", "counter", "cp", "cpv_split",
25                 "inherited", "invalid", "iuse", "masks", "mtime",
26                 "pf", "pv_split", "root", "slot", "slot_atom", "visible",) + \
27         ("_use",)
28
29         metadata_keys = [
30                 "BUILD_TIME", "CHOST", "COUNTER", "DEPEND", "EAPI",
31                 "INHERITED", "IUSE", "KEYWORDS",
32                 "LICENSE", "PDEPEND", "PROVIDE", "RDEPEND",
33                 "repository", "PROPERTIES", "RESTRICT", "SLOT", "USE", "_mtime_"]
34
35         def __init__(self, **kwargs):
36                 Task.__init__(self, **kwargs)
37                 self.root = self.root_config.root
38                 self.metadata = _PackageMetadataWrapper(self, self.metadata)
39                 if not self.built:
40                         self.metadata['CHOST'] = self.root_config.settings.get('CHOST', '')
41                 self.cp = portage.cpv_getkey(self.cpv)
42                 slot = self.slot
43                 if _slot_re.match(slot) is None:
44                         self._invalid_metadata('SLOT.invalid',
45                                 "SLOT: invalid value: '%s'" % slot)
46                         # Avoid an InvalidAtom exception when creating slot_atom.
47                         # This package instance will be masked due to empty SLOT.
48                         slot = '0'
49                 self.slot_atom = portage.dep.Atom("%s:%s" % (self.cp, slot))
50                 self.category, self.pf = portage.catsplit(self.cpv)
51                 self.cpv_split = portage.catpkgsplit(self.cpv)
52                 self.pv_split = self.cpv_split[1:]
53                 self.masks = self._masks()
54                 self.visible = self._visible(self.masks)
55
56         def _masks(self):
57                 masks = {}
58                 settings = self.root_config.settings
59
60                 if self.invalid is not None:
61                         masks['invalid'] = self.invalid
62
63                 if not settings._accept_chost(self.cpv, self.metadata):
64                         masks['CHOST'] = self.metadata['CHOST']
65
66                 eapi = self.metadata["EAPI"]
67                 if not portage.eapi_is_supported(eapi):
68                         masks['EAPI.unsupported'] = eapi
69                 if portage._eapi_is_deprecated(eapi):
70                         masks['EAPI.deprecated'] = eapi
71
72                 missing_keywords = settings._getMissingKeywords(
73                         self.cpv, self.metadata)
74                 if missing_keywords:
75                         masks['KEYWORDS'] = missing_keywords
76
77                 try:
78                         missing_properties = settings._getMissingProperties(
79                                 self.cpv, self.metadata)
80                         if missing_properties:
81                                 masks['PROPERTIES'] = missing_properties
82                 except portage.exception.InvalidDependString:
83                         # already recorded as 'invalid'
84                         pass
85
86                 mask_atom = settings._getMaskAtom(self.cpv, self.metadata)
87                 if mask_atom is not None:
88                         masks['package.mask'] = mask_atom
89
90                 system_mask = settings._getProfileMaskAtom(
91                         self.cpv, self.metadata)
92                 if system_mask is not None:
93                         masks['profile.system'] = system_mask
94
95                 try:
96                         missing_licenses = settings._getMissingLicenses(
97                                 self.cpv, self.metadata)
98                         if missing_licenses:
99                                 masks['LICENSE'] = missing_licenses
100                 except portage.exception.InvalidDependString:
101                         # already recorded as 'invalid'
102                         pass
103
104                 if not masks:
105                         masks = None
106
107                 return masks
108
109         def _visible(self, masks):
110
111                 if masks is not None:
112
113                         if 'EAPI.unsupported' in masks:
114                                 return False
115
116                         if not self.installed and ( \
117                                 'invalid' in masks or \
118                                 'CHOST' in masks or \
119                                 'EAPI.deprecated' in masks or \
120                                 'KEYWORDS' in masks or \
121                                 'PROPERTIES' in masks):
122                                 return False
123
124                         if 'package.mask' in masks or \
125                                 'profile.system' in masks or \
126                                 'LICENSE' in masks:
127                                 return False
128
129                 return True
130
131         def _invalid_metadata(self, msg_type, msg):
132                 if self.invalid is None:
133                         self.invalid = {}
134                 msgs = self.invalid.get(msg_type)
135                 if msgs is None:
136                         msgs = []
137                         self.invalid[msg_type] = msgs
138                 msgs.append(msg)
139
140         class _use_class(object):
141
142                 __slots__ = ("__weakref__", "enabled")
143
144                 def __init__(self, use):
145                         self.enabled = frozenset(use)
146
147         @property
148         def use(self):
149                 if self._use is None:
150                         self._use = self._use_class(self.metadata['USE'].split())
151                 return self._use
152
153         class _iuse(object):
154
155                 __slots__ = ("__weakref__", "all", "enabled", "disabled",
156                         "iuse_implicit", "tokens") + \
157                         ('_regex',)
158
159                 def __init__(self, tokens, iuse_implicit):
160                         self.tokens = tuple(tokens)
161                         self.iuse_implicit = iuse_implicit
162                         enabled = []
163                         disabled = []
164                         other = []
165                         for x in tokens:
166                                 prefix = x[:1]
167                                 if prefix == "+":
168                                         enabled.append(x[1:])
169                                 elif prefix == "-":
170                                         disabled.append(x[1:])
171                                 else:
172                                         other.append(x)
173                         self.enabled = frozenset(enabled)
174                         self.disabled = frozenset(disabled)
175                         self.all = frozenset(chain(enabled, disabled, other))
176
177                 @property
178                 def regex(self):
179                         """
180                         @returns: A regular expression that matches valid USE values which
181                                 may be specified in USE dependencies.
182                         """
183                         try:
184                                 return self._regex
185                         except AttributeError:
186                                 # Escape anything except ".*" which is supposed
187                                 # to pass through from _get_implicit_iuse()
188                                 regex = (re.escape(x) for x in \
189                                         chain(self.all, self.iuse_implicit))
190                                 regex = "^(%s)$" % "|".join(regex)
191                                 regex = re.compile(regex.replace("\\.\\*", ".*"))
192                                 self._regex = regex
193                                 return regex
194
195         def _get_hash_key(self):
196                 hash_key = getattr(self, "_hash_key", None)
197                 if hash_key is None:
198                         if self.operation is None:
199                                 self.operation = "merge"
200                                 if self.onlydeps or self.installed:
201                                         self.operation = "nomerge"
202                         self._hash_key = \
203                                 (self.type_name, self.root, self.cpv, self.operation)
204                 return self._hash_key
205
206         def __lt__(self, other):
207                 if other.cp != self.cp:
208                         return False
209                 if portage.pkgcmp(self.pv_split, other.pv_split) < 0:
210                         return True
211                 return False
212
213         def __le__(self, other):
214                 if other.cp != self.cp:
215                         return False
216                 if portage.pkgcmp(self.pv_split, other.pv_split) <= 0:
217                         return True
218                 return False
219
220         def __gt__(self, other):
221                 if other.cp != self.cp:
222                         return False
223                 if portage.pkgcmp(self.pv_split, other.pv_split) > 0:
224                         return True
225                 return False
226
227         def __ge__(self, other):
228                 if other.cp != self.cp:
229                         return False
230                 if portage.pkgcmp(self.pv_split, other.pv_split) >= 0:
231                         return True
232                 return False
233
234 _all_metadata_keys = set(x for x in portage.auxdbkeys \
235         if not x.startswith("UNUSED_"))
236 _all_metadata_keys.update(Package.metadata_keys)
237 _all_metadata_keys = frozenset(_all_metadata_keys)
238
239 _PackageMetadataWrapperBase = slot_dict_class(_all_metadata_keys)
240
241 class _PackageMetadataWrapper(_PackageMetadataWrapperBase):
242         """
243         Detect metadata updates and synchronize Package attributes.
244         """
245
246         __slots__ = ("_pkg",)
247         _wrapped_keys = frozenset(
248                 ["COUNTER", "INHERITED", "IUSE", "SLOT", "_mtime_"])
249         _use_conditional_keys = frozenset(
250                 ['LICENSE', 'PROPERTIES', 'PROVIDE', 'RESTRICT',])
251
252         def __init__(self, pkg, metadata):
253                 _PackageMetadataWrapperBase.__init__(self)
254                 self._pkg = pkg
255                 if not pkg.built:
256                         # USE is lazy, but we want it to show up in self.keys().
257                         self['USE'] = ''
258
259                 self.update(metadata)
260
261         def __getitem__(self, k):
262                 v = _PackageMetadataWrapperBase.__getitem__(self, k)
263                 if k in self._use_conditional_keys:
264                         if self._pkg.root_config.settings.local_config and '?' in v:
265                                 try:
266                                         v = paren_enclose(paren_normalize(use_reduce(
267                                                 paren_reduce(v), uselist=self._pkg.use.enabled)))
268                                 except portage.exception.InvalidDependString:
269                                         # This error should already have been registered via
270                                         # self._pkg._invalid_metadata().
271                                         pass
272                                 else:
273                                         self[k] = v
274
275                 elif k == 'USE' and not self._pkg.built:
276                         if not v:
277                                 # This is lazy because it's expensive.
278                                 pkgsettings = self._pkg.root_config.trees[
279                                         'porttree'].dbapi.doebuild_settings
280                                 pkgsettings.setcpv(self._pkg)
281                                 v = pkgsettings["PORTAGE_USE"]
282                                 self['USE'] = v
283
284                 return v
285
286         def __setitem__(self, k, v):
287                 _PackageMetadataWrapperBase.__setitem__(self, k, v)
288                 if k in self._wrapped_keys:
289                         getattr(self, "_set_" + k.lower())(k, v)
290                 elif k in self._use_conditional_keys:
291                         try:
292                                 reduced = use_reduce(paren_reduce(v), matchall=1)
293                         except portage.exception.InvalidDependString as e:
294                                 self._pkg._invalid_metadata(k + ".syntax", "%s: %s" % (k, e))
295                         else:
296                                 if reduced and k == 'PROVIDE':
297                                         for x in portage.flatten(reduced):
298                                                 if not isvalidatom(x):
299                                                         self._pkg._invalid_metadata(k + ".syntax",
300                                                                 "%s: %s" % (k, x))
301
302         def _set_inherited(self, k, v):
303                 if isinstance(v, basestring):
304                         v = frozenset(v.split())
305                 self._pkg.inherited = v
306
307         def _set_iuse(self, k, v):
308                 self._pkg.iuse = self._pkg._iuse(
309                         v.split(), self._pkg.root_config.iuse_implicit)
310
311         def _set_slot(self, k, v):
312                 self._pkg.slot = v
313
314         def _set_counter(self, k, v):
315                 if isinstance(v, basestring):
316                         try:
317                                 v = long(v.strip())
318                         except ValueError:
319                                 v = 0
320                 self._pkg.counter = v
321
322         def _set__mtime_(self, k, v):
323                 if isinstance(v, basestring):
324                         try:
325                                 v = long(v.strip())
326                         except ValueError:
327                                 v = 0
328                 self._pkg.mtime = v
329
330         @property
331         def properties(self):
332                 return self['PROPERTIES'].split()
333
334         @property
335         def restrict(self):
336                 return self['RESTRICT'].split()