add a new function to MaskManager that optimizies getting any raw mask atoms
[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 sys
5 from itertools import chain
6 import portage
7 from portage import _encodings, _unicode_decode, _unicode_encode
8 from portage.cache.mappings import slot_dict_class
9 from portage.const import EBUILD_PHASES
10 from portage.dep import Atom, check_required_use, use_reduce, \
11         paren_enclose, _slot_re, _slot_separator, _repo_separator
12 from portage.eapi import eapi_has_iuse_defaults, eapi_has_required_use
13 from portage.exception import InvalidDependString
14 from portage.repository.config import _gen_valid_repo
15 from _emerge.Task import Task
16
17 if sys.hexversion >= 0x3000000:
18         basestring = str
19         long = int
20
21 class Package(Task):
22
23         __hash__ = Task.__hash__
24         __slots__ = ("built", "cpv", "depth",
25                 "installed", "metadata", "onlydeps", "operation",
26                 "root_config", "type_name",
27                 "category", "counter", "cp", "cpv_split",
28                 "inherited", "invalid", "iuse", "masks", "mtime",
29                 "pf", "pv_split", "root", "slot", "slot_atom", "visible",) + \
30         ("_raw_metadata", "_use",)
31
32         metadata_keys = [
33                 "BUILD_TIME", "CHOST", "COUNTER", "DEPEND", "EAPI",
34                 "INHERITED", "IUSE", "KEYWORDS",
35                 "LICENSE", "PDEPEND", "PROVIDE", "RDEPEND",
36                 "repository", "PROPERTIES", "RESTRICT", "SLOT", "USE",
37                 "_mtime_", "DEFINED_PHASES", "REQUIRED_USE"]
38
39         _dep_keys = ('DEPEND', 'PDEPEND', 'RDEPEND',)
40         _use_conditional_misc_keys = ('LICENSE', 'PROPERTIES', 'RESTRICT')
41
42         def __init__(self, **kwargs):
43                 Task.__init__(self, **kwargs)
44                 # the SlotObject constructor assigns self.root_config from keyword args
45                 # and is an instance of a '_emerge.RootConfig.RootConfig class
46                 self.root = self.root_config.root
47                 self._raw_metadata = _PackageMetadataWrapperBase(self.metadata)
48                 self.metadata = _PackageMetadataWrapper(self, self._raw_metadata)
49                 if not self.built:
50                         self.metadata['CHOST'] = self.root_config.settings.get('CHOST', '')
51                 self.cp = portage.cpv_getkey(self.cpv)
52                 slot = self.slot
53                 if _slot_re.match(slot) is None:
54                         self._invalid_metadata('SLOT.invalid',
55                                 "SLOT: invalid value: '%s'" % slot)
56                         # Avoid an InvalidAtom exception when creating slot_atom.
57                         # This package instance will be masked due to empty SLOT.
58                         slot = '0'
59                 if (self.iuse.enabled or self.iuse.disabled) and \
60                         not eapi_has_iuse_defaults(self.metadata["EAPI"]):
61                         if not self.installed:
62                                 self._invalid_metadata('EAPI.incompatible',
63                                         "IUSE contains defaults, but EAPI doesn't allow them")
64                 self.slot_atom = portage.dep.Atom("%s%s%s" % (self.cp, _slot_separator, slot))
65                 self.category, self.pf = portage.catsplit(self.cpv)
66                 self.cpv_split = portage.catpkgsplit(self.cpv)
67                 self.pv_split = self.cpv_split[1:]
68                 if self.inherited is None:
69                         self.inherited = frozenset()
70                 repo = _gen_valid_repo(self.metadata.get('repository', ''))
71                 if not repo:
72                         repo = '__unknown__'
73                 self.metadata['repository'] = repo
74
75                 self._validate_deps()
76                 self.masks = self._masks()
77                 self.visible = self._visible(self.masks)
78                 if self.operation is None:
79                         if self.onlydeps or self.installed:
80                                 self.operation = "nomerge"
81                         else:
82                                 self.operation = "merge"
83
84         def _validate_deps(self):
85                 """
86                 Validate deps. This does not trigger USE calculation since that
87                 is expensive for ebuilds and therefore we want to avoid doing
88                 in unnecessarily (like for masked packages).
89                 """
90                 eapi = self.metadata['EAPI']
91                 dep_eapi = eapi
92                 dep_valid_flag = self.iuse.is_valid_flag
93                 if self.installed:
94                         # Ignore EAPI.incompatible and conditionals missing
95                         # from IUSE for installed packages since these issues
96                         # aren't relevant now (re-evaluate when new EAPIs are
97                         # deployed).
98                         dep_eapi = None
99                         dep_valid_flag = None
100
101                 for k in self._dep_keys:
102                         v = self.metadata.get(k)
103                         if not v:
104                                 continue
105                         try:
106                                 use_reduce(v, eapi=dep_eapi, matchall=True,
107                                         is_valid_flag=dep_valid_flag, token_class=Atom)
108                         except InvalidDependString as e:
109                                 self._metadata_exception(k, e)
110
111                 k = 'PROVIDE'
112                 v = self.metadata.get(k)
113                 if v:
114                         try:
115                                 use_reduce(v, eapi=dep_eapi, matchall=True,
116                                         is_valid_flag=dep_valid_flag, token_class=Atom)
117                         except InvalidDependString as e:
118                                 self._metadata_exception(k, e)
119
120                 for k in self._use_conditional_misc_keys:
121                         v = self.metadata.get(k)
122                         if not v:
123                                 continue
124                         try:
125                                 use_reduce(v, eapi=dep_eapi, matchall=True,
126                                         is_valid_flag=dep_valid_flag)
127                         except InvalidDependString as e:
128                                 self._metadata_exception(k, e)
129
130                 k = 'REQUIRED_USE'
131                 v = self.metadata.get(k)
132                 if v:
133                         if not eapi_has_required_use(eapi):
134                                 self._invalid_metadata('EAPI.incompatible',
135                                         "REQUIRED_USE set, but EAPI='%s' doesn't allow it" % eapi)
136                         else:
137                                 try:
138                                         check_required_use(v, (),
139                                                 self.iuse.is_valid_flag)
140                                 except InvalidDependString as e:
141                                         # Force unicode format string for python-2.x safety,
142                                         # ensuring that PortageException.__unicode__() is used
143                                         # when necessary.
144                                         self._invalid_metadata(k + ".syntax",
145                                                 _unicode_decode("%s: %s") % (k, e))
146
147                 k = 'SRC_URI'
148                 v = self.metadata.get(k)
149                 if v:
150                         try:
151                                 use_reduce(v, is_src_uri=True, eapi=eapi, matchall=True,
152                                         is_valid_flag=self.iuse.is_valid_flag)
153                         except InvalidDependString as e:
154                                 if not self.installed:
155                                         self._metadata_exception(k, e)
156
157         def copy(self):
158                 return Package(built=self.built, cpv=self.cpv, depth=self.depth,
159                         installed=self.installed, metadata=self._raw_metadata,
160                         onlydeps=self.onlydeps, operation=self.operation,
161                         root_config=self.root_config, type_name=self.type_name)
162
163         def _masks(self):
164                 masks = {}
165                 settings = self.root_config.settings
166
167                 if self.invalid is not None:
168                         masks['invalid'] = self.invalid
169
170                 if not settings._accept_chost(self.cpv, self.metadata):
171                         masks['CHOST'] = self.metadata['CHOST']
172
173                 eapi = self.metadata["EAPI"]
174                 if not portage.eapi_is_supported(eapi):
175                         masks['EAPI.unsupported'] = eapi
176                 if portage._eapi_is_deprecated(eapi):
177                         masks['EAPI.deprecated'] = eapi
178
179                 missing_keywords = settings._getMissingKeywords(
180                         self.cpv, self.metadata)
181                 if missing_keywords:
182                         masks['KEYWORDS'] = missing_keywords
183
184                 try:
185                         missing_properties = settings._getMissingProperties(
186                                 self.cpv, self.metadata)
187                         if missing_properties:
188                                 masks['PROPERTIES'] = missing_properties
189                 except InvalidDependString:
190                         # already recorded as 'invalid'
191                         pass
192
193                 mask_atom = settings._getMaskAtom(self.cpv, self.metadata)
194                 if mask_atom is not None:
195                         masks['package.mask'] = mask_atom
196
197                 system_mask = settings._getProfileMaskAtom(
198                         self.cpv, self.metadata)
199                 if system_mask is not None:
200                         masks['profile.system'] = system_mask
201
202                 try:
203                         missing_licenses = settings._getMissingLicenses(
204                                 self.cpv, self.metadata)
205                         if missing_licenses:
206                                 masks['LICENSE'] = missing_licenses
207                 except InvalidDependString:
208                         # already recorded as 'invalid'
209                         pass
210
211                 if not masks:
212                         masks = None
213
214                 return masks
215
216         def _visible(self, masks):
217
218                 if masks is not None:
219
220                         if 'EAPI.unsupported' in masks:
221                                 return False
222
223                         if 'invalid' in masks:
224                                 return False
225
226                         if not self.installed and ( \
227                                 'CHOST' in masks or \
228                                 'EAPI.deprecated' in masks or \
229                                 'KEYWORDS' in masks or \
230                                 'PROPERTIES' in masks):
231                                 return False
232
233                         if 'package.mask' in masks or \
234                                 'profile.system' in masks or \
235                                 'LICENSE' in masks:
236                                 return False
237
238                 return True
239
240         def accepted_keyword(self):
241                 """returns the keyword used from the ebuild's KEYWORDS string"""
242
243                 keywords = set(self.metadata.get('KEYWORDS').split())
244                 accept_keywords = set(self.root_config.settings['ACCEPT_KEYWORDS'].split())
245                 used_keyword = list(set.intersection(keywords, accept_keywords))
246                 if used_keyword and len(used_keyword) == 1:
247                         used_keyword = used_keyword[0]
248                 elif len(used_keyword) > 1:
249                         # you can raise an error here if you prefer, remove it, or set the correct levels
250                         writemsg_level( "_emerge.output.resolver.Display(), too many keywords recieved for pkg: %s, %s"
251                                         % (pkg.cpv, used_keyword))
252                         used_keyword = used_keyword[0]
253                 return used_keyword
254
255         def isHardMasked(self):
256                 """returns a bool if the cpv is in the list of
257                 expanded pmaskdict[cp] availble ebuilds"""
258                 pmask = self.root_config.settings._getRawMaskAtom(self.cpv, self.metadata)
259                 print "pmask =", pmask
260                 return pmask is not None
261
262
263         def _metadata_exception(self, k, e):
264
265                 # For unicode safety with python-2.x we need to avoid
266                 # using the string format operator with a non-unicode
267                 # format string, since that will result in the
268                 # PortageException.__str__() method being invoked,
269                 # followed by unsafe decoding that may result in a
270                 # UnicodeDecodeError. Therefore, use _unicode_decode()
271                 # to ensure that format strings are unicode, so that
272                 # PortageException.__unicode__() is used when necessary
273                 # in python-2.x.
274                 if not self.installed:
275                         categorized_error = False
276                         if e.errors:
277                                 for error in e.errors:
278                                         if getattr(error, 'category', None) is None:
279                                                 continue
280                                         categorized_error = True
281                                         self._invalid_metadata(error.category,
282                                                 _unicode_decode("%s: %s") % (k, error))
283
284                         if not categorized_error:
285                                 self._invalid_metadata(k + ".syntax",
286                                         _unicode_decode("%s: %s") % (k, e))
287                 else:
288                         # For installed packages, show the path of the file
289                         # containing the invalid metadata, since the user may
290                         # want to fix the deps by hand.
291                         vardb = self.root_config.trees['vartree'].dbapi
292                         path = vardb.getpath(self.cpv, filename=k)
293                         self._invalid_metadata(k + ".syntax",
294                                 _unicode_decode("%s: %s in '%s'") % (k, e, path))
295
296         def _invalid_metadata(self, msg_type, msg):
297                 if self.invalid is None:
298                         self.invalid = {}
299                 msgs = self.invalid.get(msg_type)
300                 if msgs is None:
301                         msgs = []
302                         self.invalid[msg_type] = msgs
303                 msgs.append(msg)
304
305         def __str__(self):
306                 if self.operation == "merge":
307                         if self.type_name == "binary":
308                                 cpv_color = "PKG_BINARY_MERGE"
309                         else:
310                                 cpv_color = "PKG_MERGE"
311                 elif self.operation == "uninstall":
312                         cpv_color = "PKG_UNINSTALL"
313                 else:
314                         cpv_color = "PKG_NOMERGE"
315
316                 s = "(%s, %s" \
317                         % (portage.output.colorize(cpv_color, self.cpv + _repo_separator + self.repo) , self.type_name)
318
319                 if self.type_name == "installed":
320                         if self.root != "/":
321                                 s += " in '%s'" % self.root
322                         if self.operation == "uninstall":
323                                 s += " scheduled for uninstall"
324                 else:
325                         if self.operation == "merge":
326                                 s += " scheduled for merge"
327                                 if self.root != "/":
328                                         s += " to '%s'" % self.root
329                 s += ")"
330                 return s
331
332         if sys.hexversion < 0x3000000:
333
334                 __unicode__ = __str__
335
336                 def __str__(self):
337                         return _unicode_encode(self.__unicode__(),
338                                 encoding=_encodings['content'])
339
340         class _use_class(object):
341
342                 __slots__ = ("enabled", "_force", "_pkg", "_mask")
343
344                 def __init__(self, pkg, use_str):
345                         self._pkg = pkg
346                         self._force = None
347                         self._mask = None
348                         self.enabled = frozenset(use_str.split())
349                         if pkg.built:
350                                 # Use IUSE to validate USE settings for built packages,
351                                 # in case the package manager that built this package
352                                 # failed to do that for some reason (or in case of
353                                 # data corruption).
354                                 missing_iuse = pkg.iuse.get_missing_iuse(self.enabled)
355                                 if missing_iuse:
356                                         self.enabled = self.enabled.difference(missing_iuse)
357
358                 def _init_force_mask(self):
359                         pkgsettings = self._pkg._get_pkgsettings()
360                         self._force = pkgsettings.useforce
361                         self._mask = pkgsettings.usemask
362
363                 @property
364                 def force(self):
365                         if self._force is None:
366                                 self._init_force_mask()
367                         return self._force
368
369                 @property
370                 def mask(self):
371                         if self._mask is None:
372                                 self._init_force_mask()
373                         return self._mask
374
375         @property
376         def repo(self):
377                 return self.metadata['repository']
378
379         @property
380         def repo_priority(self):
381                 repo_info = self.root_config.settings.repositories.prepos.get(self.repo)
382                 if repo_info is None:
383                         return None
384                 return repo_info.priority
385
386         @property
387         def use(self):
388                 if self._use is None:
389                         self.metadata._init_use()
390                 return self._use
391
392         def _get_pkgsettings(self):
393                 pkgsettings = self.root_config.trees[
394                         'porttree'].dbapi.doebuild_settings
395                 pkgsettings.setcpv(self)
396                 return pkgsettings
397
398         class _iuse(object):
399
400                 __slots__ = ("__weakref__", "all", "enabled", "disabled",
401                         "tokens") + ("_iuse_implicit_match",)
402
403                 def __init__(self, tokens, iuse_implicit_match):
404                         self.tokens = tuple(tokens)
405                         self._iuse_implicit_match = iuse_implicit_match
406                         enabled = []
407                         disabled = []
408                         other = []
409                         for x in tokens:
410                                 prefix = x[:1]
411                                 if prefix == "+":
412                                         enabled.append(x[1:])
413                                 elif prefix == "-":
414                                         disabled.append(x[1:])
415                                 else:
416                                         other.append(x)
417                         self.enabled = frozenset(enabled)
418                         self.disabled = frozenset(disabled)
419                         self.all = frozenset(chain(enabled, disabled, other))
420
421                 def is_valid_flag(self, flags):
422                         """
423                         @returns: True if all flags are valid USE values which may
424                                 be specified in USE dependencies, False otherwise.
425                         """
426                         if isinstance(flags, basestring):
427                                 flags = [flags]
428
429                         for flag in flags:
430                                 if not flag in self.all and \
431                                         not self._iuse_implicit_match(flag):
432                                         return False
433                         return True
434
435                 def get_missing_iuse(self, flags):
436                         """
437                         @returns: A list of flags missing from IUSE.
438                         """
439                         if isinstance(flags, basestring):
440                                 flags = [flags]
441                         missing_iuse = []
442                         for flag in flags:
443                                 if not flag in self.all and \
444                                         not self._iuse_implicit_match(flag):
445                                         missing_iuse.append(flag)
446                         return missing_iuse
447
448         def _get_hash_key(self):
449                 hash_key = getattr(self, "_hash_key", None)
450                 if hash_key is None:
451                         # For installed (and binary) packages we don't care for the repo
452                         # when it comes to hashing, because there can only be one cpv.
453                         # So overwrite the repo_key with type_name.
454                         repo_key = self.metadata.get('repository')
455                         if self.type_name != 'ebuild':
456                                 repo_key = self.type_name
457                         self._hash_key = \
458                                 (self.type_name, self.root, self.cpv, self.operation, repo_key)
459                 return self._hash_key
460
461         def __len__(self):
462                 return 4
463
464         def __iter__(self):
465                 """
466                 This is used to generate mtimedb resume mergelist entries, so we
467                 limit it to 4 items for backward compatibility.
468                 """
469                 return iter(self._get_hash_key()[:4])
470
471         def __lt__(self, other):
472                 if other.cp != self.cp:
473                         return False
474                 if portage.pkgcmp(self.pv_split, other.pv_split) < 0:
475                         return True
476                 return False
477
478         def __le__(self, other):
479                 if other.cp != self.cp:
480                         return False
481                 if portage.pkgcmp(self.pv_split, other.pv_split) <= 0:
482                         return True
483                 return False
484
485         def __gt__(self, other):
486                 if other.cp != self.cp:
487                         return False
488                 if portage.pkgcmp(self.pv_split, other.pv_split) > 0:
489                         return True
490                 return False
491
492         def __ge__(self, other):
493                 if other.cp != self.cp:
494                         return False
495                 if portage.pkgcmp(self.pv_split, other.pv_split) >= 0:
496                         return True
497                 return False
498
499 _all_metadata_keys = set(x for x in portage.auxdbkeys \
500         if not x.startswith("UNUSED_"))
501 _all_metadata_keys.update(Package.metadata_keys)
502 _all_metadata_keys = frozenset(_all_metadata_keys)
503
504 _PackageMetadataWrapperBase = slot_dict_class(_all_metadata_keys)
505
506 class _PackageMetadataWrapper(_PackageMetadataWrapperBase):
507         """
508         Detect metadata updates and synchronize Package attributes.
509         """
510
511         __slots__ = ("_pkg",)
512         _wrapped_keys = frozenset(
513                 ["COUNTER", "INHERITED", "IUSE", "SLOT", "USE", "_mtime_"])
514         _use_conditional_keys = frozenset(
515                 ['LICENSE', 'PROPERTIES', 'PROVIDE', 'RESTRICT',])
516
517         def __init__(self, pkg, metadata):
518                 _PackageMetadataWrapperBase.__init__(self)
519                 self._pkg = pkg
520                 if not pkg.built:
521                         # USE is lazy, but we want it to show up in self.keys().
522                         _PackageMetadataWrapperBase.__setitem__(self, 'USE', '')
523
524                 self.update(metadata)
525
526         def _init_use(self):
527                 if self._pkg.built:
528                         use_str = self['USE']
529                         self._pkg._use = self._pkg._use_class(
530                                 self._pkg, use_str)
531                 else:
532                         try:
533                                 use_str = _PackageMetadataWrapperBase.__getitem__(self, 'USE')
534                         except KeyError:
535                                 use_str = None
536                         calculated_use = False
537                         if not use_str:
538                                 use_str = self._pkg._get_pkgsettings()["PORTAGE_USE"]
539                                 calculated_use = True
540                         _PackageMetadataWrapperBase.__setitem__(self, 'USE', use_str)
541                         self._pkg._use = self._pkg._use_class(
542                                 self._pkg, use_str)
543                         # Initialize these now, since USE access has just triggered
544                         # setcpv, and we want to cache the result of the force/mask
545                         # calculations that were done.
546                         if calculated_use:
547                                 self._pkg._use._init_force_mask()
548
549                 return use_str
550
551         def __getitem__(self, k):
552                 v = _PackageMetadataWrapperBase.__getitem__(self, k)
553                 if k in self._use_conditional_keys:
554                         if self._pkg.root_config.settings.local_config and '?' in v:
555                                 try:
556                                         v = paren_enclose(use_reduce(v, uselist=self._pkg.use.enabled, \
557                                                 is_valid_flag=self._pkg.iuse.is_valid_flag))
558                                 except InvalidDependString:
559                                         # This error should already have been registered via
560                                         # self._pkg._invalid_metadata().
561                                         pass
562                                 else:
563                                         self[k] = v
564
565                 elif k == 'USE' and not self._pkg.built:
566                         if not v:
567                                 # This is lazy because it's expensive.
568                                 v = self._init_use()
569
570                 return v
571
572         def __setitem__(self, k, v):
573                 _PackageMetadataWrapperBase.__setitem__(self, k, v)
574                 if k in self._wrapped_keys:
575                         getattr(self, "_set_" + k.lower())(k, v)
576
577         def _set_inherited(self, k, v):
578                 if isinstance(v, basestring):
579                         v = frozenset(v.split())
580                 self._pkg.inherited = v
581
582         def _set_iuse(self, k, v):
583                 self._pkg.iuse = self._pkg._iuse(
584                         v.split(), self._pkg.root_config.settings._iuse_implicit_match)
585
586         def _set_slot(self, k, v):
587                 self._pkg.slot = v
588
589         def _set_counter(self, k, v):
590                 if isinstance(v, basestring):
591                         try:
592                                 v = long(v.strip())
593                         except ValueError:
594                                 v = 0
595                 self._pkg.counter = v
596
597         def _set_use(self, k, v):
598                 # Force regeneration of _use attribute
599                 self._pkg._use = None
600                 # Use raw metadata to restore USE conditional values
601                 # to unevaluated state
602                 raw_metadata = self._pkg._raw_metadata
603                 for x in self._use_conditional_keys:
604                         try:
605                                 self[x] = raw_metadata[x]
606                         except KeyError:
607                                 pass
608
609         def _set__mtime_(self, k, v):
610                 if isinstance(v, basestring):
611                         try:
612                                 v = long(v.strip())
613                         except ValueError:
614                                 v = 0
615                 self._pkg.mtime = v
616
617         @property
618         def properties(self):
619                 return self['PROPERTIES'].split()
620
621         @property
622         def restrict(self):
623                 return self['RESTRICT'].split()
624
625         @property
626         def defined_phases(self):
627                 """
628                 Returns tokens from DEFINED_PHASES metadata if it is defined,
629                 otherwise returns a tuple containing all possible phases. This
630                 makes it easy to do containment checks to see if it's safe to
631                 skip execution of a given phase.
632                 """
633                 s = self['DEFINED_PHASES']
634                 if s:
635                         return s.split()
636                 return EBUILD_PHASES