diff --git a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Resource.Designer.targets b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Resource.Designer.targets index 7105bf0286c..1a6bc1166a8 100644 --- a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Resource.Designer.targets +++ b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Resource.Designer.targets @@ -227,15 +227,10 @@ Copyright (C) 2016 Xamarin. All rights reserved. is processed by the ILLink step. If we do not do this then the reference to `netstandard.dll` is not replaced with `System.Private.CoreLib` and the app crashes. - We use a TrimmerRootDescriptor (not TrimmerRootAssembly) to prevent ILLink from - trimming the designer's resource properties. FixLegacyResourceDesignerStep runs - after ILLink and needs all properties present when rewriting library assemblies. - A descriptor (-x) preserves types without making the assembly an entry point, - which avoids pulling netstandard.dll into the output. - See https://github.com/dotnet/runtime/issues/126518 - - TODO: Once dotnet/runtime#126518 is fixed and flows to dotnet/android, - simplify this to use TrimmerRootAssembly instead of the XML descriptor. + No TrimmerRootDescriptor is needed: PreTrimmingFixLegacyDesigner rewrites library + assemblies *before* ILLink, replacing designer field loads with calls to the designer + assembly's property getters. ILLink then naturally preserves only the referenced + properties through its mark step, allowing everything else to be trimmed. --> + <_RemoveRegisterFlag>$(MonoAndroidIntermediateAssemblyDir)shrunk\shrunk.flag @@ -223,6 +224,61 @@ + + + + <_PreTrimmingAssembly Include="@(ResolvedFileToPublish)" Condition=" '%(Extension)' == '.dll' and '%(ResolvedFileToPublish.PostprocessAssembly)' == 'true' " /> + + + + + + + + + + + + + + + + + <_PreTrimmingSwappableItem Include="@(ResolvedFileToPublish)" + Condition=" '%(Extension)' == '.dll' and Exists('$(IntermediateOutputPath)prelink/%(Filename)%(Extension)') " /> + + + + + +/// Runs on assemblies that are about to be +/// trimmed by ILLink. This rewrites library assemblies so their resource field accesses +/// (ldsfld) become calls to the designer assembly's property getters. +/// +/// Running this *before* ILLink means the trimmer sees the rewritten IL and can freely +/// trim unused designer types/fields. This avoids the need to root the entire designer +/// assembly during trimming (which causes an APK size regression). +/// +/// Modified assemblies are written to rather than in-place, +/// to avoid mutating files in the shared NuGet cache or shared intermediate output paths. +/// +public class PreTrimmingFixLegacyDesigner : AndroidTask +{ + public override string TaskPrefix => "PTD"; + + [Required] + public ITaskItem [] Assemblies { get; set; } = []; + + [Required] + public string TargetName { get; set; } = ""; + + [Required] + public string OutputDirectory { get; set; } = ""; + + public bool Deterministic { get; set; } + + [Output] + public ITaskItem []? ModifiedAssemblies { get; set; } + + public override bool RunTask () + { + Directory.CreateDirectory (OutputDirectory); + + using var resolver = new DirectoryAssemblyResolver ( + this.CreateTaskLogger (), loadDebugSymbols: true); + + foreach (var assembly in Assemblies) { + var dir = Path.GetFullPath (Path.GetDirectoryName (assembly.ItemSpec) ?? ""); + if (!resolver.SearchDirectories.Contains (dir)) { + resolver.SearchDirectories.Add (dir); + } + } + + var linkContext = new MSBuildLinkContext (resolver, Log); + var fixLegacyStep = new FixLegacyResourceDesignerStep (); + fixLegacyStep.Initialize (linkContext); + + var modified = new List (); + + foreach (var item in Assemblies) { + // Match the filtering in FixLegacyResourceDesignerStep.ProcessAssembly: + // skip the main assembly and framework/BCL assemblies. + if (Path.GetFileNameWithoutExtension (item.ItemSpec) == TargetName) { + continue; + } + if (MonoAndroidHelper.IsFrameworkAssembly (item)) { + continue; + } + + var assembly = resolver.GetAssembly (item.ItemSpec); + if (fixLegacyStep.ProcessAssemblyDesigner (assembly)) { + var outputPath = Path.Combine (OutputDirectory, Path.GetFileName (item.ItemSpec)); + Log.LogDebugMessage ($" Writing modified assembly: {outputPath}"); + assembly.Write (outputPath, new WriterParameters { + WriteSymbols = assembly.MainModule.HasSymbols, + DeterministicMvid = Deterministic, + }); + + var outputItem = new TaskItem (outputPath); + item.CopyMetadataTo (outputItem); + outputItem.SetMetadata ("OriginalPath", item.ItemSpec); + modified.Add (outputItem); + } + } + + ModifiedAssemblies = modified.ToArray (); + + return !Log.HasLoggedErrors; + } +}