From b7fa2c8718371cd2f60e54a0bc8580a74d50b51b Mon Sep 17 00:00:00 2001 From: Daniel Han Date: Mon, 27 Apr 2026 00:08:07 +0000 Subject: [PATCH] comments: condense multiline blocks added by this PR Across the 27-cycle review process, comments accumulated as multiline blocks explaining each fix's history (cycle numbers, prior bugs, reviewer rationale). Compress every block to 1-2 lines that capture just the WHY, dropping cycle references and history that belongs in the PR description / commit log instead. Net: 268 deletions / 124 insertions (-144 lines) of comments only. Behavior unchanged. Verified: bash -n, pwsh parser, python ast.parse, cargo check all pass. --- install.ps1 | 91 +++++------- install.sh | 138 ++++++------------- studio/backend/core/inference/llama_cpp.py | 22 +-- studio/backend/main.py | 9 +- studio/backend/run.py | 11 +- studio/backend/utils/models/model_config.py | 1 - studio/backend/utils/paths/storage_roots.py | 19 +-- studio/backend/utils/transformers_version.py | 1 - studio/setup.ps1 | 21 +-- studio/setup.sh | 19 +-- studio/src-tauri/src/desktop_auth.rs | 6 +- studio/src-tauri/src/install.rs | 6 +- studio/src-tauri/src/process.rs | 9 +- studio/src-tauri/src/update.rs | 5 +- unsloth_cli/commands/studio.py | 34 ++--- 15 files changed, 124 insertions(+), 268 deletions(-) diff --git a/install.ps1 b/install.ps1 index 9db4856034..8d8338aeed 100644 --- a/install.ps1 +++ b/install.ps1 @@ -70,10 +70,8 @@ function Install-UnslothStudio { # Resolve install destinations. Priority: env vars, then USERPROFILE-redirect, then default. $envOverride = if ($env:UNSLOTH_STUDIO_HOME) { $env:UNSLOTH_STUDIO_HOME } elseif ($env:STUDIO_HOME) { $env:STUDIO_HOME } else { $null } - # Custom Studio roots are not supported in Tauri-mode installs because - # the desktop app still resolves the legacy %USERPROFILE%\.unsloth\studio - # path. Pass through when the override resolves to that legacy default - # (the desktop app already uses it); fail fast otherwise. + # Custom Studio roots are not supported with --tauri (desktop app still + # resolves %USERPROFILE%\.unsloth\studio). Pass through if override == legacy. if ($TauriMode -and $envOverride) { $_tauriOverride = $envOverride if ($_tauriOverride -eq "~" -or $_tauriOverride -like "~/*" -or $_tauriOverride -like "~\*") { @@ -86,8 +84,7 @@ function Install-UnslothStudio { try { $_legacyTauriRoot = [System.IO.Path]::GetFullPath($_legacyTauriRoot) } catch {} - # Strip trailing separators so a legacy override with a trailing - # backslash or slash still matches the legacy root. + # Strip trailing separators so ".../studio\" matches ".../studio". $_trimSeps = @( [System.IO.Path]::DirectorySeparatorChar, [System.IO.Path]::AltDirectorySeparatorChar @@ -106,35 +103,29 @@ function Install-UnslothStudio { $defaultProfile = $null try { $defaultProfile = [Environment]::GetFolderPath("UserProfile") } catch {} - # Default DataDir uses LOCALAPPDATA. Guard the lookup: in service / CI - # contexts LOCALAPPDATA may be unset, and Join-Path under - # $ErrorActionPreference='Stop' would otherwise abort the installer. + # LOCALAPPDATA may be unset in service / CI contexts; Join-Path would abort + # under ErrorActionPreference=Stop without this guard. $defaultDataDir = if ($env:LOCALAPPDATA -and -not [string]::IsNullOrWhiteSpace($env:LOCALAPPDATA)) { Join-Path $env:LOCALAPPDATA "Unsloth Studio" } else { $null } if ($envOverride) { - # Expand a leading '~' to $HOME / $env:USERPROFILE because env-var - # values are not subject to tilde expansion in any shell. + # Tilde expansion: env vars aren't subject to it when quoted on assignment. if ($envOverride -eq "~" -or $envOverride -like "~/*" -or $envOverride -like "~\*") { $envOverride = (Join-Path $env:USERPROFILE $envOverride.Substring(1).TrimStart('/','\')) } try { - # New-Item has no -LiteralPath in PowerShell 5.1 and -Path treats - # square brackets as wildcards. Use the .NET API so a custom root - # like C:\workspaces\studio[abc] is handled literally. + # .NET API: New-Item -Path treats brackets as wildcards and has no + # -LiteralPath in PS 5.1, so a root like C:\studio[abc] would fail. [System.IO.Directory]::CreateDirectory($envOverride) | Out-Null $StudioHome = (Resolve-Path -LiteralPath $envOverride).Path } catch { Write-Host "ERROR: STUDIO_HOME=$envOverride cannot be created or accessed." -ForegroundColor Red exit 1 } - # Default ToString() form already produces a unique GUID string. $probe = Join-Path $StudioHome (".unsloth-write-probe-" + [guid]::NewGuid()) try { - # WriteAllText is literal-path safe and closes the file handle - # before the Remove-Item below; New-Item -Path would fail on - # bracketed roots (wildcard expansion) just like the dir case. + # WriteAllText: literal-path safe + closes handle so Remove-Item works. [System.IO.File]::WriteAllText($probe, "") Remove-Item -LiteralPath $probe -Force -ErrorAction SilentlyContinue } catch { @@ -415,8 +406,7 @@ function Install-UnslothStudio { # This prevents runtime variable expansion for paths containing '$'. $SingleQuotedExePath = $UnslothExePath -replace "'", "''" - # $StudioDataDir was resolved at install start (default = LOCALAPPDATA\Unsloth Studio, - # or $StudioHome\share when UNSLOTH_STUDIO_HOME / STUDIO_HOME is set). + # $StudioDataDir = LOCALAPPDATA\Unsloth Studio, or $StudioHome\share in env-mode. if (-not $StudioDataDir -or [string]::IsNullOrWhiteSpace($StudioDataDir)) { substep "DataDir path unavailable; skipped shortcut creation" "Yellow" return @@ -457,18 +447,14 @@ function Install-UnslothStudio { [System.IO.Directory]::CreateDirectory($appDir) | Out-Null } - # Persist UNSLOTH_STUDIO_HOME inside the generated launcher when - # in env-override mode. Lets fresh shells launch Studio without - # the user re-exporting the env var. Default installs get an - # empty string here so behavior matches today exactly. + # Env-mode: persist UNSLOTH_STUDIO_HOME (and llama path) in the + # launcher so fresh shells don't need to re-export. Default installs + # get an empty prefix so behavior matches pre-PR exactly. $studioHomeExport = if ($StudioRedirectMode -eq 'env') { - # Mirror setup.ps1: when an env override happens to equal the - # legacy default, llama.cpp still lives at ~/.unsloth/llama.cpp. - # Keep the persisted UNSLOTH_LLAMA_CPP_PATH consistent with that. + # When override == legacy default, llama.cpp stays at + # ~/.unsloth/llama.cpp (one shared build). Canonicalize the + # legacy side so the comparison survives path normalization. $_legacyStudio = Join-Path $env:USERPROFILE ".unsloth\studio" - # Canonicalize the legacy side (when it exists) to match the - # resolved $StudioHome from the env-override path. This keeps - # the legacy-equality check stable across path normalization. if (Test-Path -LiteralPath $_legacyStudio -PathType Container) { $_legacyStudio = (Resolve-Path -LiteralPath $_legacyStudio).Path } @@ -479,9 +465,7 @@ function Install-UnslothStudio { } $_sq = $StudioHome -replace "'", "''" $_llama = $_llamaPath -replace "'", "''" - # UNSLOTH_LLAMA_CPP_PATH is a pre-existing user-controlled - # llama.cpp directory override. Only default it when the - # caller has not already set one in their environment. + # UNSLOTH_LLAMA_CPP_PATH is a pre-existing user override; only default if unset. "`$env:UNSLOTH_STUDIO_HOME = '$_sq'`nif (-not `$env:UNSLOTH_LLAMA_CPP_PATH) {`n `$env:UNSLOTH_LLAMA_CPP_PATH = '$_llama'`n}`n" } else { "" } @@ -707,10 +691,8 @@ shell.Run cmd, 0, False } } - # Env-override installs are workspace-scoped: skip persistent - # Desktop / Start Menu .lnk shortcuts that would point at a - # path the user may later delete. The launcher scripts and - # icon written above ARE kept regardless of mode. + # Env-mode: skip persistent Desktop / Start Menu .lnk shortcuts + # that may point at a deleted workspace; launcher + icon stay. if ($StudioRedirectMode -eq 'env') { substep "wrote launcher at $launcherPs1 (persistent shortcuts skipped in env-override mode)" return @@ -909,8 +891,7 @@ shell.Run cmd, 0, False # a version string back to a conda interpreter. Write-TauriLog "STEP" "Creating virtual environment" if (-not (Test-Path -LiteralPath $StudioHome)) { - # New-Item has no -LiteralPath parameter; use the .NET API to honor - # bracket characters in custom Studio roots. + # .NET API: New-Item -Path treats brackets as wildcards. [System.IO.Directory]::CreateDirectory($StudioHome) | Out-Null } @@ -946,11 +927,9 @@ shell.Run cmd, 0, False $StudioRedirectMode -ne 'env' ` -and (Test-Path -LiteralPath (Join-Path $env:USERPROFILE "unsloth_studio\Scripts\python.exe")) ) { - # CWD-relative venv from old install.ps1 -- migrate to absolute path. - # Skip in env-override mode: workspace-scoped installs must not - # move the user's pre-existing default-install venv away from - # %USERPROFILE%, which would break their legacy default install - # and contaminate the workspace root. + # CWD-relative venv from old install.ps1 -> migrate to absolute path. + # Skip in env-mode so we don't relocate the default-install venv into + # the workspace root. $CwdVenv = Join-Path $env:USERPROFILE "unsloth_studio" substep "found CWD-relative Studio environment, migrating to $VenvDir..." Move-Item -LiteralPath $CwdVenv -Destination $VenvDir -Force @@ -1225,9 +1204,8 @@ shell.Run cmd, 0, False # Use 'studio setup' (not 'studio update') because 'update' pops # SKIP_STUDIO_BASE, which would cause redundant package reinstallation # and bypass the fast-path version check from PR #4667. - # Only propagate UNSLOTH_STUDIO_HOME for actual env-override installs; - # otherwise default-install setups would mistakenly take the env path - # (placing llama.cpp under $StudioHome\llama.cpp instead of legacy). + # Propagate UNSLOTH_STUDIO_HOME only for env-override installs; otherwise + # an inherited value would put llama.cpp in the wrong place. $previousUnslothStudioHome = $env:UNSLOTH_STUDIO_HOME $hadPreviousUnslothStudioHome = ($null -ne $previousUnslothStudioHome) if ($StudioRedirectMode -eq 'env') { @@ -1314,10 +1292,8 @@ shell.Run cmd, 0, False Write-Host " Launch unsloth studio directly via '$UnslothExe' until the next successful install." -ForegroundColor Yellow } } - # Only add to PATH when the launcher actually exists on disk. - # Skip persistent registry PATH modification when env-override is active: - # the workspace path may be deleted, and we should not pollute the user's - # persistent PATH with it. Caller is expected to add $StudioHome\bin manually. + # Add to PATH only when launcher exists. Env-mode: session-only export, + # no registry change (workspace path may be deleted later). $pathAdded = $false if (Test-Path -LiteralPath $ShimExe) { if ($StudioRedirectMode -eq 'env') { @@ -1332,9 +1308,8 @@ shell.Run cmd, 0, False } Refresh-SessionPath # sync current session with registry - # Re-prepend the env-override shim AFTER Refresh-SessionPath, otherwise - # a previously-installed legacy User PATH entry would win precedence - # (Refresh rebuilds Path as Machine > User > current $env:Path). + # Re-prepend env-mode shim AFTER Refresh-SessionPath; otherwise a legacy + # User PATH entry (Machine > User > current $env:Path) would win. if ($StudioRedirectMode -eq 'env' -and (Test-Path -LiteralPath $ShimExe)) { $env:Path = "$ShimDir;$env:Path" } @@ -1345,9 +1320,7 @@ shell.Run cmd, 0, False return } - # New-StudioShortcuts itself gates the persistent Desktop / Start - # Menu .lnk shortcuts based on $StudioRedirectMode; the launcher - # script and icon are always written so env-mode shims still resolve. + # New-StudioShortcuts gates the .lnk shortcuts on env-mode internally. New-StudioShortcuts -UnslothExePath $UnslothExe # Launch studio automatically in interactive terminals; @@ -1358,9 +1331,7 @@ shell.Run cmd, 0, False } else { step "launch" "manual commands:" if ($StudioRedirectMode -eq 'env') { - # Env-override mode skips persistent registry PATH update, so - # `unsloth` may not resolve in a fresh shell. Print the - # absolute shim path so callers can launch directly. + # Env-mode skips registry PATH; print the absolute shim path. $_shim = Join-Path $StudioHome "bin\unsloth.exe" substep "& `"$_shim`" studio -H 0.0.0.0 -p 8888" substep "or activate env first:" diff --git a/install.sh b/install.sh index 5e42b4f58d..54e060dfc5 100755 --- a/install.sh +++ b/install.sh @@ -71,13 +71,8 @@ if [ "$_VERBOSE" = true ]; then export UNSLOTH_VERBOSE=1 fi -# Custom Studio roots are not supported in Tauri-mode installs because the -# desktop app still resolves the legacy ~/.unsloth/studio path. Producing -# a custom-root --tauri install would yield a desktop app that cannot -# locate the freshly installed binary or auth secret. Fail fast for real -# custom roots; pass through when the override resolves to the legacy -# default ($HOME/.unsloth/studio), since that is exactly what the desktop -# app already uses. +# Custom Studio roots are not supported with --tauri (desktop app still +# resolves ~/.unsloth/studio). Pass through if the override == legacy default. if [ "$TAURI_MODE" = true ]; then _tauri_override="${UNSLOTH_STUDIO_HOME:-${STUDIO_HOME:-}}" if [ -n "$_tauri_override" ]; then @@ -85,25 +80,20 @@ if [ "$TAURI_MODE" = true ]; then "~") _tauri_override="$HOME" ;; "~/"*) _tauri_override="$HOME/${_tauri_override#'~/'}" ;; esac - # Resolve symlinks/relative paths if the dir exists, else use as-is. - # CDPATH= prevents `cd` from echoing the resolved path on stdout when - # the user has CDPATH set in their environment. -P resolves symlinks. + # Canonicalize both sides (CDPATH=, -P) so a CDPATH-set env or + # symlinked $HOME doesn't break the legacy-equality comparison. if [ -d "$_tauri_override" ]; then _tauri_override_abs=$(CDPATH= cd -P -- "$_tauri_override" 2>/dev/null && pwd -P) \ || _tauri_override_abs="$_tauri_override" else _tauri_override_abs="$_tauri_override" fi - # Strip trailing separators so a legacy override with a trailing - # slash (".../studio/") still matches the legacy root (".../studio"). + # Strip trailing separators so ".../studio/" matches ".../studio". while [ "$_tauri_override_abs" != "/" ] \ && [ "${_tauri_override_abs%/}" != "$_tauri_override_abs" ]; do _tauri_override_abs=${_tauri_override_abs%/} done _tauri_legacy_root="$HOME/.unsloth/studio" - # Apply the same physical-path canonicalization to the legacy root - # so a symlinked $HOME (e.g. /home/alice -> /u/alice) doesn't make - # the comparison fail when both sides point at the same directory. if [ -d "$_tauri_legacy_root" ]; then _tauri_legacy_root=$(CDPATH= cd -P -- "$_tauri_legacy_root" 2>/dev/null && pwd -P) \ || _tauri_legacy_root="$HOME/.unsloth/studio" @@ -220,14 +210,11 @@ tauri_log() { PYTHON_VERSION="" # resolved after platform detection -# Resolve install destinations. Priority: env vars, then HOME-redirect, then default. -# Best-effort HOME-redirect detection: skipped on hosts without getent/dscl. +# Resolve install destinations: env override, HOME-redirect (best-effort +# via getent/dscl), or default. _resolve_studio_destinations() { _override="${UNSLOTH_STUDIO_HOME:-${STUDIO_HOME:-}}" - # Expand leading ~ / ~/path to $HOME because env vars are not subject to - # tilde expansion when set with quotes around the value. Quote the - # ~/ inside the prefix-removal so the shell does not tilde-expand it - # back to $HOME/ before matching. + # Tilde expansion: env vars are not subject to it when quoted on assignment. case "$_override" in "~") _override="$HOME" ;; "~/"*) _override="$HOME/${_override#'~/'}" ;; @@ -235,8 +222,6 @@ _resolve_studio_destinations() { if [ -n "$_override" ]; then mkdir -p -- "$_override" 2>/dev/null || { echo "ERROR: STUDIO_HOME=$_override cannot be created." >&2; exit 1; } [ -w "$_override" ] || { echo "ERROR: STUDIO_HOME=$_override is not writable." >&2; exit 1; } - # CDPATH= prevents `cd` from echoing a CDPATH-prefixed path on stdout - # if the user has CDPATH set; -P / pwd -P canonicalizes symlinks. STUDIO_HOME="$(CDPATH= cd -P -- "$_override" && pwd -P)" || exit 1 DATA_DIR="$STUDIO_HOME/share" _LOCAL_BIN="$STUDIO_HOME/bin" @@ -381,10 +366,8 @@ create_studio_shortcuts() { mkdir -p "$_css_data_dir" # ── Write launcher script ── - # Heredoc is single-quoted (no $-expansion at write time). The - # @@DATA_DIR@@ placeholder below is substituted via sed after the - # heredoc is written so the runtime launcher reads studio.conf - # from the same DATA_DIR install.sh resolved. + # Single-quoted heredoc; @@DATA_DIR@@ is substituted via sed below so + # the runtime launcher reads studio.conf from the resolved DATA_DIR. cat > "$_css_launcher" << 'LAUNCHER_EOF' #!/usr/bin/env bash # Unsloth Studio Launcher @@ -638,33 +621,20 @@ else fi LAUNCHER_EOF - # Substitute @@DATA_DIR@@. Default and HOME-redirect installs keep the - # legacy runtime form (DATA_DIR="$HOME/.local/share/unsloth") so a later - # shell with a different $HOME still resolves DATA_DIR correctly -- - # byte-identical to pre-PR. Only env-mode installs bake the resolved - # absolute path because their root is fixed at install time. - # Portable in-place edit: redirect to tempfile, then mv. Avoids the - # GNU-vs-BSD-vs-BusyBox `sed -i` divergence (per Gemini review feedback). + # Default / HOME-redirect installs replace the placeholder line with the + # legacy literal (DATA_DIR="$HOME/.local/share/unsloth") so a later shell + # with a different $HOME still resolves correctly -- byte-identical to + # pre-PR. Env-mode installs bake the resolved absolute path because their + # root is fixed at install time. if [ "$_STUDIO_HOME_REDIRECT" = "env" ]; then - # Two-stage escape so path metacharacters survive sed and shell - # single-quoted embedding. Verified end-to-end with apostrophes, - # spaces, &, |, $; do not "simplify" to fewer/more backslashes. - # 1) ' -> '\'' for safe single-quote shell embedding. - # The shell pattern "s/'/'\\''/g" passes "s/'/'\''/g" to sed - # (\\ -> \). sed's replacement '\'' contains a literal - # backslash-quote-quote, producing the canonical close-quote / - # escaped-quote / open-quote sequence on output. Result: - # `a b's` -> `a b'\''s`. - # 2) For the value to land inside a sed s|...|VALUE|g call below, - # escape \, &, and | (the chosen delimiter) so they survive sed - # replacement. Result of stage 2: `a b'\''s` -> `a b'\\''s`. + # Two-stage escape: (1) `'` -> `'\''` for shell single-quote embedding, + # (2) backslash/&/| escape so the value survives the s|...|VALUE| sed + # below. Verified end-to-end with apostrophes, spaces, &, |, $. _sq_escaped=$(printf '%s' "$DATA_DIR" | sed "s/'/'\\\\''/g") _sed_safe=$(printf '%s' "$_sq_escaped" | sed 's/[\\&|]/\\&/g') sed "s|@@DATA_DIR@@|$_sed_safe|g" "$_css_launcher" > "$_css_launcher.tmp" \ && mv "$_css_launcher.tmp" "$_css_launcher" else - # Replace the placeholder line entirely with the legacy literal - # so the launcher reads $HOME at runtime, not install time. sed "s|DATA_DIR='@@DATA_DIR@@'|DATA_DIR=\"\$HOME/.local/share/unsloth\"|" \ "$_css_launcher" > "$_css_launcher.tmp" \ && mv "$_css_launcher.tmp" "$_css_launcher" @@ -672,30 +642,16 @@ LAUNCHER_EOF chmod +x "$_css_launcher" - # Write the exe path to a separate conf file sourced by the launcher. - # Single-quote wrapping with the standard `'\''` escape (shell pattern - # "s/'/'\\''/g" -> sed "s/'/'\''/g"; replacement `'\''` produces - # close-quote, backslash-quote, open-quote on output). No sed templating - # is applied to studio.conf, so no second-stage backslash/&/| escaping - # is needed here. + # studio.conf: exe path + (env-mode only) persisted env vars so fresh + # shells launch the right install without re-exporting. _css_quoted_exe=$(printf '%s' "$_css_exe" | sed "s/'/'\\\\''/g") { printf '%s\n' "UNSLOTH_EXE='$_css_quoted_exe'" - # Persist UNSLOTH_STUDIO_HOME for env-override installs so the launcher, - # CLI, and backend pick up the same root in fresh shells where the user - # did not re-export the env var. Also persist UNSLOTH_LLAMA_CPP_PATH so - # unsloth-zoo's import-time LLAMA_CPP_DEFAULT_DIR binding finds the - # custom-root build. Default installs do NOT get these lines so the - # legacy ~/.unsloth/studio + ~/.unsloth/llama.cpp resolution stands. if [ "$_STUDIO_HOME_REDIRECT" = "env" ]; then - # Mirror setup.sh's legacy-equality check: when an env override - # happens to point at the legacy default, llama.cpp still lives - # at ~/.unsloth/llama.cpp (one shared build across legacy - # installs) -- keep UNSLOTH_LLAMA_CPP_PATH consistent with that. - # $STUDIO_HOME is canonicalized (cycle 24) but $HOME/.unsloth/studio - # is still logical. Canonicalize the legacy side too so a symlinked - # $HOME doesn't make the comparison fail when both point at the - # same dir. + # When an override resolves to the legacy default, llama.cpp + # still lives at ~/.unsloth/llama.cpp (one shared build). + # Canonicalize the legacy side so a symlinked $HOME doesn't + # break the comparison. _css_legacy_studio="$HOME/.unsloth/studio" if [ -d "$_css_legacy_studio" ]; then _css_legacy_studio=$(CDPATH= cd -P -- "$_css_legacy_studio" 2>/dev/null && pwd -P) \ @@ -709,9 +665,8 @@ LAUNCHER_EOF _css_quoted_home=$(printf '%s' "$STUDIO_HOME" | sed "s/'/'\\\\''/g") _css_quoted_llama=$(printf '%s' "$_css_llama_path" | sed "s/'/'\\\\''/g") printf '%s\n' "export UNSLOTH_STUDIO_HOME='$_css_quoted_home'" - # UNSLOTH_LLAMA_CPP_PATH is a pre-existing custom-llama.cpp-dir - # override the Python backend / unsloth-zoo respect. Only default - # it when the caller has not already set one in their shell. + # UNSLOTH_LLAMA_CPP_PATH is a pre-existing user-controlled + # llama.cpp dir override; only default it if unset. printf '%s\n' 'if [ -z "${UNSLOTH_LLAMA_CPP_PATH:-}" ]; then' printf '%s\n' " export UNSLOTH_LLAMA_CPP_PATH='$_css_quoted_llama'" printf '%s\n' 'fi' @@ -762,11 +717,9 @@ LAUNCHER_EOF fi # ── Platform-specific shortcuts ── - # Env-override installs are workspace-scoped: skip persistent - # desktop / Start-Menu / dock launchers that would point at a path - # the user may later delete. The runtime launcher + studio.conf + - # icon written above are still essential for the env-mode shim and - # ARE kept regardless of mode. + # Env-mode installs are workspace-scoped: skip persistent desktop / + # Start-Menu / dock launchers that may point at a deleted workspace. + # Runtime launcher + studio.conf + icon are still written above. if [ "$_STUDIO_HOME_REDIRECT" = "env" ]; then substep "wrote launcher at $_css_launcher (persistent shortcuts skipped in env-override mode)" return 0 @@ -849,12 +802,9 @@ DESKTOP_EOF PLIST_EOF - # Executable stub - # The stub embeds the resolved launcher path. Use a single-quoted - # heredoc + post-write sed substitution + single-quoted shell - # embedding so any path metacharacters ($, ", `, &, |, \, ') in - # $_css_data_dir do not expand at .app launch time. Same pattern - # as the launch-studio.sh @@DATA_DIR@@ substitution above. + # Executable stub: same single-quoted-heredoc + sed-substitute + # pattern as launch-studio.sh so $-vars in $_css_data_dir don't + # expand at .app launch time. _css_sq_dir=$(printf '%s' "$_css_data_dir" | sed "s/'/'\\\\''/g") _css_sed_dir=$(printf '%s' "$_css_sq_dir" | sed 's/[\\&|]/\\&/g') cat > "$_css_macos_dir/launch-studio" << 'STUB_EOF' @@ -1807,10 +1757,8 @@ _SKIP_FRONTEND=0 if [ "$TAURI_MODE" = true ]; then _SKIP_FRONTEND=1 fi -# Helper: prepend UNSLOTH_STUDIO_HOME=$STUDIO_HOME to "$@" only for actual -# env-override installs. Avoids word-splitting on whitespace paths -- a -# string-form '_STUDIO_ENV_FOR_SETUP="UNSLOTH_STUDIO_HOME=$STUDIO_HOME"' -# would split '/tmp/space path' into separate argv entries. +# Prepend UNSLOTH_STUDIO_HOME=$STUDIO_HOME to "$@" for env-override installs +# without word-splitting on whitespace paths. _run_setup_with_studio_home() { if [ "$_STUDIO_HOME_REDIRECT" = "env" ]; then UNSLOTH_STUDIO_HOME="$STUDIO_HOME" "$@" @@ -1844,9 +1792,8 @@ else fi # ── Make 'unsloth' available via $_LOCAL_BIN (resolved earlier) ── -# When STUDIO_HOME / UNSLOTH_STUDIO_HOME is used, $_LOCAL_BIN is workspace-scoped -# ($STUDIO_HOME/bin) and we skip the shell-rc PATH append so we do not pollute -# the user's profile with a path that may be deleted with the workspace. +# Env-mode: $_LOCAL_BIN is $STUDIO_HOME/bin; skip shell-rc PATH append so we +# don't pollute the user's profile with a workspace-scoped path. mkdir -p "$_LOCAL_BIN" ln -sf "$VENV_DIR/bin/unsloth" "$_LOCAL_BIN/unsloth" @@ -1879,9 +1826,8 @@ case ":$PATH:" in esac # Non-Tauri installs keep shortcuts even if setup reports failure. -# create_studio_shortcuts itself gates the persistent desktop/menu -# launchers based on $_STUDIO_HOME_REDIRECT; the runtime launcher + -# studio.conf + icon are always written so env-mode shims still resolve. +# create_studio_shortcuts gates persistent menu shortcuts on env-mode; +# launcher + studio.conf + icon are always written. if [ "$TAURI_MODE" != true ]; then create_studio_shortcuts "$VENV_ABS_BIN/unsloth" "$OS" fi @@ -1925,15 +1871,11 @@ if [ -t 1 ]; then exit "$_LAUNCH_EXIT" else step "launch" "manual commands:" - # Single-quote-escape paths so values with spaces / apostrophes / - # metacharacters round-trip through copy-paste from a non-TTY install log. + # Single-quote-escape so paths with spaces / apostrophes copy-paste cleanly. _li_shim_q="'$(printf '%s' "${_LOCAL_BIN}/unsloth" | sed "s/'/'\\\\''/g")'" _li_act_q="'$(printf '%s' "${VENV_DIR}/bin/activate" | sed "s/'/'\\\\''/g")'" if [ "$_STUDIO_HOME_REDIRECT" = "env" ]; then - # In env-override mode we deliberately skip the persistent shell - # rc PATH append, so a fresh shell will not have `unsloth` on PATH - # unless the caller re-exports UNSLOTH_STUDIO_HOME. Print the - # absolute shim path and the activate-then-run alternative. + # Env-mode skips the rc PATH append, so print the absolute shim path. substep "$_li_shim_q studio -H 0.0.0.0 -p 8888" substep "or activate env first:" substep "source $_li_act_q" diff --git a/studio/backend/core/inference/llama_cpp.py b/studio/backend/core/inference/llama_cpp.py index 057d737aea..199b14af5a 100644 --- a/studio/backend/core/inference/llama_cpp.py +++ b/studio/backend/core/inference/llama_cpp.py @@ -491,14 +491,10 @@ class LlamaCppBackend: if win_bin.is_file(): return str(win_bin) - # 2-4. Mirror setup.sh / setup.ps1's install layout: - # - In env-override mode (custom Studio root != legacy default), - # llama.cpp is installed under $STUDIO_HOME/llama.cpp. - # - Otherwise (default install or HOME redirect), llama.cpp is - # installed at ~/.unsloth/llama.cpp (sibling of the Studio dir). - # Default-mode searches must NOT look at $STUDIO_HOME/llama.cpp, - # to avoid picking a stale partial install from a previous failed - # install over the real legacy binary. + # 2-4. Match installer layout: env-mode -> $STUDIO_HOME/llama.cpp; + # default/HOME-redirect -> ~/.unsloth/llama.cpp (sibling of studio). + # Default mode skips $STUDIO_HOME/llama.cpp so a stale partial install + # there cannot shadow the real legacy binary. legacy_llama = Path.home() / ".unsloth" / "llama.cpp" try: from utils.paths.storage_roots import studio_root as _sr # noqa: WPS433 @@ -511,7 +507,6 @@ class LlamaCppBackend: search_roots = [_resolved_sr / "llama.cpp", legacy_llama] except (ImportError, OSError, ValueError): search_roots = [legacy_llama] - # De-dupe while preserving order (defensive in case both resolve equal). _seen: set[str] = set() for unsloth_home in [ r for r in search_roots if str(r) not in _seen and not _seen.add(str(r)) @@ -2084,12 +2079,9 @@ class LlamaCppBackend: # (binary must be *under* one of these) install_roots: list[Path] = [] - # Resolved Studio root (covers env-override custom installs). - # Mirror _find_llama_server_binary: only treat $STUDIO_HOME/llama.cpp - # as Studio-owned when STUDIO_HOME is a real env override, not the - # legacy default. In default-mode installs llama.cpp is a sibling - # of studio (~/.unsloth/llama.cpp), and ~/.unsloth/studio/llama.cpp - # may be owned by a different tool or a stale partial install. + # Env-mode custom root (mirrors _find_llama_server_binary). Default + # mode excluded so we don't kill llama-server from another tool + # running under ~/.unsloth/studio/llama.cpp. try: from utils.paths.storage_roots import studio_root as _sr # noqa: WPS433 diff --git a/studio/backend/main.py b/studio/backend/main.py index 365a4be812..0b9fbdd283 100644 --- a/studio/backend/main.py +++ b/studio/backend/main.py @@ -23,12 +23,9 @@ if _backend_dir not in sys.path: # See: https://github.com/python/cpython/issues/102396 import _platform_compat # noqa: F401 -# Custom-root env propagation BEFORE any unsloth/unsloth-zoo import: when -# main.py is launched directly via `uvicorn main:app` from a custom-root -# venv, neither unsloth_cli nor run.py runs, so UNSLOTH_LLAMA_CPP_PATH -# stays unset and unsloth-zoo's import-time LLAMA_CPP_DEFAULT_DIR binding -# falls back to the legacy ~/.unsloth/llama.cpp. Mirror run.py's logic: -# only export when the resolved root is a real custom override. +# Direct `uvicorn main:app` launches bypass run.py, so re-export here too +# (mirrors run.py). Required BEFORE the unsloth-zoo import below, since +# its LLAMA_CPP_DEFAULT_DIR binding is import-time. from utils.paths.storage_roots import studio_root as _studio_root _LEGACY_STUDIO_ROOT = (_Path.home() / ".unsloth" / "studio").resolve() diff --git a/studio/backend/run.py b/studio/backend/run.py index e6035e705b..f6c7208278 100644 --- a/studio/backend/run.py +++ b/studio/backend/run.py @@ -159,17 +159,14 @@ def _find_free_port(host: str, start: int, max_attempts: int = 20) -> int: ) -# Resolved via the shared helper so custom installs land in the right place. from utils.paths.storage_roots import studio_root as _studio_root _PID_FILE = _studio_root() / "studio.pid" -# When the backend is launched directly (bypassing unsloth_cli, which -# normally re-exports these env vars), make sure unsloth-zoo's import-time -# LLAMA_CPP_DEFAULT_DIR binding still picks up the custom-root build. Only -# set when the resolved root is a real custom override -- legacy default -# installs must NOT export, since the installers treat any non-empty -# UNSLOTH_STUDIO_HOME as env-override mode. +# Direct backend launches bypass the CLI's env re-export; do it here for +# real custom roots so unsloth-zoo's import-time LLAMA_CPP_DEFAULT_DIR +# picks up the custom build. Skip for legacy-default to avoid flipping +# default-mode installs into env-override. _LEGACY_STUDIO_ROOT = (Path.home() / ".unsloth" / "studio").resolve() try: _STUDIO_ROOT_RESOLVED = _studio_root().resolve() diff --git a/studio/backend/utils/models/model_config.py b/studio/backend/utils/models/model_config.py index ce9fe4fe6d..9d04ec09d6 100644 --- a/studio/backend/utils/models/model_config.py +++ b/studio/backend/utils/models/model_config.py @@ -499,7 +499,6 @@ _VLM_MODEL_TYPES = { # Pre-computed .venv_t5 paths and backend dir for subprocess version switching. # Vision check uses 5.5.0 (newest, recognizes all architectures). -# Use the shared resolver so custom-root installs find their own venv. from utils.paths.storage_roots import studio_root as _studio_root # noqa: E402 _VENV_T5_DIR = str(_studio_root() / ".venv_t5_550") diff --git a/studio/backend/utils/paths/storage_roots.py b/studio/backend/utils/paths/storage_roots.py index f1a3038871..e52fdcd946 100644 --- a/studio/backend/utils/paths/storage_roots.py +++ b/studio/backend/utils/paths/storage_roots.py @@ -11,14 +11,9 @@ import tempfile def _infer_studio_home_from_venv() -> Path | None: - """If running from an installer-managed unsloth_studio venv, return the - parent dir as STUDIO_HOME. - - Fallback for fresh shells after a custom install where the installer - wrote to a workspace path but the user did not re-export the env var. - Narrowed via installer-sentinel check (share/studio.conf or bin shim) - so a developer venv that happens to be named ``unsloth_studio`` is not - misidentified as a custom Studio root. + """Return parent dir of sys.prefix as STUDIO_HOME if running from an + installer-managed unsloth_studio venv. Sentinel-gated (share/studio.conf + or bin shim) so a developer venv named unsloth_studio is not misidentified. """ try: prefix = Path(sys.prefix).resolve() @@ -36,12 +31,10 @@ def _infer_studio_home_from_venv() -> Path | None: def studio_root() -> Path: - """Resolve the Studio install root. + """Studio install root. - Priority: UNSLOTH_STUDIO_HOME env, STUDIO_HOME env (alias), sys.prefix - inference, legacy ~/.unsloth/studio. Backwards-compatible: identical - to the legacy default when no env var is set and we are not running - from the venv. + Priority: UNSLOTH_STUDIO_HOME, STUDIO_HOME, sys.prefix inference, + legacy ~/.unsloth/studio. """ override = os.environ.get("UNSLOTH_STUDIO_HOME") or os.environ.get("STUDIO_HOME") if override: diff --git a/studio/backend/utils/transformers_version.py b/studio/backend/utils/transformers_version.py index 5b683fd4d2..1a5a6d668c 100644 --- a/studio/backend/utils/transformers_version.py +++ b/studio/backend/utils/transformers_version.py @@ -94,7 +94,6 @@ TRANSFORMERS_DEFAULT_VERSION = "4.57.6" TRANSFORMERS_5_VERSION = TRANSFORMERS_550_VERSION # Pre-installed directories — created by setup.sh / setup.ps1. -# Use the shared resolver so custom-root installs find their own venvs. from utils.paths.storage_roots import studio_root as _studio_root # noqa: E402 _VENV_T5_530_DIR = str(_studio_root() / ".venv_t5_530") diff --git a/studio/setup.ps1 b/studio/setup.ps1 index 2dd52d36bb..0e6969ce8a 100644 --- a/studio/setup.ps1 +++ b/studio/setup.ps1 @@ -1455,11 +1455,8 @@ if (-not $PythonCmd) { substep "Using $PythonCmd ($(& $PythonCmd --version 2>&1))" -# The venv must already exist (created by install.ps1). -# This script (setup.ps1 / "unsloth studio update") only updates packages. -# UNSLOTH_STUDIO_HOME / STUDIO_HOME (alias) override the install root, -# mirroring install.ps1. install.ps1 exports UNSLOTH_STUDIO_HOME when -# invoking the studio update. +# The venv must already exist (created by install.ps1); this script only +# updates packages. UNSLOTH_STUDIO_HOME / STUDIO_HOME override the root. $_studioOverride = if ($env:UNSLOTH_STUDIO_HOME) { $env:UNSLOTH_STUDIO_HOME } elseif ($env:STUDIO_HOME) { $env:STUDIO_HOME } else { $null } if ($_studioOverride) { if ($_studioOverride -eq "~" -or $_studioOverride -like "~/*" -or $_studioOverride -like "~\*") { @@ -1728,8 +1725,7 @@ if ($stackExit -ne 0) { # ── 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. -# T5 sidecar venvs live next to the main venv under the resolved $StudioHome -# so custom-root installs are self-contained. +# T5 sidecar venvs live under the resolved $StudioHome so custom installs are self-contained. $VenvT5_530Dir = Join-Path $StudioHome ".venv_t5_530" $VenvT5_550Dir = Join-Path $StudioHome ".venv_t5_550" $VenvT5Legacy = Join-Path $StudioHome ".venv_t5" @@ -1822,15 +1818,12 @@ step "transformers" "5.5.0 pre-installed" # ========================================================================== # PHASE 3.4: Prefer prebuilt llama.cpp bundles before source build # ========================================================================== -# Only nest llama.cpp under $StudioHome when the resolved Studio root is -# a real override (not the legacy default). Compare resolved paths so a -# stale UNSLOTH_STUDIO_HOME pointing at the legacy default does not +# Nest llama.cpp under $StudioHome only for real env-overrides, never the +# legacy default. Stale UNSLOTH_STUDIO_HOME pointing at legacy must not # accidentally relocate llama.cpp. $LegacyStudioHome = Join-Path $env:USERPROFILE ".unsloth\studio" -# Canonicalize BOTH sides. $StudioHome is the resolved env-override path -# in env-mode (line 1474) but the bare logical path in default mode -# (line 1480). Canonicalizing on the fly handles a junctioned/symlinked -# %USERPROFILE% the same in both modes. +# Canonicalize both sides so a junctioned/symlinked %USERPROFILE% behaves +# the same in env-mode (resolved) and default mode (logical $StudioHome). $_studioHomeCanon = $StudioHome if (Test-Path -LiteralPath $_studioHomeCanon -PathType Container) { $_studioHomeCanon = (Resolve-Path -LiteralPath $_studioHomeCanon).Path diff --git a/studio/setup.sh b/studio/setup.sh index affcc5683a..48f1e925c1 100755 --- a/studio/setup.sh +++ b/studio/setup.sh @@ -399,18 +399,14 @@ if [ -d "$SCRIPT_DIR/backend/core/data_recipe/oxc-validator" ] && command -v npm fi # ── Python venv + deps ── -# UNSLOTH_STUDIO_HOME / STUDIO_HOME (alias) override the install root, mirroring -# install.sh. install.sh exports UNSLOTH_STUDIO_HOME when invoking this script. +# UNSLOTH_STUDIO_HOME / STUDIO_HOME override the install root (mirrors install.sh). _studio_override="${UNSLOTH_STUDIO_HOME:-${STUDIO_HOME:-}}" -# Expand a leading ~ / ~/path because env vars are not subject to tilde -# expansion when set with quotes around the value. case "$_studio_override" in "~") _studio_override="$HOME" ;; "~/"*) _studio_override="$HOME/${_studio_override#'~/'}" ;; esac if [ -n "$_studio_override" ]; then mkdir -p -- "$_studio_override" - # CDPATH= prevents CDPATH-prefixed echo; -P canonicalizes symlinks. STUDIO_HOME="$(CDPATH= cd -P -- "$_studio_override" && pwd -P)" || exit 1 else STUDIO_HOME="$HOME/.unsloth/studio" @@ -570,16 +566,11 @@ fi fi # ── 7. Prefer prebuilt llama.cpp bundles before any source build path ── -# Only nest llama.cpp under $STUDIO_HOME when the resolved Studio root is a -# real override (not the legacy default). Comparing resolved paths instead -# of env-var presence avoids regressing default installs that incidentally -# inherit UNSLOTH_STUDIO_HOME from a parent process or the CLI. +# Nest llama.cpp under $STUDIO_HOME only for real env-overrides, never the +# legacy default. Compare canonicalized paths so STUDIO_HOME (logical in +# default mode, canonical in env mode) and the legacy side line up under +# symlinked $HOME. _LEGACY_STUDIO_HOME="$HOME/.unsloth/studio" -# Canonicalize BOTH sides under symlinked $HOME. STUDIO_HOME is logical -# in default-mode (line 416 sets it from the bare $HOME path) and -# canonical in env-mode (line 413 uses pwd -P). Canonicalizing on the -# fly here means default-mode under symlinked $HOME still recognizes -# the legacy default and keeps llama.cpp at ~/.unsloth/llama.cpp. _studio_home_canon="$STUDIO_HOME" if [ -d "$_studio_home_canon" ]; then _studio_home_canon=$(CDPATH= cd -P -- "$_studio_home_canon" 2>/dev/null && pwd -P) \ diff --git a/studio/src-tauri/src/desktop_auth.rs b/studio/src-tauri/src/desktop_auth.rs index 3f35e16466..6bb256c14c 100644 --- a/studio/src-tauri/src/desktop_auth.rs +++ b/studio/src-tauri/src/desktop_auth.rs @@ -194,10 +194,8 @@ async fn provision_desktop_auth() -> Result<(), String> { cmd.env_remove("PYTHONPATH"); } - // Tauri does not honor UNSLOTH_STUDIO_HOME / STUDIO_HOME yet; scrub so the - // provisioning subprocess writes to the legacy ~/.unsloth/studio root that - // matches what the Rust auth code reads. UNSLOTH_LLAMA_CPP_PATH is a - // pre-existing backend-only llama.cpp directory override and is kept. + // Tauri uses the legacy root regardless of UNSLOTH_STUDIO_HOME / STUDIO_HOME. + // Scrub so provisioning writes match what the Rust auth code reads. cmd.env_remove("UNSLOTH_STUDIO_HOME"); cmd.env_remove("STUDIO_HOME"); #[cfg(windows)] diff --git a/studio/src-tauri/src/install.rs b/studio/src-tauri/src/install.rs index f8a12b5d3e..173f88a4c4 100644 --- a/studio/src-tauri/src/install.rs +++ b/studio/src-tauri/src/install.rs @@ -192,10 +192,8 @@ fn spawn_script( cmd.env_remove("PYTHONPATH"); } - // Tauri only does default-root installs. install.sh / install.ps1 reject - // UNSLOTH_STUDIO_HOME / STUDIO_HOME under --tauri; scrub them from the - // spawned installer so an inherited shell-set value cannot trip the guard. - // UNSLOTH_LLAMA_CPP_PATH is unrelated to the Studio root and kept as-is. + // Tauri only does default-root installs; install.sh / install.ps1 reject + // these under --tauri. Scrub so an inherited value can't trip the guard. cmd.env_remove("UNSLOTH_STUDIO_HOME"); cmd.env_remove("STUDIO_HOME"); diff --git a/studio/src-tauri/src/process.rs b/studio/src-tauri/src/process.rs index 67cef43a10..a05a7bd6ee 100644 --- a/studio/src-tauri/src/process.rs +++ b/studio/src-tauri/src/process.rs @@ -280,12 +280,9 @@ pub fn start_backend( cmd.env_remove("PYTHONPATH"); } - // The Tauri desktop app does not honor UNSLOTH_STUDIO_HOME / STUDIO_HOME yet; - // it always uses ~/.unsloth/studio. Scrub these from the spawned Python - // backend so it cannot diverge from the Rust-resolved Studio root when the - // user's shell happens to have them set. Keep UNSLOTH_LLAMA_CPP_PATH: that - // is a pre-existing custom-llama.cpp-directory override the Python backend - // and unsloth-zoo intentionally support, independent of the Studio root. + // Tauri uses the legacy root regardless of UNSLOTH_STUDIO_HOME / STUDIO_HOME; + // scrub so the spawned Python backend can't diverge. UNSLOTH_LLAMA_CPP_PATH + // is a pre-existing user-controlled llama.cpp dir override; keep it. cmd.env_remove("UNSLOTH_STUDIO_HOME"); cmd.env_remove("STUDIO_HOME"); diff --git a/studio/src-tauri/src/update.rs b/studio/src-tauri/src/update.rs index 8dc4ebbb6d..c138aef0d4 100644 --- a/studio/src-tauri/src/update.rs +++ b/studio/src-tauri/src/update.rs @@ -58,9 +58,8 @@ fn spawn_update( cmd.env_remove("PYTHONPATH"); } - // Tauri still manages the legacy ~/.unsloth/studio root; scrub the custom- - // root env vars so 'unsloth studio update' updates the same install the - // desktop app uses, not an unrelated custom root inherited from the shell. + // Tauri manages the legacy root; scrub so 'unsloth studio update' targets + // the same install the desktop app uses, not an inherited custom root. cmd.env_remove("UNSLOTH_STUDIO_HOME"); cmd.env_remove("STUDIO_HOME"); diff --git a/unsloth_cli/commands/studio.py b/unsloth_cli/commands/studio.py index 593d5f4e1f..d11a7a6374 100644 --- a/unsloth_cli/commands/studio.py +++ b/unsloth_cli/commands/studio.py @@ -21,17 +21,12 @@ import typer studio_app = typer.Typer(help = "Unsloth Studio commands.") -# UNSLOTH_STUDIO_HOME / STUDIO_HOME (alias) override the install root, matching -# install.sh / install.ps1. Falls back to sys.prefix inference (so direct -# invocations of /bin/unsloth still resolve correctly even after -# the installer's env var has expired), then to the legacy ~/.unsloth/studio. -# Returns (path, is_custom): is_custom=True only when the resolved root is a -# real override; we use this to decide whether to re-export the env var to -# child processes. +# Resolve install root: UNSLOTH_STUDIO_HOME / STUDIO_HOME, then sys.prefix +# inference (so a direct call to /bin/unsloth resolves after the +# installer's env var has expired), then legacy ~/.unsloth/studio. def _looks_like_installer_managed_studio_home(candidate: Path) -> bool: - """Heuristic: only treat a directory as an installer-managed Studio root - if it carries installer-written sentinels (studio.conf or the bin shim). - Avoids over-matching on a dev venv that happens to be named unsloth_studio. + """Sentinel check (studio.conf or bin shim) so a dev venv named + unsloth_studio is not misidentified as a custom Studio root. """ shim_name = "unsloth.exe" if platform.system() == "Windows" else "unsloth" return (candidate / "share" / "studio.conf").is_file() or ( @@ -58,21 +53,16 @@ def _resolve_studio_home() -> tuple[Path, bool]: STUDIO_HOME, _STUDIO_HOME_IS_CUSTOM = _resolve_studio_home() -# Re-export only for actual custom roots so child processes (setup script, -# backend run.py) inherit it. Default installs MUST NOT re-export the env -# var, because setup.sh / setup.ps1 treat its presence as "env-override -# mode" and would relocate llama.cpp / DATA_DIR away from legacy paths. -# UNSLOTH_LLAMA_CPP_PATH is also setdefault for custom roots so -# unsloth-zoo's import-time LLAMA_CPP_DEFAULT_DIR binding picks up the -# correct build dir for GGUF export. +# Re-export only for real custom roots; default installs must NOT export +# (setup.sh/setup.ps1 treat any non-empty UNSLOTH_STUDIO_HOME as env-mode). +# UNSLOTH_LLAMA_CPP_PATH is set so unsloth-zoo's import-time +# LLAMA_CPP_DEFAULT_DIR binding finds the custom build for GGUF export. if _STUDIO_HOME_IS_CUSTOM: - # Use truthy-check rather than setdefault so a blank env var (e.g. - # UNSLOTH_STUDIO_HOME=) doesn't suppress the inferred custom root. + # Truthy-check (not setdefault) so a blank UNSLOTH_STUDIO_HOME= doesn't + # suppress the inferred custom root. if not os.environ.get("UNSLOTH_STUDIO_HOME"): os.environ["UNSLOTH_STUDIO_HOME"] = str(STUDIO_HOME) - # Mirror setup.sh / setup.ps1's legacy-equality check: when an env - # override happens to equal the legacy default, llama.cpp still lives - # at ~/.unsloth/llama.cpp (one shared build across legacy installs). + # When override == legacy default, llama.cpp stays at ~/.unsloth/llama.cpp. _legacy_studio = (Path.home() / ".unsloth" / "studio").resolve() if STUDIO_HOME.resolve() == _legacy_studio: _llama_dir = Path.home() / ".unsloth" / "llama.cpp"