Skip to content
Back to top

# Source code for xonsh.main

    """The main xonsh script."""

    import argparse
    import atexit
    import builtins
    import contextlib
    import enum
    import os
    import signal
    import sys
    import traceback

    import xonsh.procs.pipelines as xpp
    from xonsh import __version__
    from xonsh.built_ins import XSH
    from xonsh.codecache import run_code_with_cache, run_script_with_cache
    from xonsh.environ import (
        get_home_xonshrc_path,
        load_origin_env_from_file,
        make_args_env,
        os_environ,
        xonshrc_context,
    )
    from xonsh.events import events
    from xonsh.execer import Execer
    from xonsh.imphooks import install_import_hooks
    from xonsh.lib.lazyasd import lazyobject
    from xonsh.lib.lazyimps import pyghooks, pygments
    from xonsh.lib.pretty import pretty
    from xonsh.platform import HAS_PYGMENTS, ON_WINDOWS
    from xonsh.procs.jobs import ignore_sigtstp
    from xonsh.shell import Shell
    from xonsh.timings import setup_timings
    from xonsh.tools import (
        display_error_message,
        on_main_thread,
        print_color,
        print_exception,
        to_bool_or_int,
        unquote,
    )
    from xonsh.xonfig import print_welcome_screen
    from xonsh.xontribs import auto_load_xontribs_from_entrypoints, xontribs_load

    events.transmogrify("on_post_init", "LoadEvent")
    events.doc(
        "on_post_init",
        """
    on_post_init() -> None

    Fired after all initialization is finished and we're ready to do work.

    NOTE: This is fired before the wizard is automatically started.

    Example:

    .. code-block:: xonsh

        @events.on_post_init
        def _event_greet(**kw):
            from xonsh.built_ins import XSH
            name = XSH.env.get("USER", "user")
            print(f"Welcome back, {name}!")
    """,
    )

    events.transmogrify("on_exit", "LoadEvent")
    events.doc(
        "on_exit",
        """
    on_exit(exit_code : int) -> None

    Fired after all commands have been executed, before tear-down occurs.

    NOTE: All the caveats of the ``atexit`` module also apply to this event.

    Parameters:

    * ``exit_code``: The shell's exit code (``0`` for success).

    Example:

    .. code-block:: xonsh

        @events.on_exit
        def _event_save_session_log(exit_code, **kw):
            import datetime
            with open("/tmp/xonsh_sessions.log", "a") as f:
                f.write(f"{datetime.datetime.now()}: exited with code {exit_code}\\n")
    """,
    )

    events.transmogrify("on_pre_cmdloop", "LoadEvent")
    events.doc(
        "on_pre_cmdloop",
        """
    on_pre_cmdloop() -> None

    Fired just before the command loop is started, if it is.

    Example:

    .. code-block:: xonsh

        @events.on_pre_cmdloop
        def _event_show_tip(**kw):
            import random
            tips = ["Use Tab for completion", "Try 'xonfig' to configure xonsh"]
            print("Tip:", random.choice(tips))
    """,
    )

    events.transmogrify("on_post_cmdloop", "LoadEvent")
    events.doc(
        "on_post_cmdloop",
        """
    on_post_cmdloop() -> None

    Fired just after the command loop finishes, if it is.

    NOTE: All the caveats of the ``atexit`` module also apply to this event.

    Example:

    .. code-block:: xonsh

        @events.on_post_cmdloop
        def _event_session_summary(**kw):
            print(f"Session ended after {len(@.history)} commands.")
    """,
    )

    events.transmogrify("on_xontribs_loaded", "LoadEvent")
    events.doc(
        "on_xontribs_loaded",
        """
    on_xontribs_loaded() -> None

    Fired after external xontribs with ``entrypoints defined`` are loaded.

    Example:

    .. code-block:: xonsh

        @events.on_xontribs_loaded
        def _event_check_xontribs(**kw):
            from xonsh.xontribs import xontribs_loaded
            if "coreutils" not in xontribs_loaded():
                print("Hint: 'xontrib load coreutils' adds cross-platform cp, mv, rm, ...")
    """,
    )

    events.transmogrify("on_pre_rc", "LoadEvent")
    events.doc(
        "on_pre_rc",
        """
    on_pre_rc() -> None

    Fired just before rc files are loaded, if they are.

    Example:

    .. code-block:: xonsh

        @events.on_pre_rc
        def _event_announce_rc(**kw):
            print("Loading rc files...")
    """,
    )

    events.transmogrify("on_post_rc", "LoadEvent")
    events.doc(
        "on_post_rc",
        """
    on_post_rc() -> None

    Fired just after rc files are loaded, if they are.

    Example:

    .. code-block:: xonsh

        @events.on_post_rc
        def _event_rc_loaded(**kw):
            print("RC files loaded successfully.")
    """,
    )

    def get_setproctitle():
        """Proxy function for loading process title"""
        try:
            from setproctitle import setproctitle as spt
        except ImportError:
            return
        return spt

    def get_current_xonsh():
        """Return the path used to invoke the current xonsh session.

        This is a thin wrapper around ``sys.argv[0]`` so that callers can share
        a single source of truth for "how was this xonsh started".

        Examples
        --------
        - Entry point: ``/usr/bin/xonsh``.
        - Source via ``python -m xonsh``: ``/path/to/xonsh/__main__.py``
        """
        return sys.argv[0]

    def path_argument(s):
        """Return a path only if the path is actually legal (file or directory)

        This is very similar to argparse.FileType, except that it doesn't return
        an open file handle, but rather simply validates the path."""

        s = os.path.abspath(os.path.expanduser(s))
        if not os.path.exists(s):
            msg = f"{s!r} must be a valid path to a file or directory"
            raise argparse.ArgumentTypeError(msg)
        return s

    #: Module-level state written by :func:`_acquire_controlling_terminal` and
    #: consumed by :func:`_release_controlling_terminal`. Kept as a dict so that
    #: the atexit restorer is a thin closure without extra globals.
    _fg_tty_state: dict = {"acquired": False, "tty_fd": -1, "old_fg": -1}

    #: Idempotency flag for :func:`_setup_controlling_terminal`. Flipped to
    #: ``True`` once the TTY signal handlers have been installed in a given
    #: process — a second call becomes a cheap no-op. Exposed on the module
    #: so tests can reset it between runs.
    _tty_setup_done: bool = False

    #: Delivery counter for :func:`_handle_sig_ttin_ttou`. A Python list is
    #: used instead of a plain int so that the handler (a closure / nested
    #: call site) can mutate it in place without a ``global`` declaration.
    #: Exposed so tests can reset it.
    _ttin_ttou_counter: list = [0]

    #: Threshold at which :func:`_handle_sig_ttin_ttou` escalates the
    #: ``SIGTTIN`` / ``SIGTTOU`` disposition from the Python no-op handler
    #: to ``SIG_IGN`` as a livelock guard. See the handler's docstring for
    #: the rationale. Chosen conservatively high — normal operation fires
    #: the handler zero times, and legitimate transient bursts (e.g. a
    #: misbehaving subprocess briefly stealing foreground during a
    #: pipeline) should recover well below this bound.
    _TTIN_TTOU_LIVELOCK_THRESHOLD: int = 1000

    def _handle_sig_ttin_ttou(n, f):
        """Python no-op handler for ``SIGTTIN`` / ``SIGTTOU`` with a
        built-in livelock guard.

        Installed by :func:`_setup_controlling_terminal`'s success path.
        Under normal operation xonsh is the foreground process group of
        its controlling terminal and this handler never fires — the
        kernel does not deliver ``SIGTTIN`` / ``SIGTTOU`` to a foreground
        process. It exists as a safety net for pathological cases where
        xonsh temporarily loses foreground ownership (unexpected
        ``tcsetpgrp`` from a subprocess, a PID-namespace race, a buggy
        xontrib, …) and then tries to touch the TTY.

        Why a plain ``pass`` is a trap
        ------------------------------
        There are two independent paths that can turn a ``pass`` no-op
        handler into a 99%-CPU livelock:

        1. **PEP 475 (``os.read`` retry) — triggered by SIGTTIN.**
           Since Python 3.5, ``os.read`` is one of the syscalls that
           Python automatically retries on ``EINTR`` when the signal
           handler does not raise — see
           https://peps.python.org/pep-0475/ for the full list. If xonsh
           is a background process of the TTY and calls ``os.read`` on
           the TTY fd (directly, or indirectly via ``prompt_toolkit``'s
           asyncio event loop), the kernel returns ``EINTR`` and sends
           ``SIGTTIN``. The Python handler runs, does ``pass``, returns.
           Python retries ``os.read``. The kernel sends ``SIGTTIN``
           again. Loop — at 99% CPU until the process is killed.
           ``termios.*`` is **not** in PEP 475's retry list, so
           ``tcsetattr`` itself does not trigger this path — but
           ``os.read`` does, and prompt_toolkit reads stdin on every
           prompt.

        2. **xonsh PR #6192 application-level retry — triggered by
           SIGTTOU.** The existing outer retry loop in
           ``xonsh/shells/ptk_shell/__init__.py`` catches any exception
           with ``args[0] == 4`` (``EINTR``) and calls
           ``self.prompter.prompt(**prompt_args)`` again. If
           ``tcsetattr`` inside ``raw_mode`` is what raised (because
           xonsh is background, so the kernel generates ``SIGTTOU`` and
           fails the syscall with ``EINTR``), the retry re-enters
           ``raw_mode`` and re-calls ``tcsetattr``, the kernel re-sends
           ``SIGTTOU``, the handler passes, the retry fires again —
           another 99%-CPU loop.

        The fix
        -------
        Count deliveries. On the first few thousand we do nothing — that
        covers every legitimate transient scenario. Above the threshold
        we **replace the Python handler with ``SIG_IGN``**. POSIX says
        that if ``SIGTTOU`` / ``SIGTTIN`` are ignored, the underlying
        operation "shall be allowed to perform" — so the next
        ``tcsetattr`` / ``read`` succeeds instead of re-entering the
        loop, and the caller makes progress.

        ``SIG_IGN`` is inherited across ``exec``, so subprocess children
        launched after escalation see ``SIG_IGN`` for these signals too.
        That is an acceptable trade-off: we only reach this branch if
        xonsh is already in a degraded "lost foreground" state, and the
        alternative is a hard hang.

        References
        ----------
        * PEP 475 (Retry system calls failing with EINTR):
          https://peps.python.org/pep-0475/
        * ``tcsetattr(3)`` — "the operation shall be allowed to perform"
          when SIGTTOU is ignored:
          https://man7.org/linux/man-pages/man3/tcsetattr.3.html
        * ``read(2)`` — EINTR semantics and PEP 475 coverage:
          https://man7.org/linux/man-pages/man2/read.2.html
        * xonsh PR #6192 — the application-level retry loop that
          provides the SIGTTOU livelock path.
        """
        _ttin_ttou_counter[0] += 1
        if _ttin_ttou_counter[0] > _TTIN_TTOU_LIVELOCK_THRESHOLD:
            # Escalate: let the kernel drop the signals outright. Future
            # deliveries will not reach Python at all, and interrupted
            # syscalls will complete on the next retry (POSIX: "the
            # operation shall be allowed to perform" when ignored).
            signal.signal(signal.SIGTTIN, signal.SIG_IGN)
            signal.signal(signal.SIGTTOU, signal.SIG_IGN)

    def _acquire_controlling_terminal():
        """Put xonsh into the foreground process group of its controlling TTY.

        Background
        ----------
        On POSIX systems every terminal has a single *foreground process group*.
        Only processes belonging to that group may read from or perform control
        operations on the terminal without being suspended by ``SIGTTIN`` /
        ``SIGTTOU``. Interactive shells (bash, zsh, …) perform a small startup
        handshake to make sure they are that foreground group: they put
        themselves into their own process group and call ``tcsetpgrp`` on the
        TTY. xonsh historically did not do this — it relied on the launching
        shell to have arranged things correctly. That assumption breaks in a
        number of environments:

        * Flatpak / Bubblewrap and similar sandboxes that spawn children
          without adjusting the TTY's foreground group.
        * Build systems, CI runners, IDE integrated terminals and service
          managers (e.g. ``systemd --user``) that don't wire xonsh up as the
          foreground process of the PTY they attach.
        * Nested containers that reuse the outer PTY.

        In those cases xonsh is technically a background process. Every TTY
        touch — ``tcgetattr`` during prompt_toolkit init, ``tcsetpgrp`` when
        launching a pipeline, plain writes when stdout is line-buffered —
        raises ``SIGTTIN`` or ``SIGTTOU``. The signals are delivered to
        Python's asyncio wakeup pipe faster than it can be drained, overflow
        it, and crash startup with ``BlockingIOError: Resource temporarily
        unavailable``. Other signals interrupt ``termios`` calls mid-flight
        and surface as ``termios.error: Interrupted system call``.

        The fix
        -------
        Perform the same handshake bash does, very early in xonsh's lifetime:

        1. Find a TTY file descriptor (stderr, same choice xonsh already uses
           for :func:`xonsh.procs.jobs.give_terminal_to`).
        2. Compare its current foreground group to our process group. If we
           are already foreground, record that and exit fast.
        3. Block ``SIGTTOU``, ``SIGTTIN``, ``SIGTSTP`` and ``SIGCHLD`` in the
           calling thread so that the handshake cannot suspend us or be
           reentered by a child's death.
        4. Call ``setpgid(0, 0)`` to make us a process group of our own.
        5. Call ``tcsetpgrp(tty_fd, getpgrp())`` to install that group as
           the foreground group of the TTY.
        6. Remember the previous foreground group so :func:`_release_controlling_terminal`
           (registered via ``atexit``) can hand it back on shutdown.

        Every failure mode returns ``False`` cleanly — this is a best-effort
        upgrade, never a hard requirement. Windows has no concept of
        controlling terminals in the POSIX sense; the function immediately
        returns ``False`` there.

        Environment
        -----------
        Set ``XONSH_NO_FG_TAKEOVER=1`` in the parent environment to disable
        the handshake entirely. This is an escape hatch for rare cases where
        the takeover conflicts with a parent process that expects xonsh to
        stay in its original process group.

        Returns
        -------
        bool
            ``True`` if xonsh is now (or already was) the foreground process
            group of its controlling terminal. ``False`` on any failure or
            when the handshake is not applicable (non-TTY, Windows, session
            leader, disabled by env var, …).

        References
        ----------
        POSIX.1-2017 specifications (https://pubs.opengroup.org/onlinepubs/9699919799/):

        * ``setpgid(2)``: https://man7.org/linux/man-pages/man2/setpgid.2.html
          — session leader cannot change its own pgid (EPERM); ``setpgid(0, 0)``
          creates a new pgrp with ``pgid == pid``.
        * ``tcsetpgrp(3)``: https://man7.org/linux/man-pages/man3/tcsetpgrp.3.html
          — a background-group caller receives ``SIGTTOU`` unless it is blocked
          or ignored; fails with ESRCH if the target pgid does not exist.
        * ``tcgetpgrp(3)``: https://man7.org/linux/man-pages/man3/tcgetpgrp.3.html
          — reads the controlling terminal's current foreground pgid.
        * ``tcsetattr(3)``: https://man7.org/linux/man-pages/man3/tcsetattr.3.html
          — returns EIO when called on an orphaned pgrp's controlling terminal
          (Linux) rather than sending SIGTTOU, which is the Flatpak crash path.
        * ``pthread_sigmask(3)``: https://man7.org/linux/man-pages/man3/pthread_sigmask.3.html
          — per-thread signal mask; used here to block ``SIGTTOU`` across the
          ``tcsetpgrp`` call without affecting the rest of the process.
        * ``pid_namespaces(7)``: https://man7.org/linux/man-pages/man7/pid_namespaces.7.html
          — specifies that ``getpgid``/``getsid`` return 0 for ids not
          representable inside the namespace; this is the Linux kernel
          behaviour that the ``my_pgid > 0`` guard in the fast-path exists for.
        * ``execve(2)``: https://man7.org/linux/man-pages/man2/execve.2.html
          — specifies signal-disposition inheritance across exec (``SIG_IGN``
          persists, custom handlers reset to ``SIG_DFL``), which informs the
          choice between Python no-op handler and ``SIG_IGN`` in
          :func:`_setup_controlling_terminal`.
        * ``credentials(7)``: https://man7.org/linux/man-pages/man7/credentials.7.html
          — general background on sessions and process groups.

        Non-POSIX / Linux-specific:

        * ``signal(7)``: https://man7.org/linux/man-pages/man7/signal.7.html
          — Linux signal semantics table, including the inheritance rules
          across ``fork`` and ``exec``.

        Historical prior art:

        * bash ``jobs.c``
          (https://cgit.git.savannah.gnu.org/cgit/bash.git/tree/jobs.c) —
          source of the ``setpgid`` + ``tcsetpgrp`` startup pattern, verified
          for ``start_pipeline``, ``make_child``, ``stop_pipeline``,
          ``default_tty_job_signals`` and ``ignore_tty_job_signals``.
        * Original xonsh-flatpak wrapper that documented the failure modes
          (``BlockingIOError``, ``termios.error``):
          https://github.com/anki-code/xonsh-flatpak/blob/main/xonsh-wrapper.py
        """
        if ON_WINDOWS:
            return False
        if os.environ.get("XONSH_NO_FG_TAKEOVER"):
            return False
        # pthread_sigmask is POSIX-only and may be absent on exotic builds.
        # See pthread_sigmask(3):
        #   https://man7.org/linux/man-pages/man3/pthread_sigmask.3.html
        if not hasattr(signal, "pthread_sigmask"):
            return False
        # Use stderr as the TTY handle. xonsh already uses FD 2 for
        # give_terminal_to in pipeline management (xonsh/procs/jobs.py), so
        # this keeps the choice consistent across subsystems.
        try:
            tty_fd = sys.stderr.fileno()
        except (AttributeError, OSError, ValueError):
            return False
        try:
            if not os.isatty(tty_fd):
                return False
        except OSError:
            return False
        # A session leader cannot change its own process group id, so the
        # setpgid(0, 0) call below would fail with EPERM. Detect this up
        # front and skip cleanly — if we are session leader we almost
        # certainly already own the TTY anyway. See setpgid(2):
        #   https://man7.org/linux/man-pages/man2/setpgid.2.html
        try:
            if os.getsid(0) == os.getpid():
                return False
        except OSError:
            return False
        # Block the signals that can disrupt the handshake. SIGTTOU is the
        # critical one — without this mask, the tcsetpgrp() call below
        # would send SIGTTOU to our own process group (we are in a
        # background group at this point) and suspend us. SIGTTIN is
        # blocked for symmetry; SIGTSTP so that a stray Ctrl+Z cannot
        # desynchronise state; SIGCHLD so a child reaping does not
        # interrupt us mid-handshake. The block is thread-local — it
        # does not affect signal disposition for the rest of the process.
        # See tcsetpgrp(3) for the SIGTTOU-when-bg semantics:
        #   https://man7.org/linux/man-pages/man3/tcsetpgrp.3.html
        block = {signal.SIGTTOU, signal.SIGTTIN, signal.SIGTSTP, signal.SIGCHLD}
        try:
            old_mask = signal.pthread_sigmask(signal.SIG_BLOCK, block)
        except (AttributeError, OSError):
            return False
        try:
            try:
                current_fg = os.tcgetpgrp(tty_fd)
            except OSError:
                return False
            my_pgid = os.getpgrp()
            if current_fg == my_pgid and my_pgid > 0:
                # We are already the foreground group. Nothing to acquire
                # and nothing to release on shutdown — return success but
                # do *not* mark state as acquired, so the atexit restorer
                # stays a no-op and we don't risk racing with the parent
                # shell's own tcsetpgrp() on exit.
                #
                # The ``my_pgid > 0`` guard handles PID namespaces
                # (Flatpak, Bubblewrap, Podman rootless, systemd-nspawn,
                # kubectl exec, Chrome/Firefox sandboxes, …). From inside
                # a PID namespace, if our process group id or the TTY's
                # foreground group id refers to a process *outside* the
                # namespace (typically because bwrap/flatpak inherited the
                # pgid from the host before creating the namespace), the
                # kernel returns 0 from both ``getpgrp`` and ``tcgetpgrp``
                # — 0 is the "unrepresentable pgid" sentinel specified in
                # pid_namespaces(7):
                #   https://man7.org/linux/man-pages/man7/pid_namespaces.7.html
                # Without the guard, the fast path would see 0 == 0 and
                # declare success, leaving our pgid as the unrepresentable
                # 0. Subsequent ``tcsetpgrp(fd, 0)`` calls from
                # :func:`xonsh.procs.jobs.give_terminal_to` would fail with
                # ESRCH (tcsetpgrp(3):
                #   https://man7.org/linux/man-pages/man3/tcsetpgrp.3.html),
                # the TTY's foreground group would end up orphaned on the
                # previous pipeline's process group, and the next
                # ``tcsetattr`` from prompt_toolkit would raise
                # ``termios.error: (5, 'Input/output error')`` — the
                # Flatpak sandbox failure mode reported downstream
                # (tcsetattr(3):
                #   https://man7.org/linux/man-pages/man3/tcsetattr.3.html).
                # Falling through to the ``setpgid(0, 0)`` call below
                # creates a new process group visible inside the namespace
                # (``pgid == pid``), so the subsequent ``tcsetpgrp`` has a
                # valid, representable target (setpgid(2):
                #   https://man7.org/linux/man-pages/man2/setpgid.2.html).
                return True
            # Become our own process group leader. If we are already a
            # leader this is a no-op; if the kernel refuses (EPERM), we
            # bail out. After this succeeds our pgid == pid. See
            # setpgid(2):
            #   https://man7.org/linux/man-pages/man2/setpgid.2.html
            try:
                os.setpgid(0, 0)
            except OSError:
                return False
            my_pgid = os.getpgrp()
            # Install our pgid as the TTY's foreground group. SIGTTOU is
            # blocked, so this cannot suspend us; it may still fail with
            # EPERM if the TTY belongs to a different session, or ENOTTY
            # if the fd lost its TTY-ness between isatty() and now — both
            # degrade to a clean False return.
            try:
                os.tcsetpgrp(tty_fd, my_pgid)
            except OSError:
                return False
            _fg_tty_state["acquired"] = True
            _fg_tty_state["tty_fd"] = tty_fd
            _fg_tty_state["old_fg"] = current_fg
            return True
        finally:
            # Always restore the signal mask, even on error paths. We only
            # changed the mask for this thread, so this call is cheap and
            # cannot fail in any practical scenario — but guard it anyway.
            try:
                signal.pthread_sigmask(signal.SIG_SETMASK, old_mask)
            except (AttributeError, OSError):
                pass

    def _release_controlling_terminal():
        """Give the controlling TTY's foreground group back to its previous
        owner on shutdown.

        Registered via :mod:`atexit` when :func:`_acquire_controlling_terminal`
        actually took over the TTY. A no-op in every other case: if xonsh was
        already foreground, if the handshake failed, or if there is no TTY.

        Restoring the previous foreground group matters because the parent
        shell (bash, zsh, another xonsh, …) usually calls ``wait`` on us and
        then expects to read from the terminal. If xonsh leaves its own pgid
        installed as foreground, the parent will immediately receive
        ``SIGTTIN`` on the next read. Robust shells recover from this, but it
        is much cleaner to hand the TTY back explicitly.

        References
        ----------
        * ``atexit(3)``: https://docs.python.org/3/library/atexit.html
        * ``tcsetpgrp(3)``: https://man7.org/linux/man-pages/man3/tcsetpgrp.3.html
        * ``pthread_sigmask(3)``: https://man7.org/linux/man-pages/man3/pthread_sigmask.3.html
        """
        if not _fg_tty_state.get("acquired"):
            return
        tty_fd = _fg_tty_state.get("tty_fd", -1)
        old_fg = _fg_tty_state.get("old_fg", -1)
        if tty_fd < 0 or old_fg < 0:
            return
        if not hasattr(signal, "pthread_sigmask"):
            return
        try:
            old_mask = signal.pthread_sigmask(
                signal.SIG_BLOCK, {signal.SIGTTOU, signal.SIGTTIN}
            )
        except (AttributeError, OSError):
            return
        try:
            try:
                os.tcsetpgrp(tty_fd, old_fg)
            except OSError:
                # Parent may already have reclaimed the TTY, or the fd may
                # have been closed by shutdown — either way nothing
                # actionable remains. Swallow silently: this runs inside
                # atexit and raising here would just make shutdown noisy.
                pass
        finally:
            try:
                signal.pthread_sigmask(signal.SIG_SETMASK, old_mask)
            except (AttributeError, OSError):
                pass
            # Clear state so a second call (e.g. atexit + explicit shutdown)
            # is a no-op.
            _fg_tty_state["acquired"] = False
            _fg_tty_state["tty_fd"] = -1
            _fg_tty_state["old_fg"] = -1

    def _setup_ctrl_break():
        """Windows: make Ctrl+Break interrupt the foreground job, not kill xonsh.

        The Windows console delivers ``CTRL_BREAK_EVENT`` to *every* process
        attached to the console — xonsh and the subprocess it is waiting on
        alike. Python maps that event to ``SIGBREAK``, whose default disposition
        terminates the process, so with no handler installed xonsh itself is torn
        down the moment the user presses Ctrl+Break while a child is running
        (issue #4852).

        Install :func:`signal.default_int_handler` for ``SIGBREAK`` so the event
        surfaces as a catchable ``KeyboardInterrupt``, exactly like ``SIGINT``
        (Ctrl+C) already does. The foreground child still receives the console
        event directly and exits on its own; xonsh's wait then unblocks, the
        ``KeyboardInterrupt`` is handled by the shell loop, and we return to the
        prompt instead of dying. Threaded/captured subprocesses additionally
        install their own ``SIGBREAK`` handler while running (see
        ``xonsh/procs/posix.py`` and ``xonsh/procs/proxies.py``).

        ``signal.signal`` only works on the main thread, so skip otherwise
        (embedded hosts that call :func:`setup` off-main), matching the guard in
        :func:`xonsh.built_ins.resetting_signal_handle`.
        """
        if not on_main_thread():
            return
        if not hasattr(signal, "SIGBREAK"):
            # SIGBREAK is a Windows-only signal; nothing to do elsewhere.
            return
        signal.signal(signal.SIGBREAK, signal.default_int_handler)

    def _setup_controlling_terminal():
        """Run the TTY startup handshake and install matching signal handlers.

        This is the *orchestration* layer around
        :func:`_acquire_controlling_terminal`: it installs the baseline
        signal policy, optionally calls the handshake, and registers the
        ``atexit`` restorer when appropriate. It is idempotent — a second
        call in the same process is a cheap no-op.

        It is called from the top of :func:`main` so that the handshake
        happens *before* :func:`premain`. ``premain`` loads xontribs and
        runs user ``xonshrc`` files, and rc files are arbitrary xonsh code
        — they routinely contain ``$(...)`` / ``!(...)`` captures and can
        invoke interactive programs like ``fzf`` that will themselves want
        to take over the TTY via ``tcsetattr``. When xonsh is foreground
        *before* rc runs, every downstream TTY operation simply works; when
        it is not, and the handshake cannot fix that (sandboxed nested
        containers, cross-session TTYs, …), the ``SIG_IGN`` fallback
        installed here prevents the asyncio wakeup pipe from overflowing
        during rc execution. It is also called from :func:`main_xonsh` as a
        backup for callers that enter the shell loop without going through
        :func:`main` (tests, programmatic invocation).

        Signal policy
        -------------
        Step 1: **always** install a Python-level no-op handler for
        ``SIGTTIN`` and ``SIGTTOU``, on any POSIX invocation. This matches
        the historical xonsh behavior (before this function existed,
        ``main_xonsh`` installed the same no-op handler unconditionally)
        and protects script-mode xonsh from being suspended by
        ``SIG_DFL`` when something indirectly touches the TTY. A Python
        handler is preferred over ``SIG_IGN`` here because the latter is
        inherited across ``exec`` and would subtly break job control in
        subprocess children.

        Step 2: if stderr is a real TTY, attempt the foreground handshake.
        If it succeeds, the no-op handlers installed in step 1 already
        cover the ``success`` case and no further action is needed. If
        the handshake fails (typical in sandboxes that cannot be made
        foreground), **replace** the no-op handlers with ``SIG_IGN`` —
        the kernel will then discard ``SIGTTIN`` / ``SIGTTOU`` outright
        and they will never reach Python's asyncio wakeup pipe, which
        would otherwise overflow with ``BlockingIOError`` under a signal
        storm.

        Step 3: register ``atexit`` to restore the previous foreground
        group on shutdown, but only when the handshake *actually*
        transferred foreground ownership (``_fg_tty_state["acquired"]``
        is True). Fast-path success — where we were already foreground —
        leaves state unacquired so the restorer does nothing and we don't
        race with the parent shell's own ``tcsetpgrp`` on exit.

        Non-TTY callers (script mode, piped input, test runners under
        pytest) stop after step 1 and keep the historical Python handlers.

        References
        ----------
        * ``signal(7)``: https://man7.org/linux/man-pages/man7/signal.7.html
          — Linux signal semantics, inheritance across ``fork``/``exec``.
        * ``execve(2)``: https://man7.org/linux/man-pages/man2/execve.2.html
          — specifies that ``SIG_IGN`` dispositions survive ``exec`` while
          custom handlers are reset to ``SIG_DFL``. This asymmetry drives
          the step-1 / step-3 policy split: a Python callable handler in
          the success path so subprocess children see ``SIG_DFL`` and keep
          normal job control; ``SIG_IGN`` in the sandbox-failure path to
          discard signals at the kernel boundary before they reach Python's
          asyncio wakeup pipe.
        * ``tcsetattr(3)``: https://man7.org/linux/man-pages/man3/tcsetattr.3.html
          — the syscall ``prompt_toolkit``'s ``raw_mode`` uses; the one that
          actually crashes under the Flatpak failure mode described above.
        * PEP 475 (Retry system calls failing with EINTR):
          https://peps.python.org/pep-0475/ — confirms that ``termios.*``
          is *not* covered by Python's automatic EINTR retry, motivating
          the existing outer retry loop in ``ptk_shell.singleline``.
        """
        global _tty_setup_done
        if _tty_setup_done:
            return
        if ON_WINDOWS:
            _setup_ctrl_break()
            _tty_setup_done = True
            return
        _tty_setup_done = True

        # Step 1: unconditional Python no-op-with-livelock-guard handler
        # for SIGTTIN/SIGTTOU. The ``pass`` body of the historical inline
        # handler is a trap — a background xonsh touching the TTY would
        # loop forever via PEP 475 retry on ``os.read`` (SIGTTIN path) or
        # via the application-level retry in
        # ``xonsh/shells/ptk_shell/__init__.py`` (SIGTTOU path). The
        # module-level :func:`_handle_sig_ttin_ttou` counts deliveries and
        # escalates to ``SIG_IGN`` after a high threshold, breaking the
        # loop while preserving normal job control for typical use (where
        # the handler never fires at all).
        _ttin_ttou_counter[0] = 0  # reset any previous-run state
        signal.signal(signal.SIGTTIN, _handle_sig_ttin_ttou)
        signal.signal(signal.SIGTTOU, _handle_sig_ttin_ttou)

        # Step 2: only attempt the handshake when stderr is a real TTY.
        # Script mode, piped stdin/stdout, and test runners (pytest
        # captures stderr via a pipe) all bail out here and keep the
        # Python handlers from step 1.
        try:
            if not os.isatty(sys.stderr.fileno()):
                return
        except (AttributeError, OSError, ValueError):
            return

        fg_acquired = _acquire_controlling_terminal()
        if fg_acquired:
            # Step 3: register the shutdown restorer only if the handshake
            # actually changed the foreground group. The fast-path case
            # (we were already foreground) deliberately leaves
            # ``_fg_tty_state`` unacquired so the restorer does nothing
            # and we never race with the parent shell's own tcsetpgrp
            # on exit.
            if _fg_tty_state.get("acquired"):
                atexit.register(_release_controlling_terminal)
            # Python handlers from step 1 remain in place as a safety net.
        else:
            # Sandbox failure path: the kernel *will* keep sending
            # SIGTTIN/SIGTTOU every time xonsh touches the TTY, and a
            # Python-level handler would overflow asyncio's signal wakeup
            # pipe with ``BlockingIOError`` during a signal storm. Replace
            # the step-1 handlers with SIG_IGN so the kernel discards the
            # signals outright and never routes them through Python.
            # SIG_IGN is inherited across ``exec``, but in a sandbox
            # children have the same TTY ownership problem anyway, so
            # this is the right default.
            signal.signal(signal.SIGTTIN, signal.SIG_IGN)
            signal.signal(signal.SIGTTOU, signal.SIG_IGN)

    @lazyobject
    def parser():
        p = argparse.ArgumentParser(
            description="xonsh",
            add_help=False,
            formatter_class=argparse.RawDescriptionHelpFormatter,
            epilog=(
                "subcommands:\n"
                "  format          Format xonsh source files. "
                "Run `xonsh format --help` for options.\n"
                "  check           Check xonsh source files for syntax errors "
                "without running them. Run `xonsh check --help` for options.\n"
                "  lint            Lint xonsh source files for likely mistakes. "
                "Run `xonsh lint --help` for options.\n"
            ),
        )
        p.add_argument(
            "-h",
            "--help",
            dest="help",
            action="store_true",
            default=False,
            help="Show help and exit.",
        )
        p.add_argument(
            "-V",
            "--version",
            action="version",
            help="Show version information and exit.",
            version=f"xonsh/{__version__}",
        )
        p.add_argument(
            "-c",
            help="Run a single command and exit.",
            dest="command",
            required=False,
            default=None,
        )
        p.add_argument(
            "-n",
            "--no-execute",
            help="Parse and compile the code but do not execute it. Exit non-zero on "
            "errors. Use the `xonsh check` subcommand for more options "
            "e.g. to check several files.",
            dest="no_execute",
            action="store_true",
            default=False,
        )
        p.add_argument(
            "-i",
            "--interactive",
            help="Force running in interactive mode.",
            dest="force_interactive",
            action="store_true",
            default=False,
        )
        p.add_argument(
            "-l",
            "--login",
            help="Run as a login shell.",
            dest="login",
            action="store_true",
            default=False,
        )
        p.add_argument(
            "--rc",
            help="The xonshrc files to load, these may be either xonsh "
            "files or directories containing xonsh files",
            dest="rc",
            nargs="+",
            type=path_argument,
            default=None,
        )
        p.add_argument(
            "--no-rc",
            help="Do not load any xonsh RC files. Argument --rc will "
            "be ignored if --no-rc is set.",
            dest="norc",
            action="store_true",
            default=False,
        )
        p.add_argument(
            "--no-env",
            help="Do not inherit parent environment variables.",
            dest="inherit_env",
            action="store_false",
            default=True,
        )
        p.add_argument(
            "--no-script-cache",
            help="Do not cache scripts as they are run.",
            dest="scriptcache",
            action="store_false",
            default=True,
        )
        p.add_argument(
            "--cache-everything",
            help="Use a cache, even for interactive commands.",
            dest="cacheall",
            action="store_true",
            default=False,
        )
        p.add_argument(
            "-D",
            dest="defines",
            help="Define an environment variable, in the form of "
            "-DVAR=VAL or inherit existing variable with -DVAR. May be used many times.",
            metavar="ITEM",
            action="append",
            default=None,
        )
        p.add_argument(
            "-st",
            "--shell-type",
            help="What kind of shell should be used. "
            "Possible options: "
            + ", ".join(Shell.shell_type_aliases.keys())
            + ". Warning! If set this overrides $SHELL_TYPE variable.",
            metavar="SHELL_TYPE",
            dest="shell_type",
            choices=tuple(Shell.shell_type_aliases.keys()),
            default=None,
        )
        p.add_argument(
            "--timings",
            help="Prints timing information before the prompt is shown. "
            "This is useful while tracking down performance issues "
            "and investigating startup times.",
            dest="timings",
            action="store_true",
            default=None,
        )
        p.add_argument(
            "file",
            metavar="script-file",
            help="If present, execute the script in script-file and exit.",
            nargs="?",
            default=None,
        )
        p.add_argument(
            "args",
            metavar="args",
            help="Additional arguments to the script specified by script-file.",
            nargs=argparse.REMAINDER,
            default=[],
        )
        p.add_argument(
            "--save-origin-env",
            help="Save origin environment variables before running xonsh. Use --load-origin-env to run xonsh with saved origin environment.",
            dest="save_origin_env",
            action="store_true",
            default=False,
        )
        p.add_argument(
            "--load-origin-env",
            help="Load origin environment variables that were saved before running xonsh by using --save-origin-env",
            dest="load_origin_env",
            action="store_true",
            default=False,
        )
        return p

    def _pprint_displayhook(value):
        if value is None:
            return
        builtins._ = None  # Set '_' to None to avoid recursion
        if isinstance(value, xpp.HiddenCommandPipeline):
            builtins._ = value
            return
        env = XSH.env
        printed_val = None
        if env.get("PRETTY_PRINT_RESULTS"):
            printed_val = pretty(value)
        if not isinstance(printed_val, str):
            # pretty may fail (i.e for unittest.mock.Mock)
            printed_val = repr(value)
        # A repr that already carries its own terminal escapes (e.g. a __repr__
        # that emits ANSI colors) must not be re-highlighted: the lexer would
        # split the escapes into separate tokens and the raw ESC bytes would be
        # mangled when re-colored, so pass it through raw like CPython's default
        # displayhook does. See https://github.com/xonsh/xonsh/issues/6503.
        if HAS_PYGMENTS and env.get("COLOR_RESULTS") and "\x1b" not in printed_val:
            tokens = list(pygments.lex(printed_val, lexer=pyghooks.XonshLexer()))
            end = "" if env.get("SHELL_TYPE") == "prompt_toolkit" else "\n"
            print_color(tokens, end=end)
        else:
            print(printed_val)  # black & white case, or an already-colored repr
        builtins._ = value

    class XonshMode(enum.Enum):
        single_command = 0
        script_from_file = 1
        script_from_stdin = 2
        interactive = 3

    def _get_rc_files(shell_kwargs: dict, args, env):
        if shell_kwargs.get("norc"):
            # if --no-rc was passed, then disable loading RC files and dirs
            return (), ()

        # determine which RC files to load, including whether any RC directories
        # should be scanned for such files
        rc_cli = shell_kwargs.get("rc")
        if rc_cli:
            # if an explicit --rc was passed, then we should load only that RC
            # file, and nothing else (ignore both XONSHRC and XONSHRC_DIR)
            rc = tuple(r for r in rc_cli if os.path.isfile(r))
            rcd = tuple(r for r in rc_cli if os.path.isdir(r))
            return rc, rcd

        # otherwise, get the RC files from XONSHRC, and RC dirs from XONSHRC_DIR
        rc = env.get("XONSHRC")
        rcd = env.get("XONSHRC_DIR")

        if not env.get("XONSH_INTERACTIVE", False):
            """
            Home based ``~/.xonshrc`` file has special meaning and history. The ecosystem around shells treats this kind of files
            as the place where interactive tools can add configs. To avoid unintended and unexpected affection
            of this file to non-interactive behavior we remove this file in non-interactive mode e.g. script with shebang.
            """
            home_xonshrc = get_home_xonshrc_path()
            rc = tuple(c for c in rc if c != home_xonshrc)

        return rc, rcd

    def _load_rc_files(shell_kwargs: dict, args, env, execer, ctx):
        events.on_pre_rc.fire()
        # load rc files
        login = shell_kwargs.get("login", True)
        rc, rcd = _get_rc_files(shell_kwargs, args, env)
        XSH.rc_files = xonshrc_context(
            rcfiles=rc, rcdirs=rcd, execer=execer, ctx=ctx, env=env, login=login
        )
        events.on_post_rc.fire()

    def _autoload_xontribs(env):
        events.on_timingprobe.fire(name="pre_xontribs_autoload")
        disabled = env.get("XONTRIBS_AUTOLOAD_DISABLED", False)
        if disabled is True:
            return
        blocked_xontribs = disabled or ()
        auto_load_xontribs_from_entrypoints(
            blocked_xontribs, verbose=bool(env.get("XONSH_DEBUG", False))
        )
        events.on_xontribs_loaded.fire()
        events.on_timingprobe.fire(name="post_xontribs_autoload")

    def start_services(shell_kwargs, args, pre_env=None):
        """Starts up the essential services in the proper order.
        This returns the environment instance as a convenience.
        """
        if pre_env is None:
            pre_env = {}
        # create execer, which loads builtins
        ctx = shell_kwargs.get("ctx", {})
        debug = to_bool_or_int(os.getenv("XONSH_DEBUG", "0"))
        events.on_timingprobe.fire(name="pre_execer_init")
        execer = Execer(
            filename="<stdin>",
            debug_level=debug,
            scriptcache=shell_kwargs.get("scriptcache", True),
            cacheall=shell_kwargs.get("cacheall", False),
        )
        events.on_timingprobe.fire(name="post_execer_init")
        events.on_timingprobe.fire(name="pre_xonsh_session_load")
        XSH.load(
            ctx=ctx,
            execer=execer,
            inherit_env=shell_kwargs.get("inherit_env", True),
            save_origin_env=args.save_origin_env,
        )
        events.on_timingprobe.fire(name="post_xonsh_session_load")

        install_import_hooks(execer)

        env = XSH.env
        for k, v in pre_env.items():
            env[k] = v

        _load_rc_files(shell_kwargs, args, env, execer, ctx)
        if not shell_kwargs.get("norc"):
            _autoload_xontribs(env)
        # create shell
        XSH.shell = Shell(execer=execer, **shell_kwargs)
        ctx["__name__"] = "__main__"
        return env

    def premain(argv=None):
        """Setup for main xonsh entry point. Returns parsed arguments."""
        if argv is None:
            argv = sys.argv[1:]
        setup_timings(argv)
        setproctitle = get_setproctitle()
        if setproctitle is not None:
            setproctitle(" ".join(["xonsh"] + argv))
        args = parser.parse_args(argv)
        if args.help:
            parser.print_help()
            parser.exit()
        if args.no_execute:
            from xonsh.checker.cli import check_no_execute

            sys.exit(check_no_execute(args))
        shell_kwargs = {
            "shell_type": args.shell_type,
            "completer": False,
            "login": False,
            "scriptcache": args.scriptcache,
            "cacheall": args.cacheall,
            "ctx": XSH.ctx,
        }
        if args.login or sys.argv[0].startswith("-"):
            args.login = True
            shell_kwargs["login"] = True
        if args.norc:
            shell_kwargs["norc"] = True
        elif args.rc:
            shell_kwargs["rc"] = args.rc
        shell_kwargs["inherit_env"] = args.inherit_env
        sys.displayhook = _pprint_displayhook
        if args.command is not None:
            args.mode = XonshMode.single_command
            shell_kwargs["shell_type"] = "none"
            xonsh_mode = "single_command"
        elif args.file is not None:
            args.mode = XonshMode.script_from_file
            shell_kwargs["shell_type"] = "none"
            xonsh_mode = "script_from_file"
        elif not sys.stdin.isatty() and not args.force_interactive:
            args.mode = XonshMode.script_from_stdin
            shell_kwargs["shell_type"] = "none"
            xonsh_mode = "script_from_stdin"
        else:
            args.mode = XonshMode.interactive
            shell_kwargs["completer"] = True
            shell_kwargs["login"] = True
            xonsh_mode = "interactive"

        pre_env = {
            "XONSH_LOGIN": shell_kwargs["login"],
            "XONSH_INTERACTIVE": args.force_interactive
            or (args.mode == XonshMode.interactive),
            "XONSH_MODE": xonsh_mode,
        }

        # Load -DVAR=VAL arguments.
        if args.defines is not None:
            for x in args.defines:
                var = x.split("=", 1)
                if len(var) == 2:
                    var, val = var
                    pre_env[var] = unquote(val)
                elif len(var) == 1:
                    var = var[0]
                    if var in os_environ:
                        pre_env[var] = os_environ[var]
                    elif os_environ.get("XONSH_DEBUG", "0") != "0":
                        print(
                            f"Variable {var!r} is not defined in origin environment.",
                            file=sys.stderr,
                        )

        if args.load_origin_env:
            origin_env = load_origin_env_from_file()
            os.environ.clear()
            os.environ.update(origin_env)

        pre_env["COLOR_RESULTS"] = os.getenv(
            "COLOR_RESULTS", str(pre_env["XONSH_INTERACTIVE"])
        )

        start_services(shell_kwargs, args, pre_env=pre_env)
        return args

    def _failback_to_other_shells(args, err):
        # only failback for interactive shell; if we cannot tell, treat it
        # as an interactive one for safe.
        if hasattr(args, "mode") and args.mode != XonshMode.interactive:
            raise err

        foreign_shell = None

        # look first in users login shell $SHELL.
        # use real os.environ, in case Xonsh hasn't initialized yet
        # but don't fail back to same shell that just failed.

        try:
            env_shell = os.getenv("SHELL")
            if env_shell and os.path.exists(env_shell) and env_shell != sys.argv[0]:
                foreign_shell = env_shell
        except Exception:
            pass

        # otherwise, find acceptable shell from (unix) list of installed shells.

        if not foreign_shell:
            excluded_list = ["xonsh", "screen"]
            shells_file = "/etc/shells"
            if not os.path.exists(shells_file):
                # right now, it will always break here on Windows
                raise err
            with open(shells_file) as f:
                for line in f:
                    line = line.strip()
                    if not line or line.startswith("#"):
                        continue
                    if "/" not in line:
                        continue
                    _, shell = line.rsplit("/", 1)
                    if shell in excluded_list:
                        continue
                    if not os.path.exists(line):
                        continue
                    foreign_shell = line
                    break

        if foreign_shell:
            traceback.print_exc()
            print("Xonsh encountered an issue during launch.", file=sys.stderr)
            print("Please report to https://github.com/xonsh/xonsh/issues", file=sys.stderr)
            print(f"Failback to {foreign_shell}", file=sys.stderr)
            os.execlp(foreign_shell, foreign_shell)
        else:
            raise err

    def main(argv=None):
        if argv is None:
            argv = sys.argv[1:]

        # Subcommand dispatch. ``xonsh format ...`` does not need a shell
        # session, xontribs, rc files, or even a TTY handshake — bypass
        # premain entirely and hand off to the formatter CLI. Add new
        # subcommands here when their lifecycle differs from the regular
        # shell entry point.
        if argv and argv[0] == "format":
            from xonsh.formatter.cli import main as format_main

            sys.exit(format_main(argv[1:]))

        if argv and argv[0] == "check":
            from xonsh.checker.cli import main as check_main

            sys.exit(check_main(argv[1:]))

        if argv and argv[0] == "lint":
            from xonsh.linter.cli import main as lint_main

            sys.exit(lint_main(argv[1:]))

        # Run the TTY startup handshake *before* premain so that xontrib
        # loading and xonshrc execution happen with xonsh already as the
        # foreground process group. rc files are arbitrary user code and
        # can contain subprocess captures ``$(...)``/``!(...)`` — including
        # interactive tools like ``fzf`` that take over the TTY via
        # ``tcsetattr``. Having the handshake done before rc runs means
        # those child processes inherit a "foreground shell" context and
        # the race between their own TTY manipulation and xonsh's
        # :func:`xonsh.procs.jobs.give_terminal_to` never has to open.
        # See :func:`_setup_controlling_terminal` for the full rationale.
        _setup_controlling_terminal()
        args = None
        try:
            args = premain(argv)
            sys.exit(main_xonsh(args))
        except Exception as err:
            _failback_to_other_shells(args, err)

    def main_xonsh(args):
        """Main entry point for xonsh cli."""
        # Normally :func:`main` has already run the handshake before
        # premain — this call is a cheap idempotent no-op in that case
        # (the module-level ``_tty_setup_done`` flag short-circuits).
        # It exists so that callers which enter the shell loop without
        # going through ``main`` (tests, programmatic launches) still
        # get the same TTY signal setup.
        _setup_controlling_terminal()

        events.on_post_init.fire()

        env = XSH.env
        shell = XSH.shell
        history = XSH.history
        exit_code = 0

        if shell and not env["XONSH_INTERACTIVE"]:
            shell.ctx.update({"exit": sys.exit})

        # store a sys.exc_info() tuple to record any exception that might occur in the user code that we are about to execute
        # if this does not change, no exceptions were thrown. Otherwise, print a traceback that does not expose xonsh internals
        exc_info = None, None, None

        try:
            if args.mode == XonshMode.interactive:
                # enter the shell

                # Setted again here because it is possible to call main_xonsh() without calling premain(), namely in the tests.
                env["XONSH_INTERACTIVE"] = True

                ignore_sigtstp()
                if (
                    env["XONSH_INTERACTIVE"]
                    and not env["XONSH_SUPPRESS_WELCOME"]
                    and sys.stdin.isatty()  # In case the interactive mode is forced but no tty (input from pipe).
                    and not any(os.path.isfile(i) for i in env["XONSHRC"])
                    and not any(os.path.isdir(i) for i in env["XONSHRC_DIR"])
                ):
                    print_welcome_screen()
                events.on_pre_cmdloop.fire()
                try:
                    shell.shell.cmdloop()
                finally:
                    events.on_post_cmdloop.fire()
            elif args.mode == XonshMode.single_command:
                # run a single command and exit
                exc_info = run_code_with_cache(
                    args.command.lstrip(),
                    "<string>",
                    shell.execer,
                    glb=shell.ctx,
                    mode="single",
                )
                if history is not None and history.last_cmd_rtn is not None:
                    exit_code = history.last_cmd_rtn
            elif args.mode == XonshMode.script_from_file:
                # run a script contained in a file
                path = os.path.abspath(os.path.expanduser(args.file))
                if os.path.isdir(path):
                    print(f"xonsh: {args.file}: Is a directory.")
                    exit_code = 1
                elif os.path.exists(path):
                    sys.argv = [args.file] + args.args
                    env.update(make_args_env())  # $ARGS is not sys.argv
                    env["XONSH_SOURCE"] = path
                    shell.ctx.update({"__file__": args.file, "__name__": "__main__"})
                    # Add script directory to sys.path[0], matching CPython behavior.
                    # See https://docs.python.org/3/library/sys_path_init.html
                    script_dir = os.path.dirname(path)
                    old_sys_path = sys.path.copy()
                    sys.path.insert(0, script_dir)
                    try:
                        exc_info = run_script_with_cache(
                            args.file, shell.execer, glb=shell.ctx, loc=None, mode="exec"
                        )
                    finally:
                        sys.path[:] = old_sys_path
                else:
                    print(f"xonsh: {args.file}: No such file.")
                    exit_code = 1
            elif args.mode == XonshMode.script_from_stdin:
                # run a script given on stdin
                code = sys.stdin.read()
                # Reopen stdin from /dev/tty so that child processes
                # (e.g. fzf, vim) can interact with the terminal instead
                # of inheriting the exhausted pipe.
                try:
                    tty_fd = os.open("/dev/tty", os.O_RDONLY)
                    os.dup2(tty_fd, 0)
                    os.close(tty_fd)
                    sys.stdin = open(0, closefd=False)
                except OSError:
                    pass  # no controlling terminal (cron, CI, etc.)
                exc_info = run_code_with_cache(
                    code, "<stdin>", shell.execer, glb=shell.ctx, loc=None, mode="exec"
                )
        except SyntaxError:
            exit_code = 1
            debug_level = env.get("XONSH_DEBUG", 0)
            if debug_level == 0:
                # print error without tracktrace
                display_error_message(sys.exc_info())
            else:
                # pass error to finally clause
                exc_info = sys.exc_info()
        except SystemExit:
            exc_info = sys.exc_info()
        finally:
            if exc_info != (None, None, None):
                err_type, err, _ = exc_info
                if err_type is SystemExit:
                    code = getattr(exc_info[1], "code", 0)
                    if code is None:
                        exit_code = 0
                    else:
                        exit_code = code
                        try:
                            exit_code = int(code)
                        except ValueError:
                            pass
                    XSH.exit = exit_code
                else:
                    exit_code = 1
                    print_exception(None, exc_info)

            if isinstance(XSH.exit, int):
                exit_code = XSH.exit
            events.on_exit.fire(exit_code=exit_code)
            postmain(args)
        return exit_code

    def postmain(args=None):
        """Teardown for main xonsh entry point, accepts parsed arguments."""
        XSH.unload()
        XSH.shell = None

    @contextlib.contextmanager
    def main_context(argv=None):
        """Generator that runs pre- and post-main() functions. This has two iterations.
        The first yields the shell. The second returns None but cleans
        up the shell.
        """
        args = premain(argv)
        yield XSH.shell
        postmain(args)

    def setup(
        ctx=None,
        shell_type="none",
        env=(("XONSH_SUBPROC_CMD_RAISE_ERROR", True),),
        aliases=(),
        xontribs=(),
        threadable_predictors=(),
        history_backend=None,
    ):
        """Starts up a new xonsh shell. Calling this in function in another
        packages ``__init__.py`` will allow xonsh to be fully used in the
        package in headless or headed mode. This function is primarily indended to
        make starting up xonsh for 3rd party packages easier.

        Here is example of using this at the top of an ``__init__.py``::

            from xonsh.main import setup
            setup()
            del setup

        Parameters
        ----------
        ctx : dict-like or None, optional
            The xonsh context to start with. If None, an empty dictionary
            is provided.
        shell_type : str, optional
            The type of shell to start. By default this is 'none', indicating
            we should start in headless mode.
        env : dict-like, optional
            Environment to update the current environment with after the shell
            has been initialized.
        aliases : dict-like, optional
            Aliases to add after the shell has been initialized.
        xontribs : iterable of str, optional
            Xontrib names to load.
        threadable_predictors : dict-like, optional
            Threadable predictors to start up with. These overide the defaults.
        """
        ctx = {} if ctx is None else ctx
        # setup xonsh ctx and execer
        if not hasattr(builtins, "__xonsh__"):
            execer = Execer(filename="<stdin>")
            XSH.load(ctx=ctx, execer=execer)
            XSH.shell = Shell(
                execer, ctx=ctx, shell_type=shell_type, history_backend=history_backend
            )
        XSH.env.update(env)
        install_import_hooks(XSH.execer)
        XSH.aliases.update(aliases)
        if xontribs:
            xontribs_load(xontribs)

        if threadable_predictors:
            XSH.commands_cache.threadable_predictors.update(threadable_predictors)