From 7393bb5544ed5719e61c68912acd09cf7dea8cce Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Wed, 8 Apr 2026 10:43:11 -0700 Subject: [PATCH 1/3] Exclude satellite assemblies from _PostTrimmingPipeline The _PostTrimmingPipeline target was including all .dll files from ResolvedFileToPublish, including satellite assemblies (.resources.dll) from the shared NuGet package cache. PostTrimmingPipeline opens these with ReadWrite access, causing IOException when parallel multi-RID builds contend for locks on the same cached files. Filter _PostTrimmingAssembly to only include items where PostprocessAssembly metadata is 'true', which excludes satellite assemblies that ILLink does not process. Fixes: https://github.com/dotnet/android/issues/11085 --- ...crosoft.Android.Sdk.TypeMap.LlvmIr.targets | 2 +- .../PackagingTest.cs | 69 +++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.LlvmIr.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.LlvmIr.targets index 699b9baf1e3..a4e72376098 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.LlvmIr.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.LlvmIr.targets @@ -248,7 +248,7 @@ AfterTargets="ILLink" Condition=" '$(PublishTrimmed)' == 'true' "> - <_PostTrimmingAssembly Include="@(ResolvedFileToPublish)" Condition=" '%(Extension)' == '.dll' " /> + <_PostTrimmingAssembly Include="@(ResolvedFileToPublish)" Condition=" '%(Extension)' == '.dll' and '%(ResolvedFileToPublish.PostprocessAssembly)' == 'true' " /> + /// Verifies that _PostTrimmingPipeline does not include satellite assemblies + /// (.resources.dll) in its input. Satellite assemblies are not processed by + /// ILLink and retain their original paths in the shared NuGet package cache. + /// Including them causes PostTrimmingPipeline to open them with ReadWrite + /// access, leading to IOException in parallel multi-RID builds. + /// See: https://github.com/dotnet/android/issues/11085 + /// + [Test] + [NonParallelizable] // Commonly fails NuGet restore + public void PostTrimmingPipelineExcludesSatelliteAssemblies () + { + var proj = new XamarinAndroidApplicationProject { + IsRelease = true, + }; + proj.SetRuntime (AndroidRuntime.MonoVM); + proj.SetRuntimeIdentifiers (AndroidTargetArch.Arm64); + proj.PackageReferences.Add (new Package { + Id = "Humanizer.Core", + Version = "2.14.1", + }); + proj.PackageReferences.Add (new Package { + Id = "Humanizer.Core.es", + Version = "2.14.1", + }); + proj.MainActivity = proj.DefaultMainActivity + .Replace ("//${USINGS}", @"using Humanizer; +using System.Globalization;") + .Replace ("//${AFTER_ONCREATE}", @"var c = new CultureInfo (""es-ES""); +Console.WriteLine ($""{DateTime.UtcNow.AddHours(-30).Humanize(culture:c)}"");"); + proj.OtherBuildItems.Add (new BuildItem ("Using", "System.Globalization")); + proj.OtherBuildItems.Add (new BuildItem ("Using", "Humanizer")); + + // Inject a diagnostic target that logs the _PostTrimmingAssembly items + // created by the production _PostTrimmingPipeline target. In MSBuild, + // items defined in a target's are visible to subsequent targets. + proj.Imports.Add (new Import ("PostTrimmingDiag.targets") { + TextContent = () => """ + + + + + + """, + }); + + using var b = CreateApkBuilder (); + Assert.IsTrue (b.Build (proj), "Build should have succeeded."); + + // Verify the diagnostic target actually ran and logged assemblies + Assert.IsTrue ( + b.LastBuildOutput.ContainsText ("DIAG_PTA:"), + "Diagnostic target should have logged _PostTrimmingAssembly items"); + + // Verify satellite assemblies were NOT included in _PostTrimmingAssembly + var satelliteLines = new List (); + foreach (var line in b.LastBuildOutput) { + if (line.Contains ("DIAG_PTA:") && line.Contains (".resources.dll")) { + satelliteLines.Add (line.Trim ()); + } + } + Assert.IsEmpty (satelliteLines, + "Satellite assemblies (.resources.dll) should not be passed to PostTrimmingPipeline. " + + "They retain paths in the shared NuGet cache and cause file locking conflicts in parallel RID builds. Found:\n" + + string.Join ("\n", satelliteLines)); + } + [Test] public void IgnoreManifestFromJar ([Values] AndroidRuntime runtime) { From b673dddf4f3707ae6762c1c83f89fff5c03073d0 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Wed, 8 Apr 2026 14:42:49 -0700 Subject: [PATCH 2/3] Add positive assertion for satellite assemblies in ResolvedFileToPublish Address review feedback: verify satellite assemblies (.resources.dll) are actually present in ResolvedFileToPublish before asserting they are excluded from _PostTrimmingAssembly. This ensures the test is actually exercising the scenario and won't pass trivially if satellite assemblies are never packaged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Tests/Xamarin.Android.Build.Tests/PackagingTest.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs index 5355772a85d..6e14bb0d9a8 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs @@ -769,6 +769,8 @@ public void PostTrimmingPipelineExcludesSatelliteAssemblies () AfterTargets="_PostTrimmingPipeline" Condition=" '$(PublishTrimmed)' == 'true' "> + """, @@ -782,6 +784,11 @@ public void PostTrimmingPipelineExcludesSatelliteAssemblies () b.LastBuildOutput.ContainsText ("DIAG_PTA:"), "Diagnostic target should have logged _PostTrimmingAssembly items"); + // Verify satellite assemblies ARE present in ResolvedFileToPublish (positive check) + Assert.IsTrue ( + b.LastBuildOutput.ContainsText ("DIAG_RFP:") && b.LastBuildOutput.ContainsText (".resources.dll"), + "Satellite assemblies should be present in ResolvedFileToPublish to confirm the scenario is exercised"); + // Verify satellite assemblies were NOT included in _PostTrimmingAssembly var satelliteLines = new List (); foreach (var line in b.LastBuildOutput) { From 620a8a56c559c942f5601c89c932f3d53cf96cb9 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Thu, 9 Apr 2026 05:57:14 -0700 Subject: [PATCH 3/3] Fix satellite assertion to check DIAG_RFP and .resources.dll on same line The previous assertion checked ContainsText for each string independently, which could match them on different lines. Now iterates lines and checks both strings appear on the same line, matching the pattern used for the negative DIAG_PTA check. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Tests/Xamarin.Android.Build.Tests/PackagingTest.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs index 6e14bb0d9a8..adba96617a1 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs @@ -785,8 +785,14 @@ public void PostTrimmingPipelineExcludesSatelliteAssemblies () "Diagnostic target should have logged _PostTrimmingAssembly items"); // Verify satellite assemblies ARE present in ResolvedFileToPublish (positive check) - Assert.IsTrue ( - b.LastBuildOutput.ContainsText ("DIAG_RFP:") && b.LastBuildOutput.ContainsText (".resources.dll"), + bool hasSatelliteInRfp = false; + foreach (var line in b.LastBuildOutput) { + if (line.Contains ("DIAG_RFP:") && line.Contains (".resources.dll")) { + hasSatelliteInRfp = true; + break; + } + } + Assert.IsTrue (hasSatelliteInRfp, "Satellite assemblies should be present in ResolvedFileToPublish to confirm the scenario is exercised"); // Verify satellite assemblies were NOT included in _PostTrimmingAssembly