mirror of
https://github.com/unslothai/unsloth
synced 2026-04-21 13:37:39 +00:00
fix: [Studio] avoid Windows in-use unsloth.exe lock during update
install.ps1 now stages a standalone unsloth.exe in $LOCALAPPDATA\Unsloth Studio\bin and prepends that dir to user PATH so the running launcher during `unsloth studio update` lives outside the venv. setup.ps1 gains an idempotent migration path that parks any pre-existing locked venv launcher (MoveFileEx-allowed) before uv runs and syncs the standalone copy afterwards via SHA256 compare.
This commit is contained in:
parent
14ab6fbfae
commit
d6aff00de3
2 changed files with 82 additions and 1 deletions
25
install.ps1
25
install.ps1
|
|
@ -906,11 +906,34 @@ shell.Run cmd, 0, False
|
|||
}
|
||||
}
|
||||
|
||||
# ── Stage standalone launcher outside the venv ──
|
||||
# Copying the distlib-generated unsloth.exe to $LOCALAPPDATA\Unsloth Studio\bin
|
||||
# (and prepending that dir to User PATH) means future `unsloth studio update`
|
||||
# invocations run from the standalone copy, so pip/uv can freely rewrite
|
||||
# the venv's in-use Scripts\unsloth.exe during dependency upgrades.
|
||||
$VenvScriptsExe = Join-Path $VenvDir "Scripts\unsloth.exe"
|
||||
$StandaloneBinDir = Join-Path $env:LOCALAPPDATA "Unsloth Studio\bin"
|
||||
$StandaloneExe = Join-Path $StandaloneBinDir "unsloth.exe"
|
||||
if (Test-Path $VenvScriptsExe) {
|
||||
if (-not (Test-Path $StandaloneBinDir)) {
|
||||
New-Item -ItemType Directory -Force $StandaloneBinDir | Out-Null
|
||||
}
|
||||
try { Copy-Item -LiteralPath $VenvScriptsExe -Destination $StandaloneExe -Force } catch {}
|
||||
$_userPath = [System.Environment]::GetEnvironmentVariable("Path", "User")
|
||||
if (-not $_userPath -or $_userPath -notlike "*$StandaloneBinDir*") {
|
||||
$_newPath = if ($_userPath) { "$StandaloneBinDir;$_userPath" } else { $StandaloneBinDir }
|
||||
[System.Environment]::SetEnvironmentVariable("Path", $_newPath, "User")
|
||||
Refresh-SessionPath
|
||||
}
|
||||
}
|
||||
|
||||
# ── Run studio setup ──
|
||||
# setup.ps1 will handle installing Git, CMake, Visual Studio Build Tools,
|
||||
# CUDA Toolkit, Node.js, and other dependencies automatically via winget.
|
||||
step "setup" "running unsloth studio setup..."
|
||||
$UnslothExe = Join-Path $VenvDir "Scripts\unsloth.exe"
|
||||
# Prefer the standalone launcher (so the running .exe is not inside the venv)
|
||||
# and fall back to the venv copy if the staging step above did not succeed.
|
||||
$UnslothExe = if (Test-Path $StandaloneExe) { $StandaloneExe } else { $VenvScriptsExe }
|
||||
if (-not (Test-Path $UnslothExe)) {
|
||||
Write-Host "[ERROR] unsloth CLI was not installed correctly." -ForegroundColor Red
|
||||
Write-Host " Expected: $UnslothExe" -ForegroundColor Yellow
|
||||
|
|
|
|||
|
|
@ -1418,6 +1418,22 @@ $ErrorActionPreference = "Continue"
|
|||
$ActivateScript = Join-Path $VenvDir "Scripts\Activate.ps1"
|
||||
. $ActivateScript
|
||||
|
||||
# ── Standalone launcher locations (Windows in-use .exe lock workaround) ──
|
||||
# Windows forbids DeleteFile on a mapped (running) .exe but permits
|
||||
# MoveFileEx. Keeping the CLI launcher outside the venv means pip/uv
|
||||
# never fights a lock when it reinstalls the unsloth wheel during update.
|
||||
$StandaloneBinDir = Join-Path $env:LOCALAPPDATA "Unsloth Studio\bin"
|
||||
$StandaloneExe = Join-Path $StandaloneBinDir "unsloth.exe"
|
||||
$VenvExe = Join-Path $VenvDir "Scripts\unsloth.exe"
|
||||
|
||||
# Sweep stale parked launchers left by prior migrations (safe: not in use)
|
||||
foreach ($_dir in @((Join-Path $VenvDir "Scripts"), $StandaloneBinDir)) {
|
||||
if (Test-Path $_dir) {
|
||||
Get-ChildItem $_dir -Filter "unsloth.exe.old-*" -ErrorAction SilentlyContinue |
|
||||
ForEach-Object { Remove-Item $_.FullName -Force -ErrorAction SilentlyContinue }
|
||||
}
|
||||
}
|
||||
|
||||
# Try to use uv (much faster than pip), fall back to pip if unavailable
|
||||
$UseUv = $false
|
||||
if (Get-Command uv -ErrorAction SilentlyContinue) {
|
||||
|
|
@ -1490,6 +1506,15 @@ if ($env:SKIP_STUDIO_BASE -ne "1" -and $env:STUDIO_LOCAL_INSTALL -ne "1") {
|
|||
|
||||
if (-not $SkipPythonDeps) {
|
||||
|
||||
# Migration: if there is no standalone launcher yet AND install.ps1 is not
|
||||
# the caller (SKIP_STUDIO_BASE != 1 => uv will reinstall the unsloth wheel),
|
||||
# park the (possibly-running) venv launcher so uv's reinstall succeeds.
|
||||
# Fresh-install flow leaves $VenvExe alone: install.ps1 creates the
|
||||
# standalone copy before invoking setup, so this branch is a no-op there.
|
||||
if (-not (Test-Path $StandaloneExe) -and (Test-Path $VenvExe) -and $env:SKIP_STUDIO_BASE -ne "1") {
|
||||
try { Move-Item -LiteralPath $VenvExe -Destination "$VenvExe.old-$PID" -Force } catch {}
|
||||
}
|
||||
|
||||
if ($script:UnslothVerbose) {
|
||||
Fast-Install --upgrade pip
|
||||
} else {
|
||||
|
|
@ -1587,6 +1612,39 @@ if ($stackExit -ne 0) {
|
|||
$ErrorActionPreference = $prevEAP
|
||||
}
|
||||
|
||||
# ── Sync standalone launcher and prepend its dir to user PATH ──
|
||||
# Runs unconditionally (both fast-path and reinstall branches) so that users
|
||||
# on an old layout migrate on the first update where no standalone exists.
|
||||
# Copy path uses park-and-replace (MoveFileEx) so even a currently-running
|
||||
# $StandaloneExe can be swapped when distlib's launcher bytes actually change.
|
||||
if (Test-Path $VenvExe) {
|
||||
if (-not (Test-Path $StandaloneBinDir)) {
|
||||
New-Item -ItemType Directory -Force $StandaloneBinDir | Out-Null
|
||||
}
|
||||
$needsCopy = -not (Test-Path $StandaloneExe)
|
||||
if (-not $needsCopy) {
|
||||
try {
|
||||
$needsCopy = (Get-FileHash $VenvExe).Hash -ne (Get-FileHash $StandaloneExe).Hash
|
||||
} catch {
|
||||
$needsCopy = $true
|
||||
}
|
||||
}
|
||||
if ($needsCopy) {
|
||||
if (Test-Path $StandaloneExe) {
|
||||
try { Move-Item -LiteralPath $StandaloneExe -Destination "$StandaloneExe.old-$PID" -Force } catch {}
|
||||
}
|
||||
try { Copy-Item -LiteralPath $VenvExe -Destination $StandaloneExe -Force } catch {}
|
||||
}
|
||||
}
|
||||
|
||||
if (Test-Path $StandaloneExe) {
|
||||
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
|
||||
if (-not $userPath -or $userPath -notlike "*$StandaloneBinDir*") {
|
||||
$newPath = if ($userPath) { "$StandaloneBinDir;$userPath" } else { $StandaloneBinDir }
|
||||
[Environment]::SetEnvironmentVariable('Path', $newPath, 'User')
|
||||
}
|
||||
}
|
||||
|
||||
# ── Pre-install transformers 5.x into .venv_t5_530/ and .venv_t5_550/ ──
|
||||
# Runs outside the deps fast-path gate so that upgrades from the legacy
|
||||
# single .venv_t5 are always migrated to the tiered layout.
|
||||
|
|
|
|||
Loading…
Reference in a new issue