From 5b744690ad515e1690eb18274d203d0f00d08fef Mon Sep 17 00:00:00 2001 From: Ryan Kurtz Date: Wed, 10 Jun 2026 10:44:47 -0400 Subject: [PATCH] GP-6948: Improved error messages when pyghidra_launcher.py sees that venv or pip isn't installed --- .../PyGhidra/support/pyghidra_launcher.py | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/Ghidra/Features/PyGhidra/support/pyghidra_launcher.py b/Ghidra/Features/PyGhidra/support/pyghidra_launcher.py index aef20c521e..965b610124 100644 --- a/Ghidra/Features/PyGhidra/support/pyghidra_launcher.py +++ b/Ghidra/Features/PyGhidra/support/pyghidra_launcher.py @@ -18,6 +18,7 @@ import platform import os import sys import subprocess +from subprocess import DEVNULL import sysconfig from pathlib import Path from itertools import chain @@ -130,7 +131,24 @@ def get_venv_exe(venv_dir: Path) -> List[str]: def get_ghidra_venv(install_dir: Path, dev: bool) -> Path: return (install_dir / 'build' if dev else get_user_settings_dir(install_dir, dev)) / 'venv' - + +def check_module(python_cmd: List[str], module_name: str) -> bool: + return subprocess.run(python_cmd + ['-c', f'import {module_name}'], stdout=DEVNULL, stderr=DEVNULL).returncode == 0 + +def check_venv(python_cmd: List[str]) -> bool: + if check_module(python_cmd, 'ensurepip'): + return True + print('A virtual environment cannot be created because "ensurepip" is not available.') + print('You likely need to install the appropriate "venv" package for your platform and python version.') + return False + +def check_pip(python_cmd: List[str]) -> bool: + if check_module(python_cmd, 'pip'): + return True + print('pip is not present in the current environment.') + print("Please install pip, remove your virtual environment if applicable, and relaunch PyGhidra.") + return False + def create_ghidra_venv(python_cmd: List[str], venv_dir: Path) -> None: print(f'Creating Ghidra virtual environment at {venv_dir}...') subprocess.run(python_cmd + ['-m', 'venv', venv_dir.absolute()]) @@ -171,6 +189,8 @@ def install(install_dir: Path, python_cmd: List[str], pip_args: List[str], offer if offer_venv: ghidra_venv_choice: str = input('Install into new Ghidra virtual environment (y/n)? ') if ghidra_venv_choice.lower() in ('y', 'yes'): + if not check_venv(python_cmd): + return None venv_dir = get_ghidra_venv(install_dir, False) create_ghidra_venv(python_cmd, venv_dir) python_cmd = get_venv_exe(venv_dir) @@ -183,6 +203,8 @@ def install(install_dir: Path, python_cmd: List[str], pip_args: List[str], offer else: print('Please answer yes or no.') return None + if not check_pip(python_cmd): + return None subprocess.check_call(python_cmd + pip_args) return python_cmd elif not install_choice.lower() in ('n', 'no'): @@ -203,6 +225,8 @@ def upgrade(python_cmd: List[str], pip_args: List[str], dist_dir: Path, current_ return False choice: str = input(f'Do you wish to upgrade PyGhidra {current_version} to {included_version} (y/n)? ') if choice.lower() in ('y', 'yes'): + if not check_pip(python_cmd): + return False pip_args.append('-U') subprocess.check_call(python_cmd + pip_args) return True @@ -265,6 +289,8 @@ def main() -> None: print('Externally managed environment detected') current_pyghidra_version = get_package_version(python_cmd, 'pyghidra') if current_pyghidra_version is None: + if not check_venv(python_cmd): + sys.exit(1) create_ghidra_venv(python_cmd, venv_dir) python_cmd = get_venv_exe(venv_dir) print(f'Switching to Ghidra virtual environment: {venv_dir}') @@ -295,7 +321,7 @@ def main() -> None: subprocess.call(py_args + remaining) else: creation_flags = getattr(subprocess, 'CREATE_NO_WINDOW', 0) - subprocess.Popen(py_args + remaining, creationflags=creation_flags, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + subprocess.Popen(py_args + remaining, creationflags=creation_flags, stdout=DEVNULL, stderr=DEVNULL) if __name__ == "__main__": main()