diff --git a/.github/actions/setup-build/action.yml b/.github/actions/setup-build/action.yml new file mode 100644 index 00000000..6d267339 --- /dev/null +++ b/.github/actions/setup-build/action.yml @@ -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: + 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) { + $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 + + 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() + + 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 + exit 1 + } + } + + # Remove more recent Windows SDKs, if present. This is used to work + # 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 + $RevisionVersion = $MinorVersion - 30 + $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 }} diff --git a/.github/workflows/test-setup-build.yml b/.github/workflows/test-setup-build.yml new file mode 100644 index 00000000..10ad2b34 --- /dev/null +++ b/.github/workflows/test-setup-build.yml @@ -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." + }