Pass is_valid_flag to use_reduce if possible
[portage.git] / pym / _emerge / Package.py
1 # Copyright 1999-2010 Gentoo Foundation
2 # Distributed under the terms of the GNU General Public License v2
3
4 import re
5 import sys
6 from itertools import chain
7 import portage
8 from portage.cache.mappings import slot_dict_class
9 from portage.dep import isvalidatom, use_reduce, \
10         paren_enclose, _slot_re
11 from portage.eapi import eapi_has_iuse_defaults, eapi_has_required_use
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         ("_raw_metadata", "_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",
34                 "_mtime_", "DEFINED_PHASES", "REQUIRED_USE"]
35
36         def __init__(self, **kwargs):
37                 Task.__init__(self, **kwargs)
38                 self.root = self.root_config.root
39                 self._raw_metadata = _PackageMetadataWrapperBase(self.metadata)
40                 self.metadata = _PackageMetadataWrapper(self, self._raw_metadata)
41                 if not self.built:
42                         self.metadata['CHOST'] = self.root_config.settings.get('CHOST', '')
43                 self.cp = portage.cpv_getkey(self.cpv)
44                 slot = self.slot
45                 if _slot_re.match(slot) is None:
46                         self._invalid_metadata('SLOT.invalid',
47                                 "SLOT: invalid value: '%s'" % slot)
48                         # Avoid an InvalidAtom exception when creating slot_atom.
49                         # This package instance will be masked due to empty SLOT.
50                         slot = '0'
51                 if (self.iuse.enabled or self.iuse.disabled) and \
52                         not eapi_has_iuse_defaults(self.metadata["EAPI"]):
53                         self._invalid_metadata('IUSE.invalid',
54                                 "IUSE contains defaults, but EAPI doesn't allow them")
55                 if self.metadata.get("REQUIRED_USE") and \
56                         not eapi_has_required_use(self.metadata["EAPI"]):
57                         self._invalid_metadata('REQUIRED_USE.invalid',
58                                 "REQUIRED_USE set, but EAPI doesn't allow it")
59                 self.slot_atom = portage.dep.Atom("%s:%s" % (self.cp, slot))
60                 self.category, self.pf = portage.catsplit(self.cpv)
61                 self.cpv_split = portage.catpkgsplit(self.cpv)
62                 self.pv_split = self.cpv_split[1:]
63                 self.masks = self._masks()
64                 self.visible = self._visible(self.masks)
65
66         def copy(self):
67                 return Package(built=self.built, cpv=self.cpv, depth=self.depth,
68                         installed=self.installed, metadata=self._raw_metadata,
69                         onlydeps=self.onlydeps, operation=self.operation,
70                         root_config=self.root_config, type_name=self.type_name)
71
72         def _masks(self):
73                 masks = {}
74                 settings = self.root_config.settings
75
76                 if self.invalid is not None:
77                         masks['invalid'] = self.invalid
78
79                 if not settings._accept_chost(self.cpv, self.metadata):
80                         masks['CHOST'] = self.metadata['CHOST']
81
82                 eapi = self.metadata["EAPI"]
83                 if not portage.eapi_is_supported(eapi):
84                         masks['EAPI.unsupported'] = eapi
85                 if portage._eapi_is_deprecated(eapi):
86                         masks['EAPI.deprecated'] = eapi
87
88                 missing_keywords = settings._getMissingKeywords(
89                         self.cpv, self.metadata)
90                 if missing_keywords:
91                         masks['KEYWORDS'] = missing_keywords
92
93                 try:
94                         missing_properties = settings._getMissingProperties(
95                                 self.cpv, self.metadata)
96                         if missing_properties:
97                                 masks['PROPERTIES'] = missing_properties
98                 except portage.exception.InvalidDependString:
99                         # already recorded as 'invalid'
100                         pass
101
102                 mask_atom = settings._getMaskAtom(self.cpv, self.metadata)
103                 if mask_atom is not None:
104                         masks['package.mask'] = mask_atom
105
106                 system_mask = settings._getProfileMaskAtom(
107                         self.cpv, self.metadata)
108                 if system_mask is not None:
109                         masks['profile.system'] = system_mask
110
111                 try:
112                         missing_licenses = settings._getMissingLicenses(
113                                 self.cpv, self.metadata)
114                         if missing_licenses:
115                                 masks['LICENSE'] = missing_licenses
116                 except portage.exception.InvalidDependString:
117                         # already recorded as 'invalid'
118                         pass
119
120                 if not masks:
121                         masks = None
122
123                 return masks
124
125         def _visible(self, masks):
126
127                 if masks is not None:
128
129                         if 'EAPI.unsupported' in masks:
130                                 return False
131
132                         if not self.installed and ( \
133                                 'invalid' in masks or \
134                                 'CHOST' in masks or \
135                                 'EAPI.deprecated' in masks or \
136                                 'KEYWORDS' in masks or \
137                                 'PROPERTIES' in masks):
138                                 return False
139
140                         if 'package.mask' in masks or \
141                                 'profile.system' in masks or \
142                                 'LICENSE' in masks:
143                                 return False
144
145                 return True
146
147         def _invalid_metadata(self, msg_type, msg):
148                 if self.invalid is None:
149                         self.invalid = {}
150                 msgs = self.invalid.get(msg_type)
151                 if msgs is None:
152                         msgs = []
153                         self.invalid[msg_type] = msgs
154                 msgs.append(msg)
155
156         def __str__(self):
157                 if self.operation is None:
158                         self.operation = "merge"
159                         if self.onlydeps or self.installed:
160                                 self.operation = "nomerge"
161
162                 if self.operation == "merge":
163                         if self.type_name == "binary":
164                                 cpv_color = "PKG_BINARY_MERGE"
165                         else:
166                                 cpv_color = "PKG_MERGE"
167                 elif self.operation == "uninstall":
168                         cpv_color = "PKG_UNINSTALL"
169                 else:
170                         cpv_color = "PKG_NOMERGE"
171
172                 s = "(%s, %s" \
173                         % (portage.output.colorize(cpv_color, self.cpv) , self.type_name)
174
175                 if self.type_name == "installed":
176                         if self.root != "/":
177                                 s += " in '%s'" % self.root
178                         if self.operation == "uninstall":
179                                 s += " scheduled for uninstall"
180                 else:
181                         if self.operation == "merge":
182                                 s += " scheduled for merge"
183                                 if self.root != "/":
184                                         s += " to '%s'" % self.root
185                 s += ")"
186                 return s
187
188         class _use_class(object):
189
190                 __slots__ = ("__weakref__", "enabled")
191
192                 def __init__(self, use):
193                         self.enabled = frozenset(use)
194
195         @property
196         def use(self):
197                 if self._use is None:
198                         self._use = self._use_class(self.metadata['USE'].split())
199                 return self._use
200
201         class _iuse(object):
202
203                 __slots__ = ("__weakref__", "all", "enabled", "disabled",
204                         "tokens") + ("_iuse_implicit_regex",)
205
206                 def __init__(self, tokens, iuse_implicit_regex):
207                         self.tokens = tuple(tokens)
208                         self._iuse_implicit_regex = iuse_implicit_regex
209                         enabled = []
210                         disabled = []
211                         other = []
212                         for x in tokens:
213                                 prefix = x[:1]
214                                 if prefix == "+":
215                                         enabled.append(x[1:])
216                                 elif prefix == "-":
217                                         disabled.append(x[1:])
218                                 else:
219                                         other.append(x)
220                         self.enabled = frozenset(enabled)
221                         self.disabled = frozenset(disabled)
222                         self.all = frozenset(chain(enabled, disabled, other))
223
224                 def is_valid_flag(self, flags):
225                         """
226                         @returns: True if all flags are valid USE values which may
227                                 be specified in USE dependencies, False otherwise.
228                         """
229                         if isinstance(flags, basestring):
230                                 flags = [flags]
231
232                         for flag in flags:
233                                 if not flag in self.all and \
234                                         self._iuse_implicit_regex.match(flag) is None:
235                                         return False
236                         return True
237                 
238                 def get_missing_iuse(self, flags):
239                         """
240                         @returns: A list of flags missing from IUSE.
241                         """
242                         if isinstance(flags, basestring):
243                                 flags = [flags]
244                         missing_iuse = []
245                         for flag in flags:
246                                 if not flag in self.all and \
247                                         self._iuse_implicit_regex.match(flag) is None:
248                                         missing_iuse.append(flag)
249                         return missing_iuse
250
251         def _get_hash_key(self):
252                 hash_key = getattr(self, "_hash_key", None)
253                 if hash_key is None:
254                         if self.operation is None:
255                                 self.operation = "merge"
256                                 if self.onlydeps or self.installed:
257                                         self.operation = "nomerge"
258                         self._hash_key = \
259                                 (self.type_name, self.root, self.cpv, self.operation)
260                 return self._hash_key
261
262         def __lt__(self, other):
263                 if other.cp != self.cp:
264                         return False
265                 if portage.pkgcmp(self.pv_split, other.pv_split) < 0:
266                         return True
267                 return False
268
269         def __le__(self, other):
270                 if other.cp != self.cp:
271                         return False
272                 if portage.pkgcmp(self.pv_split, other.pv_split) <= 0:
273                         return True
274                 return False
275
276         def __gt__(self, other):
277                 if other.cp != self.cp:
278                         return False
279                 if portage.pkgcmp(self.pv_split, other.pv_split) > 0:
280                         return True
281                 return False
282
283         def __ge__(self, other):
284                 if other.cp != self.cp:
285                         return False
286                 if portage.pkgcmp(self.pv_split, other.pv_split) >= 0:
287                         return True
288                 return False
289
290 _all_metadata_keys = set(x for x in portage.auxdbkeys \
291         if not x.startswith("UNUSED_"))
292 _all_metadata_keys.update(Package.metadata_keys)
293 _all_metadata_keys = frozenset(_all_metadata_keys)
294
295 _PackageMetadataWrapperBase = slot_dict_class(_all_metadata_keys)
296
297 class _PackageMetadataWrapper(_PackageMetadataWrapperBase):
298         """
299         Detect metadata updates and synchronize Package attributes.
300         """
301
302         __slots__ = ("_pkg",)
303         _wrapped_keys = frozenset(
304                 ["COUNTER", "INHERITED", "IUSE", "SLOT", "USE", "_mtime_"])
305         _use_conditional_keys = frozenset(
306                 ['LICENSE', 'PROPERTIES', 'PROVIDE', 'RESTRICT',])
307
308         def __init__(self, pkg, metadata):
309                 _PackageMetadataWrapperBase.__init__(self)
310                 self._pkg = pkg
311                 if not pkg.built:
312                         # USE is lazy, but we want it to show up in self.keys().
313                         _PackageMetadataWrapperBase.__setitem__(self, 'USE', '')
314
315                 self.update(metadata)
316
317         def __getitem__(self, k):
318                 v = _PackageMetadataWrapperBase.__getitem__(self, k)
319                 if k in self._use_conditional_keys:
320                         if self._pkg.root_config.settings.local_config and '?' in v:
321                                 try:
322                                         v = paren_enclose(use_reduce(v, uselist=self._pkg.use.enabled, \
323                                                 is_valid_flag=self._pkg.iuse.is_valid_flag))
324                                 except portage.exception.InvalidDependString:
325                                         # This error should already have been registered via
326                                         # self._pkg._invalid_metadata().
327                                         pass
328                                 else:
329                                         self[k] = v
330
331                 elif k == 'USE' and not self._pkg.built:
332                         if not v:
333                                 # This is lazy because it's expensive.
334                                 pkgsettings = self._pkg.root_config.trees[
335                                         'porttree'].dbapi.doebuild_settings
336                                 pkgsettings.setcpv(self._pkg)
337                                 v = pkgsettings["PORTAGE_USE"]
338                                 _PackageMetadataWrapperBase.__setitem__(self, 'USE', v)
339
340                 return v
341
342         def __setitem__(self, k, v):
343                 _PackageMetadataWrapperBase.__setitem__(self, k, v)
344                 if k in self._wrapped_keys:
345                         getattr(self, "_set_" + k.lower())(k, v)
346                 elif k in self._use_conditional_keys:
347                         try:
348                                 reduced = use_reduce(v, matchall=1, flat=True)
349                         except portage.exception.InvalidDependString as e:
350                                 self._pkg._invalid_metadata(k + ".syntax", "%s: %s" % (k, e))
351                         else:
352                                 if reduced and k == 'PROVIDE':
353                                         for x in reduced:
354                                                 if not isvalidatom(x):
355                                                         self._pkg._invalid_metadata(k + ".syntax",
356                                                                 "%s: %s" % (k, x))
357
358         def _set_inherited(self, k, v):
359                 if isinstance(v, basestring):
360                         v = frozenset(v.split())
361                 self._pkg.inherited = v
362
363         def _set_iuse(self, k, v):
364                 self._pkg.iuse = self._pkg._iuse(
365                         v.split(), self._pkg.root_config.settings._iuse_implicit_re)
366
367         def _set_slot(self, k, v):
368                 self._pkg.slot = v
369
370         def _set_counter(self, k, v):
371                 if isinstance(v, basestring):
372                         try:
373                                 v = long(v.strip())
374                         except ValueError:
375                                 v = 0
376                 self._pkg.counter = v
377
378         def _set_use(self, k, v):
379                 # Force regeneration of _use attribute
380                 self._pkg._use = None
381                 # Use raw metadata to restore USE conditional values
382                 # to unevaluated state
383                 raw_metadata = self._pkg._raw_metadata
384                 for x in self._use_conditional_keys:
385                         try:
386                                 self[x] = raw_metadata[x]
387                         except KeyError:
388                                 pass
389
390         def _set__mtime_(self, k, v):
391                 if isinstance(v, basestring):
392                         try:
393                                 v = long(v.strip())
394                         except ValueError:
395                                 v = 0
396                 self._pkg.mtime = v
397
398         @property
399         def properties(self):
400                 return self['PROPERTIES'].split()
401
402         @property
403         def restrict(self):
404                 return self['RESTRICT'].split()
405
406         @property
407         def defined_phases(self):
408                 return self['DEFINED_PHASES'].split()