Skip to content

Commit 202344a

Browse files
committed
Add retry and verification to ARM64 dotnet-install script (#4178)
1 parent 92d4ade commit 202344a

1 file changed

Lines changed: 129 additions & 11 deletions

File tree

eng/pipelines/steps/install-dotnet-arm64.ps1

Lines changed: 129 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44
55
.DESCRIPTION
66
Special handling is required for ARM64 due to a bug in UseDotNet@2:
7-
7+
88
[BUG]: UseDotNet@2 task installs x86 build
99
https://github.com/microsoft/azure-pipelines-tasks/issues/20300
10-
10+
1111
The downloaded dotnet-install.ps1 script is kept in the $InstallDir to avoid
1212
downloading it multiple times during the pipeline job.
13-
13+
1414
The following environment variables are set for subsequent steps in the pipeline:
15-
15+
1616
DOTNET_ROOT: Set to $InstallDir.
1717
PATH: $DOTNET_ROOT is prepended to the PATH environment variable.
1818
@@ -58,6 +58,72 @@ param
5858
# Stop on all errors.
5959
$ErrorActionPreference = 'Stop'
6060

61+
# Maximum number of retry attempts for transient install failures (e.g.
62+
# corrupt downloads, network timeouts).
63+
$maxAttempts = 3
64+
$retryDelaySec = 10
65+
66+
#------------------------------------------------------------------------------
67+
# Invoke dotnet-install.ps1 with retry logic. On each attempt the script is
68+
# called with the supplied $Params. If a non-zero exit code is returned, or
69+
# if the optional $Verify script-block throws, the attempt is considered failed
70+
# and will be retried after a short delay.
71+
72+
function Invoke-DotNetInstall
73+
{
74+
param
75+
(
76+
[Parameter(Mandatory)]
77+
[string]$Description,
78+
79+
[Parameter(Mandatory)]
80+
[hashtable]$Params,
81+
82+
[scriptblock]$Verify = $null
83+
)
84+
85+
for ($attempt = 1; $attempt -le $maxAttempts; $attempt++)
86+
{
87+
try
88+
{
89+
Write-Host "$Description (attempt $attempt of $maxAttempts)"
90+
91+
$global:LASTEXITCODE = 0
92+
& "$InstallDir/dotnet-install.ps1" -Verbose:$Debug -DryRun:$DryRun @Params
93+
$installSucceeded = $?
94+
95+
if (-not $installSucceeded)
96+
{
97+
throw "dotnet-install.ps1 failed."
98+
}
99+
100+
if ($global:LASTEXITCODE -ne 0)
101+
{
102+
throw "dotnet-install.ps1 failed with exit code $global:LASTEXITCODE"
103+
}
104+
105+
if ($Verify)
106+
{
107+
& $Verify
108+
}
109+
110+
return
111+
}
112+
catch
113+
{
114+
Write-Warning "Attempt $attempt failed: $_"
115+
116+
if ($attempt -ge $maxAttempts)
117+
{
118+
throw "$Description failed after $maxAttempts attempts. Last error: $_"
119+
}
120+
121+
Write-Host "Retrying in $retryDelaySec seconds..."
122+
Start-Sleep -Seconds $retryDelaySec
123+
}
124+
}
125+
}
126+
61127
#------------------------------------------------------------------------------
62128
# Emit our command-line arguments.
63129

@@ -80,7 +146,7 @@ if (-not (Test-Path -Path "$InstallDir/dotnet-install.ps1" -PathType Leaf))
80146
}
81147

82148
Write-Host "Downloading dotnet-install.ps1..."
83-
149+
84150
$params =
85151
@{
86152
Uri = "https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.ps1"
@@ -113,8 +179,6 @@ if ($Debug)
113179
#------------------------------------------------------------------------------
114180
# Install the SDK.
115181

116-
Write-Host "Installing .NET SDK version: $sdkVersion"
117-
118182
$installParams =
119183
@{
120184
Architecture = "arm64"
@@ -128,15 +192,40 @@ if ($Debug)
128192
Write-Host ($installParams | ConvertTo-Json -Depth 1)
129193
}
130194

131-
& "$InstallDir/dotnet-install.ps1" -Verbose:$Debug -DryRun:$DryRun @installParams
195+
# Verify the SDK was actually installed. dotnet-install.ps1 can silently fail
196+
# with exit code 0 when the package download is corrupt or the size cannot be
197+
# measured.
198+
$verifySdk =
199+
if (-not $DryRun)
200+
{
201+
{
202+
$dotnetExe = Join-Path $InstallDir "dotnet"
203+
$installedSdks = & $dotnetExe --list-sdks 2>&1
204+
$installedSdksText = [string]::Join("`n", @($installedSdks))
205+
206+
Write-Host "Installed SDKs:`n$installedSdksText"
207+
208+
$sdkPattern = "(?m)^$([regex]::Escape($sdkVersion))\s+\["
209+
210+
if (-not [regex]::IsMatch($installedSdksText, $sdkPattern))
211+
{
212+
throw "SDK $sdkVersion is not present after installation."
213+
}
214+
215+
Write-Host "Verified SDK $sdkVersion is installed."
216+
}
217+
}
218+
219+
Invoke-DotNetInstall `
220+
-Description "Installing .NET SDK version: $sdkVersion" `
221+
-Params $installParams `
222+
-Verify $verifySdk
132223

133224
#------------------------------------------------------------------------------
134225
# Install the Runtimes, if any.
135226

136227
foreach ($channel in $Runtimes)
137228
{
138-
Write-Host "Installing .NET Runtime GA channel: $channel"
139-
140229
$installParams =
141230
@{
142231
Architecture = "arm64"
@@ -152,7 +241,36 @@ foreach ($channel in $Runtimes)
152241
Write-Host ($installParams | ConvertTo-Json -Depth 1)
153242
}
154243

155-
& "$InstallDir/dotnet-install.ps1" -Verbose:$Debug -DryRun:$DryRun @installParams
244+
# Verify the runtime was actually installed. Use the same guard against
245+
# silent corruption that we use for the SDK.
246+
$verifyRuntime =
247+
if (-not $DryRun)
248+
{
249+
# Capture $channel in a local variable so the script-block closure
250+
# binds to the current iteration value.
251+
$ch = $channel
252+
{
253+
$dotnetExe = Join-Path $InstallDir "dotnet"
254+
$installedRuntimes = & $dotnetExe --list-runtimes 2>&1
255+
$installedRuntimesText = [string]::Join("`n", @($installedRuntimes))
256+
257+
Write-Host "Installed runtimes:`n$installedRuntimesText"
258+
259+
$runtimePattern = "Microsoft\.NETCore\.App $([regex]::Escape($ch))\."
260+
261+
if (-not [regex]::IsMatch($installedRuntimesText, $runtimePattern))
262+
{
263+
throw "Runtime $ch is not present after installation."
264+
}
265+
266+
Write-Host "Verified runtime $ch is installed."
267+
}
268+
}
269+
270+
Invoke-DotNetInstall `
271+
-Description "Installing .NET Runtime GA channel: $channel" `
272+
-Params $installParams `
273+
-Verify $verifyRuntime
156274
}
157275

158276
#------------------------------------------------------------------------------

0 commit comments

Comments
 (0)