[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