mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 19:19:58 -04:00
Run from a checkout, the launcher uses the Python that started it. A Python older than 3.14 without the zstandard package got through the first-run screen and stopped at Gather Build Tools with only "No module named 'zstandard'", after the packages were downloaded. toolchain.zstd_missing now looks for compression.zstd or zstandard when the launcher starts. Without either, the log and the first screen name the Python and give the pip line for tools/requirements-launcher.txt, and Gather Build Tools stops with the same message before it unpacks anything. A release's own Python always has the package; if it ever lacks it, the message says to reinstall NX2navi. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
62 lines
2.4 KiB
Python
62 lines
2.4 KiB
Python
"""What every part of the launcher shares: the folder layout, the settings and the log (data/logs/launcher.log,
|
|
the run before it in launcher.1.log)."""
|
|
import os
|
|
import threading
|
|
import time
|
|
|
|
from .paths import Layout
|
|
from .settings import Settings
|
|
|
|
LOG_LIMIT = 4 << 20
|
|
|
|
|
|
class Context:
|
|
def __init__(self, layout=None, echo=False):
|
|
self.layout = (layout or Layout()).ensure()
|
|
self.settings = Settings(self.layout.settings)
|
|
self.version = self.layout.version
|
|
self.echo = echo
|
|
self._lock = threading.Lock()
|
|
path = self.layout.logs / 'launcher.log'
|
|
try:
|
|
if path.is_file() and path.stat().st_size > LOG_LIMIT:
|
|
os.replace(path, path.with_name('launcher.1.log'))
|
|
except OSError:
|
|
pass
|
|
try:
|
|
self._file = open(path, 'a', encoding='utf-8', errors='replace')
|
|
except OSError:
|
|
self._file = None
|
|
self.log(f'--- NX2navi launcher {self.version} ({"checkout" if self.layout.dev else "release"}) in '
|
|
f'{self.layout.root}')
|
|
from .toolchain import zstd_missing
|
|
self.zstd_missing = zstd_missing(self.layout)
|
|
if self.zstd_missing:
|
|
self.log(self.zstd_missing)
|
|
if self.settings.unreadable:
|
|
self.log(f'{self.layout.settings} is not valid JSON: the settings start from their defaults; the file as '
|
|
'it was is kept as launcher.json.bad')
|
|
|
|
def log(self, line):
|
|
"""One line (or several) into launcher.log. A log that cannot be written (a full disk, a file closed under
|
|
it) loses the line and nothing else: the callers log from the build's reader thread and from the window's
|
|
timers, where an exception would stop the build's output from being read or the timer from running."""
|
|
stamp = time.strftime('%H:%M:%S')
|
|
with self._lock:
|
|
if self._file:
|
|
try:
|
|
for part in str(line).splitlines() or ['']:
|
|
self._file.write(f'{stamp} {part}\n')
|
|
self._file.flush()
|
|
except (OSError, ValueError): # ValueError: the file was closed
|
|
pass
|
|
if self.echo:
|
|
print(line, flush=True)
|
|
|
|
def log_tail(self, n=80):
|
|
try:
|
|
with open(self.layout.logs / 'launcher.log', encoding='utf-8', errors='replace') as f:
|
|
return f.readlines()[-n:]
|
|
except OSError:
|
|
return []
|