Skip to content

Commit c80dfff

Browse files
authored
[One .NET] Fix RemoveResourceDesignerStep in .NET 6 (#6696)
Context: #6427 The current implementation of the `RemoveResourceDesignerStep` linker step does not work under .NET 6. This is mostly down to the fact that we didn't `override` the require methods to make it work in that environment. Reworks `RemoveResourceDesignerStep` to split some of the functionality out into a new `LinkDesignerBase` base class. `LinkDesignerBase` will be used in the future by the Resource Assembly linker step #6427. `.apk` size difference in a Basic Android Application shows a effectively no reduction in Package size. This is a single App Head project with no additional libraries or Nuget references: Size difference in bytes ([*1] apk1 only, [*2] apk2 only): - 596 assemblies/assemblies.blob - 760 lib/x86/libaot-DotNet6AndroidBasic.dll.so - 764 lib/armeabi-v7a/libaot-DotNet6AndroidBasic.dll.so - 864 lib/x86_64/libaot-DotNet6AndroidBasic.dll.so - 5,032 lib/arm64-v8a/libaot-DotNet6AndroidBasic.dll.so Summary: - 596 Other entries -0.03% (of 2,347,758) + 0 Dalvik executables 0.00% (of 333,284) - 7,420 Shared libraries -0.03% (of 23,525,172) + 0 Package size difference 0.00% (of 11,745,501) `.apk` size difference in a Basic Android app which references AndroidX, shows a Package size reduction of ~164Kb. This example includes an App Head project as well as a single Android Library project. Both projects contain 2-3 `@(AndroidResource)` items and both reference AndroidX. `Resource.Designer.cs` file is 460kb in size. Size difference in bytes ([*1] apk1 only, [*2] apk2 only): - 63,664 lib/x86/libaot-DotNet6AndroidTest.App.dll.so - 63,952 lib/x86_64/libaot-DotNet6AndroidTest.App.dll.so - 83,900 assemblies/assemblies.blob - 84,408 lib/arm64-v8a/libaot-DotNet6AndroidTest.App.dll.so - 92,340 lib/armeabi-v7a/libaot-DotNet6AndroidTest.App.dll.so Summary: - 83,900 Other entries -2.83% (of 2,965,109) + 0 Dalvik executables 0.00% (of 959,460) - 304,364 Shared libraries -1.28% (of 23,781,188) - 163,840 Package size difference -1.30% (of 12,620,512) `.apk` size difference for a Basic Maui application shows a Package size reduction of ~168Kb. This is just a standard `dotnet new maui` app built for the Android platform: Size difference in bytes ([*1] apk1 only, [*2] apk2 only): + 24 lib/arm64-v8a/libaot-Microsoft.Maui.Graphics.dll.so - 48 lib/arm64-v8a/libaot-Microsoft.Maui.Controls.Xaml.dll.so - 396 lib/armeabi-v7a/libaot-Microsoft.Maui.dll.so - 448 lib/arm64-v8a/libaot-Microsoft.Maui.dll.so - 536 lib/x86/libaot-Microsoft.Maui.Controls.Compatibility.dll.so - 592 lib/x86_64/libaot-Microsoft.Maui.Controls.Compatibility.dll.so - 632 lib/armeabi-v7a/libaot-Microsoft.Maui.Controls.dll.so - 632 lib/x86/libaot-Microsoft.Maui.Controls.dll.so - 720 lib/x86_64/libaot-Microsoft.Maui.Controls.dll.so - 4,492 lib/x86/libaot-Microsoft.Maui.dll.so - 4,544 lib/x86_64/libaot-Microsoft.Maui.dll.so - 4,568 lib/arm64-v8a/libaot-Microsoft.Maui.Controls.Compatibility.dll.so - 4,628 lib/armeabi-v7a/libaot-Microsoft.Maui.Controls.Compatibility.dll.so - 4,792 lib/arm64-v8a/libaot-Microsoft.Maui.Controls.dll.so - 26,676 lib/x86/libaot-BasicMauiApp.dll.so - 27,000 lib/x86_64/libaot-BasicMauiApp.dll.so - 30,772 lib/armeabi-v7a/libaot-BasicMauiApp.dll.so - 31,096 lib/arm64-v8a/libaot-BasicMauiApp.dll.so - 124,129 assemblies/assemblies.blob Summary: - 124,129 Other entries -1.18% (of 10,513,973) + 0 Dalvik executables 0.00% (of 6,459,432) - 142,548 Shared libraries -0.28% (of 51,768,960) - 167,936 Package size difference -0.57% (of 29,604,197) For Android applications which make use of lots of resources, these changes can also impact startup times. The follow are the start up improvements on the Android Application and Library projects, both of which use AndroidX. | Before (ms) | After (ms) | Δ (%) | Notes | | ----------: | ----------: | --------: | ------------------------------------ | | 188.500 | 176.150 | -6.55% ✓ | defaults; 32-bit build | | 174.150 | 175.100 | +0.54% ✗ | defaults; profiled AOT; 32-bit build | | 187.550 | 178.300 | -4.93% ✓ | defaults; full AOT; 32-bit build | | 173.250 | 174.050 | +0.46% ✗ | defaults; 64-bit build | | 186.400 | 176.450 | -5.34% ✓ | defaults; profiled AOT; 64-bit build | | 181.350 | 174.400 | -3.83% ✓ | defaults; full AOT; 64-bit build | ~~ Known Issues ~~ `RemoveResourceDesignerStep` doesn't work properly with array resources; consider: int iconDimen = Resource.Styleable.AlertDialog[Resource.Styleable.AlertDialog_buttonIconDimen]; The `RemoveResourceDesignerStep` will *remove* the `Resource.Styleable.AlertDialog` field, but the field is still accessed (?!), resulting in a `BadImageFormatException` at runtime: android.runtime.JavaProxyThrowable: System.BadImageFormatException: Could not resolve field token 0x0400052b
1 parent 51320b7 commit c80dfff

File tree

4 files changed

+188
-123
lines changed

4 files changed

+188
-123
lines changed
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
using Mono.Cecil;
2+
using Mono.Linker;
3+
using Mono.Linker.Steps;
4+
using System;
5+
using System.Linq;
6+
using Xamarin.Android.Tasks;
7+
using System.Collections.Generic;
8+
using Mono.Cecil.Cil;
9+
using System.Text.RegularExpressions;
10+
#if ILLINK
11+
using Microsoft.Android.Sdk.ILLink;
12+
#endif
13+
14+
15+
namespace MonoDroid.Tuner {
16+
public abstract class LinkDesignerBase : BaseStep {
17+
public virtual void LogMessage (string message)
18+
{
19+
Context.LogMessage (message);
20+
}
21+
22+
public virtual AssemblyDefinition Resolve (AssemblyNameReference name)
23+
{
24+
return Context.Resolve (name);
25+
}
26+
27+
protected bool FindResourceDesigner (AssemblyDefinition assembly, bool mainApplication, out TypeDefinition designer, out CustomAttribute designerAttribute)
28+
{
29+
string designerFullName = null;
30+
designer = null;
31+
designerAttribute = null;
32+
foreach (CustomAttribute attribute in assembly.CustomAttributes)
33+
{
34+
if (attribute.AttributeType.FullName == "Android.Runtime.ResourceDesignerAttribute")
35+
{
36+
designerAttribute = attribute;
37+
if (attribute.HasProperties)
38+
{
39+
foreach (var p in attribute.Properties)
40+
{
41+
if (p.Name == "IsApplication" && (bool)p.Argument.Value == (mainApplication ? mainApplication : (bool)p.Argument.Value))
42+
{
43+
designerFullName = attribute.ConstructorArguments[0].Value.ToString ();
44+
break;
45+
}
46+
}
47+
}
48+
break;
49+
50+
}
51+
}
52+
if (string.IsNullOrEmpty(designerFullName))
53+
return false;
54+
55+
foreach (ModuleDefinition module in assembly.Modules)
56+
{
57+
foreach (TypeDefinition type in module.Types)
58+
{
59+
if (type.FullName == designerFullName)
60+
{
61+
designer = type;
62+
return true;
63+
}
64+
}
65+
}
66+
return false;
67+
}
68+
69+
protected void ClearDesignerClass (TypeDefinition designer)
70+
{
71+
LogMessage ($" TryRemoving {designer.FullName}");
72+
designer.NestedTypes.Clear ();
73+
designer.Methods.Clear ();
74+
designer.Fields.Clear ();
75+
designer.Properties.Clear ();
76+
designer.CustomAttributes.Clear ();
77+
designer.Interfaces.Clear ();
78+
designer.Events.Clear ();
79+
}
80+
protected Dictionary<string, int> BuildResourceDesignerFieldLookup (TypeDefinition type)
81+
{
82+
var output = new Dictionary<string, int> ();
83+
foreach (TypeDefinition definition in type.NestedTypes)
84+
{
85+
foreach (FieldDefinition field in definition.Fields)
86+
{
87+
string key = $"{definition.Name}::{field.Name}";
88+
if (!output.ContainsKey (key))
89+
output.Add(key, int.Parse (field.Constant?.ToString () ?? "0"));
90+
}
91+
}
92+
return output;
93+
}
94+
95+
protected void FixType (TypeDefinition type, TypeDefinition localDesigner)
96+
{
97+
foreach (MethodDefinition method in type.Methods)
98+
{
99+
if (!method.HasBody)
100+
continue;
101+
FixBody (method.Body, localDesigner);
102+
}
103+
foreach (PropertyDefinition property in type.Properties)
104+
{
105+
if (property.GetMethod != null && property.GetMethod.HasBody)
106+
{
107+
FixBody (property.GetMethod.Body, localDesigner);
108+
}
109+
if (property.SetMethod != null && property.SetMethod.HasBody)
110+
{
111+
FixBody (property.SetMethod.Body, localDesigner);
112+
}
113+
}
114+
foreach (TypeDefinition nestedType in type.NestedTypes)
115+
{
116+
FixType (nestedType, localDesigner);
117+
}
118+
}
119+
120+
protected void FixupAssemblyTypes (AssemblyDefinition assembly, TypeDefinition designer)
121+
{
122+
foreach (ModuleDefinition module in assembly.Modules)
123+
{
124+
foreach (TypeDefinition type in module.Types)
125+
{
126+
if (type.FullName == designer.FullName)
127+
continue;
128+
FixType (type, designer);
129+
}
130+
}
131+
}
132+
133+
protected override void ProcessAssembly (AssemblyDefinition assembly)
134+
{
135+
LoadDesigner ();
136+
137+
var action = Annotations.HasAction (assembly) ? Annotations.GetAction (assembly) : AssemblyAction.Skip;
138+
if (action == AssemblyAction.Delete)
139+
return;
140+
141+
if (ProcessAssemblyDesigner (assembly)) {
142+
if (action == AssemblyAction.Skip || action == AssemblyAction.Copy)
143+
Annotations.SetAction (assembly, AssemblyAction.Save);
144+
}
145+
}
146+
147+
internal abstract bool ProcessAssemblyDesigner (AssemblyDefinition assemblyDefinition);
148+
protected abstract void LoadDesigner ();
149+
protected abstract void FixBody (MethodBody body, TypeDefinition designer);
150+
}
151+
}

src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/RemoveResourceDesignerStep.cs

Lines changed: 26 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,23 @@
77
using System.Collections.Generic;
88
using Mono.Cecil.Cil;
99
using System.Text.RegularExpressions;
10+
#if ILLINK
11+
using Microsoft.Android.Sdk.ILLink;
12+
#endif
1013

1114
namespace MonoDroid.Tuner
1215
{
13-
public class RemoveResourceDesignerStep : BaseStep
16+
public class RemoveResourceDesignerStep : LinkDesignerBase
1417
{
1518
TypeDefinition mainDesigner = null;
1619
AssemblyDefinition mainAssembly = null;
1720
CustomAttribute mainDesignerAttribute;
1821
Dictionary<string, int> designerConstants;
1922
Regex opCodeRegex = new Regex (@"([\w]+): ([\w]+) ([\w.]+) ([\w:./]+)");
20-
21-
protected override void Process ()
23+
protected override void LoadDesigner ()
2224
{
25+
if (mainAssembly != null)
26+
return;
2327
// resolve the MainAssembly Resource designer TypeDefinition
2428
AndroidLinkConfiguration config = AndroidLinkConfiguration.GetInstance (Context);
2529
if (config == null)
@@ -31,95 +35,26 @@ protected override void Process ()
3135
}
3236
}
3337
if (mainDesigner == null) {
34-
Context.LogMessage ($" Main Designer not found.");
38+
LogMessage ($" Main Designer not found.");
3539
return;
3640
}
37-
Context.LogMessage ($" Main Designer found {mainDesigner.FullName}.");
41+
LogMessage ($" Main Designer found {mainDesigner.FullName}.");
3842
designerConstants = BuildResourceDesignerFieldLookup (mainDesigner);
3943
}
4044

4145
protected override void EndProcess ()
4246
{
4347
if (mainDesigner != null) {
44-
Context.LogMessage ($" Setting Action on {mainAssembly.Name} to Save.");
48+
LogMessage ($" Setting Action on {mainAssembly.Name} to Save.");
4549
Annotations.SetAction (mainAssembly, AssemblyAction.Save);
4650
}
4751
}
4852

49-
bool FindResourceDesigner (AssemblyDefinition assembly, bool mainApplication, out TypeDefinition designer, out CustomAttribute designerAttribute)
50-
{
51-
string designerFullName = null;
52-
designer = null;
53-
designerAttribute = null;
54-
foreach (CustomAttribute attribute in assembly.CustomAttributes)
55-
{
56-
if (attribute.AttributeType.FullName == "Android.Runtime.ResourceDesignerAttribute")
57-
{
58-
designerAttribute = attribute;
59-
if (attribute.HasProperties)
60-
{
61-
foreach (var p in attribute.Properties)
62-
{
63-
if (p.Name == "IsApplication" && (bool)p.Argument.Value == (mainApplication ? mainApplication : (bool)p.Argument.Value))
64-
{
65-
designerFullName = attribute.ConstructorArguments[0].Value.ToString ();
66-
break;
67-
}
68-
}
69-
}
70-
break;
71-
72-
}
73-
}
74-
if (string.IsNullOrEmpty(designerFullName))
75-
return false;
76-
77-
foreach (ModuleDefinition module in assembly.Modules)
78-
{
79-
foreach (TypeDefinition type in module.Types)
80-
{
81-
if (type.FullName == designerFullName)
82-
{
83-
designer = type;
84-
return true;
85-
}
86-
}
87-
}
88-
return false;
89-
}
90-
91-
Dictionary<string, int> BuildResourceDesignerFieldLookup (TypeDefinition type)
92-
{
93-
var output = new Dictionary<string, int> ();
94-
foreach (TypeDefinition definition in type.NestedTypes)
95-
{
96-
foreach (FieldDefinition field in definition.Fields)
97-
{
98-
string key = $"{definition.Name}::{field.Name}";
99-
if (!output.ContainsKey (key))
100-
output.Add(key, int.Parse (field.Constant?.ToString () ?? "0"));
101-
}
102-
}
103-
return output;
104-
}
105-
106-
void ClearDesignerClass (TypeDefinition designer)
107-
{
108-
Context.LogMessage ($" TryRemoving {designer.FullName}");
109-
designer.NestedTypes.Clear ();
110-
designer.Methods.Clear ();
111-
designer.Fields.Clear ();
112-
designer.Properties.Clear ();
113-
designer.CustomAttributes.Clear ();
114-
designer.Interfaces.Clear ();
115-
designer.Events.Clear ();
116-
}
117-
118-
void FixBody (MethodBody body, TypeDefinition localDesigner)
53+
protected override void FixBody (MethodBody body, TypeDefinition designer)
11954
{
12055
Dictionary<Instruction, int> instructions = new Dictionary<Instruction, int>();
12156
var processor = body.GetILProcessor ();
122-
string designerFullName = $"{localDesigner.FullName}/";
57+
string designerFullName = $"{designer.FullName}/";
12358
foreach (var i in body.Instructions)
12459
{
12560
string line = i.ToString ();
@@ -134,77 +69,48 @@ void FixBody (MethodBody body, TypeDefinition localDesigner)
13469
}
13570
}
13671
if (instructions.Count > 0)
137-
Context.LogMessage ($" Fixing up {body.Method.FullName}");
72+
LogMessage ($" Fixing up {body.Method.FullName}");
13873
foreach (var i in instructions)
13974
{
14075
var newCode = Extensions.CreateLoadArraySizeOrOffsetInstruction (i.Value);
141-
Context.LogMessage ($" Replacing {i.Key}");
142-
Context.LogMessage ($" With {newCode}");
76+
LogMessage ($" Replacing {i.Key}");
77+
LogMessage ($" With {newCode}");
14378
processor.Replace(i.Key, newCode);
14479
}
14580
}
14681

147-
void FixType (TypeDefinition type, TypeDefinition localDesigner)
148-
{
149-
foreach (MethodDefinition method in type.Methods)
150-
{
151-
if (!method.HasBody)
152-
continue;
153-
FixBody (method.Body, localDesigner);
154-
}
155-
foreach (PropertyDefinition property in type.Properties)
156-
{
157-
if (property.GetMethod != null && property.GetMethod.HasBody)
158-
{
159-
FixBody (property.GetMethod.Body, localDesigner);
160-
}
161-
if (property.SetMethod != null && property.SetMethod.HasBody)
162-
{
163-
FixBody (property.SetMethod.Body, localDesigner);
164-
}
165-
}
166-
foreach (TypeDefinition nestedType in type.NestedTypes)
167-
{
168-
FixType (nestedType, localDesigner);
169-
}
170-
}
171-
172-
protected override void ProcessAssembly (AssemblyDefinition assembly)
82+
internal override bool ProcessAssemblyDesigner (AssemblyDefinition assembly)
17383
{
17484
if (mainDesigner == null)
175-
return;
85+
return false;
17686
var fileName = assembly.Name.Name + ".dll";
17787
if (MonoAndroidHelper.IsFrameworkAssembly (fileName))
178-
return;
88+
return false;
17989

180-
Context.LogMessage ($" Fixing up {assembly.Name.Name}");
90+
LogMessage ($" Fixing up {assembly.Name.Name}");
18191
TypeDefinition localDesigner = null;
18292
CustomAttribute designerAttribute;
18393
if (assembly != mainAssembly) {
184-
Context.LogMessage ($" {assembly.Name.Name} is not the main assembly. ");
94+
LogMessage ($" {assembly.Name.Name} is not the main assembly. ");
18595
if (!FindResourceDesigner (assembly, mainApplication: false, designer: out localDesigner, designerAttribute: out designerAttribute)) {
18696
Context.LogMessage ($" {assembly.Name.Name} does not have a designer file.");
187-
return;
97+
return false;
18898
}
18999
} else {
190-
Context.LogMessage ($" {assembly.Name.Name} is the main assembly. ");
100+
LogMessage ($" {assembly.Name.Name} is the main assembly. ");
191101
localDesigner = mainDesigner;
192102
designerAttribute = mainDesignerAttribute;
193103
}
194104

195-
Context.LogMessage ($" {assembly.Name.Name} has designer {localDesigner.FullName}.");
105+
LogMessage ($" {assembly.Name.Name} has designer {localDesigner.FullName}.");
106+
107+
FixupAssemblyTypes (assembly, localDesigner);
196108

197-
foreach (var mod in assembly.Modules) {
198-
foreach (var type in mod.Types) {
199-
if (type == localDesigner)
200-
continue;
201-
FixType (type, localDesigner);
202-
}
203-
}
204109
ClearDesignerClass (localDesigner);
205110
if (designerAttribute != null) {
206111
assembly.CustomAttributes.Remove (designerAttribute);
207112
}
113+
return true;
208114
}
209115
}
210116
}

src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.DefaultProperties.targets

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,9 @@
9191
<InvariantGlobalization Condition="'$(InvariantGlobalization)' == ''">false</InvariantGlobalization>
9292
<StartupHookSupport Condition="'$(StartupHookSupport)' == ''">false</StartupHookSupport>
9393
<UseNativeHttpHandler Condition="'$(UseNativeHttpHandler)' == ''">true</UseNativeHttpHandler>
94-
<_AggressiveAttributeTrimming Condition="'$(_AggressiveAttributeTrimming)' == ''">true</_AggressiveAttributeTrimming>
94+
<_AggressiveAttributeTrimming Condition="'$(_AggressiveAttributeTrimming)' == ''">true</_AggressiveAttributeTrimming>
9595
<NullabilityInfoContextSupport Condition="'$(NullabilityInfoContextSupport)' == ''">false</NullabilityInfoContextSupport>
96-
<BuiltInComInteropSupport Condition="'$(BuiltInComInteropSupport)' == ''">false</BuiltInComInteropSupport>
96+
<BuiltInComInteropSupport Condition="'$(BuiltInComInteropSupport)' == ''">false</BuiltInComInteropSupport>
9797
<!-- Verify DI trimmability at development-time, but turn the validation off for production/trimmed builds. -->
9898
<VerifyDependencyInjectionOpenGenericServiceTrimmability Condition="'$(VerifyDependencyInjectionOpenGenericServiceTrimmability)' == '' and '$(PublishTrimmed)' == 'true'">false</VerifyDependencyInjectionOpenGenericServiceTrimmability>
9999
<VerifyDependencyInjectionOpenGenericServiceTrimmability Condition="'$(VerifyDependencyInjectionOpenGenericServiceTrimmability)' == ''">true</VerifyDependencyInjectionOpenGenericServiceTrimmability>

0 commit comments

Comments
 (0)