import os
import re
import subprocess
import sys
from collections import defaultdict
from functools import partial
from weakref import WeakValueDictionary

from snakeoil import klass
from snakeoil.demandload import demand_compile_regexp
from snakeoil.mappings import ImmutableDict, OrderedFrozenSet, inject_getitem_as_getattr
from snakeoil.osutils import pjoin
from snakeoil.process.spawn import bash_version

LATEST_PMS_EAPI_VER = "8"


def get_latest_PMS_eapi():
    """return the latest PMS EAPI object known to this version of pkgcore"""
    return get_eapi(LATEST_PMS_EAPI_VER)


from ..log import logger
from . import atom, const

demand_compile_regexp("_valid_EAPI_regex", r"^[A-Za-z0-9_][A-Za-z0-9+_.-]*$")
demand_compile_regexp("_valid_use_flag", r"^[A-Za-z0-9][A-Za-z0-9+_@-]*$")

eapi_optionals = ImmutableDict(
    {
        # Controls whether PROPERTIES and RESTRICT are accumulated across eclasses.
        "accumulate_properties_restrict": False,
        # Controls what version of bash compatibility to force; see PMS.
        "bash_compat": "3.2",
        # Controls whether -r is allowed for dodoc.
        "dodoc_allow_recursive": False,
        # Controls the language awareness of doman; see PMS.
        "doman_language_detect": False,
        # Controls whether -i18n option is allowed.
        "doman_language_override": False,
        # Controls whether dosym -r option is allowed.
        "dosym_relative": False,
        # Controls whether an ebuild_phase function exists for ebuild consumption.
        "ebuild_phase_func": False,
        # Controls whether REPLACING vars are exported to ebuilds; see PMS.
        "exports_replacing": False,
        # Controls of whether failglob is enabled globally; see PMS.
        "global_failglob": False,
        # Controls whether MERGE vars are exported to ebuilds; see PMS.
        "has_merge_type": False,
        # Controls whether PORTDIR and ECLASSDIR are exported to ebuilds; see PMS.
        "has_portdir": True,
        # Controls whether DESTTREE and INSDESTTREE are exported during src_install; see PMS.
        "has_desttree": True,
        # Controls whether atoms support USE dependencies.
        "has_use_deps": False,
        # Controls wheter atoms support slot dependencies",
        "has_slot_deps": False,
        # Controls whether ROOT, EROOT, D, and ED end with a trailing slash; see PMS.
        "trailing_slash": os.sep,
        # Controls whether SYSROOT, ESYSROOT, and BROOT are defined; see PMS.
        "has_sysroot": False,
        # Controls whether package.provided files in profiles are supported; see PMS.
        "profile_pkg_provided": True,
        # Controls whether package.mask and other files in profiles can
        # be directories; see PMS.
        "has_profile_data_dirs": False,
        # Controls whether REQUIRED_USE is supported, enforcing constraints on
        # allowed use configuration states.
        "has_required_use": False,
        # Controls whether USE dependency defaults are supported, see PMS.
        "has_use_dep_defaults": False,
        # Controls whether ENV_UNSET is supported, see PMS.
        "has_env_unset": False,
        # Controls whether AA env var is exported to ebuilds; this is a flattened
        # listing of each filename in SRC_URI.
        "has_AA": True,
        # Controls whether KV (kernel version; see PMS for details) is exported.
        "has_KV": True,
        # Controls whether or not pkgcore, or extensions loaded, actually fully
        # support this EAPI.
        "is_supported": True,
        # Controls whether IUSE defaults are supported; see PMS.
        "iuse_defaults": False,
        # Controls whether new* style bash functions can take their content input
        # from stdin, rather than an explicit ondisk file.
        "new_reads_stdin": False,
        # Controls whether utilities die on failure; see PMS.
        "nonfatal": True,
        # Controls whether die supports a nonfatal option; see PMS.
        "nonfatal_die": False,
        # Controls whether this EAPI supports prefix related variables/settings;
        # prefix awareness basically. See PMS for full details.
        "prefix_capable": False,
        # Controls whether profile-defined IUSE injection is supported.
        "profile_iuse_injection": False,
        # Controls whether profiles support package.use.stable.* and use.stable.* files.
        "profile_stable_use": False,
        # Controls whether has_version/best_version supports --host-root option; see PMS.
        "query_host_root": False,
        # Controls whether has_version/best_version supports -b/-d/-r options; see PMS.
        "query_deps": False,
        # Controls whether SLOT values can actually be multi-part; see PMS EAPI 5.
        # This is related to ABI breakage detection.
        "sub_slotting": False,
        # Controls whether REQUIRED_USE supports the ?? operator.
        "required_use_one_of": False,
        # Controls whether SRC_URI supports the '->' operator for url filename renaming.
        "src_uri_renames": False,
        # Controls whether SRC_URI supports fetch+ and mirror+ prefixes.
        "src_uri_unrestrict": False,
        # Controls whether strong blockers- hard deps of "things are broken after this merge" are supported for atom syntax.
        "strong_blockers": False,
        # Controls whether or not use dependency atoms are able to control their enforced
        # value relative to another; standard use deps just enforce either on or off; EAPIs
        # supporting this allow syntax that can enforce (for example) X to be on if Y is on.
        # See PMS EAPI 4 for full details.
        "transitive_use_atoms": False,
        # Controls whether or DEFINED_PHASES is mandated for this EAPI; if so, then we can
        # trust the cache definition and skip invoking those phases if they're not defined.
        # If the EAPI didn't mandate this var, then we can do our inference, but generally will
        # invoke the phase in the absense of that metadata var since we have no other choice.
        "trust_defined_phases_cache": False,
        # Controls whether unpack supports absolute paths; see PMS.
        "unpack_absolute_paths": False,
        # Controls whether unpack supports absolute paths; see PMS.
        "unpack_case_insensitive": False,
        # Regular expression to filter out valid update files
        "update_regex": re.compile(r"^([1-4])Q-(\d{4})$"),
        # Controls whether user patches are supported.
        "user_patches": False,
    }
)


class _optionals_cls(ImmutableDict):
    inject_getitem_as_getattr(locals())


class EAPI(metaclass=klass.immutable_instance):
    known_eapis = WeakValueDictionary()
    unknown_eapis = WeakValueDictionary()

    def __init__(
        self,
        magic,
        parent=None,
        phases=(),
        default_phases=(),
        mandatory_keys=(),
        dep_keys=(),
        metadata_keys=(),
        eclass_keys=(),
        tracked_attributes=(),
        archive_exts=(),
        optionals=None,
        ebd_env_options=None,
    ):
        sf = object.__setattr__

        sf(self, "_magic", str(magic))
        sf(self, "_parent", parent)

        sf(self, "phases", ImmutableDict(phases))
        sf(self, "phases_rev", ImmutableDict((v, k) for k, v in self.phases.items()))

        # We track the phases that have a default implementation- this is
        # primarily due to DEFINED_PHASES cache values not including it.
        sf(self, "default_phases", frozenset(default_phases))

        sf(self, "mandatory_keys", frozenset(mandatory_keys))
        sf(self, "dep_keys", frozenset(dep_keys))
        sf(
            self,
            "metadata_keys",
            (self.mandatory_keys | self.dep_keys | frozenset(metadata_keys)),
        )
        # variables that eclasses have access to (used by pkgcheck eclass inherit checks)
        sf(
            self,
            "eclass_keys",
            self.mandatory_keys | self.dep_keys | frozenset(eclass_keys),
        )
        sf(
            self,
            "tracked_attributes",
            (frozenset(tracked_attributes) | frozenset(x.lower() for x in dep_keys)),
        )
        sf(self, "archive_exts", frozenset(archive_exts))

        if optionals is None:
            optionals = {}
        sf(self, "options", _optionals_cls(optionals))
        if ebd_env_options is None:
            ebd_env_options = ()
        sf(self, "_ebd_env_options", ebd_env_options)

    @classmethod
    def register(cls, *args, **kwds):
        eapi = cls(*args, **kwds)
        pre_existing = cls.known_eapis.get(eapi._magic)
        if pre_existing is not None:
            raise ValueError(
                f"EAPI '{eapi}' is already known/instantiated- {pre_existing!r}"
            )

        if (
            getattr(eapi.options, "bash_compat", False)
            and bash_version() < eapi.options.bash_compat
        ):
            # hard exit if the system doesn't have an adequate bash installed
            raise SystemExit(
                f"EAPI '{eapi}' requires >=bash-{eapi.options.bash_compat}, "
                f"system version: {bash_version()}"
            )

        cls.known_eapis[eapi._magic] = eapi
        # generate EAPI bash libs when running from git repo
        eapi.bash_libs()
        return eapi

    @klass.jit_attr
    def is_supported(self):
        """Check if an EAPI is supported."""
        if EAPI.known_eapis.get(self._magic) is not None:
            if not self.options.is_supported:
                logger.warning(f"EAPI '{self}' isn't fully supported")
                sys.stderr.flush()
            return True
        return False

    @klass.jit_attr
    def bash_funcs_global(self):
        """Internally implemented global EAPI specific functions to skip when exporting."""
        # TODO: This is currently duplicated across EAPI objs, but
        # instead could be cached to a class attr.
        funcs = pjoin(const.EBD_PATH, ".generated", "funcs", "global")
        if not os.path.exists(funcs):
            # we're probably running in a cacheless git repo, so generate a cached version
            try:
                os.makedirs(os.path.dirname(funcs), exist_ok=True)
                with open(funcs, "w") as f:
                    subprocess.run(
                        [pjoin(const.EBD_PATH, "generate_global_func_list")],
                        cwd=const.EBD_PATH,
                        stdout=f,
                    )
            except (IOError, subprocess.CalledProcessError) as e:
                raise Exception(
                    f"failed to generate list of global EAPI '{self}' specific functions: {str(e)}"
                )

        with open(funcs, "r") as f:
            return frozenset(line.strip() for line in f)

    @klass.jit_attr
    def bash_funcs(self):
        """Internally implemented EAPI specific functions to skip when exporting."""
        funcs = pjoin(const.EBD_PATH, ".generated", "funcs", self._magic)
        if not os.path.exists(funcs):
            # we're probably running in a cacheless git repo, so generate a cached version
            try:
                os.makedirs(os.path.dirname(funcs), exist_ok=True)
                with open(funcs, "w") as f:
                    subprocess.run(
                        [pjoin(const.EBD_PATH, "generate_eapi_func_list"), self._magic],
                        cwd=const.EBD_PATH,
                        stdout=f,
                    )
            except (IOError, subprocess.CalledProcessError) as e:
                raise Exception(
                    f"failed to generate list of EAPI '{self}' specific functions: {str(e)}"
                )

        with open(funcs, "r") as f:
            return frozenset(line.strip() for line in f)

    @klass.jit_attr
    def bash_cmds_internal(self):
        """EAPI specific commands for this EAPI."""
        cmds = pjoin(const.EBD_PATH, ".generated", "cmds", self._magic, "internal")
        if not os.path.exists(cmds):
            # we're probably running in a cacheless git repo, so generate a cached version
            try:
                os.makedirs(os.path.dirname(cmds), exist_ok=True)
                with open(cmds, "w") as f:
                    subprocess.run(
                        [
                            pjoin(const.EBD_PATH, "generate_eapi_cmd_list"),
                            "-i",
                            self._magic,
                        ],
                        cwd=const.EBD_PATH,
                        stdout=f,
                    )
            except (IOError, subprocess.CalledProcessError) as e:
                raise Exception(
                    f"failed to generate list of EAPI {self} internal commands: {str(e)}"
                )

        with open(cmds, "r") as f:
            return frozenset(line.strip() for line in f)

    @klass.jit_attr
    def bash_cmds_deprecated(self):
        """EAPI specific commands deprecated for this EAPI."""
        cmds = pjoin(const.EBD_PATH, ".generated", "cmds", self._magic, "deprecated")
        if not os.path.exists(cmds):
            # we're probably running in a cacheless git repo, so generate a cached version
            try:
                os.makedirs(os.path.dirname(cmds), exist_ok=True)
                with open(cmds, "w") as f:
                    subprocess.run(
                        [
                            pjoin(const.EBD_PATH, "generate_eapi_cmd_list"),
                            "-d",
                            self._magic,
                        ],
                        cwd=const.EBD_PATH,
                        stdout=f,
                    )
            except (IOError, subprocess.CalledProcessError) as e:
                raise Exception(
                    f"failed to generate list of EAPI {self} deprecated commands: {str(e)}"
                )

        with open(cmds, "r") as f:
            return frozenset(line.strip() for line in f)

    @klass.jit_attr
    def bash_cmds_banned(self):
        """EAPI specific commands banned for this EAPI."""
        cmds = pjoin(const.EBD_PATH, ".generated", "cmds", self._magic, "banned")
        if not os.path.exists(cmds):
            # we're probably running in a cacheless git repo, so generate a cached version
            try:
                os.makedirs(os.path.dirname(cmds), exist_ok=True)
                with open(cmds, "w") as f:
                    subprocess.run(
                        [
                            pjoin(const.EBD_PATH, "generate_eapi_cmd_list"),
                            "-b",
                            self._magic,
                        ],
                        cwd=const.EBD_PATH,
                        stdout=f,
                    )
            except (IOError, subprocess.CalledProcessError) as e:
                raise Exception(
                    f"failed to generate list of EAPI {self} banned commands: {str(e)}"
                )

        with open(cmds, "r") as f:
            return frozenset(line.strip() for line in f)

    def bash_libs(self):
        """Generate internally implemented EAPI specific bash libs required by the ebd."""
        eapi_global_lib = pjoin(
            const.EBD_PATH, ".generated", "libs", self._magic, "global"
        )
        script = pjoin(const.EBD_PATH, "generate_eapi_lib")
        # skip generation when installing as the install process takes care of it
        if not os.path.exists(script):
            return

        if not os.path.exists(eapi_global_lib):
            try:
                os.makedirs(os.path.dirname(eapi_global_lib), exist_ok=True)
                with open(eapi_global_lib, "w") as f:
                    subprocess.run(
                        [script, "-s", "global", self._magic],
                        cwd=const.EBD_PATH,
                        stdout=f,
                    )
            except (IOError, subprocess.CalledProcessError) as e:
                raise Exception(
                    f"failed to generate EAPI '{self}' global lib: {str(e)}"
                )

        for phase in self.phases.values():
            eapi_lib = pjoin(const.EBD_PATH, ".generated", "libs", self._magic, phase)
            if not os.path.exists(eapi_lib):
                try:
                    os.makedirs(os.path.dirname(eapi_lib), exist_ok=True)
                    with open(eapi_lib, "w") as f:
                        subprocess.run(
                            [script, "-s", phase, self._magic],
                            cwd=const.EBD_PATH,
                            stdout=f,
                        )
                except (IOError, subprocess.CalledProcessError) as e:
                    raise Exception(
                        f"failed to generate EAPI '{self}' phase {phase} lib: {str(e)}"
                    )

    @klass.jit_attr
    def archive_exts_regex_pattern(self):
        """Regex pattern for supported archive extensions."""
        pattern = "|".join(map(re.escape, self.archive_exts))
        if self.options.unpack_case_insensitive:
            return f"(?i:({pattern}))"
        return f"({pattern})"

    @klass.jit_attr
    def archive_exts_regex(self):
        """Regex matching strings ending with supported archive extensions."""
        return re.compile(rf"{self.archive_exts_regex_pattern}$")

    @klass.jit_attr
    def valid_slot_regex(self):
        """Regex matching valid SLOT values."""
        valid_slot = r"[A-Za-z0-9_][A-Za-z0-9+_.-]*"
        if self.options.sub_slotting:
            valid_slot += rf"(/{valid_slot})?"
        return re.compile(rf"^{valid_slot}$")

    @klass.jit_attr
    def atom_kls(self):
        return partial(atom.atom, eapi=self._magic)

    def interpret_cache_defined_phases(self, sequence):
        phases = set(sequence)
        if not self.options.trust_defined_phases_cache:
            if not phases:
                # run them all; cache was generated
                # by a pm that didn't support DEFINED_PHASES
                return frozenset(self.phases)

        phases.discard("-")
        return frozenset(phases)

    def __str__(self):
        return self._magic

    @klass.jit_attr
    def inherits(self):
        """Ordered set containing an EAPI's inheritance tree.

        Note that this assumes a simple, linear inheritance tree.
        """
        eapis = [self]
        eapi = self
        while eapi := eapi._parent:
            eapis.append(eapi)
        return OrderedFrozenSet(eapis)

    @klass.jit_attr
    def helpers(self):
        """Phase to directory mapping for EAPI specific helpers to add to $PATH."""
        paths = defaultdict(list)
        for eapi in self.inherits:
            paths["global"].append(pjoin(const.EBUILD_HELPERS_PATH, "common"))
            helper_dir = pjoin(const.EBUILD_HELPERS_PATH, eapi._magic)
            for dirpath, dirnames, filenames in os.walk(helper_dir):
                if not filenames:
                    continue
                if dirpath == helper_dir:
                    paths["global"].append(dirpath)
                else:
                    phase = os.path.basename(dirpath)
                    if phase in self.phases_rev:
                        paths[phase].append(dirpath)
                    else:
                        raise ValueError(f"unknown phase: {phase!r}")
        return ImmutableDict((k, tuple(v)) for k, v in paths.items())

    @klass.jit_attr
    def ebd_env(self):
        """Dictionary of EAPI options passed to the ebd environment."""
        d = {}
        for k in self._ebd_env_options:
            d[f"PKGCORE_{k.upper()}"] = str(getattr(self.options, k)).lower()
        d["PKGCORE_EAPI_INHERITS"] = " ".join(x._magic for x in self.inherits)
        d["EAPI"] = self._magic
        return ImmutableDict(d)

    def is_valid_use_flag(self, s: str) -> bool:
        """returns True if the flag is parsable under this EAPI"""
        return _valid_use_flag.match(s) is not None


def get_eapi(magic, suppress_unsupported=True):
    """Return EAPI object for a given identifier."""
    if _valid_EAPI_regex.match(magic) is None:
        eapi_str = f" {magic!r}" if magic else ""
        raise ValueError(f"invalid EAPI{eapi_str}")
    eapi = EAPI.known_eapis.get(magic)
    if eapi is None and suppress_unsupported:
        eapi = EAPI.unknown_eapis.get(magic)
        if eapi is None:
            eapi = EAPI(magic=magic, optionals={"is_supported": False})
            EAPI.unknown_eapis[eapi._magic] = eapi
    return eapi


def _shorten_phase_name(func_name):
    if func_name.startswith(("src_", "pkg_")):
        return func_name[4:]
    return func_name


def _mk_phase_func_map(*sequence):
    return {_shorten_phase_name(x): x for x in sequence}


def _combine_dicts(*mappings):
    return {k: v for d in mappings for k, v in d.items()}


# Note that pkg_setup is forced by default since this is how our env setup occurs.
common_default_phases = tuple(
    _shorten_phase_name(x)
    for x in ("pkg_setup", "src_unpack", "src_compile", "src_test", "pkg_nofetch")
)

common_phases = (
    "pkg_setup",
    "pkg_config",
    "pkg_info",
    "pkg_nofetch",
    "pkg_prerm",
    "pkg_postrm",
    "pkg_preinst",
    "pkg_postinst",
    "src_unpack",
    "src_compile",
    "src_test",
    "src_install",
)

common_mandatory_metadata_keys = (
    "DESCRIPTION",
    "HOMEPAGE",
    "IUSE",
    "KEYWORDS",
    "LICENSE",
    "SLOT",
    "SRC_URI",
)

common_dep_keys = (
    "DEPEND",
    "RDEPEND",
    "PDEPEND",
)

common_metadata_keys = (
    "RESTRICT",
    "PROPERTIES",
    "DEFINED_PHASES",
    "INHERIT",
    "INHERITED",
    "EAPI",
)

common_eclass_keys = ("S", "RESTRICT", "PROPERTIES", "ECONF_SOURCE")

common_tracked_attributes = (
    "cflags",
    "cbuild",
    "chost",
    "ctarget",
    "cxxflags",
    "defined_phases",
    "description",
    "eapi",
    "distfiles",
    "fullslot",
    "homepage",
    "inherited",
    "iuse",
    "keywords",
    "ldflags",
    "license",
    "properties",
    "restrict",
    "source_repository",
)

common_archive_exts = (
    ".tar",
    ".tar.gz",
    ".tgz",
    ".tar.Z",
    ".tar.z",
    ".tar.bz2",
    ".tbz2",
    ".tbz",
    ".zip",
    ".ZIP",
    ".jar",
    ".gz",
    ".Z",
    ".z",
    ".bz2",
    ".rar",
    ".RAR",
    ".lha",
    ".LHa",
    ".LHA",
    ".lzh",
    ".a",
    ".deb",
    ".tar.lzma",
    ".lzma",
    ".7z",
    ".7Z",
)

# Boolean variables exported to the bash side, e.g. ebuild_phase_func is
# exported as PKGCORE_EBUILD_PHASE_FUNC.
common_env_optionals = (
    "bash_compat",
    "ebuild_phase_func",
    "global_failglob",
    "new_reads_stdin",
    "nonfatal",
    "nonfatal_die",
    "has_desttree",
    "accumulate_properties_restrict",
)

eapi0 = EAPI.register(
    magic="0",
    parent=None,
    phases=_mk_phase_func_map(*common_phases),
    default_phases=_mk_phase_func_map(*common_default_phases),
    mandatory_keys=common_mandatory_metadata_keys,
    dep_keys=common_dep_keys,
    metadata_keys=common_metadata_keys,
    eclass_keys=common_eclass_keys,
    tracked_attributes=common_tracked_attributes,
    archive_exts=common_archive_exts,
    optionals=eapi_optionals,
    ebd_env_options=common_env_optionals,
)

eapi1 = EAPI.register(
    magic="1",
    parent=eapi0,
    phases=eapi0.phases,
    default_phases=eapi0.default_phases,
    mandatory_keys=eapi0.mandatory_keys,
    dep_keys=eapi0.dep_keys,
    metadata_keys=eapi0.metadata_keys,
    eclass_keys=eapi0.eclass_keys,
    tracked_attributes=eapi0.tracked_attributes,
    archive_exts=eapi0.archive_exts,
    optionals=_combine_dicts(
        eapi0.options,
        dict(
            iuse_defaults=True,
            has_slot_deps=True,
        ),
    ),
    ebd_env_options=eapi0._ebd_env_options,
)

eapi2 = EAPI.register(
    magic="2",
    parent=eapi1,
    phases=_combine_dicts(
        eapi1.phases, _mk_phase_func_map("src_prepare", "src_configure")
    ),
    default_phases=eapi1.default_phases.union(
        list(map(_shorten_phase_name, ["src_prepare", "src_configure"]))
    ),
    mandatory_keys=eapi1.mandatory_keys,
    dep_keys=eapi1.dep_keys,
    metadata_keys=eapi1.metadata_keys,
    eclass_keys=eapi1.eclass_keys,
    tracked_attributes=eapi1.tracked_attributes,
    archive_exts=eapi1.archive_exts,
    optionals=_combine_dicts(
        eapi1.options,
        dict(
            has_use_deps=True,
            strong_blockers=True,
            doman_language_detect=True,
            transitive_use_atoms=True,
            src_uri_renames=True,
        ),
    ),
    ebd_env_options=eapi1._ebd_env_options,
)

eapi3 = EAPI.register(
    magic="3",
    parent=eapi2,
    phases=eapi2.phases,
    default_phases=eapi2.default_phases,
    mandatory_keys=eapi2.mandatory_keys,
    dep_keys=eapi2.dep_keys,
    metadata_keys=eapi2.metadata_keys,
    eclass_keys=eapi2.eclass_keys | frozenset(["EPREFIX", "ED", "EROOT"]),
    tracked_attributes=eapi2.tracked_attributes,
    archive_exts=eapi2.archive_exts | frozenset([".tar.xz", ".xz"]),
    optionals=_combine_dicts(
        eapi2.options,
        dict(
            prefix_capable=True,
        ),
    ),
    ebd_env_options=eapi2._ebd_env_options,
)

eapi4 = EAPI.register(
    magic="4",
    parent=eapi3,
    phases=_combine_dicts(eapi3.phases, _mk_phase_func_map("pkg_pretend")),
    default_phases=eapi3.default_phases.union([_shorten_phase_name("src_install")]),
    mandatory_keys=eapi3.mandatory_keys,
    dep_keys=eapi3.dep_keys,
    metadata_keys=eapi3.metadata_keys | frozenset(["REQUIRED_USE"]),
    eclass_keys=eapi3.eclass_keys | frozenset(["DOCS", "REQUIRED_USE"]),
    tracked_attributes=eapi3.tracked_attributes,
    archive_exts=eapi3.archive_exts,
    optionals=_combine_dicts(
        eapi3.options,
        dict(
            dodoc_allow_recursive=True,
            doman_language_override=True,
            nonfatal=False,
            exports_replacing=True,
            has_AA=False,
            has_KV=False,
            has_merge_type=True,
            has_required_use=True,
            has_use_dep_defaults=True,
            trust_defined_phases_cache=True,
        ),
    ),
    ebd_env_options=eapi3._ebd_env_options,
)

eapi5 = EAPI.register(
    magic="5",
    parent=eapi4,
    phases=eapi4.phases,
    default_phases=eapi4.default_phases,
    mandatory_keys=eapi4.mandatory_keys,
    dep_keys=eapi4.dep_keys,
    metadata_keys=eapi4.metadata_keys,
    eclass_keys=eapi4.eclass_keys,
    tracked_attributes=eapi4.tracked_attributes | frozenset(["iuse_effective"]),
    archive_exts=eapi4.archive_exts,
    optionals=_combine_dicts(
        eapi4.options,
        dict(
            ebuild_phase_func=True,
            profile_iuse_injection=True,
            profile_stable_use=True,
            query_host_root=True,
            new_reads_stdin=True,
            required_use_one_of=True,
            sub_slotting=True,
        ),
    ),
    ebd_env_options=eapi4._ebd_env_options,
)

eapi6 = EAPI.register(
    magic="6",
    parent=eapi5,
    phases=eapi5.phases,
    default_phases=eapi5.default_phases,
    mandatory_keys=eapi5.mandatory_keys,
    dep_keys=eapi5.dep_keys,
    metadata_keys=eapi5.metadata_keys,
    eclass_keys=eapi5.eclass_keys | frozenset(["HTML_DOCS", "PATCHES"]),
    tracked_attributes=eapi5.tracked_attributes | frozenset(["user_patches"]),
    archive_exts=eapi5.archive_exts | frozenset([".txz"]),
    optionals=_combine_dicts(
        eapi5.options,
        dict(
            global_failglob=True,
            nonfatal_die=True,
            unpack_absolute_paths=True,
            unpack_case_insensitive=True,
            user_patches=True,
            bash_compat="4.2",
        ),
    ),
    ebd_env_options=eapi5._ebd_env_options,
)

eapi7 = EAPI.register(
    magic="7",
    parent=eapi6,
    phases=eapi6.phases,
    default_phases=eapi6.default_phases,
    mandatory_keys=eapi6.mandatory_keys,
    dep_keys=eapi6.dep_keys | frozenset(["BDEPEND"]),
    metadata_keys=eapi6.metadata_keys,
    eclass_keys=eapi6.eclass_keys,
    tracked_attributes=eapi6.tracked_attributes,
    archive_exts=eapi6.archive_exts,
    optionals=_combine_dicts(
        eapi6.options,
        dict(
            has_profile_data_dirs=True,
            has_portdir=False,
            has_desttree=False,
            profile_pkg_provided=False,
            query_host_root=False,
            query_deps=True,
            has_sysroot=True,
            has_env_unset=True,
            trailing_slash="",
        ),
    ),
    ebd_env_options=eapi6._ebd_env_options,
)

eapi8 = EAPI.register(
    magic="8",
    parent=eapi7,
    phases=eapi7.phases,
    default_phases=eapi7.default_phases,
    mandatory_keys=eapi7.mandatory_keys,
    dep_keys=eapi7.dep_keys | frozenset(["IDEPEND"]),
    metadata_keys=eapi7.metadata_keys,
    eclass_keys=eapi7.eclass_keys,
    tracked_attributes=eapi7.tracked_attributes,
    archive_exts=eapi7.archive_exts
    - frozenset(
        [
            ".rar",
            ".RAR",
            ".lha",
            ".LHa",
            ".LHA",
            ".lzh",
            ".7z",
            ".7Z",
        ]
    ),
    optionals=_combine_dicts(
        eapi7.options,
        dict(
            accumulate_properties_restrict=True,
            bash_compat="5.0",
            dosym_relative=True,
            src_uri_unrestrict=True,
            update_regex=re.compile(r"^[^.]"),
        ),
    ),
    ebd_env_options=eapi7._ebd_env_options,
)
