From 59324e1168ed40b9acdee12b241b138eaf437d13 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 4 Jun 2026 10:06:38 -0500 Subject: [PATCH 01/15] [ci] Make dnceng-public the default PR pipeline; disable PR triggers on DevDiv pipeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reverses the IsFork gating added in #11153 so the dnceng-public pipeline (`dotnet-android`) runs the full PR test matrix for ALL pull requests, not just fork PRs. Adds `pr: none` to the DevDiv pipeline (`Xamarin.Android-PR`) so it stops running for pull requests entirely. Build https://dev.azure.com/dnceng-public/public/_build/results?buildId=1443410 (PR #11531) demonstrates full matrix coverage on dnceng-public for a fork PR (~2h 18m, all green): Mac MSBuild 1-10, Mac MSBuild+Emulator 1-8, Mac APKs 1-2, Windows MSBuild 1-8, Linux MSBuild 1-2. `azure-pipelines.yaml` continues to run for CI on `main` / `d16-*` / `d17-*` / `release/*` / `feature/*` branches and for official signed builds — only PR triggering is removed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../automation/azure-pipelines-public.yaml | 19 ++++++------------- build-tools/automation/azure-pipelines.yaml | 2 ++ 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/build-tools/automation/azure-pipelines-public.yaml b/build-tools/automation/azure-pipelines-public.yaml index f00a09174e2..fab093c6827 100644 --- a/build-tools/automation/azure-pipelines-public.yaml +++ b/build-tools/automation/azure-pipelines-public.yaml @@ -16,10 +16,10 @@ pr: parameters: - name: macTestAgentsUseCleanImages # Test agents we do not need to clean up when finished because they are not reused default: true -- name: skipTests # Skip all test stages (builds only). Use 'auto' to auto-skip for non-fork PRs. +- name: skipTests # Skip all test stages (builds only). Default 'false' so PRs run the full test matrix. displayName: Skip Tests type: string - default: 'auto' + default: 'false' values: - 'auto' - 'true' @@ -51,14 +51,10 @@ variables: value: windows.vs2022.amd64.open - name: LinuxPoolImageNetCorePublic value: build.Ubuntu.2204.amd64.open -# Skip tests for non-fork PRs (they should use DevDiv pipeline) unless explicitly requested +# Skip tests when explicitly requested; otherwise run full PR test matrix. - name: SkipTestStages ${{ if eq(parameters.skipTests, 'true') }}: value: true - ${{ elseif eq(parameters.skipTests, 'false') }}: - value: false - ${{ elseif and(eq(variables['Build.Reason'], 'PullRequest'), ne(variables['System.PullRequest.IsFork'], 'True')) }}: - value: true ${{ else }}: value: false @@ -67,7 +63,6 @@ stages: # macOS Build Stage - stage: mac_build displayName: Mac - condition: eq(variables['System.PullRequest.IsFork'], 'True') jobs: - job: mac_build_create_installers displayName: macOS > Build @@ -104,7 +99,6 @@ stages: - stage: win_build_test displayName: Windows dependsOn: [] - condition: eq(variables['System.PullRequest.IsFork'], 'True') jobs: - job: win_build_test displayName: Windows > Build & Smoke Test @@ -126,7 +120,6 @@ stages: - stage: linux_build displayName: Linux dependsOn: [] - condition: eq(variables['System.PullRequest.IsFork'], 'True') jobs: - job: linux_build_create_sdk_pack displayName: Linux > Build @@ -174,7 +167,7 @@ stages: dependsOn: - mac_build - linux_build - condition: and(succeeded(), eq(variables['System.PullRequest.IsFork'], 'True'), ne(variables['SkipTestStages'], 'true')) + condition: and(succeeded(), ne(variables['SkipTestStages'], 'true')) jobs: - job: linux_tests_smoke_1 displayName: Linux > Tests > MSBuild 1 @@ -270,7 +263,7 @@ stages: - stage: msbuild_dotnet displayName: MSBuild Tests dependsOn: mac_build - condition: and(succeeded(), eq(variables['System.PullRequest.IsFork'], 'True'), ne(variables['SkipTestStages'], 'true')) + condition: and(succeeded(), ne(variables['SkipTestStages'], 'true')) jobs: - job: mac_msbuild_tests strategy: @@ -362,7 +355,7 @@ stages: - stage: msbuilddevice_tests displayName: MSBuild Emulator Tests dependsOn: mac_build - condition: and(succeeded(), eq(variables['System.PullRequest.IsFork'], 'True'), ne(variables['SkipTestStages'], 'true')) + condition: and(succeeded(), ne(variables['SkipTestStages'], 'true')) jobs: - job: mac_dotnetdevice_tests strategy: diff --git a/build-tools/automation/azure-pipelines.yaml b/build-tools/automation/azure-pipelines.yaml index f11fdd1d613..9f9454386e3 100644 --- a/build-tools/automation/azure-pipelines.yaml +++ b/build-tools/automation/azure-pipelines.yaml @@ -9,6 +9,8 @@ trigger: - release/* - feature/* +pr: none + # External sources, scripts, tests, and yaml template files. resources: repositories: From e7c6ed452b947a4127e1729beed28b2ab6295f17 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 4 Jun 2026 14:14:14 -0500 Subject: [PATCH 02/15] [build] Reset workload manifest mtimes after extract The dnceng-public ACES_VM_SharedPool_Tahoe macOS pack agents have a clock/timezone that runs roughly 5 hours ahead of the matching test agents (verified on build 1449920: mac_build ran 16:12-16:27 UTC but wrote zip entry mtimes of 21:25:30). When tests extract the manifest nupkg, `WorkloadManifest.targets` lands on disk with a mtime ~5h in the future relative to the test agent's clock. WorkloadManifest.targets is included in \ and therefore in every Android incremental target's input list. A future mtime causes MSBuild to re-run every incremental target on the second build, which breaks tests that assert targets like `_CompileJava`, `_GenerateJavaStubs`, `_UpdateAndroidResgen`, `_ManifestMerger`, `_Sign`, etc. should be skipped. This bug was masked previously because non-fork PR validation ran on the DevDiv `Xamarin.Android-PR` pipeline (different macOS pool), and the dnceng-public pipeline was gated to fork PRs only where those assertions are skipped via `AssertCommercialBuild()`. Add a `` in `ExtractWorkloadPacks` to reset the extracted manifest files' mtimes to the local agent's `now`. The dnceng infra clock-drift issue should be filed separately. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- build-tools/create-packs/Directory.Build.targets | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/build-tools/create-packs/Directory.Build.targets b/build-tools/create-packs/Directory.Build.targets index 67acf8b5aa9..401ef5d3ae2 100644 --- a/build-tools/create-packs/Directory.Build.targets +++ b/build-tools/create-packs/Directory.Build.targets @@ -101,6 +101,21 @@ + + + <_WLExtractedDestFiles Include="$(_SdkManifestsFolder)microsoft.net.sdk.android\$(AndroidPackVersionLong)\WorkloadManifest.targets" /> + <_WLExtractedDestFiles Include="$(_SdkManifestsFolder)microsoft.net.sdk.android\$(AndroidPackVersionLong)\WorkloadManifest.json" /> + <_WLExtractedDestFiles Include="$(_SdkManifestsFolder)microsoft.net.sdk.android\$(AndroidPackVersionLong)\WorkloadDependencies.json" /> + + Date: Thu, 4 Jun 2026 14:15:34 -0500 Subject: [PATCH 03/15] [build] Simplify Touch to wildcard all extracted files Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- build-tools/create-packs/Directory.Build.targets | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/build-tools/create-packs/Directory.Build.targets b/build-tools/create-packs/Directory.Build.targets index 401ef5d3ae2..6608c4181e3 100644 --- a/build-tools/create-packs/Directory.Build.targets +++ b/build-tools/create-packs/Directory.Build.targets @@ -111,9 +111,7 @@ subsequent builds, breaking "target should be skipped" assertions. --> - <_WLExtractedDestFiles Include="$(_SdkManifestsFolder)microsoft.net.sdk.android\$(AndroidPackVersionLong)\WorkloadManifest.targets" /> - <_WLExtractedDestFiles Include="$(_SdkManifestsFolder)microsoft.net.sdk.android\$(AndroidPackVersionLong)\WorkloadManifest.json" /> - <_WLExtractedDestFiles Include="$(_SdkManifestsFolder)microsoft.net.sdk.android\$(AndroidPackVersionLong)\WorkloadDependencies.json" /> + <_WLExtractedDestFiles Include="$(_SdkManifestsFolder)microsoft.net.sdk.android\$(AndroidPackVersionLong)\*" /> From f5b124349354393b19add3d84ea1d31095465f99 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Mon, 8 Jun 2026 08:51:35 -0500 Subject: [PATCH 04/15] [ci] Port MAUI Tests stage; match emulator slice count Comparing dnceng-public build 1450217 with DevDiv build 14308486 surfaced two gaps in the public pipeline: 1. The DevDiv pipeline runs a MAUI Tests stage that builds the MAUI workload against the android pack from this PR and then builds a `dotnet new maui` template in Debug and Release. dnceng-public had no equivalent, so a PR that broke MAUI consumption would slip through PR validation. 2. The MSBuild Emulator Tests stage on dnceng-public ran with `parallel: 8` while DevDiv used the template default of 12, so each slice ran ~50% more tests. Port the MAUI stage into `azure-pipelines-public.yaml`, swapping the internal `MAUI-1ESPT` pool and `xamarin` GitHub endpoint for the public `NetCore-Public` pool and existing `public` endpoint. Call `setup-test-environment-steps.yaml` directly so we can perform both the `self` and `maui` checkouts explicitly into `s/android` and `s/maui`. Bump `mac_dotnetdevice_tests` `parallel` from 8 to 12 to match DevDiv. The remaining gap vs DevDiv -- PoliCheck, SDLSources, BuildSummary -- is by design: those stages require Microsoft-internal SDL tooling and will continue to run on the DevDiv pipeline for `main` and release branch CI. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../automation/azure-pipelines-public.yaml | 145 +++++++++++++++++- 1 file changed, 144 insertions(+), 1 deletion(-) diff --git a/build-tools/automation/azure-pipelines-public.yaml b/build-tools/automation/azure-pipelines-public.yaml index fab093c6827..c0149ff6962 100644 --- a/build-tools/automation/azure-pipelines-public.yaml +++ b/build-tools/automation/azure-pipelines-public.yaml @@ -33,6 +33,11 @@ resources: name: dotnet/android-tools endpoint: public ref: refs/heads/main + - repository: maui + type: github + name: dotnet/maui + endpoint: public + ref: net11.0 # Global variables variables: @@ -359,7 +364,7 @@ stages: jobs: - job: mac_dotnetdevice_tests strategy: - parallel: 8 + parallel: 12 displayName: "macOS > Tests > MSBuild+Emulator" # NOTE: AcesShared pool cannot boot Android emulators, so these jobs must use hosted images pool: @@ -408,3 +413,141 @@ stages: - template: /build-tools/automation/yaml-templates/fail-on-issue.yaml parameters: condition: true + +# MAUI Tests Stage +- stage: maui_tests + displayName: MAUI Tests + dependsOn: mac_build + condition: and(succeeded(), ne(variables['SkipTestStages'], 'true')) + jobs: + - job: maui_tests_integration + displayName: MAUI Integration + pool: + name: $(NetCorePublicPoolName) + demands: + - ImageOverride -equals $(WindowsPoolImageNetCorePublic) + timeoutInMinutes: 180 + workspace: + clean: all + variables: + BuildVersion: $(Build.BuildId) + # Ignore MAUI's public API analyzer errors (RS0016) - dotnet/android does not care about those + PublicApiType: Generate + steps: + - checkout: self + path: s/android + persistCredentials: false + + - checkout: maui + clean: true + submodules: recursive + path: s/maui + persistCredentials: true + + - template: /build-tools/automation/yaml-templates/setup-test-environment-steps.yaml + parameters: + xaSourcePath: $(Build.SourcesDirectory)/android + androidSdkPlatforms: $(DefaultTestSdkPlatforms) + dotnetVersion: $(DotNetSdkVersion) + dotnetQuality: $(DotNetSdkQuality) + use1ESTemplate: false + + - task: DownloadPipelineArtifact@2 + inputs: + artifactName: $(NuGetArtifactName) + downloadPath: $(Build.StagingDirectory)/android-packs + + - pwsh: | + $searchPath = Join-Path $(Build.StagingDirectory) android-packs + $wlmanPack = Get-ChildItem $searchPath -Filter *Android*Manifest*.nupkg | Select-Object -First 1 + $dest = Join-Path $searchPath "tmp-wlman" "$($wlmanPack.BaseName)" + Expand-Archive -LiteralPath $wlmanPack -DestinationPath $dest + $wlmanJsonPath = Join-Path $dest "data" "WorkloadManifest.json" + $json = Get-Content $wlmanJsonPath | ConvertFrom-Json -AsHashtable + Write-Host "Setting variable ANDROID_PACK_VERSION = $($json["version"])" + Write-Host "##vso[task.setvariable variable=ANDROID_PACK_VERSION;]$($json["version"])" + displayName: Set ANDROID_PACK_VERSION + + - pwsh: >- + $(Build.SourcesDirectory)/maui/eng/scripts/update-version-props.ps1 + -xmlFileName "$(Build.SourcesDirectory)/maui/eng/Versions.props" + -androidVersion $(ANDROID_PACK_VERSION) + displayName: Update MAUI's Android dependency + + - task: DotNetCoreCLI@2 + displayName: Update Android SDK band in Workloads.csproj + inputs: + projects: $(Build.SourcesDirectory)/android/Xamarin.Android.sln + arguments: -t:UpdateMauiWorkloadsProj -c $(XA.Build.Configuration) --no-restore -v:n -bl:$(Build.StagingDirectory)/logs/update-maui-workloadsproj.binlog + + - pwsh: ./build.ps1 --target=dotnet --configuration="$(XA.Build.Configuration)" --nugetsource="$(Build.StagingDirectory)\android-packs" --verbosity=diagnostic + displayName: Install .NET + retryCountOnTaskFailure: 3 + workingDirectory: $(Build.SourcesDirectory)/maui + + - pwsh: ./build.ps1 --target=dotnet-pack --configuration="$(XA.Build.Configuration)" --nugetsource="$(Build.StagingDirectory)\android-packs" --verbosity=diagnostic + displayName: Pack .NET Maui + workingDirectory: $(Build.SourcesDirectory)/maui + + - task: DotNetCoreCLI@2 + displayName: Install MAUI workload packs + retryCountOnTaskFailure: 3 + inputs: + projects: $(Build.SourcesDirectory)/android/Xamarin.Android.sln + arguments: -t:InstallMaui -p:MauiUseLocalPacks=true -p:MauiWorkloadToInstall=maui -p:MauiManifestDiagnosticsPath=$(Build.StagingDirectory)/logs/maui-manifest -c $(XA.Build.Configuration) --no-restore -v:n -bl:$(Build.StagingDirectory)/logs/install-maui.binlog + + - template: /build-tools/automation/yaml-templates/run-dotnet-preview.yaml + parameters: + command: new + arguments: maui -o $(Build.StagingDirectory)/MauiTestProj + xaSourcePath: $(Build.SourcesDirectory)/android + displayName: Create MAUI template + continueOnError: false + + - powershell: | + $project = '$(Build.StagingDirectory)/MauiTestProj/MauiTestProj.csproj' + [xml] $xml = Get-Content $project + $node = $xml.SelectSingleNode('/Project/PropertyGroup/TargetFrameworks') + $node.InnerText = '$(DotNetTargetFramework)-android' + $xml.Save($project) + displayName: set TargetFrameworks to Android-only + + - template: /build-tools/automation/yaml-templates/run-dotnet-preview.yaml + parameters: + project: $(Build.StagingDirectory)/MauiTestProj/MauiTestProj.csproj + arguments: >- + -f $(DotNetTargetFramework)-android -c Debug + --configfile $(Build.SourcesDirectory)/maui/NuGet.config + -bl:$(Build.StagingDirectory)/logs/MauiTestProj-Debug.binlog + xaSourcePath: $(Build.SourcesDirectory)/android + displayName: Build MAUI template - Debug + + - template: /build-tools/automation/yaml-templates/run-dotnet-preview.yaml + parameters: + project: $(Build.StagingDirectory)/MauiTestProj/MauiTestProj.csproj + arguments: >- + -f $(DotNetTargetFramework)-android -c Release + --configfile $(Build.SourcesDirectory)/maui/NuGet.config + -bl:$(Build.StagingDirectory)/logs/MauiTestProj-Release.binlog + xaSourcePath: $(Build.SourcesDirectory)/android + displayName: Build MAUI template - Release + + - task: CopyFiles@2 + displayName: copy build logs + condition: always() + inputs: + Contents: | + $(Build.SourcesDirectory)/android/bin/*$(XA.Build.Configuration)/*.*log + $(Build.SourcesDirectory)/maui/artifacts/logs/** + TargetFolder: $(Build.StagingDirectory)/logs + flattenFolders: true + + - template: /build-tools/automation/yaml-templates/publish-artifact.yaml + parameters: + displayName: upload build and test results + artifactName: Test Results - MAUI Integration + targetPath: $(Build.StagingDirectory)/logs + condition: or(ne(variables['Agent.JobStatus'], 'Succeeded'), eq(variables['XA.PublishAllLogs'], 'true')) + use1ESTemplate: false + + - template: /build-tools/automation/yaml-templates/fail-on-issue.yaml From fafa3a5795281049d78422e63cfae4211c8865e1 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Mon, 8 Jun 2026 11:34:58 -0500 Subject: [PATCH 05/15] [ci] MAUI job: useAgentJdkPath=false on public Windows image The dnceng-public `windows.vs2022.amd64.open` image does not preinstall `JAVA_HOME_21_X64`, so the JDK lookup in `setup-jdk-variables.yaml` fails before xa-prepare ever runs. The existing Windows MSBuild test job in this file already passes `useAgentJdkPath: false` for the same reason; do the same in the new MAUI Integration job so JAVA_HOME points at the xa-prepare-managed JDK under `%USERPROFILE%\android-toolchain`. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- build-tools/automation/azure-pipelines-public.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/build-tools/automation/azure-pipelines-public.yaml b/build-tools/automation/azure-pipelines-public.yaml index c0149ff6962..16d4a2c7ece 100644 --- a/build-tools/automation/azure-pipelines-public.yaml +++ b/build-tools/automation/azure-pipelines-public.yaml @@ -450,6 +450,7 @@ stages: androidSdkPlatforms: $(DefaultTestSdkPlatforms) dotnetVersion: $(DotNetSdkVersion) dotnetQuality: $(DotNetSdkQuality) + useAgentJdkPath: false use1ESTemplate: false - task: DownloadPipelineArtifact@2 From f2ee5c9bfea2cd7f632c578ecbb221bf8d6c6125 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Mon, 8 Jun 2026 12:42:33 -0500 Subject: [PATCH 06/15] [ci] MAUI job: checkout self with submodules The MAUI Integration job uses `setup-test-environment-steps.yaml` directly (not the `-public` wrapper) so it can stage two repos in `s/android` and `s/maui`. The wrapper template performs its own `checkout: self` with `submodules: recursive`, but skipping it means the explicit `checkout: self` in this job needs the same. Without it `external/xamarin-android-tools` is missing and `BootstrapTasks.csproj` fails to import `MSBuildReferences.projitems` from that submodule. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- build-tools/automation/azure-pipelines-public.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build-tools/automation/azure-pipelines-public.yaml b/build-tools/automation/azure-pipelines-public.yaml index 16d4a2c7ece..cdc348442b9 100644 --- a/build-tools/automation/azure-pipelines-public.yaml +++ b/build-tools/automation/azure-pipelines-public.yaml @@ -435,6 +435,8 @@ stages: PublicApiType: Generate steps: - checkout: self + clean: true + submodules: recursive path: s/android persistCredentials: false From 92bb6fb769f27f18695377df14d4974c50d612e5 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Mon, 8 Jun 2026 13:47:28 -0500 Subject: [PATCH 07/15] [ci] MAUI job: point ANDROID_HOME at xa-prepare SDK xa-prepare installs the API levels listed in \ (35, 36, 36.1, 37.0) under %USERPROFILE%\android-toolchain\sdk and threads that path through XA's MSBuild via the AndroidSdkDirectory property. dotnet/maui's build.ps1 is a separate process tree though; it discovers the SDK via ANDROID_HOME / ANDROID_SDK_ROOT, which on the dnceng-public windows.vs2022.amd64.open image still points at the preinstalled C:\Program Files (x86)\Android\android-sdk -- a location that doesn't have API 37.0. Set ANDROID_HOME and ANDROID_SDK_ROOT to the xa-prepare path between Install .NET and Pack .NET Maui so the subsequent build/pack/template steps pick up the right SDK. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../automation/azure-pipelines-public.yaml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/build-tools/automation/azure-pipelines-public.yaml b/build-tools/automation/azure-pipelines-public.yaml index cdc348442b9..0c972ef1c0f 100644 --- a/build-tools/automation/azure-pipelines-public.yaml +++ b/build-tools/automation/azure-pipelines-public.yaml @@ -488,6 +488,21 @@ stages: retryCountOnTaskFailure: 3 workingDirectory: $(Build.SourcesDirectory)/maui + - pwsh: | + $sdk = "$env:USERPROFILE\android-toolchain\sdk" + if (-not (Test-Path $sdk)) { + Write-Error "Expected xa-prepare-managed Android SDK at '$sdk' but it does not exist" + } + Write-Host "Setting ANDROID_HOME / ANDROID_SDK_ROOT to '$sdk'" + Write-Host "##vso[task.setvariable variable=ANDROID_HOME]$sdk" + Write-Host "##vso[task.setvariable variable=ANDROID_SDK_ROOT]$sdk" + displayName: Point ANDROID_HOME at xa-prepare SDK + # The dnceng-public Windows image's preinstalled Android SDK at + # C:\Program Files (x86)\Android\android-sdk lacks the API levels + # listed in $(DefaultTestSdkPlatforms). xa-prepare's androidsdk + # step installs them under %USERPROFILE%\android-toolchain\sdk, so + # redirect the MAUI build there. + - pwsh: ./build.ps1 --target=dotnet-pack --configuration="$(XA.Build.Configuration)" --nugetsource="$(Build.StagingDirectory)\android-packs" --verbosity=diagnostic displayName: Pack .NET Maui workingDirectory: $(Build.SourcesDirectory)/maui From e1c2491b17971abab6e6247da75c5ebdf66488f3 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Mon, 8 Jun 2026 14:35:58 -0500 Subject: [PATCH 08/15] [ci] MAUI job: install Node.js for TypeScript build MAUI's BlazorWebView build invokes Microsoft.TypeScript.MSBuild, which shells out to node.exe. The dnceng-public windows.vs2022.amd64.open image does not preinstall Node (DevDiv's MAUI-1ESPT image does). Add a NodeTool@0 step before the MAUI pack so Pack .NET Maui can build the TypeScript projects. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- build-tools/automation/azure-pipelines-public.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/build-tools/automation/azure-pipelines-public.yaml b/build-tools/automation/azure-pipelines-public.yaml index 0c972ef1c0f..47bc2469cab 100644 --- a/build-tools/automation/azure-pipelines-public.yaml +++ b/build-tools/automation/azure-pipelines-public.yaml @@ -488,6 +488,14 @@ stages: retryCountOnTaskFailure: 3 workingDirectory: $(Build.SourcesDirectory)/maui + - task: NodeTool@0 + displayName: Install Node.js + # MAUI's TypeScript build steps for the BlazorWebView project shell out + # to node.exe; the dnceng-public windows.vs2022.amd64.open image does + # not preinstall Node, so install it explicitly. + inputs: + versionSpec: '20.x' + - pwsh: | $sdk = "$env:USERPROFILE\android-toolchain\sdk" if (-not (Test-Path $sdk)) { From 063c8bfa4ec69cd15b938d2bf6ce980df7a1bb3a Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Mon, 8 Jun 2026 16:24:15 -0500 Subject: [PATCH 09/15] [ci] MAUI job: match dotnet/maui's 1es-windows-2022-open image Align the MAUI Integration job's image with what dotnet/maui uses for its own dnceng-public PR validation (eng/pipelines/ci.yml -> WindowsPool.public). Both pipelines now run on: pool name: NetCore-Public ImageOverride: 1es-windows-2022-open os: windows This is the image MAUI's pack/build stages already use successfully and matches what the upstream MAUI Android build expects. (The recent Pack .NET Maui failure was a transient HTTP 500 from the dotnet-public-maven Azure Artifacts feed -- 'exceeding usage of resource Concurrency in namespace IPAddress' -- not an image problem. Matching MAUI's image is a quality-of-life alignment, not a fix for that rate limit.) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- build-tools/automation/azure-pipelines-public.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build-tools/automation/azure-pipelines-public.yaml b/build-tools/automation/azure-pipelines-public.yaml index 47bc2469cab..7caaace7a41 100644 --- a/build-tools/automation/azure-pipelines-public.yaml +++ b/build-tools/automation/azure-pipelines-public.yaml @@ -425,7 +425,8 @@ stages: pool: name: $(NetCorePublicPoolName) demands: - - ImageOverride -equals $(WindowsPoolImageNetCorePublic) + - ImageOverride -equals 1es-windows-2022-open + os: windows timeoutInMinutes: 180 workspace: clean: all From f3082154417ced16f1ae7f5b26f10bba95e7d678 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Mon, 8 Jun 2026 16:48:38 -0500 Subject: [PATCH 10/15] [ci] MAUI job: restore Gradle dependency cache between runs The Pack .NET Maui step in build 1454897 failed because Gradle's resolution of com.android.tools.build:gradle-settings-api:8.11.1 (and sdklib/ddmlib) hit an Azure DevOps Artifacts rate limit on the dotnet-public-maven feed: Received status code 500 from server: Internal Server Error - Request was blocked due to exceeding usage of resource 'Concurrency' in namespace 'IPAddress'. This is the same class of transient failure that motivated commit 0a681023 (dotnet/android PR #10986), which added the shared cache-gradle.yaml template that mac/linux/windows build legs already use. dotnet/maui's eng/pipelines/common/cache-gradle.yml is in fact adapted from that commit. The MAUI Integration job uses setup-test-environment-steps.yaml directly and so was not pulling in cache-gradle.yaml. Add it immediately after the checkouts so subsequent runs restore ~/.gradle/caches and avoid re-resolving Android Gradle Plugin artifacts from the rate-limited mirror. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- build-tools/automation/azure-pipelines-public.yaml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/build-tools/automation/azure-pipelines-public.yaml b/build-tools/automation/azure-pipelines-public.yaml index 7caaace7a41..9216c49a173 100644 --- a/build-tools/automation/azure-pipelines-public.yaml +++ b/build-tools/automation/azure-pipelines-public.yaml @@ -447,6 +447,18 @@ stages: path: s/maui persistCredentials: true + # Restore ~/.gradle/caches between runs so MAUI's Gradle resolution + # for android-gradle-plugin/sdklib/ddmlib is not gated on the + # dotnet-public-maven Maven feed every build. The dnceng-public Maven + # feed periodically rate-limits ('exceeding usage of resource Concurrency + # in namespace IPAddress', HTTP 500), which deterministically fails + # MAUI's ./build.ps1 --target=dotnet-pack. + # Adapted from upstream dotnet/maui, which adopted the pattern from us: + # https://github.com/dotnet/maui/blob/net11.0/eng/pipelines/common/cache-gradle.yml + - template: /build-tools/automation/yaml-templates/cache-gradle.yaml + parameters: + xaSourcePath: $(Build.SourcesDirectory)/android + - template: /build-tools/automation/yaml-templates/setup-test-environment-steps.yaml parameters: xaSourcePath: $(Build.SourcesDirectory)/android From 2e16aef23f78cdb6114415d6ab04728846309983 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Mon, 8 Jun 2026 20:30:17 -0500 Subject: [PATCH 11/15] [ci] MAUI job: resolve ANDROID_HOME via Configuration.props rules The previous Point ANDROID_HOME step hard-coded the SDK location to %USERPROFILE%\android-toolchain\sdk. That assumption only holds when ANDROID_SDK_ROOT is unset; otherwise androidsdk.csproj follows the Configuration.props logic: \ \\sdk The 1es-windows-2022-open image presets ANDROID_SDK_ROOT to the agent-preinstalled SDK, so xa-prepare's 'install OpenJDK and accept Android SDK licenses' step extends that location with the requested API levels (build 1455110, log 854, took 8m 16s and downloaded android-archives there). %USERPROFILE%\android-toolchain\sdk is never created, and the prior step erred out. Match the Configuration.props resolution order in the pwsh step: 1. \ 2. \ 3. \\android-toolchain\sdk 4. \C:\Users\jopepper\android-toolchain\sdk Pick the first one that has a 'platforms' subdir and use it for both ANDROID_HOME and ANDROID_SDK_ROOT so MAUI sees API 37. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../automation/azure-pipelines-public.yaml | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/build-tools/automation/azure-pipelines-public.yaml b/build-tools/automation/azure-pipelines-public.yaml index 9216c49a173..f62f8c6ae8b 100644 --- a/build-tools/automation/azure-pipelines-public.yaml +++ b/build-tools/automation/azure-pipelines-public.yaml @@ -510,19 +510,30 @@ stages: versionSpec: '20.x' - pwsh: | - $sdk = "$env:USERPROFILE\android-toolchain\sdk" - if (-not (Test-Path $sdk)) { - Write-Error "Expected xa-prepare-managed Android SDK at '$sdk' but it does not exist" + # Match Configuration.props: prefer ANDROID_SDK_ROOT if set+exists, + # else $(HOME)\android-toolchain\sdk, else $env:USERPROFILE fallback. + $candidates = @() + if ($env:ANDROID_SDK_ROOT) { $candidates += $env:ANDROID_SDK_ROOT } + if ($env:ANDROID_HOME) { $candidates += $env:ANDROID_HOME } + if ($env:HOME) { $candidates += "$env:HOME\android-toolchain\sdk" } + if ($env:USERPROFILE) { $candidates += "$env:USERPROFILE\android-toolchain\sdk" } + $sdk = $candidates | Where-Object { $_ -and (Test-Path (Join-Path $_ 'platforms')) } | Select-Object -First 1 + if (-not $sdk) { + Write-Error "Could not find an Android SDK install with a 'platforms' subdir. Tried:`n$($candidates -join "`n")" + exit 1 } Write-Host "Setting ANDROID_HOME / ANDROID_SDK_ROOT to '$sdk'" + Get-ChildItem (Join-Path $sdk 'platforms') | ForEach-Object { Write-Host " platforms\$($_.Name)" } Write-Host "##vso[task.setvariable variable=ANDROID_HOME]$sdk" Write-Host "##vso[task.setvariable variable=ANDROID_SDK_ROOT]$sdk" displayName: Point ANDROID_HOME at xa-prepare SDK - # The dnceng-public Windows image's preinstalled Android SDK at - # C:\Program Files (x86)\Android\android-sdk lacks the API levels - # listed in $(DefaultTestSdkPlatforms). xa-prepare's androidsdk - # step installs them under %USERPROFILE%\android-toolchain\sdk, so - # redirect the MAUI build there. + # androidsdk.csproj's install path follows Configuration.props rules: + # ANDROID_SDK_ROOT if it exists, otherwise %HOME%\android-toolchain\sdk. + # On the 1es-windows-2022-open image, ANDROID_SDK_ROOT is preset to + # the agent's preinstalled SDK (e.g. C:\Program Files (x86)\Android\android-sdk), + # so xa-prepare ends up extending that location with the requested + # API levels. Whichever path it picked, MAUI's build needs the same + # ANDROID_HOME / ANDROID_SDK_ROOT so it sees API 37, etc. - pwsh: ./build.ps1 --target=dotnet-pack --configuration="$(XA.Build.Configuration)" --nugetsource="$(Build.StagingDirectory)\android-packs" --verbosity=diagnostic displayName: Pack .NET Maui From f2eb3ba61d49191ad8971cabe2b1c220ece5e244 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Mon, 8 Jun 2026 21:30:04 -0500 Subject: [PATCH 12/15] [ci] Retrigger: transient HTTP 500 rate-limit on NuGet feed in 1455280 Mac and Linux both failed during the first xaprepare.csproj NuGet restore with Concurrency in namespace 'IPAddress' rate-limiting from pkgs.dev.azure.com/dnceng/public. Same pipeline succeeded in build 1450217. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> From 83a9d08540705fb1b264ee1fb7c498fdb16ed89c Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Mon, 8 Jun 2026 22:11:27 -0500 Subject: [PATCH 13/15] [ci] Retrigger: transient HTTP 500 rate-limit on Windows in 1455352 Windows Prepare Solution failed restoring darc-pub-dotnet-runtime-2b2a06c8 with NU1301 / HTTP 500 'Concurrency in namespace IPAddress'. Linux passed, Mac still running. Pure infra flake. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> From 86f90bb2addcbceffabd9a91db97f3b07d12df0e Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 9 Jun 2026 00:39:49 -0500 Subject: [PATCH 14/15] [ci] Retry make jenkins / Prepare Solution on transient NuGet HTTP 500 Builds 1455280, 1455352, 1455377 all failed during the first NuGet restore in xaprepare/Prepare Solution with NU1301 / 'Concurrency in namespace IPAddress' rate-limiting from the dnceng-public Azure Artifacts feed (HTTP 500). The errors are transient and clear within seconds, but with no retry the entire stage fails. Add retryCountOnTaskFailure: 3 to: - build-linux-steps.yaml: make jenkins - build-macos-steps.yaml: make jenkins - build-windows-steps.yaml: Prepare Solution Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- build-tools/automation/yaml-templates/build-linux-steps.yaml | 1 + build-tools/automation/yaml-templates/build-macos-steps.yaml | 1 + build-tools/automation/yaml-templates/build-windows-steps.yaml | 1 + 3 files changed, 3 insertions(+) diff --git a/build-tools/automation/yaml-templates/build-linux-steps.yaml b/build-tools/automation/yaml-templates/build-linux-steps.yaml index 448999622c0..02c3241d4fa 100644 --- a/build-tools/automation/yaml-templates/build-linux-steps.yaml +++ b/build-tools/automation/yaml-templates/build-linux-steps.yaml @@ -34,6 +34,7 @@ steps: - script: make jenkins PREPARE_CI=1 PREPARE_AUTOPROVISION=1 CONFIGURATION=$(XA.Build.Configuration) MSBUILD_ARGS='${{ parameters.makeMSBuildArgs }}' workingDirectory: ${{ parameters.xaSourcePath }} displayName: make jenkins + retryCountOnTaskFailure: 3 - script: make create-nupkgs CONFIGURATION=$(XA.Build.Configuration) workingDirectory: ${{ parameters.xaSourcePath }} diff --git a/build-tools/automation/yaml-templates/build-macos-steps.yaml b/build-tools/automation/yaml-templates/build-macos-steps.yaml index a578f34addc..ba776cfa7b4 100644 --- a/build-tools/automation/yaml-templates/build-macos-steps.yaml +++ b/build-tools/automation/yaml-templates/build-macos-steps.yaml @@ -44,6 +44,7 @@ steps: - script: make jenkins CONFIGURATION=$(XA.Build.Configuration) PREPARE_CI=1 PREPARE_AUTOPROVISION=1 MSBUILD_ARGS='${{ parameters.makeMSBuildArgs }}' workingDirectory: ${{ parameters.xaSourcePath }} displayName: make jenkins + retryCountOnTaskFailure: 3 - script: make create-installers CONFIGURATION=$(XA.Build.Configuration) MSBUILD_ARGS='${{ parameters.makeMSBuildArgs }}' workingDirectory: ${{ parameters.xaSourcePath }} diff --git a/build-tools/automation/yaml-templates/build-windows-steps.yaml b/build-tools/automation/yaml-templates/build-windows-steps.yaml index c8651b02d76..2a76c2f9c40 100644 --- a/build-tools/automation/yaml-templates/build-windows-steps.yaml +++ b/build-tools/automation/yaml-templates/build-windows-steps.yaml @@ -29,6 +29,7 @@ steps: - task: DotNetCoreCLI@2 displayName: Prepare Solution + retryCountOnTaskFailure: 3 inputs: projects: Xamarin.Android.sln arguments: '-c $(XA.Build.Configuration) -t:Prepare --no-restore -p:AutoProvision=true -bl:$(System.DefaultWorkingDirectory)\bin\Build$(XA.Build.Configuration)\dotnet-build-prepare.binlog' From f5df5d10a7a1affe7394a96e8cdeb84426e3796f Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 9 Jun 2026 08:56:32 -0500 Subject: [PATCH 15/15] [ci] Reduce build retry count to 1; drop unused 'auto' skipTests value Per PR review feedback: - Lower retryCountOnTaskFailure from 3 to 1 on 'make jenkins' (linux/macos) and 'Prepare Solution' (windows). One retry is enough for the transient HTTP 500 NuGet rate-limit; three would burn ~30 min of CI time on a genuine build error before surfacing it. - Drop 'auto' from the skipTests parameter values list. The SkipTestStages variable expression no longer has a distinct 'auto' branch (it was the IsFork-aware path that's now gone), so 'auto' silently behaved the same as 'false'. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- build-tools/automation/azure-pipelines-public.yaml | 1 - build-tools/automation/yaml-templates/build-linux-steps.yaml | 2 +- build-tools/automation/yaml-templates/build-macos-steps.yaml | 2 +- build-tools/automation/yaml-templates/build-windows-steps.yaml | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/build-tools/automation/azure-pipelines-public.yaml b/build-tools/automation/azure-pipelines-public.yaml index f62f8c6ae8b..a2f83418def 100644 --- a/build-tools/automation/azure-pipelines-public.yaml +++ b/build-tools/automation/azure-pipelines-public.yaml @@ -21,7 +21,6 @@ parameters: type: string default: 'false' values: - - 'auto' - 'true' - 'false' diff --git a/build-tools/automation/yaml-templates/build-linux-steps.yaml b/build-tools/automation/yaml-templates/build-linux-steps.yaml index 02c3241d4fa..ce12eb40d86 100644 --- a/build-tools/automation/yaml-templates/build-linux-steps.yaml +++ b/build-tools/automation/yaml-templates/build-linux-steps.yaml @@ -34,7 +34,7 @@ steps: - script: make jenkins PREPARE_CI=1 PREPARE_AUTOPROVISION=1 CONFIGURATION=$(XA.Build.Configuration) MSBUILD_ARGS='${{ parameters.makeMSBuildArgs }}' workingDirectory: ${{ parameters.xaSourcePath }} displayName: make jenkins - retryCountOnTaskFailure: 3 + retryCountOnTaskFailure: 1 - script: make create-nupkgs CONFIGURATION=$(XA.Build.Configuration) workingDirectory: ${{ parameters.xaSourcePath }} diff --git a/build-tools/automation/yaml-templates/build-macos-steps.yaml b/build-tools/automation/yaml-templates/build-macos-steps.yaml index ba776cfa7b4..e3c7280dd09 100644 --- a/build-tools/automation/yaml-templates/build-macos-steps.yaml +++ b/build-tools/automation/yaml-templates/build-macos-steps.yaml @@ -44,7 +44,7 @@ steps: - script: make jenkins CONFIGURATION=$(XA.Build.Configuration) PREPARE_CI=1 PREPARE_AUTOPROVISION=1 MSBUILD_ARGS='${{ parameters.makeMSBuildArgs }}' workingDirectory: ${{ parameters.xaSourcePath }} displayName: make jenkins - retryCountOnTaskFailure: 3 + retryCountOnTaskFailure: 1 - script: make create-installers CONFIGURATION=$(XA.Build.Configuration) MSBUILD_ARGS='${{ parameters.makeMSBuildArgs }}' workingDirectory: ${{ parameters.xaSourcePath }} diff --git a/build-tools/automation/yaml-templates/build-windows-steps.yaml b/build-tools/automation/yaml-templates/build-windows-steps.yaml index 2a76c2f9c40..0866bdfa98d 100644 --- a/build-tools/automation/yaml-templates/build-windows-steps.yaml +++ b/build-tools/automation/yaml-templates/build-windows-steps.yaml @@ -29,7 +29,7 @@ steps: - task: DotNetCoreCLI@2 displayName: Prepare Solution - retryCountOnTaskFailure: 3 + retryCountOnTaskFailure: 1 inputs: projects: Xamarin.Android.sln arguments: '-c $(XA.Build.Configuration) -t:Prepare --no-restore -p:AutoProvision=true -bl:$(System.DefaultWorkingDirectory)\bin\Build$(XA.Build.Configuration)\dotnet-build-prepare.binlog'