run_loop 2.7.1 → 3.0.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.
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