Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
246 changes: 246 additions & 0 deletions .github/actions/setup-msvc-env/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
name: setup-msvc-env
description: Expose an MSVC developer environment for the requested Windows target.
inputs:
target:
description: Rust target triple that will be built on this Windows runner.
required: true
host-arch:
description: Optional Visual Studio host architecture override.
required: false
default: ""

runs:
using: composite
steps:
- name: Expose MSVC SDK environment
shell: pwsh
run: |
switch ("${{ inputs.target }}") {
"x86_64-pc-windows-msvc" {
$targetArch = "x64"
$requiredComponent = "Microsoft.VisualStudio.Component.VC.Tools.x86.x64"
}
"aarch64-pc-windows-msvc" {
$targetArch = "arm64"
$requiredComponent = "Microsoft.VisualStudio.Component.VC.Tools.ARM64"
}
default {
throw "Unsupported Windows MSVC target: ${{ inputs.target }}"
}
}

$hostArch = "${{ inputs.host-arch }}"
if (-not $hostArch) {
$hostArch = if ($env:PROCESSOR_ARCHITEW6432 -eq "ARM64" -or $env:PROCESSOR_ARCHITECTURE -eq "ARM64") {
"arm64"
} else {
"x64"
}
}

$vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
if (-not (Test-Path $vswhere)) {
throw "vswhere.exe not found"
}

$installPath = & $vswhere -latest -products * -requires $requiredComponent -property installationPath 2>$null
if (-not $installPath) {
throw "Could not locate a Visual Studio installation with component $requiredComponent"
}

$vsDevCmd = Join-Path $installPath "Common7\Tools\VsDevCmd.bat"
if (-not (Test-Path $vsDevCmd)) {
throw "VsDevCmd.bat not found at $vsDevCmd"
}

$varsToExport = @(
"INCLUDE",
"LIB",
"LIBPATH",
"PATH",
"UCRTVersion",
"UniversalCRTSdkDir",
"VCINSTALLDIR",
"VCToolsInstallDir",
"WindowsLibPath",
"WindowsSdkBinPath",
"WindowsSdkDir",
"WindowsSDKLibVersion",
"WindowsSDKVersion"
)

$envLines = & cmd.exe /c ('"{0}" -no_logo -arch={1} -host_arch={2} >nul && set' -f $vsDevCmd, $targetArch, $hostArch)
$vcToolsInstallDir = $null
foreach ($line in $envLines) {
if ($line -notmatch "^(.*?)=(.*)$") {
continue
}

$name = $matches[1]
$value = $matches[2]
if ($varsToExport -contains $name) {
if ($name -ieq "Path") {
$name = "PATH"
}
if ($name -eq "VCToolsInstallDir") {
$vcToolsInstallDir = $value
}
"$name=$value" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
}
}

if (-not $vcToolsInstallDir) {
throw "VCToolsInstallDir was not exported by VsDevCmd.bat"
}

$linker = $null
$rustc = Get-Command rustc -ErrorAction SilentlyContinue
if ($rustc) {
$sysroot = (& rustc --print sysroot 2>$null).Trim()
$rustHost = & rustc -vV 2>$null | Select-String "^host: " | ForEach-Object { $_.Line.Substring(6) }
if ($rustHost) {
$rustHost = $rustHost.Trim()
}
if ($sysroot -and $rustHost) {
$rustLld = Join-Path $sysroot "lib\rustlib\$rustHost\bin\rust-lld.exe"
if (Test-Path $rustLld) {
$linker = $rustLld
}
}
}
if (-not $linker) {
$linker = Join-Path $installPath "VC\Tools\Llvm\x64\bin\lld-link.exe"
}
if (-not (Test-Path $linker)) {
$linker = Join-Path $vcToolsInstallDir "bin\Host${hostArch}\${targetArch}\link.exe"
}
if (-not (Test-Path $linker)) {
throw "Windows linker not found at $linker"
}

if ($targetArch -eq "arm64" -and (Split-Path -Leaf $linker) -match "lld") {
$wrapperDir = Join-Path $env:RUNNER_TEMP "msvc-lld-wrapper"
New-Item -Path $wrapperDir -ItemType Directory -Force | Out-Null
$wrapperPath = Join-Path $wrapperDir "lld-link-wrapper.exe"
$wrapperSource = @'
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;

internal static class Program
{
private static int Main(string[] args)
{
var linker = Environment.GetEnvironmentVariable("MSVC_REAL_LINKER");
if (string.IsNullOrEmpty(linker))
{
Console.Error.WriteLine("MSVC_REAL_LINKER is not set");
return 1;
}

var startInfo = new ProcessStartInfo(linker)
{
UseShellExecute = false,
};
var filteredArgs = new List<string> { "-flavor", "link", "/defaultlib:ucrt", "/nodefaultlib:libucrt" };
foreach (var arg in args)
{
if (!string.Equals(arg, "/arm64hazardfree", StringComparison.OrdinalIgnoreCase))
{
filteredArgs.Add(QuoteArgument(FilterResponseFile(arg)));
}
}
startInfo.Arguments = string.Join(" ", filteredArgs);

using var process = Process.Start(startInfo);
if (process is null)
{
Console.Error.WriteLine($"Failed to start linker: {linker}");
return 1;
}

process.WaitForExit();
return process.ExitCode;
}

private static string FilterResponseFile(string argument)
{
if (argument.Length < 2 || argument[0] != '@')
{
return argument;
}

var responsePath = argument.Substring(1);
if (!File.Exists(responsePath))
{
return argument;
}

var filteredResponsePath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".rsp");
var responseContents = Regex.Replace(
File.ReadAllText(responsePath),
"/arm64hazardfree",
string.Empty,
RegexOptions.IgnoreCase);
File.WriteAllText(filteredResponsePath, responseContents);
return "@" + filteredResponsePath;
}

private static string QuoteArgument(string argument)
{
if (argument.Length == 0)
{
return "\"\"";
}
if (argument.IndexOfAny(new[] { ' ', '\t', '"' }) < 0)
{
return argument;
}

var quoted = new StringBuilder("\"");
var backslashes = 0;
foreach (var character in argument)
{
if (character == '\\')
{
backslashes++;
continue;
}
if (character == '"')
{
quoted.Append('\\', (backslashes * 2) + 1);
quoted.Append(character);
backslashes = 0;
continue;
}

quoted.Append('\\', backslashes);
backslashes = 0;
quoted.Append(character);
}
quoted.Append('\\', backslashes * 2);
quoted.Append('"');
return quoted.ToString();
}
}
'@
$wrapperSourcePath = Join-Path $wrapperDir "lld-link-wrapper.cs"
$wrapperSource | Out-File -FilePath $wrapperSourcePath -Encoding utf8
$csc = Join-Path $installPath "MSBuild\Current\Bin\Roslyn\csc.exe"
if (-not (Test-Path $csc)) {
throw "csc.exe not found at $csc"
}
& $csc /nologo /target:exe /out:$wrapperPath $wrapperSourcePath
if ($LASTEXITCODE -ne 0) {
throw "Failed to compile lld-link wrapper"
}
"MSVC_REAL_LINKER=$linker" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
$linker = $wrapperPath
}

Write-Output "Using Windows linker: $linker"
$cargoTarget = "${{ inputs.target }}".ToUpperInvariant().Replace("-", "_")
"CARGO_TARGET_${cargoTarget}_LINKER=$linker" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
Loading
Loading