From 2db68de256db957d503c9184046877f4d1fad1d4 Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Mon, 3 Nov 2025 09:23:29 -0500 Subject: [PATCH 1/4] Retry installing Visual Studio Build Tools on failure We've seen occasional failures to install the Visual Studio Build Tools due to flakey responses from the server. Attempt to make this script more robust by adding a retry with exponential backoff, attempting up to 10 times before eventually giving up. This adds the same logic to both the VSB and Swift toolchain downloads. --- .../workflows/scripts/windows/install-vsb.ps1 | 26 +++++++++++++++++-- .../scripts/windows/swift/install-swift.ps1 | 26 +++++++++++++++++-- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/.github/workflows/scripts/windows/install-vsb.ps1 b/.github/workflows/scripts/windows/install-vsb.ps1 index 931cbbbc..3be9a24c 100644 --- a/.github/workflows/scripts/windows/install-vsb.ps1 +++ b/.github/workflows/scripts/windows/install-vsb.ps1 @@ -14,8 +14,30 @@ $VSB_SHA256='C792BDB0FD46155DE19955269CAC85D52C4C63C23DB2CF43D96B9390146F9390' Set-Variable ErrorActionPreference Stop Set-Variable ProgressPreference SilentlyContinue Write-Host -NoNewLine ('Downloading {0} ... ' -f ${VSB}) -Invoke-WebRequest -Uri $VSB -OutFile $env:TEMP\vs_buildtools.exe -Write-Host 'SUCCESS' +$MaxRetries = 10 +$BaseDelay = 1 +$Attempt = 0 +$Success = $false + +while (-not $Success -and $Attempt -lt $MaxRetries) { + $Attempt++ + try { + Invoke-WebRequest -Uri $VSB -OutFile $env:TEMP\vs_buildtools.exe + $Success = $true + Write-Host 'SUCCESS' + } + catch { + if ($Attempt -eq $MaxRetries) { + Write-Host "FAILED after $MaxRetries attempts: $($_.Exception.Message)" + exit 1 + } + + # Calculate exponential backoff delay (2^attempt * base delay) + $Delay = $BaseDelay * [Math]::Pow(2, $Attempt - 1) + Write-Host "Attempt $Attempt failed, retrying in $Delay seconds..." + Start-Sleep -Seconds $Delay + } +} Write-Host -NoNewLine ('Verifying SHA256 ({0}) ... ' -f $VSB_SHA256) $Hash = Get-FileHash $env:TEMP\vs_buildtools.exe -Algorithm sha256 if ($Hash.Hash -eq $VSB_SHA256) { diff --git a/.github/workflows/scripts/windows/swift/install-swift.ps1 b/.github/workflows/scripts/windows/swift/install-swift.ps1 index 811a53da..6b658f48 100644 --- a/.github/workflows/scripts/windows/swift/install-swift.ps1 +++ b/.github/workflows/scripts/windows/swift/install-swift.ps1 @@ -17,8 +17,30 @@ function Install-Swift { Set-Variable ErrorActionPreference Stop Set-Variable ProgressPreference SilentlyContinue Write-Host -NoNewLine ('Downloading {0} ... ' -f $url) - Invoke-WebRequest -Uri $url -OutFile installer.exe - Write-Host 'SUCCESS' + $MaxRetries = 10 + $BaseDelay = 1 + $Attempt = 0 + $Success = $false + + while (-not $Success -and $Attempt -lt $MaxRetries) { + $Attempt++ + try { + Invoke-WebRequest -Uri $url -OutFile installer.exe + $Success = $true + Write-Host 'SUCCESS' + } + catch { + if ($Attempt -eq $MaxRetries) { + Write-Host "FAILED after $MaxRetries attempts: $($_.Exception.Message)" + exit 1 + } + + # Calculate exponential backoff delay (2^attempt * base delay) + $Delay = $BaseDelay * [Math]::Pow(2, $Attempt - 1) + Write-Host "Attempt $Attempt failed, retrying in $Delay seconds..." + Start-Sleep -Seconds $Delay + } + } Write-Host -NoNewLine ('Verifying SHA256 ({0}) ... ' -f $Sha256) $Hash = Get-FileHash installer.exe -Algorithm sha256 if ($Hash.Hash -eq $Sha256 -or $Sha256 -eq "") { From 43099ce569b10d334ad71e5fa458913b1f3c82a1 Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Mon, 3 Nov 2025 10:43:13 -0500 Subject: [PATCH 2/4] Move retry logic out to a utility --- .../workflows/scripts/windows/install-vsb.ps1 | 31 +++------ .../scripts/windows/swift/install-swift.ps1 | 31 +++------ .../scripts/windows/web-request-utils.psm1 | 67 +++++++++++++++++++ 3 files changed, 83 insertions(+), 46 deletions(-) create mode 100644 .github/workflows/scripts/windows/web-request-utils.psm1 diff --git a/.github/workflows/scripts/windows/install-vsb.ps1 b/.github/workflows/scripts/windows/install-vsb.ps1 index 3be9a24c..cdd3ed01 100644 --- a/.github/workflows/scripts/windows/install-vsb.ps1 +++ b/.github/workflows/scripts/windows/install-vsb.ps1 @@ -9,34 +9,19 @@ ## See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors ## ##===----------------------------------------------------------------------===## + +Import-Module $PSScriptRoot\web-request-utils.psm1 + $VSB='https://download.visualstudio.microsoft.com/download/pr/5536698c-711c-4834-876f-2817d31a2ef2/c792bdb0fd46155de19955269cac85d52c4c63c23db2cf43d96b9390146f9390/vs_BuildTools.exe' $VSB_SHA256='C792BDB0FD46155DE19955269CAC85D52C4C63C23DB2CF43D96B9390146F9390' Set-Variable ErrorActionPreference Stop Set-Variable ProgressPreference SilentlyContinue Write-Host -NoNewLine ('Downloading {0} ... ' -f ${VSB}) -$MaxRetries = 10 -$BaseDelay = 1 -$Attempt = 0 -$Success = $false - -while (-not $Success -and $Attempt -lt $MaxRetries) { - $Attempt++ - try { - Invoke-WebRequest -Uri $VSB -OutFile $env:TEMP\vs_buildtools.exe - $Success = $true - Write-Host 'SUCCESS' - } - catch { - if ($Attempt -eq $MaxRetries) { - Write-Host "FAILED after $MaxRetries attempts: $($_.Exception.Message)" - exit 1 - } - - # Calculate exponential backoff delay (2^attempt * base delay) - $Delay = $BaseDelay * [Math]::Pow(2, $Attempt - 1) - Write-Host "Attempt $Attempt failed, retrying in $Delay seconds..." - Start-Sleep -Seconds $Delay - } +try { + Invoke-WebRequestWithRetry -Uri $VSB -OutFile $env:TEMP\vs_buildtools.exe +} +catch { + exit 1 } Write-Host -NoNewLine ('Verifying SHA256 ({0}) ... ' -f $VSB_SHA256) $Hash = Get-FileHash $env:TEMP\vs_buildtools.exe -Algorithm sha256 diff --git a/.github/workflows/scripts/windows/swift/install-swift.ps1 b/.github/workflows/scripts/windows/swift/install-swift.ps1 index 6b658f48..901aa174 100644 --- a/.github/workflows/scripts/windows/swift/install-swift.ps1 +++ b/.github/workflows/scripts/windows/swift/install-swift.ps1 @@ -9,6 +9,9 @@ ## See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors ## ##===----------------------------------------------------------------------===## + +Import-Module $PSScriptRoot\..\web-request-utils.psm1 + function Install-Swift { param ( [string]$Url, @@ -17,29 +20,11 @@ function Install-Swift { Set-Variable ErrorActionPreference Stop Set-Variable ProgressPreference SilentlyContinue Write-Host -NoNewLine ('Downloading {0} ... ' -f $url) - $MaxRetries = 10 - $BaseDelay = 1 - $Attempt = 0 - $Success = $false - - while (-not $Success -and $Attempt -lt $MaxRetries) { - $Attempt++ - try { - Invoke-WebRequest -Uri $url -OutFile installer.exe - $Success = $true - Write-Host 'SUCCESS' - } - catch { - if ($Attempt -eq $MaxRetries) { - Write-Host "FAILED after $MaxRetries attempts: $($_.Exception.Message)" - exit 1 - } - - # Calculate exponential backoff delay (2^attempt * base delay) - $Delay = $BaseDelay * [Math]::Pow(2, $Attempt - 1) - Write-Host "Attempt $Attempt failed, retrying in $Delay seconds..." - Start-Sleep -Seconds $Delay - } + try { + Invoke-WebRequestWithRetry -Uri $url -OutFile installer.exe + } + catch { + exit 1 } Write-Host -NoNewLine ('Verifying SHA256 ({0}) ... ' -f $Sha256) $Hash = Get-FileHash installer.exe -Algorithm sha256 diff --git a/.github/workflows/scripts/windows/web-request-utils.psm1 b/.github/workflows/scripts/windows/web-request-utils.psm1 new file mode 100644 index 00000000..4e0a1b05 --- /dev/null +++ b/.github/workflows/scripts/windows/web-request-utils.psm1 @@ -0,0 +1,67 @@ +# WebRequestUtils.psm1 +# Shared utilities for web requests with retry logic + +<# +.SYNOPSIS +Invokes a web request with retry logic and exponential backoff. + +.DESCRIPTION +Attempts to download a file from a URL with automatic retry on failure. +Uses exponential backoff to handle transient network failures. + +.PARAMETER Uri +The URL to download from. + +.PARAMETER OutFile +The destination file path for the download. + +.PARAMETER MaxRetries +Maximum number of retry attempts (default: 10). + +.PARAMETER BaseDelay +Base delay in seconds for exponential backoff (default: 1). + +.EXAMPLE +Invoke-WebRequestWithRetry -Uri "https://example.com/file.exe" -OutFile "file.exe" + +.EXAMPLE +Invoke-WebRequestWithRetry -Uri "https://example.com/file.exe" -OutFile "file.exe" -MaxRetries 5 -BaseDelay 2 +#> +function Invoke-WebRequestWithRetry { + param( + [Parameter(Mandatory=$true)] + [string]$Uri, + + [Parameter(Mandatory=$true)] + [string]$OutFile, + + [int]$MaxRetries = 10, + + [int]$BaseDelay = 1 + ) + + $Attempt = 0 + $Success = $false + + while (-not $Success -and $Attempt -lt $MaxRetries) { + $Attempt++ + try { + Invoke-WebRequest -Uri $Uri -OutFile $OutFile + $Success = $true + Write-Host 'SUCCESS' + } + catch { + if ($Attempt -eq $MaxRetries) { + Write-Host "FAILED after $MaxRetries attempts: $($_.Exception.Message)" + throw + } + + # Calculate exponential backoff delay (2^attempt * base delay) + $Delay = $BaseDelay * [Math]::Pow(2, $Attempt - 1) + Write-Host "Attempt $Attempt failed, retrying in $Delay seconds..." + Start-Sleep -Seconds $Delay + } + } +} + +Export-ModuleMember -Function Invoke-WebRequestWithRetry From 79b5dd5aa625493750ac3b4d4579baf3f11b6055 Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Mon, 3 Nov 2025 11:47:23 -0500 Subject: [PATCH 3/4] Add licence header to web-request-utils.psm1 --- .../scripts/windows/web-request-utils.psm1 | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/scripts/windows/web-request-utils.psm1 b/.github/workflows/scripts/windows/web-request-utils.psm1 index 4e0a1b05..7e3f49bc 100644 --- a/.github/workflows/scripts/windows/web-request-utils.psm1 +++ b/.github/workflows/scripts/windows/web-request-utils.psm1 @@ -1,4 +1,15 @@ -# WebRequestUtils.psm1 +##===----------------------------------------------------------------------===## +## +## This source file is part of the Swift.org open source project +## +## Copyright (c) 2024 Apple Inc. and the Swift project authors +## Licensed under Apache License v2.0 with Runtime Library Exception +## +## See https://swift.org/LICENSE.txt for license information +## See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +## +##===----------------------------------------------------------------------===## + # Shared utilities for web requests with retry logic <# @@ -31,12 +42,9 @@ function Invoke-WebRequestWithRetry { param( [Parameter(Mandatory=$true)] [string]$Uri, - [Parameter(Mandatory=$true)] [string]$OutFile, - [int]$MaxRetries = 10, - [int]$BaseDelay = 1 ) From d2e883df0559efd25ff38fb3878613975f39e944 Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Mon, 17 Nov 2025 09:56:52 -0500 Subject: [PATCH 4/4] Use curl with --retry --- .../workflows/scripts/windows/install-vsb.ps1 | 22 +++++- .../scripts/windows/swift/install-swift.ps1 | 22 +++++- .../scripts/windows/web-request-utils.psm1 | 75 ------------------- 3 files changed, 38 insertions(+), 81 deletions(-) delete mode 100644 .github/workflows/scripts/windows/web-request-utils.psm1 diff --git a/.github/workflows/scripts/windows/install-vsb.ps1 b/.github/workflows/scripts/windows/install-vsb.ps1 index cdd3ed01..d06c552f 100644 --- a/.github/workflows/scripts/windows/install-vsb.ps1 +++ b/.github/workflows/scripts/windows/install-vsb.ps1 @@ -10,17 +10,33 @@ ## ##===----------------------------------------------------------------------===## -Import-Module $PSScriptRoot\web-request-utils.psm1 - $VSB='https://download.visualstudio.microsoft.com/download/pr/5536698c-711c-4834-876f-2817d31a2ef2/c792bdb0fd46155de19955269cac85d52c4c63c23db2cf43d96b9390146f9390/vs_BuildTools.exe' $VSB_SHA256='C792BDB0FD46155DE19955269CAC85D52C4C63C23DB2CF43D96B9390146F9390' Set-Variable ErrorActionPreference Stop Set-Variable ProgressPreference SilentlyContinue Write-Host -NoNewLine ('Downloading {0} ... ' -f ${VSB}) try { - Invoke-WebRequestWithRetry -Uri $VSB -OutFile $env:TEMP\vs_buildtools.exe + # Use curl with retry logic (10 retries with exponential backoff starting at 1 second) + # --retry-all-errors ensures we retry on transfer failures (e.g., exit code 18) + # -C - enables resume for partial downloads + $exitCode = (Start-Process -FilePath "curl" -ArgumentList @( + "--retry", "10", + "--retry-delay", "1", + "--retry-all-errors", + "--retry-max-time", "300", + "--location", + "-C", "-", + "--output", "$env:TEMP\vs_buildtools.exe", + $VSB + ) -Wait -PassThru -NoNewWindow).ExitCode + + if ($exitCode -ne 0) { + throw "curl failed with exit code $exitCode" + } + Write-Host 'SUCCESS' } catch { + Write-Host "FAILED: $($_.Exception.Message)" exit 1 } Write-Host -NoNewLine ('Verifying SHA256 ({0}) ... ' -f $VSB_SHA256) diff --git a/.github/workflows/scripts/windows/swift/install-swift.ps1 b/.github/workflows/scripts/windows/swift/install-swift.ps1 index 901aa174..6c53e2de 100644 --- a/.github/workflows/scripts/windows/swift/install-swift.ps1 +++ b/.github/workflows/scripts/windows/swift/install-swift.ps1 @@ -10,8 +10,6 @@ ## ##===----------------------------------------------------------------------===## -Import-Module $PSScriptRoot\..\web-request-utils.psm1 - function Install-Swift { param ( [string]$Url, @@ -21,9 +19,27 @@ function Install-Swift { Set-Variable ProgressPreference SilentlyContinue Write-Host -NoNewLine ('Downloading {0} ... ' -f $url) try { - Invoke-WebRequestWithRetry -Uri $url -OutFile installer.exe + # Use curl with retry logic (10 retries with exponential backoff starting at 1 second) + # --retry-all-errors ensures we retry on transfer failures (e.g., exit code 18) + # -C - enables resume for partial downloads + $exitCode = (Start-Process -FilePath "curl" -ArgumentList @( + "--retry", "10", + "--retry-delay", "1", + "--retry-all-errors", + "--retry-max-time", "300", + "--location", + "-C", "-", + "--output", "installer.exe", + $url + ) -Wait -PassThru -NoNewWindow).ExitCode + + if ($exitCode -ne 0) { + throw "curl failed with exit code $exitCode" + } + Write-Host 'SUCCESS' } catch { + Write-Host "FAILED: $($_.Exception.Message)" exit 1 } Write-Host -NoNewLine ('Verifying SHA256 ({0}) ... ' -f $Sha256) diff --git a/.github/workflows/scripts/windows/web-request-utils.psm1 b/.github/workflows/scripts/windows/web-request-utils.psm1 deleted file mode 100644 index 7e3f49bc..00000000 --- a/.github/workflows/scripts/windows/web-request-utils.psm1 +++ /dev/null @@ -1,75 +0,0 @@ -##===----------------------------------------------------------------------===## -## -## This source file is part of the Swift.org open source project -## -## Copyright (c) 2024 Apple Inc. and the Swift project authors -## Licensed under Apache License v2.0 with Runtime Library Exception -## -## See https://swift.org/LICENSE.txt for license information -## See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors -## -##===----------------------------------------------------------------------===## - -# Shared utilities for web requests with retry logic - -<# -.SYNOPSIS -Invokes a web request with retry logic and exponential backoff. - -.DESCRIPTION -Attempts to download a file from a URL with automatic retry on failure. -Uses exponential backoff to handle transient network failures. - -.PARAMETER Uri -The URL to download from. - -.PARAMETER OutFile -The destination file path for the download. - -.PARAMETER MaxRetries -Maximum number of retry attempts (default: 10). - -.PARAMETER BaseDelay -Base delay in seconds for exponential backoff (default: 1). - -.EXAMPLE -Invoke-WebRequestWithRetry -Uri "https://example.com/file.exe" -OutFile "file.exe" - -.EXAMPLE -Invoke-WebRequestWithRetry -Uri "https://example.com/file.exe" -OutFile "file.exe" -MaxRetries 5 -BaseDelay 2 -#> -function Invoke-WebRequestWithRetry { - param( - [Parameter(Mandatory=$true)] - [string]$Uri, - [Parameter(Mandatory=$true)] - [string]$OutFile, - [int]$MaxRetries = 10, - [int]$BaseDelay = 1 - ) - - $Attempt = 0 - $Success = $false - - while (-not $Success -and $Attempt -lt $MaxRetries) { - $Attempt++ - try { - Invoke-WebRequest -Uri $Uri -OutFile $OutFile - $Success = $true - Write-Host 'SUCCESS' - } - catch { - if ($Attempt -eq $MaxRetries) { - Write-Host "FAILED after $MaxRetries attempts: $($_.Exception.Message)" - throw - } - - # Calculate exponential backoff delay (2^attempt * base delay) - $Delay = $BaseDelay * [Math]::Pow(2, $Attempt - 1) - Write-Host "Attempt $Attempt failed, retrying in $Delay seconds..." - Start-Sleep -Seconds $Delay - } - } -} - -Export-ModuleMember -Function Invoke-WebRequestWithRetry