From d80693d95d8be8b836775d758e9c3d7a76567766 Mon Sep 17 00:00:00 2001 From: Kevin Ernst Date: Mon, 20 Jul 2026 18:50:50 -0400 Subject: [PATCH] Use a private virtualenv for dependencies Assisted by: Aider + gemini/gemini-3.1-pro-preview --- .gitignore | 2 ++ python3/ghost_install.py | 21 +++++++++++++++------ pythonx/ghost_wrapper.py | 17 +++++++++++++++++ rplugin/python3/ghost.py | 19 +++++++++++++++++-- 4 files changed, 51 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 3368b7b..1d3931b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ __pycache__/ README.html +doc/tags +venv diff --git a/python3/ghost_install.py b/python3/ghost_install.py index a9e1ec3..3a165d0 100644 --- a/python3/ghost_install.py +++ b/python3/ghost_install.py @@ -3,6 +3,7 @@ from os import path import os import vim +import venv def install(): @@ -13,13 +14,19 @@ def isvim(): req = "requirements_win.txt" if sys.platform == "win32" else \ "requirements_posix.txt" reqFile = path.join(base, req) + venv_dir = path.join(base, "venv") + + print("ghost: creating virtualenv at %s" % venv_dir) + venv.create(venv_dir, with_pip=True) + + if sys.platform == "win32": + pip_exe = path.join(venv_dir, "Scripts", "pip.exe") + else: + pip_exe = path.join(venv_dir, "bin", "pip") + print("ghost: installing dependencies from %s" % reqFile) - py3 = sys.executable - # on windows vim reports sys.executable as the path to gvim/vim - if isvim() and sys.platform == "win32": - py3 = path.normpath(path.join(os.__file__, "../..", "python.exe")) - out = subprocess.check_output([py3, - "-m", "pip", "install", "--user", "-r", + out = subprocess.check_output([pip_exe, + "install", "-r", reqFile], stderr=subprocess.STDOUT, shell=False) for l in out.decode().split(os.linesep): @@ -27,3 +34,5 @@ def isvim(): print("ghost: dependencies installed successfully") except subprocess.CalledProcessError as cpe: print("ghost: error installing dependencies: %s" % cpe) + except Exception as e: + print("ghost: error: %s" % e) diff --git a/pythonx/ghost_wrapper.py b/pythonx/ghost_wrapper.py index ee62df6..6e4849c 100644 --- a/pythonx/ghost_wrapper.py +++ b/pythonx/ghost_wrapper.py @@ -1,3 +1,20 @@ +import os +import sys +import site +import glob + +# Inject virtualenv site-packages if it exists +_plugin_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) +_venv_path = os.path.join(_plugin_root, 'venv') +if os.path.exists(_venv_path): + if sys.platform == 'win32': + _site_packages = os.path.join(_venv_path, 'Lib', 'site-packages') + else: + _site_packages_matches = glob.glob(os.path.join(_venv_path, 'lib', 'python3.*', 'site-packages')) + _site_packages = _site_packages_matches[0] if _site_packages_matches else None + if _site_packages and os.path.exists(_site_packages): + site.addsitedir(_site_packages) + from ghost import Ghost as _Ghost import vim diff --git a/rplugin/python3/ghost.py b/rplugin/python3/ghost.py index 89aa056..dfd3cb0 100644 --- a/rplugin/python3/ghost.py +++ b/rplugin/python3/ghost.py @@ -1,3 +1,20 @@ +import os +import sys +import site +import glob + +# Inject virtualenv site-packages if it exists +_plugin_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..')) +_venv_path = os.path.join(_plugin_root, 'venv') +if os.path.exists(_venv_path): + if sys.platform == 'win32': + _site_packages = os.path.join(_venv_path, 'Lib', 'site-packages') + else: + _site_packages_matches = glob.glob(os.path.join(_venv_path, 'lib', 'python3.*', 'site-packages')) + _site_packages = _site_packages_matches[0] if _site_packages_matches else None + if _site_packages and os.path.exists(_site_packages): + site.addsitedir(_site_packages) + import subprocess from http.server import HTTPServer, BaseHTTPRequestHandler from random import randint @@ -5,8 +22,6 @@ from tempfile import mkstemp import logging import json -import os -import sys import platform from SimpleWebSocketServer import SimpleWebSocketServer, WebSocket try: