Files
tailscale/tool/gocross/gocross-wrapper.ps1
T
Aaron Klotz 02f6030dbd tool, tool/gocross: update gocross to support building natively on Windows and add a PowerShell Core wrapper script
gocross-wrapper.ps1 is a PowerShell core script that is essentially a
straight port of gocross-wrapper.sh. It requires PowerShell 7.4, which
is the latest LTS release of PSCore.

Why use PowerShell Core instead of Windows PowerShell? Essentially
because the former is much better to script with and is the edition
that is currently maintained.

Because we're using PowerShell Core, but many people will be running
scripts from a machine that only has Windows PowerShell, go.cmd has
been updated to prompt the user for PowerShell core installation if
necessary.

gocross-wrapper.sh has also been updated to utilize the PSCore script
when running under cygwin or msys.

gocross itself required a couple of updates:

We update gocross to output the PowerShell Core wrapper alongside the
bash wrapper, which will propagate the revised scripts to other repos
as necessary.

We also fix a couple of things in gocross that didn't work on Windows:
we change the toolchain resolution code to use os.UserHomeDir instead
of directly referencing the HOME environment variable, and we fix a
bug in the way arguments were being passed into exec.Command on
non-Unix systems.

Updates https://github.com/tailscale/corp/issues/29940

Signed-off-by: Aaron Klotz <aaron@tailscale.com>
2025-08-18 09:49:24 -06:00

221 lines
7.9 KiB
PowerShell

# 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'
$goBuildEnv.Remove('GOOS')
$goBuildEnv.Remove('GOARCH')
$goBuildEnv.Remove('GO111MODULE')
$goBuildEnv.Remove('GOROOT')
$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
$execEnv.Remove('GOROOT')
$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