diff --git a/.github/workflows/scripts/install-and-build-with-sdk.sh b/.github/workflows/scripts/install-and-build-with-sdk.sh index 63b1dc0e..aeabc7fa 100755 --- a/.github/workflows/scripts/install-and-build-with-sdk.sh +++ b/.github/workflows/scripts/install-and-build-with-sdk.sh @@ -17,6 +17,91 @@ log() { printf -- "** %s\n" "$*" >&2; } error() { printf -- "** ERROR: %s\n" "$*" >&2; } fatal() { error "$@"; exit 1; } +# Retry configuration +CURL_MAX_RETRIES=5 +CURL_RETRY_DELAY=5 +CURL_TIMEOUT=300 + +SDK_INSTALL_MAX_RETRIES=5 +SDK_INSTALL_INITIAL_RETRY_DELAY=10 + +curl_with_retry() { + local attempt=1 + local exit_code=0 + + while [ $attempt -le $CURL_MAX_RETRIES ]; do + if [ $attempt -gt 1 ]; then + log "Retry attempt $attempt of $CURL_MAX_RETRIES after ${CURL_RETRY_DELAY}s delay..." + sleep $CURL_RETRY_DELAY + fi + + # Run curl with connection timeout and max time limits + if curl --connect-timeout 30 --max-time $CURL_TIMEOUT --retry 3 --retry-delay 2 --retry-max-time 60 "$@"; then + return 0 + else + exit_code=$? + log "curl failed with exit code $exit_code on attempt $attempt" + fi + + attempt=$((attempt + 1)) + done + + error "curl failed after $CURL_MAX_RETRIES attempts" + return $exit_code +} + +swift_sdk_install_with_retry() { + local swift_executable="$1" + local sdk_url="$2" + local checksum="$3" + local sdk_type="$4" + + local attempt=1 + local retry_delay=$SDK_INSTALL_INITIAL_RETRY_DELAY + + # Extract SDK name from URL for checking if already installed + local sdk_filename + sdk_filename=$(basename "$sdk_url") + local sdk_name="${sdk_filename%.tar.gz}" + + while [ $attempt -le $SDK_INSTALL_MAX_RETRIES ]; do + if [ $attempt -gt 1 ]; then + log "Retry attempt $attempt of $SDK_INSTALL_MAX_RETRIES for ${sdk_type} SDK installation after ${retry_delay}s delay..." + + # Before retrying, check if SDK was partially installed and remove it + log "Checking for partially installed SDK..." + if "$swift_executable" sdk list 2>/dev/null | grep -q "^${sdk_name}"; then + log "Found partially installed SDK, attempting to remove it..." + if "$swift_executable" sdk remove "$sdk_name" 2>/dev/null; then + log "Successfully removed partially installed SDK" + else + log "Warning: Failed to remove partially installed SDK, continuing anyway..." + fi + fi + + sleep $retry_delay + fi + + log "Attempt $attempt: Installing ${sdk_type} SDK from ${sdk_url}" + + if "$swift_executable" sdk install "$sdk_url" --checksum "$checksum"; then + log "✅ ${sdk_type} SDK installed successfully" + return 0 + else + local exit_code=$? + log "swift sdk install failed with exit code $exit_code on attempt $attempt" + + # Exponential backoff: double the delay each time + retry_delay=$((retry_delay * 2)) + fi + + attempt=$((attempt + 1)) + done + + error "${sdk_type} SDK installation failed after $SDK_INSTALL_MAX_RETRIES attempts" + return 1 +} + # Parse command line options INSTALL_ANDROID=false INSTALL_STATIC_LINUX=false @@ -125,7 +210,7 @@ find_latest_swift_version() { log "Fetching releases from swift.org API..." local releases_json - releases_json=$(curl -fsSL "${SWIFT_API_INSTALL_ROOT}/releases.json") || fatal "Failed to fetch Swift releases" + releases_json=$(curl_with_retry -fsSL "${SWIFT_API_INSTALL_ROOT}/releases.json") || fatal "Failed to fetch Swift releases" # Find all releases that start with the minor version (e.g, "6.1") # Sort them and get the latest one @@ -212,7 +297,7 @@ find_latest_sdk_snapshot() { log "Fetching development snapshots from swift.org API..." local sdk_json - sdk_json=$(curl -fsSL "${SWIFT_API_INSTALL_ROOT}/dev/${version}/${sdk_name}-sdk.json") || fatal "Failed to fetch ${sdk_name}-sdk development snapshots" + sdk_json=$(curl_with_retry -fsSL "${SWIFT_API_INSTALL_ROOT}/dev/${version}/${sdk_name}-sdk.json") || fatal "Failed to fetch ${sdk_name}-sdk development snapshots" # Extract the snapshot tag from the "dir" field of the first (newest) element local snapshot_tag @@ -400,16 +485,16 @@ download_and_verify() { local temp_sig="${output_file}.sig" log "Downloading ${url}" - curl -fsSL "$url" -o "$output_file" + curl_with_retry -fsSL "$url" -o "$output_file" log "Downloading signature" - curl -fsSL "$sig_url" -o "$temp_sig" + curl_with_retry -fsSL "$sig_url" -o "$temp_sig" log "Setting up GPG for verification" local gnupghome gnupghome="$(mktemp -d)" export GNUPGHOME="$gnupghome" - curl -fSsL https://swift.org/keys/all-keys.asc | zcat -f | gpg --import - >/dev/null 2>&1 + curl_with_retry -fSsL https://swift.org/keys/all-keys.asc | zcat -f | gpg --import - >/dev/null 2>&1 log "Verifying signature" if gpg --batch --verify "$temp_sig" "$output_file" >/dev/null 2>&1; then @@ -445,7 +530,7 @@ download_and_extract_toolchain() { # Check if toolchain is available local http_code - http_code=$(curl -sSL --head -w "%{http_code}" -o /dev/null "$toolchain_url") + http_code=$(curl_with_retry -sSL --head -w "%{http_code}" -o /dev/null "$toolchain_url") if [[ "$http_code" == "404" ]]; then log "Toolchain not found: ${toolchain_filename}" log "Exiting workflow..." @@ -556,11 +641,7 @@ install_android_sdk() { local android_sdk_filename="${android_sdk_bundle_name}.tar.gz" local sdk_url="${ANDROID_SDK_DOWNLOAD_ROOT}/${ANDROID_SDK_TAG}/${android_sdk_filename}" - log "Running: ${SWIFT_EXECUTABLE_FOR_ANDROID_SDK} sdk install ${sdk_url} --checksum ${ANDROID_SDK_CHECKSUM}" - - if "$SWIFT_EXECUTABLE_FOR_ANDROID_SDK" sdk install "$sdk_url" --checksum "$ANDROID_SDK_CHECKSUM"; then - log "✅ Android Swift SDK installed successfully" - else + if ! swift_sdk_install_with_retry "$SWIFT_EXECUTABLE_FOR_ANDROID_SDK" "$sdk_url" "$ANDROID_SDK_CHECKSUM" "Android Swift"; then fatal "Failed to install Android Swift SDK" fi @@ -579,7 +660,7 @@ install_android_sdk() { if [[ ! -d "${ANDROID_NDK_HOME:-}" ]]; then # permit the "--android-ndk" flag to override the default local android_ndk_version="${ANDROID_NDK_VERSION:-r27d}" - curl -fsSL -o ndk.zip --retry 3 https://dl.google.com/android/repository/android-ndk-"${android_ndk_version}"-"$(uname -s)".zip + curl_with_retry -fsSL -o ndk.zip https://dl.google.com/android/repository/android-ndk-"${android_ndk_version}"-"$(uname -s)".zip command -v unzip >/dev/null || install_package unzip unzip -q ndk.zip rm ndk.zip @@ -602,11 +683,7 @@ install_static_linux_sdk() { local static_linux_sdk_filename="${STATIC_LINUX_SDK_TAG}_static-linux-0.0.1.artifactbundle.tar.gz" local sdk_url="${STATIC_LINUX_SDK_DOWNLOAD_ROOT}/${STATIC_LINUX_SDK_TAG}/${static_linux_sdk_filename}" - log "Running: ${SWIFT_EXECUTABLE_FOR_STATIC_LINUX_SDK} sdk install ${sdk_url} --checksum ${STATIC_LINUX_SDK_CHECKSUM}" - - if "$SWIFT_EXECUTABLE_FOR_STATIC_LINUX_SDK" sdk install "$sdk_url" --checksum "$STATIC_LINUX_SDK_CHECKSUM"; then - log "✅ Static Linux Swift SDK installed successfully" - else + if ! swift_sdk_install_with_retry "$SWIFT_EXECUTABLE_FOR_STATIC_LINUX_SDK" "$sdk_url" "$STATIC_LINUX_SDK_CHECKSUM" "Static Linux Swift"; then fatal "Failed to install Static Linux Swift SDK" fi @@ -625,11 +702,7 @@ install_wasm_sdk() { local wasm_sdk_filename="${WASM_SDK_TAG}_wasm.artifactbundle.tar.gz" local sdk_url="${WASM_SDK_DOWNLOAD_ROOT}/${WASM_SDK_TAG}/${wasm_sdk_filename}" - log "Running: ${SWIFT_EXECUTABLE_FOR_WASM_SDK} sdk install ${sdk_url} --checksum ${WASM_SDK_CHECKSUM}" - - if "$SWIFT_EXECUTABLE_FOR_WASM_SDK" sdk install "$sdk_url" --checksum "$WASM_SDK_CHECKSUM"; then - log "✅ Swift SDK for Wasm installed successfully" - else + if ! swift_sdk_install_with_retry "$SWIFT_EXECUTABLE_FOR_WASM_SDK" "$sdk_url" "$WASM_SDK_CHECKSUM" "Swift Wasm"; then fatal "Failed to install Swift SDK for Wasm" fi diff --git a/.github/workflows/scripts/windows/install-vsb.ps1 b/.github/workflows/scripts/windows/install-vsb.ps1 index d251bf4a..439e4975 100644 --- a/.github/workflows/scripts/windows/install-vsb.ps1 +++ b/.github/workflows/scripts/windows/install-vsb.ps1 @@ -9,36 +9,185 @@ ## See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors ## ##===----------------------------------------------------------------------===## -$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}) -Invoke-WebRequest -Uri $VSB -OutFile $env:TEMP\vs_buildtools.exe -Write-Host 'SUCCESS' -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) { - Write-Host 'SUCCESS' -} else { - Write-Host ('FAILED ({0})' -f $Hash.Hash) - exit 1 + +# Retry configuration +$MaxRetries = 5 +$RetryDelay = 5 + +function Invoke-WebRequestWithRetry { + param ( + [string]$Uri, + [string]$OutFile, + [int]$TimeoutSec = 300 + ) + + $attempt = 1 + + while ($attempt -le $MaxRetries) { + try { + if ($attempt -gt 1) { + Write-Host "Retry attempt $attempt of $MaxRetries after ${RetryDelay}s delay..." + Start-Sleep -Seconds $RetryDelay + } + + Write-Host "Attempt $attempt`: Downloading from $Uri" + + # Clean up any existing partial download + if (Test-Path $OutFile) { + Remove-Item -Force $OutFile -ErrorAction SilentlyContinue + } + + # Get expected file size from HTTP headers + $headRequest = Invoke-WebRequest -Uri $Uri -Method Head -UseBasicParsing -TimeoutSec 30 + $expectedSize = $null + if ($headRequest.Headers.ContainsKey('Content-Length')) { + $expectedSize = [long]$headRequest.Headers['Content-Length'][0] + Write-Host "Expected file size: $([math]::Round($expectedSize / 1MB, 2)) MB" + } + + # Download with progress tracking disabled for better performance + $webClient = New-Object System.Net.WebClient + $webClient.DownloadFile($Uri, $OutFile) + $webClient.Dispose() + + # Verify file exists and has content + if (-not (Test-Path $OutFile)) { + throw "Download completed but file not found at $OutFile" + } + + $actualSize = (Get-Item $OutFile).Length + Write-Host "Downloaded file size: $([math]::Round($actualSize / 1MB, 2)) MB" + + # Verify file size matches expected size + if ($expectedSize -and $actualSize -ne $expectedSize) { + throw "File size mismatch. Expected: $expectedSize bytes, Got: $actualSize bytes" + } + + # Verify file is not corrupted by checking if it's a valid PE executable + $fileBytes = [System.IO.File]::ReadAllBytes($OutFile) + if ($fileBytes.Length -lt 2 -or $fileBytes[0] -ne 0x4D -or $fileBytes[1] -ne 0x5A) { + throw "Downloaded file is not a valid executable (missing MZ header)" + } + + Write-Host "Download completed and verified successfully" + return $true + } + catch { + Write-Host "Download failed on attempt $attempt`: $($_.Exception.Message)" + + # Clean up partial download if it exists + if (Test-Path $OutFile) { + Remove-Item -Force $OutFile -ErrorAction SilentlyContinue + } + + if ($attempt -eq $MaxRetries) { + Write-Host "Download failed after $MaxRetries attempts" + throw + } + } + + $attempt++ + } + + return $false +} + +function Remove-FileWithRetry { + param ( + [string]$Path + ) + + $attempt = 1 + + while ($attempt -le $MaxRetries) { + try { + if (Test-Path $Path) { + Remove-Item -Force $Path -ErrorAction Stop + Write-Host "Successfully removed $Path" + return $true + } else { + return $true + } + } + catch { + if ($attempt -eq $MaxRetries) { + Write-Host "Warning: Failed to remove $Path after $MaxRetries attempts: $($_.Exception.Message)" + Write-Host "The file may be locked by another process. It will be cleaned up later." + return $false + } + + Write-Host "Attempt $attempt to remove $Path failed, retrying in ${RetryDelay}s..." + Start-Sleep -Seconds $RetryDelay + } + + $attempt++ + } + + return $false } -Write-Host -NoNewLine 'Installing Visual Studio Build Tools ... ' -$Process = - Start-Process $env:TEMP\vs_buildtools.exe -Wait -PassThru -NoNewWindow -ArgumentList @( - '--quiet', - '--wait', - '--norestart', - '--nocache', - '--add', 'Microsoft.VisualStudio.Component.Windows11SDK.22000', - '--add', 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64', - '--add', 'Microsoft.VisualStudio.Component.VC.Tools.ARM64' + +function Install-VisualStudioBuildTools { + param ( + [string]$Url, + [string]$Sha256 ) -if ($Process.ExitCode -eq 0 -or $Process.ExitCode -eq 3010) { - Write-Host 'SUCCESS' -} else { - Write-Host ('FAILED ({0})' -f $Process.ExitCode) - exit 1 + + Set-Variable ErrorActionPreference Stop + Set-Variable ProgressPreference SilentlyContinue + + $installerPath = "$env:TEMP\vs_buildtools.exe" + + Write-Host "Downloading Visual Studio Build Tools from $Url" + + try { + Invoke-WebRequestWithRetry -Uri $Url -OutFile $installerPath + Write-Host 'Download SUCCESS' + } + catch { + Write-Host "Download FAILED: $($_.Exception.Message)" + Remove-FileWithRetry -Path $installerPath + exit 1 + } + + Write-Host -NoNewLine ('Verifying SHA256 ({0}) ... ' -f $Sha256) + $Hash = Get-FileHash $installerPath -Algorithm sha256 + if ($Hash.Hash -eq $Sha256) { + Write-Host 'SUCCESS' + } else { + Write-Host ('FAILED ({0})' -f $Hash.Hash) + Remove-FileWithRetry -Path $installerPath + exit 1 + } + + Write-Host -NoNewLine 'Installing Visual Studio Build Tools ... ' + try { + $Process = Start-Process $installerPath -Wait -PassThru -NoNewWindow -ArgumentList @( + '--quiet', + '--wait', + '--norestart', + '--nocache', + '--add', 'Microsoft.VisualStudio.Component.Windows11SDK.22000', + '--add', 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64', + '--add', 'Microsoft.VisualStudio.Component.VC.Tools.ARM64' + ) + if ($Process.ExitCode -eq 0 -or $Process.ExitCode -eq 3010) { + Write-Host 'SUCCESS' + } else { + Write-Host ('FAILED ({0})' -f $Process.ExitCode) + Remove-FileWithRetry -Path $installerPath + exit 1 + } + } + catch { + Write-Host "FAILED: $($_.Exception.Message)" + Remove-FileWithRetry -Path $installerPath + exit 1 + } + + Remove-FileWithRetry -Path $installerPath } -Remove-Item -Force $env:TEMP\vs_buildtools.exe \ No newline at end of file + +$VSB = 'https://download.visualstudio.microsoft.com/download/pr/5536698c-711c-4834-876f-2817d31a2ef2/c792bdb0fd46155de19955269cac85d52c4c63c23db2cf43d96b9390146f9390/vs_BuildTools.exe' +$VSB_SHA256 = 'C792BDB0FD46155DE19955269CAC85D52C4C63C23DB2CF43D96B9390146F9390' + +Install-VisualStudioBuildTools -Url $VSB -Sha256 $VSB_SHA256 diff --git a/.github/workflows/scripts/windows/swift/install-swift.ps1 b/.github/workflows/scripts/windows/swift/install-swift.ps1 index 811a53da..23e06a33 100644 --- a/.github/workflows/scripts/windows/swift/install-swift.ps1 +++ b/.github/workflows/scripts/windows/swift/install-swift.ps1 @@ -9,6 +9,123 @@ ## See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors ## ##===----------------------------------------------------------------------===## + +# Retry configuration +$MaxRetries = 5 +$RetryDelay = 5 + +function Invoke-WebRequestWithRetry { + param ( + [string]$Uri, + [string]$OutFile, + [int]$TimeoutSec = 300 + ) + + $attempt = 1 + + while ($attempt -le $MaxRetries) { + try { + if ($attempt -gt 1) { + Write-Host "Retry attempt $attempt of $MaxRetries after ${RetryDelay}s delay..." + Start-Sleep -Seconds $RetryDelay + } + + Write-Host "Attempt $attempt`: Downloading from $Uri" + + # Clean up any existing partial download + if (Test-Path $OutFile) { + Remove-Item -Force $OutFile -ErrorAction SilentlyContinue + } + + # Get expected file size from HTTP headers + $headRequest = Invoke-WebRequest -Uri $Uri -Method Head -UseBasicParsing -TimeoutSec 30 + $expectedSize = $null + if ($headRequest.Headers.ContainsKey('Content-Length')) { + $expectedSize = [long]$headRequest.Headers['Content-Length'][0] + Write-Host "Expected file size: $($expectedSize / 1MB) MB" + } + + # Download with progress tracking disabled for better performance + $webClient = New-Object System.Net.WebClient + $webClient.DownloadFile($Uri, $OutFile) + $webClient.Dispose() + + # Verify file exists and has content + if (-not (Test-Path $OutFile)) { + throw "Download completed but file not found at $OutFile" + } + + $actualSize = (Get-Item $OutFile).Length + Write-Host "Downloaded file size: $($actualSize / 1MB) MB" + + # Verify file size matches expected size + if ($expectedSize -and $actualSize -ne $expectedSize) { + throw "File size mismatch. Expected: $expectedSize bytes, Got: $actualSize bytes" + } + + # Verify file is not corrupted by checking if it's a valid PE executable + $fileBytes = [System.IO.File]::ReadAllBytes($OutFile) + if ($fileBytes.Length -lt 2 -or $fileBytes[0] -ne 0x4D -or $fileBytes[1] -ne 0x5A) { + throw "Downloaded file is not a valid executable (missing MZ header)" + } + + Write-Host "Download completed and verified successfully" + return $true + } + catch { + Write-Host "Download failed on attempt $attempt`: $($_.Exception.Message)" + + # Clean up partial download if it exists + if (Test-Path $OutFile) { + Remove-Item -Force $OutFile -ErrorAction SilentlyContinue + } + + if ($attempt -eq $MaxRetries) { + Write-Host "Download failed after $MaxRetries attempts" + throw + } + } + + $attempt++ + } + + return $false +} + +function Remove-FileWithRetry { + param ( + [string]$Path + ) + + $attempt = 1 + + while ($attempt -le $MaxRetries) { + try { + if (Test-Path $Path) { + Remove-Item -Force $Path -ErrorAction Stop + Write-Host "Successfully removed $Path" + return $true + } else { + return $true + } + } + catch { + if ($attempt -eq $MaxRetries) { + Write-Host "Warning: Failed to remove $Path after $MaxRetries attempts: $($_.Exception.Message)" + Write-Host "The file may be locked by another process. It will be cleaned up later." + return $false + } + + Write-Host "Attempt $attempt to remove $Path failed, retrying in ${RetryDelay}s..." + Start-Sleep -Seconds $RetryDelay + } + + $attempt++ + } + + return $false +} + function Install-Swift { param ( [string]$Url, @@ -16,27 +133,46 @@ 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' + + Write-Host "Downloading $Url ... " + + try { + Invoke-WebRequestWithRetry -Uri $Url -OutFile installer.exe + Write-Host 'SUCCESS' + } + catch { + Write-Host "FAILED: $($_.Exception.Message)" + Remove-FileWithRetry -Path installer.exe + exit 1 + } + Write-Host -NoNewLine ('Verifying SHA256 ({0}) ... ' -f $Sha256) $Hash = Get-FileHash installer.exe -Algorithm sha256 if ($Hash.Hash -eq $Sha256 -or $Sha256 -eq "") { Write-Host 'SUCCESS' } else { Write-Host ('FAILED ({0})' -f $Hash.Hash) + Remove-FileWithRetry -Path installer.exe exit 1 } Write-Host -NoNewLine 'Installing Swift ... ' - $Process = Start-Process installer.exe -Wait -PassThru -NoNewWindow -ArgumentList @( - '/quiet', - '/norestart' - ) - if ($Process.ExitCode -eq 0) { - Write-Host 'SUCCESS' - } else { - Write-Host ('FAILED ({0})' -f $Process.ExitCode) + try { + $Process = Start-Process installer.exe -Wait -PassThru -NoNewWindow -ArgumentList @( + '/quiet', + '/norestart' + ) + if ($Process.ExitCode -eq 0) { + Write-Host 'SUCCESS' + } else { + Write-Host ('FAILED ({0})' -f $Process.ExitCode) + Remove-FileWithRetry -Path installer.exe + exit 1 + } + } + catch { + Write-Host "FAILED: $($_.Exception.Message)" + Remove-FileWithRetry -Path installer.exe exit 1 } - Remove-Item -Force installer.exe + Remove-FileWithRetry -Path installer.exe } \ No newline at end of file