diff --git a/src/Microsoft.Android.Sdk.ILLink/GetAssembliesStep.cs b/src/Microsoft.Android.Sdk.ILLink/GetAssembliesStep.cs deleted file mode 100644 index 7721dc05151..00000000000 --- a/src/Microsoft.Android.Sdk.ILLink/GetAssembliesStep.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Mono.Cecil; -using Mono.Linker; -using Mono.Linker.Steps; -using System; -using System.Linq; -using Xamarin.Android.Tasks; -using System.Collections.Generic; -using Mono.Cecil.Cil; - -namespace MonoDroid.Tuner -{ - public class GetAssembliesStep : BaseStep - { - AndroidLinkConfiguration config = null; - - protected override void Process () - { - config = AndroidLinkConfiguration.GetInstance (Context); - } - - protected override void ProcessAssembly (AssemblyDefinition assembly) - { - if (config == null) - return; - config.Assemblies.Add (assembly); - } - } -} 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 92c92c0ee94..26d2067d1fc 100644 --- a/src/Microsoft.Android.Sdk.ILLink/Microsoft.Android.Sdk.ILLink.csproj +++ b/src/Microsoft.Android.Sdk.ILLink/Microsoft.Android.Sdk.ILLink.csproj @@ -19,7 +19,6 @@ - diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/RemoveResourceDesignerStep.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/RemoveResourceDesignerStep.cs index c5a34887a0f..51e25383a0d 100644 --- a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/RemoveResourceDesignerStep.cs +++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/RemoveResourceDesignerStep.cs @@ -1,123 +1,323 @@ #nullable disable -using Mono.Cecil; -using Mono.Linker; -using Mono.Linker.Steps; using System; -using System.Linq; -using Xamarin.Android.Tasks; using System.Collections.Generic; -using Mono.Cecil.Cil; +using System.Globalization; using System.Text.RegularExpressions; -#if ILLINK -using Microsoft.Android.Sdk.ILLink; -#endif +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.Collections.Generic; +using Xamarin.Android.Tasks; + +namespace MonoDroid.Tuner; -namespace MonoDroid.Tuner +/// +/// Inlines resource designer constants and removes designer classes. +/// The core logic is adapted from the original ILLink RemoveResourceDesignerStep / LinkDesignerBase. +/// +class RemoveResourceDesignerStep : IAssemblyModifierPipelineStep { - public class RemoveResourceDesignerStep : LinkDesignerBase + readonly IList allAssemblies; + readonly Action logMessage; + readonly Regex opCodeRegex = new Regex (@"([\w]+): ([\w]+) ([\w.]+) ([\w:./]+)"); + + TypeDefinition mainDesigner = null; + AssemblyDefinition mainAssembly = null; + CustomAttribute mainDesignerAttribute; + Dictionary designerConstants; + bool designerLoaded; + + public RemoveResourceDesignerStep (IList allAssemblies, Action logMessage) + { + this.allAssemblies = allAssemblies; + this.logMessage = logMessage; + } + + public void ProcessAssembly (AssemblyDefinition assembly, StepContext context) { - TypeDefinition mainDesigner = null; - AssemblyDefinition mainAssembly = null; - CustomAttribute mainDesignerAttribute; - Dictionary designerConstants; - Regex opCodeRegex = new Regex (@"([\w]+): ([\w]+) ([\w.]+) ([\w:./]+)"); + LoadDesigner (); + context.IsAssemblyModified |= ProcessAssemblyDesigner (assembly); + } + + void LoadDesigner () + { + if (designerLoaded) + return; + designerLoaded = true; + + foreach (var asm in allAssemblies) { + if (FindResourceDesigner (asm, mainApplication: true, designer: out mainDesigner, designerAttribute: out mainDesignerAttribute)) { + mainAssembly = asm; + break; + } + } + if (mainDesigner == null) { + logMessage (" Main Designer not found."); + return; + } + logMessage ($" Main Designer found {mainDesigner.FullName}."); + designerConstants = BuildResourceDesignerFieldLookup (mainDesigner); + } + + bool ProcessAssemblyDesigner (AssemblyDefinition assembly) + { + if (mainDesigner == null) + return false; + if (MonoAndroidHelper.IsFrameworkAssembly (assembly)) + return false; + + logMessage ($" Fixing up {assembly.Name.Name}"); + TypeDefinition localDesigner = null; + CustomAttribute designerAttribute; + if (assembly != mainAssembly) { + logMessage ($" {assembly.Name.Name} is not the main assembly. "); + if (!FindResourceDesigner (assembly, mainApplication: false, designer: out localDesigner, designerAttribute: out designerAttribute)) { + logMessage ($" {assembly.Name.Name} does not have a designer file."); + return false; + } + } else { + logMessage ($" {assembly.Name.Name} is the main assembly. "); + localDesigner = mainDesigner; + designerAttribute = mainDesignerAttribute; + } + + logMessage ($" {assembly.Name.Name} has designer {localDesigner.FullName}."); + + FixupAssemblyTypes (assembly, localDesigner); + + ClearDesignerClass (localDesigner); + if (designerAttribute != null) { + assembly.CustomAttributes.Remove (designerAttribute); + } + return true; + } + + // ---- Methods below are from LinkDesignerBase, adapted for non-ILLink use ---- - protected override void LoadDesigner () + bool FindResourceDesigner (AssemblyDefinition assembly, bool mainApplication, out TypeDefinition designer, out CustomAttribute designerAttribute) + { + string designerFullName = null; + designer = null; + designerAttribute = null; + foreach (CustomAttribute attribute in assembly.CustomAttributes) { - if (mainAssembly != null) - return; - // resolve the MainAssembly Resource designer TypeDefinition - AndroidLinkConfiguration config = AndroidLinkConfiguration.GetInstance (Context); - if (config == null) - return; - foreach(var asm in config.Assemblies) { - if (FindResourceDesigner (asm, mainApplication: true, designer: out mainDesigner, designerAttribute: out mainDesignerAttribute)) { - mainAssembly = asm; - break; + if (attribute.AttributeType.FullName == "Android.Runtime.ResourceDesignerAttribute") + { + designerAttribute = attribute; + if (attribute.HasProperties) + { + foreach (var p in attribute.Properties) + { + if (p.Name == "IsApplication" && (bool)p.Argument.Value == (mainApplication ? mainApplication : (bool)p.Argument.Value)) + { + designerFullName = attribute.ConstructorArguments[0].Value.ToString (); + break; + } + } } + break; + } - if (mainDesigner == null) { - LogMessage ($" Main Designer not found."); - return; + } + + if (string.IsNullOrEmpty(designerFullName)) { + logMessage ($"Inspecting member references for assembly: {assembly.FullName};"); + var memberRefs = assembly.MainModule.GetMemberReferences (); + foreach (var memberRef in memberRefs) { + string declaringType = memberRef.DeclaringType?.ToString () ?? string.Empty; + if (!declaringType.Contains (".Resource/")) { + continue; + } + if (declaringType.Contains ("_Microsoft.Android.Resource.Designer")) { + continue; + } + var resolved = false; + try { + var def = memberRef.Resolve (); + if (resolved = def != null) { + logMessage ($"Resolved member `{memberRef?.Name}`"); + } + } catch (Exception ex) { + logMessage ($"Exception resolving member `{memberRef?.Name}`: {ex}"); + resolved = false; + } + if (!resolved) { + logMessage ($"Adding _Linker.Generated.Resource to {assembly.Name.Name}. Could not resolve {memberRef?.Name} : {declaringType}"); + designer = new TypeDefinition ("_Linker.Generated", "Resource", TypeAttributes.Public | TypeAttributes.AnsiClass); + designer.BaseType = new TypeDefinition ("System", "Object", TypeAttributes.Public | TypeAttributes.AnsiClass); + return true; + } } - LogMessage ($" Main Designer found {mainDesigner.FullName}."); - designerConstants = BuildResourceDesignerFieldLookup (mainDesigner); } - protected override void EndProcess () + if (string.IsNullOrEmpty(designerFullName)) + return false; + + foreach (ModuleDefinition module in assembly.Modules) { - if (mainDesigner != null) { - LogMessage ($" Setting Action on {mainAssembly.Name} to Save."); - Annotations.SetAction (mainAssembly, AssemblyAction.Save); + foreach (TypeDefinition type in module.Types) + { + if (type.FullName == designerFullName) + { + designer = type; + return true; + } } } + return false; + } - protected override void FixBody (MethodBody body, TypeDefinition designer) + void FixBody (MethodBody body, TypeDefinition designer) + { + Dictionary instructions = new Dictionary(); + var processor = body.GetILProcessor (); + string designerFullName = $"{designer.FullName}/"; + bool isDesignerMethod = designerFullName.Contains (body.Method.DeclaringType.FullName); + string declaringTypeName = body.Method.DeclaringType.Name; + foreach (var i in body.Instructions) { - Dictionary instructions = new Dictionary(); - var processor = body.GetILProcessor (); - string designerFullName = $"{designer.FullName}/"; - bool isDesignerMethod = designerFullName.Contains (body.Method.DeclaringType.FullName); - string declaringTypeName = body.Method.DeclaringType.Name; - foreach (var i in body.Instructions) + string line = i.ToString (); + if ((line.Contains (designerFullName) || (isDesignerMethod && i.OpCode == OpCodes.Stsfld)) && !instructions.ContainsKey (i)) { - string line = i.ToString (); - if ((line.Contains (designerFullName) || (isDesignerMethod && i.OpCode == OpCodes.Stsfld)) && !instructions.ContainsKey (i)) - { - var match = opCodeRegex.Match (line); - if (match.Success && match.Groups.Count == 5) { - string key = match.Groups[4].Value.Replace (designerFullName, string.Empty); - if (isDesignerMethod) { - key = declaringTypeName +"::" + key; - } - if (designerConstants.ContainsKey (key) && !instructions.ContainsKey (i)) - instructions.Add(i, designerConstants [key]); + var match = opCodeRegex.Match (line); + if (match.Success && match.Groups.Count == 5) { + string key = match.Groups[4].Value.Replace (designerFullName, string.Empty); + if (isDesignerMethod) { + key = declaringTypeName +"::" + key; } + if (designerConstants.ContainsKey (key) && !instructions.ContainsKey (i)) + instructions.Add(i, designerConstants [key]); } } - if (instructions.Count > 0) - LogMessage ($" Fixing up {body.Method.FullName}"); - foreach (var i in instructions) + } + if (instructions.Count > 0) + logMessage ($" Fixing up {body.Method.FullName}"); + foreach (var i in instructions) + { + var newCode = Extensions.CreateLoadArraySizeOrOffsetInstruction (i.Value); + logMessage ($" Replacing {i.Key}"); + logMessage ($" With {newCode}"); + processor.Replace(i.Key, newCode); + } + } + + Dictionary BuildResourceDesignerFieldLookup (TypeDefinition type) + { + var output = new Dictionary (); + foreach (TypeDefinition definition in type.NestedTypes) + { + foreach (FieldDefinition field in definition.Fields) { - var newCode = Extensions.CreateLoadArraySizeOrOffsetInstruction (i.Value); - LogMessage ($" Replacing {i.Key}"); - LogMessage ($" With {newCode}"); - processor.Replace(i.Key, newCode); + string key = $"{definition.Name}::{field.Name}"; + if (!output.ContainsKey (key)) + output.Add(key, int.Parse (field.Constant?.ToString () ?? "0", CultureInfo.InvariantCulture)); } } + return output; + } - internal override bool ProcessAssemblyDesigner (AssemblyDefinition assembly) + void ClearDesignerClass (TypeDefinition designer, bool completely = false) + { + logMessage ($" TryRemoving {designer.FullName}"); + // for each of the nested types clear all but the + // int[] fields. + if (!completely) { + for (int i = designer.NestedTypes.Count -1; i >= 0; i--) { + var nestedType = designer.NestedTypes [i]; + RemoveFieldsFromType (nestedType, designer.Module); + if (nestedType.Fields.Count == 0) { + // no fields we do not need this class at all. + designer.NestedTypes.RemoveAt (i); + } + } + RemoveUpdateIdValues (designer); + } else { + designer.NestedTypes.Clear (); + } + designer.Fields.Clear (); + designer.Properties.Clear (); + designer.CustomAttributes.Clear (); + designer.Interfaces.Clear (); + designer.Events.Clear (); + } + + void FixType (TypeDefinition type, TypeDefinition localDesigner) + { + foreach (MethodDefinition method in type.Methods) { - if (mainDesigner == null) - return false; - if (MonoAndroidHelper.IsFrameworkAssembly (assembly)) - return false; + if (!method.HasBody) + continue; + FixBody (method.Body, localDesigner); + } + foreach (PropertyDefinition property in type.Properties) + { + if (property.GetMethod != null && property.GetMethod.HasBody) + { + FixBody (property.GetMethod.Body, localDesigner); + } + if (property.SetMethod != null && property.SetMethod.HasBody) + { + FixBody (property.SetMethod.Body, localDesigner); + } + } + foreach (TypeDefinition nestedType in type.NestedTypes) + { + FixType (nestedType, localDesigner); + } + } - LogMessage ($" Fixing up {assembly.Name.Name}"); - TypeDefinition localDesigner = null; - CustomAttribute designerAttribute; - if (assembly != mainAssembly) { - LogMessage ($" {assembly.Name.Name} is not the main assembly. "); - if (!FindResourceDesigner (assembly, mainApplication: false, designer: out localDesigner, designerAttribute: out designerAttribute)) { - Context.LogMessage ($" {assembly.Name.Name} does not have a designer file."); - return false; - } - } else { - LogMessage ($" {assembly.Name.Name} is the main assembly. "); - localDesigner = mainDesigner; - designerAttribute = mainDesignerAttribute; + void FixupAssemblyTypes (AssemblyDefinition assembly, TypeDefinition designer) + { + foreach (ModuleDefinition module in assembly.Modules) + { + foreach (TypeDefinition type in module.Types) + { + if (type.FullName == designer.FullName) + continue; + FixType (type, designer); + } + } + } + + void RemoveFieldsFromType (TypeDefinition type, ModuleDefinition module) + { + for (int i = type.Fields.Count - 1; i >= 0; i--) { + var field = type.Fields [i]; + if (field.FieldType.IsArray) { + continue; } + logMessage ($"Removing {type.Name}::{field.Name}"); + type.Fields.RemoveAt (i); + } + } - LogMessage ($" {assembly.Name.Name} has designer {localDesigner.FullName}."); + void RemoveUpdateIdValues (TypeDefinition type) + { + foreach (var method in type.Methods) { + if (method.Name.Contains ("UpdateIdValues")) { + FixUpdateIdValuesBody (method); + } else { + FixBody (method.Body, type); + } + } - FixupAssemblyTypes (assembly, localDesigner); + foreach (var nestedType in type.NestedTypes) { + RemoveUpdateIdValues (nestedType); + } + } - ClearDesignerClass (localDesigner); - if (designerAttribute != null) { - assembly.CustomAttributes.Remove (designerAttribute); + void FixUpdateIdValuesBody (MethodDefinition method) + { + List finalInstructions = new List (); + Collection instructions = method.Body.Instructions; + for (int i = 0; i < method.Body.Instructions.Count-1; i++) { + Instruction instruction = instructions[i]; + string line = instruction.ToString (); + bool found = line.Contains ("Int32[]") || instruction.OpCode == OpCodes.Ret; + if (!found) { + method.Body.Instructions.Remove (instruction); + i--; } - return true; } } } 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 84e68d8bf6f..9b8870a8295 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 @@ -198,18 +198,6 @@ <_TrimmerCustomSteps Include="$(_AndroidLinkerCustomStepAssembly)" Type="MonoDroid.Tuner.FixAbstractMethodsStep" /> - <_TrimmerCustomSteps - Condition=" '$(AndroidLinkResources)' == 'true' " - Include="$(_AndroidLinkerCustomStepAssembly)" - AfterStep="CleanStep" - Type="MonoDroid.Tuner.RemoveResourceDesignerStep" - /> - <_TrimmerCustomSteps - Condition=" '$(AndroidLinkResources)' == 'true' " - Include="$(_AndroidLinkerCustomStepAssembly)" - AfterStep="CleanStep" - Type="MonoDroid.Tuner.GetAssembliesStep" - /> <_TrimmerCustomSteps Condition=" '$(AndroidUseDesignerAssembly)' == 'true' " Include="$(_AndroidLinkerCustomStepAssembly)" @@ -259,6 +247,7 @@ diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/PostTrimmingPipeline.cs b/src/Xamarin.Android.Build.Tasks/Tasks/PostTrimmingPipeline.cs index 7a33ea3c7be..f7b91cfb1ba 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/PostTrimmingPipeline.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/PostTrimmingPipeline.cs @@ -29,6 +29,8 @@ public class PostTrimmingPipeline : AndroidTask public bool AddKeepAlives { get; set; } + public bool AndroidLinkResources { get; set; } + public bool Deterministic { get; set; } public override bool RunTask () @@ -66,6 +68,13 @@ public override bool RunTask () }, (msg) => Log.LogDebugMessage (msg))); } + if (AndroidLinkResources) { + var allAssemblies = new List (Assemblies.Length); + foreach (var item in Assemblies) { + allAssemblies.Add (resolver.GetAssembly (item.ItemSpec)); + } + steps.Add (new RemoveResourceDesignerStep (allAssemblies, (msg) => Log.LogDebugMessage (msg))); + } foreach (var item in Assemblies) { var assembly = resolver.GetAssembly (item.ItemSpec); 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 c9157be3712..a79d667bb18 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj @@ -53,12 +53,12 @@ + -