mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-08-03 01:38:06 +00:00
fix(plugins): replace dependency marker files with a real satisfaction check
The .dependencies_installed hash-marker system only tracked "was this exact requirements.txt hashed before" — not whether the packages it names are actually present. That made it fragile (a wiped venv, a manually removed package, or a lost/corrupted marker forces a needless full pip reinstall or, worse, a false skip) and produced dead weight for the ~10 plugins whose requirements.txt is comment-only (they still paid a pip subprocess on first boot before a marker existed). Replace it with requirements_are_satisfied() in plugin_loader.py, which checks each real requirement line against importlib.metadata directly, so install_dependencies() only shells out to pip when something is actually missing or version-mismatched. Drops the marker file entirely: removed all marker read/write sites in plugin_loader.py and store_manager.py, the now-pointless marker-cleanup step in the git-update path, the unused legacy marker implementation in plugin_manager.py, and the already-stale clear_dependency_markers.sh script. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
978a03b42d
commit
d86dc5914b
@@ -9,7 +9,6 @@ API Version: 1.0.0
|
||||
|
||||
import json
|
||||
import sys
|
||||
import subprocess
|
||||
import time
|
||||
import threading
|
||||
from pathlib import Path
|
||||
@@ -177,90 +176,6 @@ class PluginManager:
|
||||
|
||||
return plugin_ids
|
||||
|
||||
def _get_dependency_marker_path(self, plugin_id: str) -> Path:
|
||||
"""Get path to dependency installation marker file."""
|
||||
plugin_dir = self.plugins_dir / plugin_id
|
||||
if not plugin_dir.exists():
|
||||
# Try with ledmatrix- prefix
|
||||
plugin_dir = self.plugins_dir / f"ledmatrix-{plugin_id}"
|
||||
return plugin_dir / ".dependencies_installed"
|
||||
|
||||
def _check_dependencies_installed(self, plugin_id: str) -> bool:
|
||||
"""Check if dependencies are already installed for a plugin."""
|
||||
marker_path = self._get_dependency_marker_path(plugin_id)
|
||||
return marker_path.exists()
|
||||
|
||||
def _mark_dependencies_installed(self, plugin_id: str) -> None:
|
||||
"""Mark dependencies as installed for a plugin."""
|
||||
marker_path = self._get_dependency_marker_path(plugin_id)
|
||||
try:
|
||||
marker_path.touch()
|
||||
# Set proper file permissions after creating marker
|
||||
from src.common.permission_utils import (
|
||||
ensure_file_permissions,
|
||||
get_plugin_file_mode
|
||||
)
|
||||
ensure_file_permissions(marker_path, get_plugin_file_mode())
|
||||
except (OSError, PermissionError) as e:
|
||||
self.logger.warning("Could not create dependency marker for %s: %s", plugin_id, e)
|
||||
|
||||
def _remove_dependency_marker(self, plugin_id: str) -> None:
|
||||
"""Remove dependency installation marker."""
|
||||
marker_path = self._get_dependency_marker_path(plugin_id)
|
||||
try:
|
||||
if marker_path.exists():
|
||||
marker_path.unlink()
|
||||
except (OSError, PermissionError) as e:
|
||||
self.logger.warning("Could not remove dependency marker for %s: %s", plugin_id, e)
|
||||
|
||||
def _install_plugin_dependencies(self, requirements_file: Path) -> bool:
|
||||
"""
|
||||
Install plugin dependencies from requirements.txt.
|
||||
|
||||
Args:
|
||||
requirements_file: Path to requirements.txt
|
||||
|
||||
Returns:
|
||||
True if installation succeeded or not needed, False on error
|
||||
"""
|
||||
try:
|
||||
self.logger.info("Installing dependencies from %s", requirements_file)
|
||||
result = subprocess.run(
|
||||
[sys.executable, "-m", "pip", "install", "--break-system-packages", "--no-cache-dir", "-r", str(requirements_file)],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=300,
|
||||
check=False
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
self.logger.info("Dependencies installed successfully")
|
||||
return True
|
||||
else:
|
||||
self.logger.warning("Dependency installation returned non-zero exit code: %s", result.stderr)
|
||||
return False
|
||||
except subprocess.TimeoutExpired:
|
||||
self.logger.error("Dependency installation timed out")
|
||||
return False
|
||||
except FileNotFoundError as e:
|
||||
self.logger.warning("Command not found: %s. Skipping dependency installation", e)
|
||||
return True
|
||||
except (BrokenPipeError, OSError) as e:
|
||||
# Handle broken pipe errors (errno 32) which can occur during pip downloads
|
||||
# Often caused by network interruptions or output buffer issues
|
||||
if isinstance(e, OSError) and e.errno == 32:
|
||||
self.logger.error(
|
||||
"Broken pipe error during dependency installation. "
|
||||
"This usually indicates a network interruption or pip output buffer issue. "
|
||||
"Try installing again or check your network connection."
|
||||
)
|
||||
else:
|
||||
self.logger.error("OS error during dependency installation: %s", e)
|
||||
return False
|
||||
except Exception as e:
|
||||
self.logger.error("Unexpected error installing dependencies: %s", e, exc_info=True)
|
||||
return True
|
||||
|
||||
def load_plugin(self, plugin_id: str) -> bool:
|
||||
"""
|
||||
Load a plugin by ID.
|
||||
|
||||
Reference in New Issue
Block a user