run_loop 2.7.1 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: run_loop
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.7.1
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Karl Krukow
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2018-05-15 00:00:00.000000000 Z
12
+ date: 2018-08-30 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: json
@@ -340,6 +340,7 @@ files:
340
340
  - lib/run_loop/cli/cli.rb
341
341
  - lib/run_loop/cli/codesign.rb
342
342
  - lib/run_loop/cli/errors.rb
343
+ - lib/run_loop/cli/idm.rb
343
344
  - lib/run_loop/cli/instruments.rb
344
345
  - lib/run_loop/cli/locale.rb
345
346
  - lib/run_loop/cli/simctl.rb
@@ -390,7 +391,6 @@ files:
390
391
  - lib/run_loop/process_waiter.rb
391
392
  - lib/run_loop/regex.rb
392
393
  - lib/run_loop/shell.rb
393
- - lib/run_loop/sim_control.rb
394
394
  - lib/run_loop/simctl.rb
395
395
  - lib/run_loop/strings.rb
396
396
  - lib/run_loop/template.rb
@@ -431,7 +431,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
431
431
  version: '0'
432
432
  requirements: []
433
433
  rubyforge_project:
434
- rubygems_version: 2.7.6
434
+ rubygems_version: 2.7.7
435
435
  signing_key:
436
436
  specification_version: 4
437
437
  summary: The bridge between Calabash iOS and Xcode command-line tools like instruments
@@ -1,1268 +0,0 @@
1
- require 'cfpropertylist'
2
-
3
- module RunLoop
4
-
5
- # One class interact with the iOS Simulators.
6
- #
7
- # @note All command line tools are run in the context of `xcrun`.
8
- #
9
- # Throughout this class's documentation, there are references to the
10
- # _current version of Xcode_. The current Xcode version is the one returned
11
- # by `xcrun xcodebuild`. The current Xcode version can be set using
12
- # `xcode-select` or overridden using the `DEVELOPER_DIR`.
13
- #
14
- # @todo `puts` calls need to be replaced with proper logging
15
- class SimControl
16
-
17
- include RunLoop::Regex
18
-
19
- # @!visibility private
20
- def xcode
21
- @xcode ||= RunLoop::Xcode.new
22
- end
23
-
24
- # @!visibility private
25
- def xcode_version
26
- xcode.version
27
- end
28
-
29
- # @!visibility private
30
- def xcode_version_gte_7?
31
- xcode.version_gte_7?
32
- end
33
-
34
- # @!visibility private
35
- def xcode_version_gte_6?
36
- xcode.version_gte_6?
37
- end
38
-
39
- # @!visibility private
40
- # @deprecated 2.1.0
41
- def xcode_version_gte_51?
42
- #RunLoop.deprecated("2.1.0", "No replacement.")
43
- xcode.version_gte_51?
44
- end
45
-
46
- # @!visibility private
47
- def xcode_developer_dir
48
- xcode.developer_dir
49
- end
50
-
51
- def xcrun
52
- @xcrun ||= RunLoop::Xcrun.new
53
- end
54
-
55
- # Return an instance of PlistBuddy.
56
- # @return [RunLoop::PlistBuddy] The plist buddy instance that is used internally.
57
- def pbuddy
58
- @pbuddy ||= RunLoop::PlistBuddy.new
59
- end
60
-
61
- # Is the simulator for the current version of Xcode running?
62
- # @return [Boolean] True if the simulator is running.
63
- def sim_is_running?
64
- not sim_pid.nil?
65
- end
66
-
67
- # If it is running, quit the simulator for the current version of Xcode.
68
- #
69
- # @param [Hash] opts Optional controls.
70
- # @option opts [Float] :post_quit_wait (1.0) How long to sleep after the
71
- # simulator has quit.
72
- #
73
- # @todo Consider migrating AppleScript calls to separate class
74
- def quit_sim(opts={})
75
- if sim_is_running?
76
- default_opts = {:post_quit_wait => 1.0 }
77
- merged_opts = default_opts.merge(opts)
78
- `echo 'application "#{sim_name}" quit' | xcrun osascript`
79
- sleep(merged_opts[:post_quit_wait]) if merged_opts[:post_quit_wait]
80
- end
81
- end
82
-
83
- # If it is not already running, launch the simulator for the current version
84
- # of Xcode. Launches the simulator in the background so it does not
85
- # steal focus.
86
- #
87
- # @param [Hash] opts Optional controls.
88
- # @option opts [Float] :post_launch_wait (2.0) How long to sleep after the
89
- # simulator has launched.
90
- def launch_sim(opts={})
91
- unless sim_is_running?
92
- default_opts = {:post_launch_wait => 2.0}
93
- merged_opts = default_opts.merge(opts)
94
- `xcrun open -g -a "#{sim_app_path}"`
95
- sleep(merged_opts[:post_launch_wait]) if merged_opts[:post_launch_wait]
96
- end
97
- end
98
-
99
- # Relaunch the simulator for the current version of Xcode. If that
100
- # simulator is already running, it is quit.
101
- #
102
- # @param [Hash] opts Optional controls.
103
- # @option opts [Float] :post_quit_wait (1.0) How long to sleep after the
104
- # simulator has quit.
105
- # @option opts [Float] :post_launch_wait (2.0) How long to sleep after the
106
- # simulator has launched.
107
- def relaunch_sim(opts={})
108
- default_opts = {:post_quit_wait => 1.0,
109
- :post_launch_wait => 2.0}
110
- merged_opts = default_opts.merge(opts)
111
- quit_sim(merged_opts)
112
- launch_sim(merged_opts)
113
- end
114
-
115
- # Terminates all simulators.
116
- #
117
- # @note Sends `kill -9` to all Simulator processes. Use sparingly or not
118
- # at all.
119
- #
120
- # SimulatorBridge
121
- # launchd_sim
122
- # ScriptAgent
123
- #
124
- # There can be only one simulator running at a time. However, during
125
- # gem testing, situations can arise where multiple simulators are active.
126
- def self.terminate_all_sims
127
-
128
- # @todo Throwing SpringBoard crashed UI dialog.
129
- # Tried the gentle approach first; it did not work.
130
- # SimControl.new.quit_sim({:post_quit_wait => 0.5})
131
-
132
- processes =
133
- [
134
- # Xcode < 5.1
135
- 'iPhone Simulator.app',
136
- # 7.0 < Xcode <= 6.0
137
- 'iOS Simulator.app',
138
- # Xcode >= 7.0
139
- 'Simulator.app',
140
-
141
- # Multiple launchd_sim processes have been causing problems. This
142
- # is a first pass at investigating what it would mean to kill the
143
- # launchd_sim process.
144
- 'launchd_sim'
145
-
146
- # RE: Throwing SpringBoard crashed UI dialog
147
- # These are children of launchd_sim. I tried quiting them
148
- # to suppress related UI dialogs about crashing processes. Killing
149
- # them can throw 'launchd_sim' UI Dialogs
150
- #'SimulatorBridge', 'SpringBoard', 'ScriptAgent', 'configd_sim', 'xpcproxy_sim'
151
- ]
152
-
153
- # @todo Maybe should try to send -TERM first and -KILL if TERM fails.
154
- # @todo Needs benchmarking.
155
- processes.each do |process_name|
156
- descripts = `ps x -o pid,command | grep "#{process_name}" | grep -v grep`.strip.split("\n")
157
- descripts.each do |process_desc|
158
- pid = process_desc.split(' ').first
159
- Open3.popen3("kill -9 #{pid} && xcrun wait #{pid}") do |_, stdout, stderr, _|
160
- if ENV['DEBUG_UNIX_CALLS'] == '1'
161
- out = stdout.read.strip
162
- err = stderr.read.strip
163
- next if out.to_s.empty? and err.to_s.empty?
164
- puts "Terminate all simulators: kill process '#{process_name}: #{pid}' => stdout: '#{out}' | stderr: '#{err}'"
165
- end
166
- end
167
- end
168
- end
169
- end
170
-
171
- # Resets the simulator content and settings.
172
- #
173
- # In Xcode < 6, it is analogous to touching the menu item _for every
174
- # simulator_, regardless of SDK.
175
- #
176
- # In Xcode 6, the default is the same; the content and settings for every
177
- # simulator is erased. However, in Xcode 6 it is possible to pass
178
- # a `:sim_udid` as a option to erase an individual simulator.
179
- #
180
- # On Xcode 5, it works by deleting the following directories:
181
- #
182
- # * ~/Library/Application Support/iPhone Simulator/Library
183
- # * ~/Library/Application Support/iPhone Simulator/Library/<sdk>[-64]
184
- #
185
- # and relaunching the iOS Simulator which will recreate the Library
186
- # directory and the latest SDK directory.
187
- #
188
- # On Xcode 6, it uses the `simctl erase <udid>` command line tool.
189
- #
190
- # @param [Hash] opts Optional controls for quitting and launching the simulator.
191
- # @option opts [Float] :post_quit_wait (1.0) How long to sleep after the
192
- # simulator has quit.
193
- # @option opts [Float] :post_launch_wait (3.0) How long to sleep after the
194
- # simulator has launched. Waits longer than normal because we need the
195
- # simulator directories to be repopulated. **NOTE:** This option is ignored
196
- # in Xcode 6.
197
- # @option opts [String] :sim_udid (nil) The udid of the simulator to reset.
198
- # **NOTE:** This option is ignored in Xcode < 6.
199
- def reset_sim_content_and_settings(opts={})
200
- default_opts = {:post_quit_wait => 1.0,
201
- :post_launch_wait => 3.0,
202
- :sim_udid => nil}
203
- merged_opts = default_opts.merge(opts)
204
-
205
- quit_sim(merged_opts)
206
-
207
- # WARNING - DO NOT TRY TO DELETE Developer/CoreSimulator/Devices!
208
- # Very bad things will happen. Unlike Xcode < 6, the re-launching the
209
- # simulator will _not_ recreate the SDK (aka Devices) directories.
210
- if xcode_version_gte_6?
211
- simctl_reset(merged_opts[:sim_udid])
212
- else
213
- sim_lib_path = File.join(sim_app_support_dir, 'Library')
214
- FileUtils.rm_rf(sim_lib_path)
215
- existing_sim_sdk_or_device_data_dirs.each do |dir|
216
- FileUtils.rm_rf(dir)
217
- end
218
- launch_sim(merged_opts)
219
-
220
- # This is tricky because we need to wait for the simulator to recreate
221
- # the directories. Specifically, we need the Accessibility plist to be
222
- # exist so subsequent calabash launches will be able to enable
223
- # accessibility.
224
- #
225
- # The directories take ~3.0 - ~5.0 to create.
226
- counter = 0
227
- loop do
228
- break if counter == 80
229
- dirs = existing_sim_sdk_or_device_data_dirs
230
- if dirs.count == 0
231
- sleep(0.2)
232
- else
233
- break if dirs.all? { |dir|
234
- plist = File.expand_path("#{dir}/Library/Preferences/com.apple.Accessibility.plist")
235
- File.exist?(plist)
236
- }
237
- sleep(0.2)
238
- end
239
- counter = counter + 1
240
- end
241
- end
242
- end
243
-
244
- # @!visibility private
245
- # Enables accessibility on all iOS Simulators by adjusting the
246
- # simulator's Library/Preferences/com.apple.Accessibility.plist contents.
247
- #
248
- # A simulator 'exists' if has an Application Support directory. for
249
- # example, the 6.1, 7.0.3-64, and 7.1 simulators exist if the following
250
- # directories are present:
251
- #
252
- # ~/Library/Application Support/iPhone Simulator/Library/6.1
253
- # ~/Library/Application Support/iPhone Simulator/Library/7.0.3-64
254
- # ~/Library/Application Support/iPhone Simulator/Library/7.1
255
- #
256
- # A simulator is 'possible' if the SDK is available in the Xcode version.
257
- #
258
- # This method merges (uniquely) the possible and existing SDKs.
259
- #
260
- # This method also hides the AXInspector.
261
- #
262
- # **Q:** _Why do we need to enable for both existing and possible SDKs?_
263
- # **A:** Consider what would happen if we were launching against the 7.0.3
264
- # SDK for the first time. The 7.0.3 SDK directory does not exist _until the
265
- # simulator has been launched_. The upshot is that we need to create the
266
- # the plist _before_ we try to launch the simulator.
267
- #
268
- # @note This method will quit the current simulator.
269
- #
270
- # @param [Hash] opts controls the behavior of the method
271
- # @option opts [Boolean] :verbose controls logging output
272
- # @return [Boolean] true if enabling accessibility worked on all sdk
273
- # directories
274
- #
275
- # @todo Should benchmark to see if memo-izing can help speed this up. Or if
276
- # we can intuit the SDK and before launching and enable access on only
277
- # that SDK.
278
- #
279
- # @todo Testing this is _hard_. ATM, I am using a reset sim content
280
- # and settings + RunLoop.run to test.
281
- def enable_accessibility_on_sims(opts={})
282
- default_opts = {:verbose => false}
283
- merged_opts = default_opts.merge(opts)
284
-
285
- existing = existing_sim_sdk_or_device_data_dirs
286
-
287
- if xcode_version_gte_6?
288
- details = sim_details :udid
289
- results = existing.map do |dir|
290
- enable_accessibility_in_sim_data_dir(dir, details, opts)
291
- # This is done here so we don't have to make a public method
292
- # to enable the keyboards for all devices.
293
- enable_keyboard_in_sim_data_dir(dir, details, opts)
294
- end
295
- else
296
- possible = XCODE_5_SDKS.map do |sdk|
297
- File.join(RunLoop::Environment.user_home_directory,
298
- "Library",
299
- "Application Support",
300
- "iPhone Simulator",
301
- sdk)
302
- end
303
-
304
- dirs = (possible + existing).uniq
305
- results = dirs.map do |dir|
306
- enable_accessibility_in_sdk_dir(dir, merged_opts)
307
- end
308
- end
309
- results.all?
310
- end
311
-
312
- # Is the arg a valid Xcode >= 6.0 simulator udid?
313
- # @param [String] udid the String to check
314
- # @return [Boolean] Returns true iff the `udid` matches /[A-F0-9]{8}-([A-F0-9]{4}-){3}[A-F0-9]{12}/
315
- def sim_udid?(udid)
316
- udid.length == 36 and udid[CORE_SIMULATOR_UDID_REGEX,0] != nil
317
- end
318
-
319
- def simulators
320
- unless xcode_version_gte_6?
321
- raise RuntimeError, 'simctl is only available on Xcode >= 6'
322
- end
323
-
324
- hash = simctl_list :devices
325
- sims = []
326
- hash.each_pair do |sdk, list|
327
- list.each do |details|
328
- sims << RunLoop::Device.new(details[:name], sdk, details[:udid], details[:state])
329
- end
330
- end
331
- sims
332
- end
333
-
334
- def accessibility_enabled?(device)
335
- plist = device.simulator_accessibility_plist_path
336
- return false unless File.exist?(plist)
337
-
338
- if device.version >= RunLoop::Version.new('8.0')
339
- plist_hash = SDK_80_ACCESSIBILITY_PROPERTIES_HASH
340
- else
341
- plist_hash = SDK_LT_80_ACCESSIBILITY_PROPERTIES_HASH
342
- end
343
-
344
- plist_hash.each do |_, details|
345
- key = details[:key]
346
- value = details[:value]
347
-
348
- unless pbuddy.plist_read(key, plist) == "#{value}"
349
- return false
350
- end
351
- end
352
- true
353
- end
354
-
355
- def ensure_accessibility(device)
356
- if accessibility_enabled?(device)
357
- true
358
- else
359
- enable_accessibility(device)
360
- end
361
- end
362
-
363
- def enable_accessibility(device)
364
- debug_logging = RunLoop::Environment.debug?
365
-
366
- quit_sim
367
-
368
- plist_path = device.simulator_accessibility_plist_path
369
-
370
- if device.version >= RunLoop::Version.new('8.0')
371
- plist_hash = SDK_80_ACCESSIBILITY_PROPERTIES_HASH
372
- else
373
- plist_hash = SDK_LT_80_ACCESSIBILITY_PROPERTIES_HASH
374
- end
375
-
376
- unless File.exist? plist_path
377
- preferences_dir = File.join(device.simulator_root_dir, 'data/Library/Preferences')
378
- FileUtils.mkdir_p(preferences_dir)
379
- plist = CFPropertyList::List.new
380
- data = {}
381
- plist.value = CFPropertyList.guess(data)
382
- plist.save(plist_path, CFPropertyList::List::FORMAT_BINARY)
383
- end
384
-
385
- msgs = []
386
-
387
- successes = plist_hash.map do |hash_key, settings|
388
- success = pbuddy.plist_set(settings[:key], settings[:type], settings[:value], plist_path)
389
- unless success
390
- if debug_logging
391
- if settings[:type] == 'bool'
392
- value = settings[:value] ? 'YES' : 'NO'
393
- else
394
- value = settings[:value]
395
- end
396
- msgs << "could not set #{hash_key} => '#{settings[:key]}' to #{value}"
397
- end
398
- end
399
- success
400
- end
401
-
402
- if successes.all?
403
- true
404
- else
405
- return false, msgs
406
- end
407
- end
408
-
409
- def software_keyboard_enabled?(device)
410
- plist = device.simulator_preferences_plist_path
411
- return false unless File.exist?(plist)
412
-
413
- CORE_SIMULATOR_KEYBOARD_PROPERTIES_HASH.each do |_, details|
414
- key = details[:key]
415
- value = details[:value]
416
-
417
- unless pbuddy.plist_read(key, plist) == "#{value}"
418
- return false
419
- end
420
- end
421
- true
422
- end
423
-
424
- def ensure_software_keyboard(device)
425
- if software_keyboard_enabled?(device)
426
- true
427
- else
428
- enable_software_keyboard(device)
429
- end
430
- end
431
-
432
- def enable_software_keyboard(device)
433
- debug_logging = RunLoop::Environment.debug?
434
-
435
- quit_sim
436
-
437
- plist_path = device.simulator_preferences_plist_path
438
-
439
- unless File.exist? plist_path
440
- preferences_dir = File.join(device.simulator_root_dir, 'data/Library/Preferences')
441
- FileUtils.mkdir_p(preferences_dir)
442
- plist = CFPropertyList::List.new
443
- data = {}
444
- plist.value = CFPropertyList.guess(data)
445
- plist.save(plist_path, CFPropertyList::List::FORMAT_BINARY)
446
- end
447
-
448
- msgs = []
449
-
450
- successes = CORE_SIMULATOR_KEYBOARD_PROPERTIES_HASH.map do |hash_key, settings|
451
- success = pbuddy.plist_set(settings[:key], settings[:type], settings[:value], plist_path)
452
- unless success
453
- if debug_logging
454
- if settings[:type] == 'bool'
455
- value = settings[:value] ? 'YES' : 'NO'
456
- else
457
- value = settings[:value]
458
- end
459
- msgs << "could not set #{hash_key} => '#{settings[:key]}' to #{value}"
460
- end
461
- end
462
- success
463
- end
464
-
465
- if successes.all?
466
- true
467
- else
468
- return false, msgs
469
- end
470
- end
471
-
472
- private
473
-
474
- # @!visibility private
475
- # The list of possible SDKs for 5.0 <= Xcode < 6.0
476
- #
477
- # @note Used to enable automatically enable accessibility on the simulators.
478
- #
479
- # @see #enable_accessibility_on_sims
480
- XCODE_5_SDKS = ['6.1', '7.0', '7.0.3', '7.0.3-64', '7.1', '7.1-64'].freeze
481
-
482
-
483
- # @!visibility private
484
- # A hash table of the accessibility properties that control whether or not
485
- # accessibility is enabled and whether the AXInspector is visible.
486
- #
487
- # @note Xcode 5 or Xcode 6 SDK < 8.0
488
- #
489
- # @see #enable_accessibility_on_sims
490
- SDK_LT_80_ACCESSIBILITY_PROPERTIES_HASH =
491
- {
492
- :access_enabled => {:key => 'AccessibilityEnabled',
493
- :value => 'true',
494
- :type => 'bool'},
495
-
496
- :app_access_enabled => {:key => 'ApplicationAccessibilityEnabled',
497
- :value => 'true',
498
- :type => 'bool'},
499
-
500
- :automation_enabled => {:key => 'AutomationEnabled',
501
- :value => 'true',
502
- :type => 'bool'},
503
-
504
- # Determines if the Accessibility Inspector is showing.
505
- #
506
- # It turns out we can set this to 'false' as of Xcode 5.1 and
507
- # hide the inspector altogether.
508
- #
509
- # I don't know what the behavior is on Xcode 5.0*.
510
- :inspector_showing => {:key => 'AXInspectorEnabled',
511
- :value => 'false',
512
- :type => 'bool'},
513
-
514
- # Controls if the Accessibility Inspector is expanded or not
515
- # expanded.
516
- :inspector_full_size => {:key => 'AXInspector.enabled',
517
- :value => 'false',
518
- :type => 'bool'},
519
-
520
- # Controls the frame of the Accessibility Inspector.
521
- # This is the best we can do because the OS will rewrite the
522
- # frame if it does not conform to some expected range.
523
- :inspector_frame => {:key => 'AXInspector.frame',
524
- :value => '{{290, -13}, {276, 166}}',
525
- :type => 'string'},
526
-
527
-
528
- }.freeze
529
-
530
- # @!visibility private
531
- # A hash table of the accessibility properties that control whether or not
532
- # accessibility is enabled and whether the AXInspector is visible.
533
- #
534
- # @note Xcode 6 SDK >= 8.0
535
- #
536
- # @see #enable_accessibility_in_sim_data_dir
537
- SDK_80_ACCESSIBILITY_PROPERTIES_HASH =
538
- {
539
- :access_enabled => {:key => 'AccessibilityEnabled',
540
- :value => 'true',
541
- :type => 'bool'},
542
-
543
- :app_access_enabled => {:key => 'ApplicationAccessibilityEnabled',
544
- :value => 1,
545
- :type => 'integer'},
546
-
547
- :automation_enabled => {:key => 'AutomationEnabled',
548
- :value => 1,
549
- :type => 'integer'},
550
-
551
- # Determines if the Accessibility Inspector is showing.
552
- # Hurray! We can turn this off in Xcode 6.
553
- :inspector_showing => {:key => 'AXInspectorEnabled',
554
- :value => 0,
555
- :type => 'integer'},
556
-
557
- # controls if the Accessibility Inspector is expanded or not expanded
558
- :inspector_full_size => {:key => 'AXInspector.enabled',
559
- :value => 'false',
560
- :type => 'bool'},
561
-
562
- # Controls the frame of the Accessibility Inspector
563
- #
564
- # In Xcode 6, positioning this is difficult because the OS
565
- # rewrites the value if the frame does not conform to an
566
- # expected range. This is the best we can do.
567
- #
568
- # But see :inspector_showing! Woot!
569
- :inspector_frame => {:key => 'AXInspector.frame',
570
- :value => '{{270, 0}, {276, 166}}',
571
- :type => 'string'},
572
-
573
- # new and shiny - looks interesting!
574
- :automation_disable_faux_collection_cells =>
575
- {
576
- :key => 'AutomationDisableFauxCollectionCells',
577
- :value => 1,
578
- :type => 'integer'
579
- }
580
- }.freeze
581
-
582
- # @!visibility private
583
- # A regex for finding directories under ~/Library/Developer/CoreSimulator/Devices
584
- # and parsing the output of `simctl list sessions`.
585
- CORE_SIMULATOR_UDID_REGEX = /[A-F0-9]{8}-([A-F0-9]{4}-){3}[A-F0-9]{12}/.freeze
586
-
587
- CORE_SIMULATOR_KEYBOARD_PROPERTIES_HASH =
588
- {
589
- :automatic_minimization => {
590
- :key => 'AutomaticMinimizationEnabled',
591
- :value => 0,
592
- :type => 'integer'
593
- }
594
- }
595
-
596
- # @!visibility private
597
- # Returns the current Simulator pid.
598
- #
599
- # @note Will only search for the current Xcode simulator.
600
- #
601
- # @return [String, nil] The pid as a String or nil if no process is found.
602
- def sim_pid
603
- process_name = "MacOS/#{sim_name}"
604
- `ps x -o pid,command | grep "#{process_name}" | grep -v grep`.strip.split(' ').first
605
- end
606
-
607
- # @!visibility private
608
- # Returns the current simulator name.
609
- #
610
- # @note In Xcode >= 6.0 the simulator name changed.
611
- #
612
- # @note Returns with the .app extension because on Xcode < 6.0, multiple
613
- # processes can be found with 'iPhone Simulator'; the .app ensures that
614
- # other methods find the right pid and application path.
615
- # @return [String] A String suitable for searching for a pid, quitting, or
616
- # launching the current simulator.
617
- def sim_name
618
- @sim_name ||= lambda {
619
- if xcode_version_gte_7?
620
- 'Simulator'
621
- elsif xcode_version_gte_6?
622
- 'iOS Simulator'
623
- else
624
- 'iPhone Simulator'
625
- end
626
- }.call
627
- end
628
-
629
- # @!visibility private
630
- # Returns the path to the current simulator.
631
- #
632
- # @note Xcode >= 6.0 the simulator app has a different path.
633
- #
634
- # @return [String] The path to the simulator app for the current version of
635
- # Xcode.
636
- def sim_app_path
637
- @sim_app_path ||= lambda {
638
- dev_dir = xcode_developer_dir
639
- if xcode_version_gte_7?
640
- "#{dev_dir}/Applications/Simulator.app"
641
- elsif xcode_version_gte_6?
642
- "#{dev_dir}/Applications/iOS Simulator.app"
643
- else
644
- "#{dev_dir}/Platforms/iPhoneSimulator.platform/Developer/Applications/iPhone Simulator.app"
645
- end
646
- }.call
647
- end
648
-
649
- # @!visibility private
650
- # The absolute path to the iPhone Simulator Application Support directory.
651
- # @return [String] absolute path
652
- def sim_app_support_dir
653
- home_dir = RunLoop::Environment.user_home_directory
654
- if xcode_version_gte_6?
655
- File.join(home_dir, "Library", "Developer", "CoreSimulator", "Devices")
656
- else
657
- File.join(home_dir, "Library", "Application Support", "iPhone Simulator")
658
- end
659
- end
660
-
661
- # @!visibility private
662
- # In Xcode 5, this returns a list of absolute paths to the existing
663
- # simulators SDK directories.
664
- #
665
- # In Xcode 6, this returns a list of absolute paths to the existing
666
- # simulators `<udid>/data` directories.
667
- #
668
- # @note This can _never_ be memoized to a variable; its value reflects the
669
- # state of the file system at the time it is called.
670
- #
671
- # In Xcode 5, a simulator 'exists' if it appears in the Application Support
672
- # directory. For example, the 6.1, 7.0.3-64, and 7.1 simulators exist if
673
- # the following directories are present:
674
- #
675
- # ```
676
- # ~/Library/Application Support/iPhone Simulator/Library/6.1
677
- # ~/Library/Application Support/iPhone Simulator/Library/7.0.3-64
678
- # ~/Library/Application Support/iPhone Simulator/Library/7.1
679
- # ```
680
- #
681
- # In Xcode 6, a simulator 'exists' if it appears in the
682
- # CoreSimulator/Devices directory. For example:
683
- #
684
- # ```
685
- # ~/Library/Developer/CoreSimulator/Devices/0BF52B67-F8BB-4246-A668-1880237DD17B
686
- # ~/Library/Developer/CoreSimulator/Devices/2FCF6AFF-8C85-442F-B472-8D489ECBFAA5
687
- # ~/Library/Developer/CoreSimulator/Devices/578A16BE-C31F-46E5-836E-66A2E77D89D4
688
- # ```
689
- #
690
- # @example Xcode 5 behavior
691
- # ~/Library/Application Support/iPhone Simulator/Library/6.1
692
- # ~/Library/Application Support/iPhone Simulator/Library/7.0.3-64
693
- # ~/Library/Application Support/iPhone Simulator/Library/7.1
694
- #
695
- # @example Xcode 6 behavior
696
- # ~/Library/Developer/CoreSimulator/Devices/0BF52B67-F8BB-4246-A668-1880237DD17B/data
697
- # ~/Library/Developer/CoreSimulator/Devices/2FCF6AFF-8C85-442F-B472-8D489ECBFAA5/data
698
- # ~/Library/Developer/CoreSimulator/Devices/578A16BE-C31F-46E5-836E-66A2E77D89D4/data
699
- #
700
- # @return[Array<String>] a list of absolute paths to simulator directories
701
- def existing_sim_sdk_or_device_data_dirs
702
- base_dir = sim_app_support_dir
703
- if xcode_version_gte_6?
704
- regex = CORE_SIMULATOR_UDID_REGEX
705
- else
706
- regex = XCODE_511_SIMULATOR_REGEX
707
- end
708
- dirs = Dir.glob("#{base_dir}/*").select { |path|
709
- path =~ regex
710
- }
711
-
712
- if xcode_version_gte_6?
713
- dirs.map { |elm| File.expand_path(File.join(elm, 'data')) }
714
- else
715
- dirs
716
- end
717
- end
718
-
719
- # @!visibility private
720
- # Enables accessibility on the simulator indicated by `app_support_sdk_dir`.
721
- #
722
- # @note This will quit the simulator.
723
- #
724
- # @note This is for Xcode 5 only. Will raise an error if called on Xcode 6.
725
- #
726
- # @example
727
- # path = '~/Library/Application Support/iPhone Simulator/6.1'
728
- # enable_accessibility_in_sdk_dir(path)
729
- #
730
- # This method also hides the AXInspector.
731
- #
732
- # If the Library/Preferences/com.apple.Accessibility.plist does not exist
733
- # this method will create a Library/Preferences/com.apple.Accessibility.plist
734
- # that (oddly) the Simulator will _not_ overwrite.
735
- #
736
- # @see #enable_accessibility_on_sims for the public API.
737
- #
738
- # @param [String] app_support_sdk_dir the directory where the
739
- # Library/Preferences/com.apple.Accessibility.plist can be found.
740
- #
741
- # @param [Hash] opts controls the behavior of the method
742
- # @option opts [Boolean] :verbose controls logging output
743
- # @return [Boolean] if the plist exists and the plist was successfully
744
- # updated.
745
- # @raise [RuntimeError] If called when Xcode 6 is the active Xcode version.
746
- def enable_accessibility_in_sdk_dir(app_support_sdk_dir, opts={})
747
-
748
- if xcode_version_gte_6?
749
- raise RuntimeError, 'it is illegal to call this method when Xcode >= 6 is the current Xcode version'
750
- end
751
-
752
- default_opts = {:verbose => false}
753
- merged_opts = default_opts.merge(opts)
754
-
755
- quit_sim
756
-
757
- verbose = merged_opts[:verbose]
758
- sdk = File.basename(app_support_sdk_dir)
759
- msgs = ["cannot enable accessibility for #{sdk} SDK"]
760
-
761
- plist_path = File.expand_path("#{app_support_sdk_dir}/Library/Preferences/com.apple.Accessibility.plist")
762
-
763
- if File.exist?(plist_path)
764
- res = SDK_LT_80_ACCESSIBILITY_PROPERTIES_HASH.map do |hash_key, settings|
765
- success = pbuddy.plist_set(settings[:key], settings[:type], settings[:value], plist_path)
766
- unless success
767
- if verbose
768
- if settings[:type] == 'bool'
769
- value = settings[:value] ? 'YES' : 'NO'
770
- else
771
- value = settings[:value]
772
- end
773
- msgs << "could not set #{hash_key} => '#{settings[:key]}' to #{value}"
774
- puts "WARN: #{msgs.join("\n")}"
775
- end
776
- end
777
- success
778
- end
779
- res.all?
780
- else
781
- FileUtils.mkdir_p("#{app_support_sdk_dir}/Library/Preferences")
782
- plist = CFPropertyList::List.new
783
- data = {}
784
- plist.value = CFPropertyList.guess(data)
785
- plist.save(plist_path, CFPropertyList::List::FORMAT_BINARY)
786
- enable_accessibility_in_sdk_dir(app_support_sdk_dir, merged_opts)
787
- end
788
- end
789
-
790
- # @!visibility private
791
- # Enables accessibility on the simulator indicated by `sim_data_dir`.
792
- #
793
- # @note This will quit the simulator.
794
- #
795
- # @note This is for Xcode 6 only. Will raise an error if called on Xcode 5.
796
- #
797
- # @note The Accessibility plist contents differ by iOS version. For
798
- # example, iOS 8 uses Number instead of Boolean as the data type for
799
- # several entries. It is an _error_ to try to set a Number type to a
800
- # Boolean value. This is why we need the second arg:
801
- # `sim_details_key_with_udid` which is a hash that maps a sim udid to a
802
- # a simulator version number. See the todo.
803
- #
804
- # @todo Should consider updating the API to pass just the version number instead
805
- # of passing the entire sim_details hash.
806
- #
807
- # @example
808
- # path = '~/Library/Developer/CoreSimulator/Devices/0BF52B67-F8BB-4246-A668-1880237DD17B'
809
- # enable_accessibility_in_sim_data_dir(path, sim_details(:udid))
810
- #
811
- # This method also hides the AXInspector.
812
- #
813
- # If the Library/Preferences/com.apple.Accessibility.plist does not exist
814
- # this method will create a Library/Preferences/com.apple.Accessibility.plist
815
- # that (oddly) the Simulator will _not_ overwrite.
816
- #
817
- # @see #enable_accessibility_on_sims for the public API.
818
- #
819
- # @param [String] sim_data_dir The directory where the
820
- # Library/Preferences/com.apple.Accessibility.plist can be found.
821
- # @param [Hash] sim_details_keyed_with_udid A hash table of simulator details
822
- # that can be obtained by calling `sim_details(:udid)`.
823
- #
824
- # @param [Hash] opts controls the behavior of the method
825
- # @option opts [Boolean] :verbose controls logging output
826
- # @return [Boolean] If the plist exists and the plist was successfully
827
- # updated or if the directory was skipped (see code comments).
828
- # @raise [RuntimeError] If called when Xcode 6 is _not_ the active Xcode version.
829
- def enable_accessibility_in_sim_data_dir(sim_data_dir, sim_details_keyed_with_udid, opts={})
830
- unless xcode_version_gte_6?
831
- raise RuntimeError, 'it is illegal to call this method when the Xcode < 6 is the current Xcode version'
832
- end
833
-
834
- default_opts = {:verbose => false}
835
- merged_opts = default_opts.merge(opts)
836
-
837
- quit_sim
838
-
839
- verbose = merged_opts[:verbose]
840
- target_udid = sim_data_dir[CORE_SIMULATOR_UDID_REGEX, 0]
841
-
842
- # Directory contains simulators not reported by instruments -s devices
843
- simulator_details = sim_details_keyed_with_udid[target_udid]
844
- if simulator_details.nil?
845
- if verbose
846
- puts ["INFO: Skipping '#{target_udid}' directory because",
847
- "there is no corresponding simulator for active Xcode (version '#{xcode_version}')"].join("\n")
848
- end
849
- return true
850
- end
851
-
852
- launch_name = simulator_details.fetch(:launch_name, nil)
853
- sdk_version = simulator_details.fetch(:sdk_version, nil)
854
- msgs = ["cannot enable accessibility for '#{target_udid}' - '#{launch_name}'"]
855
- plist_path = File.expand_path("#{sim_data_dir}/Library/Preferences/com.apple.Accessibility.plist")
856
-
857
- if sdk_version >= RunLoop::Version.new('8.0')
858
- hash = SDK_80_ACCESSIBILITY_PROPERTIES_HASH
859
- else
860
- hash = SDK_LT_80_ACCESSIBILITY_PROPERTIES_HASH
861
- end
862
-
863
- unless File.exist? plist_path
864
- FileUtils.mkdir_p("#{sim_data_dir}/Library/Preferences")
865
- plist = CFPropertyList::List.new
866
- data = {}
867
- plist.value = CFPropertyList.guess(data)
868
- plist.save(plist_path, CFPropertyList::List::FORMAT_BINARY)
869
- end
870
-
871
- res = hash.map do |hash_key, settings|
872
- success = pbuddy.plist_set(settings[:key], settings[:type], settings[:value], plist_path)
873
- unless success
874
- if verbose
875
- if settings[:type] == 'bool'
876
- value = settings[:value] ? 'YES' : 'NO'
877
- else
878
- value = settings[:value]
879
- end
880
- msgs << "could not set #{hash_key} => '#{settings[:key]}' to #{value}"
881
- puts "WARN: #{msgs.join("\n")}"
882
- end
883
- end
884
- success
885
- end
886
- res.all?
887
- end
888
-
889
- # @!visibility private
890
- # Enables the keyboard to be shown by default on the new Xcode 6 simulators.
891
- #
892
- # The new CoreSimulator environment has a new Hardware > Keyboard > Connect
893
- # Hardware Keyboard option which is on by default and prevents the native
894
- # keyboard from being presented.
895
- #
896
- # @note This will quit the simulator.
897
- #
898
- # @note This is for Xcode 6 only. Will raise an error if called on Xcode 5.
899
- #
900
- # If the Library/Preferences/com.apple.Preferences.plist file doesn't exist
901
- # this method will create one with the content to activate the keyboard.
902
- #
903
- # @param [String] sim_data_dir The directory where the
904
- # Library/Preferences/com.apple.Preferences.plist can be found.
905
- # @param [Hash] sim_details_keyed_with_udid A hash table of simulator details
906
- # that can be obtained by calling `sim_details(:udid)`.
907
- #
908
- # @param [Hash] opts controls the behavior of the method
909
- # @option opts [Boolean] :verbose controls logging output
910
- # @return [Boolean] If the plist exists and the plist was successfully
911
- # updated or if the directory was skipped (see code comments).
912
- # @raise [RuntimeError] If called when Xcode 6 is _not_ the active Xcode version.
913
- def enable_keyboard_in_sim_data_dir(sim_data_dir, sim_details_keyed_with_udid, opts={})
914
-
915
- unless xcode_version_gte_6?
916
- raise RuntimeError, 'it is illegal to call this method when the Xcode < 6 is the current Xcode version'
917
- end
918
-
919
- hash = {:key => 'AutomaticMinimizationEnabled',
920
- :value => 0,
921
- :type => 'integer'}
922
-
923
- default_opts = {:verbose => false}
924
- merged_opts = default_opts.merge(opts)
925
-
926
- quit_sim
927
-
928
- verbose = merged_opts[:verbose]
929
- target_udid = sim_data_dir[CORE_SIMULATOR_UDID_REGEX, 0]
930
-
931
- # Directory contains simulators not reported by instruments -s devices
932
- simulator_details = sim_details_keyed_with_udid[target_udid]
933
- if simulator_details.nil?
934
- if verbose
935
- puts ["INFO: Skipping '#{target_udid}' directory because",
936
- "there is no corresponding simulator for active Xcode (version '#{xcode_version}')"].join("\n")
937
- end
938
- return true
939
- end
940
-
941
- launch_name = simulator_details.fetch(:launch_name, nil)
942
-
943
- msgs = ["cannot enable keyboard for '#{target_udid}' - '#{launch_name}'"]
944
- plist_path = File.expand_path("#{sim_data_dir}/Library/Preferences/com.apple.Preferences.plist")
945
-
946
- unless File.exist? plist_path
947
- FileUtils.mkdir_p("#{sim_data_dir}/Library/Preferences")
948
- plist = CFPropertyList::List.new
949
- data = {}
950
- plist.value = CFPropertyList.guess(data)
951
- plist.save(plist_path, CFPropertyList::List::FORMAT_BINARY)
952
- end
953
-
954
- success = pbuddy.plist_set(hash[:key], hash[:type], hash[:value], plist_path)
955
- unless success
956
- if verbose
957
- msgs << "could not set #{hash[:key]} => '#{hash[:key]}' to #{hash[:value]}"
958
- puts "WARN: #{msgs.join("\n")}"
959
- end
960
- end
961
-
962
- success
963
- end
964
-
965
- # @!visibility private
966
- # Returns a hash table that contains detailed information about the
967
- # available simulators. Use the `primary_key` to control the primary hash
968
- # key. The same information is available regardless of the `primary_key`.
969
- # Choose a key that matches your access pattern.
970
- #
971
- # @note This is for Xcode 6 only. Will raise an error if called on Xcode 5.
972
- #
973
- # @example :udid
974
- # "FD50223C-C29E-497A-BF16-0D6451318251" => {
975
- # :launch_name => "iPad Retina (7.1 Simulator)",
976
- # :udid => "FD50223C-C29E-497A-BF16-0D6451318251",
977
- # :sdk_version => #<RunLoop::Version:0x007f8ee8a9aac8 @major=7, @minor=1, @patch=nil>
978
- # },
979
- # "21DED687-77F5-4125-A480-0DBA6A1BA6D1" => {
980
- # :launch_name => "iPad Retina (8.0 Simulator)",
981
- # :udid => "21DED687-77F5-4125-A480-0DBA6A1BA6D1",
982
- # :sdk_version => #<RunLoop::Version:0x007f8ee8a9a730 @major=8, @minor=0, @patch=nil>
983
- # },
984
- #
985
- #
986
- # @example :launch_name
987
- # "iPad Retina (7.1 Simulator)" => {
988
- # :launch_name => "iPad Retina (7.1 Simulator)",
989
- # :udid => "FD50223C-C29E-497A-BF16-0D6451318251",
990
- # :sdk_version => #<RunLoop::Version:0x007f8ee8a9aac8 @major=7, @minor=1, @patch=nil>
991
- # },
992
- # "iPad Retina (8.0 Simulator)" => {
993
- # :launch_name => "iPad Retina (8.0 Simulator)",
994
- # :udid => "21DED687-77F5-4125-A480-0DBA6A1BA6D1",
995
- # :sdk_version => #<RunLoop::Version:0x007f8ee8a9a730 @major=8, @minor=0, @patch=nil>
996
- # },
997
- #
998
- # @param [Symbol] primary_key Can be on of `{:udid | :launch_name}`.
999
- # @raise [RuntimeError] If called when Xcode 6 is _not_ the active Xcode version.
1000
- # @raise [RuntimeError] If called with an invalid `primary_key`.
1001
- def sim_details(primary_key)
1002
- unless xcode_version_gte_6?
1003
- raise RuntimeError, 'this method is only available on Xcode >= 6'
1004
- end
1005
-
1006
- allowed = [:udid, :launch_name]
1007
- unless allowed.include? primary_key
1008
- raise ArgumentError, "expected '#{primary_key}' to be one of '#{allowed}'"
1009
- end
1010
-
1011
- hash = {}
1012
-
1013
- simulators.each do |device|
1014
- launch_name = device.instruments_identifier(xcode)
1015
- udid = device.udid
1016
- value = {
1017
- :launch_name => device.instruments_identifier(xcode),
1018
- :udid => device.udid,
1019
- :sdk_version => device.version
1020
-
1021
- }
1022
-
1023
- if primary_key == :udid
1024
- key = udid
1025
- else
1026
- key = launch_name
1027
- end
1028
- hash[key] = value
1029
- end
1030
- hash
1031
- end
1032
-
1033
- # @!visibility private
1034
- # Uses the `simctl erase` command to reset a simulator content and settings.
1035
- # If no `sim_udid` is nil, _all_ simulators are reset.
1036
- #
1037
- # # @note This is an Xcode 6 only method. It will raise an error if called on
1038
- # Xcode < 6.
1039
- #
1040
- # @note This method will quit the simulator.
1041
- #
1042
- # @param [String] sim_udid The udid of the simulator that will be reset.
1043
- # If sim_udid is nil, _all_ simulators will be reset.
1044
- # @raise [RuntimeError] If called on Xcode < 6.
1045
- # @raise [RuntimeError] If `sim_udid` is not a valid simulator udid. Valid
1046
- # simulator udids are determined by calling `simctl list`.
1047
- def simctl_reset(sim_udid = nil)
1048
- unless xcode_version_gte_6?
1049
- raise RuntimeError, 'this method is only available on Xcode >= 6'
1050
- end
1051
-
1052
- quit_sim
1053
-
1054
- sim_details = sim_details(:udid)
1055
-
1056
- simctl_erase = lambda { |udid|
1057
- args = "simctl erase #{udid}".split(' ')
1058
- Open3.popen3('xcrun', *args) do |_, stdout, stderr, wait_thr|
1059
- out = stdout.read.strip
1060
- err = stderr.read.strip
1061
- if ENV['DEBUG_UNIX_CALLS'] == '1'
1062
- cmd = "xcrun simctl erase #{udid}"
1063
- puts "#{cmd} => stdout: '#{out}' | stderr: '#{err}'"
1064
- end
1065
- wait_thr.value.success?
1066
- end
1067
- }
1068
-
1069
- # Call erase on all simulators
1070
- if sim_udid.nil?
1071
- res = []
1072
- sim_details.each_key do |key|
1073
- res << simctl_erase.call(key)
1074
- end
1075
- res.all?
1076
- else
1077
- if sim_details[sim_udid]
1078
- simctl_erase.call(sim_udid)
1079
- else
1080
- raise "Could not find simulator with udid '#{sim_udid}'"
1081
- end
1082
- end
1083
- end
1084
-
1085
- # @!visibility private
1086
- #
1087
- # A ruby interface to the `simctl list` command.
1088
- #
1089
- # @note This is an Xcode >= 6.0 method.
1090
- # @raise [RuntimeError] if called on Xcode < 6.0
1091
- # @return [Hash] A hash whose primary key is a base SDK. For example,
1092
- # SDK 7.0.3 => "7.0". The value of the Hash will vary based on what is
1093
- # being listed.
1094
- def simctl_list(what)
1095
- unless xcode_version_gte_6?
1096
- raise RuntimeError, 'simctl is only available on Xcode >= 6'
1097
- end
1098
-
1099
- case what
1100
- when :devices
1101
- simctl_list_devices
1102
- when :runtimes
1103
- # The 'com.apple.CoreSimulator.SimRuntime.iOS-7-0' is the runtime-id,
1104
- # which can be used to create devices.
1105
- simctl_list_runtimes
1106
- else
1107
- allowed = [:devices, :runtimes]
1108
- raise ArgumentError, "expected '#{what}' to be one of '#{allowed}'"
1109
- end
1110
- end
1111
-
1112
- # @!visibility private
1113
- #
1114
- # Helper method for simctl_list.
1115
- #
1116
- # @example
1117
- # RunLoop::SimControl.new.simctl_list :devices
1118
- # {
1119
- # "7.1" =>
1120
- # [
1121
- # {
1122
- # :name => "iPhone 4s",
1123
- # :udid => "3BC5E3D7-9B81-4CE0-9C76-1888287F507B",
1124
- # :state => "Shutdown"
1125
- # }
1126
- # ],
1127
- # "8.0" => [
1128
- # {
1129
- # :name => "iPad 2",
1130
- # :udid => "D8F224D3-A59F-4F01-81AB-1959557A7E4E",
1131
- # :state => "Shutdown"
1132
- # }
1133
- # ]
1134
- # }
1135
- # @return [Hash<Array<Hash>>] Lists of available simulator details keyed by
1136
- # base sdk version.
1137
- # @see #simctl_list
1138
- def simctl_list_devices
1139
- # Ensure correct CoreSimulator service is installed.
1140
- RunLoop::Simctl.new
1141
- args = ["simctl", 'list', 'devices']
1142
- hash = xcrun.run_command_in_context(args)
1143
-
1144
- current_sdk = nil
1145
- simulators = {}
1146
-
1147
- out = hash[:out]
1148
-
1149
- out.split("\n").each do |line|
1150
-
1151
- not_ios = [
1152
- line[/Unavailable/, 0], # Unavailable SDK
1153
- line[/Apple Watch/, 0],
1154
- line[/watchOS/, 0],
1155
- line[/Apple TV/, 0],
1156
- line[/tvOS/, 0],
1157
- line[/Devices/, 0],
1158
- line[/CoreSimulatorService/, 0],
1159
- line[/simctl\[.+\]/, 0]
1160
- ].any?
1161
-
1162
- if not_ios
1163
- current_sdk = nil
1164
- next
1165
- end
1166
-
1167
- ios_sdk = line[VERSION_REGEX,0]
1168
- if ios_sdk
1169
- current_sdk = ios_sdk
1170
- simulators[current_sdk] = []
1171
- next
1172
- end
1173
-
1174
- if current_sdk
1175
- unless line[/unavailable/,0]
1176
- name = line.split('(').first.strip
1177
- udid = line[CORE_SIMULATOR_UDID_REGEX,0]
1178
- state = line[/(Booted|Shutdown)/,0]
1179
- simulators[current_sdk] << {
1180
- :name => name,
1181
- :udid => udid,
1182
- :state => state
1183
- }
1184
- end
1185
- end
1186
- end
1187
- simulators
1188
- end
1189
-
1190
- # @!visibility private
1191
- # Helper method for simctl_list
1192
- #
1193
- # @example
1194
- # RunLoop::SimControl.new.simctl_list :runtimes
1195
- # :iOS => {
1196
- # <Version 8.1> => {
1197
- # :name => "iOS",
1198
- # :runtime => "com.apple.CoreSimulator.SimRuntime.iOS-8-1",
1199
- # :complete => "iOS 8.1 (8.1 - 12B411) (com.apple.CoreSimulator.SimRuntime.iOS-8-1)"
1200
- # },
1201
- # ...
1202
- # },
1203
- #
1204
- # :tvOS => {
1205
- # <Version 9.0> => {
1206
- # :name => "tvOS",
1207
- # :runtime => "com.apple.CoreSimulator.SimRuntime.tvOS-9-0",
1208
- # :complete => "tvOS 9.0 (9.0 - 13T5365h) (com.apple.CoreSimulator.SimRuntime.tvOS-9-0)"
1209
- # },
1210
- # ...
1211
- # },
1212
- #
1213
- # :watchOS => {
1214
- # <Version 2.0> => {
1215
- # :name => "watchOS",
1216
- # :runtime => "com.apple.CoreSimulator.SimRuntime.watchOS-2-0",
1217
- # :complete => "watchOS 2.0 (2.0 - 13S343) (com.apple.CoreSimulator.SimRuntime.watchOS-2-0)"
1218
- # },
1219
- # ...
1220
- # }
1221
- #
1222
- # @see #simctl_list
1223
- def simctl_list_runtimes
1224
- # Ensure correct CoreSimulator service is installed.
1225
- RunLoop::Simctl.new
1226
- args = ["simctl", 'list', 'runtimes']
1227
- hash = xcrun.run_command_in_context(args)
1228
-
1229
- # Ex.
1230
- # == Runtimes ==
1231
- # iOS 7.0 (7.0.3 - 11B507) (com.apple.CoreSimulator.SimRuntime.iOS-7-0)
1232
- # iOS 7.1 (7.1 - 11D167) (com.apple.CoreSimulator.SimRuntime.iOS-7-1)
1233
- # iOS 8.0 (8.0 - 12A4331d) (com.apple.CoreSimulator.SimRuntime.iOS-8-0)
1234
-
1235
- out = hash[:out]
1236
-
1237
- runtimes = {}
1238
-
1239
- out.split("\n").each do |line|
1240
- next if line[/unavailable/, 0]
1241
- next if !line[/com.apple.CoreSimulator.SimRuntime/,0]
1242
-
1243
- tokens = line.split(' ')
1244
-
1245
- name = tokens.first
1246
-
1247
- key = name.to_sym
1248
-
1249
- unless runtimes[key]
1250
- runtimes[key] = {}
1251
- end
1252
-
1253
- version_str = tokens[1]
1254
- version = RunLoop::Version.new(version_str)
1255
-
1256
- runtime = line[/com.apple.CoreSimulator.SimRuntime.*/, 0].chomp(')')
1257
-
1258
- runtimes[key][version] =
1259
- {
1260
- :name => name,
1261
- :runtime => runtime,
1262
- :complete => line
1263
- }
1264
- end
1265
- runtimes
1266
- end
1267
- end
1268
- end