Skip to content

Commit

Permalink
First Pass at using Resource Designer assembly [WIP]
Browse files Browse the repository at this point in the history
Context #6310
  • Loading branch information
dellis1972 committed Dec 2, 2021
1 parent f3de57d commit edaa5d8
Show file tree
Hide file tree
Showing 27 changed files with 1,013 additions and 152 deletions.
1 change: 1 addition & 0 deletions build-tools/installers/create-installers.targets
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@
<_MSBuildFiles Include="$(MSBuildSrcDir)\Xamarin.Android.CSharp.targets" ExcludeFromAndroidNETSdk="true" />
<_MSBuildFiles Include="$(MSBuildSrcDir)\Xamarin.Android.D8.targets" />
<_MSBuildFiles Include="$(MSBuildSrcDir)\Xamarin.Android.Designer.targets" />
<_MSBuildFiles Include="$(MSBuildSrcDir)\Xamarin.Android.Resource.Designer.targets" />
<_MSBuildFiles Include="$(MSBuildSrcDir)\Xamarin.Android.DesignTime.targets" />
<_MSBuildFiles Include="$(MSBuildSrcDir)\Xamarin.Android.DX.targets" ExcludeFromAndroidNETSdk="true" />
<_MSBuildFiles Include="$(MSBuildSrcDir)\Xamarin.Android.EmbeddedResource.targets" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
using System;
using System.Collections.Generic;
using System.Linq;

using Mono.Cecil;
using Mono.Cecil.Cil;

using Java.Interop.Tools.Cecil;

using Mono.Linker;
using Mono.Linker.Steps;

using Mono.Tuner;
#if ILLINK
using Microsoft.Android.Sdk.ILLink;
#endif // ILLINK

namespace MonoDroid.Tuner
{
public class FixLegacyResourceDesignerStep : LinkDesignerBase
{
#if ILLINK
protected override void Process ()
{
cache = Context;
}
#else
public FixLegacyResourceDesignerStep (IMetadataResolver cache)
{
this.cache = cache;
}

readonly
#endif
IMetadataResolver cache;
AssemblyDefinition designerAssembly = null;
TypeDefinition designerType = null;
Dictionary<string, MethodDefinition> lookup;

protected override void EndProcess ()
{
if (designerAssembly != null) {
LogMessage ($" Setting Action on {designerAssembly.Name} to Link.");
Annotations.SetAction (designerAssembly, AssemblyAction.Link);
}
}

protected override void LoadDesigner ()
{
if (designerAssembly != null)
return;
var designerNameAssembly = AssemblyNameReference.Parse ("Xamarin.Android.Resource.Designer, Version=1.0.0.0");
try {
designerAssembly = Resolve (designerNameAssembly);
} catch (Mono.Cecil.AssemblyResolutionException) {
} catch (System.IO.FileNotFoundException) {
}
if (designerAssembly == null) {
LogMessage ($" Did not find Xamarin.Android.Resource.Designer");
return;
}
designerType = designerAssembly.MainModule.GetTypes ().First (x => x.FullName == "Xamarin.Android.Resource.Designer.Resource");
lookup = BuildResourceDesignerPropertyLookup (designerType);
return;
}
internal override bool ProcessAssemblyDesigner (AssemblyDefinition assembly)
{
if (designerAssembly == null) {
LogMessage ($" Not using Xamarin.Android.Resource.Designer");
return false;
}

if (!FindResourceDesigner (assembly, mainApplication: false, out TypeDefinition designer, out CustomAttribute designerAttribute)) {
LogMessage ($" {assembly.Name.Name} has no designer. ");
return false;
}

LogMessage ($" {assembly.Name.Name} has a designer. ");
if (designer.BaseType.FullName == "Xamarin.Android.Resource.Designer.Resource") {
LogMessage ($" {assembly.Name.Name} has already been processed. ");
return false;
}

assembly.MainModule.AssemblyReferences.Add (designerAssembly.Name);
var importedDesignerType = assembly.MainModule.ImportReference (designerType.Resolve ());

// now replace all ldsfld with a call to the property get_ method.
FixupAssemblyTypes (assembly, designer);

// then clean out the designer.
ClearDesignerClass (designer);
designer.BaseType = importedDesignerType;
return true;
}

Dictionary<string, MethodDefinition> BuildResourceDesignerPropertyLookup (TypeDefinition type)
{
LogMessage ($" Building Designer Lookups for {type.FullName}");
var output = new Dictionary<string, MethodDefinition> ();
foreach (TypeDefinition definition in type.NestedTypes)
{
foreach (PropertyDefinition property in definition.Properties)
{
string key = $"{definition.Name}::{property.Name}";
if (!output.ContainsKey (key)) {
output.Add(key, property.GetMethod);
}
}
}
return output;
}

protected override void FixBody (MethodBody body, TypeDefinition designer)
{
// replace
// IL_0068: ldsfld int32 Xamarin.Forms.Platform.Android.Resource/Layout::Toolbar
// with
// call int32 Xamarin.Forms.Platform.Android.Resource/Layout::get_Toolbar()
string designerFullName = $"{designer.FullName}/";
var processor = body.GetILProcessor ();
Dictionary<Instruction, Instruction> instructions = new Dictionary<Instruction, Instruction>();
foreach (var i in body.Instructions)
{
string line = i.ToString ();
int idx = line.IndexOf (designerFullName, StringComparison.OrdinalIgnoreCase);
if (idx >= 0) {
string key = line.Substring (idx + designerFullName.Length);
if (lookup.TryGetValue (key, out MethodDefinition method)) {
var importedMethod = designer.Module.ImportReference (method);
var newIn = Instruction.Create (OpCodes.Call, importedMethod);
instructions.Add (i, newIn);
}
}
}
if (instructions.Count > 0)
LogMessage ($" Fixing up {body.Method.FullName}");
foreach (var i in instructions)
{
LogMessage ($" Replacing {i.Key}");
LogMessage ($" With {i.Value}");
processor.Replace(i.Key, i.Value);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
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.Text.RegularExpressions;
#if ILLINK
using Microsoft.Android.Sdk.ILLink;
#endif


namespace MonoDroid.Tuner {
public abstract class LinkDesignerBase : BaseStep {
public virtual void LogMessage (string message)
{
Context.LogMessage (message);
}

public virtual AssemblyDefinition Resolve (AssemblyNameReference name)
{
return Context.Resolve (name);
}

protected 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 (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 (string.IsNullOrEmpty(designerFullName))
return false;

foreach (ModuleDefinition module in assembly.Modules)
{
foreach (TypeDefinition type in module.Types)
{
if (type.FullName == designerFullName)
{
designer = type;
return true;
}
}
}
return false;
}

protected void ClearDesignerClass (TypeDefinition designer)
{
LogMessage ($" TryRemoving {designer.FullName}");
designer.NestedTypes.Clear ();
designer.Methods.Clear ();
designer.Fields.Clear ();
designer.Properties.Clear ();
designer.CustomAttributes.Clear ();
designer.Interfaces.Clear ();
designer.Events.Clear ();
}

protected void FixType (TypeDefinition type, TypeDefinition localDesigner)
{
foreach (MethodDefinition method in type.Methods)
{
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);
}
}

protected 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);
}
}
}

protected override void ProcessAssembly (AssemblyDefinition assembly)
{
LoadDesigner ();

var action = Annotations.HasAction (assembly) ? Annotations.GetAction (assembly) : AssemblyAction.Skip;
if (action == AssemblyAction.Delete)
return;

if (ProcessAssemblyDesigner (assembly)) {
if (action == AssemblyAction.Skip || action == AssemblyAction.Copy)
Annotations.SetAction (assembly, AssemblyAction.Save);
}
}

internal abstract bool ProcessAssemblyDesigner (AssemblyDefinition assemblyDefinition);
protected abstract void LoadDesigner ();
protected abstract void FixBody (MethodBody body, TypeDefinition designer);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ static Pipeline CreatePipeline (LinkerOptions options)
pipeline.AppendStep (new RemoveResources (options.I18nAssemblies)); // remove collation tables
// end monodroid specific

if (options.UseDesignerAssembly)
pipeline.AppendStep (new FixLegacyResourceDesignerStep (cache));
pipeline.AppendStep (new FixAbstractMethodsStep (cache));
pipeline.AppendStep (new MonoDroidMarkStep (cache));
pipeline.AppendStep (new SweepStep ());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,6 @@ class LinkerOptions
public bool PreserveJniMarshalMethods { get; set; }
public bool DeterministicOutput { get; set; }
public bool LinkResources { get; set; }
public bool UseDesignerAssembly { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ protected override bool IsSdk (string assemblyName)
{
return assemblyName.Equals ("Java.Interop", StringComparison.Ordinal)
|| assemblyName.Equals ("Java.Interop.GenericMarshaler", StringComparison.Ordinal)
|| assemblyName.Equals ("Xamarin.Android.Resource.Designer", StringComparison.Ordinal)
|| base.IsSdk (assemblyName);
}
}
Expand Down
Loading

0 comments on commit edaa5d8

Please sign in to comment.