Skip to content

Commit 65ddbbf

Browse files
committed
Add reusable action for MSVC + Win SDK overrides
This introduces a new reusable action, `setup-build`, which will eventually replace all of the custom installation steps in the swift-build jobs. Currently it only supports providing explicit versions for MSVC and the Windows SDK. This also adds tests for the `setup-build` action, with a configurable runner, ensuring we can catch issues early as runner images get updated.
1 parent dba385b commit 65ddbbf

File tree

2 files changed

+354
-0
lines changed

2 files changed

+354
-0
lines changed
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
name: Setup build
2+
description: Sets up the build environment for the current job
3+
4+
inputs:
5+
windows-sdk-version:
6+
description: The Windows SDK version to use, e.g. "10.0.22621.0"
7+
required: false
8+
type: string
9+
windows-msvc-version:
10+
description: The Windows MSVC version to use, e.g. "14.42"
11+
required: false
12+
type: string
13+
setup-vs-dev-env:
14+
description: Whether to set up a Visual Studio Dev Environment
15+
default: false
16+
required: false
17+
type: boolean
18+
target-arch:
19+
description: The target architecture, "x86", "amd64" or "arm64". Defaults to the host architecture.
20+
required: false
21+
type: string
22+
23+
runs:
24+
using: composite
25+
steps:
26+
- name: Verify input
27+
id: verify-input
28+
shell: pwsh
29+
run: |
30+
if ($IsWindows) {
31+
$HostOs = "windows"
32+
} elseif ($IsMacOS) {
33+
$HostOs = "mac"
34+
} else {
35+
Write-Output "::error::Unsupported host OS."
36+
exit 1
37+
}
38+
39+
$Arch = ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture).ToString()
40+
switch ($Arch) {
41+
"X64" { $HostArch = "amd64" }
42+
"Arm64" { $HostArch = "arm64" }
43+
default {
44+
Write-Output "::error::Unsupported host architecture: `"$HostArch`""
45+
exit 1
46+
}
47+
}
48+
49+
$WinMsvcVersion = "${{ inputs.windows-msvc-version }}"
50+
if ($WinMsvcVersion -ne "") {
51+
$ParsedWinMsvcVersion = [System.Version]::Parse($WinMsvcVersion)
52+
if ($ParsedWinMsvcVersion -eq $null) {
53+
Write-Output "::error::Invalid Windows MSVC version: `"${WinMsvcVersion}`"."
54+
exit 1
55+
}
56+
if ($ParsedWinMsvcVersion.Major -ne 14) {
57+
Write-Output "::error::Unsupported Windows MSVC version (major version not supported): `"${WinMsvcVersion}`"."
58+
exit 1
59+
}
60+
if ($ParsedWinMsvcVersion.Build -ne -1) {
61+
Write-Output "::error::Unsupported Windows MSVC version (build version was specified): `"${WinMsvcVersion}`"."
62+
exit 1
63+
}
64+
if ($ParsedWinMsvcVersion.Revision -ne -1) {
65+
Write-Output "::error::Unsupported Windows MSVC version (revision version was specified): `"${WinMsvcVersion}`"."
66+
exit 1
67+
}
68+
}
69+
70+
switch ("${{ inputs.target-arch }}") {
71+
"x86" { $TargetArch = "x86" }
72+
"amd64" { $TargetArch = "amd64" }
73+
"arm64" { $TargetArch = "arm64" }
74+
"" { $TargetArch = $HostArch }
75+
default {
76+
Write-Output "::error::Unsupported target architecture: `"${{ inputs.target-arch }}`""
77+
exit 1
78+
}
79+
}
80+
81+
Write-Output "Host OS: $HostOs"
82+
Write-Output "Host architecture: $HostArch"
83+
Write-Output "Target architecture: $TargetArch"
84+
85+
$VerifiedInput = @"
86+
host-os=$HostOs
87+
host-arch=$HostArch
88+
target-arch=$TargetArch
89+
"@
90+
Write-Output $VerifiedInput | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
91+
92+
- name: Install Windows SDK version ${{ inputs.windows-sdk-version }}
93+
if: steps.verify-input.outputs.host-os == 'windows' && inputs.windows-sdk-version != ''
94+
shell: pwsh
95+
run: |
96+
$WinSdkVersionString = "${{ inputs.windows-sdk-version }}"
97+
$WinSdkVersion = [System.Version]::Parse($WinSdkVersionString)
98+
$WinSdkVersionBuild = $WinSdkVersion.Build
99+
100+
$Win10SdkRoot = Get-ItemPropertyValue `
101+
-Path "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Kits\Installed Roots" `
102+
-Name "KitsRoot10"
103+
$Win10SdkLib = Join-Path $Win10SdkRoot "Lib"
104+
$Win10SdkInclude = Join-Path $Win10SdkRoot "Include"
105+
$Win10SdkIncludeVersion = Join-Path $Win10SdkInclude $WinSdkVersionString
106+
107+
if (Test-Path -Path $Win10SdkIncludeVersion -PathType Container) {
108+
Write-Output "Windows SDK ${WinSdkVersionString} already installed."
109+
} else {
110+
# Install the missing SDK.
111+
Write-Output "Installing Windows SDK ${WinSdkVersionString}..."
112+
113+
$InstallerLocation = Join-Path "${env:ProgramFiles(x86)}" "Microsoft Visual Studio" "Installer"
114+
$VsWhere = Join-Path "${InstallerLocation}" "vswhere.exe"
115+
$VsInstaller = Join-Path "${InstallerLocation}" "vs_installer.exe"
116+
$InstallPath = (& "$VsWhere" -latest -products * -format json | ConvertFrom-Json).installationPath
117+
$process = Start-Process "$VsInstaller" `
118+
-PassThru `
119+
-ArgumentList "modify", `
120+
"--installPath", "`"$InstallPath`"", `
121+
"--channelId", "https://aka.ms/vs/17/release/channel", `
122+
"--quiet", "--norestart", "--nocache", `
123+
"--add", "Microsoft.VisualStudio.Component.Windows11SDK.${WinSdkVersionBuild}"
124+
$process.WaitForExit()
125+
126+
if (Test-Path -Path $Win10SdkIncludeVersion -PathType Container) {
127+
Write-Output "Windows SDK ${WinSdkVersionString} installed successfully."
128+
} else {
129+
Write-Output "::error::Failed to install Windows SDK ${WinSdkVersionString}."
130+
Write-Output "Installer log:"
131+
$log = Get-ChildItem "${env:TEMP}" -Filter "dd_installer_*.log" | Sort-Object LastWriteTime -Descending | Select-Object -First 1
132+
Get-Content $log.FullName
133+
exit 1
134+
}
135+
}
136+
137+
# Remove more recent Windows SDKs, if present. This is used to work
138+
# around issues where LLVM uses the most recent Windows SDK.
139+
Get-ChildItem -Path $Win10SdkInclude -Directory | ForEach-Object {
140+
$IncludeDirName = $_.Name
141+
try {
142+
$IncludeDirVersion = [System.Version]::Parse($IncludeDirName)
143+
if ($IncludeDirVersion -gt $WinSdkVersion) {
144+
$LibDirVersion = Join-Path $Win10SdkLib $IncludeDirName
145+
Write-Output "Removing folders for Windows SDK ${IncludeDirVersion}."
146+
Remove-Item -Path $_.FullName -Recurse -Force -ErrorAction Ignore
147+
Remove-Item -Path $LibDirVersion -Recurse -Force -ErrorAction Ignore
148+
}
149+
} catch {
150+
# Skip if the directory cannot be parsed as a version.
151+
}
152+
}
153+
154+
- name: Install Windows MSVC version ${{ inputs.windows-msvc-version }}
155+
if: steps.verify-input.outputs.host-os == 'windows' && inputs.windows-msvc-version != ''
156+
shell: pwsh
157+
run: |
158+
# This is assuming a VS2022 toolchain. e.g.
159+
# MSVC 14.42 corresponds to the 14.42.17.12 package.
160+
# MSVC 14.43 corresponds to the 14.43.17.13 package.
161+
$WinMsvcVersionString = "${{ inputs.windows-msvc-version }}"
162+
163+
$InstallerLocation = Join-Path "${env:ProgramFiles(x86)}" "Microsoft Visual Studio" "Installer"
164+
$VsWhere = Join-Path "${InstallerLocation}" "vswhere.exe"
165+
$VsInstaller = Join-Path "${InstallerLocation}" "vs_installer.exe"
166+
$InstallPath = (& "$VsWhere" -latest -products * -format json | ConvertFrom-Json).installationPath
167+
$MSVCDir = Join-Path $InstallPath "VC" "Tools" "MSVC"
168+
169+
# Check if this MSVC version is already installed.
170+
Get-ChildItem -Path $MSVCDir -Directory | ForEach-Object {
171+
$MsvcDirName = $_.Name
172+
if ($MsvcDirName.StartsWith($WinMsvcVersionString)) {
173+
Write-Output "MSVC ${WinMsvcVersionString} already installed."
174+
exit 0
175+
}
176+
}
177+
178+
# Compute the MSVC version package name.
179+
$WinMsvcVersion = [System.Version]::Parse($WinMsvcVersionString)
180+
$MajorVersion = $WinMsvcVersion.Major
181+
$MinorVersion = $WinMsvcVersion.Minor
182+
$BuildVersion = 17
183+
$RevisionVersion = $MinorVersion - 30
184+
$WinMsvcVersionBuild = "${MajorVersion}.${MinorVersion}.${BuildVersion}.${RevisionVersion}"
185+
186+
# Install the missing MSVC version.
187+
Write-Output "Installing MSVC packages for ${WinMsvcVersionBuild}..."
188+
$process = Start-Process "$VsInstaller" `
189+
-PassThru `
190+
-ArgumentList "modify", `
191+
"--installPath", "`"$InstallPath`"", `
192+
"--channelId", "https://aka.ms/vs/17/release/channel", `
193+
"--quiet", "--norestart", "--nocache", `
194+
"--add", "Microsoft.VisualStudio.Component.VC.${WinMsvcVersionBuild}.x86.x64", `
195+
"--add", "Microsoft.VisualStudio.Component.VC.${WinMsvcVersionBuild}.ATL", `
196+
"--add", "Microsoft.VisualStudio.Component.VC.${WinMsvcVersionBuild}.ARM64", `
197+
"--add", "Microsoft.VisualStudio.Component.VC.${WinMsvcVersionBuild}.ATL.ARM64"
198+
$process.WaitForExit()
199+
200+
# Check if the MSVC version was installed successfully.
201+
$MSVCDirFound = $false
202+
Get-ChildItem -Path $MSVCDir -Directory | ForEach-Object {
203+
$MsvcDirName = $_.Name
204+
if ($MsvcDirName.StartsWith($WinMsvcVersionString)) {
205+
Write-Output "MSVC ${WinMsvcVersionString} installed successfully."
206+
$MSVCDirFound = $true
207+
break
208+
}
209+
}
210+
211+
if (-not $MSVCDirFound) {
212+
Write-Output "::error::Failed to install MSVC ${WinMsvcVersionString}."
213+
Write-Output "Installer log:"
214+
$log = Get-ChildItem "${env:TEMP}" -Filter "dd_installer_*.log" | Sort-Object LastWriteTime -Descending | Select-Object -First 1
215+
Get-Content $log.FullName
216+
exit 1
217+
}
218+
219+
- name: Setup Visual Studio Developer Environment
220+
if: steps.verify-input.outputs.host-os == 'windows' && inputs.setup-vs-dev-env
221+
uses: compnerd/gha-setup-vsdevenv@5eb3eae1490d4f7875d574c4973539f69109700d # main
222+
with:
223+
host_arch: ${{ steps.verify-input.outputs.host-arch }}
224+
arch: ${{ steps.verify-input.outputs.target-arch }}
225+
winsdk: ${{ inputs.windows-msvc-version }}
226+
toolset_version: ${{ inputs.windows-msvc-version }}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
name: Test the setup-build action
2+
on:
3+
pull_request:
4+
branches:
5+
- 'main'
6+
paths:
7+
- '.github/actions/action.yml'
8+
- '.github/workflows/test-setup-build.yml'
9+
workflow_dispatch:
10+
inputs:
11+
windows-runner:
12+
description: "The Windows runner to use"
13+
required: false
14+
type: string
15+
workflow_call:
16+
inputs:
17+
windows-runner:
18+
description: "The Windows runner to use"
19+
required: false
20+
type: string
21+
22+
env:
23+
TEST_WIN_SDK_VERSION: 10.0.22621.0
24+
TEST_MSVC_VERSION: 14.42
25+
26+
jobs:
27+
test-setup-build-windows:
28+
name: Test MSVC and Windows SDK environment setup
29+
runs-on: ${{ inputs.windows-runner || 'windows-latest' }}
30+
steps:
31+
- name: Checkout
32+
uses: actions/[email protected]
33+
34+
- name: Set up build
35+
uses: ./.github/actions/setup-build
36+
with:
37+
windows-sdk-version: ${{ env.TEST_WIN_SDK_VERSION }}
38+
windows-msvc-version: ${{ env.TEST_MSVC_VERSION }}
39+
setup-vs-dev-env: true
40+
41+
- name: Check environment
42+
run: |
43+
$HasError = $false
44+
45+
$ParsedWinSdkVersion = [System.Version]::Parse($env:TEST_WIN_SDK_VERSION)
46+
$Win10SdkRoot = Get-ItemPropertyValue `
47+
-Path "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Kits\Installed Roots" `
48+
-Name "KitsRoot10"
49+
$Win10SdkInclude = Join-Path $Win10SdkRoot "Include"
50+
51+
# Check if the Windows SDK version is installed.
52+
$ExpectedWinSdkDir = Join-Path $Win10SdkInclude "$($env:TEST_WIN_SDK_VERSION)"
53+
if (Test-Path -Path $ExpectedWinSdkDir) {
54+
Write-Output "✅ Windows SDK version `"${env:TEST_WIN_SDK_VERSION}`" is installed."
55+
} else {
56+
Write-Output "::error::Expected Windows SDK version not found: `"${env:TEST_WIN_SDK_VERSION}`"."
57+
$HasError = $true
58+
}
59+
60+
# Check if Windows SDK versions greater than the expected version are installed.
61+
$UnexpectedSdkFound = $false
62+
Get-ChildItem -Path $Win10SdkInclude -Directory | ForEach-Object {
63+
$Version = $_.Name
64+
try {
65+
$ParsedVersion = [System.Version]::Parse($Version)
66+
if ($ParsedVersion -gt $ParsedWinSdkVersion) {
67+
Write-Output "::error::Unexpected Windows SDK version found: `"${Version}`" (greater than expected: `"${env:TEST_WIN_SDK_VERSION}`")."
68+
$HasError = $true
69+
$UnexpectedSdkFound = $true
70+
}
71+
} catch {
72+
# Skip if the directory cannot be parsed as a version.
73+
}
74+
}
75+
if (-not $UnexpectedSdkFound) {
76+
Write-Output "✅ No unexpected Windows SDK versions greater than `"${env:TEST_WIN_SDK_VERSION}`" found."
77+
}
78+
79+
# Check if the correct MSVC version is installed.
80+
$InstallerLocation = Join-Path "${env:ProgramFiles(x86)}" "Microsoft Visual Studio" "Installer"
81+
$VsWhere = Join-Path "${InstallerLocation}" "vswhere.exe"
82+
$InstallPath = (& "$VsWhere" -latest -products * -format json | ConvertFrom-Json).installationPath
83+
$MSVCDir = Join-Path $InstallPath "VC" "Tools" "MSVC"
84+
$DirFound = $false
85+
foreach ($dir in Get-ChildItem -Path $MSVCDir -Directory) {
86+
$MsvcDirName = $dir.Name
87+
if ($MsvcDirName.StartsWith($env:TEST_MSVC_VERSION)) {
88+
$DirFound = $true
89+
break
90+
}
91+
}
92+
if ($DirFound) {
93+
Write-Output "✅ MSVC version `${env:TEST_MSVC_VERSION}`" is installed."
94+
} else {
95+
Write-Output "::error::Expected MSVC version not found: `"${env:TEST_MSVC_VERSION}`"."
96+
$HasError = $true
97+
}
98+
99+
# Check the current cl.exe version by expanding the _MSC_VER macro.
100+
$tempFile = [System.IO.Path]::GetTempFileName().Replace('.tmp', '.c')
101+
Set-Content -Path $tempFile -Value "_MSC_VER"
102+
$clOutput = & cl /nologo /EP $tempFile 2>&1
103+
$lastLine = $clOutput | Select-Object -Last 1
104+
Remove-Item $tempFile -Force
105+
106+
$ParsedMsvcVersion = [System.Version]::Parse($env:TEST_MSVC_VERSION)
107+
$ExpectedVersion = ($ParsedMsvcVersion.Major + 5) * 100 + $ParsedMsvcVersion.Minor
108+
if ($lastLine -eq $ExpectedVersion) {
109+
Write-Output "✅ cl.exe reports expected _MSC_VER `"${ExpectedVersion}`"."
110+
} else {
111+
Write-Output "::error::Unexpected MSVC version found: `"${lastLine}`" (expected: `"${ExpectedVersion}`")."
112+
$HasError = $true
113+
}
114+
115+
# Check if the Windows SDK version is set in the environment.
116+
if ($env:UCRTVersion -eq $env:TEST_WIN_SDK_VERSION) {
117+
Write-Output "✅ UCRTVersion environment variable is set to `"${env:TEST_WIN_SDK_VERSION}`"."
118+
} else {
119+
Write-Output "::error::UCRTVersion environment variable (`"${env:UCRTVersion}`") is not set to the expected Windows SDK version (`"${env:TEST_WIN_SDK_VERSION}`")."
120+
$HasError = $true
121+
}
122+
123+
if ($HasError) {
124+
Write-Output "::error::There were errors in the environment setup. Check the logs for details."
125+
exit 1
126+
} else {
127+
Write-Output "🎉 All environment checks passed successfully."
128+
}

0 commit comments

Comments
 (0)