danger-apkstats 0.1.0 → 0.3.1

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.
@@ -9,12 +9,12 @@ Gem::Specification.new do |spec|
9
9
  spec.version = Apkstats::VERSION
10
10
  spec.authors = ["Jumpei Matsuda"]
11
11
  spec.email = ["jmatsu.drm@gmail.com"]
12
- spec.description = "To inpsect android application file with danger."
12
+ spec.description = "To inspect android application file with danger."
13
13
  spec.summary = "This is a danger plugin to inspect android application file."
14
14
  spec.homepage = "https://github.com/jmatsu/danger-apkstats"
15
15
  spec.license = "MIT"
16
16
 
17
- spec.files = `git ls-files`.split($/)
17
+ spec.files = `git ls-files | grep -v 'fixture/'`.split($/)
18
18
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
19
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
20
  spec.require_paths = ["lib"]
@@ -22,8 +22,7 @@ Gem::Specification.new do |spec|
22
22
  spec.add_runtime_dependency "danger-plugin-api", "~> 1.0"
23
23
 
24
24
  # General ruby development
25
- spec.add_development_dependency "bundler", "~> 1.3"
26
- spec.add_development_dependency "rake", "~> 10.0"
25
+ spec.add_development_dependency "rake", "~> 13.0"
27
26
 
28
27
  # Testing support
29
28
  spec.add_development_dependency "rspec", "~> 3.4"
@@ -5,7 +5,7 @@ module Apkstats::Command
5
5
  include Apkstats::Command::Executable
6
6
 
7
7
  def initialize(opts)
8
- @command_path = opts[:command_path] || "#{ENV.fetch('ANDROID_HOME')}/tools/bin/apkanalyzer"
8
+ @command_path = opts.fetch(:command_path)
9
9
  end
10
10
 
11
11
  def file_size(apk_filepath)
@@ -37,6 +37,17 @@ module Apkstats::Command
37
37
  run_command("manifest", "target-sdk", apk_filepath)
38
38
  end
39
39
 
40
+ def method_reference_count(apk_filepath)
41
+ ApkAnalyzer.parse_reference_to_map(run_command("dex", "references", apk_filepath))
42
+ .values
43
+ .map(&:to_i)
44
+ .inject(:+)
45
+ end
46
+
47
+ def dex_count(apk_filepath)
48
+ ApkAnalyzer.parse_reference_to_map(run_command("dex", "references", apk_filepath)).size
49
+ end
50
+
40
51
  def self.parse_permissions(command_output)
41
52
  command_output.split(/\r?\n/).map { |s| to_permission(s) }
42
53
  end
@@ -59,11 +70,19 @@ module Apkstats::Command
59
70
  ::Apkstats::Entity::Feature.new(name, not_required: kind == "not-required", implied_reason: kind == "implied:" && tail)
60
71
  end
61
72
 
73
+ def self.parse_reference_to_map(command_output)
74
+ command_output.split(/\r?\n/).each_with_object({}) do |s, acc|
75
+ dex_file, method_count = s.strip.split(/\t/, 2)
76
+ acc[dex_file] = method_count
77
+ end
78
+ end
79
+
62
80
  private
63
81
 
64
82
  def run_command(*args)
65
- out, err, status = Open3.capture3("#{command_path} #{args.join(' ')}")
83
+ out, err, status = Open3.capture3(command_path, *args)
66
84
  raise err unless status.success?
85
+
67
86
  out.rstrip
68
87
  end
69
88
  end
@@ -21,6 +21,8 @@ module Apkstats::Command
21
21
  # permissions: Array<String>,
22
22
  # min_sdk: String,
23
23
  # target_sdk: String,
24
+ # method_reference_count: Integer,
25
+ # dex_count: Integer,
24
26
  # },
25
27
  # other: {
26
28
  # file_size: Integer,
@@ -30,6 +32,8 @@ module Apkstats::Command
30
32
  # permissions: Array<String>,
31
33
  # min_sdk: String,
32
34
  # target_sdk: String,
35
+ # method_reference_count: Integer,
36
+ # dex_count: Integer,
33
37
  # },
34
38
  # diff: {
35
39
  # file_size: Integer,
@@ -48,6 +52,8 @@ module Apkstats::Command
48
52
  # },
49
53
  # min_sdk: Array<String>,
50
54
  # target_sdk: Array<String>,
55
+ # method_reference_count: Integer,
56
+ # dex_count: Integer,
51
57
  # }
52
58
  # }
53
59
  #
@@ -10,10 +10,12 @@ module Apkstats::Entity
10
10
  permissions
11
11
  min_sdk
12
12
  target_sdk
13
+ method_reference_count
14
+ dex_count
13
15
  ).freeze
14
16
 
15
17
  # Integer
16
- attr_accessor :file_size, :download_size
18
+ attr_accessor :file_size, :download_size, :method_reference_count, :dex_count
17
19
 
18
20
  # String
19
21
  attr_accessor :min_sdk, :target_sdk
@@ -63,5 +63,15 @@ module Apkstats::Entity
63
63
  # String
64
64
  [@base[__method__], @other[__method__]].uniq
65
65
  end
66
+
67
+ def method_reference_count
68
+ # Integer
69
+ @base[__method__].to_i - @other[__method__].to_i
70
+ end
71
+
72
+ def dex_count
73
+ # Integer
74
+ @base[__method__].to_i - @other[__method__].to_i
75
+ end
66
76
  end
67
77
  end
@@ -84,6 +84,7 @@ module Apkstats::Entity
84
84
 
85
85
  def eql?(other)
86
86
  return if !other || other.class == Features
87
+
87
88
  other.values == values
88
89
  end
89
90
 
@@ -70,6 +70,7 @@ module Apkstats::Entity
70
70
 
71
71
  def eql?(other)
72
72
  return if !other || other.class == Permissions
73
+
73
74
  other.values == values
74
75
  end
75
76
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Apkstats
4
- VERSION = "0.1.0"
4
+ VERSION = "0.3.1"
5
5
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "gem_version"
3
4
  require_relative "helper/bytes"
4
5
 
5
6
  require_relative "entity/apk_info"
@@ -51,28 +52,46 @@ module Danger
51
52
  #
52
53
  # apkstats.target_sdk
53
54
  #
55
+ # @example Show the methods reference count of your apk file.
56
+ #
57
+ # apkstats.method_reference_count
58
+ #
59
+ # @example Show the number of dex of your apk file.
60
+ #
61
+ # apkstats.dex_count
62
+ #
54
63
  # @see Jumpei Matsuda/danger-apkstats
55
64
  # @tags android, apk_stats
56
65
  #
57
66
  class DangerApkstats < Plugin
67
+ class Error < StandardError; end
68
+
69
+ # @deprecated this field have no effect
58
70
  COMMAND_TYPE_MAP = {
59
71
  apk_analyzer: Apkstats::Command::ApkAnalyzer,
60
72
  }.freeze
61
73
 
62
74
  private_constant(:COMMAND_TYPE_MAP)
63
75
 
64
- # *Optional*
65
- # A command type to be run.
66
- # One of keys of COMMAND_TYPE_MAP
76
+ # @deprecated this field have no effect
77
+ # This will be removed in further versions
67
78
  #
68
- # @return [Symbol, Nil] _
79
+ # @return [String] _
69
80
  attr_accessor :command_type
70
81
 
71
- # *Optional*
72
- # A custom command path
82
+ # *Required*
83
+ # A path of apkanalyzer command
73
84
  #
74
- # @return [Symbol, Nil] _
75
- attr_accessor :command_path
85
+ # @return [String] _
86
+ attr_accessor :apkanalyzer_path
87
+
88
+ # @deprecated Use apkanalyzer_path instead
89
+ # @return [String] _
90
+ alias command_path apkanalyzer_path
91
+
92
+ # @deprecated Use apkanalyzer_path= instead
93
+ # @return [String] _
94
+ alias command_path= apkanalyzer_path=
76
95
 
77
96
  # *Required*
78
97
  # Your target apk filepath.
@@ -80,7 +99,7 @@ module Danger
80
99
  # @return [String]
81
100
  attr_accessor :apk_filepath
82
101
 
83
- # rubocop:disable Metrics/AbcSize
102
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
84
103
 
85
104
  # Get stats of two apk files and calculate diffs between them.
86
105
  #
@@ -90,84 +109,102 @@ module Danger
90
109
  def compare_with(other_apk_filepath, do_report: true)
91
110
  raise "apk filepaths must be specified" if apk_filepath.nil? || apk_filepath.empty?
92
111
 
93
- base_apk = Apkstats::Entity::ApkInfo.new(command, apk_filepath)
94
- other_apk = Apkstats::Entity::ApkInfo.new(command, other_apk_filepath)
112
+ base_apk = Apkstats::Entity::ApkInfo.new(apkanalyzer_command, apk_filepath)
113
+ other_apk = Apkstats::Entity::ApkInfo.new(apkanalyzer_command, other_apk_filepath)
95
114
 
96
- return {
115
+ result = {
97
116
  base: base_apk.to_h,
98
117
  other: base_apk.to_h,
99
118
  diff: Apkstats::Entity::ApkInfoDiff.new(base_apk, other_apk).to_h,
100
- }.tap do |result|
101
- break unless do_report
119
+ }
102
120
 
103
- diff = result[:diff]
121
+ return result unless do_report
104
122
 
105
- md = +"### Apk comparision results" << "\n\n"
106
- md << "Property | Summary" << "\n"
107
- md << ":--- | :---" << "\n"
123
+ diff = result[:diff]
108
124
 
109
- diff[:min_sdk].tap do |min_sdk|
110
- break if min_sdk.size == 1
125
+ md = +"### Apk comparison results" << "\n\n"
126
+ md << "Property | Summary" << "\n"
127
+ md << ":--- | :---" << "\n"
111
128
 
112
- md << "Min SDK Change | Before #{min_sdk[1]} / After #{min_sdk[0]}" << "\n"
113
- end
129
+ diff[:min_sdk].tap do |min_sdk|
130
+ break if min_sdk.size == 1
114
131
 
115
- diff[:target_sdk].tap do |target_sdk|
116
- break if target_sdk.size == 1
132
+ md << "Min SDK Change | Before #{min_sdk[1]} / After #{min_sdk[0]}" << "\n"
133
+ end
117
134
 
118
- md << "Target SDK Change | Before #{target_sdk[1]} / After #{target_sdk[0]}" << "\n"
119
- end
135
+ diff[:target_sdk].tap do |target_sdk|
136
+ break if target_sdk.size == 1
120
137
 
121
- result[:base][:file_size].tap do |file_size|
122
- size = Apkstats::Helper::Bytes.from_b(file_size)
138
+ md << "Target SDK Change | Before #{target_sdk[1]} / After #{target_sdk[0]}" << "\n"
139
+ end
123
140
 
124
- md << "New File Size | #{size.to_b} Bytes. (#{size.to_mb} MB " << "\n"
125
- end
141
+ result[:base][:file_size].tap do |file_size|
142
+ size = Apkstats::Helper::Bytes.from_b(file_size)
126
143
 
127
- diff[:file_size].tap do |file_size|
128
- size = Apkstats::Helper::Bytes.from_b(file_size)
144
+ md << "New File Size | #{size.to_b} Bytes. (#{size.to_mb} MB) " << "\n"
145
+ end
129
146
 
130
- md << "File Size Change | #{size.to_s_b} Bytes. (#{size.to_s_kb} KB) " << "\n"
131
- end
147
+ diff[:file_size].tap do |file_size|
148
+ size = Apkstats::Helper::Bytes.from_b(file_size)
132
149
 
133
- diff[:download_size].tap do |download_size|
134
- size = Apkstats::Helper::Bytes.from_b(download_size)
150
+ md << "File Size Change | #{size.to_s_b} Bytes. (#{size.to_s_kb} KB) " << "\n"
151
+ end
135
152
 
136
- md << "Download Size Change | #{size.to_s_b} Bytes. (#{size.to_s_kb} KB) " << "\n"
137
- end
153
+ diff[:download_size].tap do |download_size|
154
+ size = Apkstats::Helper::Bytes.from_b(download_size)
138
155
 
139
- report_hash_and_arrays = lambda { |key, name|
140
- list_up_entities = lambda { |type_key, label|
141
- diff[key][type_key].tap do |features|
142
- break if features.empty?
156
+ md << "Download Size Change | #{size.to_s_b} Bytes. (#{size.to_s_kb} KB) " << "\n"
157
+ end
143
158
 
144
- md << "#{label} | " << features.map { |f| "- #{f}" }.join("<br>").to_s << "\n"
145
- end
146
- }
159
+ result[:base][:method_reference_count].tap do |method_reference_count|
160
+ md << "New Method Reference Count | #{method_reference_count}" << "\n"
161
+ end
147
162
 
148
- list_up_entities.call(:new, "New #{name}")
149
- list_up_entities.call(:removed, "Removed #{name}")
150
- }
163
+ diff[:method_reference_count].tap do |method_reference_count|
164
+ md << "Method Reference Count Change | #{method_reference_count}" << "\n"
165
+ end
151
166
 
152
- report_hash_and_arrays.call(:required_features, "Required Features")
153
- report_hash_and_arrays.call(:non_required_features, "Non-required Features")
154
- report_hash_and_arrays.call(:permissions, "Permissions")
167
+ result[:base][:dex_count].tap do |dex_count|
168
+ md << "New Number of dex file(s) | #{dex_count}" << "\n"
169
+ end
155
170
 
156
- markdown(md)
171
+ diff[:dex_count].tap do |dex_count|
172
+ md << "Number of dex file(s) Change | #{dex_count}" << "\n"
157
173
  end
174
+
175
+ report_hash_and_arrays = lambda { |key, name|
176
+ list_up_entities = lambda { |type_key, label|
177
+ diff[key][type_key].tap do |features|
178
+ break if features.empty?
179
+
180
+ md << "#{label} | " << features.map { |f| "- #{f}" }.join("<br>").to_s << "\n"
181
+ end
182
+ }
183
+
184
+ list_up_entities.call(:new, "New #{name}")
185
+ list_up_entities.call(:removed, "Removed #{name}")
186
+ }
187
+
188
+ report_hash_and_arrays.call(:required_features, "Required Features")
189
+ report_hash_and_arrays.call(:non_required_features, "Non-required Features")
190
+ report_hash_and_arrays.call(:permissions, "Permissions")
191
+
192
+ markdown(md)
193
+ true
158
194
  rescue StandardError => e
159
195
  warn("apkstats failed to execute the command due to #{e.message}")
160
196
 
161
- e.backtrace&.each { |line| STDOUT.puts line }
197
+ on_error(e)
198
+ false
162
199
  end
163
200
 
164
- # rubocop:enable Metrics/AbcSize
201
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
165
202
 
166
203
  # Show the file size of your apk file.
167
204
  #
168
205
  # @return [Fixnum] return positive value if exists, otherwise -1.
169
206
  def file_size(_opts = {})
170
- result = run_command(__method__)
207
+ result = run_command(apkanalyzer_command, __method__)
171
208
  result ? result.to_i : -1
172
209
  end
173
210
 
@@ -175,7 +212,7 @@ module Danger
175
212
  #
176
213
  # @return [Fixnum] return positive value if exists, otherwise -1.
177
214
  def download_size(_opts = {})
178
- result = run_command(__method__)
215
+ result = run_command(apkanalyzer_command, __method__)
179
216
  result ? result.to_i : -1
180
217
  end
181
218
 
@@ -184,7 +221,7 @@ module Danger
184
221
  #
185
222
  # @return [Array<String>, Nil] return nil unless retrieved.
186
223
  def required_features(_opts = {})
187
- result = run_command(__method__)
224
+ result = run_command(apkanalyzer_command, __method__)
188
225
  result ? result.to_a : nil
189
226
  end
190
227
 
@@ -193,7 +230,7 @@ module Danger
193
230
  #
194
231
  # @return [Array<String>, Nil] return nil unless retrieved.
195
232
  def non_required_features(_opts = {})
196
- result = run_command(__method__)
233
+ result = run_command(apkanalyzer_command, __method__)
197
234
  result ? result.to_a : nil
198
235
  end
199
236
 
@@ -201,7 +238,7 @@ module Danger
201
238
  #
202
239
  # @return [Array<String>, Nil] return nil unless retrieved.
203
240
  def permissions(_opts = {})
204
- result = run_command(__method__)
241
+ result = run_command(apkanalyzer_command, __method__)
205
242
  result ? result.to_a : nil
206
243
  end
207
244
 
@@ -209,33 +246,78 @@ module Danger
209
246
  #
210
247
  # @return [String, Nil] return nil unless retrieved.
211
248
  def min_sdk(_opts = {})
212
- run_command(__method__)
249
+ run_command(apkanalyzer_command, __method__)
213
250
  end
214
251
 
215
252
  # Show the target sdk version of your apk file.
216
253
  #
217
254
  # @return [String, Nil] return nil unless retrieved.
218
255
  def target_sdk(_opts = {})
219
- run_command(__method__)
256
+ run_command(apkanalyzer_command, __method__)
257
+ end
258
+
259
+ # Show the methods reference count of your apk file.
260
+ #
261
+ # @return [Fixnum] return positive value if exists, otherwise -1.
262
+ def method_reference_count(_opts = {})
263
+ result = run_command(apkanalyzer_command, __method__)
264
+ result || -1
265
+ end
266
+
267
+ # Show the number of dex of your apk file.
268
+ #
269
+ # @return [Fixnum] return positive value if exists, otherwise -1.
270
+ def dex_count(_opts = {})
271
+ result = run_command(apkanalyzer_command, __method__)
272
+ result || -1
220
273
  end
221
274
 
222
275
  private
223
276
 
224
- def run_command(name)
277
+ # @param [Apkstats::Command::Executable] command a wrapper class of a command
278
+ # @param [String] name an attribute name
279
+ def run_command(command, name)
225
280
  raise "#{command.command_path} is not found or is not executable" unless command.executable?
226
281
 
227
282
  return command.send(name, apk_filepath)
228
283
  rescue StandardError => e
229
284
  warn("apkstats failed to execute the command #{name} due to #{e.message}")
230
285
 
231
- e.backtrace&.each { |line| puts line }
286
+ on_error(e)
287
+ end
232
288
 
233
- nil
289
+ def apkanalyzer_command
290
+ return @apkanalyzer_command if defined?(@apkanalyzer_command)
291
+
292
+ command_path = apkanalyzer_path || `which apkanalyzer`.chomp
293
+
294
+ if command_path.empty?
295
+ sdk_path = ENV["ANDROID_HOME"] || ENV["ANDROID_SDK_ROOT"]
296
+
297
+ if sdk_path
298
+ tmp_path = File.join(sdk_path, "cmdline-tools/tools/bin/apkanalyzer")
299
+ tmp_path = File.join(sdk_path, "tools/bin/apkanalyzer") unless File.executable?(tmp_path)
300
+
301
+ command_path = tmp_path if File.executable?(tmp_path)
302
+ else
303
+ warn("apkstats will not infer the apkanalyzer path in further versions so please include apkanalyer in your PATH or specify it explicitly.")
304
+ end
305
+ end
306
+
307
+ command_path = command_path.chomp
308
+
309
+ raise Error, "Please include apkanalyer in your PATH or specify it explicitly." if command_path.empty?
310
+ raise Error, "#{command_path} is not executable." unless File.executable?(command_path)
311
+
312
+ @apkanalyzer_command = Apkstats::Command::ApkAnalyzer.new(command_path: command_path)
234
313
  end
235
314
 
236
- def command
237
- command_type ||= :apk_analyzer
238
- @command ||= COMMAND_TYPE_MAP[command_type.to_sym].new(command_path: command_path)
315
+ # @param [StandardError] err a happened error
316
+ # @return [NilClass]
317
+ def on_error(err)
318
+ warn err.message
319
+ err.backtrace&.each { |line| warn line }
320
+ nil
239
321
  end
240
322
  end
241
323
  end