|
| 1 | +param( |
| 2 | + [Parameter(Mandatory = $true)] |
| 3 | + [string]$Target, |
| 4 | + |
| 5 | + [string]$HostArch = "" |
| 6 | +) |
| 7 | + |
| 8 | +# Cargo can cross-compile the Rust code for Windows ARM64 on a Windows x64 |
| 9 | +# runner, but rustup alone does not expose the matching MSVC/UCRT include and |
| 10 | +# library paths. Ask Visual Studio for the target-specific developer |
| 11 | +# environment, then persist the relevant variables through GITHUB_ENV so the |
| 12 | +# later Cargo step sees the same environment as a normal VsDevCmd shell. |
| 13 | +switch ($Target) { |
| 14 | + "x86_64-pc-windows-msvc" { |
| 15 | + $TargetArch = "x64" |
| 16 | + $RequiredComponent = "Microsoft.VisualStudio.Component.VC.Tools.x86.x64" |
| 17 | + } |
| 18 | + "aarch64-pc-windows-msvc" { |
| 19 | + $TargetArch = "arm64" |
| 20 | + $RequiredComponent = "Microsoft.VisualStudio.Component.VC.Tools.ARM64" |
| 21 | + } |
| 22 | + default { |
| 23 | + throw "Unsupported Windows MSVC target: $Target" |
| 24 | + } |
| 25 | +} |
| 26 | + |
| 27 | +# VsDevCmd needs both sides of the cross compile: the architecture of the |
| 28 | +# machine running the tools and the architecture of the binaries being linked. |
| 29 | +# Infer the host from the runner unless a caller needs to override it. |
| 30 | +if (-not $HostArch) { |
| 31 | + $HostArch = if ($env:PROCESSOR_ARCHITEW6432 -eq "ARM64" -or $env:PROCESSOR_ARCHITECTURE -eq "ARM64") { |
| 32 | + "arm64" |
| 33 | + } else { |
| 34 | + "x64" |
| 35 | + } |
| 36 | +} |
| 37 | + |
| 38 | +$VsWhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" |
| 39 | +if (-not (Test-Path $VsWhere)) { |
| 40 | + throw "vswhere.exe not found" |
| 41 | +} |
| 42 | + |
| 43 | +# Require the target VC tools component, not merely any Visual Studio install, |
| 44 | +# so an x64 archive producer cannot silently link ARM64 tests with the wrong |
| 45 | +# SDK/toolchain layout. |
| 46 | +$InstallPath = & $VsWhere -latest -products * -requires $RequiredComponent -property installationPath 2>$null |
| 47 | +if (-not $InstallPath) { |
| 48 | + throw "Could not locate a Visual Studio installation with component $RequiredComponent" |
| 49 | +} |
| 50 | + |
| 51 | +$VsDevCmd = Join-Path $InstallPath "Common7\Tools\VsDevCmd.bat" |
| 52 | +if (-not (Test-Path $VsDevCmd)) { |
| 53 | + throw "VsDevCmd.bat not found at $VsDevCmd" |
| 54 | +} |
| 55 | + |
| 56 | +$VarsToExport = @( |
| 57 | + "INCLUDE", |
| 58 | + "LIB", |
| 59 | + "LIBPATH", |
| 60 | + "PATH", |
| 61 | + "UCRTVersion", |
| 62 | + "UniversalCRTSdkDir", |
| 63 | + "VCINSTALLDIR", |
| 64 | + "VCToolsInstallDir", |
| 65 | + "WindowsLibPath", |
| 66 | + "WindowsSdkBinPath", |
| 67 | + "WindowsSdkDir", |
| 68 | + "WindowsSDKLibVersion", |
| 69 | + "WindowsSDKVersion" |
| 70 | +) |
| 71 | + |
| 72 | +# Run VsDevCmd inside cmd.exe because it is a batch file, then copy just the |
| 73 | +# variables Cargo/rustc need into the GitHub Actions environment file. PowerShell |
| 74 | +# cannot mutate the parent composite-action environment directly. |
| 75 | +$EnvLines = & cmd.exe /c ('"{0}" -no_logo -arch={1} -host_arch={2} >nul && set' -f $VsDevCmd, $TargetArch, $HostArch) |
| 76 | +$VcToolsInstallDir = $null |
| 77 | +foreach ($Line in $EnvLines) { |
| 78 | + if ($Line -notmatch "^(.*?)=(.*)$") { |
| 79 | + continue |
| 80 | + } |
| 81 | + |
| 82 | + $Name = $Matches[1] |
| 83 | + $Value = $Matches[2] |
| 84 | + if ($VarsToExport -contains $Name) { |
| 85 | + if ($Name -ieq "Path") { |
| 86 | + $Name = "PATH" |
| 87 | + } |
| 88 | + if ($Name -eq "VCToolsInstallDir") { |
| 89 | + $VcToolsInstallDir = $Value |
| 90 | + } |
| 91 | + "$Name=$Value" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append |
| 92 | + } |
| 93 | +} |
| 94 | + |
| 95 | +if (-not $VcToolsInstallDir) { |
| 96 | + throw "VCToolsInstallDir was not exported by VsDevCmd.bat" |
| 97 | +} |
| 98 | + |
| 99 | +# Prefer Rust's bundled linker when rustup provides one, then Visual Studio's |
| 100 | +# LLVM linker, and finally MSVC link.exe. This keeps the cross-compile path close |
| 101 | +# to Rust's normal Windows MSVC behavior while still working on runner images |
| 102 | +# where one of those linkers is absent. |
| 103 | +$Linker = $null |
| 104 | +$Rustc = Get-Command rustc -ErrorAction SilentlyContinue |
| 105 | +if ($Rustc) { |
| 106 | + $Sysroot = (& rustc --print sysroot 2>$null).Trim() |
| 107 | + $RustHost = & rustc -vV 2>$null | Select-String "^host: " | ForEach-Object { $_.Line.Substring(6) } |
| 108 | + if ($RustHost) { |
| 109 | + $RustHost = $RustHost.Trim() |
| 110 | + } |
| 111 | + if ($Sysroot -and $RustHost) { |
| 112 | + $RustLld = Join-Path $Sysroot "lib\rustlib\$RustHost\bin\rust-lld.exe" |
| 113 | + if (Test-Path $RustLld) { |
| 114 | + $Linker = $RustLld |
| 115 | + } |
| 116 | + } |
| 117 | +} |
| 118 | +if (-not $Linker) { |
| 119 | + $Linker = Join-Path $InstallPath "VC\Tools\Llvm\x64\bin\lld-link.exe" |
| 120 | +} |
| 121 | +if (-not (Test-Path $Linker)) { |
| 122 | + $Linker = Join-Path $VcToolsInstallDir "bin\Host${HostArch}\${TargetArch}\link.exe" |
| 123 | +} |
| 124 | +if (-not (Test-Path $Linker)) { |
| 125 | + throw "Windows linker not found at $Linker" |
| 126 | +} |
| 127 | + |
| 128 | +# rustc passes `/arm64hazardfree` for ARM64 MSVC links. The lld variants on our |
| 129 | +# Windows x64 archive producers reject that flag, including when rustc places it |
| 130 | +# inside a response file. Compile a tiny forwarding wrapper that strips only |
| 131 | +# that unsupported flag, then delegate every other argument to the real linker. |
| 132 | +if ($TargetArch -eq "arm64" -and (Split-Path -Leaf $Linker) -match "lld") { |
| 133 | + $WrapperDir = Join-Path $env:RUNNER_TEMP "msvc-lld-wrapper" |
| 134 | + New-Item -Path $WrapperDir -ItemType Directory -Force | Out-Null |
| 135 | + $WrapperPath = Join-Path $WrapperDir "lld-link-wrapper.exe" |
| 136 | + $WrapperSource = @' |
| 137 | +using System; |
| 138 | +using System.Collections.Generic; |
| 139 | +using System.Diagnostics; |
| 140 | +using System.IO; |
| 141 | +using System.Text; |
| 142 | +using System.Text.RegularExpressions; |
| 143 | +
|
| 144 | +internal static class Program |
| 145 | +{ |
| 146 | + private static int Main(string[] args) |
| 147 | + { |
| 148 | + var linker = Environment.GetEnvironmentVariable("MSVC_REAL_LINKER"); |
| 149 | + if (string.IsNullOrEmpty(linker)) |
| 150 | + { |
| 151 | + Console.Error.WriteLine("MSVC_REAL_LINKER is not set"); |
| 152 | + return 1; |
| 153 | + } |
| 154 | +
|
| 155 | + var startInfo = new ProcessStartInfo(linker) |
| 156 | + { |
| 157 | + UseShellExecute = false, |
| 158 | + }; |
| 159 | + var filteredArgs = new List<string> { "-flavor", "link", "/defaultlib:ucrt", "/nodefaultlib:libucrt" }; |
| 160 | + foreach (var arg in args) |
| 161 | + { |
| 162 | + if (!string.Equals(arg, "/arm64hazardfree", StringComparison.OrdinalIgnoreCase)) |
| 163 | + { |
| 164 | + filteredArgs.Add(QuoteArgument(FilterResponseFile(arg))); |
| 165 | + } |
| 166 | + } |
| 167 | + startInfo.Arguments = string.Join(" ", filteredArgs); |
| 168 | +
|
| 169 | + using var process = Process.Start(startInfo); |
| 170 | + if (process is null) |
| 171 | + { |
| 172 | + Console.Error.WriteLine($"Failed to start linker: {linker}"); |
| 173 | + return 1; |
| 174 | + } |
| 175 | +
|
| 176 | + process.WaitForExit(); |
| 177 | + return process.ExitCode; |
| 178 | + } |
| 179 | +
|
| 180 | + private static string FilterResponseFile(string argument) |
| 181 | + { |
| 182 | + if (argument.Length < 2 || argument[0] != '@') |
| 183 | + { |
| 184 | + return argument; |
| 185 | + } |
| 186 | +
|
| 187 | + var responsePath = argument.Substring(1); |
| 188 | + if (!File.Exists(responsePath)) |
| 189 | + { |
| 190 | + return argument; |
| 191 | + } |
| 192 | +
|
| 193 | + var filteredResponsePath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".rsp"); |
| 194 | + var responseContents = Regex.Replace( |
| 195 | + File.ReadAllText(responsePath), |
| 196 | + "/arm64hazardfree", |
| 197 | + string.Empty, |
| 198 | + RegexOptions.IgnoreCase); |
| 199 | + File.WriteAllText(filteredResponsePath, responseContents); |
| 200 | + return "@" + filteredResponsePath; |
| 201 | + } |
| 202 | +
|
| 203 | + private static string QuoteArgument(string argument) |
| 204 | + { |
| 205 | + if (argument.Length == 0) |
| 206 | + { |
| 207 | + return "\"\""; |
| 208 | + } |
| 209 | + if (argument.IndexOfAny(new[] { ' ', '\t', '"' }) < 0) |
| 210 | + { |
| 211 | + return argument; |
| 212 | + } |
| 213 | +
|
| 214 | + var quoted = new StringBuilder("\""); |
| 215 | + var backslashes = 0; |
| 216 | + foreach (var character in argument) |
| 217 | + { |
| 218 | + if (character == '\\') |
| 219 | + { |
| 220 | + backslashes++; |
| 221 | + continue; |
| 222 | + } |
| 223 | + if (character == '"') |
| 224 | + { |
| 225 | + quoted.Append('\\', (backslashes * 2) + 1); |
| 226 | + quoted.Append(character); |
| 227 | + backslashes = 0; |
| 228 | + continue; |
| 229 | + } |
| 230 | +
|
| 231 | + quoted.Append('\\', backslashes); |
| 232 | + backslashes = 0; |
| 233 | + quoted.Append(character); |
| 234 | + } |
| 235 | + quoted.Append('\\', backslashes * 2); |
| 236 | + quoted.Append('"'); |
| 237 | + return quoted.ToString(); |
| 238 | + } |
| 239 | +} |
| 240 | +'@ |
| 241 | + $WrapperSourcePath = Join-Path $WrapperDir "lld-link-wrapper.cs" |
| 242 | + $WrapperSource | Out-File -FilePath $WrapperSourcePath -Encoding utf8 |
| 243 | + $Csc = Join-Path $InstallPath "MSBuild\Current\Bin\Roslyn\csc.exe" |
| 244 | + if (-not (Test-Path $Csc)) { |
| 245 | + throw "csc.exe not found at $Csc" |
| 246 | + } |
| 247 | + & $Csc /nologo /target:exe /out:$WrapperPath $WrapperSourcePath |
| 248 | + if ($LASTEXITCODE -ne 0) { |
| 249 | + throw "Failed to compile lld-link wrapper" |
| 250 | + } |
| 251 | + "MSVC_REAL_LINKER=$Linker" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append |
| 252 | + $Linker = $WrapperPath |
| 253 | +} |
| 254 | + |
| 255 | +Write-Output "Using Windows linker: $Linker" |
| 256 | +$CargoTarget = $Target.ToUpperInvariant().Replace("-", "_") |
| 257 | +"CARGO_TARGET_${CargoTarget}_LINKER=$Linker" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append |
0 commit comments