From 137ca28a86d368a3b1f12d6e84bda6b9f57c6b12 Mon Sep 17 00:00:00 2001 From: Ricardo Colombo Oliveira Date: Fri, 23 Oct 2026 12:57:13 +0200 Subject: [PATCH 1/4] ci: install .NET and NuGet packages next to the workspace on Windows The Windows .NET legs spend about 12 minutes in Setup Environment and Restore, against about 4.5 on Linux. On the Windows images, setup-dotnet installs into C:\Program Files\dotnet, where the SDK installs workloads as MSI packages, and NuGet extracts packages into the user profile on C:. The workload step takes 5.6 minutes there, against about 2 on macOS and Linux, which install more workloads. This installs .NET and the NuGet cache next to the workspace, where workloads install as plain files. On the x64 runners that also moves them to D:, and on the arm64 runners they stay on C:, so the two legs separate the install mode from the drive. Windows also stops restoring the Android test app, which its solution filters exclude. Co-Authored-By: Claude Opus 5.5 --- .github/actions/environment/action.yml | 13 ++++++++----- .github/workflows/build.yml | 11 +++++++++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/.github/actions/environment/action.yml b/.github/actions/environment/action.yml index 3754453a45..a55f337a4f 100644 --- a/.github/actions/environment/action.yml +++ b/.github/actions/environment/action.yml @@ -93,8 +93,11 @@ runs: dotnet workload restore \ --temp-dir "${{ runner.temp }}" \ --skip-sign-check - dotnet workload restore test/AndroidTestApp/AndroidTestApp.csproj \ - --temp-dir "${{ runner.temp }}" \ - --skip-sign-check - # Restore the Android test app explicitly to ensure runtime packs are available - dotnet restore test/AndroidTestApp/AndroidTestApp.csproj -r android-x64 --nologo || true + # The Windows solution filters exclude the Android test app and the tests that build it + if [[ "$RUNNER_OS" != "Windows" ]]; then + dotnet workload restore test/AndroidTestApp/AndroidTestApp.csproj \ + --temp-dir "${{ runner.temp }}" \ + --skip-sign-check + # Restore the Android test app explicitly to ensure runtime packs are available + dotnet restore test/AndroidTestApp/AndroidTestApp.csproj -r android-x64 --nologo || true + fi diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b3b3ad321b..ca9e1afb98 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -143,6 +143,17 @@ jobs: if: ${{ !matrix.container }} uses: ./.github/actions/freediskspace + # The Windows images keep .NET under Program Files, where workloads install as MSI packages, and the NuGet + # cache in the user profile on C:. Next to the workspace, workloads install as plain files, and on the x64 + # runners the workspace is on D:. + - name: Install .NET and NuGet packages next to the workspace + if: runner.os == 'Windows' + shell: pwsh + run: | + $root = Split-Path $env:GITHUB_WORKSPACE + "DOTNET_INSTALL_DIR=$root\.dotnet" >> $env:GITHUB_ENV + "NUGET_PACKAGES=$root\.nuget\packages" >> $env:GITHUB_ENV + - name: Setup Environment uses: ./.github/actions/environment From 32b92d03c94ad8a1c4874fa391a6a82209bbb9c3 Mon Sep 17 00:00:00 2001 From: Ricardo Colombo Oliveira Date: Sat, 24 Oct 2026 22:02:55 +0200 Subject: [PATCH 2/4] ci: put .NET and NuGet on a Dev Drive on win-arm64 The arm64 Windows runners have no D: drive, so .NET and the NuGet packages stayed on C:, and the win-arm64 leg kept a 4 to 5 minute restore. A probe ran the leg's steps with them and TEMP on a Dev Drive, and the job took 33.0 minutes twice, against 40.0 and 44.3 without it. Restore, build and the integration tests each got 2 to 4 minutes faster. Turning off Defender's real-time scanning saved less, because the image already excludes C:\ from it. The step moves into a composite action, because the MSBuild job uses it too. That job's workspace is on D:, but its restore ran in the user profile on C: and took 8.8 minutes. Co-Authored-By: Claude Opus 5.5 --- .../actions/windows-dotnet-drive/action.yml | 31 +++++++++++++++++++ .github/workflows/build.yml | 14 +++------ 2 files changed, 36 insertions(+), 9 deletions(-) create mode 100644 .github/actions/windows-dotnet-drive/action.yml diff --git a/.github/actions/windows-dotnet-drive/action.yml b/.github/actions/windows-dotnet-drive/action.yml new file mode 100644 index 0000000000..3c0f7e61fe --- /dev/null +++ b/.github/actions/windows-dotnet-drive/action.yml @@ -0,0 +1,31 @@ +name: Windows .NET drive +description: Installs .NET and the NuGet packages on a fast drive on Windows runners +runs: + using: composite + + steps: + # The Windows images keep .NET under Program Files, where workloads install as MSI packages, and the NuGet cache + # in the user profile on C:. The x64 runners put the workspace on D:, so both go next to it. The arm64 runners + # only have C:, so both go on a Dev Drive there, with TEMP. Defender already excludes C:\ on these images. + - name: Install .NET and NuGet packages on a fast drive + shell: pwsh + run: | + $root = Split-Path $env:GITHUB_WORKSPACE + if ($root.StartsWith('C:')) + { + 'create vdisk file="C:\devdrive.vhdx" maximum=81920 type=expandable', 'select vdisk file="C:\devdrive.vhdx"', 'attach vdisk', 'convert gpt', 'create partition primary' | + Set-Content "$env:RUNNER_TEMP\diskpart.txt" + diskpart /s "$env:RUNNER_TEMP\diskpart.txt" | Out-Host + if ($LASTEXITCODE -ne 0) { throw "diskpart failed with exit code $LASTEXITCODE" } + $partition = Get-DiskImage -ImagePath 'C:\devdrive.vhdx' | Get-Disk | Get-Partition | Where-Object Type -eq 'Basic' + $partition | Format-Volume -DevDrive -Confirm:$false -Force | Out-Host + $partition | Add-PartitionAccessPath -AccessPath 'X:\' + fsutil devdrv enable /disallowAv + fsutil devdrv trust X: + $root = 'X:' + New-Item -ItemType Directory "$root\tmp" | Out-Null + "TEMP=$root\tmp" >> $env:GITHUB_ENV + "TMP=$root\tmp" >> $env:GITHUB_ENV + } + "DOTNET_INSTALL_DIR=$root\.dotnet" >> $env:GITHUB_ENV + "NUGET_PACKAGES=$root\.nuget\packages" >> $env:GITHUB_ENV diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ca9e1afb98..d38c3ef20a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -143,16 +143,9 @@ jobs: if: ${{ !matrix.container }} uses: ./.github/actions/freediskspace - # The Windows images keep .NET under Program Files, where workloads install as MSI packages, and the NuGet - # cache in the user profile on C:. Next to the workspace, workloads install as plain files, and on the x64 - # runners the workspace is on D:. - - name: Install .NET and NuGet packages next to the workspace + - name: Install .NET and NuGet packages on a fast drive if: runner.os == 'Windows' - shell: pwsh - run: | - $root = Split-Path $env:GITHUB_WORKSPACE - "DOTNET_INSTALL_DIR=$root\.dotnet" >> $env:GITHUB_ENV - "NUGET_PACKAGES=$root\.nuget\packages" >> $env:GITHUB_ENV + uses: ./.github/actions/windows-dotnet-drive - name: Setup Environment uses: ./.github/actions/environment @@ -358,6 +351,9 @@ jobs: with: submodules: recursive + - name: Install .NET and NuGet packages on a fast drive + uses: ./.github/actions/windows-dotnet-drive + - name: Download sentry-native (win-x64) uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: From 0a4f92739b2f67b0e045ec4b82df4e75720181fc Mon Sep 17 00:00:00 2001 From: Ricardo Colombo Oliveira Date: Sat, 24 Oct 2026 23:59:00 +0200 Subject: [PATCH 3/4] ci: keep .NET off the Dev Drive and retry trusting it A second probe ran the win-arm64 leg's steps three ways in one run. With only the NuGet packages and TEMP on the Dev Drive, the job took 33.8 and 32.4 minutes. With .NET there too, it took 36.9, and with nothing there 37.2 and 35.8. Installing every SDK onto the new drive made Setup Environment slower than it saved. In one of those jobs, `fsutil devdrv trust` failed with "Access is denied" because the new volume was still in use, and that failed the job. Trust is now retried, and if it still fails the step only warns, because the Dev Drive is a speedup. The antivirus setting now comes before the drive exists, since it only applies to volumes mounted after it. Co-Authored-By: Claude Opus 5.5 --- .../actions/windows-dotnet-drive/action.yml | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/.github/actions/windows-dotnet-drive/action.yml b/.github/actions/windows-dotnet-drive/action.yml index 3c0f7e61fe..1e0a2a1ed3 100644 --- a/.github/actions/windows-dotnet-drive/action.yml +++ b/.github/actions/windows-dotnet-drive/action.yml @@ -5,14 +5,18 @@ runs: steps: # The Windows images keep .NET under Program Files, where workloads install as MSI packages, and the NuGet cache - # in the user profile on C:. The x64 runners put the workspace on D:, so both go next to it. The arm64 runners - # only have C:, so both go on a Dev Drive there, with TEMP. Defender already excludes C:\ on these images. + # in the user profile on C:. So .NET goes next to the workspace, where workloads install as plain files. The x64 + # runners put the workspace on D:, so the NuGet packages go there too. The arm64 runners only have C:, so the + # NuGet packages and TEMP go on a Dev Drive there. Defender already excludes C:\ on these images. - name: Install .NET and NuGet packages on a fast drive shell: pwsh run: | $root = Split-Path $env:GITHUB_WORKSPACE + "DOTNET_INSTALL_DIR=$root\.dotnet" >> $env:GITHUB_ENV if ($root.StartsWith('C:')) { + # Only volumes mounted after this get no antivirus filters + fsutil devdrv enable /disallowAv | Out-Host 'create vdisk file="C:\devdrive.vhdx" maximum=81920 type=expandable', 'select vdisk file="C:\devdrive.vhdx"', 'attach vdisk', 'convert gpt', 'create partition primary' | Set-Content "$env:RUNNER_TEMP\diskpart.txt" diskpart /s "$env:RUNNER_TEMP\diskpart.txt" | Out-Host @@ -20,12 +24,19 @@ runs: $partition = Get-DiskImage -ImagePath 'C:\devdrive.vhdx' | Get-Disk | Get-Partition | Where-Object Type -eq 'Basic' $partition | Format-Volume -DevDrive -Confirm:$false -Force | Out-Host $partition | Add-PartitionAccessPath -AccessPath 'X:\' - fsutil devdrv enable /disallowAv - fsutil devdrv trust X: + # Right after the mount, trust can fail with "Access is denied" while the volume is still in use + for ($attempt = 1; $attempt -le 5; $attempt++) + { + fsutil devdrv trust X: | Out-Host + if ($LASTEXITCODE -eq 0) { break } + Start-Sleep -Seconds 3 + } + if ($LASTEXITCODE -ne 0) { Write-Warning "Couldn't mark X: as a trusted Dev Drive, so antivirus filters may still scan it" } + fsutil devdrv query X: | Out-Host $root = 'X:' New-Item -ItemType Directory "$root\tmp" | Out-Null "TEMP=$root\tmp" >> $env:GITHUB_ENV "TMP=$root\tmp" >> $env:GITHUB_ENV } - "DOTNET_INSTALL_DIR=$root\.dotnet" >> $env:GITHUB_ENV "NUGET_PACKAGES=$root\.nuget\packages" >> $env:GITHUB_ENV + $global:LASTEXITCODE = 0 From 85191afe4a0c7d393f202c60923e2c71c1f22deb Mon Sep 17 00:00:00 2001 From: Ricardo Colombo Oliveira Date: Sun, 25 Oct 2026 13:21:02 +0100 Subject: [PATCH 4/4] ci: install .NET and NuGet packages on a faster drive in the Windows API approval job verify-api's Windows job builds the same solution filter as the win-x64 leg, and was the last Windows job without the drive step. On one run each, its Restore went from 330 to 59 seconds and the job from 15.4 to 10.0 minutes. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/verify-api.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/verify-api.yml b/.github/workflows/verify-api.yml index 5e4ccf5115..e55fd61180 100644 --- a/.github/workflows/verify-api.yml +++ b/.github/workflows/verify-api.yml @@ -42,6 +42,10 @@ jobs: - name: Remove unused applications uses: ./.github/actions/freediskspace + - name: Install .NET and NuGet packages on a fast drive + if: runner.os == 'Windows' + uses: ./.github/actions/windows-dotnet-drive + - name: Setup Environment uses: ./.github/actions/environment