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.
@@ -42,27 +42,114 @@ module RunLoop
42
42
  debug = RunLoop::Environment.debug?
43
43
 
44
44
  sha = OpenSSL::Digest::SHA256.new
45
- self.recursive_glob_for_entries(path).each do |file|
46
- if File.directory?(file)
47
- # skip directories
48
- elsif !Pathname.new(file).exist?
49
- # skip broken symlinks
50
- else
51
- case File.ftype(file)
52
- when 'fifo'
53
- RunLoop.log_warn("SHA1 SKIPPING FIFO #{file}") if debug
54
- when 'socket'
55
- RunLoop.log_warn("SHA1 SKIPPING SOCKET #{file}") if debug
56
- when 'characterSpecial'
57
- RunLoop.log_warn("SHA1 SKIPPING CHAR SPECIAL #{file}") if debug
58
- when 'blockSpecial'
59
- RunLoop.log_warn("SHA1 SKIPPING BLOCK SPECIAL #{file}") if debug
60
- else
61
- sha << File.read(file)
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
+
@@ -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 XCTool instance
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
- def self.with_debugging(&block)
100
- original_value = ENV['DEBUG']
101
- ENV['DEBUG'] = '1'
102
- begin
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
@@ -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
+
@@ -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
- if debug_logging
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
- if debug_logging
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
- if debug_logging
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
- if RunLoop::Environment.debug?
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"
@@ -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'.split(' ')
1141
- Open3.popen3('xcrun', *args) do |_, stdout, stderr, _|
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
- current_sdk = nil
1150
- res = {}
1151
- out.split("\n").each do |line|
1147
+ current_sdk = nil
1148
+ simulators = {}
1152
1149
 
1153
- possible_sdk = line[VERSION_REGEX,0]
1154
- if possible_sdk
1155
- current_sdk = possible_sdk
1156
- res[current_sdk] = []
1157
- next
1158
- end
1150
+ out = hash[:out]
1159
1151
 
1160
- unavailable_sdk = line[/Unavailable/, 0]
1161
- if unavailable_sdk
1162
- current_sdk = nil
1163
- next
1164
- end
1152
+ out.split("\n").each do |line|
1165
1153
 
1166
- watch_os = line[/watchOS/, 0]
1167
- if watch_os
1168
- current_sdk = nil
1169
- next
1170
- end
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
- watch = line[/Apple Watch/, 0]
1173
- if watch
1174
- current_sdk = nil
1175
- next
1176
- end
1161
+ unavailable_sdk = line[/Unavailable/, 0]
1162
+ if unavailable_sdk
1163
+ current_sdk = nil
1164
+ next
1165
+ end
1177
1166
 
1178
- if current_sdk
1179
- unless line[/unavailable/,0]
1180
- name = line.split('(').first.strip
1181
- udid = line[CORE_SIMULATOR_UDID_REGEX,0]
1182
- state = line[/(Booted|Shutdown)/,0]
1183
- res[current_sdk] << {:name => name, :udid => udid, :state => state}
1184
- end
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
- # "7.0" => {
1198
- # :sdk => "7.0.3",
1199
- # :runtime => "com.apple.CoreSimulator.SimRuntime.iOS-7-0"
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
- # "8.0" => {
1206
- # :sdk => "8.0",
1207
- # :runtime => "com.apple.CoreSimulator.SimRuntime.iOS-8-0"
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
- # The 'com.apple.CoreSimulator.SimRuntime.iOS-7-0' is the runtime-id,
1213
- # which can be used to create devices.
1214
- args = 'simctl list runtimes'.split(' ')
1215
- Open3.popen3('xcrun', *args) do |_, stdout, stderr, _|
1216
- out = stdout.read.strip
1217
- err = stderr.read.strip
1218
- if ENV['DEBUG_UNIX_CALLS'] == '1'
1219
- cmd = "xcrun #{args.join(' ')}"
1220
- puts "#{cmd} => stdout: '#{out}' | stderr: '#{err}'"
1221
- end
1222
- # Ex.
1223
- # == Runtimes ==
1224
- # iOS 7.0 (7.0.3 - 11B507) (com.apple.CoreSimulator.SimRuntime.iOS-7-0)
1225
- # iOS 7.1 (7.1 - 11D167) (com.apple.CoreSimulator.SimRuntime.iOS-7-1)
1226
- # iOS 8.0 (8.0 - 12A4331d) (com.apple.CoreSimulator.SimRuntime.iOS-8-0)
1227
- lines = out.split("\n").delete_if { |line| not line =~ /com.apple.CoreSimulator.SimRuntime/ }
1228
- res = {}
1229
- lines.each do |line|
1230
- key = line[/iOS (\d.\d)/,1]
1231
- sdk_version = line[/(\d.\d)(.\d)?\s-/, 0].tr(' -','')
1232
- runtime = line[/com.apple.CoreSimulator.SimRuntime.iOS-\d-\d/,0]
1233
- value = {:sdk => sdk_version, :runtime => runtime}
1234
- res[key] = value
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
- res
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