build_windows.ps1 15.5 KB
Newer Older
1
2
3
4
5
6
7
8
#!powershell
#
# powershell -ExecutionPolicy Bypass -File .\scripts\build_windows.ps1
#
# gcloud auth application-default login

$ErrorActionPreference = "Stop"

9
10
11
mkdir -Force -path .\dist | Out-Null

function checkEnv {
12
13
14
15
16
17
18
19
20
21
22
    if ($null -ne $env:ARCH ) {
        $script:ARCH = $env:ARCH
    } else {
        $arch=([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)
        if ($null -ne $arch) {
            $script:ARCH = ($arch.ToString().ToLower()).Replace("x64", "amd64")
        } else {
            write-host "WARNING: old powershell detected, assuming amd64 architecture - set `$env:ARCH to override"
            $script:ARCH="amd64"
        }
    }
23
    $script:TARGET_ARCH=$script:ARCH
24
    Write-host "Building for ${script:TARGET_ARCH}"
25
26
    write-host "Locating required tools and paths"
    $script:SRC_DIR=$PWD
27

28
29
30
    # Locate CUDA versions
    $cudaList=(get-item "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v*\bin\" -ea 'silentlycontinue')
    if ($cudaList.length -eq 0) {
31
        $d=(get-command -ea 'silentlycontinue' nvcc).path
32
33
        if ($null -ne $d) {
            $script:CUDA_DIRS=@($d| split-path -parent)
34
35
        }
    } else {
36
37
38
39
40
41
42
43
44
45
46
47
48
49
        # Favor newer patch versions if available
        $script:CUDA_DIRS=($cudaList | sort-object -Descending)
    }
    if ($script:CUDA_DIRS.length -gt 0) {
        write-host "Available CUDA Versions: $script:CUDA_DIRS"
    } else {
        write-host "No CUDA versions detected"
    }

    # Locate ROCm version
    if ($null -ne $env:HIP_PATH) {
        $script:HIP_PATH=$env:HIP_PATH
    } else {
        $script:HIP_PATH=(get-item "C:\Program Files\AMD\ROCm\*\bin\" -ea 'silentlycontinue' | sort-object -Descending)
50
51
    }
    
52
53
54
55
    $inoSetup=(get-item "C:\Program Files*\Inno Setup*\")
    if ($inoSetup.length -gt 0) {
        $script:INNO_SETUP_DIR=$inoSetup[0]
    }
56

57
    $script:DIST_DIR="${script:SRC_DIR}\dist\windows-${script:TARGET_ARCH}"
58
    $env:CGO_ENABLED="1"
59
    Write-Output "Checking version"
60
61
62
63
64
65
66
67
68
    if (!$env:VERSION) {
        $data=(git describe --tags --first-parent --abbrev=7 --long --dirty --always)
        $pattern="v(.+)"
        if ($data -match $pattern) {
            $script:VERSION=$matches[1]
        }
    } else {
        $script:VERSION=$env:VERSION
    }
69
    $pattern = "(\d+[.]\d+[.]\d+).*"
70
    if ($script:VERSION -match $pattern) {
71
        $script:PKG_VERSION=$matches[1]
72
    } else {
73
        $script:PKG_VERSION="0.0.0"
74
75
76
    }
    write-host "Building Ollama $script:VERSION with package version $script:PKG_VERSION"

77
78
79
80
81
82
    # Note: Windows Kits 10 signtool crashes with GCP's plugin
    if ($null -eq $env:SIGN_TOOL) {
        ${script:SignTool}="C:\Program Files (x86)\Windows Kits\8.1\bin\x64\signtool.exe"
    } else {
        ${script:SignTool}=${env:SIGN_TOOL}
    }
83
    if ("${env:KEY_CONTAINER}") {
84
        ${script:OLLAMA_CERT}=$(resolve-path "${script:SRC_DIR}\ollama_inc.crt")
85
86
87
88
        Write-host "Code signing enabled"
    } else {
        write-host "Code signing disabled - please set KEY_CONTAINERS to sign and copy ollama_inc.crt to the top of the source tree"
    }
89
    $script:JOBS=([Environment]::ProcessorCount)
90
91
92
}


93
94
function cpu {
    mkdir -Force -path "${script:DIST_DIR}\" | Out-Null
Daniel Hiltgen's avatar
Daniel Hiltgen committed
95
    if ($script:ARCH -ne "arm64") {
96
        Remove-Item -ea 0 -recurse -force -path "${script:SRC_DIR}\dist\windows-${script:ARCH}"
Michael Yang's avatar
Michael Yang committed
97
98
        New-Item "${script:SRC_DIR}\dist\windows-${script:ARCH}\lib\ollama\" -ItemType Directory -ea 0

99
        & cmake -B build\cpu --preset CPU --install-prefix $script:DIST_DIR
100
        if ($LASTEXITCODE -ne 0) { exit($LASTEXITCODE)}
101
        & cmake --build build\cpu --target ggml-cpu --config Release --parallel $script:JOBS
102
        if ($LASTEXITCODE -ne 0) { exit($LASTEXITCODE)}
103
        & cmake --install build\cpu --component CPU --strip
Michael Yang's avatar
Michael Yang committed
104
        if ($LASTEXITCODE -ne 0) { exit($LASTEXITCODE)}
105
106
    }
}
Michael Yang's avatar
Michael Yang committed
107

108
function cuda11 {
109
110
111
    # CUDA v11 claims to be compatible with MSVC 2022, but the latest updates are no longer compatible
    # 19.40 is the last compiler version that works, but recent udpates are 19.43
    # So this pins to MSVC 2019 for best compatibility
112
113
    mkdir -Force -path "${script:DIST_DIR}\" | Out-Null
    $cudaMajorVer="11"
114
    if ($script:ARCH -ne "arm64") {
115
116
117
118
119
120
121
122
123
124
        if ("$script:CUDA_DIRS".Contains("v$cudaMajorVer")) {
            foreach ($d in $Script:CUDA_DIRS){ 
                if ($d.FullName.Contains("v$cudaMajorVer")) {
                    if (test-path -literalpath (join-path -path $d -childpath "nvcc.exe" ) ) {
                        $cuda=($d.FullName|split-path -parent)
                        break
                    }
                }
            }
            write-host "Building CUDA v$cudaMajorVer backend libraries $cuda"
125
            $env:CUDAToolkit_ROOT=$cuda
126
            & cmake -B build\cuda_v$cudaMajorVer --preset "CUDA $cudaMajorVer" -T cuda="$cuda" -DCMAKE_CUDA_COMPILER="$cuda\bin\nvcc.exe" -G "Visual Studio 16 2019" --install-prefix "$script:DIST_DIR"
127
            if ($LASTEXITCODE -ne 0) { exit($LASTEXITCODE)}
128
            & cmake --build build\cuda_v$cudaMajorVer --target ggml-cuda --config Release --parallel $script:JOBS
129
            if ($LASTEXITCODE -ne 0) { exit($LASTEXITCODE)}
130
            & cmake --install build\cuda_v$cudaMajorVer --component "CUDA" --strip
131
            if ($LASTEXITCODE -ne 0) { exit($LASTEXITCODE)}
132
133
        } else {
            write-host "CUDA v$cudaMajorVer not detected, skipping"
134
        }
135
136
    } else {
        write-host "not arch we wanted"
137
    }
138
    write-host "done"
139
140
}

141
142
143
144
145
function cudaCommon {
    param (
        [string]$cudaMajorVer
    )
    mkdir -Force -path "${script:DIST_DIR}\" | Out-Null
146
    if ($script:ARCH -ne "arm64") {
147
148
149
150
151
152
153
154
155
156
        if ("$script:CUDA_DIRS".Contains("v$cudaMajorVer")) {
            foreach ($d in $Script:CUDA_DIRS){ 
                if ($d.FullName.Contains("v$cudaMajorVer")) {
                    if (test-path -literalpath (join-path -path $d -childpath "nvcc.exe" ) ) {
                        $cuda=($d.FullName|split-path -parent)
                        break
                    }
                }
            }
            write-host "Building CUDA v$cudaMajorVer backend libraries $cuda"
157
            $env:CUDAToolkit_ROOT=$cuda
158
            & cmake -B build\cuda_v$cudaMajorVer --preset "CUDA $cudaMajorVer" -T cuda="$cuda" --install-prefix "$script:DIST_DIR"
159
            if ($LASTEXITCODE -ne 0) { exit($LASTEXITCODE)}
160
            & cmake --build build\cuda_v$cudaMajorVer --target ggml-cuda --config Release --parallel $script:JOBS
161
            if ($LASTEXITCODE -ne 0) { exit($LASTEXITCODE)}
162
            & cmake --install build\cuda_v$cudaMajorVer --component "CUDA" --strip
163
            if ($LASTEXITCODE -ne 0) { exit($LASTEXITCODE)}
164
165
        } else {
            write-host "CUDA v$cudaMajorVer not detected, skipping"
166
        }
167
168
169
    }
}

170
171
172
173
174
175
function cuda12 {
    cudaCommon("12")
}

function cuda13 {
    cudaCommon("13")
176
177
}

178
179
function rocm {
    mkdir -Force -path "${script:DIST_DIR}\" | Out-Null
180
    if ($script:ARCH -ne "arm64") {
181
182
        if ($script:HIP_PATH) {
            write-host "Building ROCm backend libraries $script:HIP_PATH"
Daniel Hiltgen's avatar
Daniel Hiltgen committed
183
            if (-Not (get-command -ErrorAction silent ninja)) {
184
                $NINJA_DIR=(gci -path (Get-CimInstance MSFT_VSInstance -Namespace root/cimv2/vs)[0].InstallLocation -r -fi ninja.exe).Directory.FullName
Daniel Hiltgen's avatar
Daniel Hiltgen committed
185
                $env:PATH="$NINJA_DIR;$env:PATH"
186
            }
187
            $env:HIPCXX="${script:HIP_PATH}\bin\clang++.exe"
188
            $env:HIP_PLATFORM="amd"
189
190
            $env:CMAKE_PREFIX_PATH="${script:HIP_PATH}"
            & cmake -B build\rocm --preset "ROCm 6" -G Ninja `
191
192
193
194
195
                -DCMAKE_C_COMPILER=clang `
                -DCMAKE_CXX_COMPILER=clang++ `
                -DCMAKE_C_FLAGS="-parallel-jobs=4 -Wno-ignored-attributes -Wno-deprecated-pragma" `
                -DCMAKE_CXX_FLAGS="-parallel-jobs=4 -Wno-ignored-attributes -Wno-deprecated-pragma" `
                --install-prefix $script:DIST_DIR
196
197
198
199
            if ($LASTEXITCODE -ne 0) { exit($LASTEXITCODE)}
            $env:HIPCXX=""
            $env:HIP_PLATFORM=""
            $env:CMAKE_PREFIX_PATH=""
200
            & cmake --build build\rocm --target ggml-hip --config Release --parallel $script:JOBS
201
            if ($LASTEXITCODE -ne 0) { exit($LASTEXITCODE)}
202
            & cmake --install build\rocm --component "HIP" --strip
203
            if ($LASTEXITCODE -ne 0) { exit($LASTEXITCODE)}
204
            Remove-Item -Path $script:DIST_DIR\lib\ollama\rocm\rocblas\library\*gfx906* -ErrorAction SilentlyContinue
205
206
        } else {
            write-host "ROCm not detected, skipping"
207
        }
208
    }
209
210
}

211
function vulkan {
212
213
    if ($env:VULKAN_SDK) {
        write-host "Building Vulkan backend libraries"
214
        & cmake -B build\vulkan --preset Vulkan --install-prefix $script:DIST_DIR
215
        if ($LASTEXITCODE -ne 0) { exit($LASTEXITCODE)}
216
        & cmake --build build\vulkan --target ggml-vulkan --config Release --parallel $script:JOBS
217
        if ($LASTEXITCODE -ne 0) { exit($LASTEXITCODE)}
218
        & cmake --install build\vulkan  --component Vulkan --strip
219
        if ($LASTEXITCODE -ne 0) { exit($LASTEXITCODE)}
220
221
    } else {
        write-host "Vulkan not detected, skipping"
222
223
224
    }
}

225
226
function ollama {
    mkdir -Force -path "${script:DIST_DIR}\" | Out-Null
227
    write-host "Building ollama CLI"
228
    & go build -trimpath -ldflags "-s -w -X=github.com/ollama/ollama/version.Version=$script:VERSION -X=github.com/ollama/ollama/server.mode=release" .
229
    if ($LASTEXITCODE -ne 0) { exit($LASTEXITCODE)}
Michael Yang's avatar
Michael Yang committed
230
    cp .\ollama.exe "${script:DIST_DIR}\"
231
232
}

233
234
function app {
    write-host "Building Ollama App $script:VERSION with package version $script:PKG_VERSION"
235

236
237
238
    if (!(Get-Command npm -ErrorAction SilentlyContinue)) {
        write-host "npm is not installed. Please install Node.js and npm first:"
        write-host "   Visit: https://nodejs.org/"
239
240
        exit 1
    }
241
242
243
244

    if (!(Get-Command tsc -ErrorAction SilentlyContinue)) {
        write-host "Installing TypeScript compiler..."
        npm install -g typescript
245
    }
246
247
248
249
250
251
    if (!(Get-Command tscriptify -ErrorAction SilentlyContinue)) {
        write-host "Installing tscriptify..."
        go install github.com/tkrajina/typescriptify-golang-structs/tscriptify@latest
    }
    if (!(Get-Command tscriptify -ErrorAction SilentlyContinue)) {
        $env:PATH="$env:PATH;$(go env GOPATH)\bin"
Daniel Hiltgen's avatar
Daniel Hiltgen committed
252
    }
253

254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
    Push-Location app/ui/app
    npm install
    if ($LASTEXITCODE -ne 0) { 
        write-host "ERROR: npm install failed with exit code $LASTEXITCODE"
        exit $LASTEXITCODE
    }

    write-host "Building React application..."
    npm run build
    if ($LASTEXITCODE -ne 0) { 
        write-host "ERROR: npm run build failed with exit code $LASTEXITCODE"
        exit $LASTEXITCODE
    }

    # Check if dist directory exists and has content
    if (!(Test-Path "dist")) {
        write-host "ERROR: dist directory was not created by npm run build"
        exit 1
    }

    $distFiles = Get-ChildItem "dist" -Recurse
    if ($distFiles.Count -eq 0) {
        write-host "ERROR: dist directory is empty after npm run build"
        exit 1
    }

    Pop-Location

    write-host "Running go generate"
    & go generate ./...
    if ($LASTEXITCODE -ne 0) { exit($LASTEXITCODE)}
	& go build -trimpath -ldflags "-s -w -H windowsgui -X=github.com/ollama/ollama/app/version.Version=$script:VERSION" -o .\dist\windows-ollama-app-${script:ARCH}.exe ./app/cmd/app/
    if ($LASTEXITCODE -ne 0) { exit($LASTEXITCODE)}
}

function deps {
    write-host "Download MSVC Redistributables"
    mkdir -Force -path "${script:SRC_DIR}\dist\\windows-arm64" | Out-Null
    mkdir -Force -path "${script:SRC_DIR}\dist\\windows-amd64" | Out-Null
    invoke-webrequest -Uri "https://aka.ms/vs/17/release/vc_redist.arm64.exe" -OutFile  "${script:SRC_DIR}\dist\windows-arm64\vc_redist.arm64.exe"
    invoke-webrequest -Uri "https://aka.ms/vs/17/release/vc_redist.x64.exe" -OutFile  "${script:SRC_DIR}\dist\windows-amd64\vc_redist.x64.exe"
    write-host "Done."
296
297
}

298
function sign {
299
    if ("${env:KEY_CONTAINER}") {
300
301
302
303
304
305
306
        write-host "Signing Ollama executables, scripts and libraries"
        & "${script:SignTool}" sign /v /fd sha256 /t http://timestamp.digicert.com /f "${script:OLLAMA_CERT}" `
            /csp "Google Cloud KMS Provider" /kc ${env:KEY_CONTAINER} `
            $(get-childitem -path "${script:SRC_DIR}\dist\windows-*" -r -include @('*.exe', '*.dll'))
        if ($LASTEXITCODE -ne 0) { exit($LASTEXITCODE)}
    } else {
        write-host "Signing not enabled"
307
308
309
    }
}

310
function installer {
311
    if ($null -eq ${script:INNO_SETUP_DIR}) {
312
313
        write-host "ERROR: missing Inno Setup installation directory - install from https://jrsoftware.org/isdl.php"
        exit 1
314
    }
315
316
317
318
    write-host "Building Ollama Installer"
    cd "${script:SRC_DIR}\app"
    $env:PKG_VERSION=$script:PKG_VERSION
    if ("${env:KEY_CONTAINER}") {
319
        & "${script:INNO_SETUP_DIR}\ISCC.exe" /DARCH=$script:TARGET_ARCH /SMySignTool="${script:SignTool} sign /fd sha256 /t http://timestamp.digicert.com /f ${script:OLLAMA_CERT} /csp `$qGoogle Cloud KMS Provider`$q /kc ${env:KEY_CONTAINER} `$f" .\ollama.iss
320
    } else {
321
        & "${script:INNO_SETUP_DIR}\ISCC.exe" /DARCH=$script:TARGET_ARCH .\ollama.iss
322
323
324
325
    }
    if ($LASTEXITCODE -ne 0) { exit($LASTEXITCODE)}
}

326
function zip {
Michael Yang's avatar
Michael Yang committed
327
    if (Test-Path -Path "${script:SRC_DIR}\dist\windows-amd64") {
328
329
330
331
332
333
334
335
336
337
        if (Test-Path -Path "${script:SRC_DIR}\dist\windows-amd64\lib\ollama\rocm") {
            write-host "Generating stand-alone distribution zip file ${script:SRC_DIR}\dist\ollama-windows-amd64-rocm.zip"
            # Temporarily adjust paths so we can retain the same directory structure
            Remove-Item -ea 0 -r "${script:SRC_DIR}\dist\windows-amd64-rocm"
            mkdir -Force -path "${script:SRC_DIR}\dist\windows-amd64-rocm\lib\ollama"
            Write-Output "Extract this ROCm zip file to the same location where you extracted ollama-windows-amd64.zip" > "${script:SRC_DIR}\dist\windows-amd64-rocm\README.txt"
            Move-Item -path "${script:SRC_DIR}\dist\windows-amd64\lib\ollama\rocm" -destination "${script:SRC_DIR}\dist\windows-amd64-rocm\lib\ollama"
            Compress-Archive -CompressionLevel Optimal -Path "${script:SRC_DIR}\dist\windows-amd64-rocm\*" -DestinationPath "${script:SRC_DIR}\dist\ollama-windows-amd64-rocm.zip" -Force
        }

Michael Yang's avatar
Michael Yang committed
338
        write-host "Generating stand-alone distribution zip file ${script:SRC_DIR}\dist\ollama-windows-amd64.zip"
339
340
        Compress-Archive -CompressionLevel Optimal -Path "${script:SRC_DIR}\dist\windows-amd64\*" -DestinationPath "${script:SRC_DIR}\dist\ollama-windows-amd64.zip" -Force
        if (Test-Path -Path "${script:SRC_DIR}\dist\windows-amd64-rocm") {
341
            Move-Item -destination "${script:SRC_DIR}\dist\windows-amd64\lib\ollama\rocm" -path "${script:SRC_DIR}\dist\windows-amd64-rocm\lib\ollama\rocm"
342
        }
Michael Yang's avatar
Michael Yang committed
343
344
345
346
    }

    if (Test-Path -Path "${script:SRC_DIR}\dist\windows-arm64") {
        write-host "Generating stand-alone distribution zip file ${script:SRC_DIR}\dist\ollama-windows-arm64.zip"
347
        Compress-Archive -CompressionLevel Optimal -Path "${script:SRC_DIR}\dist\windows-arm64\*" -DestinationPath "${script:SRC_DIR}\dist\ollama-windows-arm64.zip" -Force
Michael Yang's avatar
Michael Yang committed
348
    }
349
350
}

351
352
353
354
355
function clean {
    Remove-Item -ea 0 -r "${script:SRC_DIR}\dist\"
    Remove-Item -ea 0 -r "${script:SRC_DIR}\build\"
}

356
checkEnv
357
try {
358
    if ($($args.count) -eq 0) {
359
360
361
362
363
364
365
366
        cpu
        cuda12
        cuda13
        rocm
        vulkan
        ollama
        app
        deps
367
        sign
368
369
        installer
        zip
370
371
    } else {
        for ( $i = 0; $i -lt $args.count; $i++ ) {
372
            write-host "running build step $($args[$i])"
373
374
375
            & $($args[$i])
        } 
    }
376
377
378
379
380
381
} catch {
    write-host "Build Failed"
    write-host $_
} finally {
    set-location $script:SRC_DIR
    $env:PKG_VERSION=""
382
}