danger-app_size_report 0.0.3 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,102 +2,281 @@
2
2
 
3
3
  module Danger
4
4
  require 'json'
5
+ require 'open-uri'
6
+ require 'fileutils'
7
+
5
8
  require_relative '../converter/parser/report_parser'
6
9
  require_relative '../converter/helper/memory_size'
10
+ require_relative '../converter/helper/android_utils'
7
11
 
8
- # A Danger plugin for reporting iOS app size violations.
12
+ # A Danger plugin for reporting iOS and Android app size violations.
13
+ #
14
+ #
9
15
  # A valid App Thinning Size Report must be passed to the plugin
10
- # for accurate functionality.
16
+ # for accurate functionality in case of iOS.
11
17
  #
12
- # @example Report app size violations if one or more App variants
13
- # exceed 4GB.
18
+ # @example Report iOS app size violations if one or more App variants exceed 4GB.
14
19
  #
15
20
  # report_path = "/Path/to/AppSize/Report.txt"
16
- # app_size_report.flag_violations(
21
+ # app_size_report.flag_ios_violations(
17
22
  # report_path,
18
23
  # build_type: 'App',
19
- # size_limit: 4,
24
+ # limit_size: 4,
20
25
  # limit_unit: 'GB',
21
26
  # fail_on_warning: false
22
27
  # )
23
28
  #
24
- # @example Report app size violations if one or more App Clip variants
25
- # exceed 8MB.
29
+ # @example Report iOS app size violations if one or more App Clip variants exceed 8MB.
26
30
  #
27
31
  # report_path = "/Path/to/AppSize/Report.txt"
28
- # app_size_report.flag_violations(
32
+ # app_size_report.flag_ios_violations(
29
33
  # report_path,
30
34
  # build_type: 'Clip',
31
- # size_limit: 8,
35
+ # limit_size: 8,
32
36
  # limit_unit: 'MB',
33
37
  # fail_on_warning: false
34
38
  # )
35
39
  #
36
- # @example Fail PR if one or more App Clip variants exceed 8MB.
40
+ # @example Fail PR if one or more iOS App Clip variants exceed 8MB.
37
41
  #
38
42
  # report_path = "/Path/to/AppSize/Report.txt"
39
- # app_size_report.flag_violations(
43
+ # app_size_report.flag_ios_violations(
40
44
  # report_path,
41
45
  # build_type: 'Clip',
42
- # size_limit: 8,
46
+ # limit_size: 8,
43
47
  # limit_unit: 'MB',
44
48
  # fail_on_warning: true
45
49
  # )
46
50
  #
47
- # @example Get JSON string representation of app thinning size report
51
+ # @example Get JSON string representation of iOS app thinning size report
48
52
  #
49
53
  # report_path = "/Path/to/AppSize/Report.txt"
50
54
  # app_size_json = app_size_report.report_json(report_path)
51
55
  #
56
+ # @example Report Android app size violations if one or more App variants
57
+ #
58
+ # aab_path = "/Path/to/app.aab"
59
+ # ks_path = "/Path/to/keyStore"
60
+ # ks_alias = "KeyAlias"
61
+ # ks_password = "Key Password"
62
+ # ks_alias_password = "Key Alias Password"
63
+ # app_size_report.flag_android_violations(
64
+ # aab_path,
65
+ # ks_path,
66
+ # ks_alias,
67
+ # ks_password,
68
+ # ks_alias_password,
69
+ # screen_densities: ["MDPI", "HDPI", "XHDPI", "XXHDPI", "XXXHDPI"],
70
+ # languages: ["en", "de", "da", "es", "fr", "it", "nb", "nl", "sv"],
71
+ # build_type: 'App',
72
+ # limit_size: 14,
73
+ # limit_unit: 'MB',
74
+ # fail_on_warning: false
75
+ # )
76
+ #
77
+ # @example Report Android Instant app size violations if one or more App variants
78
+ #
79
+ # aab_path = "/Path/to/app.aab"
80
+ # ks_path = "/Path/to/keyStore"
81
+ # ks_alias = "KeyAlias"
82
+ # ks_password = "Key Password"
83
+ # ks_alias_password = "Key Alias Password"
84
+ # app_size_report.flag_android_violations(
85
+ # aab_path,
86
+ # ks_path,
87
+ # ks_alias,
88
+ # ks_password,
89
+ # ks_alias_password,
90
+ # screen_densities: ["MDPI", "HDPI", "XHDPI", "XXHDPI", "XXXHDPI"],
91
+ # languages: ["en", "de", "da", "es", "fr", "it", "nb", "nl", "sv"],
92
+ # build_type: 'Instant',
93
+ # limit_size: 4,
94
+ # limit_unit: 'MB',
95
+ # fail_on_warning: false
96
+ # )
97
+ #
98
+ # @example Fail PR if one or more Android Instant App variants exceed 4MB.
99
+ #
100
+ # aab_path = "/Path/to/app.aab"
101
+ # ks_path = "/Path/to/keyStore"
102
+ # ks_alias = "KeyAlias"
103
+ # ks_password = "Key Password"
104
+ # ks_alias_password = "Key Alias Password"
105
+ # app_size_report.flag_android_violations(
106
+ # aab_path,
107
+ # ks_path,
108
+ # ks_alias,
109
+ # ks_password,
110
+ # ks_alias_password,
111
+ # screen_densities: ["MDPI", "HDPI", "XHDPI", "XXHDPI", "XXXHDPI"],
112
+ # languages: ["en", "de", "da", "es", "fr", "it", "nb", "nl", "sv"],
113
+ # build_type: 'Instant',
114
+ # limit_size: 4,
115
+ # limit_unit: 'MB',
116
+ # fail_on_warning: true
117
+ # )
118
+ #
52
119
  # @see ChargePoint/danger-app_size_report
53
120
  # @tags ios, xcode, appclip, thinning, size
54
121
  #
55
122
  class DangerAppSizeReport < Plugin
56
- # Reports app size violations given a valid App Thinning Size Report.
57
- # @param [String, required] report_path
58
- # Path to valid App Thinning Size Report text file.
59
- # @param [String, optional] build_type
60
- # Specify whether the report corresponds to an App or an App Clip.
61
- # Default: 'App'
62
- # Supported values: 'App', 'Clip'
63
- # @param [Numeric, optional] size_limit
64
- # Specify the app size limit.
65
- # Default: 4
66
- # @param [String, optional] limit_unit
67
- # Specific the unit for the given size limit.
68
- # Default: 'GB'
69
- # Supported values: 'KB', 'MB', 'GB'
70
- # @param [Boolean, optional] fail_on_warning
71
- # Specify whether the PR should fail if one or more app variants
72
- # exceed the given size limit. By default, the plugin issues
73
- # a warning in this case.
74
- # Default: 'false'
123
+ # Reports IOS app size violations given a valid App Thinning Size Report.
124
+ # The plugin also warns users about variants that exceed the optimal size
125
+ # limit for cellular downloads (200 MB).
126
+ # @overload flag_ios_violations(report_path, build_type, limit_size, limit_unit, fail_on_warning)
127
+ # @param [String, required] report_path
128
+ # Path to valid App Thinning Size Report text file.
129
+ # @param [String, optional] build_type
130
+ # Specify whether the report corresponds to an App or an App Clip.
131
+ # Default: 'App'
132
+ # Supported values: 'App', 'Clip'
133
+ # @param [Numeric, optional] limit_size
134
+ # Specify the app size limit. If the build type is set to 'Clip' and the
135
+ # specified app size limit exceeds 10 MB, the 10 MB limit will be enforced
136
+ # to meet Apple's App Clip size requirements.
137
+ # Default: 4
138
+ # @param [String, optional] limit_unit
139
+ # Specific the unit for the given size limit.
140
+ # Default: 'GB'
141
+ # Supported values: 'KB', 'MB', 'GB'
142
+ # @param [Boolean, optional] fail_on_warning
143
+ # Specify whether the PR should fail if one or more app variants
144
+ # exceed the given size limit. By default, the plugin issues
145
+ # a warning in this case.
146
+ # Default: 'false'
75
147
  # @return [void]
76
148
  #
77
- def flag_violations(report_path, build_type: 'App', size_limit: 4, limit_unit: 'GB', fail_on_warning: false)
149
+ def flag_ios_violations(report_path, **kargs)
150
+ supported_kargs = %i[build_type limit_size limit_unit size_limit fail_on_warning]
151
+
152
+ # Identify any unsupported arguments passed to method
153
+ unsupported_kargs = kargs.keys - supported_kargs
154
+
155
+ raise ArgumentError, "The argument '#{unsupported_kargs[0]}' is not supported by flag_ios_violations" if unsupported_kargs.count == 1
156
+
157
+ raise ArgumentError, "The arguments #{unsupported_kargs} are not supported by flag_ios_violations" if unsupported_kargs.count > 1
158
+
159
+ # Set up optional arguments with default values if needed
160
+ build_type = kargs[:build_type] || 'App'
161
+ limit_size = kargs[:limit_size] || kargs[:size_limit] || 4
162
+ limit_unit = kargs[:limit_unit] || 'GB'
163
+ fail_on_warning = kargs[:fail_on_warning] || false
164
+
78
165
  report_text = File.read(report_path)
79
166
  variants = ReportParser.parse(report_text)
80
167
 
81
- unless %w[App Clip].include? build_type
82
- raise ArgumentError, "The 'build_type' argument only accepts the values \"App\" and \"Clip\""
83
- end
168
+ raise ArgumentError, "The 'build_type' argument only accepts the values \"App\" and \"Clip\"" unless %w[App Clip].include? build_type
84
169
 
85
- raise ArgumentError, "The 'size_limit' argument only accepts numeric values" unless size_limit.is_a? Numeric
170
+ if kargs[:limit_size]
171
+ raise ArgumentError, "The 'limit_size' argument only accepts numeric values" unless limit_size.is_a? Numeric
172
+ elsif kargs[:size_limit]
173
+ raise ArgumentError, "The 'size_limit' argument only accepts numeric values" unless limit_size.is_a? Numeric
174
+ end
86
175
 
87
176
  limit_unit.upcase!
88
- unless %w[KB MB GB].include? limit_unit
89
- raise ArgumentError, "The 'build_type' argument only accepts the values \"KB\", \"MB\" and \"GB\""
90
- end
177
+ raise ArgumentError, "The 'build_type' argument only accepts the values \"KB\", \"MB\" and \"GB\"" unless %w[KB MB GB].include? limit_unit
91
178
 
92
- unless [true, false].include? fail_on_warning
93
- raise ArgumentError, "The 'fail_on_warning' argument only accepts the values 'true' and 'false'"
94
- end
179
+ raise ArgumentError, "The 'fail_on_warning' argument only accepts the values 'true' and 'false'" unless [true, false].include? fail_on_warning
95
180
 
96
- generate_size_report_markdown(variants, build_type, size_limit, limit_unit, fail_on_warning)
181
+ generate_size_report_markdown(variants, build_type, limit_size, limit_unit, fail_on_warning)
97
182
  generate_variant_descriptors_markdown(variants)
98
183
  generate_ads_label_markdown
99
184
  end
100
185
 
186
+ # Reports Android app size violations given a valid AAB.
187
+ # @overload flag_android_violations(aab_path, ks_path, ks_alias, ks_password, ks_alias_password, screen_densities, languages, build_type, limit_size, size_limit, limit_unit, fail_on_warning)
188
+ # @param [String, required] aab_path
189
+ # Path to valid AAB file.
190
+ # @param [String, required] ks_path
191
+ # Path to valid signing key file.
192
+ # @param [String, required] ks_alias
193
+ # Alias of signing key.
194
+ # @param [String, required] ks_password
195
+ # Password of signing key.
196
+ # @param [String, required] ks_alias_password
197
+ # Alias Password of signing key.
198
+ # @param [Array, optional] screen_densities
199
+ # Array of screen densities to check APK size.
200
+ # Default: ["MDPI", "HDPI", "XHDPI", "XXHDPI", "XXXHDPI"]
201
+ # @param [Array, optional] languages
202
+ # Array of languages to check APK size.
203
+ # Default: ["en"]
204
+ # @param [String, optional] build_type
205
+ # Specify whether the report corresponds to an App, Instant.
206
+ # Default: 'App'
207
+ # Supported values: 'App', 'Instant'
208
+ # @param [Numeric, optional] limit_size
209
+ # Specify the app size limit. If the build type is set to 'Instant' and the
210
+ # specified app size limit exceeds 4 MB, the 4 MB limit will be enforced to
211
+ # meet Android Instant App size requirements.
212
+ # Default: 150
213
+ # @param [String, optional] limit_unit
214
+ # Specific the unit for the given size limit.
215
+ # Default: 'MB'
216
+ # Supported values: 'KB', 'MB', 'GB'
217
+ # @param [Boolean, optional] fail_on_warning
218
+ # Specify whether the PR should fail if one or more app variants
219
+ # exceed the given size limit. By default, the plugin issues
220
+ # a warning in this case.
221
+ # Default: 'false'
222
+ # @return [void]
223
+ #
224
+ def flag_android_violations(aab_path, ks_path, ks_alias, ks_password, ks_alias_password, **kargs)
225
+ project_root = Dir.pwd
226
+ temp_path = "#{project_root}/temp"
227
+ apks_path = "#{temp_path}/output.apks"
228
+ size_csv_path = "#{temp_path}/output.csv"
229
+ bundletool_path = "#{temp_path}/bundletool.jar"
230
+ bundletool_version = '1.8.2'
231
+ variants_limit = 25
232
+
233
+ supported_kargs = %i[screen_densities languages build_type limit_size size_limit limit_unit fail_on_warning]
234
+ unsupported_kargs = kargs.keys - supported_kargs
235
+
236
+ raise ArgumentError, "The argument #{unsupported_kargs[0]} is not supported by flag_android_violations" if unsupported_kargs.count == 1
237
+
238
+ raise ArgumentError, "The arguments #{unsupported_kargs} are not supported by flag_android_violations" if unsupported_kargs.count > 1
239
+
240
+ # Set up optional arguments with default values if needed
241
+ screen_densities = kargs[:screen_densities] || %w[MDPI HDPI XHDPI XXHDPI XXXHDPI]
242
+ languages = kargs[:languages] || ['en']
243
+ build_type = kargs[:build_type] || 'App'
244
+ limit_size = kargs[:limit_size] || kargs[:size_limit] || 150
245
+ limit_unit = kargs[:limit_unit] || 'MB'
246
+ fail_on_warning = kargs[:fail_on_warning] || false
247
+
248
+ raise ArgumentError, "The 'build_type' argument only accepts the values \"App\" and \"Instant\"" unless %w[App Instant].include? build_type
249
+
250
+ if kargs[:limit_size]
251
+ raise ArgumentError, "The 'limit_size' arguments only accepts numeric values" unless limit_size.is_a? Numeric
252
+ elsif kargs[:size_limit]
253
+ raise ArgumentError, "The 'size_limit' argument only accepts numeric values" unless limit_size.is_a? Numeric
254
+ end
255
+
256
+ limit_unit.upcase!
257
+ raise ArgumentError, "The 'limit_unit' argument only accepts the values \"KB\", \"MB\" and \"GB\"" unless %w[KB MB GB].include? limit_unit
258
+
259
+ raise ArgumentError, "The 'fail_on_warning' argument only accepts the values 'true' and 'false'" unless [true, false].include? fail_on_warning
260
+
261
+ create_temp_dir(temp_path)
262
+
263
+ unless AndroidUtils.download_bundletool(bundletool_version, bundletool_path)
264
+ clean_temp!
265
+ return
266
+ end
267
+
268
+ AndroidUtils.generate_apks(aab_path, ks_path, ks_alias, ks_password, ks_alias_password, apks_path,
269
+ bundletool_path)
270
+ AndroidUtils.generate_estimated_sizes(apks_path, size_csv_path, bundletool_path, build_type)
271
+ filtered_sizes = AndroidUtils.filter_estimated_sizes(size_csv_path, screen_densities, languages)
272
+ sorted_sizes = AndroidUtils.sort_estimated_sizes(filtered_sizes)
273
+
274
+ clean_temp!(temp_path)
275
+
276
+ generate_android_size_report_markdown(sorted_sizes, build_type, limit_size, limit_unit, fail_on_warning,
277
+ variants_limit)
278
+ end
279
+
101
280
  # Returns a JSON string representation of the given App Thinning Size Report.
102
281
  # @param [String, required] report_path
103
282
  # Path to valid App Thinning Size Report text file.
@@ -111,14 +290,105 @@ module Danger
111
290
 
112
291
  private
113
292
 
293
+ def create_temp_dir(temp_path)
294
+ Dir.mkdir temp_path
295
+ end
296
+
297
+ def clean_temp!(temp_path)
298
+ FileUtils.rm_rf(temp_path)
299
+ end
300
+
301
+ def generate_android_size_report_markdown(sorted_sizes, build_type, size_limit, limit_unit, fail_on_warning, variants_limit)
302
+ limit_size = MemorySize.new("#{size_limit}#{limit_unit}")
303
+
304
+ if build_type == 'Instant' && limit_size.megabytes > 4
305
+ message "The size limit was set to 4 MB as the given limit of #{size_limit} #{limit_unit} exceeds Android Instant App size restrictions"
306
+ size_limit = 4
307
+ limit_unit = 'MB'
308
+ limit_size.kilobytes = 4 * 1024
309
+ elsif build_type == 'App' && limit_size.megabytes > 150
310
+ message "The size limit was set to 150 MB as the given limit of #{size_limit} #{limit_unit} exceeds Android App size restrictions"
311
+ size_limit = 150
312
+ limit_unit = 'MB'
313
+ limit_size.kilobytes = 150 * 1024
314
+ end
315
+
316
+ violation_count = AndroidUtils.violations_count(sorted_sizes, limit_size.bytes)
317
+
318
+ if violation_count.positive?
319
+ if fail_on_warning
320
+ failure "The size limit of #{size_limit} #{limit_unit.upcase} has been exceeded by #{violation_count} variants"
321
+ else
322
+ warn "The size limit of #{size_limit} #{limit_unit.upcase} has been exceeded by #{violation_count} variants"
323
+ end
324
+ end
325
+
326
+ exceed_size_report = "| Under Limit | SDK | ABI | Screen Density | Language | Size (Bytes) |\n"
327
+ exceed_size_report << "| :-: | :-: | :-: | :-: | :-: | :-: |\n"
328
+
329
+ more_exceed_size_report = "| Under Limit | SDK | ABI | Screen Density | Language | Size (Bytes) |\n"
330
+ more_exceed_size_report << "| :-: | :-: | :-: | :-: | :-: | :-: |\n"
331
+
332
+ under_size_report = "| Under Limit | SDK | ABI | Screen Density | Language | Size (Bytes) |\n"
333
+ under_size_report << "| :-: | :-: | :-: | :-: | :-: | :-: |\n"
334
+
335
+ counter = sorted_sizes.length - 1
336
+ exceed_counter = 1
337
+ under_counter = 1
338
+
339
+ while counter >= 0
340
+ variant = sorted_sizes[counter]
341
+ is_violating = variant.max > limit_size.bytes ? '❌' : '✅'
342
+ variant_report = "#{is_violating} | #{variant.sdk} | #{variant.abi} | #{variant.screen_density} | #{variant.language} | #{variant.max} |\n"
343
+
344
+ if variant.max > limit_size.bytes
345
+ if exceed_counter <= variants_limit
346
+ exceed_counter += 1
347
+ exceed_size_report << variant_report
348
+ else
349
+ more_exceed_size_report << variant_report
350
+ end
351
+ elsif under_counter <= variants_limit
352
+ under_counter += 1
353
+ under_size_report << variant_report
354
+ end
355
+
356
+ counter -= 1
357
+ end
358
+
359
+ size_report = "# Android #{build_type} Size Report\n"
360
+ size_report << "### Size limit = #{size_limit} #{limit_unit.upcase}\n\n"
361
+
362
+ if violation_count.positive?
363
+ size_report << "## Variants exceeding the size limit\n\n"
364
+ size_report << exceed_size_report
365
+ size_report << "\n"
366
+
367
+ if violation_count > variants_limit
368
+ size_report << "<details>\n<summary>Click to view more violating variants!</summary>\n\n"
369
+ size_report << more_exceed_size_report
370
+ size_report << "</details>\n\n"
371
+ end
372
+ end
373
+
374
+ size_report << "## Variants under or equal to the size limit\n\n"
375
+ size_report << "<details>\n<summary>Click to expand!</summary>\n\n"
376
+
377
+ size_report << under_size_report
378
+ size_report << "</details>\n"
379
+
380
+ markdown size_report
381
+ end
382
+
114
383
  def generate_size_report_markdown(variants, build_type, size_limit, limit_unit, fail_on_warning)
115
384
  limit_size = MemorySize.new("#{size_limit}#{limit_unit}")
385
+ cellular_limit_size = MemorySize.new('200 MB')
116
386
 
117
- if build_type == 'Clip' && limit_size.megabytes > 10
118
- message "The size limit was set to 10 MB as the given limit of #{size_limit} #{limit_unit} exceeds Apple's App Clip size restrictions"
119
- size_limit = 10
387
+ if build_type == 'Clip' && limit_size.megabytes > 15
388
+ message "The size limit was set to 15 MB as the given limit of #{size_limit} #{limit_unit} exceeds Apple's App Clip size restrictions"
389
+ size_limit = 15
120
390
  limit_unit = 'MB'
121
- limit_size.kilobytes = 10 * 1024
391
+ limit_size.kilobytes = 15 * 1024
122
392
  elsif build_type == 'App' && limit_size.gigabytes > 4
123
393
  message "The size limit was set to 4 GB as the given limit of #{size_limit} #{limit_unit} exceeds Apple's App size restrictions"
124
394
  size_limit = 4
@@ -127,10 +397,10 @@ module Danger
127
397
  end
128
398
 
129
399
  flagged_variant_names = []
400
+ flagged_cellular_variant_names = []
130
401
  variants.each do |variant|
131
- if variant.app_size.uncompressed.value > limit_size.megabytes || variant.on_demand_resources_size.uncompressed.value > limit_size.megabytes
132
- flagged_variant_names.append(variant.variant)
133
- end
402
+ flagged_variant_names.append(variant.variant) if variant.app_size.uncompressed.value > limit_size.megabytes || variant.on_demand_resources_size.uncompressed.value > limit_size.megabytes
403
+ flagged_cellular_variant_names.append(variant.variant) if variant.app_size.uncompressed.value > cellular_limit_size.megabytes || variant.on_demand_resources_size.uncompressed.value > cellular_limit_size.megabytes
134
404
  end
135
405
 
136
406
  if flagged_variant_names.length.positive?
@@ -141,21 +411,25 @@ module Danger
141
411
  end
142
412
  end
143
413
 
414
+ warn 'The optimal cellular size limit of 200 MB has been exceeded by one or more variants' if flagged_cellular_variant_names.length.positive?
415
+
144
416
  size_report = "# App Thinning Size Report\n"
145
417
  size_report << "### Size limit = #{size_limit} #{limit_unit.upcase}\n\n"
146
- size_report << "| Under Limit | Variant | App Size - Compressed | App Size - Uncompressed | ODR Size - Compressed | ODR Size - Uncompressed |\n"
147
- size_report << "| :-: | :-: | :-: | :-: | :-: | :-: |\n"
418
+ size_report << "| Under Limit | Variant | Cellular Friendly | App Size - Compressed | App Size - Uncompressed | ODR Size - Compressed | ODR Size - Uncompressed |\n"
419
+ size_report << "| :-: | :-: | :-: | :-: | :-: | :-: | :-:|\n"
148
420
 
149
421
  flagged_variants_set = flagged_variant_names.to_set
422
+ flagged_cellular_variants_set = flagged_cellular_variant_names.to_set
150
423
 
151
424
  variants.each do |variant|
152
425
  is_violating = flagged_variants_set.include?(variant.variant) ? '❌' : '✅'
426
+ is_cellular_friendly = flagged_cellular_variants_set.include?(variant.variant) ? '❌' : '✅'
153
427
  app_size_compressed = "#{variant.app_size.compressed.value} #{variant.app_size.compressed.unit}"
154
428
  app_size_uncompressed = "#{variant.app_size.uncompressed.value} #{variant.app_size.uncompressed.unit}"
155
429
  odr_size_compressed = "#{variant.on_demand_resources_size.compressed.value} #{variant.on_demand_resources_size.compressed.unit}"
156
430
  odr_size_uncompressed = "#{variant.on_demand_resources_size.uncompressed.value} #{variant.on_demand_resources_size.uncompressed.unit}"
157
431
 
158
- size_report << "#{is_violating} | #{variant.variant} | #{app_size_compressed} | #{app_size_uncompressed} | #{odr_size_compressed} | #{odr_size_uncompressed} |\n"
432
+ size_report << "#{is_violating} | #{variant.variant} | #{is_cellular_friendly} | #{app_size_compressed} | #{app_size_uncompressed} | #{odr_size_compressed} | #{odr_size_uncompressed} |\n"
159
433
  end
160
434
 
161
435
  markdown size_report
@@ -178,7 +452,7 @@ module Danger
178
452
  end
179
453
 
180
454
  def generate_ads_label_markdown
181
- ads_label = 'Powered by [danger-app_size_report](https://github.com/ChargePoint)'
455
+ ads_label = 'Powered by [danger-app_size_report](https://github.com/ChargePoint/danger-app_size_report)'
182
456
  markdown ads_label
183
457
  end
184
458
  end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'csv'
4
+ require 'open3'
5
+
6
+ require_relative '../models/android_variant_model'
7
+
8
+ # Used to obtain and parse Android app size information
9
+ class AndroidUtils
10
+ def self.filter_estimated_sizes(path, screen_densities, languages)
11
+ filtered_sizes = []
12
+ CSV.foreach(path, headers: true) do |row|
13
+ sdk = row[AndroidVariant::PARSING_KEYS[:sdk]]
14
+ abi = row[AndroidVariant::PARSING_KEYS[:abi]]
15
+ screen_density = row[AndroidVariant::PARSING_KEYS[:screen_density]]
16
+ language = row[AndroidVariant::PARSING_KEYS[:language]]
17
+ texture_compression_format = row[AndroidVariant::PARSING_KEYS[:texture_compression_format]]
18
+ device_tire = row[AndroidVariant::PARSING_KEYS[:device_tire]]
19
+ min = row[AndroidVariant::PARSING_KEYS[:min]]
20
+ max = row[AndroidVariant::PARSING_KEYS[:max]]
21
+
22
+ if screen_densities.include?(screen_density) && languages.include?(language)
23
+ filtered_sizes << AndroidVariant.new(sdk, abi, screen_density, language, texture_compression_format,
24
+ device_tire, min, max)
25
+ end
26
+ end
27
+ filtered_sizes
28
+ end
29
+
30
+ def self.sort_estimated_sizes(size_arr)
31
+ size_arr.sort { |o1, o2| o1.max <=> o2.max }
32
+ end
33
+
34
+ def self.violations_count(sorted_arr, limit)
35
+ lb = -1
36
+ ub = sorted_arr.length
37
+ while ub - lb > 1
38
+ mid = (lb + ub) / 2
39
+ if sorted_arr[mid].max <= limit
40
+ lb = mid
41
+ else
42
+ ub = mid
43
+ end
44
+ end
45
+ sorted_arr.length - ub
46
+ end
47
+
48
+ def self.generate_apks(aab_path, ks_path, ks_alias, ks_password, ks_alias_password, apks_path, bundletool_path)
49
+ keystore_params = "--ks=\"#{ks_path}\" --ks-key-alias=\"#{ks_alias}\" --ks-pass=\"pass:#{ks_password}\" --key-pass=\"pass:#{ks_alias_password}\""
50
+ cmd = "java -jar #{bundletool_path} build-apks --bundle=\"#{aab_path}\" --output=\"#{apks_path}\" #{keystore_params}"
51
+ Open3.popen3(cmd) do |_, _, stderr, wait_thr|
52
+ exit_status = wait_thr.value
53
+ raise stderr.read unless exit_status.success?
54
+ end
55
+ end
56
+
57
+ def self.generate_estimated_sizes(apks_path, size_csv_path, bundletool_path, build_type)
58
+ bundletool_cmd = "java -jar #{bundletool_path} get-size total --apks=\"#{apks_path}\" --dimensions=ALL"
59
+ bundletool_cmd += ' --instant' if build_type == 'Instant'
60
+ cmd = "#{bundletool_cmd} > #{size_csv_path}"
61
+ Open3.popen3(cmd) do |_, _, stderr, wait_thr|
62
+ exit_status = wait_thr.value
63
+ raise stderr.read unless exit_status.success?
64
+ end
65
+ end
66
+
67
+ def self.download_bundletool(version, bundletool_path)
68
+ URI.open("https://github.com/google/bundletool/releases/download/#{version}/bundletool-all-#{version}.jar") do |bundletool|
69
+ File.binwrite(bundletool_path, bundletool.read)
70
+ end
71
+ rescue OpenURI::HTTPError
72
+ false
73
+ end
74
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'json'
4
4
 
5
+ # Generate pretty JSON from list of variants
5
6
  class JSONConverter
6
7
  def to_json(_options = {})
7
8
  hash = {}
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: false
2
2
 
3
3
  require_relative '../helper/json_converter'
4
+ # Defines memory size object to be used to parse the App Thinning Size Report
4
5
  class MemorySize < JSONConverter
5
6
  attr_accessor :kilobytes
6
7
 
@@ -4,6 +4,7 @@ require_relative '../parser/variant_parser'
4
4
  require_relative '../parser/variant_descriptor_parser'
5
5
  require_relative '../parser/app_size_parser'
6
6
 
7
+ # Used to parse sections of the App Thinning Size Report using different parsers
7
8
  class ResultFactory
8
9
  def self.parse(from_text: '', parser: nil)
9
10
  result = nil
@@ -16,15 +17,7 @@ class ResultFactory
16
17
  variant_descriptor_parser = VariantDescriptorParser.new(from_text)
17
18
  variant_descriptor_parser.parse
18
19
  result = variant_descriptor_parser.result
19
- when :app_on_demand_resources_size
20
- app_size_parser = AppSizeParser.new(from_text)
21
- app_size_parser.parse
22
- result = app_size_parser.result
23
- when :app_size
24
- app_size_parser = AppSizeParser.new(from_text)
25
- app_size_parser.parse
26
- result = app_size_parser.result
27
- when :on_demand_resources_size
20
+ when :app_on_demand_resources_size, :app_size, :on_demand_resources_size
28
21
  app_size_parser = AppSizeParser.new(from_text)
29
22
  app_size_parser.parse
30
23
  result = app_size_parser.result
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Android variant model
4
+ class AndroidVariant
5
+ attr_reader :sdk, :abi, :screen_density, :language, :texture_compression_format, :device_tire, :min, :max
6
+
7
+ PARSING_KEYS = {
8
+ sdk: 'SDK',
9
+ abi: 'ABI',
10
+ screen_density: 'SCREEN_DENSITY',
11
+ language: 'LANGUAGE',
12
+ texture_compression_format: 'TEXTURE_COMPRESSION_FORMAT',
13
+ device_tire: 'DEVICE_TIER',
14
+ min: 'MIN',
15
+ max: 'MAX'
16
+ }.freeze
17
+
18
+ def initialize(sdk, abi, screen_density, language, texture_compression_format, device_tire, min, max)
19
+ @sdk = sdk
20
+ @abi = abi
21
+ @screen_density = screen_density
22
+ @language = language
23
+ @texture_compression_format = texture_compression_format
24
+ @device_tire = device_tire
25
+ @min = min.to_i
26
+ @max = max.to_i
27
+ end
28
+ end
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../helper/json_converter'
4
+
5
+ # App Size Model.
6
+ # Example: 'App size: 6.6 MB compressed, 12.9 MB uncompressed'
4
7
  class AppSizeModel < JSONConverter
5
8
  attr_reader :compressed, :uncompressed
6
9
 
@@ -16,6 +19,7 @@ class AppSizeModel < JSONConverter
16
19
  end
17
20
  end
18
21
 
22
+ # Size Model
19
23
  class SizeModel < JSONConverter
20
24
  attr_reader :raw_value, :value, :unit
21
25
 
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../helper/json_converter'
4
+
5
+ # Device Model.
6
+ # Example: 'device: iPhone10,3, os-version: 14.0'
4
7
  class DeviceModel < JSONConverter
5
8
  attr_reader :device, :os_version
6
9