aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--idatui/worker_client.py37
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,
)