Skip to main content
Glama
python_detection.py6.97 kB
""" Shared utilities for Python interpreter detection and virtual environment management. """ import os import subprocess import sys import venv def get_system_python_candidates() -> list[str]: """Get a list of candidate system Python paths for the current platform. Returns: List of potential Python interpreter paths to try. """ if sys.platform == "win32": return ["py", "python.exe", "python3.exe"] elif sys.platform == "darwin": return ["/opt/homebrew/bin/python3", "/usr/local/bin/python3", "/usr/bin/python3"] else: # Linux and other Unix-like systems return ["/usr/bin/python3", "/usr/local/bin/python3"] def is_binary_ninja_python(python_path: str) -> bool: """Check if a Python path appears to be Binary Ninja's embedded Python. Args: python_path: Path to Python interpreter to check. Returns: True if this appears to be Binary Ninja's embedded Python. """ return "Binary Ninja" in python_path or "binaryninja" in python_path.lower() def get_python_executable() -> str: """Best-effort detection of a Python interpreter for running the bridge. Priority: 1) VIRTUAL_ENV (if active) 2) System Python (on macOS/Linux when running from Binary Ninja) 3) Inferred from sys.path zip/embedded layout 4) Fallback to current interpreter Returns: Path to the best available Python interpreter. """ # Check for active virtual environment first venv_path = os.environ.get("VIRTUAL_ENV") if venv_path: if sys.platform == "win32": python = os.path.join(venv_path, "Scripts", "python.exe") else: python = os.path.join(venv_path, "bin", "python3") if os.path.exists(python): return python # On macOS/Linux, if we're running from Binary Ninja, try to find system Python first if sys.platform in ("darwin", "linux"): if is_binary_ninja_python(sys.executable): candidates = get_system_python_candidates() for python_path in candidates: if os.path.exists(python_path): return python_path # Try to infer from sys.path zip/embedded layout for path in sys.path: if sys.platform == "win32": path = path.replace("/", "\\") parts = path.split(os.sep) if parts and parts[-1].endswith(".zip"): base = os.path.dirname(path) if sys.platform == "win32": candidate = os.path.join(base, "python.exe") else: candidate = os.path.abspath(os.path.join(base, "..", "bin", "python3")) if os.path.exists(candidate): return candidate # Final fallback return sys.executable def create_venv_with_system_python(venv_dir: str, requirements_file: str | None = None) -> str: """Create a virtual environment using system Python when possible. Args: venv_dir: Directory where the virtual environment should be created. requirements_file: Optional path to requirements.txt to install. Returns: Path to the Python interpreter in the created virtual environment. Raises: Exception: If virtual environment creation fails. """ # Determine expected Python path in venv if sys.platform == "win32": venv_python = os.path.join(venv_dir, "Scripts", "python.exe") else: venv_python = os.path.join(venv_dir, "bin", "python3") # Check if we need to recreate the venv should_recreate = not os.path.exists(venv_python) # On Windows, check for Binary Ninja launcher if sys.platform == "win32": bn_launcher = os.path.join(venv_dir, "Scripts", "binaryninja.exe") if os.path.exists(bn_launcher): should_recreate = True # On macOS/Linux, check if existing venv uses Binary Ninja's Python if sys.platform in ("darwin", "linux") and os.path.exists(venv_python): try: result = subprocess.run( [venv_python, "-c", "import sys; print(sys.executable)"], capture_output=True, text=True, timeout=10, ) if result.returncode == 0 and is_binary_ninja_python(result.stdout.strip()): should_recreate = True except Exception: pass if should_recreate: os.makedirs(venv_dir, exist_ok=True) created = False # Try to use system Python for venv creation if sys.platform == "win32": # Use Python launcher on Windows try: subprocess.run( ["py", "-3", "-m", "venv", venv_dir], check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) created = True except Exception: pass else: # On macOS/Linux, try system Python interpreters candidates = get_system_python_candidates() for python_path in candidates: if os.path.exists(python_path): try: subprocess.run( [python_path, "-m", "venv", venv_dir], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=30, ) created = True break except Exception: continue # Fallback to venv.EnvBuilder if system Python methods failed if not created: builder = venv.EnvBuilder(with_pip=True, upgrade=False) builder.create(venv_dir) # Install requirements if provided if requirements_file and os.path.exists(requirements_file): try: subprocess.run( [venv_python, "-m", "pip", "install", "-r", requirements_file], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=False, ) except Exception: pass return venv_python def copy_python_env(env: dict) -> bool: """Copy Python-related environment variables that affect imports. Args: env: Dictionary to copy environment variables into. Returns: True if any variables were copied, False otherwise. """ python_vars = [ "PYTHONHOME", "PYTHONPATH", "PYTHONSAFEPATH", "PYTHONPLATLIBDIR", "PYTHONPYCACHEPREFIX", "PYTHONNOUSERSITE", "PYTHONUSERBASE", ] copied = False for var in python_vars: value = os.environ.get(var) if value: copied = True env[var] = value return copied

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/fosdickio/binary_ninja_mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server