Skip to main content

max / audiofiles

5.3 KB · 133 lines History Blame Raw
1 <#
2 .SYNOPSIS
3 Build AudioFiles MSI natively on windows-x86 (per the native-builds-per-arch
4 policy). Also produces a matching standalone .exe.
5
6 .DESCRIPTION
7 Runs on the windows-x86 build machine. Uses the WiX Toolset (candle.exe,
8 light.exe) — install from https://wixtoolset.org/ or `winget install
9 WiXToolset.WiXToolset`.
10
11 Code signing is opt-in via environment variables. If unset, the script
12 produces an unsigned MSI/EXE (current state — Azure certificate blocker
13 documented in docs/deploy.md).
14
15 .EXAMPLE
16 # From repo root:
17 PS> pwsh -File dist\build-msi-native.ps1
18
19 .ENVIRONMENT
20 AF_SIGN_CERT Path to .pfx or thumbprint of an installed cert.
21 When set, MSI and EXE are signed via signtool.exe.
22 AF_SIGN_PASS Password for the .pfx (skip if cert is in store).
23 AF_SIGN_TIMESTAMP_URL RFC 3161 timestamp URL.
24 Default: http://timestamp.digicert.com
25 #>
26
27 $ErrorActionPreference = 'Stop'
28
29 $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
30 $ProjectDir = Resolve-Path (Join-Path $ScriptDir '..')
31 $DistDir = $ScriptDir
32 $Target = 'x86_64-pc-windows-msvc'
33
34 # Read version from workspace Cargo.toml (workspace.package.version) or app crate.
35 $AppCargo = Join-Path $ProjectDir 'crates\audiofiles-app\Cargo.toml'
36 $VersionLine = Select-String -Path $AppCargo -Pattern '^version\s*=\s*"([^"]+)"' | Select-Object -First 1
37 if (-not $VersionLine) { throw "Could not parse version from $AppCargo" }
38 $Version = $VersionLine.Matches[0].Groups[1].Value
39 $WixVersion = "$Version.0" # WiX requires four-part version
40 Write-Host "Building AudioFiles v$Version MSI ($Target)" -ForegroundColor Cyan
41
42 # Prerequisites
43 foreach ($tool in @('candle.exe', 'light.exe')) {
44 if (-not (Get-Command $tool -ErrorAction SilentlyContinue)) {
45 throw "$tool not on PATH. Install WiX Toolset from https://wixtoolset.org/"
46 }
47 }
48 $installedTargets = & rustup target list --installed
49 if ($installedTargets -notcontains $Target) {
50 throw "Rust target $Target not installed. Run: rustup target add $Target"
51 }
52
53 # Step 1: Native release build
54 Write-Host "==> Building release binary..." -ForegroundColor Cyan
55 Push-Location $ProjectDir
56 try {
57 & cargo build --release -p audiofiles-app --target $Target
58 if ($LASTEXITCODE -ne 0) { throw "cargo build failed (exit $LASTEXITCODE)" }
59 } finally {
60 Pop-Location
61 }
62
63 $ExeSrc = Join-Path $ProjectDir "target\$Target\release\audiofiles-app.exe"
64 if (-not (Test-Path $ExeSrc)) { throw "Build artifact missing: $ExeSrc" }
65
66 # Step 2: Stage files
67 Write-Host "==> Staging files..." -ForegroundColor Cyan
68 $Staging = Join-Path $DistDir '.msi-staging'
69 if (Test-Path $Staging) { Remove-Item -Recurse -Force $Staging }
70 New-Item -ItemType Directory -Path $Staging | Out-Null
71 Copy-Item $ExeSrc (Join-Path $Staging 'AudioFiles.exe')
72 Copy-Item (Join-Path $DistDir 'audiofiles.png') (Join-Path $Staging 'audiofiles.png')
73
74 # Optional: sign the EXE before embedding it in the MSI so it's signed in-place.
75 function Invoke-Signtool {
76 param([string]$Path)
77 if (-not $env:AF_SIGN_CERT) { return $false }
78 $signtool = Get-Command 'signtool.exe' -ErrorAction SilentlyContinue
79 if (-not $signtool) { throw "AF_SIGN_CERT set but signtool.exe not on PATH" }
80 $timestampUrl = if ($env:AF_SIGN_TIMESTAMP_URL) { $env:AF_SIGN_TIMESTAMP_URL } else { 'http://timestamp.digicert.com' }
81 $args = @('sign', '/fd', 'SHA256', '/tr', $timestampUrl, '/td', 'SHA256')
82 if ($env:AF_SIGN_CERT -match '\.pfx$') {
83 $args += @('/f', $env:AF_SIGN_CERT)
84 if ($env:AF_SIGN_PASS) { $args += @('/p', $env:AF_SIGN_PASS) }
85 } else {
86 # Treat as SHA1 thumbprint of a cert in the local store.
87 $args += @('/sha1', $env:AF_SIGN_CERT)
88 }
89 $args += $Path
90 Write-Host "==> Signing $Path" -ForegroundColor Cyan
91 & $signtool.Source @args
92 if ($LASTEXITCODE -ne 0) { throw "signtool failed on $Path (exit $LASTEXITCODE)" }
93 return $true
94 }
95
96 $StagedExe = Join-Path $Staging 'AudioFiles.exe'
97 $signed = Invoke-Signtool -Path $StagedExe
98 if (-not $signed) {
99 Write-Host "==> AF_SIGN_CERT unset — producing UNSIGNED build (see docs/deploy.md)" -ForegroundColor Yellow
100 }
101
102 # Step 3: Generate WiX source from template
103 $WxsTemplate = Join-Path $DistDir 'audiofiles.wxs.in'
104 $Wxs = Join-Path $Staging 'audiofiles.wxs'
105 (Get-Content -Raw $WxsTemplate).Replace('@VERSION@', $WixVersion) | Set-Content -Path $Wxs -Encoding utf8
106
107 # Step 4: candle + light → MSI
108 Write-Host "==> Building MSI..." -ForegroundColor Cyan
109 $MsiName = "AudioFiles_${Version}_x86_64.msi"
110 $MsiPath = Join-Path $DistDir $MsiName
111 if (Test-Path $MsiPath) { Remove-Item -Force $MsiPath }
112
113 $Wixobj = Join-Path $Staging 'audiofiles.wixobj'
114 & candle.exe -nologo -out $Wixobj $Wxs
115 if ($LASTEXITCODE -ne 0) { throw "candle.exe failed (exit $LASTEXITCODE)" }
116 & light.exe -nologo -b $Staging -out $MsiPath $Wixobj
117 if ($LASTEXITCODE -ne 0) { throw "light.exe failed (exit $LASTEXITCODE)" }
118
119 # Step 5: Sign the MSI (if signing is configured)
120 Invoke-Signtool -Path $MsiPath | Out-Null
121
122 # Step 6: Cleanup + ship the standalone .exe alongside
123 Remove-Item -Recurse -Force $Staging
124 $ExeDest = Join-Path $DistDir "AudioFiles_${Version}_x86_64.exe"
125 Copy-Item $ExeSrc $ExeDest
126 # Re-sign the standalone exe (the one we signed earlier was the staged copy).
127 Invoke-Signtool -Path $ExeDest | Out-Null
128
129 Write-Host ""
130 Write-Host "Done:" -ForegroundColor Green
131 Write-Host " MSI: $MsiPath"
132 Write-Host " EXE: $ExeDest"
133