Prefer venv Python and normalize venv extraction

This commit is contained in:
黄仁欢 2026-03-19 14:36:51 +08:00
parent 9064d3ea10
commit b5baaa38c3
2 changed files with 94 additions and 36 deletions

View File

@ -298,6 +298,7 @@ public class ResourceExtractor {
Files.createDirectories(targetDir);
copyDirectoryFromClasspath("python-runtime/venv-offline", targetDir);
}
normalizeVenvDir(targetDir.getParent(), targetDir);
makeExecutable(targetDir.resolve("bin/python3.10"));
makeExecutable(targetDir.resolve("bin/python3"));
log.info(" Done");
@ -468,4 +469,28 @@ public class ResourceExtractor {
}
}
}
private void normalizeVenvDir(Path parentDir, Path expectedDir) throws IOException {
if (Files.isDirectory(expectedDir)) {
return;
}
Path altLinux = parentDir.resolve("venv-linux-offline");
if (Files.isDirectory(altLinux)) {
log.info(" Renaming venv directory: {} -> {}", altLinux.getFileName(), expectedDir.getFileName());
Files.move(altLinux, expectedDir, StandardCopyOption.REPLACE_EXISTING);
return;
}
Path altVenv = parentDir.resolve("venv");
if (Files.isDirectory(altVenv)) {
log.info(" Renaming venv directory: {} -> {}", altVenv.getFileName(), expectedDir.getFileName());
Files.move(altVenv, expectedDir, StandardCopyOption.REPLACE_EXISTING);
return;
}
Path altOff = parentDir.resolve("venv-offline");
if (Files.isDirectory(altOff)) {
// already present under expected name in parent, but expectedDir path differs
return;
}
log.warn(" Venv directory not found after extraction under {}", parentDir);
}
}

View File

@ -153,10 +153,20 @@ public class FlaskProcessManager implements ApplicationListener<ContextRefreshed
if (libPath != null) {
env.put("PYTHONPATH", libPath.toString());
}
// Prefer embedded Python home when using standalone runtime
Path pythonHome = resolveEmbeddedPythonHome();
if (pythonHome != null) {
env.put("PYTHONHOME", pythonHome.toString());
Path venvRoot = resolveEmbeddedVenvRoot();
boolean usingVenv = venvRoot != null && embeddedPython != null &&
embeddedPython.toAbsolutePath().startsWith(venvRoot.toAbsolutePath());
if (usingVenv) {
Path venvBin = venvRoot.resolve(isWindows() ? "Scripts" : "bin");
env.put("VIRTUAL_ENV", venvRoot.toString());
String oldPath = env.getOrDefault("PATH", "");
env.put("PATH", venvBin.toString() + File.pathSeparator + oldPath);
} else {
// Prefer embedded Python home when using standalone runtime
Path pythonHome = resolveEmbeddedPythonHome();
if (pythonHome != null) {
env.put("PYTHONHOME", pythonHome.toString());
}
}
// Use bundled models
@ -338,7 +348,40 @@ public class FlaskProcessManager implements ApplicationListener<ContextRefreshed
}
private Path resolveEmbeddedPython() {
// Prefer embedded standalone runtime first
// Prefer venv interpreter first (ensures Flask and deps are available)
Path venvRoot = resolveEmbeddedVenvRoot();
if (venvRoot != null) {
Path linuxPython310 = venvRoot.resolve("bin/python3.10");
if (Files.exists(linuxPython310)) {
return linuxPython310;
}
Path linuxPython311 = venvRoot.resolve("bin/python3.11");
if (Files.exists(linuxPython311)) {
return linuxPython311;
}
Path linuxPython39 = venvRoot.resolve("bin/python3.9");
if (Files.exists(linuxPython39)) {
return linuxPython39;
}
Path linuxPython38 = venvRoot.resolve("bin/python3.8");
if (Files.exists(linuxPython38)) {
return linuxPython38;
}
Path linuxPythonVenv = venvRoot.resolve("bin/python3");
if (Files.exists(linuxPythonVenv)) {
return linuxPythonVenv;
}
Path linuxPythonAlt = venvRoot.resolve("bin/python");
if (Files.exists(linuxPythonAlt)) {
return linuxPythonAlt;
}
Path windowsPython = venvRoot.resolve("Scripts/python.exe");
if (Files.exists(windowsPython)) {
return windowsPython;
}
}
// Fallback to embedded standalone runtime
Path linuxPython310 = Paths.get(resourceDir, "python-runtime/python/bin/python3.10");
if (Files.exists(linuxPython310)) {
return linuxPython310;
@ -351,35 +394,6 @@ public class FlaskProcessManager implements ApplicationListener<ContextRefreshed
if (Files.exists(linuxPython)) {
return linuxPython;
}
// Fallback to venv interpreter if bundled (still embedded)
linuxPython310 = Paths.get(resourceDir, "python-runtime/venv-offline/bin/python3.10");
if (Files.exists(linuxPython310)) {
return linuxPython310;
}
Path linuxPython311 = Paths.get(resourceDir, "python-runtime/venv-offline/bin/python3.11");
if (Files.exists(linuxPython311)) {
return linuxPython311;
}
Path linuxPython39 = Paths.get(resourceDir, "python-runtime/venv-offline/bin/python3.9");
if (Files.exists(linuxPython39)) {
return linuxPython39;
}
Path linuxPython38 = Paths.get(resourceDir, "python-runtime/venv-offline/bin/python3.8");
if (Files.exists(linuxPython38)) {
return linuxPython38;
}
Path linuxPythonVenv = Paths.get(resourceDir, "python-runtime/venv-offline/bin/python3");
if (Files.exists(linuxPythonVenv)) {
return linuxPythonVenv;
}
Path linuxPythonAlt = Paths.get(resourceDir, "python-runtime/venv-offline/bin/python");
if (Files.exists(linuxPythonAlt)) {
return linuxPythonAlt;
}
Path windowsPython = Paths.get(resourceDir, "python-runtime/venv-offline/Scripts/python.exe");
if (Files.exists(windowsPython)) {
return windowsPython;
}
Path windowsPythonAlt = Paths.get(resourceDir, "python-runtime/python.exe");
if (Files.exists(windowsPythonAlt)) {
return windowsPythonAlt;
@ -397,7 +411,8 @@ public class FlaskProcessManager implements ApplicationListener<ContextRefreshed
private Path resolveEmbeddedSitePackages() {
// Linux venvs use lib/pythonX.Y/site-packages
Path libRoot = Paths.get(resourceDir, "python-runtime/venv-offline/lib");
Path venvRoot = resolveEmbeddedVenvRoot();
Path libRoot = venvRoot != null ? venvRoot.resolve("lib") : Paths.get(resourceDir, "python-runtime/venv-offline/lib");
if (Files.isDirectory(libRoot)) {
try {
try (java.nio.file.DirectoryStream<Path> stream = Files.newDirectoryStream(libRoot)) {
@ -415,10 +430,28 @@ public class FlaskProcessManager implements ApplicationListener<ContextRefreshed
}
}
// Windows venvs
Path venvLibWin = Paths.get(resourceDir, "python-runtime/venv-offline/Lib/site-packages");
Path venvLibWin = venvRoot != null ? venvRoot.resolve("Lib/site-packages") :
Paths.get(resourceDir, "python-runtime/venv-offline/Lib/site-packages");
if (Files.exists(venvLibWin)) {
return venvLibWin;
}
return null;
}
private Path resolveEmbeddedVenvRoot() {
Path venv = Paths.get(resourceDir, "python-runtime/venv-offline");
if (Files.isDirectory(venv)) {
return venv;
}
Path alt = Paths.get(resourceDir, "python-runtime/venv-linux-offline");
if (Files.isDirectory(alt)) {
return alt;
}
return null;
}
private boolean isWindows() {
String os = System.getProperty("os.name").toLowerCase();
return os.contains("windows");
}
}