Skip to content

Commit df63522

Browse files
danielhanchenshimmyshimmerpre-commit-ci[bot]
authored
Installer: stop requiring a developer toolchain on the consumer path (#7547)
* Installer: stop requiring a developer toolchain on the consumer path A brand new Mac cannot install Studio at all. install.sh gates on `xcode-select -p` and exits 1 with 'Xcode Command Line Tools are required', and Linux exits 1 on any non-apt distro over cmake/gcc/git/libcurl headers. Nothing under either gate needs a toolchain. uv is a prebuilt binary, CPython comes from uv's managed python-build-standalone, llama.cpp and whisper.cpp are prebuilt downloads, Node is a pinned nodejs.org archive, and triton is skipped on macOS. unslothai/llama.cpp b10107-mix-1911198 publishes macos-arm64, macos-x64, linux-x64 and linux-arm64 builds covering cpu, cuda12, cuda13, rocm and vulkan. PR #6617 already dropped the Homebrew/cmake stop on macOS for this reason and just left the CLT stop behind. macOS: warn and continue when the CLT are absent. Linux: only a download transport (curl or wget) is fatal; build tooling warns. Both keep a hard git requirement for --local, which installs unsloth-zoo from a git+https URL. Both gates move into functions so tests/sh can extract them. The old inline form could not be reached by the tests/sh convention, which is why this shipped broken and stayed broken. test_macos_clt_gate.sh (19 assertions) and test_linux_deps_gate.sh (25) cover the clean machine, the CLT-stub shape where /usr/bin/git exists but fails, the non-apt distro, and the --local paths. Writing the Linux test caught a latent bug: the gate trimmed its list with $(echo ... | sed ...), so on a minimal image without sed the substitution yields empty and it reports 'all system dependencies found' on a machine with none of them. Replaced with parameter expansion. Also caps av<16 in the single-env constraints. av 16+ ships no cp313 macOS arm64 wheel, and it is a C extension over FFmpeg, so uv would silently fall back to a source build needing both a compiler and FFmpeg headers. Verified on GitHub-hosted macOS runners with /var/db/xcode_select_link, /Library/Developer/CommandLineTools, /Applications/Xcode*.app and Homebrew moved aside. macos-14, macos-15 and macos-26 fail on main and install cleanly with this; the recorded tool-invocation trace for the whole install is a single `xcode-select -p`, so nothing compiled and nothing installed a toolchain. * Linux: auto-install git rather than dropping it, and skip triton kernels without it Making git optional on Linux was too broad. studio/backend/requirements/ triton-kernels.txt line 2 is a git+https URL, so step 6/14 died with 'Cannot find command git' and failed the whole setup on ubuntu2404-root, ubuntu2404-arm-root and fedora41, all of which had been passing. The claim that nothing on the consumer path needs git holds on macOS, where triton is skipped, but not here. install.sh now auto-installs git through apt with the other optional tooling, so Debian and Ubuntu are unchanged. The triton kernels step skips with a message when git is absent instead of failing: they are a training speedup, not a boot requirement, and a GGUF chat install has no use for them. Six more assertions pin both halves. * macOS Intel: skip the one package with no x86_64 wheel The Intel clean-machine leg installed with the toolchain masked, then died in studio setup: subprocess.CalledProcessError: Command '['cmake', ...]' returned non-zero ERROR: Failed building wheel for pytorch_tokenizers pytorch_tokenizers publishes wheels for macOS arm64, linux x86_64, linux aarch64 and windows, but none for macOS x86_64 at any Python version, so uv falls back to an sdist that shells out to cmake. Nothing passes --only-binary, so the compiler-free property was an assumption rather than a contract, and Intel is where it broke. Marked so it installs everywhere except Intel macOS. Apple Silicon is unaffected. * Stop the optional dep gate from aborting the install _smart_apt_install exits rather than returns, and `|| true` does not catch an exit, so a box missing cmake or git aborted at the gate added to let it continue. Verified in sh, dash and bash. Run it in a subshell and re-raise only code 2, the NEED_SUDO handshake install.rs answers with an elevation prompt. install.sh treats a present-but-broken git as missing, but the Python side tested only shutil.which, so it promised to skip the git+https triton requirement and then fetched it anyway. Same check on both sides now. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Never elevate for optional build tools Re-raising code 2 turned the optional set into a NEED_SUDO handshake, so a box missing cmake or gcc got the desktop's mandatory permission dialog, whose Cancel drops back to not-installed. That re-imposes through a prompt the build-tool requirement this gate removes, and none of those tools are needed to run. Suppress the handshake for optional callers; a required package still elevates. Verified in sh, dash and bash. Also advance the progress bar on the no-git triton skip, which otherwise ends at 14/15. * Tighten the comments on the dependency gate * Correct why the PyAV cap is needed 16.0.0 does ship cp313-cp313-macosx_14_0_arm64; the comment claimed no cp313 wheel exists. The actual reason is the deployment target: 15.1.0 is macosx_13_0 and 16+ is macosx_14_0, so the cap is what keeps macOS 13 off a source build. * Tighten the installer gate comments * Cap cryptography on x86_64 macOS so the consumer install needs no Rust cryptography 49.0.0 (2026-06-12) dropped the macosx_10_9_universal2 wheel and now ships macosx_11_0_arm64 only, so x86_64 macOS has no wheel and uv falls back to the sdist. That build calls maturin, which pulls Rust and then fails at 'linking with cc failed' on a clean Mac without the Xcode Command Line Tools. It surfaced in the clean-machine leg mac macos-15-intel / mask / file, several minutes into the studio dependency step, which is exactly the up-front toolchain requirement this branch removes. 48.0.1 is the newest release carrying a universal2 wheel, and its cp39-abi3 / cp311-abi3 tags cover the 3.12 and 3.13 interpreters the installer creates. The cap is marker-scoped to darwin + x86_64, so arm64 macOS and every other platform still resolve to the latest. Lift it when cryptography ships an x86_64-capable macOS wheel again. Resolution of studio/backend/requirements/studio.txt under this constraints file gives 48.0.1 on x86_64-apple-darwin and 49.0.0 on aarch64-apple-darwin and x86_64-unknown-linux-gnu, on both 3.12 and 3.13. * Correct the av note now that cryptography also compiles on macOS * Never escalate for optional apt packages outside Tauri mode The optional bypass sat inside the TAURI_MODE branch, so a plain curl | sh install on a non-root Debian or Ubuntu box still fell through to the escalation branch and showed the default-yes permission prompt for cmake, GCC and the libcurl headers. That is exactly the toolchain this change set declared unnecessary on the consumer path, so the prompt asked for a password to install packages nothing here uses, and a headless run failed the same way instead of falling through to prebuilt llama.cpp. Move the check above the mode split so optional callers return 2 in both modes. Required packages such as curl still escalate unchanged. --------- Co-authored-by: danielhanchen <unslothai@gmail.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 9e2fc49 commit df63522

6 files changed

Lines changed: 573 additions & 66 deletions

File tree

install.sh

Lines changed: 139 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -800,8 +800,17 @@ _smart_apt_install() {
800800
return 0
801801
fi
802802

803-
# In Tauri mode, report needed packages and exit — Rust handles elevation
803+
# Optional callers never elevate, in any mode: nothing on the consumer path
804+
# builds anything, so neither the terminal sudo prompt below nor the Tauri
805+
# NEED_SUDO dialog (whose Cancel leaves the user not installed) may gate the
806+
# run over unused tools. The caller falls through to prebuilt llama.cpp.
807+
# Required packages such as curl still escalate.
808+
if [ "${_SMART_APT_OPTIONAL:-false}" = true ]; then
809+
return 2
810+
fi
811+
804812
if [ "$TAURI_MODE" = true ]; then
813+
# Report needed packages and exit — Rust handles elevation.
805814
tauri_log "NEED_SUDO" "$_STILL_MISSING"
806815
exit 2
807816
fi
@@ -1998,67 +2007,142 @@ _maybe_reroute_strixhalo_to_2404() {
19982007
_maybe_reroute_strixhalo_to_2404 || true
19992008

20002009
# ── Check system dependencies ──
2001-
# cmake/git are only needed to *build* llama.cpp from source. Unsloth downloads a
2002-
# prebuilt by default, and setup.sh self-skips the source build when they're
2003-
# absent -- so macOS doesn't block on cmake (requiring it would force a manual
2004-
# Homebrew install). Linux keeps requiring them; its package manager has them.
20052010
tauri_log "STEP" "Checking system dependencies"
20062011

2007-
case "$OS" in
2008-
macos)
2009-
# Xcode Command Line Tools provide the C/C++ compiler and git.
2010-
if ! xcode-select -p >/dev/null 2>&1; then
2011-
echo ""
2012-
echo "==> Xcode Command Line Tools are required."
2013-
echo " Installing (a system dialog will appear)..."
2014-
xcode-select --install </dev/null 2>/dev/null || true
2015-
echo " After the installation completes, please re-run this script."
2016-
exit 1
2017-
fi
2018-
# cmake is only needed for a source build; the default prebuilt path
2019-
# doesn't use it, so its absence is not fatal -- no Homebrew prerequisite.
2020-
if command -v cmake >/dev/null 2>&1; then
2021-
step "deps" "all system dependencies found"
2022-
else
2023-
step "deps" "using prebuilt llama.cpp (cmake not found)" "$C_WARN"
2024-
substep "Install cmake only if you want a source build: brew install cmake"
2025-
fi
2026-
;;
2027-
linux|wsl)
2028-
MISSING=""
2029-
command -v cmake >/dev/null 2>&1 || MISSING="$MISSING cmake"
2030-
command -v git >/dev/null 2>&1 || MISSING="$MISSING git"
2031-
# curl or wget is needed for downloads; check both
2032-
if ! command -v curl >/dev/null 2>&1 && ! command -v wget >/dev/null 2>&1; then
2033-
MISSING="$MISSING curl"
2034-
fi
2035-
command -v gcc >/dev/null 2>&1 || MISSING="$MISSING build-essential"
2036-
# libcurl dev headers for llama.cpp HTTPS support
2037-
command -v curl-config >/dev/null 2>&1 || MISSING="$MISSING libcurl4-openssl-dev"
2012+
# Without the Xcode CLT, macOS still ships /usr/bin/git as a stub that errors and pops
2013+
# a GUI dialog, so `command -v git` is not enough -- only running it tells the truth.
2014+
_has_working_git() {
2015+
command -v git >/dev/null 2>&1 || return 1
2016+
git --version >/dev/null 2>&1
2017+
}
2018+
2019+
# macOS system-dependency check. A function so tests/sh can sed-extract it; the old
2020+
# inline form was untestable, which is why this gate shipped broken.
2021+
#
2022+
# The consumer install needs no developer toolchain: uv is a prebuilt binary, CPython
2023+
# is uv-managed, llama.cpp/whisper.cpp/Node are prebuilt downloads, and triton is
2024+
# skipped on macOS. Only `--local` needs git, for the unsloth-zoo git+https URL.
2025+
_check_macos_deps() {
2026+
_clt_missing=false
2027+
xcode-select -p >/dev/null 2>&1 || _clt_missing=true
2028+
2029+
if [ "$STUDIO_LOCAL_INSTALL" = true ] && ! _has_working_git; then
2030+
echo ""
2031+
step "deps" "git is required for --local installs" "$C_ERR"
2032+
substep "--local installs unsloth-zoo from git+https://github.com/unslothai/unsloth-zoo,"
2033+
substep "which needs a working git. Install the Xcode Command Line Tools:"
2034+
substep " xcode-select --install"
2035+
substep "Then re-run this script. A normal (non---local) install needs no compiler"
2036+
substep "and no git -- it uses prebuilt binaries and wheels only."
2037+
tauri_log "NEED_XCODE_CLT" "git"
2038+
return 1
2039+
fi
2040+
2041+
if [ "$_clt_missing" = true ]; then
2042+
# Not fatal, and no GUI dialog: firing xcode-select --install and exiting is
2043+
# what stranded clean Macs.
2044+
step "deps" "no Xcode Command Line Tools (not required)" "$C_WARN"
2045+
substep "Unsloth installs prebuilt binaries and wheels, so no compiler is needed."
2046+
substep "Install them only for a llama.cpp source build: xcode-select --install"
2047+
elif command -v cmake >/dev/null 2>&1; then
2048+
step "deps" "all system dependencies found"
2049+
else
2050+
# cmake is only for a source build, so its absence is not fatal.
2051+
step "deps" "using prebuilt llama.cpp (cmake not found)" "$C_WARN"
2052+
substep "Install cmake only if you want a source build: brew install cmake"
2053+
fi
2054+
return 0
2055+
}
20382056

2039-
MISSING=$(echo "$MISSING" | sed 's/^ *//')
2040-
if [ -n "$MISSING" ]; then
2057+
# Linux/WSL system-dependency check. Same split as macOS, and a function for the same
2058+
# reason: tests/sh can extract it.
2059+
#
2060+
# Only a download transport is required. cmake, gcc and the libcurl headers exist
2061+
# solely for a llama.cpp source build the consumer path never does -- unslothai/
2062+
# llama.cpp publishes linux-x64/arm64 prebuilts for cpu, cuda12, cuda13, rocm and
2063+
# vulkan. Requiring them turned every non-apt distro into a hard exit 1 over unused
2064+
# tooling. git follows macOS: --local only.
2065+
_check_linux_deps() {
2066+
_transport_missing=false
2067+
if ! command -v curl >/dev/null 2>&1 && ! command -v wget >/dev/null 2>&1; then
2068+
_transport_missing=true
2069+
fi
2070+
2071+
# Wanted, never required: git fetches the triton_kernels git+https requirement (a
2072+
# training speedup), the rest serve the optional source build. Warn, never stop.
2073+
_optional_missing=""
2074+
command -v cmake >/dev/null 2>&1 || _optional_missing="$_optional_missing cmake"
2075+
_has_working_git || _optional_missing="$_optional_missing git"
2076+
command -v gcc >/dev/null 2>&1 || _optional_missing="$_optional_missing build-essential"
2077+
command -v curl-config >/dev/null 2>&1 || _optional_missing="$_optional_missing libcurl4-openssl-dev"
2078+
# Parameter expansion, not `sed`: sed may be absent on a minimal image, and a
2079+
# failed `$(... | sed ...)` yields "" -- "all found" on a machine that has none.
2080+
_optional_missing="${_optional_missing# }"
2081+
2082+
if [ "$STUDIO_LOCAL_INSTALL" = true ] && ! _has_working_git; then
2083+
echo ""
2084+
step "deps" "git is required for --local installs" "$C_ERR"
2085+
substep "--local installs unsloth-zoo from git+https://github.com/unslothai/unsloth-zoo,"
2086+
substep "which needs git. Install it with your package manager, then re-run."
2087+
substep "A normal (non---local) install needs no git and no compiler."
2088+
return 1
2089+
fi
2090+
2091+
# The one fatal case: nothing can be downloaded. apt is the only distro family we
2092+
# can drive unattended.
2093+
if [ "$_transport_missing" = true ]; then
2094+
if command -v apt-get >/dev/null 2>&1; then
20412095
echo ""
2042-
step "deps" "missing: $MISSING" "$C_WARN"
2043-
substep "These are needed to build the GGUF inference engine."
2044-
if command -v apt-get >/dev/null 2>&1; then
2045-
_smart_apt_install $MISSING
2046-
else
2047-
echo " Automatic system package installation is supported on apt-based"
2048-
echo " Linux distributions (Ubuntu/Debian) only. Please install the"
2049-
echo " missing dependencies with your package manager, then re-run setup:"
2050-
echo " $MISSING"
2051-
echo ""
2052-
echo " Examples:"
2053-
echo " Fedora/RHEL: sudo dnf install cmake git gcc gcc-c++ make libcurl-devel"
2054-
echo " Arch: sudo pacman -S --needed cmake git base-devel curl"
2055-
echo " openSUSE: sudo zypper install cmake git gcc gcc-c++ make libcurl-devel"
2056-
exit 1
2057-
fi
2096+
step "deps" "missing: curl" "$C_WARN"
2097+
substep "Needed to download uv, Python and the prebuilt inference engine."
2098+
_smart_apt_install curl
20582099
echo ""
20592100
else
2060-
step "deps" "all system dependencies found"
2101+
echo ""
2102+
step "deps" "missing: curl (or wget)" "$C_ERR"
2103+
substep "Unsloth needs one of them to download uv, Python and the prebuilt"
2104+
substep "inference engine. Install one, then re-run setup:"
2105+
substep " Fedora/RHEL: sudo dnf install curl"
2106+
substep " Arch: sudo pacman -S --needed curl"
2107+
substep " openSUSE: sudo zypper install curl"
2108+
return 1
20612109
fi
2110+
fi
2111+
2112+
# Try apt for the optional set too; failing only costs the features warned about
2113+
# below.
2114+
if [ -n "$_optional_missing" ] && command -v apt-get >/dev/null 2>&1; then
2115+
step "deps" "installing optional build tools: $_optional_missing" "$C_DIM"
2116+
# Subshell because _smart_apt_install exits rather than returns, so `|| true`
2117+
# alone would not catch it. _SMART_APT_OPTIONAL suppresses every escalation
2118+
# path, so no install hinges on a prompt for tools nothing here needs.
2119+
( _SMART_APT_OPTIONAL=true; _smart_apt_install $_optional_missing ) || true
2120+
_optional_missing=""
2121+
command -v cmake >/dev/null 2>&1 || _optional_missing="$_optional_missing cmake"
2122+
_has_working_git || _optional_missing="$_optional_missing git"
2123+
command -v gcc >/dev/null 2>&1 || _optional_missing="$_optional_missing build-essential"
2124+
command -v curl-config >/dev/null 2>&1 || _optional_missing="$_optional_missing libcurl4-openssl-dev"
2125+
_optional_missing="${_optional_missing# }"
2126+
fi
2127+
2128+
if [ -n "$_optional_missing" ]; then
2129+
step "deps" "using prebuilt llama.cpp (missing: $_optional_missing)" "$C_WARN"
2130+
substep "Not required to run: Unsloth downloads a prebuilt inference engine."
2131+
case " $_optional_missing " in
2132+
*" git "*) substep "Without git the triton kernels training speedup is skipped." ;;
2133+
esac
2134+
else
2135+
step "deps" "all system dependencies found"
2136+
fi
2137+
return 0
2138+
}
2139+
2140+
case "$OS" in
2141+
macos)
2142+
_check_macos_deps || exit 1
2143+
;;
2144+
linux|wsl)
2145+
_check_linux_deps || exit 1
20622146
;;
20632147
esac
20642148

studio/backend/requirements/extras-no-deps.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ trl==0.23.1
1515
torch-c-dlpack-ext
1616
sentence_transformers==5.2.0
1717
transformers==4.57.6
18-
pytorch_tokenizers
18+
# No macOS x86_64 wheel at any version, so uv falls back to an sdist that shells out to
19+
# cmake. Skipping it on Intel Macs keeps that install compiler-free.
20+
pytorch_tokenizers; sys_platform != "darwin" or platform_machine == "arm64"
1921
kernels==0.12.1
2022
# kernels<3.11 imports tomli as its tomllib fallback; --no-deps skips its own
2123
# marker dep, so list it here (no-op on the 3.12/3.13 default installs).

studio/backend/requirements/single-env/constraints.txt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,20 @@ websockets>=15.0.1
2121
anyio<4.14.0
2222

2323
pandas==2.3.3
24+
25+
# av (PyAV) 16+ builds its macOS arm64 wheels against macosx_14_0, so on macOS 13 none
26+
# are installable and the resolver falls back to a source build, which needs FFmpeg
27+
# headers the Xcode CLT do not supply and so fails however that Mac is equipped.
28+
# 15.1.0 is the newest release with a macosx_13_0 arm64 wheel; 17+ moves to cp311-abi3
29+
# at macosx_14_0 too.
30+
#
31+
# The remaining sdist-only macOS defaults are pure Python, hence allowlisted in
32+
# .github/scripts/clean-machine-assert.sh instead; cryptography below is the one
33+
# other package that would compile.
34+
av<16
35+
36+
# cryptography 49.0.0 dropped the macosx_10_9_universal2 wheel for arm64-only, so
37+
# x86_64 macOS has no wheel and builds the sdist, needing Rust plus a working
38+
# linker. 48.0.1 is the newest release with a universal2 wheel. Lift when
39+
# cryptography ships an x86_64-capable macOS wheel again.
40+
cryptography<49; sys_platform == "darwin" and platform_machine == "x86_64"

studio/install_python_stack.py

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2892,6 +2892,30 @@ def patch_package_file(package_name: str, relative_path: str, url: str) -> None:
28922892
# -- Main install sequence ---------------------------------------------
28932893

28942894

2895+
def _has_working_git() -> bool:
2896+
"""Match install.sh's _has_working_git: on PATH *and* actually runnable.
2897+
2898+
A present-but-broken git (a bare xcrun shim) counts as missing there too. Testing
2899+
only shutil.which disagreed, so the installer promised to skip the git+https triton
2900+
requirement and then tried to fetch it anyway.
2901+
"""
2902+
exe = shutil.which("git")
2903+
if exe is None:
2904+
return False
2905+
try:
2906+
return (
2907+
subprocess.run(
2908+
[exe, "--version"],
2909+
stdout = subprocess.DEVNULL,
2910+
stderr = subprocess.DEVNULL,
2911+
timeout = 30,
2912+
).returncode
2913+
== 0
2914+
)
2915+
except (OSError, subprocess.SubprocessError):
2916+
return False
2917+
2918+
28952919
def install_python_stack() -> int:
28962920
global USE_UV, _STEP, _TOTAL
28972921
_STEP = 0
@@ -3197,17 +3221,22 @@ def _win_amd_smi_has_gpu(stdout: str) -> bool:
31973221
_torchao_spec,
31983222
)
31993223

3200-
# 5. Triton kernels (no-deps, from source). Skip on Windows and macOS
3201-
# (no support).
3224+
# 5. Triton kernels (no-deps, from source). Skipped on Windows/macOS (no support)
3225+
# and without git (the requirement is a git+https URL); a training speedup
3226+
# only, so warn rather than fail the install.
32023227
if not IS_WINDOWS and not IS_MACOS:
3203-
_progress("triton kernels")
3204-
pip_install(
3205-
"Installing triton kernels",
3206-
"--no-deps",
3207-
"--no-cache-dir",
3208-
req = REQ_ROOT / "triton-kernels.txt",
3209-
constrain = False,
3210-
)
3228+
if not _has_working_git():
3229+
_progress("triton kernels (skipped, no git)")
3230+
_safe_print(" no working git -- skipping triton kernels (training speedup only)")
3231+
else:
3232+
_progress("triton kernels")
3233+
pip_install(
3234+
"Installing triton kernels",
3235+
"--no-deps",
3236+
"--no-cache-dir",
3237+
req = REQ_ROOT / "triton-kernels.txt",
3238+
constrain = False,
3239+
)
32113240

32123241
if not IS_WINDOWS and not IS_MACOS and not NO_TORCH:
32133242
_progress("flash-attn")

0 commit comments

Comments
 (0)