[llvm] [llvm][release] Modernize the Windows release script (PR #228528)
Alexandre Ganea via llvm-commits
llvm-commits at lists.llvm.org
Fri Oct 2 11:51:10 PDT 2026
================
@@ -0,0 +1,2347 @@
+#!/usr/bin/env pwsh
+<#
+.SYNOPSIS
+ Script for building the LLVM installer on Windows,
+ used for the releases at https://github.com/llvm/llvm-project/releases
+
+.DESCRIPTION
+ Builds LLVM release packages for Windows (x64, arm64).
+ Performs a 2-stage build with a ThinLTO final stage and optional PGO.
+ Can bootstrap a fresh VM by installing all prerequisites.
+
+ By default, builds x64 using the local source tree and auto-detected
+ Python. Use -DownloadSource to download a tagged release tarball
+ instead.
+
+ Build steps (in order):
+ 1. libxml2 - Build libxml2, zlib, and zstd (inside stage 1 directory)
+ 2. stage1 - Build the stage 1 bootstrap compiler with the host compiler
+ 3. pgo - PGO instrumented build + training + profile merge
+ 4. stage2 - Build the stage 2 self-hosted compiler with stage 1 clang
+ 5. package - Create WiX MSI installer
+ 6. tarball - Generate full install tarball
+
+.PARAMETER Version
+ LLVM version string (e.g. "19.1.0"). If omitted, auto-detected from
+ the source tree.
+
+.PARAMETER x64
+ Build for x64 (64-bit). This is the default if no architecture is specified.
+
+.PARAMETER arm64
+ Build for ARM64 (AArch64).
+
+.PARAMETER DownloadSource
+ Download and extract the tagged release source tarball from GitHub
+ instead of using the local source tree. Requires -Version.
+
+.PARAMETER ForceMSVC
+ Force using MSVC (cl.exe) as the stage 0 host compiler instead of the
+ official LLVM release. Skips the LLVM release prerequisite.
+
+.PARAMETER FastBuild
+ Build only the stage 1 tools needed by stage 2 and skip the remaining
+ stage 1 targets, stage 1 tests, and PGO. Stage 2 tests still run.
+
+.PARAMETER InstallPrerequisites
+ Install build prerequisites (NetFx3, Visual Studio, official LLVM release,
+ CMake, Python and its psutil module, etc.) before building.
+ When used with -arm64, also installs ARM64 Python for LLDB.
+ Requests administrator elevation once for the entire prerequisite
+ installation when the current shell is not elevated.
+
+.PARAMETER Unattended
+ Never prompt for input. Missing prerequisites cause the build to fail
+ unless -InstallPrerequisites is also specified. An existing build
+ directory must be resumed with -StartAt or removed beforehand. Run
+ from an elevated shell when using -InstallPrerequisites.
+
+.PARAMETER StartAt
+ Resume a previous build from a specific step. Artifacts from earlier
+ steps must already exist on disk. The step being restarted gets a
+ clean directory; earlier steps are skipped entirely.
+
+ Valid steps: libxml2, stage1, pgo, stage2, package, tarball
+
+ Pass an empty string (-StartAt "") or "?" (-StartAt "?") to display
+ the list of available steps and exit.
+
+.PARAMETER Help
+ Display this help message and exit.
+
+.EXAMPLE
+ .\build_llvm_release.ps1
+ Full x64 build using the local source tree.
+
+.EXAMPLE
+ .\build_llvm_release.ps1 -arm64
+ Build for ARM64.
+
+.EXAMPLE
+ .\build_llvm_release.ps1 -Version 19.1.0 -DownloadSource
+ Download version 19.1.0 sources and build x64.
+
+.EXAMPLE
+ .\build_llvm_release.ps1 -InstallPrerequisites -x64
+ Install prerequisites, then do a full x64 build.
+
+.EXAMPLE
+ .\build_llvm_release.ps1 -Unattended -InstallPrerequisites
+ From an elevated shell, install prerequisites and build without prompts.
+
+.EXAMPLE
+ .\build_llvm_release.ps1 -x64 -StartAt stage2
+ Resume from stage 2 (reuses the stage 1 bootstrap compiler and PGO profile).
+
+.EXAMPLE
+ .\build_llvm_release.ps1 -x64 -StartAt package
+ Resume at WiX MSI packaging, then regenerate the portable archive.
+
+.EXAMPLE
+ .\build_llvm_release.ps1 -StartAt ""
+ Display the list of available build steps and exit.
+
+.NOTES
+ Python is auto-detected from PATH for x64 builds. For ARM64 builds,
+ the script probes standard install locations for ARM64 Python. Use
+ -InstallPrerequisites to install it automatically.
+
+ Environment variables:
+ LLVM_NINJA_OVERRIDE - Override the ninja binary and optionally provide
+ extra flags. The first token is the executable,
+ remaining tokens are prepended to every ninja
+ invocation.
+ Example: LLVM_NINJA_OVERRIDE="myninja.exe --flag1 --flag2"
+#>
+
+param(
+ [string]$Version,
+ [switch]$x64,
+ [switch]$arm64,
+ [switch]$DownloadSource,
+ [switch]$ForceMSVC,
+ [switch]$FastBuild,
+ [switch]$InstallPrerequisites,
+ [switch]$Unattended,
+ [switch]$PrerequisitesOnly, # Internal mode for the elevated installer.
+ [string]$StartAt,
+ [switch]$Help
+)
+
+#===============================================================================
+# PowerShell 7 self-relaunch
+#
+# This script requires PowerShell 7+ features (ternary operator, null-
+# coalescing, improved error handling, etc.). If we detect we are running
+# under Windows PowerShell 5.x, we attempt to find or install PowerShell 7
+# and re-launch ourselves under it, forwarding all original arguments.
+#===============================================================================
+if ($PSVersionTable.PSVersion.Major -lt 7) {
+ # Try to find pwsh in PATH first.
+ $pwsh = Get-Command pwsh -ErrorAction SilentlyContinue
+ if (-not $pwsh) {
+ # Check the default install location.
+ $defaultPath = "$env:ProgramFiles\PowerShell\7\pwsh.exe"
+ if (Test-Path $defaultPath) {
+ $pwsh = Get-Item $defaultPath
+ }
+ }
+
+ if (-not $pwsh) {
+ if ($Unattended) {
+ Write-Error "PowerShell 7 is required. Install it before running in unattended mode."
+ exit 1
+ }
+ Write-Host "PowerShell 7 is required but not found." -ForegroundColor Yellow
+ Write-Host "Attempting to install via winget..." -ForegroundColor Yellow
+ $winget = Get-Command winget -ErrorAction SilentlyContinue
+ if (-not $winget) {
+ Write-Error "Cannot install PowerShell 7: winget is not available.`nPlease install PowerShell 7 manually from https://aka.ms/powershell-release?tag=stable"
+ exit 1
+ }
+ winget install --id Microsoft.PowerShell --source winget --accept-source-agreements --accept-package-agreements
+ if ($LASTEXITCODE -ne 0) {
+ Write-Error "Failed to install PowerShell 7 via winget (exit code $LASTEXITCODE)."
+ exit 1
+ }
+ # Refresh PATH so we can find the newly installed pwsh.
+ $env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" +
+ [System.Environment]::GetEnvironmentVariable("Path", "User")
+ $pwsh = Get-Command pwsh -ErrorAction SilentlyContinue
+ if (-not $pwsh) {
+ $defaultPath = "$env:ProgramFiles\PowerShell\7\pwsh.exe"
+ if (Test-Path $defaultPath) {
+ $pwsh = Get-Item $defaultPath
+ }
+ }
+ if (-not $pwsh) {
+ Write-Error "PowerShell 7 was installed but pwsh.exe could not be found in PATH."
+ exit 1
+ }
+ Write-Host "PowerShell 7 installed successfully." -ForegroundColor Green
+ }
+
+ # Re-launch this script under PowerShell 7, forwarding all arguments.
+ # Note: avoid PS7-only syntax (?? ternary, etc.) in this block since
+ # it executes under PS5 (PowerShell 5).
+ $pwshPath = if ($pwsh.Source) { $pwsh.Source } else { $pwsh.Path }
+ Write-Host "Re-launching under PowerShell 7 ($pwshPath)..." -ForegroundColor Cyan
+ $forwardArgs = @()
+ foreach ($entry in $PSBoundParameters.GetEnumerator()) {
+ if ($entry.Value -is [System.Management.Automation.SwitchParameter]) {
+ if ($entry.Value.IsPresent) { $forwardArgs += "-$($entry.Key)" }
+ } else {
+ $forwardArgs += "-$($entry.Key)"
+ # Windows PowerShell 5.1 drops empty-string arguments when calling
+ # native programs; a literal pair of quotes survives as "".
+ $stringValue = [string]$entry.Value
+ $forwardArgs += if ($stringValue -eq '') { '""' } else { $stringValue }
+ }
+ }
+ & $pwshPath -NoProfile -ExecutionPolicy Bypass -File $PSCommandPath @forwardArgs
+ exit $LASTEXITCODE
+}
+
+Set-StrictMode -Version Latest
+$ErrorActionPreference = 'Stop'
+$script:ReleaseScriptPath = $PSCommandPath
+
+# Save the console mode so we can restore it at exit. Child processes
+# (cmake, ninja, link.exe, etc.) sometimes disable virtual terminal
+# processing and don't restore it, which breaks arrow keys and ESC in
+# the parent shell after the script finishes.
+$script:SavedConsoleMode = $null
+try {
+ Add-Type -TypeDefinition @'
+using System;
+using System.Runtime.InteropServices;
+public static class ConsoleMode {
+ [DllImport("kernel32.dll", SetLastError = true)]
+ static extern IntPtr GetStdHandle(int nStdHandle);
+ [DllImport("kernel32.dll", SetLastError = true)]
+ static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode);
+ [DllImport("kernel32.dll", SetLastError = true)]
+ static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode);
+ const int STD_INPUT_HANDLE = -10;
+ public static uint Get() {
+ uint mode;
+ GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), out mode);
+ return mode;
+ }
+ public static void Set(uint mode) {
+ SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), mode);
+ }
+}
+'@ -ErrorAction SilentlyContinue
+ $script:SavedConsoleMode = [ConsoleMode]::Get()
+} catch {
+ # Non-fatal; we just won't be able to restore the console mode.
+}
+
+# Default to x64 if no architecture specified
+if (-not $x64 -and -not $arm64) {
+ $x64 = $true
+}
+
+#===============================================================================
+# Step-resume support
+#===============================================================================
+
+$script:StepOrder = @('libxml2','stage1','pgo','stage2','package','tarball')
+
+if ($PSBoundParameters.ContainsKey('StartAt')) {
+ if (-not $StartAt -or $StartAt -eq '?') {
+ Write-Host "Available -StartAt steps (in order):" -ForegroundColor Cyan
+ for ($i = 0; $i -lt $script:StepOrder.Count; $i++) {
+ Write-Host " $($i + 1). $($script:StepOrder[$i])"
+ }
+ exit 0
+ }
+ if ($StartAt -notin $script:StepOrder) {
+ Write-Error "Invalid -StartAt value: '$StartAt'. Valid steps: $($script:StepOrder -join ', ')"
+ exit 1
+ }
+}
+
+$script:StartAtStep = if ($StartAt) { $StartAt } else { $null }
+
+function Test-ShouldRun {
+ <#
+ .SYNOPSIS
+ Returns $true if the given step should execute (i.e. it is at or after
+ the -StartAt step). When -StartAt is not set, always returns $true.
+ #>
+ param([Parameter(Mandatory)][string]$Step)
+ if (-not $script:StartAtStep) { return $true }
+ return $script:StepOrder.IndexOf($Step) -ge $script:StepOrder.IndexOf($script:StartAtStep)
+}
+
+function Test-IsStartStep {
+ <#
+ .SYNOPSIS
+ Returns $true if the given step is exactly the -StartAt step (the step
+ whose directory should be cleaned before a fresh rebuild).
+ #>
+ param([Parameter(Mandatory)][string]$Step)
+ return ($script:StartAtStep -and $script:StartAtStep -eq $Step)
+}
+
+function Assert-PathExists {
+ <#
+ .SYNOPSIS
+ Validates that a path exists on disk. Used when skipping steps to
+ ensure the artifacts from a prior run are still present.
+ #>
+ param(
+ [Parameter(Mandatory)][string]$Path,
+ [string]$Description = $Path
+ )
+ if (-not (Test-Path $Path)) {
+ Write-Error "Required artifact missing: $Description`n Path: $Path`n Hint: run the earlier build steps first, or use a different -StartAt value."
+ exit 1
+ }
+}
+
+function Remove-StepDirectory {
+ <#
+ .SYNOPSIS
+ Removes a directory (or file) if it exists, printing a message.
+ Used to clean a step's build directory before restarting it.
+ #>
+ param([Parameter(Mandatory)][string]$Path)
+ if (Test-Path $Path) {
+ Write-SubStep "Cleaning: $Path"
+ Remove-Item -Recurse -Force $Path
+ }
+}
+
+# Ninja override: allow using a custom ninja binary with extra flags.
+# Usage: $env:LLVM_NINJA_OVERRIDE = "myninja.exe --flag1 --flag2"
+if ($env:LLVM_NINJA_OVERRIDE) {
+ $tokens = @($env:LLVM_NINJA_OVERRIDE.Trim() -split '\s+')
+ $script:NinjaCommand = $tokens[0]
+ [string[]]$script:NinjaExtraArgs = if ($tokens.Count -gt 1) { $tokens[1..($tokens.Count - 1)] } else { @() }
+ Write-Host "Using custom ninja: $($script:NinjaCommand)" -ForegroundColor Cyan
+ if ($script:NinjaExtraArgs.Count -gt 0) {
+ Write-Host " Extra ninja args: $($script:NinjaExtraArgs -join ' ')" -ForegroundColor Cyan
+ }
+} else {
+ $script:NinjaCommand = 'ninja'
+ $script:NinjaExtraArgs = @()
+}
+
+# Filter out tests that are known to fail.
+$env:LIT_FILTER_OUT = "gh110231.cpp|crt_initializers.cpp|init-order-atexit.cpp|use_after_return_linkage.cpp|initialization-bug.cpp|initialization-bug-no-global.cpp|trace-malloc-unbalanced.test|trace-malloc-2.test|TraceMallocTest|TestLockFileExclusive"
+
+#===============================================================================
+# Utility functions
+#===============================================================================
+
+function Write-Step {
+ param([string]$Message)
+ Write-Host "`n==== $Message ====`n" -ForegroundColor Cyan
+}
+
+function Write-SubStep {
+ param([string]$Message)
+ Write-Host " -- $Message" -ForegroundColor DarkCyan
+}
+
+function Write-CMakeCacheFile {
----------------
aganea wrote:
Yes, I'll do that.
https://github.com/llvm/llvm-project/pull/228528
More information about the llvm-commits
mailing list