"""Route Python `sys.stdout`/`sys.stderr` through fd 2 on iOS. When an app is launched via `xcrun simctl launch --console-pty` (what `pn run ios` does), the simulator attaches the caller's terminal to the app's stderr, which is the same channel `NSLog` and `os_log` write to. Python `print()` calls, however, go to `sys.stdout` (fd 1), and for reasons specific to how CPython's embedded framework is started on the iOS Simulator that descriptor does not reach the attached console. As a result, users see Swift-side `NSLog` output but never their own `print()` output. Redirecting `sys.stdout` and `sys.stderr` at a Python level to write straight to fd 2 is a small, reliable fix: fd 2 *is* visible to `simctl` (that is exactly how `NSLog` reaches the terminal), so Python output lands next to the Swift logs with correct ordering. This module is intentionally self-contained (no rubicon-objc or platform-specific C bindings required), so it is safe to import early during `pythonnative` package initialization. """ from __future__ import annotations import os import sys from typing import Iterable _STDERR_FD = 2 class _StderrStream: """Minimal text-mode file-like that writes UTF-8 bytes to fd 2. Write-through (no buffering), so a `print()` call appears in the terminal immediately. That matches user expectations for an interactive "run on simulator" log stream. """ encoding = "utf-8" errors = "replace" mode = "w" name = "" def write(self, s: str) -> int: if not s: return 0 data = s.encode(self.encoding, self.errors) try: return os.write(_STDERR_FD, data) except OSError: return 0 def writelines(self, lines: Iterable[str]) -> None: for line in lines: self.write(line) def flush(self) -> None: # os.write is unbuffered; nothing to flush. return None def isatty(self) -> bool: try: return os.isatty(_STDERR_FD) except OSError: return False def fileno(self) -> int: return _STDERR_FD def close(self) -> None: # Don't actually close fd 2. return None @property def closed(self) -> bool: return False _installed = False def install() -> None: """Swap `sys.stdout` and `sys.stderr` for fd-2 writers. Idempotent: only the first call has effect. """ global _installed if _installed: return sys.stdout = _StderrStream() sys.stderr = _StderrStream() _installed = True