From c33b39fa06b186277457014b65d6d8fc270a0f65 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Mon, 16 Mar 2026 11:10:08 -0700 Subject: [PATCH 01/10] [xabt] Move `AddKeepAlives` trimmer step to standalone MSBuild task Migrate AddKeepAlivesStep out of the ILLink custom step pipeline into a standalone MSBuild task that runs AfterTargets="ILLink", following the same pattern established by StripEmbeddedLibraries in #10894. Core IL-rewriting logic is extracted into AddKeepAlivesHelper, shared by both the new task (trimmed builds) and the existing pipeline step (non-trimmed builds via LinkAssembliesNoShrink). --- .../Microsoft.Android.Sdk.ILLink.csproj | 1 - .../MonoDroid.Tuner/AddKeepAlivesHelper.cs | 124 ++++++++++++++ .../MonoDroid.Tuner/AddKeepAlivesStep.cs | 158 +----------------- ...crosoft.Android.Sdk.TypeMap.LlvmIr.targets | 23 ++- .../Tasks/AddKeepAlives.cs | 91 ++++++++++ .../Xamarin.Android.Build.Tasks.csproj | 1 + 6 files changed, 239 insertions(+), 159 deletions(-) create mode 100644 src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/AddKeepAlivesHelper.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Tasks/AddKeepAlives.cs diff --git a/src/Microsoft.Android.Sdk.ILLink/Microsoft.Android.Sdk.ILLink.csproj b/src/Microsoft.Android.Sdk.ILLink/Microsoft.Android.Sdk.ILLink.csproj index 3757ed34b68..92c92c0ee94 100644 --- a/src/Microsoft.Android.Sdk.ILLink/Microsoft.Android.Sdk.ILLink.csproj +++ b/src/Microsoft.Android.Sdk.ILLink/Microsoft.Android.Sdk.ILLink.csproj @@ -14,7 +14,6 @@ - diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/AddKeepAlivesHelper.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/AddKeepAlivesHelper.cs new file mode 100644 index 00000000000..5abe21e19ad --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/AddKeepAlivesHelper.cs @@ -0,0 +1,124 @@ +using System; +using System.Linq; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Xamarin.Android.Tasks; + +namespace MonoDroid.Tuner +{ + static class AddKeepAlivesHelper + { + internal static bool AddKeepAlives (AssemblyDefinition assembly, IMetadataResolver resolver, Func getCorlibAssembly, Action logMessage) + { + if (!assembly.MainModule.HasTypeReference ("Java.Lang.Object")) + return false; + + // Anything that was built against .NET for Android will have + // keep-alives already compiled in. + if (MonoAndroidHelper.IsDotNetAndroidAssembly (assembly)) + return false; + + MethodDefinition? methodKeepAlive = null; + bool changed = false; + foreach (TypeDefinition type in assembly.MainModule.Types) + changed |= ProcessType (type, resolver, ref methodKeepAlive, getCorlibAssembly, logMessage); + + return changed; + } + + static bool ProcessType (TypeDefinition type, IMetadataResolver resolver, ref MethodDefinition? methodKeepAlive, Func getCorlibAssembly, Action logMessage) + { + bool changed = false; + if (MightNeedFix (type, resolver)) + changed |= AddKeepAlives (type, ref methodKeepAlive, getCorlibAssembly, logMessage); + + if (type.HasNestedTypes) { + foreach (var t in type.NestedTypes) { + changed |= ProcessType (t, resolver, ref methodKeepAlive, getCorlibAssembly, logMessage); + } + } + + return changed; + } + + static bool MightNeedFix (TypeDefinition type, IMetadataResolver resolver) + { + return !type.IsAbstract && type.IsSubclassOf ("Java.Lang.Object", resolver); + } + + static bool AddKeepAlives (TypeDefinition type, ref MethodDefinition? methodKeepAlive, Func getCorlibAssembly, Action logMessage) + { + bool changed = false; + foreach (MethodDefinition method in type.Methods) { + if (method.Parameters.Count == 0) + continue; + + if (!method.CustomAttributes.Any (a => a.AttributeType.FullName == "Android.Runtime.RegisterAttribute")) + continue; + + var instructions = method.Body.Instructions; + + var found = false; + for (int off = Math.Max (0, instructions.Count - 6); off < instructions.Count; off++) { + var current = instructions [off]; + if (current.OpCode == OpCodes.Call && current.Operand.ToString ().Contains ("System.GC::KeepAlive")) { + found = true; + break; + } + } + + if (found) + continue; + + var processor = method.Body.GetILProcessor (); + var module = method.DeclaringType.Module; + var end = instructions.Last (); + if (end.Previous.OpCode == OpCodes.Endfinally) + end = end.Previous; + + for (int i = 0; i < method.Parameters.Count; i++) { + if (method.Parameters [i].ParameterType.IsValueType || method.Parameters [i].ParameterType.FullName == "System.String") + continue; + + if (methodKeepAlive == null) + methodKeepAlive = GetKeepAliveMethod (getCorlibAssembly, logMessage); + + if (methodKeepAlive == null) { + logMessage ("Unable to add KeepAlive call, did not find System.GC.KeepAlive method."); + break; + } + + processor.InsertBefore (end, GetLoadArgumentInstruction (method.IsStatic ? i : i + 1, method.Parameters [i])); + processor.InsertBefore (end, Instruction.Create (OpCodes.Call, module.ImportReference (methodKeepAlive))); + changed = true; + } + } + return changed; + } + + static MethodDefinition? GetKeepAliveMethod (Func getCorlibAssembly, Action logMessage) + { + var corlibAssembly = getCorlibAssembly (); + if (corlibAssembly == null) + return null; + + var gcType = Extensions.GetType (corlibAssembly, "System.GC"); + if (gcType == null) + return null; + + return Extensions.GetMethod (gcType, "KeepAlive", new string [] { "System.Object" }); + } + + // Adapted from src/Mono.Android.Export/Mono.CodeGeneration/CodeArgumentReference.cs + static Instruction GetLoadArgumentInstruction (int argNum, ParameterDefinition parameter) + { + switch (argNum) { + case 0: return Instruction.Create (OpCodes.Ldarg_0); + case 1: return Instruction.Create (OpCodes.Ldarg_1); + case 2: return Instruction.Create (OpCodes.Ldarg_2); + case 3: return Instruction.Create (OpCodes.Ldarg_3); + default: return Instruction.Create (OpCodes.Ldarg, parameter); + } + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/AddKeepAlivesStep.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/AddKeepAlivesStep.cs index 0ca2fa7f597..32f69e5e70f 100644 --- a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/AddKeepAlivesStep.cs +++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/AddKeepAlivesStep.cs @@ -1,169 +1,23 @@ -using System; -using System.Linq; -using Java.Interop.Tools.Cecil; using Mono.Cecil; -using Mono.Cecil.Cil; -using Mono.Linker; using Mono.Linker.Steps; using Xamarin.Android.Tasks; namespace MonoDroid.Tuner { - public class AddKeepAlivesStep : BaseStep -#if !ILLINK - , IAssemblyModifierPipelineStep -#endif // !ILLINK + public class AddKeepAlivesStep : BaseStep, IAssemblyModifierPipelineStep { - protected override void ProcessAssembly (AssemblyDefinition assembly) - { - var action = Annotations.HasAction (assembly) ? Annotations.GetAction (assembly) : AssemblyAction.Skip; - if (action == AssemblyAction.Delete) - return; - - if (AddKeepAlives (assembly)) { - if (action == AssemblyAction.Skip || action == AssemblyAction.Copy) - Annotations.SetAction (assembly, AssemblyAction.Save); - } - } - -#if !ILLINK public void ProcessAssembly (AssemblyDefinition assembly, StepContext context) { // Only run this step on user Android assemblies if (!context.IsAndroidUserAssembly) return; - context.IsAssemblyModified |= AddKeepAlives (assembly); - } -#endif // !ILLINK - - internal bool AddKeepAlives (AssemblyDefinition assembly) - { - if (!assembly.MainModule.HasTypeReference ("Java.Lang.Object")) - return false; - - // Anything that was built against .NET for Android will have - // keep-alives already compiled in. - if (MonoAndroidHelper.IsDotNetAndroidAssembly (assembly)) - return false; - - bool changed = false; - foreach (TypeDefinition type in assembly.MainModule.Types) - changed |= ProcessType (type); - - return changed; - } - - bool ProcessType (TypeDefinition type) - { - bool changed = false; - if (MightNeedFix (type)) - changed |= AddKeepAlives (type); - - if (type.HasNestedTypes) { - foreach (var t in type.NestedTypes) { - changed |= ProcessType (t); - } - } - - return changed; - } - - bool MightNeedFix (TypeDefinition type) - { - return !type.IsAbstract && type.IsSubclassOf ("Java.Lang.Object", Context); - } - - MethodDefinition? methodKeepAlive = null; - - bool AddKeepAlives (TypeDefinition type) - { - bool changed = false; - foreach (MethodDefinition method in type.Methods) { - if (method.Parameters.Count == 0) - continue; - - if (!method.CustomAttributes.Any (a => a.AttributeType.FullName == "Android.Runtime.RegisterAttribute")) - continue; - - var instructions = method.Body.Instructions; - - var found = false; - for (int off = Math.Max (0, instructions.Count - 6); off < instructions.Count; off++) { - var current = instructions [off]; - if (current.OpCode == OpCodes.Call && current.Operand.ToString ().Contains ("System.GC::KeepAlive")) { - found = true; - break; - } - } - - if (found) - continue; - - var processor = method.Body.GetILProcessor (); - var module = method.DeclaringType.Module; - var end = instructions.Last (); - if (end.Previous.OpCode == OpCodes.Endfinally) - end = end.Previous; - - for (int i = 0; i < method.Parameters.Count; i++) { - if (method.Parameters [i].ParameterType.IsValueType || method.Parameters [i].ParameterType.FullName == "System.String") - continue; - - if (methodKeepAlive == null) - methodKeepAlive = GetKeepAliveMethod (); - - if (methodKeepAlive == null) { - LogMessage ("Unable to add KeepAlive call, did not find System.GC.KeepAlive method."); - break; - } - - processor.InsertBefore (end, GetLoadArgumentInstruction (method.IsStatic ? i : i + 1, method.Parameters [i])); - processor.InsertBefore (end, Instruction.Create (OpCodes.Call, module.ImportReference (methodKeepAlive))); - changed = true; - } - } - return changed; - } - - protected virtual AssemblyDefinition GetCorlibAssembly () - { - return Context.GetAssembly ("System.Private.CoreLib"); - } - - MethodDefinition? GetKeepAliveMethod () - { - var corlibAssembly = GetCorlibAssembly (); - if (corlibAssembly == null) - return null; - - var gcType = Extensions.GetType (corlibAssembly, "System.GC"); - if (gcType == null) - return null; - - return Extensions.GetMethod (gcType, "KeepAlive", new string [] { "System.Object" }); - } - - public -#if !ILLINK - override -#endif - void LogMessage (string message) - { - Context.LogMessage (message); - } - - // Adapted from src/Mono.Android.Export/Mono.CodeGeneration/CodeArgumentReference.cs - static Instruction GetLoadArgumentInstruction (int argNum, ParameterDefinition parameter) - { - switch (argNum) { - case 0: return Instruction.Create (OpCodes.Ldarg_0); - case 1: return Instruction.Create (OpCodes.Ldarg_1); - case 2: return Instruction.Create (OpCodes.Ldarg_2); - case 3: return Instruction.Create (OpCodes.Ldarg_3); - default: return Instruction.Create (OpCodes.Ldarg, parameter); - } + context.IsAssemblyModified |= AddKeepAlivesHelper.AddKeepAlives ( + assembly, + Context, + () => Context.GetAssembly ("System.Private.CoreLib"), + (msg) => LogMessage (msg)); } } } 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 4be55630c91..8a67744e311 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 @@ -8,6 +8,7 @@ + <_RemoveRegisterFlag>$(MonoAndroidIntermediateAssemblyDir)shrunk\shrunk.flag @@ -197,12 +198,6 @@ <_TrimmerCustomSteps Include="$(_AndroidLinkerCustomStepAssembly)" Type="Microsoft.Android.Sdk.ILLink.PreserveJavaInterfaces" /> <_TrimmerCustomSteps Include="$(_AndroidLinkerCustomStepAssembly)" Type="MonoDroid.Tuner.FixAbstractMethodsStep" /> - <_TrimmerCustomSteps - Condition=" '$(AndroidAddKeepAlives)' == 'true' " - Include="$(_AndroidLinkerCustomStepAssembly)" - AfterStep="CleanStep" - Type="MonoDroid.Tuner.AddKeepAlivesStep" - /> <_TrimmerCustomSteps Condition=" '$(AndroidLinkResources)' == 'true' " @@ -266,6 +261,22 @@ Deterministic="$(Deterministic)" /> + + + + <_AddKeepAlivesAssembly Include="@(ResolvedFileToPublish)" Condition=" '%(Extension)' == '.dll' " /> + + + + diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/AddKeepAlives.cs b/src/Xamarin.Android.Build.Tasks/Tasks/AddKeepAlives.cs new file mode 100644 index 00000000000..3674cff0892 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tasks/AddKeepAlives.cs @@ -0,0 +1,91 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.IO; +using Java.Interop.Tools.Cecil; +using Microsoft.Android.Build.Tasks; +using Microsoft.Build.Framework; +using Mono.Cecil; +using MonoDroid.Tuner; + +namespace Xamarin.Android.Tasks; + +/// +/// An MSBuild task that injects GC.KeepAlive() calls into binding methods of trimmed assemblies. +/// +/// This runs in the inner build after ILLink but before ReadyToRun/crossgen2 compilation, +/// so that R2R images are generated from the already-modified assemblies. +/// +public class AddKeepAlives : AndroidTask +{ + public override string TaskPrefix => "AKA"; + + [Required] + public ITaskItem [] Assemblies { get; set; } = []; + + public bool Deterministic { get; set; } + + public override bool RunTask () + { + var resolver = new DefaultAssemblyResolver (); + var cache = new TypeDefinitionCache (); + var searchDirectories = new HashSet (StringComparer.OrdinalIgnoreCase); + + foreach (var assembly in Assemblies) { + var dir = Path.GetFullPath (Path.GetDirectoryName (assembly.ItemSpec) ?? ""); + if (searchDirectories.Add (dir)) { + resolver.AddSearchDirectory (dir); + } + } + + try { + foreach (var assembly in Assemblies) { + if (MonoAndroidHelper.IsFrameworkAssembly (assembly)) { + continue; + } + + ProcessAssembly (assembly.ItemSpec, resolver, cache); + } + } finally { + resolver.Dispose (); + } + + return !Log.HasLoggedErrors; + } + + void ProcessAssembly (string assemblyPath, IAssemblyResolver resolver, IMetadataResolver cache) + { + string pdbPath = Path.ChangeExtension (assemblyPath, ".pdb"); + bool havePdb = File.Exists (pdbPath); + + var readerParams = new ReaderParameters { + ReadSymbols = havePdb, + ReadWrite = true, + AssemblyResolver = resolver, + }; + + using (var assembly = AssemblyDefinition.ReadAssembly (assemblyPath, readerParams)) { + bool modified = AddKeepAlivesHelper.AddKeepAlives ( + assembly, + cache, + () => GetCorlibAssembly (resolver), + (msg) => Log.LogDebugMessage (msg)); + + if (!modified) { + return; + } + + Log.LogDebugMessage ($" Writing modified assembly: {assemblyPath}"); + assembly.Write (new WriterParameters { + WriteSymbols = havePdb, + DeterministicMvid = Deterministic, + }); + } + } + + static AssemblyDefinition GetCorlibAssembly (IAssemblyResolver resolver) + { + return resolver.Resolve (AssemblyNameReference.Parse ("System.Private.CoreLib")); + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj index 79f6e450ae3..e1cedf463b9 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj @@ -49,6 +49,7 @@ + From e24ddc37287895f59c8de8d2682106aed4278616 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Mon, 16 Mar 2026 13:23:24 -0700 Subject: [PATCH 02/10] Fix missing using directive in AddKeepAlivesHelper.cs --- .../Linker/MonoDroid.Tuner/AddKeepAlivesHelper.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/AddKeepAlivesHelper.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/AddKeepAlivesHelper.cs index 5abe21e19ad..c051fe30971 100644 --- a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/AddKeepAlivesHelper.cs +++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/AddKeepAlivesHelper.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using Java.Interop.Tools.Cecil; using Mono.Cecil; using Mono.Cecil.Cil; using Xamarin.Android.Tasks; From 21ca47518f7c9a11c5b09c637dd14250114880d5 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Wed, 18 Mar 2026 16:19:55 -0700 Subject: [PATCH 03/10] Fix Windows file locking by using DirectoryAssemblyResolver Replace DefaultAssemblyResolver with DirectoryAssemblyResolver (ReadWrite=true) to avoid file handle conflicts on Windows. The directory resolver caches all assemblies (both explicit and dependency-resolved), preventing duplicate file opens that caused IOException when the same assembly was opened as both a dependency and a primary target. --- .../Tasks/AddKeepAlives.cs | 52 +++++-------------- 1 file changed, 14 insertions(+), 38 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/AddKeepAlives.cs b/src/Xamarin.Android.Build.Tasks/Tasks/AddKeepAlives.cs index 3674cff0892..bfed1f4012f 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/AddKeepAlives.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/AddKeepAlives.cs @@ -1,7 +1,5 @@ #nullable enable -using System; -using System.Collections.Generic; using System.IO; using Java.Interop.Tools.Cecil; using Microsoft.Android.Build.Tasks; @@ -28,64 +26,42 @@ public class AddKeepAlives : AndroidTask public override bool RunTask () { - var resolver = new DefaultAssemblyResolver (); + using var resolver = new DirectoryAssemblyResolver ( + this.CreateTaskLogger (), loadDebugSymbols: true, + loadReaderParameters: new ReaderParameters { ReadWrite = true }); var cache = new TypeDefinitionCache (); - var searchDirectories = new HashSet (StringComparer.OrdinalIgnoreCase); foreach (var assembly in Assemblies) { var dir = Path.GetFullPath (Path.GetDirectoryName (assembly.ItemSpec) ?? ""); - if (searchDirectories.Add (dir)) { - resolver.AddSearchDirectory (dir); + if (!resolver.SearchDirectories.Contains (dir)) { + resolver.SearchDirectories.Add (dir); } } - try { - foreach (var assembly in Assemblies) { - if (MonoAndroidHelper.IsFrameworkAssembly (assembly)) { - continue; - } - - ProcessAssembly (assembly.ItemSpec, resolver, cache); + foreach (var item in Assemblies) { + if (MonoAndroidHelper.IsFrameworkAssembly (item)) { + continue; } - } finally { - resolver.Dispose (); - } - - return !Log.HasLoggedErrors; - } - void ProcessAssembly (string assemblyPath, IAssemblyResolver resolver, IMetadataResolver cache) - { - string pdbPath = Path.ChangeExtension (assemblyPath, ".pdb"); - bool havePdb = File.Exists (pdbPath); - - var readerParams = new ReaderParameters { - ReadSymbols = havePdb, - ReadWrite = true, - AssemblyResolver = resolver, - }; + var assembly = resolver.GetAssembly (item.ItemSpec); - using (var assembly = AssemblyDefinition.ReadAssembly (assemblyPath, readerParams)) { bool modified = AddKeepAlivesHelper.AddKeepAlives ( assembly, cache, - () => GetCorlibAssembly (resolver), + () => resolver.Resolve (AssemblyNameReference.Parse ("System.Private.CoreLib")), (msg) => Log.LogDebugMessage (msg)); if (!modified) { - return; + continue; } - Log.LogDebugMessage ($" Writing modified assembly: {assemblyPath}"); + Log.LogDebugMessage ($" Writing modified assembly: {item.ItemSpec}"); assembly.Write (new WriterParameters { - WriteSymbols = havePdb, + WriteSymbols = assembly.MainModule.HasSymbols, DeterministicMvid = Deterministic, }); } - } - static AssemblyDefinition GetCorlibAssembly (IAssemblyResolver resolver) - { - return resolver.Resolve (AssemblyNameReference.Parse ("System.Private.CoreLib")); + return !Log.HasLoggedErrors; } } From d74ce3abb56dd67793b7fe7cdeed1e162befd4ab Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Wed, 18 Mar 2026 21:28:52 -0700 Subject: [PATCH 04/10] [xabt] Combine post-trimming steps into single PostTrimmingPipeline task Replace standalone AddKeepAlives and StripEmbeddedLibraries MSBuild tasks with a single PostTrimmingPipeline task that opens assemblies once (via DirectoryAssemblyResolver with ReadWrite) and runs both modifications in a single pass. Extract StripEmbeddedLibrariesStep as an IAssemblyModifierPipelineStep for reuse. --- .../StripEmbeddedLibrariesStep.cs | 56 +++++++++ ...crosoft.Android.Sdk.TypeMap.LlvmIr.targets | 33 ++--- .../Tasks/AddKeepAlives.cs | 67 ---------- .../Tasks/PostTrimmingPipeline.cs | 84 +++++++++++++ .../Tasks/StripEmbeddedLibraries.cs | 116 ------------------ .../Xamarin.Android.Build.Tasks.csproj | 1 + 6 files changed, 150 insertions(+), 207 deletions(-) create mode 100644 src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs delete mode 100644 src/Xamarin.Android.Build.Tasks/Tasks/AddKeepAlives.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Tasks/PostTrimmingPipeline.cs delete mode 100644 src/Xamarin.Android.Build.Tasks/Tasks/StripEmbeddedLibraries.cs diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs new file mode 100644 index 00000000000..bdd031f9420 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs @@ -0,0 +1,56 @@ +#nullable enable + +using System; +using System.Linq; +using Microsoft.Android.Build.Tasks; +using Microsoft.Build.Utilities; +using Mono.Cecil; +using Xamarin.Android.Tasks; + +namespace MonoDroid.Tuner; + +class StripEmbeddedLibrariesStep : IAssemblyModifierPipelineStep +{ + readonly TaskLoggingHelper log; + + public StripEmbeddedLibrariesStep (TaskLoggingHelper log) + { + this.log = log; + } + + public void ProcessAssembly (AssemblyDefinition assembly, StepContext context) + { + foreach (var module in assembly.Modules) { + foreach (var resource in module.Resources.ToArray ()) { + if (ShouldStripResource (resource)) { + log.LogDebugMessage ($" Stripped {resource.Name} from {assembly.Name.Name}.dll"); + module.Resources.Remove (resource); + context.IsAssemblyModified = true; + } + } + } + } + + /// + /// Determines whether a resource should be stripped from the assembly. + /// Matches the same criteria as the old ILLink StripEmbeddedLibraries step. + /// + internal static bool ShouldStripResource (Resource resource) + { + if (!(resource is EmbeddedResource)) + return false; + // Embedded jars + if (resource.Name.EndsWith (".jar", StringComparison.InvariantCultureIgnoreCase)) + return true; + // Embedded AndroidNativeLibrary archive + if (resource.Name == "__AndroidNativeLibraries__.zip") + return true; + // Embedded AndroidResourceLibrary archive + if (resource.Name == "__AndroidLibraryProjects__.zip") + return true; + // Embedded AndroidEnvironment items + if (resource.Name.StartsWith ("__AndroidEnvironment__", StringComparison.Ordinal)) + return true; + return false; + } +} 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 8a67744e311..84e68d8bf6f 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 @@ -7,8 +7,7 @@ - - + <_RemoveRegisterFlag>$(MonoAndroidIntermediateAssemblyDir)shrunk\shrunk.flag @@ -246,34 +245,20 @@ - - - <_StripEmbeddedLibrariesAssembly Include="@(ResolvedFileToPublish)" Condition=" '%(Extension)' == '.dll' " /> - - - - - - + Condition=" '$(PublishTrimmed)' == 'true' "> - <_AddKeepAlivesAssembly Include="@(ResolvedFileToPublish)" Condition=" '%(Extension)' == '.dll' " /> + <_PostTrimmingAssembly Include="@(ResolvedFileToPublish)" Condition=" '%(Extension)' == '.dll' " /> - diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/AddKeepAlives.cs b/src/Xamarin.Android.Build.Tasks/Tasks/AddKeepAlives.cs deleted file mode 100644 index bfed1f4012f..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Tasks/AddKeepAlives.cs +++ /dev/null @@ -1,67 +0,0 @@ -#nullable enable - -using System.IO; -using Java.Interop.Tools.Cecil; -using Microsoft.Android.Build.Tasks; -using Microsoft.Build.Framework; -using Mono.Cecil; -using MonoDroid.Tuner; - -namespace Xamarin.Android.Tasks; - -/// -/// An MSBuild task that injects GC.KeepAlive() calls into binding methods of trimmed assemblies. -/// -/// This runs in the inner build after ILLink but before ReadyToRun/crossgen2 compilation, -/// so that R2R images are generated from the already-modified assemblies. -/// -public class AddKeepAlives : AndroidTask -{ - public override string TaskPrefix => "AKA"; - - [Required] - public ITaskItem [] Assemblies { get; set; } = []; - - public bool Deterministic { get; set; } - - public override bool RunTask () - { - using var resolver = new DirectoryAssemblyResolver ( - this.CreateTaskLogger (), loadDebugSymbols: true, - loadReaderParameters: new ReaderParameters { ReadWrite = true }); - var cache = new TypeDefinitionCache (); - - foreach (var assembly in Assemblies) { - var dir = Path.GetFullPath (Path.GetDirectoryName (assembly.ItemSpec) ?? ""); - if (!resolver.SearchDirectories.Contains (dir)) { - resolver.SearchDirectories.Add (dir); - } - } - - foreach (var item in Assemblies) { - if (MonoAndroidHelper.IsFrameworkAssembly (item)) { - continue; - } - - var assembly = resolver.GetAssembly (item.ItemSpec); - - bool modified = AddKeepAlivesHelper.AddKeepAlives ( - assembly, - cache, - () => resolver.Resolve (AssemblyNameReference.Parse ("System.Private.CoreLib")), - (msg) => Log.LogDebugMessage (msg)); - - if (!modified) { - continue; - } - - Log.LogDebugMessage ($" Writing modified assembly: {item.ItemSpec}"); - assembly.Write (new WriterParameters { - WriteSymbols = assembly.MainModule.HasSymbols, - DeterministicMvid = Deterministic, - }); - } - - return !Log.HasLoggedErrors; - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/PostTrimmingPipeline.cs b/src/Xamarin.Android.Build.Tasks/Tasks/PostTrimmingPipeline.cs new file mode 100644 index 00000000000..ec1ea789a01 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tasks/PostTrimmingPipeline.cs @@ -0,0 +1,84 @@ +#nullable enable + +using System.Collections.Generic; +using System.IO; +using Java.Interop.Tools.Cecil; +using Microsoft.Android.Build.Tasks; +using Microsoft.Build.Framework; +using Mono.Cecil; +using MonoDroid.Tuner; + +namespace Xamarin.Android.Tasks; + +/// +/// An MSBuild task that runs post-trimming assembly modifications in a single pass. +/// +/// This opens each assembly once (via DirectoryAssemblyResolver with ReadWrite) and +/// runs all registered steps on it, then writes modified assemblies in-place. Currently +/// runs StripEmbeddedLibrariesStep and (optionally) AddKeepAlivesStep. +/// +/// Runs in the inner build after ILLink but before ReadyToRun/crossgen2 compilation, +/// so that R2R images are generated from the already-modified assemblies. +/// +public class PostTrimmingPipeline : AndroidTask +{ + public override string TaskPrefix => "PTP"; + + [Required] + public ITaskItem [] Assemblies { get; set; } = []; + + public bool AddKeepAlives { get; set; } + + public bool Deterministic { get; set; } + + public override bool RunTask () + { + using var resolver = new DirectoryAssemblyResolver ( + this.CreateTaskLogger (), loadDebugSymbols: true, + loadReaderParameters: new ReaderParameters { ReadWrite = true }); + + foreach (var assembly in Assemblies) { + var dir = Path.GetFullPath (Path.GetDirectoryName (assembly.ItemSpec) ?? ""); + if (!resolver.SearchDirectories.Contains (dir)) { + resolver.SearchDirectories.Add (dir); + } + } + + var steps = new List (); + + steps.Add (new StripEmbeddedLibrariesStep (Log)); + + if (AddKeepAlives) { + var linkContext = new MSBuildLinkContext (resolver, Log); + var addKeepAlivesStep = new AddKeepAlivesStep (); + addKeepAlivesStep.Initialize (linkContext); + steps.Add (addKeepAlivesStep); + } + + foreach (var item in Assemblies) { + if (MonoAndroidHelper.IsFrameworkAssembly (item)) { + continue; + } + + var assembly = resolver.GetAssembly (item.ItemSpec); + var context = new StepContext (item, item) { + IsAndroidAssembly = MonoAndroidHelper.IsAndroidAssembly (item), + IsUserAssembly = true, + }; + + foreach (var step in steps) { + step.ProcessAssembly (assembly, context); + } + + if (context.IsAssemblyModified) { + Log.LogDebugMessage ($" Writing modified assembly: {item.ItemSpec}"); + assembly.Write (new WriterParameters { + WriteSymbols = assembly.MainModule.HasSymbols, + DeterministicMvid = Deterministic, + }); + } + } + + return !Log.HasLoggedErrors; + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/StripEmbeddedLibraries.cs b/src/Xamarin.Android.Build.Tasks/Tasks/StripEmbeddedLibraries.cs deleted file mode 100644 index 7a65c4a8145..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Tasks/StripEmbeddedLibraries.cs +++ /dev/null @@ -1,116 +0,0 @@ -#nullable enable - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Microsoft.Android.Build.Tasks; -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; -using Mono.Cecil; - -namespace Xamarin.Android.Tasks; - -/// -/// An MSBuild task that strips embedded Android resources (.jar, __AndroidNativeLibraries__.zip, -/// __AndroidLibraryProjects__.zip, __AndroidEnvironment__) from trimmed assemblies. -/// -/// This runs in the inner build after ILLink but before ReadyToRun/crossgen2 compilation, -/// so that R2R images are generated from the already-stripped assemblies. -/// -public class StripEmbeddedLibraries : AndroidTask -{ - public override string TaskPrefix => "SEL"; - - [Required] - public ITaskItem [] Assemblies { get; set; } = []; - - public bool Deterministic { get; set; } - - public override bool RunTask () - { - var resolver = new DefaultAssemblyResolver (); - var searchDirectories = new HashSet (StringComparer.OrdinalIgnoreCase); - - foreach (var assembly in Assemblies) { - var dir = Path.GetFullPath (Path.GetDirectoryName (assembly.ItemSpec) ?? ""); - if (searchDirectories.Add (dir)) { - resolver.AddSearchDirectory (dir); - } - } - - try { - foreach (var assembly in Assemblies) { - if (MonoAndroidHelper.IsFrameworkAssembly (assembly)) { - continue; - } - - StripAssembly (assembly.ItemSpec, resolver); - } - } finally { - resolver.Dispose (); - } - - return !Log.HasLoggedErrors; - } - - void StripAssembly (string assemblyPath, IAssemblyResolver resolver) - { - string pdbPath = Path.ChangeExtension (assemblyPath, ".pdb"); - bool havePdb = File.Exists (pdbPath); - - var readerParams = new ReaderParameters { - ReadSymbols = havePdb, - ReadWrite = true, - AssemblyResolver = resolver, - }; - - bool assembly_modified = false; - - using (var assembly = AssemblyDefinition.ReadAssembly (assemblyPath, readerParams)) { - foreach (var module in assembly.Modules) { - foreach (var resource in module.Resources.ToArray ()) { - if (ShouldStripResource (resource)) { - Log.LogDebugMessage ($" Stripped {resource.Name} from {assembly.Name.Name}.dll"); - module.Resources.Remove (resource); - assembly_modified = true; - } - } - } - - if (!assembly_modified) { - return; - } - - Log.LogDebugMessage ($" Writing stripped assembly: {assemblyPath}"); - assembly.Write (new WriterParameters { - WriteSymbols = havePdb, - DeterministicMvid = Deterministic, - }); - } - } - - /// - /// Determines whether a resource should be stripped from the assembly. - /// Matches the same criteria as the old ILLink StripEmbeddedLibraries step. - /// - internal static bool ShouldStripResource (Resource resource) - { - if (!(resource is EmbeddedResource)) - return false; - // Embedded jars - if (resource.Name.EndsWith (".jar", StringComparison.InvariantCultureIgnoreCase)) - return true; - // Embedded AndroidNativeLibrary archive - if (resource.Name == "__AndroidNativeLibraries__.zip") - return true; - // Embedded AndroidResourceLibrary archive - if (resource.Name == "__AndroidLibraryProjects__.zip") - return true; - // Embedded AndroidEnvironment items - if (resource.Name.StartsWith ("__AndroidEnvironment__", StringComparison.Ordinal)) - return true; - return false; - } - -} diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj index e1cedf463b9..d309996f45a 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj @@ -50,6 +50,7 @@ + From 59e7c899f4013cf17363bbc96dc33ebaa7dd591d Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Thu, 19 Mar 2026 10:56:05 -0700 Subject: [PATCH 05/10] [xabt] Refactor PostTrimmingPipeline to use IAssemblyModifierPipelineStep pattern Use List with StripEmbeddedLibrariesStep and PostTrimmingAddKeepAlivesStep instead of calling helpers directly. Move the IsFrameworkAssembly check into StripEmbeddedLibrariesStep.ProcessAssembly and remove all outer-loop filtering so each step handles its own guards internally. --- .../PostTrimmingAddKeepAlivesStep.cs | 30 +++++++++++++++++++ .../StripEmbeddedLibrariesStep.cs | 11 ++++++- .../Tasks/PostTrimmingPipeline.cs | 21 ++++--------- .../Xamarin.Android.Build.Tasks.csproj | 1 + 4 files changed, 46 insertions(+), 17 deletions(-) create mode 100644 src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/PostTrimmingAddKeepAlivesStep.cs diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/PostTrimmingAddKeepAlivesStep.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/PostTrimmingAddKeepAlivesStep.cs new file mode 100644 index 00000000000..0a7392789cf --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/PostTrimmingAddKeepAlivesStep.cs @@ -0,0 +1,30 @@ +using System; +using Java.Interop.Tools.Cecil; +using Mono.Cecil; +using Xamarin.Android.Tasks; + +namespace MonoDroid.Tuner; + +/// +/// Post-trimming version of AddKeepAlives that calls AddKeepAlivesHelper directly, +/// matching the original ILLink behavior (no IsAndroidUserAssembly pre-filter). +/// The helper has its own assembly-level guards (HasTypeReference, IsDotNetAndroidAssembly). +/// +class PostTrimmingAddKeepAlivesStep : IAssemblyModifierPipelineStep +{ + readonly IMetadataResolver cache; + readonly Func getCorlibAssembly; + readonly Action logMessage; + + public PostTrimmingAddKeepAlivesStep (IMetadataResolver cache, Func getCorlibAssembly, Action logMessage) + { + this.cache = cache; + this.getCorlibAssembly = getCorlibAssembly; + this.logMessage = logMessage; + } + + public void ProcessAssembly (AssemblyDefinition assembly, StepContext context) + { + context.IsAssemblyModified |= AddKeepAlivesHelper.AddKeepAlives (assembly, cache, getCorlibAssembly, logMessage); + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs index bdd031f9420..78f202b23c1 100644 --- a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs +++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs @@ -20,15 +20,24 @@ public StripEmbeddedLibrariesStep (TaskLoggingHelper log) public void ProcessAssembly (AssemblyDefinition assembly, StepContext context) { + if (MonoAndroidHelper.IsFrameworkAssembly (assembly)) + return; + context.IsAssemblyModified |= StripEmbeddedLibraries (assembly, log); + } + + internal static bool StripEmbeddedLibraries (AssemblyDefinition assembly, TaskLoggingHelper log) + { + bool modified = false; foreach (var module in assembly.Modules) { foreach (var resource in module.Resources.ToArray ()) { if (ShouldStripResource (resource)) { log.LogDebugMessage ($" Stripped {resource.Name} from {assembly.Name.Name}.dll"); module.Resources.Remove (resource); - context.IsAssemblyModified = true; + modified = true; } } } + return modified; } /// diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/PostTrimmingPipeline.cs b/src/Xamarin.Android.Build.Tasks/Tasks/PostTrimmingPipeline.cs index ec1ea789a01..4084e48cd77 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/PostTrimmingPipeline.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/PostTrimmingPipeline.cs @@ -36,6 +36,7 @@ public override bool RunTask () using var resolver = new DirectoryAssemblyResolver ( this.CreateTaskLogger (), loadDebugSymbols: true, loadReaderParameters: new ReaderParameters { ReadWrite = true }); + var cache = new TypeDefinitionCache (); foreach (var assembly in Assemblies) { var dir = Path.GetFullPath (Path.GetDirectoryName (assembly.ItemSpec) ?? ""); @@ -45,31 +46,19 @@ public override bool RunTask () } var steps = new List (); - steps.Add (new StripEmbeddedLibrariesStep (Log)); - if (AddKeepAlives) { - var linkContext = new MSBuildLinkContext (resolver, Log); - var addKeepAlivesStep = new AddKeepAlivesStep (); - addKeepAlivesStep.Initialize (linkContext); - steps.Add (addKeepAlivesStep); + steps.Add (new PostTrimmingAddKeepAlivesStep (cache, + () => resolver.Resolve (AssemblyNameReference.Parse ("System.Private.CoreLib")), + (msg) => Log.LogDebugMessage (msg))); } foreach (var item in Assemblies) { - if (MonoAndroidHelper.IsFrameworkAssembly (item)) { - continue; - } - var assembly = resolver.GetAssembly (item.ItemSpec); - var context = new StepContext (item, item) { - IsAndroidAssembly = MonoAndroidHelper.IsAndroidAssembly (item), - IsUserAssembly = true, - }; - + var context = new StepContext (item, item); foreach (var step in steps) { step.ProcessAssembly (assembly, context); } - if (context.IsAssemblyModified) { Log.LogDebugMessage ($" Writing modified assembly: {item.ItemSpec}"); assembly.Write (new WriterParameters { diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj index d309996f45a..c03261cb316 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj @@ -51,6 +51,7 @@ + From e8a2b8e28c6a1bcb42f2d4b307a8076ee32ccd6b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Mar 2026 16:31:18 +0000 Subject: [PATCH 06/10] Initial plan From 8215697e2e187cb6b5c9f004c324faaaf8b80102 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Mar 2026 17:05:27 +0000 Subject: [PATCH 07/10] Address copilot reviewer comments: nullable Func, whitespace fix, updated csproj comment Co-authored-by: sbomer <787361+sbomer@users.noreply.github.com> --- .../Linker/MonoDroid.Tuner/AddKeepAlivesHelper.cs | 10 +++++----- .../MonoDroid.Tuner/PostTrimmingAddKeepAlivesStep.cs | 4 ++-- .../Tasks/PostTrimmingPipeline.cs | 9 ++++++++- .../Xamarin.Android.Build.Tasks.csproj | 3 ++- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/AddKeepAlivesHelper.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/AddKeepAlivesHelper.cs index c051fe30971..5eb15079c14 100644 --- a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/AddKeepAlivesHelper.cs +++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/AddKeepAlivesHelper.cs @@ -9,7 +9,7 @@ namespace MonoDroid.Tuner { static class AddKeepAlivesHelper { - internal static bool AddKeepAlives (AssemblyDefinition assembly, IMetadataResolver resolver, Func getCorlibAssembly, Action logMessage) + internal static bool AddKeepAlives (AssemblyDefinition assembly, IMetadataResolver resolver, Func getCorlibAssembly, Action logMessage) { if (!assembly.MainModule.HasTypeReference ("Java.Lang.Object")) return false; @@ -27,9 +27,9 @@ internal static bool AddKeepAlives (AssemblyDefinition assembly, IMetadataResolv return changed; } - static bool ProcessType (TypeDefinition type, IMetadataResolver resolver, ref MethodDefinition? methodKeepAlive, Func getCorlibAssembly, Action logMessage) + static bool ProcessType (TypeDefinition type, IMetadataResolver resolver, ref MethodDefinition? methodKeepAlive, Func getCorlibAssembly, Action logMessage) { - bool changed = false; + bool changed = false; if (MightNeedFix (type, resolver)) changed |= AddKeepAlives (type, ref methodKeepAlive, getCorlibAssembly, logMessage); @@ -47,7 +47,7 @@ static bool MightNeedFix (TypeDefinition type, IMetadataResolver resolver) return !type.IsAbstract && type.IsSubclassOf ("Java.Lang.Object", resolver); } - static bool AddKeepAlives (TypeDefinition type, ref MethodDefinition? methodKeepAlive, Func getCorlibAssembly, Action logMessage) + static bool AddKeepAlives (TypeDefinition type, ref MethodDefinition? methodKeepAlive, Func getCorlibAssembly, Action logMessage) { bool changed = false; foreach (MethodDefinition method in type.Methods) { @@ -97,7 +97,7 @@ static bool AddKeepAlives (TypeDefinition type, ref MethodDefinition? methodKeep return changed; } - static MethodDefinition? GetKeepAliveMethod (Func getCorlibAssembly, Action logMessage) + static MethodDefinition? GetKeepAliveMethod (Func getCorlibAssembly, Action logMessage) { var corlibAssembly = getCorlibAssembly (); if (corlibAssembly == null) diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/PostTrimmingAddKeepAlivesStep.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/PostTrimmingAddKeepAlivesStep.cs index 0a7392789cf..e725b5714ec 100644 --- a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/PostTrimmingAddKeepAlivesStep.cs +++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/PostTrimmingAddKeepAlivesStep.cs @@ -13,10 +13,10 @@ namespace MonoDroid.Tuner; class PostTrimmingAddKeepAlivesStep : IAssemblyModifierPipelineStep { readonly IMetadataResolver cache; - readonly Func getCorlibAssembly; + readonly Func getCorlibAssembly; readonly Action logMessage; - public PostTrimmingAddKeepAlivesStep (IMetadataResolver cache, Func getCorlibAssembly, Action logMessage) + public PostTrimmingAddKeepAlivesStep (IMetadataResolver cache, Func getCorlibAssembly, Action logMessage) { this.cache = cache; this.getCorlibAssembly = getCorlibAssembly; diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/PostTrimmingPipeline.cs b/src/Xamarin.Android.Build.Tasks/Tasks/PostTrimmingPipeline.cs index 4084e48cd77..e7e14e2e7f5 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/PostTrimmingPipeline.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/PostTrimmingPipeline.cs @@ -49,7 +49,14 @@ public override bool RunTask () steps.Add (new StripEmbeddedLibrariesStep (Log)); if (AddKeepAlives) { steps.Add (new PostTrimmingAddKeepAlivesStep (cache, - () => resolver.Resolve (AssemblyNameReference.Parse ("System.Private.CoreLib")), + () => { + try { + return resolver.Resolve (AssemblyNameReference.Parse ("System.Private.CoreLib")); + } catch (AssemblyResolutionException ex) { + Log.LogErrorFromException (ex, showStackTrace: false); + return null; + } + }, (msg) => Log.LogDebugMessage (msg))); } diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj index 62a40d93337..c9157be3712 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj @@ -44,7 +44,8 @@ - + + From e6b2e5b1d6d11a8b76cebe417d5bb5b207868dff Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Mar 2026 17:44:14 +0000 Subject: [PATCH 08/10] Memoize corlib resolution in PostTrimmingPipeline to avoid repeated resolution attempts Co-authored-by: sbomer <787361+sbomer@users.noreply.github.com> Agent-Logs-Url: https://github.com/dotnet/android/sessions/399b2f01-0576-42f4-a1d4-d0f0d31f5eb7 --- .../Tasks/PostTrimmingPipeline.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/PostTrimmingPipeline.cs b/src/Xamarin.Android.Build.Tasks/Tasks/PostTrimmingPipeline.cs index e7e14e2e7f5..7a33ea3c7be 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/PostTrimmingPipeline.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/PostTrimmingPipeline.cs @@ -48,14 +48,21 @@ public override bool RunTask () var steps = new List (); steps.Add (new StripEmbeddedLibrariesStep (Log)); if (AddKeepAlives) { + // Memoize the corlib resolution so the attempt (and any error logging) happens at most once, + // regardless of how many assemblies/methods need KeepAlive injection. + AssemblyDefinition? corlibAssembly = null; + bool corlibResolutionAttempted = false; steps.Add (new PostTrimmingAddKeepAlivesStep (cache, () => { - try { - return resolver.Resolve (AssemblyNameReference.Parse ("System.Private.CoreLib")); - } catch (AssemblyResolutionException ex) { - Log.LogErrorFromException (ex, showStackTrace: false); - return null; + if (!corlibResolutionAttempted) { + corlibResolutionAttempted = true; + try { + corlibAssembly = resolver.Resolve (AssemblyNameReference.Parse ("System.Private.CoreLib")); + } catch (AssemblyResolutionException ex) { + Log.LogErrorFromException (ex, showStackTrace: false); + } } + return corlibAssembly; }, (msg) => Log.LogDebugMessage (msg))); } From c0330842a524b0ad8c0ce749bde67b5cee92e545 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Fri, 20 Mar 2026 10:52:11 -0700 Subject: [PATCH 09/10] Update src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs index 78f202b23c1..e924636dcae 100644 --- a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs +++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs @@ -5,7 +5,6 @@ using Microsoft.Android.Build.Tasks; using Microsoft.Build.Utilities; using Mono.Cecil; -using Xamarin.Android.Tasks; namespace MonoDroid.Tuner; From 3fa2b859751394329f99476b0820d40a28d014d3 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Fri, 20 Mar 2026 13:37:14 -0700 Subject: [PATCH 10/10] Restore missing 'using Xamarin.Android.Tasks' in StripEmbeddedLibrariesStep --- .../Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs index e924636dcae..78f202b23c1 100644 --- a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs +++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/StripEmbeddedLibrariesStep.cs @@ -5,6 +5,7 @@ using Microsoft.Android.Build.Tasks; using Microsoft.Build.Utilities; using Mono.Cecil; +using Xamarin.Android.Tasks; namespace MonoDroid.Tuner;