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.
- checksums.yaml +4 -4
- data/.github/PULL_REQUEST_TEMPLATE.md +3 -0
- data/.rubocop.yml +21 -0
- data/Gemfile.lock +29 -26
- data/README.md +74 -14
- data/Resources/App Thinning Size Report CP.txt +218 -0
- data/Resources/Images/android_instant_size_report.png +0 -0
- data/Resources/Images/app_thinning_size_report.png +0 -0
- data/Resources/app.aab +0 -0
- data/Resources/testKey1 +0 -0
- data/danger-app_size_report.gemspec +6 -4
- data/lib/app_size_report/gem_version.rb +1 -1
- data/lib/app_size_report/plugin.rb +330 -56
- data/lib/converter/helper/android_utils.rb +74 -0
- data/lib/converter/helper/json_converter.rb +1 -0
- data/lib/converter/helper/memory_size.rb +1 -0
- data/lib/converter/helper/result_factory.rb +2 -9
- data/lib/converter/models/android_variant_model.rb +28 -0
- data/lib/converter/models/app_size_model.rb +4 -0
- data/lib/converter/models/device_model.rb +3 -0
- data/lib/converter/models/variant_model.rb +2 -0
- data/lib/converter/parser/app_size_parser.rb +1 -0
- data/lib/converter/parser/model_parser.rb +1 -0
- data/lib/converter/parser/report_parser.rb +1 -0
- data/lib/converter/parser/variant_descriptor_parser.rb +1 -0
- data/lib/converter/parser/variant_parser.rb +2 -0
- data/spec/app_size_report_spec.rb +34 -6
- data/spec/spec_helper.rb +1 -0
- metadata +23 -10
- /data/Resources/{App Thinning Size Report.txt → App Thinning Size Report CPClip.txt} +0 -0
@@ -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.
|
21
|
+
# app_size_report.flag_ios_violations(
|
17
22
|
# report_path,
|
18
23
|
# build_type: 'App',
|
19
|
-
#
|
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.
|
32
|
+
# app_size_report.flag_ios_violations(
|
29
33
|
# report_path,
|
30
34
|
# build_type: 'Clip',
|
31
|
-
#
|
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.
|
43
|
+
# app_size_report.flag_ios_violations(
|
40
44
|
# report_path,
|
41
45
|
# build_type: 'Clip',
|
42
|
-
#
|
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
|
-
#
|
58
|
-
#
|
59
|
-
# @
|
60
|
-
#
|
61
|
-
#
|
62
|
-
#
|
63
|
-
#
|
64
|
-
#
|
65
|
-
#
|
66
|
-
#
|
67
|
-
#
|
68
|
-
#
|
69
|
-
#
|
70
|
-
#
|
71
|
-
#
|
72
|
-
#
|
73
|
-
#
|
74
|
-
#
|
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
|
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
|
-
|
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,
|
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 >
|
118
|
-
message "The size limit was set to
|
119
|
-
size_limit =
|
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 =
|
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
|
-
|
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 << "| :-: | :-: | :-: | :-: | :-: | :-:
|
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
|
@@ -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
|
|