diff --git a/.github/workflows/clean-machine-install-ci.yml b/.github/workflows/clean-machine-install-ci.yml index 44b29e472b..a2ddcdce62 100644 --- a/.github/workflows/clean-machine-install-ci.yml +++ b/.github/workflows/clean-machine-install-ci.yml @@ -57,13 +57,28 @@ on: - 'studio/setup.sh' - 'studio/setup.ps1' - 'studio/install_python_stack.py' + # The overlay exists so a constraints or requirements change is actually + # exercised here (see the header). Without these paths the one workflow that + # resolves them on a machine with no compiler and no wheels cached never runs + # for the PR that changes them, and the update-smoke jobs cannot stand in: + # they start from a preinstalled Python and full developer tooling. + - 'studio/backend/requirements/**' - '.github/scripts/clean-machine-*.sh' - '.github/workflows/clean-machine-install-ci.yml' push: branches: [main] + # Same list as the PR filter. A direct push to main that changed setup.sh, + # setup.ps1, install_python_stack.py, a requirements file or a clean-machine + # helper skipped this workflow entirely, so the post-merge run that is supposed + # to be the backstop for exactly those files never happened. paths: - 'install.sh' - 'install.ps1' + - 'studio/setup.sh' + - 'studio/setup.ps1' + - 'studio/install_python_stack.py' + - 'studio/backend/requirements/**' + - '.github/scripts/clean-machine-*.sh' - '.github/workflows/clean-machine-install-ci.yml' workflow_dispatch: inputs: @@ -579,11 +594,25 @@ jobs: # Only ca-certificates + curl: the advertised one-liner cannot start without # a transport. Everything else must come from the installer. wsl -d unsloth-ci -u root -- sh -c 'apt-get update -qq && apt-get install -y -qq --no-install-recommends ca-certificates curl' 2>&1 | Tee-Object -FilePath logs/wsl-bootstrap.log - # Copy the script in rather than reaching across /mnt/c: a DrvFs path brings - # Windows permissions and CRLF risk, neither of which a real WSL user has. - $wslPath = (wsl -d unsloth-ci -- wslpath -a "$($env:GITHUB_WORKSPACE -replace '\\','/')/install.sh").Trim() - Write-Host "installer source in WSL: $wslPath" - wsl -d unsloth-ci -u root -- cp "$wslPath" /root/install.sh + # A dispatch that selects `published` is asking whether the script on + # unsloth.ai works; running the checked-out one here and reporting the leg + # green answered a different question under the same name. Empty on + # pull_request/push, so automatic runs stay on this ref. + if ('${{ inputs.installer_source }}' -eq 'published') { + Write-Host 'installer: published (unsloth.ai)' + wsl -d unsloth-ci -u root -- sh -c 'curl -fsSL https://unsloth.ai/install.sh -o /root/install.sh' + if ($LASTEXITCODE -ne 0) { + Write-Host '::error::could not fetch the published installer inside WSL' + exit 1 + } + } else { + # Copy the script in rather than reaching across /mnt/c: a DrvFs path + # brings Windows permissions and CRLF risk, neither of which a real WSL + # user has. + $wslPath = (wsl -d unsloth-ci -- wslpath -a "$($env:GITHUB_WORKSPACE -replace '\\','/')/install.sh").Trim() + Write-Host "installer source in WSL: $wslPath" + wsl -d unsloth-ci -u root -- cp "$wslPath" /root/install.sh + } # Piped, same shape as `curl ... | sh`, so an early exit still exposes the # broken pipe, but the script under test is this ref not production's. wsl -d unsloth-ci -u root -- sh -c 'cd /root && cat install.sh | sh' 2>&1 | Tee-Object -FilePath logs/wsl-install.log @@ -854,11 +883,24 @@ jobs: UNSLOTH_CI_SOURCE_OVERLAY: ${{ matrix.overlay && inputs.installer_source != 'published' && github.workspace || '' }} run: | $ErrorActionPreference = 'Continue' + # Windows ships its own published script (install.ps1:3), so `published` + # has a meaning here too. Running the checked-out one regardless made a + # dispatch that asked about unsloth.ai report on this ref instead. Empty on + # pull_request/push, so automatic runs stay on this ref. + $script = './install.ps1' + if ('${{ inputs.installer_source }}' -eq 'published') { + Invoke-WebRequest -Uri https://unsloth.ai/install.ps1 ` + -OutFile published-install.ps1 -UseBasicParsing -TimeoutSec 300 + $script = './published-install.ps1' + Write-Host 'installer: published (unsloth.ai)' + } else { + Write-Host "installer: this ref ($env:GITHUB_SHA)" + } # No -SkipTorch: install.ps1 has no param block and its parser matches # `--no-torch` only (112-142), so the token was silently dropped and every # Windows leg installed torch anyway. The assert below needs torch, so ask # for it explicitly rather than by accident. - & ./install.ps1 *>&1 | Tee-Object -FilePath logs/install.log + & $script *>&1 | Tee-Object -FilePath logs/install.log $rc = $LASTEXITCODE Write-Host "installer exit code: $rc" exit $rc diff --git a/.github/workflows/desktop-app-clean-machine-ci.yml b/.github/workflows/desktop-app-clean-machine-ci.yml index 8694cd4e1b..2079857d80 100644 --- a/.github/workflows/desktop-app-clean-machine-ci.yml +++ b/.github/workflows/desktop-app-clean-machine-ci.yml @@ -390,7 +390,9 @@ jobs: windows: name: desktop windows runs-on: windows-latest - timeout-minutes: 45 + # 60, not 45: this job now runs the bundled installer, and a full torch install + # on a Windows runner is the slowest of the three platforms. + timeout-minutes: 60 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -448,6 +450,51 @@ jobs: Write-Host "installed: $($found.FullName)" "APP_EXE=$($found.FullName)" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 + - name: Run the bundled installer, the path first launch takes + shell: pwsh + run: | + # The launch step below only proves the process stayed alive. On a fresh + # profile preflight reports not_installed and the app waits for the user to + # click Install (use-tauri-backend.ts:252-254, startup-screen.tsx:388-389), + # so this job passed on a bundle whose embedded install.ps1 was missing or + # broken -- the packaged-app failure the workflow exists to catch, and the + # one thing the macOS and Linux rows now check and Windows did not. + # tauri.conf.json:56-59 ships install.ps1 as a bundle resource, so find it + # where NSIS put it and invoke it as install.rs:326-341 does. + $root = Split-Path -Parent $env:APP_EXE + $ps1 = Get-ChildItem -Path $root -Recurse -Filter 'install.ps1' -ErrorAction SilentlyContinue | + Select-Object -First 1 + if (-not $ps1) { + Write-Host '::error::the bundle ships no install.ps1 resource' + exit 1 + } + Write-Host "bundled installer: $($ps1.FullName)" + # --tauri rejects a custom studio home (install.ps1:189-215), so drop the + # workspace-scoped override the same way install.rs scrubs it (354-357). + Remove-Item Env:UNSLOTH_STUDIO_HOME -ErrorAction SilentlyContinue + & powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass ` + -File $ps1.FullName --tauri *>&1 | Tee-Object -FilePath logs/bundled-install.log + $rc = $LASTEXITCODE + Write-Host "bundled installer exit: $rc" + if ($rc -ne 0) { + Write-Host "::error::bundled installer exited $rc" + exit $rc + } + # The exit code alone is not enough: it is the venv the app then boots from. + $py = Join-Path $env:USERPROFILE '.unsloth\studio\unsloth_studio\Scripts\python.exe' + if (-not (Test-Path $py)) { + Write-Host "::error::bundled installer left no venv at $py" + exit 1 + } + & $py -V + # install.rs passes only --tauri, so torch is part of first launch, and a + # venv that cannot import it is the unbootable environment from the report. + & $py -c "import torch; print('torch', torch.__version__)" + if ($LASTEXITCODE -ne 0) { + Write-Host '::error::the bundled install produced a venv with no working torch' + exit 1 + } + - name: Launch and prove it stays up shell: pwsh run: |