run_loop 1.5.5 → 1.5.6.pre1
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/cli/simctl.rb +37 -82
- data/lib/run_loop/core.rb +20 -17
- data/lib/run_loop/core_simulator.rb +648 -0
- data/lib/run_loop/device.rb +8 -8
- data/lib/run_loop/directory.rb +104 -17
- data/lib/run_loop/dot_dir.rb +83 -0
- data/lib/run_loop/environment.rb +27 -8
- data/lib/run_loop/instruments.rb +89 -1
- data/lib/run_loop/logging.rb +11 -0
- data/lib/run_loop/process_terminator.rb +5 -17
- data/lib/run_loop/sim_control.rb +114 -77
- data/lib/run_loop/version.rb +15 -3
- data/lib/run_loop.rb +9 -10
- metadata +6 -7
- data/lib/run_loop/life_cycle/core_simulator.rb +0 -515
- data/lib/run_loop/life_cycle/simulator.rb +0 -73
- data/lib/run_loop/simctl/bridge.rb +0 -499
data/lib/run_loop/directory.rb
CHANGED
@@ -42,27 +42,114 @@ module RunLoop
|
|
42
42
|
debug = RunLoop::Environment.debug?
|
43
43
|
|
44
44
|
sha = OpenSSL::Digest::SHA256.new
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
45
|
+
entries.each do |file|
|
46
|
+
unless self.skip_file?(file, 'SHA1', debug)
|
47
|
+
begin
|
48
|
+
sha << File.read(file)
|
49
|
+
rescue => e
|
50
|
+
if debug
|
51
|
+
RunLoop.log_warn(%Q{
|
52
|
+
RunLoop::Directory.directory_digest raised an error:
|
53
|
+
|
54
|
+
#{e}
|
55
|
+
|
56
|
+
while trying to find the SHA of this file:
|
57
|
+
|
58
|
+
#{file}
|
59
|
+
|
60
|
+
Please report this here:
|
61
|
+
|
62
|
+
https://github.com/calabash/run_loop/issues
|
63
|
+
|
64
|
+
})
|
65
|
+
end
|
62
66
|
end
|
63
67
|
end
|
64
68
|
end
|
65
69
|
sha.hexdigest
|
66
70
|
end
|
71
|
+
|
72
|
+
def self.size(path, format)
|
73
|
+
|
74
|
+
allowed_formats = [:bytes, :kb, :mb, :gb]
|
75
|
+
unless allowed_formats.include?(format)
|
76
|
+
raise ArgumentError, "Expected '#{format}' to be one of #{allowed_formats.join(', ')}"
|
77
|
+
end
|
78
|
+
|
79
|
+
unless File.exist?(path)
|
80
|
+
raise ArgumentError, "Expected '#{path}' to exist"
|
81
|
+
end
|
82
|
+
|
83
|
+
unless File.directory?(path)
|
84
|
+
raise ArgumentError, "Expected '#{path}' to be a directory"
|
85
|
+
end
|
86
|
+
|
87
|
+
entries = self.recursive_glob_for_entries(path)
|
88
|
+
|
89
|
+
if entries.empty?
|
90
|
+
raise ArgumentError, "Expected a non-empty dir at '#{path}' found '#{entries}'"
|
91
|
+
end
|
92
|
+
|
93
|
+
size = self.iterate_for_size(entries)
|
94
|
+
|
95
|
+
case format
|
96
|
+
when :bytes
|
97
|
+
size
|
98
|
+
when :kb
|
99
|
+
size/1000.0
|
100
|
+
when :mb
|
101
|
+
size/1000.0/1000.0
|
102
|
+
when :gb
|
103
|
+
size/1000.0/1000.0/1000.0
|
104
|
+
else
|
105
|
+
# Not expected to reach this.
|
106
|
+
size
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def self.skip_file?(file, task, debug)
|
113
|
+
skip = false
|
114
|
+
if File.directory?(file)
|
115
|
+
# Skip directories
|
116
|
+
skip = true
|
117
|
+
elsif !Pathname.new(file).exist?
|
118
|
+
# Skip broken symlinks
|
119
|
+
skip = true
|
120
|
+
elsif !File.exist?(file)
|
121
|
+
# Skip files that don't exist
|
122
|
+
skip = true
|
123
|
+
else
|
124
|
+
case File.ftype(file)
|
125
|
+
when 'fifo'
|
126
|
+
RunLoop.log_warn("#{task} IS SKIPPING FIFO #{file}") if debug
|
127
|
+
skip = true
|
128
|
+
when 'socket'
|
129
|
+
RunLoop.log_warn("#{task} IS SKIPPING SOCKET #{file}") if debug
|
130
|
+
skip = true
|
131
|
+
when 'characterSpecial'
|
132
|
+
RunLoop.log_warn("#{task} IS SKIPPING CHAR SPECIAL #{file}") if debug
|
133
|
+
skip = true
|
134
|
+
when 'blockSpecial'
|
135
|
+
skip = true
|
136
|
+
RunLoop.log_warn("#{task} SKIPPING BLOCK SPECIAL #{file}") if debug
|
137
|
+
else
|
138
|
+
|
139
|
+
end
|
140
|
+
end
|
141
|
+
skip
|
142
|
+
end
|
143
|
+
|
144
|
+
def self.iterate_for_size(entries)
|
145
|
+
debug = RunLoop::Environment.debug?
|
146
|
+
size = 0
|
147
|
+
entries.each do |file|
|
148
|
+
unless self.skip_file?(file, "SIZE", debug)
|
149
|
+
size = size + File.size(file)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
size
|
153
|
+
end
|
67
154
|
end
|
68
155
|
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# A module for managing the ~/.run-loop directory.
|
2
|
+
module RunLoop::DotDir
|
3
|
+
|
4
|
+
def self.directory
|
5
|
+
home = RunLoop::Environment.user_home_directory
|
6
|
+
dir = File.join(home, ".run-loop")
|
7
|
+
if !File.exist?(dir)
|
8
|
+
FileUtils.mkdir_p(dir)
|
9
|
+
end
|
10
|
+
dir
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.make_results_dir
|
14
|
+
if RunLoop::Environment.xtc?
|
15
|
+
next_results_dir = Dir.mktmpdir("run_loop")
|
16
|
+
else
|
17
|
+
results_dir = File.join(self.directory, 'results')
|
18
|
+
next_results_dir = self.next_timestamped_dirname(results_dir)
|
19
|
+
FileUtils.mkdir_p(next_results_dir)
|
20
|
+
end
|
21
|
+
|
22
|
+
next_results_dir
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.rotate_result_directories
|
26
|
+
return :xtc if RunLoop::Environment.xtc?
|
27
|
+
|
28
|
+
start = Time.now
|
29
|
+
|
30
|
+
glob = "#{self.directory}/results/*"
|
31
|
+
|
32
|
+
RunLoop.log_debug("Searching for run-loop results with glob: #{glob}")
|
33
|
+
|
34
|
+
directories = Dir.glob(glob).select do |path|
|
35
|
+
File.directory?(path)
|
36
|
+
end
|
37
|
+
|
38
|
+
oldest_first = directories.sort_by { |f| File.mtime(f) }
|
39
|
+
|
40
|
+
RunLoop.log_debug("Found #{oldest_first.count} previous run-loop results")
|
41
|
+
oldest_first.pop(5)
|
42
|
+
|
43
|
+
RunLoop.log_debug("Will delete #{oldest_first.count} previous run-loop results")
|
44
|
+
|
45
|
+
oldest_first.each do |path|
|
46
|
+
FileUtils.rm_rf(path)
|
47
|
+
end
|
48
|
+
|
49
|
+
elapsed = Time.now - start
|
50
|
+
|
51
|
+
RunLoop.log_debug("Deleted #{oldest_first.count} previous results in #{elapsed} seconds")
|
52
|
+
rescue StandardError => e
|
53
|
+
RunLoop.log_error("While rotating previous results, encounterd: #{e}")
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def self.timestamped_dirname(plus_seconds = 0)
|
59
|
+
(Time.now + plus_seconds).strftime("%Y-%m-%d_%H-%M-%S")
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.next_timestamped_dirname(base_dir)
|
63
|
+
dir = File.join(base_dir, self.timestamped_dirname)
|
64
|
+
return dir if !File.exist?(dir)
|
65
|
+
|
66
|
+
# Rather than wait, just increment the second. Per-second accuracy
|
67
|
+
# is not important; uniqueness is.
|
68
|
+
counter = 0
|
69
|
+
loop do
|
70
|
+
break if !File.exist?(dir)
|
71
|
+
break if counter == 4
|
72
|
+
counter = counter + 1
|
73
|
+
dir = File.join(base_dir, self.timestamped_dirname(counter))
|
74
|
+
end
|
75
|
+
|
76
|
+
# If all else fails, just return a unique UUID
|
77
|
+
if File.exist?(dir)
|
78
|
+
dir = File.join(base_dir, SecureRandom.uuid)
|
79
|
+
end
|
80
|
+
dir
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
data/lib/run_loop/environment.rb
CHANGED
@@ -1,6 +1,18 @@
|
|
1
1
|
module RunLoop
|
2
2
|
class Environment
|
3
3
|
|
4
|
+
# Returns the user home directory
|
5
|
+
def self.user_home_directory
|
6
|
+
if self.xtc?
|
7
|
+
home = File.join("./", "tmp", "home")
|
8
|
+
FileUtils.mkdir_p(home)
|
9
|
+
home
|
10
|
+
else
|
11
|
+
require 'etc'
|
12
|
+
Etc.getpwuid.dir
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
4
16
|
# Returns true if debugging is enabled.
|
5
17
|
def self.debug?
|
6
18
|
ENV['DEBUG'] == '1'
|
@@ -51,13 +63,13 @@ module RunLoop
|
|
51
63
|
if !value || value == ''
|
52
64
|
nil
|
53
65
|
else
|
54
|
-
value
|
66
|
+
File.expand_path(value)
|
55
67
|
end
|
56
68
|
end
|
57
69
|
|
58
70
|
# Returns the value of DEVELOPER_DIR
|
59
71
|
#
|
60
|
-
# @note Never call this directly. Always create an
|
72
|
+
# @note Never call this directly. Always create an Xcode instance
|
61
73
|
# and allow it to derive the path to the Xcode toolchain.
|
62
74
|
def self.developer_dir
|
63
75
|
value = ENV['DEVELOPER_DIR']
|
@@ -96,13 +108,20 @@ module RunLoop
|
|
96
108
|
end
|
97
109
|
end
|
98
110
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
111
|
+
# !@visibility private
|
112
|
+
def self.with_debugging(debug, &block)
|
113
|
+
if debug
|
114
|
+
original_value = ENV['DEBUG']
|
115
|
+
|
116
|
+
begin
|
117
|
+
ENV['DEBUG'] = '1'
|
118
|
+
block.call
|
119
|
+
ensure
|
120
|
+
ENV['DEBUG'] = original_value
|
121
|
+
end
|
122
|
+
|
123
|
+
else
|
103
124
|
block.call
|
104
|
-
ensure
|
105
|
-
ENV['DEBUG'] = original_value
|
106
125
|
end
|
107
126
|
end
|
108
127
|
end
|
data/lib/run_loop/instruments.rb
CHANGED
@@ -7,6 +7,73 @@ module RunLoop
|
|
7
7
|
|
8
8
|
include RunLoop::Regex
|
9
9
|
|
10
|
+
# @!visibility private
|
11
|
+
#
|
12
|
+
# Rotates xrtmp__ directories in /Library/Caches/com.apple.dt.instruments
|
13
|
+
# keeping the last 5. On CI systems these can be hundreds of gigabytes.
|
14
|
+
def self.rotate_cache_directories
|
15
|
+
|
16
|
+
# Never run on the XTC
|
17
|
+
return :xtc if RunLoop::Environment.xtc?
|
18
|
+
|
19
|
+
cache = self.library_cache_dir
|
20
|
+
|
21
|
+
# If the directory does not exist, do nothing.
|
22
|
+
return :no_cache if !cache || !File.exist?(cache)
|
23
|
+
|
24
|
+
start = Time.now
|
25
|
+
|
26
|
+
glob = "#{cache}/xrtmp__*"
|
27
|
+
|
28
|
+
RunLoop.log_debug("Searching for instruments caches with glob: #{glob}")
|
29
|
+
|
30
|
+
directories = Dir.glob(glob).select do |path|
|
31
|
+
File.directory?(path)
|
32
|
+
end
|
33
|
+
|
34
|
+
log_progress = false
|
35
|
+
if directories.count > 25
|
36
|
+
RunLoop.log_info2("Found #{directories.count} instruments caches: ~#{20 * directories.count} Mb")
|
37
|
+
RunLoop.log_info2("Deleting them could take a long time.")
|
38
|
+
RunLoop.log_info2("This delay will only happen once!")
|
39
|
+
RunLoop.log_info2("Please be patient and allow the directories to be deleted")
|
40
|
+
log_progress = true
|
41
|
+
else
|
42
|
+
RunLoop.log_debug("Found #{directories.count} instruments caches")
|
43
|
+
end
|
44
|
+
|
45
|
+
if log_progress
|
46
|
+
RunLoop.log_info2("Sorting instruments caches by modification time...")
|
47
|
+
end
|
48
|
+
|
49
|
+
oldest_first = directories.sort_by { |f| File.mtime(f) }
|
50
|
+
|
51
|
+
oldest_first.pop(5)
|
52
|
+
|
53
|
+
if log_progress
|
54
|
+
RunLoop.log_info2("Will delete #{oldest_first.count} instruments caches...")
|
55
|
+
else
|
56
|
+
RunLoop.log_debug("Will delete #{oldest_first.count} instruments caches")
|
57
|
+
end
|
58
|
+
|
59
|
+
oldest_first.each do |path|
|
60
|
+
FileUtils.rm_rf(path)
|
61
|
+
if log_progress
|
62
|
+
printf "."
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
elapsed = Time.now - start
|
67
|
+
|
68
|
+
if log_progress
|
69
|
+
puts ""
|
70
|
+
RunLoop.log_info2("Deleted #{oldest_first.count} instruments caches in #{elapsed} seconds")
|
71
|
+
else
|
72
|
+
RunLoop.log_debug("Deleted #{oldest_first.count} instruments caches in #{elapsed} seconds")
|
73
|
+
end
|
74
|
+
true
|
75
|
+
end
|
76
|
+
|
10
77
|
attr_reader :xcode
|
11
78
|
|
12
79
|
def pbuddy
|
@@ -192,7 +259,8 @@ Please update your sources to pass an instance of RunLoop::Xcode))
|
|
192
259
|
fetch_devices[:out].chomp.split("\n").map do |line|
|
193
260
|
stripped = line.strip
|
194
261
|
if line_is_simulator?(stripped) &&
|
195
|
-
!line_is_simulator_paired_with_watch?(stripped)
|
262
|
+
!line_is_simulator_paired_with_watch?(stripped) &&
|
263
|
+
!line_is_apple_tv?(stripped)
|
196
264
|
|
197
265
|
version = stripped[VERSION_REGEX, 0]
|
198
266
|
|
@@ -402,6 +470,11 @@ Please update your sources to pass an instance of RunLoop::Xcode))
|
|
402
470
|
line[CORE_SIMULATOR_UDID_REGEX, 0] && line[/Apple Watch/, 0]
|
403
471
|
end
|
404
472
|
|
473
|
+
# @!visibility private
|
474
|
+
def line_is_apple_tv?(line)
|
475
|
+
line[/Apple TV/, 0]
|
476
|
+
end
|
477
|
+
|
405
478
|
# @!visibility private
|
406
479
|
def path_to_instruments_app_plist
|
407
480
|
@path_to_instruments_app_plist ||=
|
@@ -412,5 +485,20 @@ Please update your sources to pass an instance of RunLoop::Xcode))
|
|
412
485
|
'Contents',
|
413
486
|
'Info.plist'))
|
414
487
|
end
|
488
|
+
|
489
|
+
# @!visibility private
|
490
|
+
#
|
491
|
+
# Instruments caches files in this directory and it can become quite large
|
492
|
+
# over time; particularly on CI system.
|
493
|
+
def self.library_cache_dir
|
494
|
+
path = "/Library/Caches/com.apple.dt.instruments"
|
495
|
+
|
496
|
+
if File.exist?(path)
|
497
|
+
path
|
498
|
+
else
|
499
|
+
nil
|
500
|
+
end
|
501
|
+
end
|
415
502
|
end
|
416
503
|
end
|
504
|
+
|
data/lib/run_loop/logging.rb
CHANGED
@@ -59,6 +59,12 @@ module RunLoop
|
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
62
|
+
# .log_info is already taken by the XTC logger. (>_O)
|
63
|
+
# green
|
64
|
+
def self.log_info2(msg)
|
65
|
+
puts self.green(" INFO: #{msg}") if msg
|
66
|
+
end
|
67
|
+
|
62
68
|
# red
|
63
69
|
def self.log_error(msg)
|
64
70
|
puts self.red("ERROR: #{msg}") if msg
|
@@ -101,4 +107,9 @@ module RunLoop
|
|
101
107
|
def self.cyan(string)
|
102
108
|
colorize(string, 36)
|
103
109
|
end
|
110
|
+
|
111
|
+
# @!visibility private
|
112
|
+
def self.green(string)
|
113
|
+
colorize(string, 32)
|
114
|
+
end
|
104
115
|
end
|
@@ -55,32 +55,22 @@ module RunLoop
|
|
55
55
|
def kill_process
|
56
56
|
return true unless process_alive?
|
57
57
|
|
58
|
-
debug_logging = RunLoop::Environment.debug?
|
59
58
|
begin
|
60
|
-
|
61
|
-
puts "Sending '#{kill_signal}' to #{display_name} process '#{pid}'"
|
62
|
-
end
|
59
|
+
RunLoop.log_debug("Sending '#{kill_signal}' to #{display_name} process '#{pid}'")
|
63
60
|
Process.kill(kill_signal, pid.to_i)
|
64
61
|
# Don't wait.
|
65
62
|
# We might not own this process and a WNOHANG would be a nop.
|
66
63
|
# Process.wait(pid, Process::WNOHANG)
|
67
64
|
rescue Errno::ESRCH
|
68
|
-
|
69
|
-
puts "Process with pid '#{pid}' does not exist; nothing to do."
|
70
|
-
end
|
65
|
+
RunLoop.log_debug("Process with pid '#{pid}' does not exist; nothing to do.")
|
71
66
|
# Return early; there is no need to wait if the process does not exist.
|
72
67
|
return true
|
73
68
|
rescue Errno::EPERM
|
74
|
-
|
75
|
-
puts "Cannot kill process '#{pid}' with '#{kill_signal}'; not a child of this process"
|
76
|
-
end
|
69
|
+
RunLoop.log_debug("Cannot kill process '#{pid}' with '#{kill_signal}'; not a child of this process")
|
77
70
|
rescue SignalException => e
|
78
71
|
raise e.message
|
79
72
|
end
|
80
|
-
|
81
|
-
if debug_logging
|
82
|
-
puts "Waiting for #{display_name} '#{pid}' to terminate"
|
83
|
-
end
|
73
|
+
RunLoop.log_debug("Waiting for #{display_name} with pid '#{pid}' to terminate")
|
84
74
|
wait_for_process_to_terminate
|
85
75
|
end
|
86
76
|
|
@@ -127,9 +117,7 @@ module RunLoop
|
|
127
117
|
sleep delay
|
128
118
|
end
|
129
119
|
|
130
|
-
|
131
|
-
puts "Waited for #{Time.now - now} seconds for #{display_name} with '#{pid}' to terminate"
|
132
|
-
end
|
120
|
+
RunLoop.log_debug("Waited for #{Time.now - now} seconds for #{display_name} with pid '#{pid}' to terminate")
|
133
121
|
|
134
122
|
if @options[:raise_on_no_terminate] and !has_terminated
|
135
123
|
raise "Waited #{options[:timeout]} seconds for #{display_name} (#{ps_details}) to terminate"
|
data/lib/run_loop/sim_control.rb
CHANGED
@@ -52,6 +52,10 @@ module RunLoop
|
|
52
52
|
xcode.developer_dir
|
53
53
|
end
|
54
54
|
|
55
|
+
def xcrun
|
56
|
+
@xcrun ||= RunLoop::Xcrun.new
|
57
|
+
end
|
58
|
+
|
55
59
|
# Return an instance of PlistBuddy.
|
56
60
|
# @return [RunLoop::PlistBuddy] The plist buddy instance that is used internally.
|
57
61
|
def pbuddy
|
@@ -1137,55 +1141,61 @@ module RunLoop
|
|
1137
1141
|
# base sdk version.
|
1138
1142
|
# @see #simctl_list
|
1139
1143
|
def simctl_list_devices
|
1140
|
-
args = 'simctl list devices'
|
1141
|
-
|
1142
|
-
out = stdout.read.strip
|
1143
|
-
err = stderr.read.strip
|
1144
|
-
if ENV['DEBUG_UNIX_CALLS'] == '1'
|
1145
|
-
cmd = "xcrun #{args.join(' ')}"
|
1146
|
-
puts "#{cmd} => stdout: '#{out}' | stderr: '#{err}'"
|
1147
|
-
end
|
1144
|
+
args = ['simctl', 'list', 'devices']
|
1145
|
+
hash = xcrun.exec(args)
|
1148
1146
|
|
1149
|
-
|
1150
|
-
|
1151
|
-
out.split("\n").each do |line|
|
1147
|
+
current_sdk = nil
|
1148
|
+
simulators = {}
|
1152
1149
|
|
1153
|
-
|
1154
|
-
if possible_sdk
|
1155
|
-
current_sdk = possible_sdk
|
1156
|
-
res[current_sdk] = []
|
1157
|
-
next
|
1158
|
-
end
|
1150
|
+
out = hash[:out]
|
1159
1151
|
|
1160
|
-
|
1161
|
-
if unavailable_sdk
|
1162
|
-
current_sdk = nil
|
1163
|
-
next
|
1164
|
-
end
|
1152
|
+
out.split("\n").each do |line|
|
1165
1153
|
|
1166
|
-
|
1167
|
-
|
1168
|
-
|
1169
|
-
|
1170
|
-
|
1154
|
+
possible_sdk = line[VERSION_REGEX,0]
|
1155
|
+
if possible_sdk
|
1156
|
+
current_sdk = possible_sdk
|
1157
|
+
simulators[current_sdk] = []
|
1158
|
+
next
|
1159
|
+
end
|
1171
1160
|
|
1172
|
-
|
1173
|
-
|
1174
|
-
|
1175
|
-
|
1176
|
-
|
1161
|
+
unavailable_sdk = line[/Unavailable/, 0]
|
1162
|
+
if unavailable_sdk
|
1163
|
+
current_sdk = nil
|
1164
|
+
next
|
1165
|
+
end
|
1177
1166
|
|
1178
|
-
|
1179
|
-
|
1180
|
-
|
1181
|
-
|
1182
|
-
|
1183
|
-
|
1184
|
-
|
1167
|
+
watch_os = line[/watchOS/, 0]
|
1168
|
+
if watch_os
|
1169
|
+
current_sdk = nil
|
1170
|
+
next
|
1171
|
+
end
|
1172
|
+
|
1173
|
+
watch = line[/Apple Watch/, 0]
|
1174
|
+
if watch
|
1175
|
+
current_sdk = nil
|
1176
|
+
next
|
1177
|
+
end
|
1178
|
+
|
1179
|
+
tv = line[/Apple TV/, 0]
|
1180
|
+
if tv
|
1181
|
+
current_sdk = nil
|
1182
|
+
next
|
1183
|
+
end
|
1184
|
+
|
1185
|
+
if current_sdk
|
1186
|
+
unless line[/unavailable/,0]
|
1187
|
+
name = line.split('(').first.strip
|
1188
|
+
udid = line[CORE_SIMULATOR_UDID_REGEX,0]
|
1189
|
+
state = line[/(Booted|Shutdown)/,0]
|
1190
|
+
simulators[current_sdk] << {
|
1191
|
+
:name => name,
|
1192
|
+
:udid => udid,
|
1193
|
+
:state => state
|
1194
|
+
}
|
1185
1195
|
end
|
1186
1196
|
end
|
1187
|
-
res
|
1188
1197
|
end
|
1198
|
+
simulators
|
1189
1199
|
end
|
1190
1200
|
|
1191
1201
|
# @!visibility private
|
@@ -1193,48 +1203,75 @@ module RunLoop
|
|
1193
1203
|
#
|
1194
1204
|
# @example
|
1195
1205
|
# RunLoop::SimControl.new.simctl_list :runtimes
|
1196
|
-
#
|
1197
|
-
#
|
1198
|
-
#
|
1199
|
-
#
|
1200
|
-
#
|
1201
|
-
# "7.1" => {
|
1202
|
-
# :sdk => "7.1",
|
1203
|
-
# :runtime => "com.apple.CoreSimulator.SimRuntime.iOS-7-1"
|
1206
|
+
# :iOS => {
|
1207
|
+
# <Version 8.1> => {
|
1208
|
+
# :name => "iOS",
|
1209
|
+
# :runtime => "com.apple.CoreSimulator.SimRuntime.iOS-8-1",
|
1210
|
+
# :complete => "iOS 8.1 (8.1 - 12B411) (com.apple.CoreSimulator.SimRuntime.iOS-8-1)"
|
1204
1211
|
# },
|
1205
|
-
#
|
1206
|
-
#
|
1207
|
-
#
|
1208
|
-
#
|
1209
|
-
#
|
1212
|
+
# ...
|
1213
|
+
# },
|
1214
|
+
#
|
1215
|
+
# :tvOS => {
|
1216
|
+
# <Version 9.0> => {
|
1217
|
+
# :name => "tvOS",
|
1218
|
+
# :runtime => "com.apple.CoreSimulator.SimRuntime.tvOS-9-0",
|
1219
|
+
# :complete => "tvOS 9.0 (9.0 - 13T5365h) (com.apple.CoreSimulator.SimRuntime.tvOS-9-0)"
|
1220
|
+
# },
|
1221
|
+
# ...
|
1222
|
+
# },
|
1223
|
+
#
|
1224
|
+
# :watchOS => {
|
1225
|
+
# <Version 2.0> => {
|
1226
|
+
# :name => "watchOS",
|
1227
|
+
# :runtime => "com.apple.CoreSimulator.SimRuntime.watchOS-2-0",
|
1228
|
+
# :complete => "watchOS 2.0 (2.0 - 13S343) (com.apple.CoreSimulator.SimRuntime.watchOS-2-0)"
|
1229
|
+
# },
|
1230
|
+
# ...
|
1231
|
+
# }
|
1232
|
+
#
|
1210
1233
|
# @see #simctl_list
|
1211
1234
|
def simctl_list_runtimes
|
1212
|
-
|
1213
|
-
|
1214
|
-
|
1215
|
-
|
1216
|
-
|
1217
|
-
|
1218
|
-
|
1219
|
-
|
1220
|
-
|
1221
|
-
|
1222
|
-
|
1223
|
-
|
1224
|
-
|
1225
|
-
|
1226
|
-
|
1227
|
-
|
1228
|
-
|
1229
|
-
|
1230
|
-
|
1231
|
-
|
1232
|
-
|
1233
|
-
|
1234
|
-
|
1235
|
+
args = ['simctl', 'list', 'runtimes']
|
1236
|
+
hash = xcrun.exec(args)
|
1237
|
+
|
1238
|
+
# Ex.
|
1239
|
+
# == Runtimes ==
|
1240
|
+
# iOS 7.0 (7.0.3 - 11B507) (com.apple.CoreSimulator.SimRuntime.iOS-7-0)
|
1241
|
+
# iOS 7.1 (7.1 - 11D167) (com.apple.CoreSimulator.SimRuntime.iOS-7-1)
|
1242
|
+
# iOS 8.0 (8.0 - 12A4331d) (com.apple.CoreSimulator.SimRuntime.iOS-8-0)
|
1243
|
+
|
1244
|
+
out = hash[:out]
|
1245
|
+
|
1246
|
+
runtimes = {}
|
1247
|
+
|
1248
|
+
out.split("\n").each do |line|
|
1249
|
+
next if line[/unavailable/, 0]
|
1250
|
+
next if !line[/com.apple.CoreSimulator.SimRuntime/,0]
|
1251
|
+
|
1252
|
+
tokens = line.split(' ')
|
1253
|
+
|
1254
|
+
name = tokens.first
|
1255
|
+
|
1256
|
+
key = name.to_sym
|
1257
|
+
|
1258
|
+
unless runtimes[key]
|
1259
|
+
runtimes[key] = {}
|
1235
1260
|
end
|
1236
|
-
|
1261
|
+
|
1262
|
+
version_str = tokens[1]
|
1263
|
+
version = RunLoop::Version.new(version_str)
|
1264
|
+
|
1265
|
+
runtime = line[/com.apple.CoreSimulator.SimRuntime.*/, 0].chomp(')')
|
1266
|
+
|
1267
|
+
runtimes[key][version] =
|
1268
|
+
{
|
1269
|
+
:name => name,
|
1270
|
+
:runtime => runtime,
|
1271
|
+
:complete => line
|
1272
|
+
}
|
1237
1273
|
end
|
1274
|
+
runtimes
|
1238
1275
|
end
|
1239
1276
|
end
|
1240
1277
|
end
|