Repository navigation
Expand file tree
/
Copy pathlaunch.py
More file actions
86 lines (76 loc) · 3.01 KB
/
Copy pathlaunch.py
File metadata and controls
86 lines (76 loc) · 3.01 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
"""UARTScope Pro Launcher - starts backend + desktop app (from source only).
This module spawns two processes, which only works when `sys.executable` is a
Python interpreter. In a PyInstaller build it is the packaged executable itself,
so `Popen([sys.executable, "-m", "uvicorn", ...])` re-runs the launcher and
recurses: one click, thousands of processes, no server. That is why the first
thing `main()` does is hand a frozen build to run_desktop.py, which serves both
ports in one process. The release workflow builds `uartscope.spec`, whose entry
point is run_desktop.py, but a hand-built `pyinstaller launch.py` must not be a
fork bomb either.
"""
import subprocess
import sys
import os
import time
def get_base_path():
"""Get the base path (handles PyInstaller _MEIPASS for bundled .exe)"""
if getattr(sys, 'frozen', False):
return sys._MEIPASS
return os.path.dirname(os.path.abspath(__file__))
def main():
if getattr(sys, 'frozen', False):
# See the module docstring: spawning is impossible in a frozen build.
from run_desktop import main as packaged_main
return packaged_main()
base_path = get_base_path()
backend_path = os.path.join(base_path, "backend")
# Start backend server in background
print("[UARTScope] Starting backend server on :8080...")
backend_proc = subprocess.Popen(
[sys.executable, "-m", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8080"],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
cwd=backend_path if os.path.exists(backend_path) else base_path
)
# Wait for backend to be ready
print("[UARTScope] Waiting for backend...")
time.sleep(4)
# Start NiceGUI desktop app
print("[UARTScope] Starting desktop app on :3000...")
print("[UARTScope] Open http://localhost:3000 in your browser")
print("[UARTScope] Press Ctrl+C to quit")
frontend_script = os.path.join(base_path, "desktop_app.py")
frontend_proc = subprocess.Popen(
[sys.executable, frontend_script],
stdout=sys.stdout,
stderr=sys.stderr,
cwd=base_path
)
# Wait for either to exit
try:
while True:
if backend_proc.poll() is not None:
print("[Backend] Exited, shutting down...")
frontend_proc.terminate()
break
if frontend_proc.poll() is not None:
print("[Frontend] Exited, shutting down...")
backend_proc.terminate()
break
time.sleep(0.5)
except KeyboardInterrupt:
print("\n[UARTScope] Shutting down...")
finally:
backend_proc.terminate()
try:
backend_proc.wait(timeout=5)
except subprocess.TimeoutExpired:
backend_proc.kill()
frontend_proc.terminate()
try:
frontend_proc.wait(timeout=5)
except subprocess.TimeoutExpired:
frontend_proc.kill()
print("[UARTScope] Goodbye!")
if __name__ == "__main__":
main()