Files
articulate-parser/scripts/build.ps1
Kaj Kowalski 33ff267644 fix: restore pre-commit, CGO_ENABLED, gohtml template
- Add CGO_ENABLED=1 to CI test step for race detection
- Fix docker job needs (remove dependency-review, only runs on PRs)
- Restore .pre-commit-config.yaml for local dev safety
- Rename html_template.html to .gohtml (conventional extension)
- Add GitHub URL and default branch info to AGENTS.md
- Add .dprint.jsonc config
- Various formatting normalization
2026-01-05 04:14:56 +01:00

542 lines
17 KiB
PowerShell

#Requires -Version 5.1
<#
.SYNOPSIS
Build articulate-parser for multiple platforms
.DESCRIPTION
This script builds the articulate-parser application for multiple operating systems and architectures.
It can build using either native Go (preferred) or WSL if Go is not available on Windows.
.PARAMETER Jobs
Number of parallel build jobs to run (default: 4)
.PARAMETER Platforms
Comma-separated list of platforms to build for (e.g., "windows,linux")
Available: windows, linux, darwin, freebsd
.PARAMETER Architectures
Comma-separated list of architectures to build for (e.g., "amd64,arm64")
Available: amd64, arm64
.PARAMETER BuildDir
Directory to place built binaries (default: 'build')
.PARAMETER EntryPoint
Entry point Go file (default: 'main.go')
Note: This script assumes the entry point is in the project root.
.PARAMETER UseWSL
Force use of WSL even if Go is available on Windows
.PARAMETER Clean
Clean build directory before building
.PARAMETER VerboseOutput
Enable verbose output
.PARAMETER LdFlags
Linker flags to pass to go build (default: "-s -w" for smaller binaries)
Use empty string ("") to disable default ldflags
.PARAMETER SkipTests
Skip running tests before building
.PARAMETER Version
Version string to embed in binaries (auto-detected from git if not provided)
.PARAMETER ShowTargets
Show available build targets and exit
.EXAMPLE
.\build.ps1
Build for all platforms and architectures
.EXAMPLE
.\build.ps1 -Platforms "windows,linux" -Architectures "amd64"
Build only for Windows and Linux on amd64
.EXAMPLE
.\build.ps1 -Jobs 8 -VerboseOutput
Build with 8 parallel jobs and verbose output
.EXAMPLE
.\build.ps1 -Version "v1.2.3" -SkipTests
Build with specific version and skip tests
.EXAMPLE
.\build.ps1 -LdFlags "-X main.version=1.0.0"
Build with custom ldflags (overrides default -s -w)
.EXAMPLE
.\build.ps1 -LdFlags ""
Build without any ldflags (disable defaults)
.EXAMPLE
.\build.ps1 -ShowTargets
Show all available build targets
.FUNCTIONALITY
Build automation, Cross-platform compilation, Go builds, Multi-architecture, Parallel builds, Windows, Linux, macOS, FreeBSD, Release management, Go applications
.NOTES
This script requires Go to be installed and available in the PATH.
It also requires git if auto-detecting version from tags.
If Go is not available on Windows, it will use WSL to perform the build.
Ensure you have the necessary permissions to create directories and files in the specified BuildDir.
For WSL builds, ensure you have a compatible Linux distribution installed and configured.
.OUTPUTS
Outputs built binaries to the specified BuildDir.
Displays build summary including successful and failed builds.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $false, Position = 0, HelpMessage = 'Number of parallel build jobs', ValueFromPipeline, ValueFromPipelineByPropertyName)]
[int]$Jobs = 4,
[Parameter(Mandatory = $false, Position = 1, HelpMessage = 'Comma-separated list of platforms to build for', ValueFromPipeline, ValueFromPipelineByPropertyName)]
[string]$Platforms = 'windows,linux,darwin,freebsd',
[Parameter(Mandatory = $false, Position = 2, HelpMessage = 'Comma-separated list of architectures to build for', ValueFromPipeline, ValueFromPipelineByPropertyName)]
[string]$Architectures = 'amd64,arm64',
[Parameter(Mandatory = $false, Position = 3, HelpMessage = 'Directory to place built binaries', ValueFromPipeline, ValueFromPipelineByPropertyName)]
[string]$BuildDir = 'build',
[Parameter(Mandatory = $false, Position = 4, HelpMessage = 'Entry point Go file', ValueFromPipeline, ValueFromPipelineByPropertyName, ValueFromRemainingArguments)]
[string]$EntryPoint = 'main.go',
[Parameter(Mandatory = $false, Position = 5, HelpMessage = 'Force use of WSL even if Go is available on Windows')]
[switch]$UseWSL,
[Parameter(Mandatory = $false, Position = 6, HelpMessage = 'Clean build directory before building')]
[switch]$Clean,
[Parameter(Mandatory = $false, Position = 7, HelpMessage = 'Enable verbose output')]
[switch]$VerboseOutput,
[Parameter(Mandatory = $false, Position = 8, HelpMessage = 'Linker flags to pass to go build')]
[string]$LdFlags = '-s -w',
[Parameter(Mandatory = $false, Position = 9, HelpMessage = 'Skip running tests before building')]
[switch]$SkipTests,
[Parameter(Mandatory = $false, Position = 10, HelpMessage = 'Version string to embed in binaries')]
[string]$Version = '',
[Parameter(Mandatory = $false, Position = 11, HelpMessage = 'Show available build targets and exit')]
[switch]$ShowTargets
)
# Set error action preference
$ErrorActionPreference = 'Stop'
# Get script directory and project root
$ScriptDir = $PSScriptRoot
$ProjectRoot = Split-Path $ScriptDir -Parent
$BuildDir = Join-Path $ProjectRoot $BuildDir
# Ensure we're in the project root
Push-Location $ProjectRoot
try {
# Show targets and exit if requested
if ($ShowTargets) {
Write-Host 'Available build targets:' -ForegroundColor Cyan
# Get available platforms and architectures from Go toolchain
try {
$GoTargets = @(go tool dist list 2>$null)
if ($LASTEXITCODE -ne 0 -or $GoTargets.Count -eq 0) {
throw 'Failed to get target list from Go toolchain'
}
} catch {
Write-Host '⚠️ Could not retrieve targets from Go. Using default targets.' -ForegroundColor Yellow
$PlatformList = $Platforms.Split(',') | ForEach-Object { $_.Trim() }
$ArchList = $Architectures.Split(',') | ForEach-Object { $_.Trim() }
foreach ($platform in $PlatformList) {
foreach ($arch in $ArchList) {
$BinaryName = "articulate-parser-$platform-$arch"
if ($platform -eq 'windows') { $BinaryName += '.exe' }
Write-Host " $platform/$arch -> $BinaryName" -ForegroundColor Gray
}
}
return
}
# Filter targets from go tool dist list
$SelectedTargets = @()
$PlatformList = $Platforms.Split(',') | ForEach-Object { $_.Trim() }
$ArchList = $Architectures.Split(',') | ForEach-Object { $_.Trim() }
foreach ($target in $GoTargets) {
$parts = $target.Split('/')
$platform = $parts[0]
$arch = $parts[1]
if ($PlatformList -contains $platform -and $ArchList -contains $arch) {
$SelectedTargets += @{
Platform = $platform
Arch = $arch
Original = $target
}
}
}
# Display filtered targets
foreach ($target in $SelectedTargets) {
$BinaryName = "articulate-parser-$($target.Platform)-$($target.Arch)"
if ($target.Platform -eq 'windows') { $BinaryName += '.exe' }
Write-Host " $($target.Original) -> $BinaryName" -ForegroundColor Gray
}
# Show all available targets if verbose
if ($VerboseOutput) {
Write-Host "`nAll Go targets available on this system:" -ForegroundColor Cyan
foreach ($target in $GoTargets) {
Write-Host " $target" -ForegroundColor DarkGray
}
}
return
}
# Validate required files exist
$RequiredFiles = @('go.mod', 'main.go')
foreach ($file in $RequiredFiles) {
if (-not (Test-Path $file)) {
Write-Error "Required file not found: $file. Make sure you're in the project root."
exit 1
}
}
# Auto-detect version from git if not provided
if (-not $Version) {
try {
$gitTag = git describe --tags --always --dirty 2>$null
if ($LASTEXITCODE -eq 0 -and $gitTag) {
$Version = $gitTag.Trim()
if ($VerboseOutput) { Write-Host "✓ Auto-detected version: $Version" -ForegroundColor Green }
} else {
$Version = 'dev'
if ($VerboseOutput) { Write-Host "⚠ Using default version: $Version" -ForegroundColor Yellow }
}
} catch {
$Version = 'dev'
if ($VerboseOutput) { Write-Host "⚠ Git not available, using default version: $Version" -ForegroundColor Yellow }
}
}
# Get build timestamp
$BuildTime = Get-Date -Format 'yyyy-MM-ddTHH:mm:ssZ'
# Get commit hash if available
$CommitHash = 'unknown'
try {
$gitCommit = git rev-parse --short HEAD 2>$null
if ($LASTEXITCODE -eq 0 -and $gitCommit) {
$CommitHash = $gitCommit.Trim()
}
} catch {
# Git not available or not in a git repo
}
# Prepare enhanced ldflags with version info
$VersionLdFlags = @(
"-X main.Version=$Version",
"-X main.BuildTime=$BuildTime",
"-X main.CommitHash=$CommitHash"
)
# Combine base ldflags with version ldflags
$AllLdFlags = @()
if ($LdFlags) {
# Remove quotes if present and split by space
$BaseLdFlags = $LdFlags.Trim('"', "'").Split(' ', [StringSplitOptions]::RemoveEmptyEntries)
$AllLdFlags += $BaseLdFlags
}
$AllLdFlags += $VersionLdFlags
$EnhancedLdFlags = $AllLdFlags -join ' '
if ($VerboseOutput) {
Write-Host "🔍 Enhanced ldflags: '$EnhancedLdFlags'" -ForegroundColor Magenta
}
# Validate Go installation
$GoAvailable = $false
try {
$goVersion = go version 2>$null
if ($LASTEXITCODE -eq 0) {
$GoAvailable = $true
if ($VerboseOutput) { Write-Host "✓ Go is available: $goVersion" -ForegroundColor Green }
}
} catch {
# Go not available
}
# Check if we should use WSL
$UseWSLBuild = $UseWSL -or (-not $GoAvailable)
if ($UseWSLBuild) {
# Check WSL availability
try {
wsl.exe --status >$null 2>&1
if ($LASTEXITCODE -ne 0) {
throw 'WSL is not available'
}
} catch {
Write-Error 'Neither Go nor WSL is available. Please install Go or WSL to build the project.'
exit 1
}
Write-Host '🔄 Using WSL for build...' -ForegroundColor Yellow
# Build script path
$bashScript = Join-Path $ScriptDir 'build.sh'
if (-not (Test-Path $bashScript)) {
Write-Error "Build script not found at $bashScript"
exit 1
}
# Prepare arguments for bash script
$bashArgs = @()
if ($Jobs -ne 4) {
$bashArgs += '-j', $Jobs
}
if ($EnhancedLdFlags) {
$bashArgs += '-ldflags', $EnhancedLdFlags
}
# Pass build directory and entry point
$bashArgs += '-o', $BuildDir
$bashArgs += '-e', $EntryPoint
# Execute WSL build
wsl.exe bash "$bashScript" @bashArgs
if ($LASTEXITCODE -ne 0) {
Write-Error "WSL build script failed with exit code $LASTEXITCODE"
exit $LASTEXITCODE
}
return
}
# Run tests before building (unless skipped)
if (-not $SkipTests) {
Write-Host '🧪 Running tests...' -ForegroundColor Cyan
$TestResult = go test -v ./... 2>&1
if ($LASTEXITCODE -ne 0) {
Write-Host '❌ Tests failed:' -ForegroundColor Red
Write-Host $TestResult -ForegroundColor Red
Write-Error 'Tests failed. Use -SkipTests to build anyway.'
exit 1
}
Write-Host '✅ All tests passed' -ForegroundColor Green
}
# Native PowerShell build
Write-Host '🔨 Building articulate-parser natively...' -ForegroundColor Cyan
# Clean build directory if requested
if ($Clean -and (Test-Path $BuildDir)) {
Write-Host '🧹 Cleaning build directory...' -ForegroundColor Yellow
Remove-Item $BuildDir -Recurse -Force
}
# Create build directory
if (-not (Test-Path $BuildDir)) {
New-Item -ItemType Directory -Path $BuildDir | Out-Null
}
# Parse platforms and architectures
$PlatformList = $Platforms.Split(',') | ForEach-Object { $_.Trim() }
$ArchList = $Architectures.Split(',') | ForEach-Object { $_.Trim() }
# Validate platforms and architectures
$ValidPlatforms = @('windows', 'linux', 'darwin', 'freebsd')
$ValidArchs = @('amd64', 'arm64')
foreach ($platform in $PlatformList) {
if ($platform -notin $ValidPlatforms) {
Write-Error "Invalid platform: $platform. Valid platforms: $($ValidPlatforms -join ', ')"
exit 1
}
}
foreach ($arch in $ArchList) {
if ($arch -notin $ValidArchs) {
Write-Error "Invalid architecture: $arch. Valid architectures: $($ValidArchs -join ', ')"
exit 1
}
}
# Generate build targets
$Targets = @()
foreach ($platform in $PlatformList) {
foreach ($arch in $ArchList) {
$BinaryName = "articulate-parser-$platform-$arch"
if ($platform -eq 'windows') {
$BinaryName += '.exe'
}
$Targets += @{
Platform = $platform
Arch = $arch
Binary = $BinaryName
Path = Join-Path $BuildDir $BinaryName
}
}
}
Write-Host "📋 Building $($Targets.Count) targets with $Jobs parallel jobs" -ForegroundColor Cyan
# Display targets
if ($VerboseOutput) {
foreach ($target in $Targets) {
Write-Host " - $($target.Platform)/$($target.Arch) -> $($target.Binary)" -ForegroundColor Gray
}
}
# Build function
$BuildTarget = {
param($Target, $EnhancedLdFlags, $VerboseOutput, $BuildDir, $EntryPoint, $ProjectRoot)
$env:GOOS = $Target.Platform
$env:GOARCH = $Target.Arch
$env:CGO_ENABLED = '0'
# Construct build arguments
$BuildArgs = @('build')
if ($EnhancedLdFlags) {
$BuildArgs += '-ldflags'
$BuildArgs += "`"$EnhancedLdFlags`""
}
$BuildArgs += '-o'
$BuildArgs += $Target.Path
# If using custom entry point that's not main.go
# we need to use the file explicitly to avoid duplicate declarations
$EntryPointPath = Join-Path $ProjectRoot $EntryPoint
$EntryPointFile = Split-Path $EntryPointPath -Leaf
$IsCustomEntryPoint = ($EntryPointFile -ne 'main.go')
if ($IsCustomEntryPoint) {
# When using custom entry point, compile only that file
$BuildArgs += $EntryPointPath
} else {
# For standard main.go, let Go find and compile all package files
$PackagePath = Split-Path $EntryPointPath -Parent
$BuildArgs += $PackagePath
}
# For verbose output, show the command that will be executed
if ($VerboseOutput) {
Write-Host "Command: go $($BuildArgs -join ' ')" -ForegroundColor DarkCyan
}
$LogFile = "$($Target.Path).log"
try {
if ($VerboseOutput) {
Write-Host "🔨 Building $($Target.Binary)..." -ForegroundColor Yellow
}
$Process = Start-Process -FilePath 'go' -ArgumentList $BuildArgs -Wait -PassThru -NoNewWindow -RedirectStandardError $LogFile
if ($Process.ExitCode -eq 0) {
# Remove log file on success
if (Test-Path $LogFile) {
Remove-Item $LogFile -Force
}
return @{ Success = $true; Target = $Target.Binary }
} else {
return @{ Success = $false; Target = $Target.Binary; LogFile = $LogFile }
}
} catch {
return @{ Success = $false; Target = $Target.Binary; Error = $_.Exception.Message }
}
}
# Execute builds with throttling
$RunspacePool = [runspacefactory]::CreateRunspacePool(1, $Jobs)
$RunspacePool.Open()
$BuildJobs = @()
foreach ($target in $Targets) {
$PowerShell = [powershell]::Create()
$PowerShell.RunspacePool = $RunspacePool
$PowerShell.AddScript($BuildTarget).AddParameters(@{
Target = $target
EnhancedLdFlags = $EnhancedLdFlags
VerboseOutput = $VerboseOutput
BuildDir = $BuildDir
EntryPoint = $EntryPoint
ProjectRoot = $ProjectRoot
}) | Out-Null
$BuildJobs += @{
PowerShell = $PowerShell
AsyncResult = $PowerShell.BeginInvoke()
Target = $target.Binary
}
}
# Wait for results and display progress
$Completed = 0
$Successful = 0
$Failed = 0
Write-Host ''
while ($Completed -lt $BuildJobs.Count) {
foreach ($job in $BuildJobs | Where-Object { $_.AsyncResult.IsCompleted -and -not $_.Processed }) {
$job.Processed = $true
$Result = $job.PowerShell.EndInvoke($job.AsyncResult)
$job.PowerShell.Dispose()
$Completed++
if ($Result.Success) {
$Successful++
Write-Host "$($Result.Target)" -ForegroundColor Green
} else {
$Failed++
if ($Result.LogFile) {
Write-Host "$($Result.Target) (see $($Result.LogFile))" -ForegroundColor Red
} else {
Write-Host "$($Result.Target): $($Result.Error)" -ForegroundColor Red
}
}
}
Start-Sleep -Milliseconds 100
}
$RunspacePool.Close()
$RunspacePool.Dispose()
# Summary
Write-Host ''
Write-Host '📊 Build Summary:' -ForegroundColor Cyan
Write-Host " 🏷️ Version: $Version" -ForegroundColor Gray
Write-Host " 🔨 Commit: $CommitHash" -ForegroundColor Gray
Write-Host " ⏰ Build Time: $BuildTime" -ForegroundColor Gray
Write-Host " ✅ Successful: $Successful" -ForegroundColor Green
Write-Host " ❌ Failed: $Failed" -ForegroundColor Red
Write-Host " 📁 Output: $BuildDir" -ForegroundColor Yellow
if ($Successful -gt 0) {
Write-Host ''
Write-Host '📦 Built binaries:' -ForegroundColor Cyan
Get-ChildItem $BuildDir -File | Where-Object { $_.Name -notlike '*.log' } | Sort-Object Name | ForEach-Object {
$Size = [math]::Round($_.Length / 1MB, 2)
$LastWrite = $_.LastWriteTime.ToString('HH:mm:ss')
Write-Host " $($_.Name.PadRight(35)) $($Size.ToString().PadLeft(6)) MB ($LastWrite)" -ForegroundColor Gray
}
# Calculate total size
$TotalSize = (Get-ChildItem $BuildDir -File | Where-Object { $_.Name -notlike '*.log' } | Measure-Object -Property Length -Sum).Sum
$TotalSizeMB = [math]::Round($TotalSize / 1MB, 2)
Write-Host " $('Total:'.PadRight(35)) $($TotalSizeMB.ToString().PadLeft(6)) MB" -ForegroundColor Cyan
}
if ($Failed -gt 0) {
exit 1
}
# Clean up environment variables to avoid contaminating future builds
Remove-Item Env:GOOS -ErrorAction SilentlyContinue
Remove-Item Env:GOARCH -ErrorAction SilentlyContinue
Remove-Item Env:CGO_ENABLED -ErrorAction SilentlyContinue
} finally {
Pop-Location
}