Fix Windows install when paths contain spaces or Python 3.14 is on PATH (#5201)
* fix(studio): use py.exe to detect supported Python on Windows Description: The previous detection looked at `python --version` on PATH and hard-failed if the resolved Python wasn't 3.11-3.13. On systems where Python 3.14 sits ahead of 3.13 in PATH order, this aborted the installer even though a supported interpreter was installed. Prefer the py.exe launcher and probe `py -3.13`, `py -3.12`, `py -3.11` in turn. Fall back to `python --version` only when py.exe is absent, and surface a clearer error when no supported version can be found via either path. * Studio: consolidate Windows studio overlay into single Tauri-gated block Replace the in-file sentinel hotfix and the unconditional file-copy overlay with a single block gated on $TauriMode. Hash-compare makes re-runs no-ops, removing the sentinel-clobbering bug that occurred when the second copy path overwrote the marker without re-adding it. Non-Tauri --local installs no longer need a copy overlay: the editable install above (uv pip install -e $RepoRoot --no-deps) makes _PACKAGE_ROOT in unsloth_cli/commands/studio.py resolve to the repo source tree via PEP 660 __file__-relative resolution, so `unsloth studio setup` finds the local setup.ps1 and install_python_stack.py without any file copying. Plain PyPI installs invoked from a checked-out repo directory are also no longer silently overlaid from cwd. * fix(studio): work around uv space-in-path truncation on Windows uv 0.11.x truncates `-c <path>` and `-r <path>` arguments at the first space, breaking installs on Windows when the venv or repo sits under a path containing spaces (e.g. C:\Users\First Last\...). Pass paths through GetShortPathNameW to convert to 8.3 short form before handing them to uv. Plain pip is unaffected and keeps the original long path. No-op on Linux/Mac (gated on IS_WINDOWS and on the path actually containing a space). * Refactor Python stack overlay logic in install.ps1 Refactor overlay logic for Python stack installation and improve handling of missing target directories. * Update Python installation logic in setup.ps1
This commit is contained in:
parent
df3a205726
commit
daf0889804
3 changed files with 123 additions and 45 deletions
|
|
@ -359,6 +359,27 @@ def _ensure_rocm_torch() -> None:
|
|||
)
|
||||
|
||||
|
||||
def _uv_safe_path(path: object) -> str:
|
||||
# uv 0.11.x: `-c <path with space>` truncates at the space; use 8.3 short form.
|
||||
s = str(path)
|
||||
if not IS_WINDOWS or " " not in s:
|
||||
return s
|
||||
try:
|
||||
import ctypes
|
||||
from ctypes import wintypes
|
||||
|
||||
get_short = ctypes.windll.kernel32.GetShortPathNameW
|
||||
get_short.argtypes = [wintypes.LPCWSTR, wintypes.LPWSTR, wintypes.DWORD]
|
||||
get_short.restype = wintypes.DWORD
|
||||
buf = ctypes.create_unicode_buffer(32768)
|
||||
rc = get_short(s, buf, 32768)
|
||||
if 0 < rc < 32768 and " " not in buf.value:
|
||||
return buf.value
|
||||
except Exception:
|
||||
pass
|
||||
return s
|
||||
|
||||
|
||||
def _windows_hidden_subprocess_kwargs() -> dict[str, object]:
|
||||
"""Return Windows-only subprocess kwargs that suppress console windows."""
|
||||
if not IS_WINDOWS:
|
||||
|
|
@ -751,14 +772,16 @@ def pip_install_try(
|
|||
"""Like pip_install but returns False on failure instead of exiting.
|
||||
For optional installs with a follow-up fallback.
|
||||
"""
|
||||
constraint_args: list[str] = []
|
||||
constraint_args_pip: list[str] = []
|
||||
constraint_args_uv: list[str] = []
|
||||
if constrain and CONSTRAINTS.is_file():
|
||||
constraint_args = ["-c", str(CONSTRAINTS)]
|
||||
constraint_args_pip = ["-c", str(CONSTRAINTS)]
|
||||
constraint_args_uv = ["-c", _uv_safe_path(CONSTRAINTS)]
|
||||
|
||||
if USE_UV:
|
||||
cmd = _build_uv_cmd(args) + constraint_args
|
||||
cmd = _build_uv_cmd(args) + constraint_args_uv
|
||||
else:
|
||||
cmd = _build_pip_cmd(args) + constraint_args
|
||||
cmd = _build_pip_cmd(args) + constraint_args_pip
|
||||
|
||||
if VERBOSE:
|
||||
_step(_LABEL, f"{label}...", _dim)
|
||||
|
|
@ -781,9 +804,11 @@ def pip_install(
|
|||
constrain: bool = True,
|
||||
) -> None:
|
||||
"""Build and run a pip install command (uses uv when available, falls back to pip)."""
|
||||
constraint_args: list[str] = []
|
||||
constraint_args_pip: list[str] = []
|
||||
constraint_args_uv: list[str] = []
|
||||
if constrain and CONSTRAINTS.is_file():
|
||||
constraint_args = ["-c", str(CONSTRAINTS)]
|
||||
constraint_args_pip = ["-c", str(CONSTRAINTS)]
|
||||
constraint_args_uv = ["-c", _uv_safe_path(CONSTRAINTS)]
|
||||
|
||||
actual_req = req
|
||||
temp_reqs: list[Path] = []
|
||||
|
|
@ -793,13 +818,15 @@ def pip_install(
|
|||
if actual_req is not None and NO_TORCH and NO_TORCH_SKIP_PACKAGES:
|
||||
actual_req = _filter_requirements(actual_req, NO_TORCH_SKIP_PACKAGES)
|
||||
temp_reqs.append(actual_req)
|
||||
req_args: list[str] = []
|
||||
req_args_pip: list[str] = []
|
||||
req_args_uv: list[str] = []
|
||||
if actual_req is not None:
|
||||
req_args = ["-r", str(actual_req)]
|
||||
req_args_pip = ["-r", str(actual_req)]
|
||||
req_args_uv = ["-r", _uv_safe_path(actual_req)]
|
||||
|
||||
try:
|
||||
if USE_UV:
|
||||
uv_cmd = _build_uv_cmd(args) + constraint_args + req_args
|
||||
uv_cmd = _build_uv_cmd(args) + constraint_args_uv + req_args_uv
|
||||
if VERBOSE:
|
||||
print(f" {label}...")
|
||||
result = subprocess.run(
|
||||
|
|
@ -814,7 +841,7 @@ def pip_install(
|
|||
if result.stdout:
|
||||
print(result.stdout.decode(errors = "replace"))
|
||||
|
||||
pip_cmd = _build_pip_cmd(args) + constraint_args + req_args
|
||||
pip_cmd = _build_pip_cmd(args) + constraint_args_pip + req_args_pip
|
||||
run(f"{label} (pip)" if USE_UV else label, pip_cmd)
|
||||
finally:
|
||||
for temp_req in temp_reqs:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue