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.
- checksums.yaml +4 -4
- data/lib/run_loop.rb +0 -38
- data/lib/run_loop/app.rb +37 -1
- data/lib/run_loop/cli/idm.rb +71 -0
- data/lib/run_loop/cli/simctl.rb +2 -2
- data/lib/run_loop/core.rb +12 -156
- data/lib/run_loop/core_simulator.rb +17 -9
- data/lib/run_loop/device.rb +39 -18
- data/lib/run_loop/device_agent/Frameworks.zip +0 -0
- data/lib/run_loop/device_agent/app/DeviceAgent-Runner.app.zip +0 -0
- data/lib/run_loop/device_agent/bin/iOSDeviceManager +0 -0
- data/lib/run_loop/device_agent/ipa/DeviceAgent-Runner.app.zip +0 -0
- data/lib/run_loop/language.rb +3 -0
- data/lib/run_loop/locale.rb +3 -0
- data/lib/run_loop/simctl.rb +95 -37
- data/lib/run_loop/strings.rb +15 -0
- data/lib/run_loop/version.rb +1 -1
- data/lib/run_loop/xcode.rb +34 -167
- metadata +4 -4
- data/lib/run_loop/sim_control.rb +0 -1268
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:
|
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-
|
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.
|
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
|
data/lib/run_loop/sim_control.rb
DELETED
@@ -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
|