-
Notifications
You must be signed in to change notification settings - Fork 55
Add reusable action for MSVC + Win SDK overrides #956
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,236 @@ | ||
name: Setup build | ||
description: Sets up the build environment for the current job | ||
|
||
inputs: | ||
windows-sdk-version: | ||
description: The Windows SDK version to use, e.g. "10.0.22621.0" | ||
required: false | ||
type: string | ||
msvc-version: | ||
description: The Windows MSVC version to use, e.g. "14.42" | ||
required: false | ||
type: string | ||
setup-vs-dev-env: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Especially since this is also Windows specific |
||
description: Whether to set up a Visual Studio Dev Environment | ||
default: false | ||
required: false | ||
type: boolean | ||
target-arch: | ||
description: The target architecture, "x86", "amd64" or "arm64". Defaults to the host architecture. | ||
required: false | ||
type: string | ||
|
||
runs: | ||
using: composite | ||
steps: | ||
- name: Verify input | ||
id: verify-input | ||
shell: pwsh | ||
run: | | ||
if ($IsWindows) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Where is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's an automatic variable set up by pwsh. I was looking for something better to use to differentiate between Windows/macOS/Other. Unfortunately, the only other option I found is |
||
$HostOS = "windows" | ||
} elseif ($IsMacOS) { | ||
$HostOS = "mac" | ||
} else { | ||
Write-Output "::error::Unsupported host OS." | ||
exit 1 | ||
} | ||
|
||
$Arch = ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture).ToString() | ||
switch ($Arch) { | ||
"X64" { $HostArch = "amd64" } | ||
"Arm64" { $HostArch = "arm64" } | ||
default { | ||
Write-Output "::error::Unsupported host architecture: `"$HostArch`"" | ||
exit 1 | ||
} | ||
} | ||
|
||
# Validate the MSVC version input. | ||
# If specified, it is expected to have a format "major.minor", without the build and | ||
# revision numbers. When a value such as "14.42" is parsed as a `System.Version`, the build | ||
# and revision numbers in that object are set to -1. | ||
$MSVCVersion = "${{ inputs.msvc-version }}" | ||
if ($MSVCVersion -ne "") { | ||
$ParsedMSVCVersion = [System.Version]::Parse($MSVCVersion) | ||
if ($ParsedMSVCVersion -eq $null) { | ||
Write-Output "::error::Invalid Windows MSVC version: `"${MSVCVersion}`"." | ||
exit 1 | ||
} | ||
if ($ParsedMSVCVersion.Major -ne 14) { | ||
Write-Output "::error::Unsupported Windows MSVC version (major version not supported): `"${MSVCVersion}`"." | ||
exit 1 | ||
} | ||
if ($ParsedMSVCVersion.Build -ne -1) { | ||
Write-Output "::error::Unsupported Windows MSVC version (build version was specified): `"${MSVCVersion}`"." | ||
exit 1 | ||
} | ||
if ($ParsedMSVCVersion.Revision -ne -1) { | ||
Write-Output "::error::Unsupported Windows MSVC version (revision version was specified): `"${MSVCVersion}`"." | ||
exit 1 | ||
} | ||
} | ||
|
||
switch ("${{ inputs.target-arch }}") { | ||
"x86" { $TargetArch = "x86" } | ||
"amd64" { $TargetArch = "amd64" } | ||
"arm64" { $TargetArch = "arm64" } | ||
"" { $TargetArch = $HostArch } | ||
default { | ||
Write-Output "::error::Unsupported target architecture: `"${{ inputs.target-arch }}`"" | ||
exit 1 | ||
} | ||
} | ||
|
||
Write-Output "ℹ️ Host OS: $HostOS" | ||
Write-Output "ℹ️ Host architecture: $HostArch" | ||
Write-Output "ℹ️ Host OS: $HostOS" | ||
Write-Output "ℹ️ Host architecture: $TargetArch" | ||
|
||
@" | ||
host-os=$HostOS | ||
host-arch=$HostArch | ||
target-arch=$TargetArch | ||
"@ | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append | ||
|
||
- name: Install Windows SDK version ${{ inputs.windows-sdk-version }} | ||
if: steps.verify-input.outputs.host-os == 'windows' && inputs.windows-sdk-version != '' | ||
shell: pwsh | ||
run: | | ||
$WinSdkVersionString = "${{ inputs.windows-sdk-version }}" | ||
$WinSdkVersion = [System.Version]::Parse($WinSdkVersionString) | ||
$WinSdkVersionBuild = $WinSdkVersion.Build | ||
|
||
$Win10SdkRoot = Get-ItemPropertyValue ` | ||
-Path "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Kits\Installed Roots" ` | ||
-Name "KitsRoot10" | ||
$Win10SdkLib = Join-Path $Win10SdkRoot "Lib" | ||
$Win10SdkInclude = Join-Path $Win10SdkRoot "Include" | ||
$Win10SdkIncludeVersion = Join-Path $Win10SdkInclude $WinSdkVersionString | ||
Steelskin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if (Test-Path -Path $Win10SdkIncludeVersion -PathType Container) { | ||
Write-Output "ℹ️ MSVCPackageVersionWindows SDK ${WinSdkVersionString} already installed." | ||
} else { | ||
# Install the missing SDK. | ||
Write-Output "ℹ️ Installing Windows SDK ${WinSdkVersionString}..." | ||
|
||
$InstallerLocation = Join-Path "${env:ProgramFiles(x86)}" "Microsoft Visual Studio" "Installer" | ||
$VSWhere = Join-Path "${InstallerLocation}" "VSWhere.exe" | ||
$VSInstaller = Join-Path "${InstallerLocation}" "vs_installer.exe" | ||
$InstallPath = (& "$VSWhere" -latest -products * -format json | ConvertFrom-Json).installationPath | ||
$process = Start-Process "$VSInstaller" ` | ||
-PassThru ` | ||
-ArgumentList "modify", ` | ||
"--installPath", "`"$InstallPath`"", ` | ||
"--channelId", "https://aka.ms/vs/17/release/channel", ` | ||
"--quiet", "--norestart", "--nocache", ` | ||
"--add", "Microsoft.VisualStudio.Component.Windows11SDK.${WinSdkVersionBuild}" | ||
$process.WaitForExit() | ||
compnerd marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if (Test-Path -Path $Win10SdkIncludeVersion -PathType Container) { | ||
Write-Output "ℹ️ Windows SDK ${WinSdkVersionString} installed successfully." | ||
} else { | ||
Write-Output "::error::Failed to install Windows SDK ${WinSdkVersionString}." | ||
Write-Output "Installer log:" | ||
$log = Get-ChildItem "${env:TEMP}" -Filter "dd_installer_*.log" | Sort-Object LastWriteTime -Descending | Select-Object -First 1 | ||
Get-Content $log.FullName | ||
Steelskin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
exit 1 | ||
} | ||
} | ||
|
||
# Remove more recent Windows SDKs, if present. This is used to work | ||
Steelskin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# around issues where LLVM uses the most recent Windows SDK. | ||
# This should be removed once a more permanent solution is found. | ||
# See https://github.com/compnerd/swift-build/issues/958 for details. | ||
Get-ChildItem -Path $Win10SdkInclude -Directory | ForEach-Object { | ||
$IncludeDirName = $_.Name | ||
try { | ||
$IncludeDirVersion = [System.Version]::Parse($IncludeDirName) | ||
if ($IncludeDirVersion -gt $WinSdkVersion) { | ||
$LibDirVersion = Join-Path $Win10SdkLib $IncludeDirName | ||
Write-Output "ℹ️ Removing folders for Windows SDK ${IncludeDirVersion}." | ||
Remove-Item -Path $_.FullName -Recurse -Force -ErrorAction Ignore | ||
Remove-Item -Path $LibDirVersion -Recurse -Force -ErrorAction Ignore | ||
} | ||
} catch { | ||
# Skip if the directory cannot be parsed as a version. | ||
} | ||
} | ||
|
||
- name: Install Windows MSVC version ${{ inputs.msvc-version }} | ||
if: steps.verify-input.outputs.host-os == 'windows' && inputs.msvc-version != '' | ||
shell: pwsh | ||
run: | | ||
# This is assuming a VS2022 toolchain. e.g. | ||
# MSVC 14.42 corresponds to the 14.42.17.12 package. | ||
# MSVC 14.43 corresponds to the 14.43.17.13 package. | ||
$MSVCVersionString = "${{ inputs.msvc-version }}" | ||
|
||
$InstallerLocation = Join-Path "${env:ProgramFiles(x86)}" "Microsoft Visual Studio" "Installer" | ||
$VSWhere = Join-Path "${InstallerLocation}" "VSWhere.exe" | ||
$VSInstaller = Join-Path "${InstallerLocation}" "vs_installer.exe" | ||
$InstallPath = (& "$VSWhere" -latest -products * -format json | ConvertFrom-Json).installationPath | ||
$MSVCDir = Join-Path $InstallPath "VC" "Tools" "MSVC" | ||
|
||
# Check if this MSVC version is already installed. | ||
Get-ChildItem -Path $MSVCDir -Directory | ForEach-Object { | ||
$MSVCDirName = $_.Name | ||
if ($MSVCDirName.StartsWith($MSVCVersionString)) { | ||
Write-Output "ℹ️ MSVCPackageVersionMSVC ${MSVCVersionString} already installed." | ||
exit 0 | ||
} | ||
} | ||
|
||
# Compute the MSVC version package name from the MSVC version, assuming this is coming from | ||
# a VS2022 installation. The version package follows the following format: | ||
# * Major and minor version are the same as the MSVC version. | ||
# * Build version is always 17 (VS2002 is VS17). | ||
# * The revision is set to the number of minor versions since VS17 release. | ||
$MSVCVersion = [System.Version]::Parse($MSVCVersionString) | ||
$MajorVersion = $MSVCVersion.Major | ||
$MinorVersion = $MSVCVersion.Minor | ||
$BuildVersion = 17 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why hardcoded to VS17? (2022) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am trying to avoid having to specify 2 versions: the MSVC build tools version and the package version, so I am computing the package version from the |
||
$RevisionVersion = $MinorVersion - 30 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is this based on? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 14.29.16.11 was the last VS16 toolchain version. Basically, the first toolchain that shipped with VS17 was 14.30 and the revision number is based on that. I added a comment to explain that. You can find the package names here. Note that counterintuitively, while the build tools version is 14.42, the compiler itself has version 19.42. |
||
$MSVCPackageVersion = "${MajorVersion}.${MinorVersion}.${BuildVersion}.${RevisionVersion}" | ||
|
||
# Install the missing MSVC version. | ||
Write-Output "ℹ️ Installing MSVC packages for ${MSVCPackageVersion}..." | ||
$process = Start-Process "$VSInstaller" ` | ||
-PassThru ` | ||
-ArgumentList "modify", ` | ||
"--installPath", "`"$InstallPath`"", ` | ||
"--channelId", "https://aka.ms/vs/17/release/channel", ` | ||
"--quiet", "--norestart", "--nocache", ` | ||
"--add", "Microsoft.VisualStudio.Component.VC.${MSVCPackageVersion}.x86.x64", ` | ||
"--add", "Microsoft.VisualStudio.Component.VC.${MSVCPackageVersion}.ATL", ` | ||
"--add", "Microsoft.VisualStudio.Component.VC.${MSVCPackageVersion}.ARM64", ` | ||
"--add", "Microsoft.VisualStudio.Component.VC.${MSVCPackageVersion}.ATL.ARM64" | ||
$process.WaitForExit() | ||
|
||
# Check if the MSVC version was installed successfully. | ||
$MSVCDirFound = $false | ||
foreach ($dir in Get-ChildItem -Path $MSVCDir -Directory) { | ||
$MSVCDirName = $dir.Name | ||
if ($MSVCDirName.StartsWith($MSVCVersionString)) { | ||
Write-Output "ℹ️ MSVC ${MSVCVersionString} installed successfully." | ||
$MSVCDirFound = $true | ||
break | ||
} | ||
} | ||
|
||
if (-not $MSVCDirFound) { | ||
Write-Output "::error::Failed to install MSVC ${MSVCVersionString}." | ||
Write-Output "Installer log:" | ||
$log = Get-ChildItem "${env:TEMP}" -Filter "dd_installer_*.log" | Sort-Object LastWriteTime -Descending | Select-Object -First 1 | ||
Get-Content $log.FullName | ||
exit 1 | ||
} | ||
|
||
- name: Setup Visual Studio Developer Environment | ||
if: steps.verify-input.outputs.host-os == 'windows' && inputs.setup-vs-dev-env | ||
uses: compnerd/gha-setup-vsdevenv@5eb3eae1490d4f7875d574c4973539f69109700d # main | ||
with: | ||
host_arch: ${{ steps.verify-input.outputs.host-arch }} | ||
arch: ${{ steps.verify-input.outputs.target-arch }} | ||
winsdk: ${{ inputs.msvc-version }} | ||
toolset_version: ${{ inputs.msvc-version }} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
name: Test the setup-build action | ||
on: | ||
pull_request: | ||
branches: | ||
- 'main' | ||
paths: | ||
- '.github/actions/action.yml' | ||
- '.github/workflows/test-setup-build.yml' | ||
workflow_dispatch: | ||
inputs: | ||
windows-runner: | ||
description: "The Windows runner to use" | ||
required: false | ||
type: string | ||
workflow_call: | ||
inputs: | ||
windows-runner: | ||
description: "The Windows runner to use" | ||
required: false | ||
type: string | ||
|
||
env: | ||
TEST_WIN_SDK_VERSION: 10.0.22621.0 | ||
TEST_MSVC_VERSION: 14.42 | ||
|
||
jobs: | ||
test-setup-build-windows: | ||
name: Test MSVC and Windows SDK environment setup | ||
runs-on: ${{ inputs.windows-runner || 'windows-latest' }} | ||
steps: | ||
- name: Checkout | ||
uses: actions/checkout@v4.2.2 | ||
|
||
- name: Set up build | ||
uses: ./.github/actions/setup-build | ||
with: | ||
windows-sdk-version: ${{ env.TEST_WIN_SDK_VERSION }} | ||
msvc-version: ${{ env.TEST_MSVC_VERSION }} | ||
setup-vs-dev-env: true | ||
|
||
- name: Check environment | ||
run: | | ||
$HasError = $false | ||
|
||
$ParsedWinSdkVersion = [System.Version]::Parse($env:TEST_WIN_SDK_VERSION) | ||
$Win10SdkRoot = Get-ItemPropertyValue ` | ||
-Path "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Kits\Installed Roots" ` | ||
-Name "KitsRoot10" | ||
$Win10SdkInclude = Join-Path $Win10SdkRoot "Include" | ||
|
||
# Check if the Windows SDK version is installed. | ||
$ExpectedWinSdkDir = Join-Path $Win10SdkInclude "$($env:TEST_WIN_SDK_VERSION)" | ||
if (Test-Path -Path $ExpectedWinSdkDir) { | ||
Write-Output "✅ Windows SDK version `"${env:TEST_WIN_SDK_VERSION}`" is installed." | ||
} else { | ||
Write-Output "::error::Expected Windows SDK version not found: `"${env:TEST_WIN_SDK_VERSION}`"." | ||
$HasError = $true | ||
} | ||
|
||
# Check if Windows SDK versions greater than the expected version are installed. | ||
$UnexpectedSdkFound = $false | ||
Get-ChildItem -Path $Win10SdkInclude -Directory | ForEach-Object { | ||
$Version = $_.Name | ||
try { | ||
$ParsedVersion = [System.Version]::Parse($Version) | ||
if ($ParsedVersion -gt $ParsedWinSdkVersion) { | ||
Write-Output "::error::Unexpected Windows SDK version found: `"${Version}`" (greater than expected: `"${env:TEST_WIN_SDK_VERSION}`")." | ||
$HasError = $true | ||
$UnexpectedSdkFound = $true | ||
} | ||
} catch { | ||
# Skip if the directory cannot be parsed as a version. | ||
} | ||
} | ||
if (-not $UnexpectedSdkFound) { | ||
Write-Output "✅ No unexpected Windows SDK versions greater than `"${env:TEST_WIN_SDK_VERSION}`" found." | ||
} | ||
|
||
# Check if the correct MSVC version is installed. | ||
$InstallerLocation = Join-Path "${env:ProgramFiles(x86)}" "Microsoft Visual Studio" "Installer" | ||
$VSWhere = Join-Path "${InstallerLocation}" "vswhere.exe" | ||
$InstallPath = (& "$VSWhere" -latest -products * -format json | ConvertFrom-Json).installationPath | ||
$MSVCDir = Join-Path $InstallPath "VC" "Tools" "MSVC" | ||
$DirFound = $false | ||
foreach ($dir in Get-ChildItem -Path $MSVCDir -Directory) { | ||
$MSVCDirName = $dir.Name | ||
if ($MSVCDirName.StartsWith($env:TEST_MSVC_VERSION)) { | ||
$DirFound = $true | ||
break | ||
} | ||
} | ||
if ($DirFound) { | ||
Write-Output "✅ MSVC version `${env:TEST_MSVC_VERSION}`" is installed." | ||
} else { | ||
Write-Output "::error::Expected MSVC version not found: `"${env:TEST_MSVC_VERSION}`"." | ||
$HasError = $true | ||
} | ||
|
||
# Check the current cl.exe version by expanding the _MSC_VER macro. | ||
$tempFile = [System.IO.Path]::GetTempFileName().Replace('.tmp', '.c') | ||
Set-Content -Path $tempFile -Value "_MSC_VER" | ||
$clOutput = & cl /nologo /EP $tempFile 2>&1 | ||
$lastLine = $clOutput | Select-Object -Last 1 | ||
Remove-Item $tempFile -Force | ||
|
||
# _MSC_VER expands to a number like 1942 for MSVC 14.42. | ||
$ParsedMSVCVersion = [System.Version]::Parse($env:TEST_MSVC_VERSION) | ||
$ExpectedVersion = ($ParsedMSVCVersion.Major + 5) * 100 + $ParsedMSVCVersion.Minor | ||
if ($lastLine -eq $ExpectedVersion) { | ||
Write-Output "✅ cl.exe reports expected _MSC_VER `"${ExpectedVersion}`"." | ||
} else { | ||
Write-Output "::error::Unexpected MSVC version found: `"${lastLine}`" (expected: `"${ExpectedVersion}`")." | ||
$HasError = $true | ||
} | ||
|
||
# Check if the Windows SDK version is set in the environment. | ||
if ($env:UCRTVersion -eq $env:TEST_WIN_SDK_VERSION) { | ||
Write-Output "✅ UCRTVersion environment variable is set to `"${env:TEST_WIN_SDK_VERSION}`"." | ||
} else { | ||
Write-Output "::error::UCRTVersion environment variable (`"${env:UCRTVersion}`") is not set to the expected Windows SDK version (`"${env:TEST_WIN_SDK_VERSION}`")." | ||
$HasError = $true | ||
} | ||
|
||
if ($HasError) { | ||
Write-Output "::error::There were errors in the environment setup. Check the logs for details." | ||
exit 1 | ||
} else { | ||
Write-Output "🎉 All environment checks passed successfully." | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason to prefix the options with
windows-
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is going to be expanded to also add sccache and cmake, which are cross-platforms. I intend to reduce the number of
if:
in the swift workflow.However, I did rename the
msvc-version
input, since Windows is implied there.