2025-07-21 14:14:02 -06:00
# Copyright (c) Tailscale Inc & AUTHORS
# SPDX-License-Identifier: BSD-3-Clause
# Requires -Version 7.4
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version 3.0
if ( ( $Env:CI -eq 'true' ) -and ( $Env:NOPWSHDEBUG -ne 'true' ) ) {
Set-PSDebug -Trace 1
}
<#
. DESCRIPTION
Copies the script ' s $args variable into an array , which is easier to work with
when preparing to start child processes .
#>
function Copy-ScriptArgs {
$list = [ System.Collections.Generic.List[string] ] :: new ( $Script:args . Count )
foreach ( $arg in $Script:args ) {
$list . Add ( $arg )
}
return $list . ToArray ( )
}
<#
. DESCRIPTION
Copies the current environment into a hashtable , which is easier to work with
when preparing to start child processes .
#>
function Copy-Environment {
$result = @ { }
foreach ( $pair in ( Get-Item -Path Env : ) ) {
$result [ $pair . Key ] = $pair . Value
}
return $result
}
<#
. DESCRIPTION
Outputs the fully-qualified path to the repository ' s root directory . This
function expects to be run from somewhere within a git repository .
The directory containing the git executable must be somewhere in the PATH .
#>
function Get-RepoRoot {
Get-Command -Name 'git' | Out-Null
$repoRoot = & git rev-parse - -show -toplevel
if ( $LASTEXITCODE -ne 0 ) {
throw " failed obtaining repo root: git failed with code $LASTEXITCODE "
}
# Git outputs a path containing forward slashes. Canonicalize.
return [ System.IO.Path ] :: GetFullPath ( $repoRoot )
}
<#
. DESCRIPTION
Runs the provided ScriptBlock in a child scope , restoring any changes to the
current working directory once the script block completes .
#>
function Start-ChildScope {
param (
[ Parameter ( Mandatory = $true ) ]
[ ScriptBlock ] $ScriptBlock
)
$initialLocation = Get-Location
try {
Invoke-Command -ScriptBlock $ScriptBlock
}
finally {
Set-Location -Path $initialLocation
}
}
<#
. SYNOPSIS
Write-Output with timestamps prepended to each line .
#>
function Write-Log {
param ( $message )
$timestamp = ( Get-Date ) . ToString ( 'yyyy-MM-dd HH:mm:ss' )
Write-Output " $timestamp - $message "
}
$bootstrapScriptBlock = {
$repoRoot = Get-RepoRoot
Set-Location -LiteralPath $repoRoot
switch -Wildcard -File . \ go . toolchain . rev {
" /* " { $toolchain = $_ }
default {
$rev = $_
$tsgo = Join-Path $Env:USERPROFILE '.cache' 'tsgo'
$toolchain = Join-Path $tsgo $rev
if ( -not ( Test-Path -LiteralPath " $toolchain .extracted " -PathType Leaf -ErrorAction SilentlyContinue ) ) {
New-Item -Force -Path $tsgo -ItemType Directory | Out-Null
Remove-Item -Force -Recurse -LiteralPath $toolchain -ErrorAction SilentlyContinue
Write-Log " Downloading Go toolchain $rev "
# Values from https://web.archive.org/web/20250227081443/https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.architecture?view=net-9.0
$cpuArch = ( [ System.Runtime.InteropServices.RuntimeInformation ] :: OSArchitecture | Out-String -NoNewline )
# Comparison in switch is case-insensitive by default.
switch ( $cpuArch ) {
'x86' { $goArch = '386' }
'x64' { $goArch = 'amd64' }
default { $goArch = $cpuArch }
}
Invoke-WebRequest -Uri " https://github.com/tailscale/go/releases/download/build- $rev /windows- $goArch .tar.gz " -OutFile " $toolchain .tar.gz "
try {
New-Item -Force -Path $toolchain -ItemType Directory | Out-Null
Start-ChildScope -ScriptBlock {
Set-Location -LiteralPath $toolchain
tar - -strip -components = 1 -xf " $toolchain .tar.gz "
if ( $LASTEXITCODE -ne 0 ) {
throw " tar failed with exit code $LASTEXITCODE "
}
}
$rev | Out-File -FilePath " $toolchain .extracted "
}
finally {
Remove-Item -Force " $toolchain .tar.gz " -ErrorAction Continue
}
# Cleanup old toolchains.
$maxDays = 90
$oldFiles = Get-ChildItem -Path $tsgo -Filter '*.extracted' -File -Recurse -Depth 1 | Where-Object { $_ . LastWriteTime -lt ( Get-Date ) . AddDays ( - $maxDays ) }
foreach ( $file in $oldFiles ) {
Write-Log " Cleaning up old Go toolchain $( $file . Basename ) "
Remove-Item -LiteralPath $file . FullName -Force -ErrorAction Continue
$dirName = Join-Path $file . DirectoryName $file . Basename -Resolve -ErrorAction Continue
if ( $dirName -and ( Test-Path -LiteralPath $dirName -PathType Container -ErrorAction Continue ) ) {
Remove-Item -LiteralPath $dirName -Recurse -Force -ErrorAction Continue
}
}
}
}
}
if ( $Env:TS_USE_GOCROSS -ne '1' ) {
return
}
if ( Test-Path -LiteralPath $toolchain -PathType Container -ErrorAction SilentlyContinue ) {
$goMod = Join-Path $repoRoot 'go.mod' -Resolve
$goLine = Get-Content -LiteralPath $goMod | Select-String -Pattern '^go (.*)$' -List
$wantGoMinor = $goLine . Matches . Groups [ 1 ] . Value . split ( '.' ) [ 1 ]
$versionFile = Join-Path $toolchain 'VERSION'
if ( Test-Path -LiteralPath $versionFile -PathType Leaf -ErrorAction SilentlyContinue ) {
try {
$haveGoMinor = ( ( Get-Content -LiteralPath $versionFile -TotalCount 1 ) . split ( '.' ) [ 1 ] ) -replace 'rc.*' , ''
}
catch {
}
}
if ( [ string ] :: IsNullOrEmpty ( $haveGoMinor ) -or ( $haveGoMinor -lt $wantGoMinor ) ) {
Remove-Item -Force -Recurse -LiteralPath $toolchain -ErrorAction Continue
Remove-Item -Force -LiteralPath " $toolchain .extracted " -ErrorAction Continue
}
}
$wantVer = & git rev-parse HEAD
$gocrossOk = $false
$gocrossPath = '.\gocross.exe'
if ( Get-Command -Name $gocrossPath -CommandType Application -ErrorAction SilentlyContinue ) {
$gotVer = & $gocrossPath gocross-version 2 > $null
if ( $gotVer -eq $wantVer ) {
$gocrossOk = $true
}
}
if ( -not $gocrossOk ) {
$goBuildEnv = Copy-Environment
$goBuildEnv [ 'CGO_ENABLED' ] = '0'
2025-08-18 15:27:16 -06:00
# Start-Process's -Environment arg applies diffs, so instead of removing
# these variables from $goBuildEnv, we must set them to $null to indicate
# that they should be cleared.
$goBuildEnv [ 'GOOS' ] = $null
$goBuildEnv [ 'GOARCH' ] = $null
$goBuildEnv [ 'GO111MODULE' ] = $null
$goBuildEnv [ 'GOROOT' ] = $null
2025-07-21 14:14:02 -06:00
$procExe = Join-Path $toolchain 'bin' 'go.exe' -Resolve
$proc = Start-Process -FilePath $procExe -WorkingDirectory $repoRoot -Environment $goBuildEnv -ArgumentList 'build' , '-o' , $gocrossPath , " -ldflags=-X=tailscale.com/version.gitCommitStamp= $wantVer " , 'tailscale.com/tool/gocross' -NoNewWindow -Wait -PassThru
if ( $proc . ExitCode -ne 0 ) {
throw 'error building gocross'
}
}
} # bootstrapScriptBlock
Start-ChildScope -ScriptBlock $bootstrapScriptBlock
$repoRoot = Get-RepoRoot
$execEnv = Copy-Environment
2025-08-18 15:27:16 -06:00
# Start-Process's -Environment arg applies diffs, so instead of removing
# these variables from $execEnv, we must set them to $null to indicate
# that they should be cleared.
$execEnv [ 'GOROOT' ] = $null
2025-07-21 14:14:02 -06:00
$argList = Copy-ScriptArgs
if ( $Env:TS_USE_GOCROSS -ne '1' ) {
$revFile = Join-Path $repoRoot 'go.toolchain.rev' -Resolve
switch -Wildcard -File $revFile {
" /* " { $toolchain = $_ }
default {
$rev = $_
$tsgo = Join-Path $Env:USERPROFILE '.cache' 'tsgo'
$toolchain = Join-Path $tsgo $rev -Resolve
}
}
$procExe = Join-Path $toolchain 'bin' 'go.exe' -Resolve
$proc = Start-Process -FilePath $procExe -WorkingDirectory $repoRoot -Environment $execEnv -ArgumentList $argList -NoNewWindow -Wait -PassThru
exit $proc . ExitCode
}
$procExe = Join-Path $repoRoot 'gocross.exe' -Resolve
$proc = Start-Process -FilePath $procExe -WorkingDirectory $repoRoot -Environment $execEnv -ArgumentList $argList -NoNewWindow -Wait -PassThru
exit $proc . ExitCode