yams/packaging/windows/build-msi.ps1
2026-05-04 14:02:43 -04:00

237 lines
8.8 KiB
PowerShell

<#
.SYNOPSIS
Builds YAMS MSI installer using WiX Toolset v4+
.DESCRIPTION
This script builds an MSI installer for YAMS from the meson install output.
It requires WiX Toolset v4+ to be installed via: dotnet tool install --global wix
.PARAMETER StageDir
Path to the meson install --destdir output directory.
Default: .\build\release\stage
.PARAMETER Version
Product version string (e.g., 0.1.0 or nightly-20251126-abc1234).
Default: extracted from meson.build or "0.0.0"
.PARAMETER OutputDir
Directory where the MSI will be created.
Default: .\build\release
.EXAMPLE
.\build-msi.ps1 -StageDir .\build\release\stage -Version 0.1.0
#>
param(
[string]$StageDir = ".\build\release\stage",
[string]$Version = "",
[string]$OutputDir = ".\build\release"
)
$ErrorActionPreference = "Stop"
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$RepoRoot = (Get-Item "$ScriptDir\..\..").FullName
# If StageDir doesn't exist, try to create it via meson install
if (-not (Test-Path $StageDir)) {
Write-Host "Stage directory not found: $StageDir" -ForegroundColor Yellow
# Try to find the build directory
$buildDir = Split-Path $StageDir -Parent
if (-not (Test-Path (Join-Path $buildDir "build.ninja"))) {
# Default to build/release
$buildDir = Join-Path $RepoRoot "build\release"
}
if (Test-Path (Join-Path $buildDir "build.ninja")) {
Write-Host "Found build at: $buildDir" -ForegroundColor Cyan
# Reconfigure with root prefix for clean MSI staging
Write-Host "Reconfiguring meson with root prefix for MSI staging..." -ForegroundColor Cyan
& meson configure $buildDir --prefix=/
if ($LASTEXITCODE -ne 0) {
Write-Warning "meson configure failed, continuing with current prefix..."
}
Write-Host "Running meson install to create stage directory..." -ForegroundColor Cyan
$stageParent = Split-Path $StageDir -Parent
if (-not (Test-Path $stageParent)) {
New-Item -ItemType Directory -Path $stageParent -Force | Out-Null
}
& meson install -C $buildDir --destdir $StageDir
if ($LASTEXITCODE -ne 0) {
Write-Error "meson install failed. Please build first: .\setup.ps1 -BuildType Release"
exit 1
}
} else {
Write-Error @"
Stage directory not found and no build available.
Please either:
1. Build first: .\setup.ps1 -BuildType Release
2. Then package: .\setup.ps1 -BuildType Release -Package
Or manually:
1. Build: meson compile -C build/release
2. Stage: meson install -C build/release --destdir build/release/stage
3. Package: .\packaging\windows\build-msi.ps1 -StageDir build/release/stage
"@
exit 1
}
}
# Verify WiX is installed
Write-Host "Checking for WiX Toolset..." -ForegroundColor Cyan
$wixPath = Get-Command wix -ErrorAction SilentlyContinue
if (-not $wixPath) {
Write-Host "WiX Toolset not found. Installing via dotnet tool..." -ForegroundColor Yellow
& dotnet tool install --global wix --version 6.0.2
if ($LASTEXITCODE -ne 0) {
Write-Error "Failed to install WiX Toolset. Please install manually: dotnet tool install --global wix --version 6.0.2"
exit 1
}
# Refresh PATH
$env:PATH = [System.Environment]::GetEnvironmentVariable("PATH", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("PATH", "User")
}
# Verify WiX version
Write-Host "WiX version:" -ForegroundColor Cyan
& wix --version
# Add WiX UI extension (pin to 6.0.2 to match WiX toolset version)
Write-Host "Adding WiX UI extension..." -ForegroundColor Cyan
& wix extension add -g WixToolset.UI.wixext/6.0.2
if ($LASTEXITCODE -ne 0) {
Write-Warning "WiX UI extension may already be installed, continuing..."
}
# Resolve stage directory
$StageDir = (Resolve-Path $StageDir -ErrorAction Stop).Path
Write-Host "Stage directory: $StageDir" -ForegroundColor Cyan
# Find yams.exe anywhere in the stage directory
# meson install with --destdir prepends destdir to prefix, creating nested paths
Write-Host "Searching for yams.exe in stage directory..." -ForegroundColor Cyan
$yamsExeFiles = Get-ChildItem -Path $StageDir -Recurse -Filter "yams.exe" -ErrorAction SilentlyContinue
if ($yamsExeFiles.Count -eq 0) {
Write-Error "yams.exe not found anywhere in stage directory: $StageDir"
exit 1
}
# Use the first found yams.exe and derive the bin directory
$yamsBin = $yamsExeFiles[0].FullName
$binDir = Split-Path $yamsBin -Parent
# The "effective stage root" is the parent of bin/
$EffectiveStageDir = Split-Path $binDir -Parent
Write-Host "Found yams.exe at: $yamsBin" -ForegroundColor Green
Write-Host "Effective stage root: $EffectiveStageDir" -ForegroundColor Cyan
# Use EffectiveStageDir for WiX (it contains bin/yams.exe)
$StageDir = $EffectiveStageDir
$tbbDll = Join-Path $StageDir "bin\tbb12.dll"
$includeTbb = if (Test-Path $tbbDll) { "true" } else { "false" }
if ($includeTbb -eq "true") {
Write-Host "Including TBB runtime DLL: $tbbDll" -ForegroundColor Cyan
} else {
Write-Host "TBB runtime DLL not staged; skipping TBB component." -ForegroundColor Yellow
}
# Create LICENSE.rtf for WiX installer (WiX requires RTF format)
$licenseSource = Join-Path $RepoRoot "LICENSE"
$licenseRtf = Join-Path $StageDir "LICENSE.rtf"
if (Test-Path $licenseSource) {
Write-Host "Converting LICENSE to RTF format..." -ForegroundColor Cyan
$licenseText = Get-Content $licenseSource -Raw
# Simple RTF wrapper - escape special chars and wrap in RTF
$licenseText = $licenseText -replace '\\', '\\\\' -replace '\{', '\{' -replace '\}', '\}'
$rtfContent = "{\rtf1\ansi\deff0{\fonttbl{\f0 Consolas;}}\f0\fs18 " + ($licenseText -replace "`r`n", "\par`r`n" -replace "`n", "\par`r`n") + "}"
$rtfContent | Out-File -FilePath $licenseRtf -Encoding ascii
Write-Host "License RTF created at: $licenseRtf" -ForegroundColor Cyan
} else {
Write-Warning "LICENSE file not found at: $licenseSource"
}
# Extract version if not provided
if ([string]::IsNullOrEmpty($Version)) {
$mesonBuild = Join-Path $RepoRoot "meson.build"
if (Test-Path $mesonBuild) {
$content = Get-Content $mesonBuild -Raw
if ($content -match "version\s*:\s*'([^']+)'") {
$Version = $matches[1]
Write-Host "Extracted version from meson.build: $Version" -ForegroundColor Cyan
}
}
if ([string]::IsNullOrEmpty($Version)) {
$Version = "0.0.0"
Write-Host "Using default version: $Version" -ForegroundColor Yellow
}
}
# Normalize version for MSI (must be X.Y.Z.W format)
# Handle nightly-YYYYMMDD-hash format
if ($Version -match "nightly-(\d{4})(\d{2})(\d{2})-") {
$year = [int]$matches[1] - 2020 # Offset from 2020
$month = [int]$matches[2]
$day = [int]$matches[3]
$MsiVersion = "$year.$month.$day.0"
Write-Host "Normalized nightly version to MSI format: $MsiVersion" -ForegroundColor Cyan
} elseif ($Version -match "^(\d+)\.(\d+)\.(\d+)") {
$MsiVersion = "$($matches[1]).$($matches[2]).$($matches[3]).0"
} else {
$MsiVersion = "0.0.0.0"
Write-Host "Could not parse version, using: $MsiVersion" -ForegroundColor Yellow
}
# Create output directory
$OutputDir = (New-Item -ItemType Directory -Force -Path $OutputDir).FullName
$MsiName = "yams-$Version-windows-x86_64.msi"
$MsiPath = Join-Path $OutputDir $MsiName
Write-Host "`nBuilding MSI..." -ForegroundColor Cyan
Write-Host " Version: $Version (MSI: $MsiVersion)" -ForegroundColor Gray
Write-Host " Stage: $StageDir" -ForegroundColor Gray
Write-Host " Output: $MsiPath" -ForegroundColor Gray
# Build MSI using WiX
$wxsPath = Join-Path $ScriptDir "yams.wxs"
& wix build $wxsPath `
-o $MsiPath `
-d Version=$MsiVersion `
-d StageDir=$StageDir `
-d IncludeTbb=$includeTbb `
-ext WixToolset.UI.wixext
if ($LASTEXITCODE -ne 0) {
Write-Error "WiX build failed with exit code $LASTEXITCODE"
exit 1
}
# Verify MSI was created
if (Test-Path $MsiPath) {
$msiSize = (Get-Item $MsiPath).Length / 1MB
Write-Host "`nMSI created successfully!" -ForegroundColor Green
Write-Host " Path: $MsiPath" -ForegroundColor Gray
Write-Host " Size: $([math]::Round($msiSize, 2)) MB" -ForegroundColor Gray
# Generate SHA256 hash
$hash = (Get-FileHash -Path $MsiPath -Algorithm SHA256).Hash.ToLower()
Write-Host " SHA256: $hash" -ForegroundColor Gray
# Write hash to file
$hashFile = "$MsiPath.sha256"
"$hash $MsiName" | Out-File -FilePath $hashFile -Encoding utf8 -NoNewline
Write-Host " Hash file: $hashFile" -ForegroundColor Gray
} else {
Write-Error "MSI file was not created at expected path: $MsiPath"
exit 1
}
Write-Host "`nTo install:" -ForegroundColor Cyan
Write-Host " msiexec /i `"$MsiPath`"" -ForegroundColor White
Write-Host "`nTo uninstall:" -ForegroundColor Cyan
Write-Host " msiexec /x `"$MsiPath`"" -ForegroundColor White