Skip to content

Commit c54ea3b

Browse files
committed
Much better assembly store reporting in package reporter
1 parent b56d8e7 commit c54ea3b

File tree

3 files changed

+236
-6
lines changed

3 files changed

+236
-6
lines changed

tools/apput/src/Common/Utilities.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,11 @@ public static string GetZipEntryFileName (string entryName)
6464

6565
return entryName.Substring (idx + 1);
6666
}
67+
68+
public static string SizeToString (ulong val)
69+
{
70+
// TODO: return both bytes and a "human readable" value (kb, mb etc)
71+
// TODO: format the byte size according to the current culture
72+
return val.ToString ();
73+
}
6774
}

tools/apput/src/Reporters/ApplicationPackageReporter.cs

Lines changed: 221 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Globalization;
4+
using System.Linq;
5+
using System.Text;
6+
using Xamarin.Android.Tools;
37

48
namespace ApplicationUtility;
59

@@ -8,6 +12,13 @@ namespace ApplicationUtility;
812
[AspectReporter (typeof (PackageBase))]
913
class ApplicationPackageReporter : BaseReporter
1014
{
15+
sealed class AssemblyInfo
16+
{
17+
public ApplicationAssembly Assembly;
18+
public AndroidTargetArch Architecture;
19+
public bool IsSatellite;
20+
}
21+
1122
readonly ApplicationPackage package;
1223

1324
protected override string AspectName => ApplicationPackage.AspectName;
@@ -90,15 +101,219 @@ protected override void DoReport (ReportForm form, uint sectionLevel)
90101
AddSection ("Assembly stores", 2);
91102
if (package.AssemblyStores == null || package.AssemblyStores.Count == 0) {
92103
AddText ("No assembly stores found");
93-
} else {
94-
AddText ($"Application contains the following {package.AssemblyStores.Count} {GetCountable (Countable.AssemblyStore, package.AssemblyStores.Count)}.");
104+
return;
105+
}
106+
107+
AddText ($"Application contains {package.AssemblyStores.Count} {GetCountable (Countable.AssemblyStore, package.AssemblyStores.Count)}.");
108+
AddParagraph ();
109+
110+
if (package.AssemblyStores.Count == 1) {
111+
AddLabeledItem ("Architecture", package.AssemblyStores[0].Architecture.ToString ());
112+
113+
// Take a shortcut here, there's just one store so we can use store reporter directly
114+
var storeReporter = new AssemblyStoreReporter (package.AssemblyStores[0], ReportDoc);
115+
storeReporter.Report (ReportForm.Subsection, sectionLevel: 2);
116+
return;
117+
}
118+
119+
// To make output more compact, we group assemblies by name. For that reason we cannot use the designated
120+
// assembly reporter.
121+
var architectures = new HashSet<AndroidTargetArch> ();
122+
var assembliesByName = new SortedDictionary<string, List<AssemblyInfo>> (StringComparer.Ordinal);
123+
var assemblyCounts = new SortedDictionary<string, int> (StringComparer.Ordinal);
124+
125+
foreach (AssemblyStore store in package.AssemblyStores) {
126+
architectures.Add (store.Architecture);
127+
assemblyCounts[store.Architecture.ToString ()] = store.Assemblies.Count;
128+
129+
foreach (var kvp in store.Assemblies) {
130+
ApplicationAssembly asm = kvp.Value;
131+
bool isSatellite = asm.Name.Contains ('/');
132+
string name = isSatellite switch {
133+
true => GetModifiedSatelliteName (asm.Name),
134+
false => asm.Name,
135+
};
136+
137+
if (!assembliesByName.TryGetValue (name, out List<AssemblyInfo>? assemblyInfos) || assemblyInfos == null) {
138+
assemblyInfos = new List<AssemblyInfo> ();
139+
assembliesByName[name] = assemblyInfos;
140+
}
141+
assemblyInfos.Add (
142+
new AssemblyInfo {
143+
Architecture = store.Architecture,
144+
Assembly = asm,
145+
IsSatellite = isSatellite,
146+
}
147+
);
148+
}
149+
}
150+
AddLabeledItem ("Architectures", String.Join (", ", architectures));
151+
AddLabeledItem ("Assembly count", String.Join (", ", assemblyCounts.Select (kvp => $"{kvp.Value} ({kvp.Key})")));
152+
153+
ReportDoc.BeginList ();
154+
foreach (var kvp in assembliesByName) {
155+
List<AssemblyInfo> infos = kvp.Value;
156+
if (infos.Count == 0) {
157+
continue;
158+
}
159+
160+
ReportDoc.StartListItem ($"{infos[0].Assembly.Name}").BeginList();
95161

96-
foreach (AssemblyStore store in package.AssemblyStores) {
97-
AddSection ($"Architecture: {store.Architecture}");
162+
ReportDoc.AddLabeledListItem ("Architectures", String.Join (", ", infos.Select (info => info.Architecture.ToString ()).Distinct ()));
163+
if (infos[0].IsSatellite) {
164+
ReportDoc.AddLabeledListItem ("Satellite", "yes");
165+
ReportDoc.AddLabeledListItem ("Culture", GetCultureInfo (infos));
166+
}
98167

99-
var storeReporter = new AssemblyStoreReporter (store, ReportDoc);
100-
storeReporter.Report (ReportForm.Subsection, sectionLevel: 2);
168+
ReportDoc.AddLabeledListItem ("Compressed", GetCompressedValue (infos));
169+
if (infos.Any (info => info.Assembly.IsCompressed)) {
170+
ReportDoc.AddLabeledListItem ("Compressed size", GetCompressedSizeValue (infos));
101171
}
172+
ReportDoc.AddLabeledListItem ("Size", GetSizeValue (infos));
173+
ReportDoc.AddLabeledListItem ("Name hash", GetNameHashValue (infos));
174+
ReportDoc.AddLabeledListItem ("Ignore on load", GetIgnoreOnLoadValue (infos));
175+
176+
ReportDoc.EndList ().EndListItem ();
177+
}
178+
ReportDoc.AddNewline ();
179+
ReportDoc.EndList ();
180+
181+
string GetCultureInfo (List<AssemblyInfo> infos)
182+
{
183+
var cultures = new HashSet<string> (StringComparer.Ordinal);
184+
var sb = new StringBuilder ();
185+
foreach (AssemblyInfo info in infos) {
186+
string cultureName = GetSatelliteCultureName (info.Assembly.Name);
187+
sb.Clear ();
188+
sb.Append (cultureName);
189+
190+
var ci = !String.IsNullOrEmpty (cultureName) ? CultureInfo.GetCultureInfo (cultureName): null;
191+
if (ci != null) {
192+
sb.Append (" (");
193+
sb.Append (ci.NativeName);
194+
sb.Append ("; ");
195+
sb.Append (ci.EnglishName);
196+
sb.Append (')');
197+
}
198+
199+
cultures.Add (sb.ToString ());
200+
}
201+
202+
var cultureList = cultures.ToList ();
203+
cultureList.Sort ();
204+
return String.Join (", ", cultureList);
205+
}
206+
207+
string GetIgnoreOnLoadValue (List<AssemblyInfo> infos)
208+
{
209+
return GetAggregatedValue<bool> (
210+
infos,
211+
(AssemblyInfo info) => info.Assembly.IgnoreOnLoad,
212+
(bool v) => YesNo (v)
213+
);
214+
}
215+
216+
string GetNameHashValue (List<AssemblyInfo> infos)
217+
{
218+
return GetAggregatedValue<ulong> (
219+
infos,
220+
(AssemblyInfo info) => info.Assembly.NameHash,
221+
(ulong v) => $"0x{v:x}"
222+
);
223+
}
224+
225+
string GetSizeValue (List<AssemblyInfo> infos)
226+
{
227+
return GetAggregatedValue<ulong> (
228+
infos,
229+
(AssemblyInfo info) => info.Assembly.Size,
230+
(ulong v) => Utilities.SizeToString (v)
231+
);
102232
}
233+
234+
string GetCompressedSizeValue (List<AssemblyInfo> infos)
235+
{
236+
return GetAggregatedValue<ulong> (
237+
infos,
238+
(AssemblyInfo info) => info.Assembly.CompressedSize,
239+
(ulong v) => Utilities.SizeToString (v)
240+
);
241+
}
242+
243+
string GetCompressedValue (List<AssemblyInfo> infos)
244+
{
245+
return GetAggregatedValue<bool> (
246+
infos,
247+
(AssemblyInfo info) => info.Assembly.IsCompressed,
248+
(bool v) => YesNo (v)
249+
);
250+
}
251+
252+
bool AllIdentical<T> (List<AssemblyInfo> infos, T expected, Func<AssemblyInfo, T> getValue) where T: notnull
253+
{
254+
for (int i = 1; i < infos.Count; i++) {
255+
T val = getValue (infos[i]);
256+
if (!val.Equals (expected)) {
257+
return false;
258+
}
259+
}
260+
261+
return true;
262+
}
263+
264+
string GetAggregatedValue<T> (List<AssemblyInfo> infos, Func<AssemblyInfo, T> getValue, Func<T, string> valToString) where T: notnull
265+
{
266+
if (infos.Count == 1) {
267+
return getValue (infos[0]).ToString () ?? String.Empty;
268+
}
269+
270+
T expected = getValue (infos[0]);
271+
if (AllIdentical (infos, expected, getValue)) {
272+
return valToString (expected);
273+
}
274+
275+
var sb = new StringBuilder ();
276+
foreach (AssemblyInfo info in infos) {
277+
if (sb.Length > 0) {
278+
sb.Append ("; ");
279+
}
280+
281+
sb.Append ($"{info.Architecture.ToString ()}: {getValue (info)}");
282+
}
283+
284+
return sb.ToString ();
285+
}
286+
287+
string GetSatelliteCultureName (string fullName)
288+
{
289+
if (fullName.Length == 0) {
290+
return fullName;
291+
}
292+
293+
int idx = fullName.IndexOf ('/');
294+
if (idx < 0 || idx == fullName.Length - 1) {
295+
return fullName;
296+
}
297+
298+
return fullName.Substring (0, idx);
299+
}
300+
301+
string GetModifiedSatelliteName (string fullName)
302+
{
303+
if (fullName.Length == 0) {
304+
return fullName;
305+
}
306+
307+
int idx = fullName.IndexOf ('/');
308+
if (idx < 0 || idx == fullName.Length - 1) {
309+
return fullName;
310+
}
311+
312+
string name = fullName.Substring (idx + 1);
313+
string culture = fullName.Substring (0, idx);
314+
315+
return $"{name} ({culture})";
316+
}
317+
103318
}
104319
}

tools/apput/src/Reporters/BaseReporter.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ abstract class BaseReporter : IReporter
1515
{
1616
protected enum Countable
1717
{
18+
Architecture,
1819
Assembly,
1920
AssemblyStore,
2021
Permission,
@@ -26,6 +27,7 @@ protected enum Countable
2627
const string TargetArchitectureLabel = "Target architecture";
2728

2829
static readonly Dictionary<Countable, (string singular, string plural)> Countables = new () {
30+
{ Countable.Architecture, ("architecture", "architectures") },
2931
{ Countable.Assembly, ("assembly", "assemblies") },
3032
{ Countable.AssemblyStore, ("assembly store", "assembly stores") },
3133
{ Countable.Permission, ("permission", "permissions") },
@@ -197,6 +199,12 @@ protected MarkdownDocument AddText (string text, MarkdownTextStyle style = Markd
197199
return ReportDoc.AddText (text, style, addIndent);
198200
}
199201

202+
protected MarkdownDocument AddParagraph ()
203+
{
204+
ReportDoc.AddNewline ();
205+
return ReportDoc.AddNewline ();
206+
}
207+
200208
public MarkdownDocument AddListItemText (string text, MarkdownTextStyle style = MarkdownTextStyle.Plain)
201209
{
202210
return AddText (text, style, addIndent: false);

0 commit comments

Comments
 (0)