diff --git a/run-debug.bat b/run-debug.bat new file mode 100644 index 0000000..7e89fae --- /dev/null +++ b/run-debug.bat @@ -0,0 +1,22 @@ +@echo off +REM Run the exe with output captured. Window does not auto-close. +setlocal +cd /d "%~dp0" +echo Running sephiria_inv.exe ... +echo. +sephiria_inv.exe > sephiria_inv_console.log 2>&1 +set EC=%errorlevel% +echo. +echo === sephiria_inv.exe exited with code %EC% === +echo. +echo --- sephiria_inv_console.log --- +if exist sephiria_inv_console.log type sephiria_inv_console.log +echo --- end console.log --- +echo. +echo --- sephiria_inv_startup.log --- +if exist sephiria_inv_startup.log type sephiria_inv_startup.log +echo --- end startup.log --- +echo. +echo Press any key to close this window. +pause > nul +endlocal diff --git a/run.py b/run.py index 15a4a8e..ba4880a 100644 --- a/run.py +++ b/run.py @@ -1,9 +1,13 @@ -"""PyInstaller entry shim. +"""PyInstaller entry shim with aggressive startup diagnostics. -Wrapped in a top-level try/except so any startup failure is written to -%LOCALAPPDATA%\\sephiria_inv\\startup.log and surfaced to the user via a -Tk messagebox when possible. Without this wrapper, --noconsole builds -exit silently on import-time errors and the user sees "nothing happens". +Writes a log entry the moment this module is loaded (before any project +imports). Logs to two locations: + 1. Next to the exe (sys.executable directory) -- usually the user's + Downloads folder; trivially findable. + 2. %LOCALAPPDATA%/sephiria_inv/startup.log + +Either log existing tells us Python started. Neither existing means the +PyInstaller bootloader died before Python ran (DLL/runtime issue). """ from __future__ import annotations @@ -14,24 +18,57 @@ import traceback from datetime import datetime -def _log_path() -> str: - base = os.environ.get("LOCALAPPDATA") or os.path.expanduser("~") - folder = os.path.join(base, "sephiria_inv") +def _candidate_log_paths(): + paths = [] + # 1. Next to the exe (most discoverable for the user) try: - os.makedirs(folder, exist_ok=True) - except OSError: - folder = os.path.dirname(os.path.abspath(sys.argv[0])) or "." - return os.path.join(folder, "startup.log") - - -def _write_log(msg: str) -> str: - path = _log_path() - try: - with open(path, "a", encoding="utf-8") as fh: - fh.write(f"\n=== {datetime.now().isoformat()} ===\n{msg}\n") - except OSError: + exe_dir = os.path.dirname(os.path.abspath(sys.executable)) + if exe_dir: + paths.append(os.path.join(exe_dir, "sephiria_inv_startup.log")) + except Exception: pass - return path + # 2. LOCALAPPDATA / home + try: + base = os.environ.get("LOCALAPPDATA") or os.path.expanduser("~") + folder = os.path.join(base, "sephiria_inv") + os.makedirs(folder, exist_ok=True) + paths.append(os.path.join(folder, "startup.log")) + except Exception: + pass + return paths + + +def _write_log(msg: str) -> list: + written = [] + stamp = datetime.now().isoformat() + for path in _candidate_log_paths(): + try: + with open(path, "a", encoding="utf-8") as fh: + fh.write(f"\n=== {stamp} ===\n{msg}\n") + written.append(path) + except Exception: + pass + return written + + +def _env_dump() -> str: + lines = [ + f"python: {sys.version}", + f"executable: {sys.executable}", + f"argv: {sys.argv}", + f"frozen: {getattr(sys, 'frozen', False)}", + f"_MEIPASS: {getattr(sys, '_MEIPASS', '')}", + f"cwd: {os.getcwd()}", + f"PATH head: {os.environ.get('PATH', '')[:200]}", + f"LOCALAPPDATA: {os.environ.get('LOCALAPPDATA', '')}", + ] + return "\n".join(lines) + + +# === MODULE-IMPORT BREADCRUMB === +# This runs the moment Python loads the entry script. If this never appears +# in either log, the PyInstaller bootloader failed before Python started. +_BOOT_LOGS = _write_log("BOOT: run.py loaded\n" + _env_dump()) def _show_error(title: str, body: str) -> None: @@ -40,14 +77,13 @@ def _show_error(title: str, body: str) -> None: from tkinter import scrolledtext root = tk.Tk() root.title(title) - root.geometry("760x420") + root.geometry("780x460") txt = scrolledtext.ScrolledText(root, wrap="word") txt.pack(fill="both", expand=True) txt.insert("1.0", body) tk.Button(root, text="Close", command=root.destroy).pack(pady=4) root.mainloop() except Exception: - # Tk itself is broken; nothing we can show. pass @@ -56,10 +92,10 @@ def _main() -> int: from sephiria_inv.__main__ import main except BaseException: tb = traceback.format_exc() - log = _write_log("IMPORT FAIL\n" + tb) + logs = _write_log("IMPORT FAIL\n" + tb + "\n--env--\n" + _env_dump()) _show_error( "sephiria_inv: import failed", - f"Failed to import sephiria_inv.__main__\n\nLog: {log}\n\n{tb}", + f"Failed to import sephiria_inv.__main__\n\nLogs: {logs}\n\n{tb}", ) return 11 try: @@ -68,10 +104,10 @@ def _main() -> int: raise except BaseException: tb = traceback.format_exc() - log = _write_log("RUNTIME FAIL\n" + tb) + logs = _write_log("RUNTIME FAIL\n" + tb) _show_error( "sephiria_inv: runtime error", - f"Crashed during main()\n\nLog: {log}\n\n{tb}", + f"Crashed during main()\n\nLogs: {logs}\n\n{tb}", ) return 12