diff options
| author | blasty <blasty@local> | 2026-07-24 01:47:05 +0200 |
|---|---|---|
| committer | blasty <blasty@local> | 2026-07-24 01:47:05 +0200 |
| commit | 2392521a1cd59420a1d75d741948dd0f03b080d3 (patch) | |
| tree | 56e668396b54e959d452991510fc29a89f68d7b6 | |
| parent | worker: surface the real startup failure (not just "code 1") (diff) | |
| download | ida-tui-2392521a1cd59420a1d75d741948dd0f03b080d3.tar.gz ida-tui-2392521a1cd59420a1d75d741948dd0f03b080d3.tar.xz ida-tui-2392521a1cd59420a1d75d741948dd0f03b080d3.zip | |
worker: spawn under the IDA python (has ida_pro_mcp), not the TUI's
Root cause of "ModuleNotFoundError: No module named 'ida_pro_mcp'": the worker was
spawned with sys.executable — the TUI's python (~/ida-venv) which has idalib +
textual but NOT ida_pro_mcp. The package split on this box:
/usr/bin/python : idapro + ida_pro_mcp (the "IDA python")
~/ida-venv/python : idapro + textual (the "TUI python", runs the app)
Fix: WorkerClient now auto-detects a python that can import ida_pro_mcp
(IDATUI_WORKER_PYTHON override, else /usr/bin/python[3], else sys.executable) and
runs worker.py as a SCRIPT rather than `-m idatui.worker`, so it doesn't import
the textual-dependent idatui package __init__ under a python that has no textual.
worker.py itself is pure stdlib at load; idapro/ida_pro_mcp are imported at
runtime (both present in the IDA python).
Verified: detection returns /usr/bin/python; worker.py loads clean there.
| -rw-r--r-- | idatui/worker_client.py | 37 |
1 files changed, 33 insertions, 4 deletions
diff --git a/idatui/worker_client.py b/idatui/worker_client.py index 70de11f..8dd8491 100644 --- a/idatui/worker_client.py +++ b/idatui/worker_client.py @@ -27,6 +27,34 @@ from .client import IDAToolError, IDAConnectionError, Session from .worker import recv as _recv from .worker import send as _send +_WORKER_PY = os.path.join(os.path.dirname(os.path.abspath(__file__)), "worker.py") +_worker_python_cache: str | None = None + + +def _find_worker_python() -> str: + """A python that can import ``ida_pro_mcp`` (and thus idalib) — NOT necessarily + the TUI's python. On a typical box the TUI runs under a venv that has textual + + idalib but not ida_pro_mcp, while the system python has idalib + + ida_pro_mcp. Override with IDATUI_WORKER_PYTHON.""" + global _worker_python_cache + if _worker_python_cache: + return _worker_python_cache + override = os.environ.get("IDATUI_WORKER_PYTHON") + candidates = [override] if override else [] + candidates += ["/usr/bin/python", "/usr/bin/python3", sys.executable] + for py in candidates: + if not py or not os.path.exists(py): + continue + try: + r = subprocess.run([py, "-c", "import ida_pro_mcp"], + capture_output=True, timeout=30) + if r.returncode == 0: + _worker_python_cache = py + return py + except Exception: # noqa: BLE001 + continue + return sys.executable # last resort; the worker will report the real error + class _NoopKeepAlive: """The worker is ours and never idles out, so keepalive is a no-op.""" @@ -45,7 +73,7 @@ class WorkerClient: def __init__(self, binary_path: str, *, ttl: int = 0, python: str | None = None) -> None: self._bin = os.path.abspath(os.path.expanduser(binary_path)) - self._python = python or sys.executable + self._python = python or _find_worker_python() tag = f"{os.getpid()}-{uuid.uuid4().hex[:8]}" self._sock_path = f"/tmp/idatui-worker-{tag}.sock" self._log_path = f"/tmp/idatui-worker-{tag}.log" @@ -62,10 +90,11 @@ class WorkerClient: if self._sock is not None: return self if self._proc is None or self._proc.poll() is not None: + # run worker.py as a SCRIPT (not -m idatui.worker) so we don't + # import the textual-dependent idatui package __init__ under the + # IDA python, which usually has no textual. self._proc = subprocess.Popen( - [self._python, "-m", "idatui.worker", - self._sock_path, self._bin], - cwd=os.path.dirname(os.path.dirname(os.path.abspath(__file__))), + [self._python, _WORKER_PY, self._sock_path, self._bin], stdout=open(self._log_path, "wb"), stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL, ) |
