11using System ;
22using System . Collections . Generic ;
3+ using System . Globalization ;
4+ using System . Linq ;
5+ using System . Text ;
6+ using Xamarin . Android . Tools ;
37
48namespace ApplicationUtility ;
59
@@ -8,6 +12,13 @@ namespace ApplicationUtility;
812[ AspectReporter ( typeof ( PackageBase ) ) ]
913class 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}
0 commit comments