mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-09-29 03:22:21 +02:00
fix(cookbook): activate local Windows venv in bash runner (#5734)
This commit is contained in:
@@ -1204,6 +1204,41 @@ def _safe_env_prefix(ep: str | None) -> str | None:
|
|||||||
return f'[ -f "{path}" ] && source "{path}" || true'
|
return f'[ -f "{path}" ] && source "{path}" || true'
|
||||||
|
|
||||||
|
|
||||||
|
def _local_windows_bash_env_prefix(ep: str | None) -> str | None:
|
||||||
|
"""Convert a frontend PowerShell venv prefix for the local Git Bash runner."""
|
||||||
|
if not ep:
|
||||||
|
return ep
|
||||||
|
|
||||||
|
prefix = ep.strip()
|
||||||
|
if not prefix.startswith("&"):
|
||||||
|
return ep
|
||||||
|
|
||||||
|
raw_path = prefix[1:].lstrip()
|
||||||
|
if not raw_path:
|
||||||
|
return ep
|
||||||
|
if raw_path.startswith("'"):
|
||||||
|
if len(raw_path) < 2 or not raw_path.endswith("'"):
|
||||||
|
return ep
|
||||||
|
quoted_path = raw_path[1:-1]
|
||||||
|
if "'" in quoted_path.replace("''", ""):
|
||||||
|
return ep
|
||||||
|
path = quoted_path.replace("''", "'")
|
||||||
|
else:
|
||||||
|
path = raw_path.rstrip()
|
||||||
|
if "'" in path or '"' in path:
|
||||||
|
return ep
|
||||||
|
if any(c in path for c in "\r\n;&|`$<>"):
|
||||||
|
return ep
|
||||||
|
if not path.replace("\\", "/").casefold().endswith("/scripts/activate.ps1"):
|
||||||
|
return ep
|
||||||
|
|
||||||
|
bash_path = _git_bash_path(path)
|
||||||
|
if "\\" in bash_path:
|
||||||
|
return ep
|
||||||
|
bash_path = bash_path[: -len("Activate.ps1")] + "activate"
|
||||||
|
return "source " + shlex.quote(bash_path)
|
||||||
|
|
||||||
|
|
||||||
def _ssh_ps(host, script_path, port=None):
|
def _ssh_ps(host, script_path, port=None):
|
||||||
"""Build SSH command to run a PowerShell script on a Windows remote."""
|
"""Build SSH command to run a PowerShell script on a Windows remote."""
|
||||||
pf = f"-p {port} " if port and port != "22" else ""
|
pf = f"-p {port} " if port and port != "22" else ""
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ from routes.cookbook_helpers import (
|
|||||||
_SESSION_ID_RE, _validate_repo_id, _validate_serve_model_id, _validate_include, _validate_token,
|
_SESSION_ID_RE, _validate_repo_id, _validate_serve_model_id, _validate_include, _validate_token,
|
||||||
_validate_local_dir, _validate_gpus, _shell_path,
|
_validate_local_dir, _validate_gpus, _shell_path,
|
||||||
_ps_squote, _bash_squote, _validate_serve_cmd, _parse_serve_phase, OLLAMA_MISSING_HINT,
|
_ps_squote, _bash_squote, _validate_serve_cmd, _parse_serve_phase, OLLAMA_MISSING_HINT,
|
||||||
_safe_env_prefix, _local_tooling_path_export, _append_serve_preflight_exit_lines,
|
_safe_env_prefix, _local_windows_bash_env_prefix, _local_tooling_path_export, _append_serve_preflight_exit_lines,
|
||||||
_append_serve_exit_code_lines, _append_llama_cpp_linux_accel_build_lines, _cached_model_scan_script,
|
_append_serve_exit_code_lines, _append_llama_cpp_linux_accel_build_lines, _cached_model_scan_script,
|
||||||
load_stored_hf_token,
|
load_stored_hf_token,
|
||||||
_append_vllm_linux_preflight_lines, _ollama_bind_from_cmd, _pip_install_fallback_chain,
|
_append_vllm_linux_preflight_lines, _ollama_bind_from_cmd, _pip_install_fallback_chain,
|
||||||
@@ -1336,7 +1336,7 @@ def setup_cookbook_routes() -> APIRouter:
|
|||||||
# Local: run hf download in the background (tmux on POSIX, a detached
|
# Local: run hf download in the background (tmux on POSIX, a detached
|
||||||
# process + logfile on Windows where tmux doesn't exist).
|
# process + logfile on Windows where tmux doesn't exist).
|
||||||
if req.env_prefix:
|
if req.env_prefix:
|
||||||
lines.append(_safe_env_prefix(req.env_prefix))
|
lines.append(_safe_env_prefix(_local_windows_bash_env_prefix(req.env_prefix) if local_windows else req.env_prefix))
|
||||||
else:
|
else:
|
||||||
lines.append("deactivate 2>/dev/null; hash -r")
|
lines.append("deactivate 2>/dev/null; hash -r")
|
||||||
# Show whether the HF token reached this run (masked) — tells a gated
|
# Show whether the HF token reached this run (masked) — tells a gated
|
||||||
@@ -2166,7 +2166,7 @@ def setup_cookbook_routes() -> APIRouter:
|
|||||||
if req.gpus:
|
if req.gpus:
|
||||||
runner_lines.append(f"export CUDA_VISIBLE_DEVICES='{req.gpus}'")
|
runner_lines.append(f"export CUDA_VISIBLE_DEVICES='{req.gpus}'")
|
||||||
if req.env_prefix:
|
if req.env_prefix:
|
||||||
runner_lines.append(_safe_env_prefix(req.env_prefix))
|
runner_lines.append(_safe_env_prefix(_local_windows_bash_env_prefix(req.env_prefix) if local_windows else req.env_prefix))
|
||||||
else:
|
else:
|
||||||
runner_lines.append("deactivate 2>/dev/null; hash -r")
|
runner_lines.append("deactivate 2>/dev/null; hash -r")
|
||||||
_append_venv_nvidia_library_path_lines(runner_lines, cmd=req.cmd)
|
_append_venv_nvidia_library_path_lines(runner_lines, cmd=req.cmd)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ let _getPlatform;
|
|||||||
let _serverByVal;
|
let _serverByVal;
|
||||||
let _isWindows;
|
let _isWindows;
|
||||||
let _buildEnvPrefix;
|
let _buildEnvPrefix;
|
||||||
|
let _psQuote;
|
||||||
let _buildServeCmd;
|
let _buildServeCmd;
|
||||||
let _detectBackend;
|
let _detectBackend;
|
||||||
let _detectToolParser;
|
let _detectToolParser;
|
||||||
@@ -538,7 +539,7 @@ export async function _runModelDownload(panel, model, backend, hostOverride) {
|
|||||||
if (srv.downloadDir) payload.local_dir = srv.downloadDir;
|
if (srv.downloadDir) payload.local_dir = srv.downloadDir;
|
||||||
if (isWin) {
|
if (isWin) {
|
||||||
if (env === 'venv' && envPath) {
|
if (env === 'venv' && envPath) {
|
||||||
payload.env_prefix = '& ' + (envPath.endsWith('\\Scripts\\Activate.ps1') ? envPath : envPath + '\\Scripts\\Activate.ps1');
|
payload.env_prefix = '& ' + _psQuote(envPath.endsWith('\\Scripts\\Activate.ps1') ? envPath : envPath + '\\Scripts\\Activate.ps1');
|
||||||
} else if (env === 'conda' && envPath) {
|
} else if (env === 'conda' && envPath) {
|
||||||
payload.env_prefix = 'conda activate ' + envPath;
|
payload.env_prefix = 'conda activate ' + envPath;
|
||||||
}
|
}
|
||||||
@@ -652,6 +653,7 @@ export function initDownload(shared) {
|
|||||||
_serverByVal = shared._serverByVal;
|
_serverByVal = shared._serverByVal;
|
||||||
_isWindows = shared._isWindows;
|
_isWindows = shared._isWindows;
|
||||||
_buildEnvPrefix = shared._buildEnvPrefix;
|
_buildEnvPrefix = shared._buildEnvPrefix;
|
||||||
|
_psQuote = shared._psQuote;
|
||||||
_buildServeCmd = shared._buildServeCmd;
|
_buildServeCmd = shared._buildServeCmd;
|
||||||
_detectBackend = shared._detectBackend;
|
_detectBackend = shared._detectBackend;
|
||||||
_detectToolParser = shared._detectToolParser;
|
_detectToolParser = shared._detectToolParser;
|
||||||
|
|||||||
@@ -338,6 +338,7 @@ let _sshPrefix;
|
|||||||
let _getPlatform;
|
let _getPlatform;
|
||||||
let _isWindows;
|
let _isWindows;
|
||||||
let _buildEnvPrefix;
|
let _buildEnvPrefix;
|
||||||
|
let _psQuote;
|
||||||
let _loadPresets;
|
let _loadPresets;
|
||||||
let _savePresets;
|
let _savePresets;
|
||||||
let _copyText;
|
let _copyText;
|
||||||
@@ -1971,7 +1972,7 @@ export async function _launchServeTask(shortName, repo, cmd, fields, hostOverrid
|
|||||||
let envPrefix = '';
|
let envPrefix = '';
|
||||||
if (_isWindows()) {
|
if (_isWindows()) {
|
||||||
if (_envState.env === 'venv' && _envState.envPath) {
|
if (_envState.env === 'venv' && _envState.envPath) {
|
||||||
envPrefix = '& ' + (_envState.envPath.endsWith('\\Scripts\\Activate.ps1') ? _envState.envPath : _envState.envPath + '\\Scripts\\Activate.ps1');
|
envPrefix = '& ' + _psQuote(_envState.envPath.endsWith('\\Scripts\\Activate.ps1') ? _envState.envPath : _envState.envPath + '\\Scripts\\Activate.ps1');
|
||||||
} else if (_envState.env === 'conda' && _envState.envPath) {
|
} else if (_envState.env === 'conda' && _envState.envPath) {
|
||||||
envPrefix = 'conda activate ' + _envState.envPath;
|
envPrefix = 'conda activate ' + _envState.envPath;
|
||||||
}
|
}
|
||||||
@@ -4402,6 +4403,7 @@ export function initRunning(shared) {
|
|||||||
_getPlatform = shared._getPlatform;
|
_getPlatform = shared._getPlatform;
|
||||||
_isWindows = shared._isWindows;
|
_isWindows = shared._isWindows;
|
||||||
_buildEnvPrefix = shared._buildEnvPrefix;
|
_buildEnvPrefix = shared._buildEnvPrefix;
|
||||||
|
_psQuote = shared._psQuote;
|
||||||
_loadPresets = shared._loadPresets;
|
_loadPresets = shared._loadPresets;
|
||||||
_savePresets = shared._savePresets;
|
_savePresets = shared._savePresets;
|
||||||
_copyText = shared._copyText;
|
_copyText = shared._copyText;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ from routes.cookbook_helpers import (
|
|||||||
_llama_cpp_rebuild_cmd,
|
_llama_cpp_rebuild_cmd,
|
||||||
_append_vllm_linux_preflight_lines,
|
_append_vllm_linux_preflight_lines,
|
||||||
_local_tooling_path_export,
|
_local_tooling_path_export,
|
||||||
|
_local_windows_bash_env_prefix,
|
||||||
_pip_install_attempt,
|
_pip_install_attempt,
|
||||||
_pip_install_fallback_chain,
|
_pip_install_fallback_chain,
|
||||||
_ollama_bind_from_cmd,
|
_ollama_bind_from_cmd,
|
||||||
@@ -107,6 +108,70 @@ def test_safe_env_prefix_accepts_powershell_activation_path():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("prefix", "expected"),
|
||||||
|
[
|
||||||
|
("& 'C:\\Users\\me\\venv\\Scripts\\Activate.ps1'", "source /c/Users/me/venv/Scripts/activate"),
|
||||||
|
(r"& C:\Users\me\venv\Scripts\Activate.ps1", "source /c/Users/me/venv/Scripts/activate"),
|
||||||
|
(
|
||||||
|
r"& C:\Users\me\My Envs\venv\Scripts\Activate.ps1",
|
||||||
|
"source '/c/Users/me/My Envs/venv/Scripts/activate'",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"& 'C:\\Users\\me\\My Envs\\venv\\Scripts\\Activate.ps1'",
|
||||||
|
"source '/c/Users/me/My Envs/venv/Scripts/activate'",
|
||||||
|
),
|
||||||
|
(r"& D:/Envs/venv/Scripts/Activate.ps1", "source /d/Envs/venv/Scripts/activate"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_local_windows_bash_env_prefix_converts_powershell_venv_activation(prefix, expected):
|
||||||
|
converted = _local_windows_bash_env_prefix(prefix)
|
||||||
|
|
||||||
|
assert converted == expected
|
||||||
|
assert _safe_env_prefix(converted).startswith('[ -f "')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"prefix",
|
||||||
|
[
|
||||||
|
None,
|
||||||
|
"",
|
||||||
|
"source /home/me/venv/bin/activate",
|
||||||
|
"conda activate qwen35",
|
||||||
|
'eval "$(conda shell.bash hook)" && conda activate qwen35',
|
||||||
|
r"& \\server\share\venv\Scripts\Activate.ps1",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_local_windows_bash_env_prefix_leaves_other_prefixes_unchanged(prefix):
|
||||||
|
assert _local_windows_bash_env_prefix(prefix) == prefix
|
||||||
|
|
||||||
|
|
||||||
|
def test_local_windows_bash_env_prefix_handles_long_whitespace_input():
|
||||||
|
prefix = "\t" * 100_000
|
||||||
|
|
||||||
|
assert _local_windows_bash_env_prefix(prefix) == prefix
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"relative_path",
|
||||||
|
["static/js/cookbookRunning.js", "static/js/cookbookDownload.js"],
|
||||||
|
)
|
||||||
|
def test_primary_windows_venv_emitters_quote_activation_path(relative_path):
|
||||||
|
source = (Path(__file__).resolve().parents[1] / relative_path).read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
assert "'& ' + _psQuote(" in source
|
||||||
|
assert "_psQuote = shared._psQuote;" in source
|
||||||
|
|
||||||
|
|
||||||
|
def test_windows_venv_conversion_stays_scoped_to_local_git_bash_runners():
|
||||||
|
source = (Path(__file__).resolve().parents[1] / "routes/cookbook_routes.py").read_text(encoding="utf-8")
|
||||||
|
guarded_conversion = (
|
||||||
|
"_local_windows_bash_env_prefix(req.env_prefix) if local_windows else req.env_prefix"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert source.count(guarded_conversion) == 2
|
||||||
|
|
||||||
|
|
||||||
def test_validate_local_dir_accepts_external_drive_paths_with_spaces():
|
def test_validate_local_dir_accepts_external_drive_paths_with_spaces():
|
||||||
path = "/Volumes/T7 2TB/AI Models/llamacpp"
|
path = "/Volumes/T7 2TB/AI Models/llamacpp"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user