[CmdletBinding()] param( [Parameter(Position = 0, ValueFromRemainingArguments = $true)] [string[]]$Targets = @("all"), [string]$GoBin = "", [string]$DistDir = "", [string]$GoCacheDir = "", [string]$GoModCacheDir = "", [string]$CgoEnabledValue = "0", [string]$BuildJobs = "", [string]$Version = "", [string]$BuildTagsOthers = "", [string]$BuildTagsWindows = "", [switch]$Help ) Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" $RootDir = Split-Path -Parent $MyInvocation.MyCommand.Path $MainPkg = "./cmd/sing-box" $DefaultTargets = @( "linux-amd64", "linux-arm64", "linux-armv7", "windows-amd64", "windows-arm64", "darwin-amd64", "darwin-arm64" ) function Show-Usage { @' Usage: .\building-windows.ps1 .\building-windows.ps1 all .\building-windows.ps1 linux-amd64 .\building-windows.ps1 linux-amd64 windows-amd64 .\building-windows.ps1 current Optional parameters: -GoBin Go binary path -DistDir Output directory, default: .\dist -GoCacheDir Go build cache directory, default: .\.cache\go-build -GoModCacheDir Go module cache directory, default: .\.cache\gomod -CgoEnabledValue <0|1> CGO_ENABLED value, default: 0 -BuildJobs Go build parallel jobs, default: GO_BUILD_JOBS or CPU core count -Version Embedded version, default: git describe --tags --always -BuildTagsOthers Override non-Windows build tags -BuildTagsWindows Override Windows build tags '@ | Write-Host } function Require-File { param([string]$Path) if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { throw "Missing required file: $Path" } } function Read-TrimmedFile { param([string]$Path) return ((Get-Content -LiteralPath $Path -Raw) -replace "`r", "").Trim() } function Resolve-GoBinary { param([string]$RequestedGoBin) if ($RequestedGoBin) { if (-not (Test-Path -LiteralPath $RequestedGoBin -PathType Leaf)) { throw "Go binary not found: $RequestedGoBin" } return (Resolve-Path -LiteralPath $RequestedGoBin).Path } $command = Get-Command go -ErrorAction SilentlyContinue if ($command) { return $command.Source } $defaultPath = "C:\Program Files\Go\bin\go.exe" if (Test-Path -LiteralPath $defaultPath -PathType Leaf) { return $defaultPath } throw "Go binary not found. Please install Go or pass -GoBin." } function Resolve-Version { param( [string]$RequestedVersion, [string]$RepoRoot ) if ($RequestedVersion) { return $RequestedVersion } $gitCommand = Get-Command git -ErrorAction SilentlyContinue if ($gitCommand) { Push-Location $RepoRoot try { $described = git describe --tags --always 2>$null if ($LASTEXITCODE -eq 0 -and $described) { return $described.Trim() } $commit = git rev-parse --short HEAD 2>$null if ($LASTEXITCODE -eq 0 -and $commit) { return $commit.Trim() } } finally { Pop-Location } } return "custom" } function Resolve-BuildJobs { param([string]$RequestedBuildJobs) if ($RequestedBuildJobs) { return $RequestedBuildJobs } if ($env:GO_BUILD_JOBS) { return $env:GO_BUILD_JOBS } return [string][Environment]::ProcessorCount } function Resolve-CachePath { param( [string]$RequestedPath, [string]$DefaultRelativePath ) if ($RequestedPath) { return [System.IO.Path]::GetFullPath($RequestedPath) } return [System.IO.Path]::GetFullPath((Join-Path $RootDir $DefaultRelativePath)) } function Get-TargetConfig { param([string]$Target) switch ($Target) { "linux-amd64" { return @{ GOOS = "linux" GOARCH = "amd64" Output = "sing-box-linux-amd64" Tags = $script:ResolvedBuildTagsOthers } } "linux-arm64" { return @{ GOOS = "linux" GOARCH = "arm64" Output = "sing-box-linux-arm64" Tags = $script:ResolvedBuildTagsOthers } } "linux-armv7" { return @{ GOOS = "linux" GOARCH = "arm" GOARM = "7" Output = "sing-box-linux-armv7" Tags = $script:ResolvedBuildTagsOthers } } "windows-amd64" { return @{ GOOS = "windows" GOARCH = "amd64" Output = "sing-box-windows-amd64.exe" Tags = $script:ResolvedBuildTagsWindows } } "windows-arm64" { return @{ GOOS = "windows" GOARCH = "arm64" Output = "sing-box-windows-arm64.exe" Tags = $script:ResolvedBuildTagsWindows } } "darwin-amd64" { return @{ GOOS = "darwin" GOARCH = "amd64" Output = "sing-box-darwin-amd64" Tags = $script:ResolvedBuildTagsOthers } } "darwin-arm64" { return @{ GOOS = "darwin" GOARCH = "arm64" Output = "sing-box-darwin-arm64" Tags = $script:ResolvedBuildTagsOthers } } "current" { $goos = (& $script:ResolvedGoBin env GOOS).Trim() $goarch = (& $script:ResolvedGoBin env GOARCH).Trim() $output = "sing-box-$goos-$goarch" $tags = $script:ResolvedBuildTagsOthers if ($goos -eq "windows") { $output += ".exe" $tags = $script:ResolvedBuildTagsWindows } return @{ GOOS = $goos GOARCH = $goarch Output = $output Tags = $tags } } default { throw "Unsupported target: $Target" } } } function Invoke-BuildTarget { param([string]$Target) $config = Get-TargetConfig -Target $Target $outputPath = Join-Path $script:ResolvedDistDir $config.Output Write-Host "==> Building $Target (jobs: $script:ResolvedBuildJobs)" -ForegroundColor Cyan Push-Location $RootDir try { $env:CGO_ENABLED = $CgoEnabledValue $env:GOOS = $config.GOOS $env:GOARCH = $config.GOARCH $env:GOCACHE = $script:ResolvedGoCacheDir $env:GOMODCACHE = $script:ResolvedGoModCacheDir if ($config.ContainsKey("GOARM")) { $env:GOARM = $config.GOARM } else { Remove-Item Env:GOARM -ErrorAction SilentlyContinue } & $script:ResolvedGoBin build -v -p $script:ResolvedBuildJobs -trimpath ` -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$script:ResolvedVersion' $script:ResolvedLdflagsShared -s -w -buildid=" ` -tags $config.Tags ` -o $outputPath ` $MainPkg if ($LASTEXITCODE -ne 0) { return [pscustomobject]@{ Target = $Target Success = $false OutputPath = $outputPath Error = "go build exited with code $LASTEXITCODE" } } return [pscustomobject]@{ Target = $Target Success = $true OutputPath = $outputPath Error = "" } } catch { return [pscustomobject]@{ Target = $Target Success = $false OutputPath = $outputPath Error = $_.Exception.Message } } finally { Pop-Location } } if ($Help) { Show-Usage exit 0 } if ($Targets.Count -eq 1 -and ($Targets[0] -eq "-h" -or $Targets[0] -eq "--help")) { Show-Usage exit 0 } $releaseTagsOthersPath = Join-Path $RootDir "release\DEFAULT_BUILD_TAGS_OTHERS" $releaseTagsWindowsPath = Join-Path $RootDir "release\DEFAULT_BUILD_TAGS_WINDOWS" $releaseLdflagsPath = Join-Path $RootDir "release\LDFLAGS" Require-File -Path $releaseTagsOthersPath Require-File -Path $releaseTagsWindowsPath Require-File -Path $releaseLdflagsPath $script:ResolvedGoBin = Resolve-GoBinary -RequestedGoBin $GoBin $script:ResolvedDistDir = if ($DistDir) { $DistDir } else { Join-Path $RootDir "dist" } $script:ResolvedDistDir = [System.IO.Path]::GetFullPath($script:ResolvedDistDir) $script:ResolvedGoCacheDir = Resolve-CachePath -RequestedPath $GoCacheDir -DefaultRelativePath ".cache\go-build" $script:ResolvedGoModCacheDir = Resolve-CachePath -RequestedPath $GoModCacheDir -DefaultRelativePath ".cache\gomod" $script:ResolvedBuildJobs = Resolve-BuildJobs -RequestedBuildJobs $BuildJobs $script:ResolvedVersion = Resolve-Version -RequestedVersion $Version -RepoRoot $RootDir $script:ResolvedBuildTagsOthers = if ($BuildTagsOthers) { $BuildTagsOthers } else { Read-TrimmedFile -Path $releaseTagsOthersPath } $script:ResolvedBuildTagsWindows = if ($BuildTagsWindows) { $BuildTagsWindows } else { Read-TrimmedFile -Path $releaseTagsWindowsPath } $script:ResolvedLdflagsShared = Read-TrimmedFile -Path $releaseLdflagsPath New-Item -ItemType Directory -Force -Path $script:ResolvedDistDir | Out-Null New-Item -ItemType Directory -Force -Path $script:ResolvedGoCacheDir | Out-Null New-Item -ItemType Directory -Force -Path $script:ResolvedGoModCacheDir | Out-Null $resolvedTargets = @() if ($Targets.Count -eq 0 -or ($Targets.Count -eq 1 -and $Targets[0] -eq "all")) { $resolvedTargets = $DefaultTargets } else { $resolvedTargets = $Targets } $results = @() foreach ($target in $resolvedTargets) { $results += Invoke-BuildTarget -Target $target } Write-Host "" Write-Host "Build completed." -ForegroundColor Green Write-Host "Output directory: $script:ResolvedDistDir" Write-Host "Go build cache: $script:ResolvedGoCacheDir" Write-Host "Go module cache: $script:ResolvedGoModCacheDir" $successfulResults = @($results | Where-Object { $_.Success }) $failedResults = @($results | Where-Object { -not $_.Success }) Write-Host "" Write-Host "Succeeded targets:" -ForegroundColor Green if ($successfulResults.Count -eq 0) { Write-Host " (none)" } else { foreach ($result in $successfulResults) { Write-Host " $($result.Target) -> $($result.OutputPath)" } } Write-Host "" Write-Host "Failed targets:" -ForegroundColor Yellow if ($failedResults.Count -eq 0) { Write-Host " (none)" } else { foreach ($result in $failedResults) { Write-Host " $($result.Target) -> $($result.Error)" } exit 1 }