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 ARE present in ResolvedFileToPublish (positive check) + 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 + 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) {