mirror of
https://github.com/kjanat/articulate-parser.git
synced 2026-01-16 09:42:09 +01:00
Expands the pre-commit configuration with a wider range of hooks to enforce file quality, validation, security, and Git safety checks. The CI pipeline is updated to: - Correct the `golangci-lint` format command to `fmt`. - Enable CGO for test execution to support the race detector. - Improve the robustness of test report parsing scripts. Additionally, this commit includes minor stylistic and formatting cleanups across various project files.
542 lines
17 KiB
PowerShell
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
|
|
}
|