Skip to content

Commit c4a3429

Browse files
committed
refactor: use pip uninstall instead of folder deletion on plugin reload
When the plugin loader is destroyed (on server reload) or created (on startup after a crash), pip uninstall all endstone_* distributions instead of deleting the prefix folder. This avoids file-lock errors on Windows, preserves shared dependencies across reloads for faster loading, and handles crash recovery since _uninstall_plugins discovers distributions dynamically rather than relying on tracked state.
1 parent 666078f commit c4a3429

1 file changed

Lines changed: 33 additions & 27 deletions

File tree

endstone/plugin/plugin_loader.py

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import importlib
55
import os
66
import os.path
7-
import shutil
87
import site
98
import subprocess
109
import sys
@@ -90,8 +89,38 @@ class PythonPluginLoader(PluginLoader):
9089

9190
def __init__(self, server: Server):
9291
PluginLoader.__init__(self, server)
93-
self._invalidate_caches()
9492
self._plugins: list[Plugin] = []
93+
self._uninstall_plugins()
94+
self._invalidate_caches()
95+
96+
def __del__(self) -> None:
97+
self._uninstall_plugins()
98+
99+
@staticmethod
100+
def _find_plugin_eps() -> list[EntryPoint]:
101+
return [
102+
ep
103+
for ep in entry_points(group="endstone")
104+
if ep.dist is not None and ep.dist.name.replace("_", "-").startswith("endstone-")
105+
]
106+
107+
@staticmethod
108+
def _uninstall_plugins() -> None:
109+
dists = [ep.dist.name for ep in PythonPluginLoader._find_plugin_eps()] # type: ignore[union-attr]
110+
if not dists:
111+
return
112+
subprocess.run(
113+
[
114+
sys.executable,
115+
"-m",
116+
"pip",
117+
"uninstall",
118+
*dists,
119+
"-y",
120+
"--quiet",
121+
"--disable-pip-version-check",
122+
],
123+
)
95124

96125
def _invalidate_caches(self) -> None:
97126
importlib.invalidate_caches()
@@ -102,21 +131,8 @@ def _invalidate_caches(self) -> None:
102131
self._prefix = os.path.join("plugins", ".local")
103132
for site_dir in site.getsitepackages(prefixes=[self._prefix]):
104133
site.addsitedir(site_dir)
105-
if (
106-
os.path.exists(site_dir)
107-
and os.path.commonpath([site_dir, self._prefix]) == self._prefix
108-
and site_dir != self._prefix
109-
):
110-
for directory in os.listdir(site_dir):
111-
if not os.path.isdir(os.path.join(site_dir, directory)):
112-
continue
113-
if directory.startswith("endstone_") or directory.startswith("~"):
114-
shutil.rmtree(os.path.join(site_dir, directory))
115134

116135
def load_plugin(self, file: str) -> Plugin | None: # type: ignore[override]
117-
env = os.environ.copy()
118-
env.pop("LD_PRELOAD", "")
119-
120136
dist_name: str | None = pkginfo.Wheel(file).name
121137
if dist_name is None:
122138
raise ValueError(f"Could not determine package name from {file}")
@@ -133,7 +149,7 @@ def load_plugin(self, file: str) -> Plugin | None: # type: ignore[override]
133149
"--no-warn-script-location",
134150
"--disable-pip-version-check",
135151
],
136-
env=env,
152+
137153
)
138154

139155
eps = distribution(dist_name).entry_points.select(group="endstone")
@@ -148,8 +164,7 @@ def load_plugins(self, directory: str) -> list[Plugin]:
148164
loaded_plugins = []
149165

150166
if not self._plugins:
151-
eps = entry_points(group="endstone")
152-
for ep in eps:
167+
for ep in self._find_plugin_eps():
153168
plugin = self._load_plugin_from_ep(ep)
154169
if plugin:
155170
loaded_plugins.append(plugin)
@@ -162,17 +177,8 @@ def load_plugins(self, directory: str) -> list[Plugin]:
162177
return loaded_plugins
163178

164179
def _load_plugin_from_ep(self, ep: EntryPoint) -> Plugin | None:
165-
# enforce naming convention
166180
if ep.dist is None:
167181
return None
168-
if not ep.dist.name.replace("_", "-").startswith("endstone-"):
169-
self.server.logger.error(
170-
f"Error occurred when trying to load plugin from entry point '{ep.name}': Invalid name."
171-
)
172-
self.server.logger.error(
173-
f"The name of distribution ({ep.dist.name}) does not start with 'endstone-' or 'endstone_'."
174-
)
175-
return None
176182

177183
dist_name = "endstone-" + ep.name.replace("_", "-")
178184
if ep.dist.name.replace("_", "-") != dist_name:

0 commit comments

Comments
 (0)