Skip to content

Commit d48cf04

Browse files
authored
Changes: radekdoulik/apkdiff@3a479d...dc6cfc * radekdoulik/apkdiff@dc6cfc4: Update the handling of an unknown option * radekdoulik/apkdiff@3b825e6: Handle unknown option * radekdoulik/apkdiff@8975470: Support aab packages * radekdoulik/apkdiff@8122150: Add new regression options * radekdoulik/apkdiff@fbac824: Use thousand separator in regression prints * radekdoulik/apkdiff@e5aef10: Count regression failures and report the count * radekdoulik/apkdiff@11ba628: Added assembly size regression testing * radekdoulik/apkdiff@e7ee8e9: Added apk size regression testing implementation * radekdoulik/apkdiff@b959951: Set platform target * radekdoulik/apkdiff@a74d022: Fix white space * radekdoulik/apkdiff@3afd5a7: Print also percentage in package size difference * radekdoulik/apkdiff@fd8d3e0: Show assemblies and shared libraries total differences in summary * radekdoulik/apkdiff@130598f: Add section sizes example * radekdoulik/apkdiff@b1ee9a6: Update LibZipSharp to newer version * radekdoulik/apkdiff@d84b35e: Compare shared libraries section sizes * radekdoulik/apkdiff@3a3ceb3: Added another example output * radekdoulik/apkdiff@4e094f5: Add initial assembly comparison * radekdoulik/apkdiff@7efe903: Compare shared libraries symbols and their sizes * radekdoulik/apkdiff@fcac80f: Improve csproj file Besides the other changes we want the regression testing. Example output: $ apkdiff.exe --test-apk-size-regression=51200 --test-assembly-size-regression=51200 Xamarin.Forms_Performance_Integration-Signed-Release.apkdesc bin\TestDebug\Xamarin.Forms_Performance_Integration-Signed.apk Size difference in bytes ([*1] apk1 only, [*2] apk2 only): + 26,749,952 assemblies/Mono.Android.dll Error: apkdiff: Assembly 'assemblies/Mono.Android.dll' size increase 26,749,952 is 26,698,752 bytes more than the threshold 51,200. + 14,655,424 assemblies/Mono.Android.pdb *2 + 2,423,388 lib/x86/libmonosgen-2.0.so + 2,413,568 assemblies/mscorlib.dll Error: apkdiff: Assembly 'assemblies/mscorlib.dll' size increase 2,413,568 is 2,362,368 bytes more than the threshold 51,200. + 2,025,076 classes.dex + 1,755,580 lib/armeabi-v7a/libmonosgen-2.0.so + 1,683,644 assemblies/mscorlib.pdb *2 + 1,511,544 assemblies/Xamarin.Android.Support.v7.AppCompat.dll Error: apkdiff: Assembly 'assemblies/Xamarin.Android.Support.v7.AppCompat.dll' size increase 1,511,544 is 1,460,344 bytes more than the threshold 51,200. ... Summary: + 43,001,792 Assemblies 303.25% (of 14,180,480) + 10,653,224 Shared libraries 89.86% (of 11,855,444) + 66,973,060 Package size difference 318.80% (of 21,007,581) Error: apkdiff: PackageSize increase 66,973,060 is 66,921,860 bytes more than the threshold 51,200. apk1 size: 21,007,581 bytes, apk2 size: 87,980,641 bytes. Error: apkdiff: Size regression occured, 39 check(s) failed.
1 parent 2d25ea6 commit d48cf04

File tree

7 files changed

+533
-16
lines changed

7 files changed

+533
-16
lines changed

tools/apkdiff/ApkDescription.cs

Lines changed: 114 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88

99
namespace apkdiff {
1010

11-
struct FileInfo {
12-
public long Size;
11+
struct FileProperties : ISizeProvider {
12+
public long Size { get; set; }
1313
}
1414

1515
[DataContract (Namespace = "apk")]
@@ -19,11 +19,15 @@ public class ApkDescription {
1919
string Comment;
2020

2121
[DataMember]
22-
long PackageSize;
22+
public long PackageSize { get; protected set; }
2323
string PackagePath;
2424

25+
ZipArchive Archive;
26+
2527
[DataMember]
26-
readonly Dictionary<string, FileInfo> Entries = new Dictionary<string, FileInfo> ();
28+
readonly Dictionary<string, FileProperties> Entries = new Dictionary<string, FileProperties> ();
29+
30+
Dictionary<string, (long Difference, long OriginalTotal)> totalDifferences = new Dictionary<string, (long, long)> ();
2731

2832
public static ApkDescription Load (string path)
2933
{
@@ -54,23 +58,23 @@ public static ApkDescription Load (string path)
5458

5559
void LoadApk (string path)
5660
{
57-
var zip = ZipArchive.Open (path, FileMode.Open);
61+
Archive = ZipArchive.Open (path, FileMode.Open);
5862

5963
if (Program.Verbose)
6064
Program.ColorWriteLine ($"Loading apk '{path}'", ConsoleColor.Yellow);
6165

6266
PackageSize = new System.IO.FileInfo (path).Length;
6367
PackagePath = path;
6468

65-
foreach (var entry in zip) {
69+
foreach (var entry in Archive) {
6670
var name = entry.FullName;
6771

6872
if (Entries.ContainsKey (name)) {
6973
Program.Warning ("Duplicate APK file entry: {name}");
7074
continue;
7175
}
7276

73-
Entries [name] = new FileInfo { Size = (long)entry.Size };
77+
Entries [name] = new FileProperties { Size = (long)entry.Size };
7478

7579
if (Program.Verbose)
7680
Program.ColorWriteLine ($" {entry.Size,12} {name}", ConsoleColor.Gray);
@@ -103,29 +107,85 @@ void SaveDescription (string path)
103107
}
104108
}
105109

106-
void PrintDifference (string key, long diff, string comment = null)
110+
static ConsoleColor PrintDifferenceStart (string key, long diff, string comment = null, string padding = null)
107111
{
108-
var color = diff > 0 ? ConsoleColor.Red : ConsoleColor.Green;
109-
Program.ColorWrite ($" {diff:+;-;+}{Math.Abs (diff),12}", color);
112+
var color = diff == 0 ? ConsoleColor.Gray : diff > 0 ? ConsoleColor.Red : ConsoleColor.Green;
113+
Program.ColorWrite ($"{padding} {diff:+;-;+}{Math.Abs (diff),12:#,0}", color);
110114
Program.ColorWrite ($" {key}", ConsoleColor.Gray);
111-
Program.ColorWriteLine (comment, color);
115+
Program.ColorWrite (comment, color);
116+
117+
return color;
118+
}
119+
120+
static public void PrintDifference (string key, long diff, string comment = null, string padding = null)
121+
{
122+
PrintDifferenceStart (key, diff, comment, padding);
123+
Console.WriteLine ();
124+
}
125+
126+
static public void PrintDifference (string key, long diff, long orig, string comment = null, string padding = null)
127+
{
128+
var color = PrintDifferenceStart (key, diff, comment, padding);
129+
130+
if (orig != 0)
131+
Program.ColorWrite ($" {(float)diff/orig:0.00%} (of {orig:#,0})", color);
132+
133+
Console.WriteLine ();
134+
}
135+
136+
void AddToTotal (string entry, long size)
137+
{
138+
var entryDiff = EntryDiff.ForExtension (Path.GetExtension (entry));
139+
if (entryDiff == null)
140+
return;
141+
142+
var diffType = entryDiff.Name;
143+
if (!totalDifferences.ContainsKey (diffType))
144+
totalDifferences.Add (diffType, (0, size));
145+
else {
146+
var info = totalDifferences [diffType];
147+
totalDifferences [diffType] = (info.Difference, info.OriginalTotal + size);
148+
}
149+
}
150+
151+
bool AddToDifference (string entry, long diff, out EntryDiff entryDiff)
152+
{
153+
entryDiff = EntryDiff.ForExtension (Path.GetExtension (entry));
154+
155+
if (entryDiff == null)
156+
return false;
157+
158+
var diffType = entryDiff.Name;
159+
if (!totalDifferences.ContainsKey (diffType))
160+
totalDifferences.Add (diffType, (diff, 0));
161+
else {
162+
var info = totalDifferences [diffType];
163+
totalDifferences [diffType] = (info.Difference + diff, info.OriginalTotal);
164+
}
165+
166+
return true;
112167
}
113168

114169
public void Compare (ApkDescription other)
115170
{
116171
var keys = Entries.Keys.Union (other.Entries.Keys);
117172
var differences = new Dictionary<string, long> ();
118173
var singles = new HashSet<string> ();
174+
var comparingApks = Archive != null && other.Archive != null;
119175

120176
Program.ColorWriteLine ("Size difference in bytes ([*1] apk1 only, [*2] apk2 only):", ConsoleColor.Yellow);
121177

122-
foreach (var key in Entries.Keys) {
178+
foreach (var entry in Entries) {
179+
var key = entry.Key;
123180
if (other.Entries.ContainsKey (key)) {
124-
differences [key] = other.Entries [key].Size - Entries [key].Size;
181+
var otherEntry = other.Entries [key];
182+
differences [key] = otherEntry.Size - Entries [key].Size;
125183
} else {
126184
differences [key] = -Entries [key].Size;
127185
singles.Add (key);
128186
}
187+
188+
AddToTotal (key, Entries [key].Size);
129189
}
130190

131191
foreach (var key in other.Entries.Keys) {
@@ -140,14 +200,53 @@ public void Compare (ApkDescription other)
140200
if (diff.Value == 0)
141201
continue;
142202

143-
PrintDifference (diff.Key, diff.Value, singles.Contains (diff.Key) ? $" *{(diff.Value > 0 ? 2 : 1)}" : null);
203+
var single = singles.Contains (diff.Key);
204+
205+
PrintDifference (diff.Key, diff.Value, single ? $" *{(diff.Value > 0 ? 2 : 1)}" : null);
206+
207+
EntryDiff entryDiff;
208+
if (!AddToDifference (diff.Key, diff.Value, out entryDiff))
209+
continue;
210+
211+
if (Program.AssemblyRegressionThreshold != 0 && entryDiff is AssemblyDiff && diff.Value > Program.AssemblyRegressionThreshold) {
212+
Program.Error ($"Assembly '{diff.Key}' size increase {diff.Value:#,0} is {diff.Value - Program.AssemblyRegressionThreshold:#,0} bytes more than the threshold {Program.AssemblyRegressionThreshold:#,0}.");
213+
Program.RegressionCount ++;
214+
}
215+
216+
if (comparingApks && !single)
217+
CompareEntries (new KeyValuePair<string, FileProperties> (diff.Key, Entries [diff.Key]), new KeyValuePair<string, FileProperties> (diff.Key, other.Entries [diff.Key]), other, entryDiff);
144218
}
145219

146220
Program.ColorWriteLine ("Summary:", ConsoleColor.Green);
147221
if (Program.Verbose)
148222
Program.ColorWriteLine ($" apk1: {PackageSize,12} {PackagePath}\n apk2: {other.PackageSize,12} {other.PackagePath}", ConsoleColor.Gray);
149223

150-
PrintDifference ("Package size difference", other.PackageSize - PackageSize);
224+
foreach (var total in totalDifferences)
225+
PrintDifference (total.Key, total.Value.Difference, total.Value.OriginalTotal);
226+
227+
PrintDifference ("Package size difference", other.PackageSize - PackageSize, PackageSize);
228+
}
229+
230+
void CompareEntries (KeyValuePair<string, FileProperties> entry, KeyValuePair<string, FileProperties> other, ApkDescription otherApk, EntryDiff diff)
231+
{
232+
var tmpDir = Path.Combine (Path.GetTempPath (), Path.GetRandomFileName ());
233+
var tmpDirOther = Path.Combine (Path.GetTempPath (), Path.GetRandomFileName ());
234+
235+
if (Program.Verbose)
236+
Program.ColorWriteLine ($"Extracting '{entry.Key}' to {tmpDir} and {tmpDirOther} temporary directories", ConsoleColor.Gray);
237+
238+
Directory.CreateDirectory (tmpDir);
239+
240+
var zipEntry = Archive.ReadEntry (entry.Key, true);
241+
zipEntry.Extract (tmpDir, entry.Key);
242+
243+
var zipEntryOther = otherApk.Archive.ReadEntry (other.Key, true);
244+
zipEntryOther.Extract (tmpDirOther, other.Key);
245+
246+
diff.Compare (Path.Combine (tmpDir, entry.Key), Path.Combine (tmpDirOther, other.Key), " ");
247+
248+
Directory.Delete (tmpDir, true);
249+
Directory.Delete (tmpDirOther, true);
151250
}
152251
}
153252
}

tools/apkdiff/AssemblyDiff.cs

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Reflection.Metadata;
5+
using System.Reflection.PortableExecutable;
6+
7+
namespace apkdiff {
8+
public class AssemblyDiff : EntryDiff {
9+
10+
MetadataReader reader1;
11+
MetadataReader reader2;
12+
13+
public AssemblyDiff ()
14+
{
15+
}
16+
17+
public override string Name { get { return "Assemblies"; } }
18+
19+
TypeDefinition GetTypeDefinition (MetadataReader reader, TypeDefinitionHandle handle, out string fullName)
20+
{
21+
var typeDef = reader.GetTypeDefinition (handle);
22+
var name = reader.GetString (typeDef.Name);
23+
var nspace = reader.GetString (typeDef.Namespace);
24+
25+
fullName = "";
26+
27+
if (typeDef.IsNested) {
28+
string declTypeFullName;
29+
30+
GetTypeDefinition (reader, typeDef.GetDeclaringType (), out declTypeFullName);
31+
fullName += declTypeFullName + "/";
32+
}
33+
34+
if (!string.IsNullOrEmpty (nspace))
35+
fullName += nspace + ".";
36+
37+
fullName += name;
38+
39+
return typeDef;
40+
}
41+
42+
public override void Compare (string file, string other, string padding)
43+
{
44+
var per1 = new PEReader (File.OpenRead (file));
45+
var per2 = new PEReader (File.OpenRead (other));
46+
47+
reader1 = per1.GetMetadataReader ();
48+
reader2 = per2.GetMetadataReader ();
49+
50+
var types1 = new Dictionary<string, TypeDefinition> (reader1.TypeDefinitions.Count);
51+
var types2 = new Dictionary<string, TypeDefinition> (reader2.TypeDefinitions.Count);
52+
53+
string fullName;
54+
55+
foreach (var typeHandle in reader1.TypeDefinitions) {
56+
var td = GetTypeDefinition (reader1, typeHandle, out fullName);
57+
types1 [fullName] = td;
58+
}
59+
60+
foreach (var typeHandle in reader2.TypeDefinitions) {
61+
var td = GetTypeDefinition (reader2, typeHandle, out fullName);
62+
types2 [fullName] = td;
63+
}
64+
65+
foreach (var pair in types1) {
66+
if (!types2.ContainsKey (pair.Key)) {
67+
Console.WriteLine ($"{padding} - Type {pair.Key}");
68+
} else
69+
CompareTypes (types1 [pair.Key], types2 [pair.Key], padding + " ");
70+
}
71+
72+
foreach (var pair in types2) {
73+
if (!types1.ContainsKey (pair.Key)) {
74+
Console.WriteLine ($"{padding} + Type {pair.Key}");
75+
}
76+
}
77+
}
78+
79+
string GetTypeName (MetadataReader reader, EntityHandle handle)
80+
{
81+
string fullName = "";
82+
83+
if (handle.Kind == HandleKind.TypeDefinition) {
84+
GetTypeDefinition (reader, (TypeDefinitionHandle) handle, out fullName);
85+
86+
return fullName;
87+
}
88+
89+
if (handle.Kind != HandleKind.TypeReference)
90+
return null;
91+
92+
var typeRef = reader.GetTypeReference ((TypeReferenceHandle)handle);
93+
var nspace = reader.GetString (typeRef.Namespace);
94+
95+
if (!string.IsNullOrEmpty (nspace))
96+
fullName += nspace + ".";
97+
98+
return fullName += reader.GetString (typeRef.Name);
99+
}
100+
101+
Dictionary<string, CustomAttribute> GetCustomAttributes (MetadataReader reader, CustomAttributeHandleCollection cac)
102+
{
103+
var dict = new Dictionary<string, CustomAttribute> ();
104+
105+
foreach (var handle in cac) {
106+
var ca = reader.GetCustomAttribute (handle);
107+
var cHandle = ca.Constructor;
108+
109+
string typeName;
110+
111+
switch (cHandle.Kind) {
112+
case HandleKind.MethodDefinition:
113+
var methodDef = reader.GetMethodDefinition ((MethodDefinitionHandle)cHandle);
114+
115+
typeName = GetTypeName (reader, methodDef.GetDeclaringType ());
116+
break;
117+
case HandleKind.MemberReference:
118+
var memberDef = reader.GetMemberReference ((MemberReferenceHandle)cHandle);
119+
120+
typeName = GetTypeName (reader, memberDef.Parent);
121+
break;
122+
default:
123+
Program.Warning ($"Unexpected EntityHandle kind: {cHandle.Kind}");
124+
continue;
125+
}
126+
127+
dict [typeName] = ca;
128+
}
129+
130+
return dict;
131+
}
132+
133+
void CompareCustomAttributes (CustomAttributeHandleCollection cac1, CustomAttributeHandleCollection cac2, string padding)
134+
{
135+
var dict1 = GetCustomAttributes (reader1, cac1);
136+
var dict2 = GetCustomAttributes (reader2, cac2);
137+
138+
foreach (var pair in dict1) {
139+
if (!dict2.ContainsKey (pair.Key)) {
140+
Console.WriteLine ($"{padding} - CustomAttribute {pair.Key}");
141+
}
142+
}
143+
144+
foreach (var pair in dict2) {
145+
if (!dict1.ContainsKey (pair.Key)) {
146+
Console.WriteLine ($"{padding} + CustomAttribute {pair.Key}");
147+
}
148+
}
149+
}
150+
151+
void CompareTypes (TypeDefinition type1, TypeDefinition type2, string padding)
152+
{
153+
CompareCustomAttributes (type1.GetCustomAttributes (), type2.GetCustomAttributes (), padding);
154+
}
155+
}
156+
}

tools/apkdiff/EntryDiff.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using System;
2+
3+
namespace apkdiff {
4+
public abstract class EntryDiff {
5+
6+
public abstract string Name { get; }
7+
8+
public static EntryDiff ForExtension (string extension)
9+
{
10+
switch (extension) {
11+
case ".dll":
12+
return new AssemblyDiff ();
13+
case ".so":
14+
return new SharedLibraryDiff ();
15+
}
16+
17+
return null;
18+
}
19+
20+
public abstract void Compare (string file, string other, string padding = null);
21+
}
22+
}

tools/apkdiff/ISizeProvider.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
namespace apkdiff {
2+
public interface ISizeProvider {
3+
long Size { get; }
4+
}
5+
}

0 commit comments

Comments
 (0)