From 3059490cb12f7f9c591a59c76d36af2e2e491a5e Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 2 Jun 2026 16:46:35 -0500 Subject: [PATCH] [Xamarin.Android.Build.Tasks] Fix _CreateAar race under parallel build (#11527) Fixes: https://github.com/dotnet/android/issues/11514 Under parallel build (`-m`), library projects with `true` can intermittently fail with: ``` error XARLP7024: System.IO.IOException: The process cannot access the file '.../.aar' because it is being used by another process. at Microsoft.Android.Build.Tasks.Files.HashFile(...) at Xamarin.Android.Tasks.ResolveLibraryProjectImports.Extract(...) ``` The reporter's `mini.binlog` shows `_CreateAar` running in two distinct project instances of the same library csproj, and a local repro shows it running **three** times for a single `dotnet build`: * Once from `BuildDependsOn`. * Once from `_UpdateAndroidResourcesDependsOn` on the regular `Build` chain. * Once more from `_UpdateAndroidResourcesDependsOn` on a Pack-dispatched project instance entered via `_GetFrameworkAssemblyReferences` (NuGet pack's per-TFM inner-target dispatch). That instance has different global properties, so MSBuild does not dedupe it and may run it on a fresh worker node, opening a write/write or write/read race against consumers that read the `.aar` via `Files.HashFile`. `_UpdateAndroidResources` is the aapt2/resource-designer step; producing the publish artifact (`.aar`) is unrelated. Remove `_CreateAar` from `_UpdateAndroidResourcesDependsOn`. The AAR is still produced because `_CreateAar` remains in `BuildDependsOn` for non-application projects, and Pack depends on `Build`, so `dotnet build` and `dotnet pack` both still create it. This collapses the count to a single invocation per build, eliminating the race window and the redundant AAR rewrites at the root rather than papering over them with reader retries or atomic writes. Added `IncrementalBuildTest.CreateAarRunsOnceWithGeneratePackageOnBuild`: builds a library with `GeneratePackageOnBuild=true` at `LoggerVerbosity.Detailed` and asserts `Target "_CreateAar" in file ...` appears exactly once in the build log. Verified against the issue's repro project: count drops from **3 to 1** with the fix. --- .../Microsoft.Android.Sdk.BuildOrder.targets | 2 +- .../IncrementalBuildTest.cs | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.BuildOrder.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.BuildOrder.targets index 9bb66ab4073..724c4a3de7b 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.BuildOrder.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.BuildOrder.targets @@ -174,7 +174,6 @@ properties that determine build ordering. _CheckForDeletedResourceFile; _ComputeAndroidResourcePaths; _UpdateAndroidResgen; - _CreateAar; _SetupMSBuildAllProjects; @@ -244,6 +243,7 @@ properties that determine build ordering. <_IncludeAarInNuGetPackageDependsOn> _BuildAndroidGradleProjects; _CategorizeAndroidLibraries; + _CreateAar; diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/IncrementalBuildTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/IncrementalBuildTest.cs index dbb0bd836f3..c2b61a40093 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/IncrementalBuildTest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/IncrementalBuildTest.cs @@ -522,6 +522,33 @@ public void LibraryProjectTargetsDoNotBreak () } } + [Test] + public void CreateAarRunsOnceWithGeneratePackageOnBuild () + { + // https://github.com/dotnet/android/issues/11514 + // `_CreateAar` was being invoked twice for a single library build when + // `GeneratePackageOnBuild=true`: once via `BuildDependsOn`, and again via + // NuGet pack's per-TFM dispatch which entered the project at + // `_GetFrameworkAssemblyReferences`. That target transitively pulled in + // `UpdateAndroidResources` -> `_UpdateAndroidResources` -> `_CreateAar` + // via `_UpdateAndroidResourcesDependsOn`. The second invocation can race + // the first writer when parallel builds (-m) consumers concurrently read + // the .aar via `Files.HashFile`, producing `XARLP7024`. + var proj = new XamarinAndroidLibraryProject (); + proj.SetProperty ("GeneratePackageOnBuild", "true"); + using (var b = CreateDllBuilder ()) { + b.Verbosity = LoggerVerbosity.Detailed; + Assert.IsTrue (b.Build (proj), "Build should have succeeded."); + // MSBuild emits "Building target X completely" only when the target + // actually runs (not when it is entered then skipped due to empty + // Inputs/Outputs). Counting that message gives us the number of real + // `_CreateAar` executions, which is what races on disk in #11514. + int count = b.LastBuildOutput.Count (l => l.Contains ("Building target \"_CreateAar\"")); + Assert.AreEqual (1, count, + $"`_CreateAar` should only execute once per build of a library project; was {count}."); + } + } + [Test] public void ManifestMergerIncremental () {