# Copyright 1999-2025 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2

import argparse
import locale
import platform
import shlex
import sys

import portage

portage.proxy.lazyimport.lazyimport(
    globals(),
    "logging",
    "portage.dep:Atom",
    "portage.output:xtermTitleReset",
    "portage.util:writemsg_level",
    "textwrap",
    "_emerge.actions:load_emerge_config,run_action," + "validate_ebuild_environment",
    "_emerge.emergelog:emergelog",
    "_emerge.help:emerge_help",
    "_emerge.is_valid_package_atom:insert_category_into_atom",
)
from portage import os
from portage.sync import _SUBMODULE_PATH_MAP

from typing import Optional

options = [
    "--alphabetical",
    "--ask-enter-invalid",
    "--buildpkgonly",
    "--changed-use",
    "--columns",
    "--debug",
    "--digest",
    "--emptytree",
    "--verbose-conflicts",
    "--fetchonly",
    "--fetch-all-uri",
    "--ignore-default-opts",
    "--noconfmem",
    "--newrepo",
    "--newuse",
    "--nodeps",
    "--noreplace",
    "--nospinner",
    "--oneshot",
    "--onlydeps",
    "--pretend",
    "--quiet-repo-display",
    "--quiet-unmerge-warn",
    "--resume",
    "--searchdesc",
    "--skipfirst",
    "--tree",
    "--unordered-display",
    "--update",
    "--update-if-installed",
]

shortmapping = {
    "1": "--oneshot",
    "B": "--buildpkgonly",
    "c": "--depclean",
    "C": "--unmerge",
    "d": "--debug",
    "e": "--emptytree",
    "f": "--fetchonly",
    "F": "--fetch-all-uri",
    "h": "--help",
    "n": "--noreplace",
    "N": "--newuse",
    "o": "--onlydeps",
    "O": "--nodeps",
    "p": "--pretend",
    "P": "--prune",
    "r": "--resume",
    "s": "--search",
    "S": "--searchdesc",
    "t": "--tree",
    "u": "--update",
    "U": "--changed-use",
    "V": "--version",
}

COWSAY_MOO = r"""

  Larry loves Gentoo ({})

 _______________________
< Have you mooed today? >
 -----------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

"""


def multiple_actions(action1, action2):
    sys.stderr.write("\n!!! Multiple actions requested... Please choose one only.\n")
    sys.stderr.write(f"!!! '{action1}' or '{action2}'\n\n")
    sys.exit(1)


def insert_optional_args(args):
    """
    Parse optional arguments and insert a value if one has
    not been provided. This is done before feeding the args
    to the optparse parser since that parser does not support
    this feature natively.
    """

    class valid_integers:
        def __contains__(self, s):
            try:
                return int(s) >= 0
            except (ValueError, OverflowError):
                return False

    valid_integers = valid_integers()

    class valid_floats:
        def __contains__(self, s):
            try:
                return float(s) >= 0
            except (ValueError, OverflowError):
                return False

    valid_floats = valid_floats()

    y_or_n = (
        "y",
        "n",
    )

    new_args = []

    default_arg_opts = {
        "--alert": y_or_n,
        "--ask": y_or_n,
        "--autounmask": y_or_n,
        "--autounmask-continue": y_or_n,
        "--autounmask-only": y_or_n,
        "--autounmask-keep-keywords": y_or_n,
        "--autounmask-keep-masks": y_or_n,
        "--autounmask-unrestricted-atoms": y_or_n,
        "--autounmask-write": y_or_n,
        "--binpkg-changed-deps": y_or_n,
        "--buildpkg": y_or_n,
        "--changed-deps": y_or_n,
        "--changed-slot": y_or_n,
        "--changed-deps-report": y_or_n,
        "--complete-graph": y_or_n,
        "--deep": valid_integers,
        "--depclean-lib-check": y_or_n,
        "--deselect": y_or_n,
        "--binpkg-respect-use": y_or_n,
        "--fail-clean": y_or_n,
        "--fuzzy-search": y_or_n,
        "--getbinpkg": y_or_n,
        "--getbinpkgonly": y_or_n,
        "--ignore-world": y_or_n,
        "--jobs": valid_integers,
        "--keep-going": y_or_n,
        "--load-average": valid_floats,
        "--onlydeps-with-ideps": y_or_n,
        "--onlydeps-with-rdeps": y_or_n,
        "--package-moves": y_or_n,
        "--quiet": y_or_n,
        "--quiet-build": y_or_n,
        "--quiet-fail": y_or_n,
        "--read-news": y_or_n,
        "--rebuild-if-new-slot": y_or_n,
        "--rebuild-if-new-rev": y_or_n,
        "--rebuild-if-new-ver": y_or_n,
        "--rebuild-if-unbuilt": y_or_n,
        "--rebuilt-binaries": y_or_n,
        "--root-deps": ("rdeps",),
        "--select": y_or_n,
        "--selective": y_or_n,
        "--use-ebuild-visibility": y_or_n,
        "--usepkg": y_or_n,
        "--usepkgonly": y_or_n,
        "--usepkg-exclude-live": y_or_n,
        "--verbose": y_or_n,
        "--verbose-slot-rebuilds": y_or_n,
        "--with-test-deps": y_or_n,
    }

    short_arg_opts = {
        "D": valid_integers,
        "j": valid_integers,
        "l": valid_floats,
    }

    # Don't make things like "-kn" expand to "-k n"
    # since existence of -n makes it too ambiguous.
    short_arg_opts_n = {
        "a": y_or_n,
        "A": y_or_n,
        "b": y_or_n,
        "g": y_or_n,
        "G": y_or_n,
        "k": y_or_n,
        "K": y_or_n,
        "q": y_or_n,
        "v": y_or_n,
        "w": y_or_n,
        "W": y_or_n,
    }

    arg_stack = args[:]
    arg_stack.reverse()
    while arg_stack:
        arg = arg_stack.pop()

        default_arg_choices = default_arg_opts.get(arg)
        if default_arg_choices is not None:
            new_args.append(arg)
            if arg_stack and arg_stack[-1] in default_arg_choices:
                new_args.append(arg_stack.pop())
            else:
                # insert default argument
                new_args.append("True")
            continue

        if arg[:1] != "-" or arg[:2] == "--":
            new_args.append(arg)
            continue

        match = None
        for k, arg_choices in short_arg_opts.items():
            if k in arg:
                match = k
                break

        if match is None:
            for k, arg_choices in short_arg_opts_n.items():
                if k in arg:
                    match = k
                    break

        if match is None:
            new_args.append(arg)
            continue

        if len(arg) == 2:
            new_args.append(arg)
            if arg_stack and arg_stack[-1] in arg_choices:
                new_args.append(arg_stack.pop())
            else:
                # insert default argument
                new_args.append("True")
            continue

        # Insert an empty placeholder in order to
        # satisfy the requirements of optparse.

        new_args.append("-" + match)
        opt_arg = None
        saved_opts = None

        if arg[1:2] == match:
            if match not in short_arg_opts_n and arg[2:] in arg_choices:
                opt_arg = arg[2:]
            else:
                saved_opts = arg[2:]
                opt_arg = "True"
        else:
            saved_opts = arg[1:].replace(match, "")
            opt_arg = "True"

        if opt_arg is None and arg_stack and arg_stack[-1] in arg_choices:
            opt_arg = arg_stack.pop()

        if opt_arg is None:
            new_args.append("True")
        else:
            new_args.append(opt_arg)

        if saved_opts is not None:
            # Recycle these on arg_stack since they
            # might contain another match.
            arg_stack.append("-" + saved_opts)

    return new_args


def _find_bad_atoms(atoms, less_strict=False):
    """
    Declares all atoms as invalid that have an operator,
    a use dependency, a blocker or a repo spec.
    It accepts atoms with wildcards.
    In less_strict mode it accepts operators and repo specs.
    """
    bad_atoms = []
    for x in " ".join(atoms).split():
        atom = x
        if "/" not in x.split(":")[0]:
            x_cat = insert_category_into_atom(x, "dummy-category")
            if x_cat is not None:
                atom = x_cat

        bad_atom = False
        try:
            atom = Atom(atom, allow_wildcard=True, allow_repo=less_strict)
        except portage.exception.InvalidAtom:
            bad_atom = True

        if bad_atom or (atom.operator and not less_strict) or atom.blocker or atom.use:
            bad_atoms.append(x)
    return bad_atoms


def parse_opts(tmpcmdline, silent=False):
    myaction = None
    myopts = {}

    actions = frozenset(
        [
            "clean",
            "check-news",
            "config",
            "depclean",
            "help",
            "info",
            "list-sets",
            "metadata",
            "moo",
            "prune",
            "rage-clean",
            "regen",
            "search",
            "sync",
            "unmerge",
            "version",
        ]
    )

    longopt_aliases = {"--cols": "--columns", "--skip-first": "--skipfirst"}
    y_or_n = ("y", "n")
    true_y_or_n = ("True", "y", "n")
    true_y = ("True", "y")
    argument_options = {
        "--alert": {
            "shortopt": "-A",
            "help": "alert (terminal bell) on prompts",
            "choices": true_y_or_n,
        },
        "--ask": {
            "shortopt": "-a",
            "help": "prompt before performing any actions",
            "choices": true_y_or_n,
        },
        "--autounmask": {
            "help": "automatically unmask packages",
            "choices": true_y_or_n,
        },
        "--autounmask-backtrack": {
            "help": (
                "continue backtracking when there are autounmask "
                "configuration changes"
            ),
            "choices": ("y", "n"),
        },
        "--autounmask-continue": {
            "help": "write autounmask changes and continue",
            "choices": true_y_or_n,
        },
        "--autounmask-only": {
            "help": "only perform --autounmask",
            "choices": true_y_or_n,
        },
        "--autounmask-license": {
            "help": "allow autounmask to change package.license",
            "choices": y_or_n,
        },
        "--autounmask-unrestricted-atoms": {
            "help": "write autounmask changes with >= atoms if possible",
            "choices": true_y_or_n,
        },
        "--autounmask-use": {
            "help": "allow autounmask to change package.use",
            "choices": y_or_n,
        },
        "--autounmask-keep-keywords": {
            "help": "don't add package.accept_keywords entries",
            "choices": true_y_or_n,
        },
        "--autounmask-keep-masks": {
            "help": "don't add package.unmask entries",
            "choices": true_y_or_n,
        },
        "--autounmask-write": {
            "help": "write changes made by --autounmask to disk",
            "choices": true_y_or_n,
        },
        "--accept-properties": {
            "help": "temporarily override ACCEPT_PROPERTIES",
            "action": "store",
        },
        "--accept-restrict": {
            "help": "temporarily override ACCEPT_RESTRICT",
            "action": "store",
        },
        "--backtrack": {
            "help": "Specifies how many times to backtrack if dependency "
            + "calculation fails ",
            "action": "store",
        },
        "--binpkg-changed-deps": {
            "help": ("reject binary packages with outdated " "dependencies"),
            "choices": true_y_or_n,
        },
        "--buildpkg": {
            "shortopt": "-b",
            "help": "build binary packages",
            "choices": true_y_or_n,
        },
        "--buildpkg-exclude": {
            "help": "A space separated list of package atoms for which "
            + "no binary packages should be built. This option overrides all "
            + "possible ways to enable building of binary packages.",
            "action": "append",
        },
        "--changed-deps": {
            "help": ("replace installed packages with " "outdated dependencies"),
            "choices": true_y_or_n,
        },
        "--changed-deps-report": {
            "help": ("report installed packages with " "outdated dependencies"),
            "choices": true_y_or_n,
        },
        "--changed-slot": {
            "help": ("replace installed packages with " "outdated SLOT metadata"),
            "choices": true_y_or_n,
        },
        "--config-root": {
            "help": "specify the location for portage configuration files",
            "action": "store",
        },
        "--color": {"help": "enable or disable color output", "choices": ("y", "n")},
        "--complete-graph": {
            "help": "completely account for all known dependencies",
            "choices": true_y_or_n,
        },
        "--complete-graph-if-new-use": {
            "help": "trigger --complete-graph behavior if USE or IUSE will change for an installed package",
            "choices": y_or_n,
        },
        "--complete-graph-if-new-ver": {
            "help": "trigger --complete-graph behavior if an installed package version will change (upgrade or downgrade)",
            "choices": y_or_n,
        },
        "--deep": {
            "shortopt": "-D",
            "help": "Specifies how deep to recurse into dependencies "
            + "of packages given as arguments. If no argument is given, "
            + "depth is unlimited. Default behavior is to skip "
            + "dependencies of installed packages.",
            "action": "store",
        },
        "--depclean-lib-check": {
            "help": "check for consumers of libraries before removing them",
            "choices": true_y_or_n,
        },
        "--deselect": {
            "shortopt": "-W",
            "help": "remove atoms/sets from the world file",
            "choices": true_y_or_n,
        },
        "--dynamic-deps": {
            "help": "substitute the dependencies of installed packages with the dependencies of unbuilt ebuilds",
            "choices": y_or_n,
        },
        "--exclude": {
            "shortopt": "-X",
            "help": "A space separated list of package names or slot atoms. "
            + "Emerge won't  install any ebuild or binary package that "
            + "matches any of the given package atoms.",
            "action": "append",
        },
        "--fail-clean": {
            "help": "clean temp files after build failure",
            "choices": true_y_or_n,
        },
        "--fuzzy-search": {
            "help": "Enable or disable fuzzy search",
            "choices": true_y_or_n,
        },
        "--ignore-built-slot-operator-deps": {
            "help": "Ignore the slot/sub-slot := operator parts of dependencies that have "
            "been recorded when packages where built. This option is intended "
            "only for debugging purposes, and it only affects built packages "
            "that specify slot/sub-slot := operator dependencies.",
            "choices": y_or_n,
        },
        "--ignore-soname-deps": {
            "help": "Ignore the soname dependencies of binary and "
            "installed packages. This option is enabled by "
            "default, since soname dependencies are relatively "
            "new, and the required metadata is not guaranteed to "
            "exist for binary and installed packages built with "
            "older versions of portage.",
            "choices": y_or_n,
        },
        "--ignore-world": {
            "help": "ignore the @world package set and its dependencies",
            "choices": true_y_or_n,
        },
        "--implicit-system-deps": {
            "help": "Assume that packages may have implicit dependencies on"
            "packages which belong to the @system set",
            "choices": y_or_n,
        },
        "--jobs": {
            "shortopt": "-j",
            "help": "Specifies the number of packages to build " + "simultaneously.",
            "action": "store",
        },
        "--keep-going": {
            "help": "continue as much as possible after an error",
            "choices": true_y_or_n,
        },
        "--load-average": {
            "shortopt": "-l",
            "help": "Specifies that no new builds should be started "
            + "if there are other builds running and the load average "
            + "is at least LOAD (a floating-point number).",
            "action": "store",
        },
        "--misspell-suggestions": {
            "help": "enable package name misspell suggestions",
            "choices": ("y", "n"),
        },
        "--with-bdeps": {
            "help": "include unnecessary build time dependencies",
            "choices": ("y", "n"),
        },
        "--with-bdeps-auto": {
            "help": (
                "automatically enable --with-bdeps for installation"
                " actions, unless --usepkg is enabled"
            ),
            "choices": ("y", "n"),
        },
        "--reinstall": {
            "help": "specify conditions to trigger package reinstallation",
            "choices": ["changed-use"],
        },
        "--reinstall-atoms": {
            "help": "A space separated list of package names or slot atoms. "
            + "Emerge will treat matching packages as if they are not "
            + "installed, and reinstall them if necessary. Implies --deep.",
            "action": "append",
        },
        "--binpkg-respect-use": {
            "help": "discard binary packages if their use flags \
				don't match the current configuration",
            "choices": true_y_or_n,
        },
        "--getbinpkg": {
            "shortopt": "-g",
            "help": "fetch binary packages",
            "choices": true_y_or_n,
        },
        "--getbinpkgonly": {
            "shortopt": "-G",
            "help": "fetch binary packages only",
            "choices": true_y_or_n,
        },
        "--usepkg-exclude": {
            "help": "A space separated list of package names or slot atoms. "
            + "Emerge will ignore matching binary packages. ",
            "action": "append",
        },
        "--onlydeps-with-ideps": {
            "help": "modify interpretation of dependencies to include IDEPEND",
            "choices": true_y_or_n,
        },
        "--onlydeps-with-rdeps": {
            "help": "modify interpretation of dependencies",
            "choices": true_y_or_n,
        },
        "--rebuild-exclude": {
            "help": "A space separated list of package names or slot atoms. "
            + "Emerge will not rebuild these packages due to the "
            + "--rebuild flag. ",
            "action": "append",
        },
        "--rebuild-ignore": {
            "help": "A space separated list of package names or slot atoms. "
            + "Emerge will not rebuild packages that depend on matching "
            + "packages due to the --rebuild flag. ",
            "action": "append",
        },
        "--package-moves": {
            "help": "perform package moves when necessary",
            "choices": true_y_or_n,
        },
        "--prefix": {"help": "specify the installation prefix", "action": "store"},
        "--pkg-format": {
            "help": "format of result binary package",
            "action": "store",
        },
        "--quickpkg-direct": {
            "help": "Enable use of installed packages directly as binary packages",
            "choices": y_or_n,
        },
        "--quickpkg-direct-root": {
            "help": "Specify the root to use as the --quickpkg-direct package source",
            "action": "store",
        },
        "--quiet": {
            "shortopt": "-q",
            "help": "reduced or condensed output",
            "choices": true_y_or_n,
        },
        "--quiet-build": {
            "help": "redirect build output to logs",
            "choices": true_y_or_n,
        },
        "--quiet-fail": {
            "help": "suppresses display of the build log on stdout",
            "choices": true_y_or_n,
        },
        "--read-news": {
            "help": "offer to read unread news via eselect",
            "choices": true_y_or_n,
        },
        "--rebuild-if-new-slot": {
            "help": (
                "Automatically rebuild or reinstall packages when slot/sub-slot := "
                "operator dependencies can be satisfied by a newer slot, so that "
                "older packages slots will become eligible for removal by the "
                "--depclean action as soon as possible."
            ),
            "choices": true_y_or_n,
        },
        "--rebuild-if-new-rev": {
            "help": "Rebuild packages when dependencies that are "
            + "used at both build-time and run-time are built, "
            + "if the dependency is not already installed with the "
            + "same version and revision.",
            "choices": true_y_or_n,
        },
        "--rebuild-if-new-ver": {
            "help": "Rebuild packages when dependencies that are "
            + "used at both build-time and run-time are built, "
            + "if the dependency is not already installed with the "
            + "same version. Revision numbers are ignored.",
            "choices": true_y_or_n,
        },
        "--rebuild-if-unbuilt": {
            "help": "Rebuild packages when dependencies that are "
            + "used at both build-time and run-time are built.",
            "choices": true_y_or_n,
        },
        "--rebuilt-binaries": {
            "help": "replace installed packages with binary "
            + "packages that have been rebuilt",
            "choices": true_y_or_n,
        },
        "--rebuilt-binaries-timestamp": {
            "help": "use only binaries that are newer than this "
            + "timestamp for --rebuilt-binaries",
            "action": "store",
        },
        "--regex-search-auto": {
            "help": "Enable or disable automatic regular expression detection for search actions",
            "choices": y_or_n,
            "default": "y",
        },
        "--root": {
            "help": "specify the target root filesystem for merging packages",
            "action": "store",
        },
        "--root-deps": {
            "help": "modify interpretation of dependencies",
            "choices": ("True", "rdeps"),
        },
        "--search-index": {
            "help": "Enable or disable indexed search (enabled by default)",
            "choices": y_or_n,
        },
        "--search-similarity": {
            "help": (
                "Set minimum similarity percentage for fuzzy seach "
                "(a floating-point number between 0 and 100)"
            ),
            "action": "store",
        },
        "--select": {
            "shortopt": "-w",
            "help": "add specified packages to the world set "
            + "(inverse of --oneshot)",
            "choices": true_y_or_n,
        },
        "--selective": {"help": "identical to --noreplace", "choices": true_y_or_n},
        "--sync-submodule": {
            "help": (
                "Restrict sync to the specified submodule(s)." " (--sync action only)"
            ),
            "choices": tuple(_SUBMODULE_PATH_MAP),
            "action": "append",
        },
        "--sysroot": {
            "help": "specify the location for build dependencies specified in DEPEND",
            "action": "store",
        },
        "--use-ebuild-visibility": {
            "help": "use unbuilt ebuild metadata for visibility checks on built packages",
            "choices": true_y_or_n,
        },
        "--useoldpkg-atoms": {
            "help": "A space separated list of package names or slot atoms. "
            + "Emerge will prefer matching binary packages over newer unbuilt packages. ",
            "action": "append",
        },
        "--usepkg": {
            "shortopt": "-k",
            "help": "use binary packages",
            "choices": true_y_or_n,
        },
        "--usepkgonly": {
            "shortopt": "-K",
            "help": "use only binary packages",
            "choices": true_y_or_n,
        },
        "--usepkg-exclude-live": {
            "help": "do not install from binary packages for live ebuilds",
            "choices": true_y_or_n,
        },
        "--verbose": {
            "shortopt": "-v",
            "help": "verbose output",
            "choices": true_y_or_n,
        },
        "--verbose-slot-rebuilds": {
            "help": "verbose slot rebuild output",
            "choices": true_y_or_n,
        },
        "--with-test-deps": {
            "help": "pull in test deps for packages " + "matched by arguments",
            "choices": true_y_or_n,
        },
    }

    parser = argparse.ArgumentParser(add_help=False)

    for action_opt in actions:
        parser.add_argument(
            f"--{action_opt}",
            action="store_true",
            dest=action_opt.replace("-", "_"),
            default=False,
        )
    for myopt in options:
        parser.add_argument(
            myopt,
            action="store_true",
            dest=myopt.lstrip("--").replace("-", "_"),
            default=False,
        )
    for shortopt, longopt in shortmapping.items():
        parser.add_argument(
            f"-{shortopt}",
            action="store_true",
            dest=longopt.lstrip("--").replace("-", "_"),
            default=False,
        )
    for myalias, myopt in longopt_aliases.items():
        parser.add_argument(
            myalias,
            action="store_true",
            dest=myopt.lstrip("--").replace("-", "_"),
            default=False,
        )

    for myopt, kwargs in argument_options.items():
        shortopt = kwargs.pop("shortopt", None)
        args = [myopt]
        if shortopt is not None:
            args.append(shortopt)
        parser.add_argument(dest=myopt.lstrip("--").replace("-", "_"), *args, **kwargs)

    parser.add_argument("positional_args", nargs="*")

    tmpcmdline = insert_optional_args(tmpcmdline)

    myoptions = getattr(parser, "parse_intermixed_args", parser.parse_args)(
        args=tmpcmdline
    )

    if myoptions.alert in true_y:
        myoptions.alert = True
    else:
        myoptions.alert = None

    if myoptions.ask in true_y:
        myoptions.ask = True
    else:
        myoptions.ask = None

    if myoptions.autounmask in true_y:
        myoptions.autounmask = True

    if myoptions.autounmask_continue in true_y:
        myoptions.autounmask_continue = True

    if myoptions.autounmask_only in true_y:
        myoptions.autounmask_only = True
    else:
        myoptions.autounmask_only = None

    if myoptions.autounmask_unrestricted_atoms in true_y:
        myoptions.autounmask_unrestricted_atoms = True

    if myoptions.autounmask_keep_keywords in true_y:
        myoptions.autounmask_keep_keywords = True

    if myoptions.autounmask_keep_masks in true_y:
        myoptions.autounmask_keep_masks = True

    if myoptions.autounmask_write in true_y:
        myoptions.autounmask_write = True

    if myoptions.binpkg_changed_deps is not None:
        if myoptions.binpkg_changed_deps in true_y:
            myoptions.binpkg_changed_deps = "y"
        else:
            myoptions.binpkg_changed_deps = "n"

    if myoptions.buildpkg in true_y:
        myoptions.buildpkg = True

    if myoptions.buildpkg_exclude:
        bad_atoms = _find_bad_atoms(myoptions.buildpkg_exclude, less_strict=True)
        if bad_atoms and not silent:
            invalid_atoms = ",".join(bad_atoms)
            parser.error(
                f"Invalid Atom(s) in --buildpkg-exclude parameter: '{invalid_atoms}'\n"
            )

    if myoptions.changed_deps is not None:
        if myoptions.changed_deps in true_y:
            myoptions.changed_deps = "y"
        else:
            myoptions.changed_deps = "n"

    if myoptions.changed_deps_report is not None:
        if myoptions.changed_deps_report in true_y:
            myoptions.changed_deps_report = "y"
        else:
            myoptions.changed_deps_report = "n"

    if myoptions.changed_slot is not None:
        if myoptions.changed_slot in true_y:
            myoptions.changed_slot = True
        else:
            myoptions.changed_slot = None

    if myoptions.changed_use is not False:
        myoptions.reinstall = "changed-use"
        myoptions.changed_use = False

    if myoptions.deselect in true_y:
        myoptions.deselect = True

    if myoptions.binpkg_respect_use is not None:
        if myoptions.binpkg_respect_use in true_y:
            myoptions.binpkg_respect_use = "y"
        else:
            myoptions.binpkg_respect_use = "n"

    if myoptions.complete_graph in true_y:
        myoptions.complete_graph = True
    else:
        myoptions.complete_graph = None

    if myoptions.depclean_lib_check in true_y:
        myoptions.depclean_lib_check = True

    candidate_bad_options = (
        (myoptions.exclude, "exclude"),
        (myoptions.reinstall_atoms, "reinstall-atoms"),
        (myoptions.rebuild_exclude, "rebuild-exclude"),
        (myoptions.rebuild_ignore, "rebuild-ignore"),
        (myoptions.usepkg_exclude, "usepkg-exclude"),
        (myoptions.useoldpkg_atoms, "useoldpkg-atoms"),
    )
    bad_options = (
        (_find_bad_atoms(atoms), flag) for atoms, flag in candidate_bad_options if atoms
    )

    for bad_atoms, flag in bad_options:
        if bad_atoms and not silent:
            invalid_atoms = ",".join(bad_atoms)
            parser.error(
                f"Invalid Atom(s) in --{flag} parameter: '{invalid_atoms}' (only package names and slot atoms (with wildcards) allowed)\n"
            )

    if myoptions.fail_clean in true_y:
        myoptions.fail_clean = True

    if myoptions.fuzzy_search in true_y:
        myoptions.fuzzy_search = True

    if myoptions.getbinpkg in true_y:
        myoptions.getbinpkg = True
    else:
        myoptions.getbinpkg = None

    if myoptions.getbinpkgonly in true_y:
        myoptions.getbinpkgonly = True
    else:
        myoptions.getbinpkgonly = None

    if myoptions.ignore_world in true_y:
        myoptions.ignore_world = True

    if myoptions.keep_going in true_y:
        myoptions.keep_going = True
    else:
        myoptions.keep_going = None

    if myoptions.package_moves in true_y:
        myoptions.package_moves = True

    if myoptions.quiet in true_y:
        myoptions.quiet = True
    else:
        myoptions.quiet = None

    if myoptions.quiet_build in true_y:
        myoptions.quiet_build = "y"

    if myoptions.quiet_fail in true_y:
        myoptions.quiet_fail = "y"

    if myoptions.read_news in true_y:
        myoptions.read_news = True
    else:
        myoptions.read_news = None

    if myoptions.rebuild_if_new_slot in true_y:
        myoptions.rebuild_if_new_slot = "y"

    if myoptions.rebuild_if_new_ver in true_y:
        myoptions.rebuild_if_new_ver = True
    else:
        myoptions.rebuild_if_new_ver = None

    if myoptions.rebuild_if_new_rev in true_y:
        myoptions.rebuild_if_new_rev = True
        myoptions.rebuild_if_new_ver = None
    else:
        myoptions.rebuild_if_new_rev = None

    if myoptions.rebuild_if_unbuilt in true_y:
        myoptions.rebuild_if_unbuilt = True
        myoptions.rebuild_if_new_rev = None
        myoptions.rebuild_if_new_ver = None
    else:
        myoptions.rebuild_if_unbuilt = None

    if myoptions.rebuilt_binaries in true_y:
        myoptions.rebuilt_binaries = True

    if myoptions.root_deps in true_y:
        myoptions.root_deps = True

    if myoptions.select in true_y:
        myoptions.select = True
        myoptions.oneshot = False
    elif myoptions.select == "n":
        myoptions.oneshot = True

    if myoptions.selective in true_y:
        myoptions.selective = True

    if myoptions.backtrack is not None:
        try:
            backtrack = int(myoptions.backtrack)
        except (OverflowError, ValueError):
            backtrack = -1

        if backtrack < 0:
            backtrack = None
            if not silent:
                parser.error(
                    f"Invalid --backtrack parameter: '{myoptions.backtrack}'\n"
                )

        myoptions.backtrack = backtrack

    if myoptions.deep is not None:
        deep = None
        if myoptions.deep == "True":
            deep = True
        else:
            try:
                deep = int(myoptions.deep)
            except (OverflowError, ValueError):
                deep = -1

        if deep is not True and deep < 0:
            deep = None
            if not silent:
                parser.error(f"Invalid --deep parameter: '{myoptions.deep}'\n")

        myoptions.deep = deep

    if myoptions.jobs is not None:
        jobs = None
        if myoptions.jobs == "True":
            jobs = True
        else:
            try:
                jobs = int(myoptions.jobs)
            except ValueError:
                jobs = -1

        if jobs is not True and jobs < 1:
            jobs = None
            if not silent:
                parser.error(f"Invalid --jobs parameter: '{myoptions.jobs}'\n")

        myoptions.jobs = jobs

    if myoptions.load_average == "True":
        myoptions.load_average = None

    if myoptions.load_average:
        try:
            load_average = float(myoptions.load_average)
        except ValueError:
            load_average = 0.0

        if load_average <= 0.0:
            load_average = None
            if not silent:
                parser.error(
                    f"Invalid --load-average parameter: '{myoptions.load_average}'\n"
                )

        myoptions.load_average = load_average

    if myoptions.rebuilt_binaries_timestamp:
        try:
            rebuilt_binaries_timestamp = int(myoptions.rebuilt_binaries_timestamp)
        except ValueError:
            rebuilt_binaries_timestamp = -1

        if rebuilt_binaries_timestamp < 0:
            rebuilt_binaries_timestamp = 0
            if not silent:
                parser.error(
                    f"Invalid --rebuilt-binaries-timestamp parameter: '{myoptions.rebuilt_binaries_timestamp}'\n"
                )

        myoptions.rebuilt_binaries_timestamp = rebuilt_binaries_timestamp

    if myoptions.search_similarity:
        try:
            search_similarity = float(myoptions.search_similarity)
        except ValueError:
            parser.error(
                f"Invalid --search-similarity parameter (not a number): '{myoptions.search_similarity}'\n"
            )

        if search_similarity < 0 or search_similarity > 100:
            parser.error(
                f"Invalid --search-similarity parameter (not between 0 and 100): '{myoptions.search_similarity}'\n"
            )

        myoptions.search_similarity = search_similarity

    if myoptions.use_ebuild_visibility in true_y:
        myoptions.use_ebuild_visibility = True
    else:
        # None or "n"
        pass

    if myoptions.usepkg in true_y:
        myoptions.usepkg = True
    else:
        myoptions.usepkg = None

    if myoptions.usepkgonly in true_y:
        myoptions.usepkgonly = True
    else:
        myoptions.usepkgonly = None

    if myoptions.usepkg_exclude_live in true_y:
        myoptions.usepkg_exclude_live = True
    else:
        myoptions.usepkg_exclude_live = None

    if myoptions.verbose in true_y:
        myoptions.verbose = True
    else:
        myoptions.verbose = None

    if myoptions.with_test_deps in true_y:
        myoptions.with_test_deps = True
    else:
        myoptions.with_test_deps = None

    for myopt in options:
        v = getattr(myoptions, myopt.lstrip("--").replace("-", "_"))
        if v:
            myopts[myopt] = True

    for myopt in argument_options:
        v = getattr(myoptions, myopt.lstrip("--").replace("-", "_"), None)
        if v is not None:
            myopts[myopt] = v

    if myoptions.searchdesc:
        myoptions.search = True

    for action_opt in actions:
        v = getattr(myoptions, action_opt.replace("-", "_"))
        if v:
            if myaction:
                multiple_actions(myaction, action_opt)
                sys.exit(1)
            myaction = action_opt

    if myaction is None and myoptions.deselect is True:
        myaction = "deselect"

    return myaction, myopts, myoptions.positional_args


def profile_check(trees, myaction):
    if myaction in ("help", "info", "search", "sync", "version"):
        return os.EX_OK
    for root_trees in trees.values():
        if (
            root_trees["root_config"].settings.profiles
            and "ARCH" in root_trees["root_config"].settings
        ):
            continue
        # generate some profile related warning messages
        validate_ebuild_environment(trees)
        msg = (
            "Your current profile is invalid. If you have just changed "
            "your profile configuration, you should revert back to the "
            "previous configuration. Allowed actions are limited to "
            "--help, --info, --search, --sync, and --version."
        )
        writemsg_level(
            "".join(f"!!! {l}\n" for l in textwrap.wrap(msg, 70)),
            level=logging.ERROR,
            noiselevel=-1,
        )
        return 1
    return os.EX_OK


def emerge_main(args: Optional[list[str]] = None):
    """
    Entry point of emerge

    Processes command line arguments (default: sys.argv[1:]) and decides
    what the current run of emerge should by creating `emerge_config`
    """
    if args is None:
        args = sys.argv[1:]

    args = portage._decode_argv(args)

    # Use system locale.
    try:
        locale.setlocale(locale.LC_ALL, "")
    except locale.Error as e:
        writemsg_level(f"setlocale: {e}\n", level=logging.WARN)

    # Disable color until we're sure that it should be enabled (after
    # EMERGE_DEFAULT_OPTS has been parsed).
    portage.output.havecolor = 0

    # This first pass is just for options that need to be known as early as
    # possible, such as --config-root.  They will be parsed again later,
    # together with EMERGE_DEFAULT_OPTS (which may vary depending on the
    # the value of --config-root).
    myaction, myopts, myfiles = parse_opts(args, silent=True)
    if "--debug" in myopts:
        os.environ["PORTAGE_DEBUG"] = "1"
        portage.util.initialize_logger(logging.DEBUG)
    else:
        portage.util.initialize_logger()

    if "--config-root" in myopts:
        os.environ["PORTAGE_CONFIGROOT"] = myopts["--config-root"]
    if "--sysroot" in myopts:
        os.environ["SYSROOT"] = myopts["--sysroot"]
    if "--root" in myopts:
        os.environ["ROOT"] = myopts["--root"]
    if "--prefix" in myopts:
        os.environ["EPREFIX"] = myopts["--prefix"]
    if "--accept-properties" in myopts:
        os.environ["ACCEPT_PROPERTIES"] = myopts["--accept-properties"]
    if "--accept-restrict" in myopts:
        os.environ["ACCEPT_RESTRICT"] = myopts["--accept-restrict"]

    # optimize --help (no need to load config / EMERGE_DEFAULT_OPTS)
    if myaction == "help":
        emerge_help()
        return os.EX_OK
    if myaction == "moo":
        print(COWSAY_MOO.format(platform.system()))
        return os.EX_OK
    if myaction == "sync":
        # need to set this to True now in order for the repository config
        # loading to allow new repos with non-existent directories
        portage._sync_mode = True

    # Verify that /dev/null exists and is a device file as a cheap early
    # filter for obviously broken /dev/s.
    try:
        if os.stat(os.devnull).st_rdev == 0:
            writemsg_level(
                "Failed to validate a sane '/dev'.\n"
                "'/dev/null' is not a device file.\n",
                level=logging.ERROR,
                noiselevel=-1,
            )
            return 1
    except OSError:
        writemsg_level(
            "Failed to validate a sane '/dev'.\n" "'/dev/null' does not exist.\n",
            level=logging.ERROR,
            noiselevel=-1,
        )
        return 1

    # Verify that BASH process substitution works as another cheap early
    # filter. Process substitution uses '/dev/fd'.
    with open(os.devnull, "r+b") as dev_null:
        fd_pipes = {
            0: dev_null.fileno(),
            1: dev_null.fileno(),
            2: dev_null.fileno(),
        }
        exit_code = portage.process.spawn_bash(
            "[[ $(< <(echo foo) ) == foo ]]", fd_pipes=fd_pipes
        )
        if exit_code != 0:
            writemsg_level(
                "Failed to validate a sane '/dev'.\n"
                "bash process substitution doesn't work; this may be an "
                "indication of a broken '/dev/fd'.\n",
                level=logging.ERROR,
                noiselevel=-1,
            )
            return 1

    # Portage needs to ensure a sane umask for the files it creates.
    os.umask(0o22)
    emerge_config = load_emerge_config(action=myaction, args=myfiles, opts=myopts)

    # Make locale variables from configuration files (make.defaults, make.conf) affect locale of emerge process.
    for locale_var_name in (
        "LANGUAGE",
        "LC_ALL",
        "LC_ADDRESS",
        "LC_COLLATE",
        "LC_CTYPE",
        "LC_IDENTIFICATION",
        "LC_MEASUREMENT",
        "LC_MESSAGES",
        "LC_MONETARY",
        "LC_NAME",
        "LC_NUMERIC",
        "LC_PAPER",
        "LC_TELEPHONE",
        "LC_TIME",
        "LANG",
    ):
        locale_var_value = emerge_config.running_config.settings.get(locale_var_name)
        if locale_var_value is not None:
            os.environ.setdefault(locale_var_name, locale_var_value)
    try:
        locale.setlocale(locale.LC_ALL, "")
    except locale.Error as e:
        writemsg_level(f"setlocale: {e}\n", level=logging.WARN)

    tmpcmdline = []
    if "--ignore-default-opts" not in myopts:
        tmpcmdline.extend(
            shlex.split(
                emerge_config.target_config.settings.get("EMERGE_DEFAULT_OPTS", "")
            )
        )
    tmpcmdline.extend(args)
    emerge_config.action, emerge_config.opts, emerge_config.args = parse_opts(
        tmpcmdline
    )

    try:
        return run_action(emerge_config)
    finally:
        # Call destructors for our portdbapi instances.
        for x in emerge_config.trees.values():
            if "porttree" in x.lazy_items:
                continue
            x["porttree"].dbapi.close_caches()

        # This gets out final log message in before we quit.
        xterm_titles = "notitles" not in emerge_config.target_config.settings.features
        if "--pretend" not in emerge_config.opts:
            emergelog(xterm_titles, " *** terminating.")

        if xterm_titles:
            xtermTitleReset()
