danger-app_size_report 0.0.3 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
|