-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsetup.ps1
More file actions
202 lines (181 loc) · 8.97 KB
/
Copy pathsetup.ps1
File metadata and controls
202 lines (181 loc) · 8.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# Workshop setup for Windows. One command, from the course root:
#
# powershell -ExecutionPolicy Bypass -File setup.ps1
#
# Like setup.sh, this exists to solve one problem -- getting a Python 3.12 onto the machine --
# and then hands over to setup.py for the model work. On Windows that matters more than
# anywhere else: a fresh Windows has no Python at all, so nothing written in Python can be the
# entry point. PowerShell is always there.
#
# ... -File setup.ps1 -Yes # assume yes (pre-session run)
# ... -File setup.ps1 -- --dry-run # plan only
# ... -File setup.ps1 -- --tier qwen # anything after -- is passed to setup.py
[CmdletBinding()]
param(
[switch]$Yes,
[Parameter(ValueFromRemainingArguments = $true)]
[string[]]$Passthrough = @()
)
# "Stop", so that a cmdlet which fails stops the script instead of carrying on with $null.
#
# The problem this used to be set to "SilentlyContinue" for is real, but narrower than the
# script: under "Stop", PowerShell 5.x turns anything a NATIVE command writes to stderr into a
# terminating error -- and probing for an interpreter means running commands whose "not found"
# answer IS stderr chatter (py.exe: "no suitable runtime"; uv: "no interpreter found"). Setting
# it script-wide fixed those four calls and silenced every cmdlet in the file at the same time,
# which is a blanket try/catch-and-continue around the whole thing: Join-Path, Test-Path,
# Get-Command or a file write could fail, hand back $null, and be used anyway.
#
# So the tolerance lives where the native calls are instead: `Invoke-Native` and `Invoke-Step`
# each drop the preference to "Continue" in their own function scope, which covers the command
# they run and nothing else. Their exit status is still checked explicitly via $LASTEXITCODE.
$ErrorActionPreference = "Stop"
$Series = "3.12"
$UvUrl = "https://astral.sh/uv/install.ps1"
$PythonOrg = "https://www.python.org/downloads/"
$root = Split-Path -Parent $MyInvocation.MyCommand.Path
$dryRun = $Passthrough -contains "--dry-run"
function Say($text) { Write-Host $text }
function Invoke-Native {
<#
Run a probe and hand back its stdout, tolerating whatever it says on stderr.
$ErrorActionPreference is assigned inside the function, so it is function-scoped: it
applies to this call and leaves the script's "Stop" alone. Callers read $LASTEXITCODE,
which native commands set globally and a function call does not disturb.
#>
param([string]$Exe, [string[]]$Arguments, [switch]$Merge)
$ErrorActionPreference = "Continue"
if ($Merge) { return (& $Exe @Arguments 2>&1) }
return (& $Exe @Arguments 2>$null)
}
function Confirm-Step($question) {
if ($Yes) { return $true }
if (-not [Environment]::UserInteractive) {
Say " (not interactive, so nothing is assumed -- re-run with -Yes)"
return $false
}
$reply = Read-Host " $question [Y/n]"
return ($reply -eq "" -or $reply -match '^(y|yes)$')
}
function Invoke-Step {
param([string]$Exe, [string[]]$Arguments)
Say " `$ $Exe $($Arguments -join ' ')"
if (-not (Confirm-Step "run it?")) { Say "declined -- nothing was changed"; exit 1 }
# Function-scoped, as in Invoke-Native: winget and the uv installer both write progress and
# warnings to stderr, and under "Stop" the first such line would abort the script instead of
# letting the exit code decide. Output is NOT silenced here -- an install is the one thing
# the learner should watch.
$ErrorActionPreference = "Continue"
& $Exe @Arguments
if ($LASTEXITCODE -ne 0) { return $false }
return $true
}
function Test-NewEnough($exe) {
if (-not $exe) { return $false }
# The interpreter answers for itself, rather than us parsing a version string.
Invoke-Native $exe @("-c", "import sys; sys.exit(0 if sys.version_info[:2] >= (3, 12) else 1)") | Out-Null
return ($LASTEXITCODE -eq 0)
}
function Find-Python {
# The py launcher first: it is the one thing that can name a specific series on Windows.
if (Get-Command py -ErrorAction SilentlyContinue) {
$found = Invoke-Native "py" @("-$Series", "-c", "import sys; print(sys.executable)")
if ($LASTEXITCODE -eq 0 -and $found -and (Test-NewEnough $found)) { return $found }
}
foreach ($name in @("python3.12", "python3", "python")) {
$command = Get-Command $name -ErrorAction SilentlyContinue
# Skip the Microsoft Store stub, which exists as an app-execution alias and exits 9009.
if ($command -and $command.Source -notlike "*WindowsApps*" -and (Test-NewEnough $command.Source)) {
return $command.Source
}
}
foreach ($uv in @("uv", "$env:USERPROFILE\.local\bin\uv.exe", "$env:USERPROFILE\.cargo\bin\uv.exe")) {
$resolved = if ($uv -eq "uv") { (Get-Command uv -ErrorAction SilentlyContinue).Source } else { $uv }
if ($resolved -and (Test-Path $resolved)) {
$found = Invoke-Native $resolved @("python", "find", $Series)
if ($found -and (Test-Path $found) -and (Test-NewEnough $found)) { return $found }
}
}
return $null
}
function Install-WithWinget {
if (-not (Get-Command winget -ErrorAction SilentlyContinue)) { return $false }
return (Invoke-Step "winget" @("install", "-e", "--id", "Python.Python.$Series",
"--accept-package-agreements", "--accept-source-agreements"))
}
function Install-WithUv {
$uv = (Get-Command uv -ErrorAction SilentlyContinue).Source
if (-not $uv) {
foreach ($candidate in @("$env:USERPROFILE\.local\bin\uv.exe", "$env:USERPROFILE\.cargo\bin\uv.exe")) {
if (Test-Path $candidate) { $uv = $candidate; break }
}
}
if (-not $uv) {
Say " installing uv, to fetch a real CPython:"
if (-not (Invoke-Step "powershell" @("-ExecutionPolicy", "ByPass", "-c", "irm $UvUrl | iex"))) {
return $null
}
foreach ($candidate in @("$env:USERPROFILE\.local\bin\uv.exe", "$env:USERPROFILE\.cargo\bin\uv.exe")) {
if (Test-Path $candidate) { $uv = $candidate; break }
}
}
if (-not $uv) { Say " uv installed but cannot be found -- open a new PowerShell and retry"; return $null }
if (-not (Invoke-Step $uv @("python", "install", $Series))) { return $null }
# `uv python find`, not PATH: uv installs into ~\.local\bin, which this session has not
# picked up yet.
$found = Invoke-Native $uv @("python", "find", $Series)
if ($found -and (Test-Path $found)) { return $found }
return $null
}
Say "agentfix setup"
$winget = if (Get-Command winget -ErrorAction SilentlyContinue) { "winget" } else { "none found" }
Say " machine: windows, package manager: $winget"
$python = Find-Python
if (-not $python) {
Say ""
Say "[todo] python: nothing here is $Series or newer"
if ($dryRun) {
Say " would install Python $Series with $winget (or uv), then run setup.py under it"
Say ""
Say "READY (dry run -- nothing was changed)"
exit 0
}
if (Install-WithWinget) { $python = Find-Python }
if (-not $python) {
if ($winget -ne "none found") {
Say " winget did not produce a Python $Series -- trying the portable uv installer instead."
}
$python = Install-WithUv
}
if (-not $python -or -not (Test-NewEnough $python)) {
Say ""
Say "Could not get Python $Series onto this machine."
Say "Install it from $PythonOrg, then run this script again."
exit 1
}
Say "[ok] python: $(Invoke-Native $python @("-V") -Merge) at $python"
}
# Everything from here -- the tier, the models, the context window, .agentfix.env, the
# MELLUM_MODEL variable -- is identical on every OS, so it lives in setup.py and nowhere else.
function Invoke-SetupPy {
# Function-scoped tolerance, as in Invoke-Native. It is not cosmetic here: `ollama pull`
# writes its progress to stderr, and under the script's "Stop" the first such line would
# abort this script part-way through an 8 GB download.
param([string]$Exe, [string[]]$Arguments)
$ErrorActionPreference = "Continue"
& $Exe @Arguments
}
$arguments = @((Join-Path $root "setup.py"), "--bootstrapped")
if ($Yes) { $arguments += "--yes" }
$arguments += $Passthrough
# Called as a STATEMENT, and read from $LASTEXITCODE afterwards. Not `exit (Invoke-SetupPy ...)`:
# a subexpression consumes the pipeline, so every line setup.py printed is collected as objects
# and then thrown away turning the value into an exit code. The learner sees setup start and then
# silence, and never sees a confirmation prompt, so an interactive run hangs with nothing on
# screen. Measured on Windows 11: setup.ps1 printed setup.py's first two lines and stopped, while
# `python setup.py --dry-run` on the same machine ran to completion.
#
# At statement level nothing consumes the output, so PowerShell writes it straight to the host,
# and $LASTEXITCODE is global -- a function call does not disturb it.
Invoke-SetupPy $python $arguments
exit $LASTEXITCODE