repoman: remove most old-style virtual checks
[portage.git] / pym / _emerge / Package.py
1 # Copyright 1999-2011 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._invalid_metadata("PROVIDE.syntax",
119                                         _unicode_decode("%s: %s") % (k, e))
120
121                 for k in self._use_conditional_misc_keys:
122                         v = self.metadata.get(k)
123                         if not v:
124                                 continue
125                         try:
126                                 use_reduce(v, eapi=dep_eapi, matchall=True,
127                                         is_valid_flag=dep_valid_flag)
128                         except InvalidDependString as e:
129                                 self._metadata_exception(k, e)
130
131                 k = 'REQUIRED_USE'
132                 v = self.metadata.get(k)
133                 if v:
134                         if not eapi_has_required_use(eapi):
135                                 self._invalid_metadata('EAPI.incompatible',
136                                         "REQUIRED_USE set, but EAPI='%s' doesn't allow it" % eapi)
137                         else:
138                                 try:
139                                         check_required_use(v, (),
140                                                 self.iuse.is_valid_flag)
141                                 except InvalidDependString as e:
142                                         # Force unicode format string for python-2.x safety,
143                                         # ensuring that PortageException.__unicode__() is used
144                                         # when necessary.
145                                         self._invalid_metadata(k + ".syntax",
146                                                 _unicode_decode("%s: %s") % (k, e))
147
148                 k = 'SRC_URI'
149                 v = self.metadata.get(k)
150                 if v:
151                         try:
152                                 use_reduce(v, is_src_uri=True, eapi=eapi, matchall=True,
153                                         is_valid_flag=self.iuse.is_valid_flag)
154                         except InvalidDependString as e:
155                                 if not self.installed:
156                                         self._metadata_exception(k, e)
157
158         def copy(self):
159                 return Package(built=self.built, cpv=self.cpv, depth=self.depth,
160                         installed=self.installed, metadata=self._raw_metadata,
161                         onlydeps=self.onlydeps, operation=self.operation,
162                         root_config=self.root_config, type_name=self.type_name)
163
164         def _masks(self):
165                 masks = {}
166                 settings = self.root_config.settings
167
168                 if self.invalid is not None:
169                         masks['invalid'] = self.invalid
170
171                 if not settings._accept_chost(self.cpv, self.metadata):
172                         masks['CHOST'] = self.metadata['CHOST']
173
174                 eapi = self.metadata["EAPI"]
175                 if not portage.eapi_is_supported(eapi):
176                         masks['EAPI.unsupported'] = eapi
177                 if portage._eapi_is_deprecated(eapi):
178                         masks['EAPI.deprecated'] = eapi
179
180                 missing_keywords = settings._getMissingKeywords(
181                         self.cpv, self.metadata)
182                 if missing_keywords:
183                         masks['KEYWORDS'] = missing_keywords
184
185                 try:
186                         missing_properties = settings._getMissingProperties(
187                                 self.cpv, self.metadata)
188                         if missing_properties:
189                                 masks['PROPERTIES'] = missing_properties
190                 except InvalidDependString:
191                         # already recorded as 'invalid'
192                         pass
193
194                 mask_atom = settings._getMaskAtom(self.cpv, self.metadata)
195                 if mask_atom is not None:
196                         masks['package.mask'] = mask_atom
197
198                 system_mask = settings._getProfileMaskAtom(
199                         self.cpv, self.metadata)
200                 if system_mask is not None:
201                         masks['profile.system'] = system_mask
202
203                 try:
204                         missing_licenses = settings._getMissingLicenses(
205                                 self.cpv, self.metadata)
206                         if missing_licenses:
207                                 masks['LICENSE'] = missing_licenses
208                 except InvalidDependString:
209                         # already recorded as 'invalid'
210                         pass
211
212                 if not masks:
213                         masks = None
214
215                 return masks
216
217         def _visible(self, masks):
218
219                 if masks is not None:
220
221                         if 'EAPI.unsupported' in masks:
222                                 return False
223
224                         if 'invalid' in masks:
225                                 return False
226
227                         if not self.installed and ( \
228                                 'CHOST' in masks or \
229                                 'EAPI.deprecated' in masks or \
230                                 'KEYWORDS' in masks or \
231                                 'PROPERTIES' in masks):
232                                 return False
233
234                         if 'package.mask' in masks or \
235                                 'profile.system' in masks or \
236                                 'LICENSE' in masks:
237                                 return False
238
239                 return True
240
241         def get_keyword_mask(self):
242                 """returns None, 'missing', or 'unstable'."""
243
244                 missing = self.root_config.settings._getRawMissingKeywords(
245                                 self.cpv, self.metadata)
246
247                 if not missing:
248                         return None
249
250                 if '**' in missing:
251                         return 'missing'
252
253                 global_accept_keywords = frozenset(
254                         self.root_config.settings.get("ACCEPT_KEYWORDS", "").split())
255
256                 for keyword in missing:
257                         if keyword.lstrip("~") in global_accept_keywords:
258                                 return 'unstable'
259
260                 return 'missing'
261
262         def isHardMasked(self):
263                 """returns a bool if the cpv is in the list of
264                 expanded pmaskdict[cp] available ebuilds"""
265                 pmask = self.root_config.settings._getRawMaskAtom(
266                         self.cpv, self.metadata)
267                 return pmask is not None
268
269
270         def _metadata_exception(self, k, e):
271
272                 # For unicode safety with python-2.x we need to avoid
273                 # using the string format operator with a non-unicode
274                 # format string, since that will result in the
275                 # PortageException.__str__() method being invoked,
276                 # followed by unsafe decoding that may result in a
277                 # UnicodeDecodeError. Therefore, use _unicode_decode()
278                 # to ensure that format strings are unicode, so that
279                 # PortageException.__unicode__() is used when necessary
280                 # in python-2.x.
281                 if not self.installed:
282                         categorized_error = False
283                         if e.errors:
284                                 for error in e.errors:
285                                         if getattr(error, 'category', None) is None:
286                                                 continue
287                                         categorized_error = True
288                                         self._invalid_metadata(error.category,
289                                                 _unicode_decode("%s: %s") % (k, error))
290
291                         if not categorized_error:
292                                 self._invalid_metadata(k + ".syntax",
293                                         _unicode_decode("%s: %s") % (k, e))
294                 else:
295                         # For installed packages, show the path of the file
296                         # containing the invalid metadata, since the user may
297                         # want to fix the deps by hand.
298                         vardb = self.root_config.trees['vartree'].dbapi
299                         path = vardb.getpath(self.cpv, filename=k)
300                         self._invalid_metadata(k + ".syntax",
301                                 _unicode_decode("%s: %s in '%s'") % (k, e, path))
302
303         def _invalid_metadata(self, msg_type, msg):
304                 if self.invalid is None:
305                         self.invalid = {}
306                 msgs = self.invalid.get(msg_type)
307                 if msgs is None:
308                         msgs = []
309                         self.invalid[msg_type] = msgs
310                 msgs.append(msg)
311
312         def __str__(self):
313                 if self.operation == "merge":
314                         if self.type_name == "binary":
315                                 cpv_color = "PKG_BINARY_MERGE"
316                         else:
317                                 cpv_color = "PKG_MERGE"
318                 elif self.operation == "uninstall":
319                         cpv_color = "PKG_UNINSTALL"
320                 else:
321                         cpv_color = "PKG_NOMERGE"
322
323                 s = "(%s, %s" \
324                         % (portage.output.colorize(cpv_color, self.cpv + _repo_separator + self.repo) , self.type_name)
325
326                 if self.type_name == "installed":
327                         if self.root != "/":
328                                 s += " in '%s'" % self.root
329                         if self.operation == "uninstall":
330                                 s += " scheduled for uninstall"
331                 else:
332                         if self.operation == "merge":
333                                 s += " scheduled for merge"
334                                 if self.root != "/":
335                                         s += " to '%s'" % self.root
336                 s += ")"
337                 return s
338
339         if sys.hexversion < 0x3000000:
340
341                 __unicode__ = __str__
342
343                 def __str__(self):
344                         return _unicode_encode(self.__unicode__(),
345                                 encoding=_encodings['content'])
346
347         class _use_class(object):
348
349                 __slots__ = ("enabled", "_force", "_pkg", "_mask")
350
351                 def __init__(self, pkg, use_str):
352                         self._pkg = pkg
353                         self._force = None
354                         self._mask = None
355                         self.enabled = frozenset(use_str.split())
356                         if pkg.built:
357                                 # Use IUSE to validate USE settings for built packages,
358                                 # in case the package manager that built this package
359                                 # failed to do that for some reason (or in case of
360                                 # data corruption).
361                                 missing_iuse = pkg.iuse.get_missing_iuse(self.enabled)
362                                 if missing_iuse:
363                                         self.enabled = self.enabled.difference(missing_iuse)
364
365                 def _init_force_mask(self):
366                         pkgsettings = self._pkg._get_pkgsettings()
367                         self._force = pkgsettings.useforce
368                         self._mask = pkgsettings.usemask
369
370                 @property
371                 def force(self):
372                         if self._force is None:
373                                 self._init_force_mask()
374                         return self._force
375
376                 @property
377                 def mask(self):
378                         if self._mask is None:
379                                 self._init_force_mask()
380                         return self._mask
381
382         @property
383         def repo(self):
384                 return self.metadata['repository']
385
386         @property
387         def repo_priority(self):
388                 repo_info = self.root_config.settings.repositories.prepos.get(self.repo)
389                 if repo_info is None:
390                         return None
391                 return repo_info.priority
392
393         @property
394         def use(self):
395                 if self._use is None:
396                         self.metadata._init_use()
397                 return self._use
398
399         def _get_pkgsettings(self):
400                 pkgsettings = self.root_config.trees[
401                         'porttree'].dbapi.doebuild_settings
402                 pkgsettings.setcpv(self)
403                 return pkgsettings
404
405         class _iuse(object):
406
407                 __slots__ = ("__weakref__", "all", "enabled", "disabled",
408                         "tokens") + ("_iuse_implicit_match",)
409
410                 def __init__(self, tokens, iuse_implicit_match):
411                         self.tokens = tuple(tokens)
412                         self._iuse_implicit_match = iuse_implicit_match
413                         enabled = []
414                         disabled = []
415                         other = []
416                         for x in tokens:
417                                 prefix = x[:1]
418                                 if prefix == "+":
419                                         enabled.append(x[1:])
420                                 elif prefix == "-":
421                                         disabled.append(x[1:])
422                                 else:
423                                         other.append(x)
424                         self.enabled = frozenset(enabled)
425                         self.disabled = frozenset(disabled)
426                         self.all = frozenset(chain(enabled, disabled, other))
427
428                 def is_valid_flag(self, flags):
429                         """
430                         @returns: True if all flags are valid USE values which may
431                                 be specified in USE dependencies, False otherwise.
432                         """
433                         if isinstance(flags, basestring):
434                                 flags = [flags]
435
436                         for flag in flags:
437                                 if not flag in self.all and \
438                                         not self._iuse_implicit_match(flag):
439                                         return False
440                         return True
441
442                 def get_missing_iuse(self, flags):
443                         """
444                         @returns: A list of flags missing from IUSE.
445                         """
446                         if isinstance(flags, basestring):
447                                 flags = [flags]
448                         missing_iuse = []
449                         for flag in flags:
450                                 if not flag in self.all and \
451                                         not self._iuse_implicit_match(flag):
452                                         missing_iuse.append(flag)
453                         return missing_iuse
454
455         def _get_hash_key(self):
456                 hash_key = getattr(self, "_hash_key", None)
457                 if hash_key is None:
458                         # For installed (and binary) packages we don't care for the repo
459                         # when it comes to hashing, because there can only be one cpv.
460                         # So overwrite the repo_key with type_name.
461                         repo_key = self.metadata.get('repository')
462                         if self.type_name != 'ebuild':
463                                 repo_key = self.type_name
464                         self._hash_key = \
465                                 (self.type_name, self.root, self.cpv, self.operation, repo_key)
466                 return self._hash_key
467
468         def __len__(self):
469                 return 4
470
471         def __iter__(self):
472                 """
473                 This is used to generate mtimedb resume mergelist entries, so we
474                 limit it to 4 items for backward compatibility.
475                 """
476                 return iter(self._get_hash_key()[:4])
477
478         def __lt__(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 __le__(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 __gt__(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         def __ge__(self, other):
500                 if other.cp != self.cp:
501                         return False
502                 if portage.pkgcmp(self.pv_split, other.pv_split) >= 0:
503                         return True
504                 return False
505
506 _all_metadata_keys = set(x for x in portage.auxdbkeys \
507         if not x.startswith("UNUSED_"))
508 _all_metadata_keys.update(Package.metadata_keys)
509 _all_metadata_keys = frozenset(_all_metadata_keys)
510
511 _PackageMetadataWrapperBase = slot_dict_class(_all_metadata_keys)
512
513 class _PackageMetadataWrapper(_PackageMetadataWrapperBase):
514         """
515         Detect metadata updates and synchronize Package attributes.
516         """
517
518         __slots__ = ("_pkg",)
519         _wrapped_keys = frozenset(
520                 ["COUNTER", "INHERITED", "IUSE", "SLOT", "USE", "_mtime_"])
521         _use_conditional_keys = frozenset(
522                 ['LICENSE', 'PROPERTIES', 'PROVIDE', 'RESTRICT',])
523
524         def __init__(self, pkg, metadata):
525                 _PackageMetadataWrapperBase.__init__(self)
526                 self._pkg = pkg
527                 if not pkg.built:
528                         # USE is lazy, but we want it to show up in self.keys().
529                         _PackageMetadataWrapperBase.__setitem__(self, 'USE', '')
530
531                 self.update(metadata)
532
533         def _init_use(self):
534                 if self._pkg.built:
535                         use_str = self['USE']
536                         self._pkg._use = self._pkg._use_class(
537                                 self._pkg, use_str)
538                 else:
539                         try:
540                                 use_str = _PackageMetadataWrapperBase.__getitem__(self, 'USE')
541                         except KeyError:
542                                 use_str = None
543                         calculated_use = False
544                         if not use_str:
545                                 use_str = self._pkg._get_pkgsettings()["PORTAGE_USE"]
546                                 calculated_use = True
547                         _PackageMetadataWrapperBase.__setitem__(self, 'USE', use_str)
548                         self._pkg._use = self._pkg._use_class(
549                                 self._pkg, use_str)
550                         # Initialize these now, since USE access has just triggered
551                         # setcpv, and we want to cache the result of the force/mask
552                         # calculations that were done.
553                         if calculated_use:
554                                 self._pkg._use._init_force_mask()
555
556                 return use_str
557
558         def __getitem__(self, k):
559                 v = _PackageMetadataWrapperBase.__getitem__(self, k)
560                 if k in self._use_conditional_keys:
561                         if self._pkg.root_config.settings.local_config and '?' in v:
562                                 try:
563                                         v = paren_enclose(use_reduce(v, uselist=self._pkg.use.enabled, \
564                                                 is_valid_flag=self._pkg.iuse.is_valid_flag))
565                                 except InvalidDependString:
566                                         # This error should already have been registered via
567                                         # self._pkg._invalid_metadata().
568                                         pass
569                                 else:
570                                         self[k] = v
571
572                 elif k == 'USE' and not self._pkg.built:
573                         if not v:
574                                 # This is lazy because it's expensive.
575                                 v = self._init_use()
576
577                 return v
578
579         def __setitem__(self, k, v):
580                 _PackageMetadataWrapperBase.__setitem__(self, k, v)
581                 if k in self._wrapped_keys:
582                         getattr(self, "_set_" + k.lower())(k, v)
583
584         def _set_inherited(self, k, v):
585                 if isinstance(v, basestring):
586                         v = frozenset(v.split())
587                 self._pkg.inherited = v
588
589         def _set_iuse(self, k, v):
590                 self._pkg.iuse = self._pkg._iuse(
591                         v.split(), self._pkg.root_config.settings._iuse_implicit_match)
592
593         def _set_slot(self, k, v):
594                 self._pkg.slot = v
595
596         def _set_counter(self, k, v):
597                 if isinstance(v, basestring):
598                         try:
599                                 v = long(v.strip())
600                         except ValueError:
601                                 v = 0
602                 self._pkg.counter = v
603
604         def _set_use(self, k, v):
605                 # Force regeneration of _use attribute
606                 self._pkg._use = None
607                 # Use raw metadata to restore USE conditional values
608                 # to unevaluated state
609                 raw_metadata = self._pkg._raw_metadata
610                 for x in self._use_conditional_keys:
611                         try:
612                                 self[x] = raw_metadata[x]
613                         except KeyError:
614                                 pass
615
616         def _set__mtime_(self, k, v):
617                 if isinstance(v, basestring):
618                         try:
619                                 v = long(v.strip())
620                         except ValueError:
621                                 v = 0
622                 self._pkg.mtime = v
623
624         @property
625         def properties(self):
626                 return self['PROPERTIES'].split()
627
628         @property
629         def restrict(self):
630                 return self['RESTRICT'].split()
631
632         @property
633         def defined_phases(self):
634                 """
635                 Returns tokens from DEFINED_PHASES metadata if it is defined,
636                 otherwise returns a tuple containing all possible phases. This
637                 makes it easy to do containment checks to see if it's safe to
638                 skip execution of a given phase.
639                 """
640                 s = self['DEFINED_PHASES']
641                 if s:
642                         return s.split()
643                 return EBUILD_PHASES