appium_instrumenter 0.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.
@@ -0,0 +1,539 @@
1
+ require 'rexml/document'
2
+ require 'timeout'
3
+ require 'luffa'
4
+
5
+ if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
6
+ require 'win32/registry'
7
+ end
8
+
9
+ module Calabash
10
+ module Android
11
+ module Dependencies
12
+ private
13
+
14
+ class ScanningTimedOutError < RuntimeError; end
15
+
16
+ def self.set_android_dependencies(android_dependencies)
17
+ @@android_dependencies = android_dependencies
18
+ end
19
+
20
+ def self.set_java_dependencies(java_dependencies)
21
+ @@java_dependencies = java_dependencies
22
+ end
23
+
24
+ def self.android_dependencies(key)
25
+ setup unless defined?(@@android_dependencies)
26
+
27
+ if @@android_dependencies.has_key?(key)
28
+ file = @@android_dependencies[key]
29
+
30
+ unless File.exists?(file)
31
+ raise "No such file '#{file}'"
32
+ end
33
+
34
+ file
35
+ else
36
+ raise "No such dependency '#{key}'"
37
+ end
38
+ end
39
+
40
+ def self.java_dependencies(key)
41
+ setup unless defined?(@@java_dependencies)
42
+
43
+ if key == :ant_path
44
+ ant_executable
45
+ elsif @@java_dependencies.has_key?(key)
46
+ file = @@java_dependencies[key]
47
+
48
+ unless File.exists?(file)
49
+ raise "No such file '#{file}'"
50
+ end
51
+
52
+ file
53
+ else
54
+ raise "No such dependency '#{key}'"
55
+ end
56
+ end
57
+
58
+ public
59
+
60
+ def self.adb_path
61
+ android_dependencies(:adb_path)
62
+ end
63
+
64
+ def self.aapt_path
65
+ android_dependencies(:aapt_path)
66
+ end
67
+
68
+ def self.zipalign_path
69
+ android_dependencies(:zipalign_path)
70
+ end
71
+
72
+ def self.android_jar_path
73
+ android_dependencies(:android_jar_path)
74
+ end
75
+
76
+ def self.java_path
77
+ java_dependencies(:java_path)
78
+ end
79
+
80
+ def self.keytool_path
81
+ java_dependencies(:keytool_path)
82
+ end
83
+
84
+ def self.jarsigner_path
85
+ java_dependencies(:jarsigner_path)
86
+ end
87
+
88
+ def self.ant_path
89
+ java_dependencies(:ant_path)
90
+ end
91
+
92
+ def self.setup
93
+ if ENV['CI_NO_ANDROID_RUNTIME'] == '1'
94
+ @@android_dependencies = {}
95
+ @@java_dependencies = {}
96
+ return
97
+ end
98
+
99
+ @@halt_scanning = false
100
+ @@halt_scanning_thread = nil
101
+
102
+ if ENV['ANDROID_HOME']
103
+ android_sdk_location = ENV['ANDROID_HOME']
104
+ p ("Setting Android SDK location to $ANDROID_HOME")
105
+ else
106
+ android_sdk_location = detect_android_sdk_location
107
+ end
108
+
109
+ if android_sdk_location.nil?
110
+ p 'Could not find an Android SDK please make sure it is installed.'
111
+ p 'You can read about how Calabash is searching for an Android SDK and how you can help here:'
112
+ p 'https://github.com/calabash/calabash-android/blob/master/documentation/installation.md#prerequisites'
113
+
114
+ raise 'Could not find an Android SDK'
115
+ end
116
+
117
+ p ("Android SDK location set to '#{android_sdk_location}'")
118
+
119
+ @@halt_scanning_thread = Thread.new do
120
+ sleep 60
121
+ @@halt_scanning = true
122
+ end
123
+
124
+ begin
125
+ set_android_dependencies(locate_android_dependencies(android_sdk_location))
126
+ rescue ScanningTimedOutError => e
127
+ p 'Timed out locating Android dependency'
128
+ p 'You can read about how Calabash is searching for an Android SDK and how you can help here:'
129
+ p 'https://github.com/calabash/calabash-android/blob/master/documentation/installation.md#prerequisites'
130
+
131
+ raise e.message
132
+ rescue Environment::InvalidEnvironmentError => e
133
+ p 'Could not locate Android dependency'
134
+ p 'You can read about how Calabash is searching for an Android SDK and how you can help here:'
135
+ p 'https://github.com/calabash/calabash-android/blob/master/documentation/installation.md#prerequisites'
136
+
137
+ raise e
138
+ end
139
+
140
+ if ENV['JAVA_HOME']
141
+ java_sdk_home = ENV['JAVA_HOME']
142
+ p ("Setting Java SDK location to $JAVA_HOME")
143
+ else
144
+ java_sdk_home = detect_java_sdk_location
145
+ end
146
+
147
+ p ("Java SDK location set to '#{java_sdk_home}'")
148
+
149
+ Thread.kill(@@halt_scanning_thread) if @@halt_scanning_thread
150
+ @@halt_scanning = false
151
+
152
+ @@halt_scanning_thread = Thread.new do
153
+ sleep 60
154
+ @@halt_scanning = true
155
+ end
156
+
157
+ begin
158
+ set_java_dependencies(locate_java_dependencies(java_sdk_home))
159
+ rescue ScanningTimedOutError => e
160
+ p 'Timed out locating Java dependency'
161
+ p "You can read about how Calabash is searching for a JDK and how you can help here:"
162
+ p "https://github.com/calabash/calabash-android/blob/master/documentation/installation.md#prerequisites"
163
+
164
+ raise e.message
165
+ rescue Environment::InvalidJavaSDKHome => e
166
+ p "Could not find Java Development Kit please make sure it is installed."
167
+ p "You can read about how Calabash is searching for a JDK and how you can help here:"
168
+ p "https://github.com/calabash/calabash-android/blob/master/documentation/installation.md#prerequisites"
169
+
170
+ raise e
171
+ rescue Environment::InvalidEnvironmentError => e
172
+ p "Could not find Java dependency"
173
+ p "You can read about how Calabash is searching for a JDK and how you can help here:"
174
+ p "https://github.com/calabash/calabash-android/blob/master/documentation/installation.md#prerequisites"
175
+
176
+ raise e
177
+ end
178
+
179
+ Thread.kill(@@halt_scanning_thread) if @@halt_scanning_thread
180
+ end
181
+
182
+ private
183
+
184
+ def self.tools_directory
185
+ tools_directories = tools_directories(ENV['ANDROID_HOME'])
186
+
187
+ File.join(ENV['ANDROID_HOME'], tools_directories.first)
188
+ end
189
+
190
+ def self.tools_directories(android_sdk_location)
191
+ build_tools_files = list_files(File.join(android_sdk_location, 'build-tools')).select {|file| File.directory?(file)}
192
+
193
+ build_tools_directories =
194
+ build_tools_files.select do |dir|
195
+ begin
196
+ Luffa::Version.new(File.basename(dir))
197
+ true
198
+ rescue ArgumentError
199
+ false
200
+ end
201
+ end.sort do |a, b|
202
+ Luffa::Version.compare(Luffa::Version.new(File.basename(a)), Luffa::Version.new(File.basename(b)))
203
+ end.reverse.map{|dir| File.join('build-tools', File.basename(dir))}
204
+
205
+ if build_tools_directories.empty?
206
+ unless build_tools_files.reverse.first.nil?
207
+ build_tools_directories = [File.join('build-tools', File.basename(build_tools_files.reverse.first))]
208
+ end
209
+ end
210
+
211
+ build_tools_directories + ['platform-tools', 'tools']
212
+ end
213
+
214
+ def self.platform_directory(android_sdk_location)
215
+ files = list_files(File.join(android_sdk_location, 'platforms'))
216
+ .select {|file| File.directory?(file)}
217
+
218
+ sorted_files = files.sort_by {|item| '%08s' % item.split('-').last}.reverse
219
+
220
+ if sorted_files.first.nil?
221
+ raise Environment::InvalidEnvironmentError,
222
+ "Could not find any platform directory in '#{File.join(android_sdk_location, 'platforms')}'"
223
+ end
224
+
225
+ File.join('platforms', File.basename(sorted_files.first))
226
+ end
227
+
228
+ def self.locate_android_dependencies(android_sdk_location)
229
+ adb_path = scan_for_path(android_sdk_location, adb_executable, ['platform-tools'])
230
+ aapt_path = scan_for_path(android_sdk_location, aapt_executable, tools_directories(android_sdk_location))
231
+ zipalign_path = scan_for_path(android_sdk_location, zipalign_executable, tools_directories(android_sdk_location))
232
+
233
+ if adb_path.nil?
234
+ raise Environment::InvalidEnvironmentError,
235
+ "Could not find '#{adb_executable}' in '#{android_sdk_location}'"
236
+ end
237
+
238
+ if aapt_path.nil?
239
+ raise Environment::InvalidEnvironmentError,
240
+ "Could not find '#{aapt_executable}' in '#{android_sdk_location}'"
241
+ end
242
+
243
+ if zipalign_path.nil?
244
+ raise Environment::InvalidEnvironmentError,
245
+ "Could not find '#{zipalign_executable}' in '#{android_sdk_location}'"
246
+ end
247
+
248
+ p ("Set aapt path to '#{aapt_path}'")
249
+ p ("Set zipalign path to '#{zipalign_path}'")
250
+ p ("Set adb path to '#{adb_path}'")
251
+
252
+ android_jar_path = scan_for_path(File.join(android_sdk_location, 'platforms'), 'android.jar', [File.basename(platform_directory(android_sdk_location))])
253
+
254
+ if android_jar_path.nil?
255
+ raise Environment::InvalidEnvironmentError,
256
+ "Could not find 'android.jar' in '#{File.join(android_sdk_location, 'platforms')}'"
257
+ end
258
+
259
+ p ("Set android jar path to '#{android_jar_path}'")
260
+
261
+ {
262
+ aapt_path: aapt_path,
263
+ zipalign_path: zipalign_path,
264
+ adb_path: adb_path,
265
+ android_jar_path: android_jar_path
266
+ }
267
+ end
268
+
269
+ def self.locate_java_dependencies(java_sdk_location)
270
+ # For the Java dependencies, we will use the PATH elements of they exist
271
+ on_path = find_executable_on_path(java_executable)
272
+
273
+ if on_path
274
+ p ('Found java on PATH')
275
+ java_path = on_path
276
+ else
277
+ if java_sdk_location.nil? || java_sdk_location.empty?
278
+ raise Environment::InvalidJavaSDKHome,
279
+ "Could not locate '#{java_executable}' on path, and Java SDK Home is invalid."
280
+ end
281
+
282
+ java_path = scan_for_path(java_sdk_location, java_executable, ['bin'])
283
+ end
284
+
285
+ p ("Set java path to '#{java_path}'")
286
+
287
+ on_path = find_executable_on_path(keytool_executable)
288
+
289
+ if on_path
290
+ p ('Found keytool on PATH')
291
+ keytool_path = on_path
292
+ else
293
+ if java_sdk_location.nil? || java_sdk_location.empty?
294
+ raise Environment::InvalidJavaSDKHome,
295
+ "Could not locate '#{keytool_executable}' on path, and Java SDK Home is invalid."
296
+ end
297
+
298
+ keytool_path = scan_for_path(java_sdk_location, keytool_executable, ['bin'])
299
+ end
300
+
301
+ p ("Set keytool path to '#{keytool_path}'")
302
+
303
+ on_path = find_executable_on_path(jarsigner_executable)
304
+
305
+ if on_path
306
+ p ('Found jarsigner on PATH')
307
+ jarsigner_path = on_path
308
+ else
309
+ if java_sdk_location.nil? || java_sdk_location.empty?
310
+ raise Environment::InvalidJavaSDKHome,
311
+ "Could not locate '#{jarsigner_executable}' on path, and Java SDK Home is invalid."
312
+ end
313
+
314
+ jarsigner_path = scan_for_path(java_sdk_location, jarsigner_executable, ['bin'])
315
+ end
316
+
317
+ p ("Set jarsigner path to '#{jarsigner_path}'")
318
+
319
+ if java_path.nil?
320
+ raise Environment::InvalidEnvironmentError,
321
+ "Could not find '#{java_executable}' on PATH or in '#{java_sdk_location}'"
322
+ end
323
+
324
+ if keytool_path.nil?
325
+ raise Environment::InvalidEnvironmentError,
326
+ "Could not find '#{keytool_executable}' on PATH or in '#{java_sdk_location}'"
327
+ end
328
+
329
+ if jarsigner_path.nil?
330
+ raise Environment::InvalidEnvironmentError,
331
+ "Could not find '#{jarsigner_executable}' on PATH or in '#{java_sdk_location}'"
332
+ end
333
+
334
+ {
335
+ java_path: java_path,
336
+ keytool_path: keytool_path,
337
+ jarsigner_path: jarsigner_path
338
+ }
339
+ end
340
+
341
+ def self.halt_scanning?
342
+ @@halt_scanning
343
+ end
344
+
345
+ def self.scan_for_path(path, file_name, expected_sub_folders = nil)
346
+ if self.halt_scanning?
347
+ p ("Timed out looking for '#{file_name}', currently looking in '#{path}'")
348
+ raise ScanningTimedOutError, "Timed out looking for '#{file_name}'"
349
+ end
350
+
351
+ # Optimization for expected folders
352
+ if expected_sub_folders && !expected_sub_folders.empty?
353
+ expected_sub_folders.each do |expected_sub_folder|
354
+ result = scan_for_path(File.join(path, expected_sub_folder), file_name)
355
+
356
+ return result if result
357
+ end
358
+
359
+ p ("Did not find '#{file_name}' in any standard directory of '#{path}'. Calabash will therefore take longer to load")
360
+ p (" - Expected to find '#{file_name}' in any of:")
361
+
362
+ expected_sub_folders.each do |expected_sub_folder|
363
+ p (" - #{File.join(path, expected_sub_folder)}")
364
+ end
365
+ end
366
+
367
+ files = list_files(path).sort.reverse
368
+
369
+ if files.reject{|file| File.directory?(file)}.
370
+ map{|file| File.basename(file)}.include?(file_name)
371
+ return File.join(path, file_name)
372
+ else
373
+ files.select{|file| File.directory?(file)}.each do |dir|
374
+ result = scan_for_path(dir, file_name)
375
+
376
+ return result if result
377
+ end
378
+ end
379
+
380
+ nil
381
+ end
382
+
383
+ def self.detect_android_sdk_location
384
+ if File.exist?(monodroid_config_file)
385
+ sdk_location = read_attribute_from_monodroid_config('android-sdk', 'path')
386
+
387
+ if sdk_location
388
+ p ("Setting Android SDK location from '#{monodroid_config_file}'")
389
+
390
+ return sdk_location
391
+ end
392
+ end
393
+
394
+ if File.exist?(File.expand_path('~/Library/Developer/Xamarin/android-sdk-mac_x86/'))
395
+ return File.expand_path('~/Library/Developer/Xamarin/android-sdk-mac_x86/')
396
+ end
397
+
398
+ if File.exist?(File.expand_path('~/Library/Developer/Xamarin/android-sdk-macosx/'))
399
+ return File.expand_path('~/Library/Developer/Xamarin/android-sdk-macosx/')
400
+ end
401
+
402
+ # Default location when installing with Android Studio
403
+ if File.exist?(File.expand_path('~/Library/Android/sdk/'))
404
+ return File.expand_path('~/Library/Android/sdk/')
405
+ end
406
+
407
+ if File.exist?('C:\\Android\\android-sdk')
408
+ return 'C:\\Android\\android-sdk'
409
+ end
410
+
411
+ if is_windows?
412
+ from_registry = read_registry(::Win32::Registry::HKEY_CURRENT_USER, "Software\\Novell\\Mono for Android", 'AndroidSdkDirectory')
413
+
414
+ if from_registry && File.exist?(from_registry)
415
+ p ("Setting Android SDK location from HKEY_CURRENT_USER Software\\Novell\\Mono for Android")
416
+ return from_registry
417
+ end
418
+
419
+ from_registry = read_registry(::Win32::Registry::HKEY_LOCAL_MACHINE, 'Software\\Android SDK Tools', 'Path')
420
+
421
+ if from_registry && File.exist?(from_registry)
422
+ p ("Setting Android SDK location from HKEY_LOCAL_MACHINE Software\\Android SDK Tools")
423
+ return from_registry
424
+ end
425
+ end
426
+
427
+ nil
428
+ end
429
+
430
+ def self.detect_java_sdk_location
431
+ if File.exist?(monodroid_config_file)
432
+ sdk_location = read_attribute_from_monodroid_config('java-sdk', 'path')
433
+
434
+ if sdk_location
435
+ p ("Setting Java SDK location from '#{monodroid_config_file}'")
436
+
437
+ return sdk_location
438
+ end
439
+ end
440
+
441
+ java_versions = ['1.9', '1.8', '1.7', '1.6']
442
+
443
+ if is_windows?
444
+ java_versions.each do |java_version|
445
+ key = "SOFTWARE\\JavaSoft\\Java Development Kit\\#{java_version}"
446
+ from_registry = read_registry(::Win32::Registry::HKEY_LOCAL_MACHINE, key, 'JavaHome')
447
+
448
+ if from_registry && File.exist?(from_registry)
449
+ p ("Setting Java SDK location from HKEY_LOCAL_MACHINE #{key}")
450
+ return from_registry
451
+ end
452
+ end
453
+ end
454
+
455
+ nil
456
+ end
457
+
458
+ def self.monodroid_config_file
459
+ File.expand_path('~/.config/xbuild/monodroid-config.xml')
460
+ end
461
+
462
+ def self.read_attribute_from_monodroid_config(element, attribute)
463
+ element = REXML::Document.new(IO.read(monodroid_config_file)).elements["//#{element}"]
464
+
465
+ if element
466
+ element.attributes[attribute]
467
+ else
468
+ nil
469
+ end
470
+ end
471
+
472
+ def self.find_executable_on_path(executable)
473
+ path_elements.each do |x|
474
+ f = File.join(x, executable)
475
+ return f if File.exists?(f)
476
+ end
477
+
478
+ nil
479
+ end
480
+
481
+ def self.path_elements
482
+ return [] unless ENV['PATH']
483
+ ENV['PATH'].split (/[:;]/)
484
+ end
485
+
486
+ def self.zipalign_executable
487
+ is_windows? ? 'zipalign.exe' : 'zipalign'
488
+ end
489
+
490
+ def self.jarsigner_executable
491
+ is_windows? ? 'jarsigner.exe' : 'jarsigner'
492
+ end
493
+
494
+ def self.java_executable
495
+ is_windows? ? 'java.exe' : 'java'
496
+ end
497
+
498
+ def self.keytool_executable
499
+ is_windows? ? 'keytool.exe' : 'keytool'
500
+ end
501
+
502
+ def self.adb_executable
503
+ is_windows? ? 'adb.exe' : 'adb'
504
+ end
505
+
506
+ def self.aapt_executable
507
+ is_windows? ? 'aapt.exe' : 'aapt'
508
+ end
509
+
510
+ def self.ant_executable
511
+ is_windows? ? 'ant.exe' : 'ant'
512
+ end
513
+
514
+ def self.is_windows?
515
+ (RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/)
516
+ end
517
+
518
+ def self.read_registry(root_key, key, value)
519
+ begin
520
+ root_key.open(key)[value]
521
+ rescue
522
+ nil
523
+ end
524
+ end
525
+
526
+ def self.list_files(path)
527
+ # Dir.glob does not accept backslashes, even on windows. We have to
528
+ # substitute all backwards slashes to forward.
529
+ # C:\foo becomes C:/foo
530
+
531
+ if is_windows?
532
+ Dir.glob(File.join(path, '*').gsub('\\', '/'))
533
+ else
534
+ Dir.glob(File.join(path, '*'))
535
+ end
536
+ end
537
+ end
538
+ end
539
+ end