EAPI="4-python" and EAPI="5-progress": Add support for use.aliases and package.use...
[portage.git] / pym / _emerge / Package.py
index 75021b9f50e83a29f3496a0cd48be6f8aaeb43cd..1c1840836984f6411646bc8bbcf42937ced69fdc 100644 (file)
@@ -8,15 +8,19 @@ from portage import _encodings, _unicode_decode, _unicode_encode
 from portage.cache.mappings import slot_dict_class
 from portage.const import EBUILD_PHASES
 from portage.dep import Atom, check_required_use, use_reduce, \
-       paren_enclose, _slot_re, _slot_separator, _repo_separator
+       paren_enclose, _slot_separator, _repo_separator
 from portage.versions import _pkg_str, _unknown_repo
-from portage.eapi import _get_eapi_attrs
+from portage.eapi import _get_eapi_attrs, eapi_has_use_aliases
 from portage.exception import InvalidDependString
+from portage.localization import _
 from _emerge.Task import Task
 
 if sys.hexversion >= 0x3000000:
        basestring = str
        long = int
+       _unicode = str
+else:
+       _unicode = unicode
 
 class Package(Task):
 
@@ -26,17 +30,20 @@ class Package(Task):
                "root_config", "type_name",
                "category", "counter", "cp", "cpv_split",
                "inherited", "iuse", "mtime",
-               "pf", "root", "slot", "slot_atom", "version") + \
-       ("_invalid", "_raw_metadata", "_masks", "_use", "_visible")
+               "pf", "root", "slot", "sub_slot", "slot_atom", "version") + \
+               ("_invalid", "_raw_metadata", "_masks", "_use",
+               "_validated_atoms", "_visible")
 
        metadata_keys = [
                "BUILD_TIME", "CHOST", "COUNTER", "DEPEND", "EAPI",
-               "INHERITED", "IUSE", "KEYWORDS",
+               "HDEPEND", "INHERITED", "IUSE", "KEYWORDS",
                "LICENSE", "PDEPEND", "PROVIDE", "RDEPEND",
                "repository", "PROPERTIES", "RESTRICT", "SLOT", "USE",
                "_mtime_", "DEFINED_PHASES", "REQUIRED_USE"]
 
-       _dep_keys = ('DEPEND', 'PDEPEND', 'RDEPEND',)
+       _dep_keys = ('DEPEND', 'HDEPEND', 'PDEPEND', 'RDEPEND')
+       _buildtime_keys = ('DEPEND', 'HDEPEND')
+       _runtime_keys = ('PDEPEND', 'RDEPEND')
        _use_conditional_misc_keys = ('LICENSE', 'PROPERTIES', 'RESTRICT')
        UNKNOWN_REPO = _unknown_repo
 
@@ -50,27 +57,34 @@ class Package(Task):
                if not self.built:
                        self.metadata['CHOST'] = self.root_config.settings.get('CHOST', '')
                eapi_attrs = _get_eapi_attrs(self.metadata["EAPI"])
-               slot = self.slot
-               if _slot_re.match(slot) is None:
+               self.cpv = _pkg_str(self.cpv, metadata=self.metadata,
+                       settings=self.root_config.settings)
+               if hasattr(self.cpv, 'slot_invalid'):
                        self._invalid_metadata('SLOT.invalid',
-                               "SLOT: invalid value: '%s'" % slot)
-                       # Avoid an InvalidAtom exception when creating slot_atom.
-                       # This package instance will be masked due to empty SLOT.
-                       slot = '0'
-               self.cpv = _pkg_str(self.cpv, slot=slot,
-                       repo=self.metadata.get('repository', ''))
+                               "SLOT: invalid value: '%s'" % self.metadata["SLOT"])
+               self.cpv_split = self.cpv.cpv_split
+               self.category, self.pf = portage.catsplit(self.cpv)
                self.cp = self.cpv.cp
+               self.version = self.cpv.version
+               self.slot = self.cpv.slot
+               self.sub_slot = self.cpv.sub_slot
+               self.slot_atom = Atom("%s%s%s" % (self.cp, _slot_separator, self.slot))
                # sync metadata with validated repo (may be UNKNOWN_REPO)
                self.metadata['repository'] = self.cpv.repo
+
+               if eapi_attrs.iuse_effective:
+                       implicit_match = self.root_config.settings._iuse_effective_match
+               else:
+                       implicit_match = self.root_config.settings._iuse_implicit_match
+               usealiases = self.root_config.settings._use_manager.getUseAliases(self)
+               self.iuse = self._iuse(self, self.metadata["IUSE"].split(), implicit_match,
+                       usealiases, self.metadata["EAPI"])
+
                if (self.iuse.enabled or self.iuse.disabled) and \
                        not eapi_attrs.iuse_defaults:
                        if not self.installed:
                                self._invalid_metadata('EAPI.incompatible',
                                        "IUSE contains defaults, but EAPI doesn't allow them")
-               self.slot_atom = portage.dep.Atom("%s%s%s" % (self.cp, _slot_separator, slot))
-               self.category, self.pf = portage.catsplit(self.cpv)
-               self.cpv_split = self.cpv.cpv_split
-               self.version = self.cpv.version
                if self.inherited is None:
                        self.inherited = frozenset()
 
@@ -87,6 +101,11 @@ class Package(Task):
                        type_name=self.type_name)
                self._hash_value = hash(self._hash_key)
 
+       # For consistency with _pkg_str
+       @property
+       def _metadata(self):
+               return self.metadata
+
        # These are calculated on-demand, so that they are calculated
        # after FakeVartree applies its metadata tweaks.
        @property
@@ -109,6 +128,21 @@ class Package(Task):
                        self._visible = self._eval_visiblity(self.masks)
                return self._visible
 
+       @property
+       def validated_atoms(self):
+               """
+               Returns *all* validated atoms from the deps, regardless
+               of USE conditionals, with USE conditionals inside
+               atoms left unevaluated.
+               """
+               if self._validated_atoms is None:
+                       self._validate_deps()
+               return self._validated_atoms
+
+       @property
+       def stable(self):
+               return self.cpv.stable
+
        @classmethod
        def _gen_hash_key(cls, cpv=None, installed=None, onlydeps=None,
                operation=None, repo_name=None, root_config=None,
@@ -143,7 +177,7 @@ class Package(Task):
                        # So overwrite the repo_key with type_name.
                        repo_key = type_name
 
-               return (type_name, root, cpv, operation, repo_key)
+               return (type_name, root, _unicode(cpv), operation, repo_key)
 
        def _validate_deps(self):
                """
@@ -162,15 +196,32 @@ class Package(Task):
                        dep_eapi = None
                        dep_valid_flag = None
 
+               validated_atoms = []
                for k in self._dep_keys:
                        v = self.metadata.get(k)
                        if not v:
                                continue
                        try:
-                               use_reduce(v, eapi=dep_eapi, matchall=True,
-                                       is_valid_flag=dep_valid_flag, token_class=Atom)
+                               atoms = use_reduce(v, eapi=dep_eapi,
+                                       matchall=True, is_valid_flag=dep_valid_flag,
+                                       token_class=Atom, flat=True)
                        except InvalidDependString as e:
                                self._metadata_exception(k, e)
+                       else:
+                               validated_atoms.extend(atoms)
+                               if not self.built:
+                                       for atom in atoms:
+                                               if not isinstance(atom, Atom):
+                                                       continue
+                                               if atom.slot_operator_built:
+                                                       e = InvalidDependString(
+                                                               _("Improper context for slot-operator "
+                                                               "\"built\" atom syntax: %s") %
+                                                               (atom.unevaluated_atom,))
+                                                       self._metadata_exception(k, e)
+
+               self._validated_atoms = tuple(set(atom for atom in
+                       validated_atoms if isinstance(atom, Atom)))
 
                k = 'PROVIDE'
                v = self.metadata.get(k)
@@ -194,14 +245,14 @@ class Package(Task):
 
                k = 'REQUIRED_USE'
                v = self.metadata.get(k)
-               if v:
+               if v and not self.built:
                        if not _get_eapi_attrs(eapi).required_use:
                                self._invalid_metadata('EAPI.incompatible',
                                        "REQUIRED_USE set, but EAPI='%s' doesn't allow it" % eapi)
                        else:
                                try:
                                        check_required_use(v, (),
-                                               self.iuse.is_valid_flag)
+                                               self.iuse.is_valid_flag, eapi=eapi)
                                except InvalidDependString as e:
                                        # Force unicode format string for python-2.x safety,
                                        # ensuring that PortageException.__unicode__() is used
@@ -326,6 +377,11 @@ class Package(Task):
 
        def _metadata_exception(self, k, e):
 
+               if k.endswith('DEPEND'):
+                       qacat = 'dependency.syntax'
+               else:
+                       qacat = k + ".syntax"
+
                # For unicode safety with python-2.x we need to avoid
                # using the string format operator with a non-unicode
                # format string, since that will result in the
@@ -346,7 +402,7 @@ class Package(Task):
                                                _unicode_decode("%s: %s") % (k, error))
 
                        if not categorized_error:
-                               self._invalid_metadata(k + ".syntax",
+                               self._invalid_metadata(qacat,
                                        _unicode_decode("%s: %s") % (k, e))
                else:
                        # For installed packages, show the path of the file
@@ -354,7 +410,7 @@ class Package(Task):
                        # want to fix the deps by hand.
                        vardb = self.root_config.trees['vartree'].dbapi
                        path = vardb.getpath(self.cpv, filename=k)
-                       self._invalid_metadata(k + ".syntax",
+                       self._invalid_metadata(qacat,
                                _unicode_decode("%s: %s in '%s'") % (k, e, path))
 
        def _invalid_metadata(self, msg_type, msg):
@@ -415,7 +471,11 @@ class Package(Task):
                        self._expand_hidden = None
                        self._force = None
                        self._mask = None
-                       self.enabled = frozenset(use_str.split())
+                       enabled_flags = use_str.split()
+                       if eapi_has_use_aliases(pkg.metadata["EAPI"]):
+                               for enabled_flag in enabled_flags:
+                                       enabled_flags.extend(pkg.iuse.alias_mapping.get(enabled_flag, []))
+                       self.enabled = frozenset(enabled_flags)
                        if pkg.built:
                                # Use IUSE to validate USE settings for built packages,
                                # in case the package manager that built this package
@@ -488,26 +548,42 @@ class Package(Task):
 
        class _iuse(object):
 
-               __slots__ = ("__weakref__", "all", "enabled", "disabled",
-                       "tokens") + ("_iuse_implicit_match",)
+               __slots__ = ("__weakref__", "_iuse_implicit_match", "_pkg", "alias_mapping",
+                       "all", "all_aliases", "enabled", "disabled", "tokens")
 
-               def __init__(self, tokens, iuse_implicit_match):
+               def __init__(self, pkg, tokens, iuse_implicit_match, aliases, eapi):
+                       self._pkg = pkg
                        self.tokens = tuple(tokens)
                        self._iuse_implicit_match = iuse_implicit_match
                        enabled = []
                        disabled = []
                        other = []
+                       enabled_aliases = []
+                       disabled_aliases = []
+                       other_aliases = []
+                       aliases_supported = eapi_has_use_aliases(eapi)
+                       self.alias_mapping = {}
                        for x in tokens:
                                prefix = x[:1]
                                if prefix == "+":
                                        enabled.append(x[1:])
+                                       if aliases_supported:
+                                               self.alias_mapping[x[1:]] = aliases.get(x[1:], [])
+                                               enabled_aliases.extend(self.alias_mapping[x[1:]])
                                elif prefix == "-":
                                        disabled.append(x[1:])
+                                       if aliases_supported:
+                                               self.alias_mapping[x[1:]] = aliases.get(x[1:], [])
+                                               disabled_aliases.extend(self.alias_mapping[x[1:]])
                                else:
                                        other.append(x)
-                       self.enabled = frozenset(enabled)
-                       self.disabled = frozenset(disabled)
+                                       if aliases_supported:
+                                               self.alias_mapping[x] = aliases.get(x, [])
+                                               other_aliases.extend(self.alias_mapping[x])
+                       self.enabled = frozenset(chain(enabled, enabled_aliases))
+                       self.disabled = frozenset(chain(disabled, disabled_aliases))
                        self.all = frozenset(chain(enabled, disabled, other))
+                       self.all_aliases = frozenset(chain(enabled_aliases, disabled_aliases, other_aliases))
 
                def is_valid_flag(self, flags):
                        """
@@ -518,7 +594,7 @@ class Package(Task):
                                flags = [flags]
 
                        for flag in flags:
-                               if not flag in self.all and \
+                               if not flag in self.all and not flag in self.all_aliases and \
                                        not self._iuse_implicit_match(flag):
                                        return False
                        return True
@@ -531,11 +607,22 @@ class Package(Task):
                                flags = [flags]
                        missing_iuse = []
                        for flag in flags:
-                               if not flag in self.all and \
+                               if not flag in self.all and not flag in self.all_aliases and \
                                        not self._iuse_implicit_match(flag):
                                        missing_iuse.append(flag)
                        return missing_iuse
 
+               def get_real_flag(self, flag):
+                       if flag in self.all:
+                               return flag
+                       elif flag in self.all_aliases:
+                               for k, v in self.alias_mapping.items():
+                                       if flag in v:
+                                               return k
+                       else:
+                               raise ValueError("'%s' flag is not in IUSE and is not an alias of any flag in IUSE of '%s::%s'" %
+                                       (flag, self._pkg.cpv, self._pkg.repo))
+
        def __len__(self):
                return 4
 
@@ -588,7 +675,7 @@ class _PackageMetadataWrapper(_PackageMetadataWrapperBase):
 
        __slots__ = ("_pkg",)
        _wrapped_keys = frozenset(
-               ["COUNTER", "INHERITED", "IUSE", "SLOT", "USE", "_mtime_"])
+               ["COUNTER", "INHERITED", "USE", "_mtime_"])
        _use_conditional_keys = frozenset(
                ['LICENSE', 'PROPERTIES', 'PROVIDE', 'RESTRICT',])
 
@@ -657,13 +744,6 @@ class _PackageMetadataWrapper(_PackageMetadataWrapperBase):
                        v = frozenset(v.split())
                self._pkg.inherited = v
 
-       def _set_iuse(self, k, v):
-               self._pkg.iuse = self._pkg._iuse(
-                       v.split(), self._pkg.root_config.settings._iuse_implicit_match)
-
-       def _set_slot(self, k, v):
-               self._pkg.slot = v
-
        def _set_counter(self, k, v):
                if isinstance(v, basestring):
                        try: