mirror of
https://github.com/kjanat/articulate-parser.git
synced 2026-01-16 07:02:09 +01:00
Implement versioning and build enhancements with cross-platform support
This commit is contained in:
35
.github/workflows/ci.yml
vendored
35
.github/workflows/ci.yml
vendored
@ -12,6 +12,8 @@ jobs:
|
||||
test:
|
||||
name: Test
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
strategy:
|
||||
matrix:
|
||||
go: [1.21.x, 1.22.x, 1.23.x, 1.24.x]
|
||||
@ -105,18 +107,37 @@ jobs:
|
||||
|
||||
- name: Build binaries
|
||||
run: |
|
||||
# Build for different platforms
|
||||
OS = ["darwin", "freebsd", "linux", "windows"]
|
||||
ARCH = ["amd64", "arm64"]
|
||||
# Set the build time environment variable
|
||||
BUILD_TIME=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
|
||||
|
||||
for os in OS:
|
||||
for arch in ARCH:
|
||||
GOOS=$os GOARCH=$arch go build -o articulate-parser-$os-$arch main.go
|
||||
# Add run permissions to the build script
|
||||
chmod +x ./scripts/build.sh
|
||||
|
||||
# Display help information for the build script
|
||||
./scripts/build.sh --help
|
||||
|
||||
# Build for all platforms
|
||||
./scripts/build.sh \
|
||||
--verbose \
|
||||
-ldflags "-s -w -X github.com/kjanat/articulate-parser/internal/version.Version=${{ github.ref_name }} -X github.com/kjanat/articulate-parser/internal/version.BuildTime=$BUILD_TIME -X github.com/kjanat/articulate-parser/internal/version.GitCommit=${{ github.sha }}"
|
||||
|
||||
- name: Upload a Build Artifact
|
||||
uses: actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
# Artifact name
|
||||
name: build-artifacts # optional, default is artifact
|
||||
# A file, directory or wildcard pattern that describes what to upload
|
||||
path: build/
|
||||
if-no-files-found: ignore
|
||||
retention-days: 1
|
||||
compression-level: 9
|
||||
overwrite: true
|
||||
include-hidden-files: true
|
||||
|
||||
- name: Create Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: articulate-parser-*
|
||||
files: build/*
|
||||
generate_release_notes: true
|
||||
draft: false
|
||||
prerelease: ${{ startsWith(github.ref, 'refs/tags/v0.') }}
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@ -30,3 +30,7 @@ go.work
|
||||
output/
|
||||
articulate-sample.json
|
||||
test-output.*
|
||||
go-os-arch-matrix.csv
|
||||
|
||||
# Build artifacts
|
||||
build/
|
||||
|
||||
15
internal/version/version.go
Normal file
15
internal/version/version.go
Normal file
@ -0,0 +1,15 @@
|
||||
// Package version provides version information for the Articulate Parser.
|
||||
// It includes the current version, build time, and Git commit hash.
|
||||
package version
|
||||
|
||||
// Version information.
|
||||
var (
|
||||
// Version is the current version of the application.
|
||||
Version = "0.1.0"
|
||||
|
||||
// BuildTime is the time the binary was built.
|
||||
BuildTime = "unknown"
|
||||
|
||||
// GitCommit is the git commit hash.
|
||||
GitCommit = "unknown"
|
||||
)
|
||||
11
main.go
11
main.go
@ -14,6 +14,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/unidoc/unioffice/document"
|
||||
|
||||
"github.com/kjanat/articulate-parser/internal/version"
|
||||
)
|
||||
|
||||
// Core data structures based on the Articulate Rise JSON format
|
||||
@ -547,8 +549,17 @@ func (p *ArticulateParser) processItemToDocx(doc *document.Document, item Item)
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Handle version flag
|
||||
if len(os.Args) > 1 && (os.Args[1] == "-v" || os.Args[1] == "--version") {
|
||||
fmt.Printf("articulate-parser %s\n", version.Version)
|
||||
fmt.Printf("Build time: %s\n", version.BuildTime)
|
||||
fmt.Printf("Commit: %s\n", version.GitCommit)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if len(os.Args) < 3 {
|
||||
fmt.Println("Usage: articulate-parser <input_uri_or_file> <output_format> [output_path]")
|
||||
fmt.Println(" articulate-parser -v|--version")
|
||||
fmt.Println(" input_uri_or_file: Articulate Rise URI or local JSON file path")
|
||||
fmt.Println(" output_format: md (Markdown) or docx (Word Document)")
|
||||
fmt.Println(" output_path: Optional output file path")
|
||||
|
||||
38
scripts/_build_tools_available.ps1
Normal file
38
scripts/_build_tools_available.ps1
Normal file
@ -0,0 +1,38 @@
|
||||
param(
|
||||
[switch]$AsCheckmark
|
||||
)
|
||||
|
||||
# Get the list from 'go tool dist list'
|
||||
$dists = & go tool dist list
|
||||
|
||||
# Parse into OS/ARCH pairs
|
||||
$parsed = $dists | ForEach-Object {
|
||||
$split = $_ -split '/'
|
||||
[PSCustomObject]@{ OS = $split[0]; ARCH = $split[1] }
|
||||
}
|
||||
|
||||
# Find all unique OSes and arches, sorted
|
||||
$oses = $parsed | Select-Object -ExpandProperty OS -Unique | Sort-Object
|
||||
$arches = $parsed | Select-Object -ExpandProperty ARCH -Unique | Sort-Object
|
||||
|
||||
# Group by OS, and build custom objects
|
||||
$results = foreach ($os in $oses) {
|
||||
$props = @{}
|
||||
$props.OS = $os
|
||||
foreach ($arch in $arches) {
|
||||
$hasArch = $parsed | Where-Object { $_.OS -eq $os -and $_.ARCH -eq $arch }
|
||||
if ($hasArch) {
|
||||
if ($AsCheckmark) {
|
||||
$props[$arch] = '✅'
|
||||
} else {
|
||||
$props[$arch] = $true
|
||||
}
|
||||
} else {
|
||||
$props[$arch] = $false
|
||||
}
|
||||
}
|
||||
[PSCustomObject]$props
|
||||
}
|
||||
|
||||
# Output
|
||||
$results | Format-Table -AutoSize
|
||||
536
scripts/build.ps1
Normal file
536
scripts/build.ps1
Normal file
@ -0,0 +1,536 @@
|
||||
|
||||
#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
|
||||
}
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
358
scripts/build.sh
Normal file
358
scripts/build.sh
Normal file
@ -0,0 +1,358 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Get the *real* path to the script, even if called via symlink
|
||||
SCRIPT_PATH="$(readlink -f "$0" 2>/dev/null || realpath "$0")"
|
||||
SCRIPT_DIR="$(dirname "$SCRIPT_PATH")"
|
||||
PARENT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
cd "$PARENT_DIR"
|
||||
|
||||
# Default values
|
||||
OS=("darwin" "freebsd" "linux" "windows")
|
||||
ARCH=("amd64" "arm64")
|
||||
OUTDIR="build"
|
||||
ENTRYPOINT="main.go"
|
||||
JOBS=4
|
||||
SHOW_TARGETS=false
|
||||
SHOW_HELP=false
|
||||
VERBOSE=false
|
||||
DEFAULT_LDFLAGS="-s -w"
|
||||
|
||||
# Function to show help
|
||||
show_help() {
|
||||
cat <<'EOF'
|
||||
articulate-parser Build Script (Bash)
|
||||
=====================================
|
||||
|
||||
SYNOPSIS:
|
||||
build.sh [OPTIONS] [GO_BUILD_FLAGS...]
|
||||
|
||||
DESCRIPTION:
|
||||
Cross-platform build script for articulate-parser. Builds binaries for multiple
|
||||
OS/architecture combinations in parallel with embedded version information.
|
||||
|
||||
OPTIONS:
|
||||
-h, --help Show this help message and exit
|
||||
-j <number> Number of parallel jobs (default: 4)
|
||||
-o <directory> Output directory for binaries (default: build)
|
||||
-e <file> Entry point Go file (default: main.go)
|
||||
-v, --verbose Enable verbose output for debugging
|
||||
--show-targets Show available Go build targets and exit
|
||||
|
||||
EXAMPLES:
|
||||
# Basic build with default settings
|
||||
./scripts/build.sh
|
||||
|
||||
# Build with 8 parallel jobs
|
||||
./scripts/build.sh -j 8
|
||||
|
||||
# Build to custom directory
|
||||
./scripts/build.sh -o my_builds
|
||||
|
||||
# Build with custom entry point
|
||||
./scripts/build.sh -e test_entry.go
|
||||
|
||||
# Build with verbose output
|
||||
./scripts/build.sh -v
|
||||
|
||||
# Build with Go build flags and version info
|
||||
./scripts/build.sh -ldflags "-s -w -X main.version=1.0.0 -X main.buildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
|
||||
# Show available targets
|
||||
./scripts/build.sh --show-targets
|
||||
|
||||
# Build with Go build flags and version info
|
||||
./scripts/build.sh -ldflags "-s -w -X github.com/kjanat/articulate-parser/internal/version.Version=1.0.0 -X github.com/kjanat/articulate-parser/internal/version.BuildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
|
||||
# Build with custom ldflags (overrides default -s -w)
|
||||
./scripts/build.sh -ldflags "-X github.com/kjanat/articulate-parser/internal/version.Version=1.0.0"
|
||||
|
||||
# Build without any ldflags (disable defaults)
|
||||
./scripts/build.sh -ldflags ""
|
||||
|
||||
DEFAULT TARGETS:
|
||||
Operating Systems: darwin, freebsd, linux, windows
|
||||
Architectures: amd64, arm64
|
||||
|
||||
This creates 8 binaries total (4 OS × 2 ARCH)
|
||||
|
||||
GO BUILD FLAGS:
|
||||
Any additional arguments are passed directly to 'go build'.
|
||||
Default: -ldflags "-s -w" (strip debug info for smaller binaries)
|
||||
Common flags include:
|
||||
-ldflags Link flags (e.g., version info, optimization)
|
||||
-tags Build tags
|
||||
-v Verbose Go build output
|
||||
-race Enable race detector
|
||||
-trimpath Remove file system paths from executable
|
||||
|
||||
To override default ldflags, specify your own -ldflags argument.
|
||||
To disable ldflags entirely, use: -ldflags ""
|
||||
|
||||
OUTPUT:
|
||||
Binaries are named: articulate-parser-{OS}-{ARCH}[.exe]
|
||||
Build logs for failed builds: {BINARY_NAME}.log
|
||||
|
||||
NOTES:
|
||||
- Requires Go to be installed and in PATH
|
||||
- Removes and recreates the output directory
|
||||
- Failed builds create .log files with error details
|
||||
- Uses colored output with real-time status updates
|
||||
- Entry point validation ensures file exists before building
|
||||
- Supports custom entry points (compiles single file to avoid conflicts)
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# Function to show available Go build targets
|
||||
show_targets() {
|
||||
echo "Available Go Build Targets:"
|
||||
echo "=========================="
|
||||
echo
|
||||
if command -v go >/dev/null 2>&1; then
|
||||
echo "Getting targets from 'go tool dist list'..."
|
||||
echo
|
||||
|
||||
# Get all targets and format them nicely
|
||||
local targets
|
||||
targets=$(go tool dist list 2>/dev/null)
|
||||
|
||||
if [ $? -eq 0 ] && [ -n "$targets" ]; then
|
||||
# Show formatted output
|
||||
printf "%-15s %-10s %s\n" "OS" "ARCH" "STATUS"
|
||||
printf "%-15s %-10s %s\n" "---------------" "----------" "------"
|
||||
|
||||
# Track our default targets
|
||||
local default_targets=()
|
||||
for os in "${OS[@]}"; do
|
||||
for arch in "${ARCH[@]}"; do
|
||||
default_targets+=("$os/$arch")
|
||||
done
|
||||
done
|
||||
|
||||
# Display all targets with status
|
||||
echo "$targets" | sort | while IFS='/' read -r os arch; do
|
||||
local status="available"
|
||||
if printf '%s\n' "${default_targets[@]}" | grep -q "^$os/$arch$"; then
|
||||
status="default"
|
||||
fi
|
||||
printf "%-15s %-10s %s\n" "$os" "$arch" "$status"
|
||||
done
|
||||
|
||||
echo
|
||||
echo "Summary:"
|
||||
echo " Total available targets: $(echo "$targets" | wc -l)"
|
||||
echo " Default targets used by this script: ${#default_targets[@]}"
|
||||
echo
|
||||
echo "Default script targets:"
|
||||
for target in "${default_targets[@]}"; do
|
||||
echo " - $target"
|
||||
done
|
||||
else
|
||||
echo "Error: Failed to get target list from 'go tool dist list'"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "Error: Go is not installed or not in PATH"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Parse parameters
|
||||
while (("$#")); do
|
||||
case $1 in
|
||||
-h | --help)
|
||||
SHOW_HELP=true
|
||||
shift
|
||||
;;
|
||||
--show-targets)
|
||||
SHOW_TARGETS=true
|
||||
shift
|
||||
;;
|
||||
-v | --verbose)
|
||||
VERBOSE=true
|
||||
shift
|
||||
;;
|
||||
-j)
|
||||
if [[ ${2-} =~ ^[0-9]+$ ]]; then
|
||||
JOBS=$2
|
||||
shift 2
|
||||
else
|
||||
echo "Error: Missing number of jobs after -j"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
-o)
|
||||
if [ -n "${2-}" ]; then
|
||||
OUTDIR=$2
|
||||
shift 2
|
||||
else
|
||||
echo "Error: Missing output directory after -o"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
-e)
|
||||
if [ -n "${2-}" ]; then
|
||||
ENTRYPOINT=$2
|
||||
shift 2
|
||||
else
|
||||
echo "Error: Missing entry point file after -e"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Handle help and show-targets early
|
||||
if [ "$SHOW_HELP" = true ]; then
|
||||
show_help
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$SHOW_TARGETS" = true ]; then
|
||||
show_targets
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Validate entry point exists
|
||||
if [ ! -f "$ENTRYPOINT" ]; then
|
||||
echo "Error: Entry point file '$ENTRYPOINT' does not exist"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Store remaining arguments as an array to preserve argument boundaries
|
||||
GO_BUILD_FLAGS_ARRAY=("$@")
|
||||
|
||||
# Apply default ldflags if no custom ldflags were provided
|
||||
HAS_CUSTOM_LDFLAGS=false
|
||||
for arg in "${GO_BUILD_FLAGS_ARRAY[@]}"; do
|
||||
if [[ "$arg" == "-ldflags" ]]; then
|
||||
HAS_CUSTOM_LDFLAGS=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$HAS_CUSTOM_LDFLAGS" == false ]] && [[ -n "$DEFAULT_LDFLAGS" ]]; then
|
||||
# Add default ldflags at the beginning
|
||||
GO_BUILD_FLAGS_ARRAY=("-ldflags" "$DEFAULT_LDFLAGS" "${GO_BUILD_FLAGS_ARRAY[@]}")
|
||||
fi
|
||||
|
||||
# Verbose output
|
||||
if [ "$VERBOSE" = true ]; then
|
||||
echo "Build Configuration:"
|
||||
echo " Entry Point: $ENTRYPOINT"
|
||||
echo " Output Dir: $OUTDIR"
|
||||
echo " Parallel Jobs: $JOBS"
|
||||
if [ ${#GO_BUILD_FLAGS_ARRAY[@]} -gt 0 ]; then
|
||||
echo " Go Build Flags: ${GO_BUILD_FLAGS_ARRAY[*]}"
|
||||
else
|
||||
echo " Go Build Flags: none"
|
||||
fi
|
||||
echo " Targets: ${#OS[@]}×${#ARCH[@]} = $((${#OS[@]} * ${#ARCH[@]})) total"
|
||||
echo
|
||||
fi
|
||||
|
||||
rm -rf "$OUTDIR"
|
||||
mkdir -p "$OUTDIR"
|
||||
|
||||
# Get build start time
|
||||
BUILD_START=$(date +%s)
|
||||
|
||||
# Compose all targets in an array
|
||||
TARGETS=()
|
||||
for os in "${OS[@]}"; do
|
||||
for arch in "${ARCH[@]}"; do
|
||||
BIN="articulate-parser-$os-$arch"
|
||||
[[ "$os" == "windows" ]] && BIN="$BIN.exe"
|
||||
TARGETS+=("$BIN|$os|$arch")
|
||||
done
|
||||
done
|
||||
|
||||
# Show targets info if verbose
|
||||
if [ "$VERBOSE" = true ]; then
|
||||
echo "Building targets:"
|
||||
for target in "${TARGETS[@]}"; do
|
||||
BIN="${target%%|*}"
|
||||
echo " - $BIN"
|
||||
done
|
||||
echo
|
||||
fi
|
||||
|
||||
# Print pending statuses and save line numbers
|
||||
for idx in "${!TARGETS[@]}"; do
|
||||
BIN="${TARGETS[$idx]%%|*}"
|
||||
printf "[ ] %-35s ... pending\n" "$BIN"
|
||||
done
|
||||
|
||||
# Make sure output isn't buffered
|
||||
export PYTHONUNBUFFERED=1
|
||||
|
||||
# Function to update a line in-place (1-based index)
|
||||
update_status() {
|
||||
local idx=$1
|
||||
local symbol=$2
|
||||
local msg=$3
|
||||
# Move cursor up to the correct line
|
||||
printf "\0337" # Save cursor position
|
||||
printf "\033[%dA" $((${#TARGETS[@]} - idx + 1)) # Move up
|
||||
printf "\r\033[K[%s] %-35s\n" "$symbol" "$msg" # Clear & update line
|
||||
printf "\0338" # Restore cursor position
|
||||
}
|
||||
|
||||
for idx in "${!TARGETS[@]}"; do
|
||||
while (($(jobs -rp | wc -l) >= JOBS)); do sleep 0.2; done
|
||||
(
|
||||
IFS='|' read -r BIN os arch <<<"${TARGETS[$idx]}"
|
||||
update_status $((idx + 1)) '>' "$BIN ... building"
|
||||
|
||||
# Prepare build command as an array to properly handle arguments with spaces
|
||||
build_cmd=(go build)
|
||||
if [ "$VERBOSE" = true ]; then
|
||||
build_cmd+=(-v)
|
||||
fi
|
||||
build_cmd+=("${GO_BUILD_FLAGS_ARRAY[@]}" -o "$OUTDIR/$BIN" "$ENTRYPOINT")
|
||||
|
||||
if GOOS="$os" GOARCH="$arch" "${build_cmd[@]}" 2>"$OUTDIR/$BIN.log"; then
|
||||
update_status $((idx + 1)) '✔' "$BIN done"
|
||||
rm -f "$OUTDIR/$BIN.log"
|
||||
else
|
||||
update_status $((idx + 1)) '✖' "$BIN FAILED (see $OUTDIR/$BIN.log)"
|
||||
fi
|
||||
) &
|
||||
done
|
||||
|
||||
wait
|
||||
|
||||
# Calculate build time
|
||||
BUILD_END=$(date +%s)
|
||||
BUILD_DURATION=$((BUILD_END - BUILD_START))
|
||||
|
||||
echo -e "\nAll builds completed in ${BUILD_DURATION}s. Find them in $OUTDIR/"
|
||||
|
||||
# Show build summary if verbose
|
||||
if [ "$VERBOSE" = true ]; then
|
||||
echo
|
||||
echo "Build Summary:"
|
||||
echo "=============="
|
||||
success_count=0
|
||||
total_size=0
|
||||
|
||||
for target in "${TARGETS[@]}"; do
|
||||
BIN="${target%%|*}"
|
||||
if [ -f "$OUTDIR/$BIN" ]; then
|
||||
success_count=$((success_count + 1))
|
||||
size=$(stat -f%z "$OUTDIR/$BIN" 2>/dev/null || stat -c%s "$OUTDIR/$BIN" 2>/dev/null || echo "0")
|
||||
total_size=$((total_size + size))
|
||||
rm -f "$OUTDIR/$BIN.log"
|
||||
printf " ✔ %-42s %s\n" "$OUTDIR/$BIN" "$(numfmt --to=iec-i --suffix=B $size 2>/dev/null || echo "${size} bytes")"
|
||||
else
|
||||
printf " ✖ %-42s %s\n" "$OUTDIR/$BIN" "FAILED"
|
||||
fi
|
||||
done
|
||||
|
||||
echo " ────────────────────────────────────────────────"
|
||||
printf " Total: %d/%d successful, %s total size\n" "$success_count" "${#TARGETS[@]}" "$(numfmt --to=iec-i --suffix=B $total_size 2>/dev/null || echo "${total_size} bytes")"
|
||||
fi
|
||||
Reference in New Issue
Block a user