Class: RunLoop::XCTools

Inherits:
Object
  • Object
show all
Defined in:
lib/run_loop/xctools.rb

Overview

TODO:

Refactor instruments related code to instruments class.

Note:

All command line tools are run in the context of ‘xcrun`.

A class for interacting with the Xcode tools.

Throughout this class’s documentation, there are references to the _current version of Xcode_. The current Xcode version is the one returned by ‘xcrun xcodebuild`. The current Xcode version can be set using `xcode-select` or overridden using the `DEVELOPER_DIR`.

Instance Method Summary collapse

Instance Method Details

#instruments(cmd = nil) ⇒ String, ...

Method for interacting with instruments.

Examples:

Getting a runnable command for instruments

instruments #=> 'xcrun instruments'

Getting a the version of instruments.

instruments(:version) #=> 5.1.1 - a Version object

Getting list of known simulators.

instruments(:sims) #=> < list of known simulators >

Getting list of physical devices.

instruments(:devices) #> < list of physical devices >

Raises:

  • (ArgumentError)

    if invalid ‘cmd` is passed



195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
# File 'lib/run_loop/xctools.rb', line 195

def instruments(cmd=nil)
  instruments = 'xcrun instruments'
  return instruments if cmd == nil

  # Xcode 6 GM is spamming "WebKit Threading Violations"
  stderr_filter = lambda { |stderr|
    stderr.read.strip.split("\n").each do |line|
      unless line[/WebKit Threading Violation/, 0]
        $stderr.puts line
      end
    end
  }
  case cmd
    when :version
      @instruments_version ||= lambda {
        # Xcode 6 can print out some very strange output, so we have to retry.
        Retriable.retriable({:tries => 5}) do
          Open3.popen3("#{instruments}") do |_, _, stderr, _|
            version_str = stderr.read.chomp.split(/\s/)[2]
            RunLoop::Version.new(version_str)
          end
        end
      }.call
    when :sims
      @instruments_sims ||=  lambda {
        # Instruments 6 spams a lot of error messages.  I don't like to
        # hide them, but they seem to be around to stay (Xcode 6 GM).
        cmd = "#{instruments} -s devices"
        Open3.popen3(cmd) do |_, stdout, stderr, _|
          stderr_filter.call(stderr)
          devices = stdout.read.chomp.split("\n")
          devices.select { |device| device.downcase.include?('simulator') }
        end
      }.call

    when :templates
      @instruments_templates ||= lambda {
        cmd = "#{instruments} -s templates"
        if self.xcode_version >= self.v60
          Open3.popen3(cmd) do |_, stdout, stderr, _|
            stderr_filter.call(stderr)
            stdout.read.chomp.split("\n").map { |elm| elm.strip.tr('"', '') }
          end
        elsif self.xcode_version >= self.v51
          `#{cmd}`.split("\n").delete_if do |path|
            not path =~ /tracetemplate/
          end.map { |elm| elm.strip }
        else
          # prints to $stderr (>_>) - seriously?
          Open3.popen3(cmd) do |_, _, stderr, _|
            stderr.read.chomp.split(/(,|\(|")/).map do |elm|
               elm.strip
            end.delete_if { |path| not path =~ /tracetemplate/ }
          end
        end
      }.call

    when :devices
      @devices ||= lambda {
        cmd = "#{instruments} -s devices"
        Open3.popen3(cmd) do |_, stdout, stderr, _|
          stderr_filter.call(stderr)
          all = stdout.read.chomp.split("\n")
          valid = all.select { |device| device =~ /[a-f0-9]{40}/ }
          valid.map do |device|
            udid = device[/[a-f0-9]{40}/, 0]
            version = device[/(\d\.\d(\.\d)?)/, 0]
            name = device.split('(').first.strip
            RunLoop::Device.new(name, version, udid)
          end
        end
      }.call
    else
      candidates = [:version, :sims, :devices]
      raise(ArgumentError, "expected '#{cmd}' to be one of '#{candidates}'")
  end
end

#instruments_supports_hyphen_s?(version = instruments(:version)) ⇒ Boolean

Does the instruments ‘version` accept the -s flag?

Examples:

instruments_supports_hyphen_s?('4.6.3') => false
instruments_supports_hyphen_s?('5.0.2') => true
instruments_supports_hyphen_s?('5.1')   => true


284
285
286
287
288
289
290
291
292
293
# File 'lib/run_loop/xctools.rb', line 284

def instruments_supports_hyphen_s?(version=instruments(:version))
  @instruments_supports_hyphen_s ||= lambda {
    if version.is_a? String
      _version = RunLoop::Version.new(version)
    else
      _version = version
    end
    _version >= RunLoop::Version.new('5.1')
  }.call
end

#v50RunLoop::Version

Returns a version instance for ‘Xcode 5.0`; ; used to check for the availability of features and paths to various items on the filesystem.



78
79
80
# File 'lib/run_loop/xctools.rb', line 78

def v50
  @xc50 ||= RunLoop::Version.new('5.0')
end

#v51RunLoop::Version

Returns a version instance for ‘Xcode 5.1`; used to check for the availability of features and paths to various items on the filesystem.



70
71
72
# File 'lib/run_loop/xctools.rb', line 70

def v51
  @xc51 ||= RunLoop::Version.new('5.1')
end

#v60RunLoop::Version

Returns a version instance for ‘Xcode 6.0`; used to check for the availability of features and paths to various items on the filesystem.



62
63
64
# File 'lib/run_loop/xctools.rb', line 62

def v60
  @xc60 ||= RunLoop::Version.new('6.0')
end

#v61RunLoop::Version

Returns a version instance for ‘Xcode 6.1`; used to check for the availability of features and paths to various items on the filesystem.



54
55
56
# File 'lib/run_loop/xctools.rb', line 54

def v61
  @xc61 ||= RunLoop::Version.new('6.1')
end

#v62RunLoop::Version

Returns a version instance for ‘Xcode 6.2`; used to check for the availability of features and paths to various items on the filesystem.



46
47
48
# File 'lib/run_loop/xctools.rb', line 46

def v62
  @xc62 ||= RunLoop::Version.new('6.2')
end

#v63RunLoop::Version

Returns a version instance for ‘Xcode 6.3`; used to check for the availability of features and paths to various items on the filesystem.



38
39
40
# File 'lib/run_loop/xctools.rb', line 38

def v63
  @xc63 ||= RunLoop::Version.new('6.3')
end

#v64RunLoop::Version

Returns a version instance for ‘Xcode 6.4`; used to check for the availability of features and paths to various items on the filesystem.



30
31
32
# File 'lib/run_loop/xctools.rb', line 30

def v64
  @xc64 ||= RunLoop::Version.new('6.4')
end

#v70RunLoop::Version

Returns a version instance for ‘Xcode 7.0`; used to check for the availability of features and paths to various items on the filesystem.



22
23
24
# File 'lib/run_loop/xctools.rb', line 22

def v70
  @xc70 ||= RunLoop::Version.new('7.0')
end

#xcode_developer_dirString

Returns the path to the current developer directory.

From the man pages:

“‘ $ man xcode-select DEVELOPER_DIR Overrides the active developer directory. When DEVELOPER_DIR is set, its value will be used instead of the system-wide active developer directory. “`



155
156
157
158
159
160
161
162
163
# File 'lib/run_loop/xctools.rb', line 155

def xcode_developer_dir
  @xcode_developer_dir ||=
        if RunLoop::Environment.developer_dir
          RunLoop::Environment.developer_dir
        else
          # fall back to xcode-select
          `xcode-select --print-path`.chomp
        end
end

#xcode_is_beta?Boolean

Note:

Relies on Xcode beta versions having and app bundle named Xcode-Beta.app

Is this a beta version of Xcode?



169
170
171
172
173
# File 'lib/run_loop/xctools.rb', line 169

def xcode_is_beta?
  @xcode_is_beta ||= lambda {
    (xcode_developer_dir =~ /Xcode-[Bb]eta.app/) != nil
  }.call
end

#xcode_versionRunLoop::Version

Returns the current version of Xcode.



135
136
137
138
139
140
# File 'lib/run_loop/xctools.rb', line 135

def xcode_version
  @xcode_version ||= lambda {
    xcode_build_output = `xcrun xcodebuild -version`.split(/\s/)[1]
    RunLoop::Version.new(xcode_build_output)
  }.call
end

#xcode_version_gte_51?Boolean

Are we running Xcode 5.1 or above?



127
128
129
# File 'lib/run_loop/xctools.rb', line 127

def xcode_version_gte_51?
  @xcode_gte_51 ||= xcode_version >= v51
end

#xcode_version_gte_61?Boolean

Are we running Xcode 6.1 or above?



106
107
108
# File 'lib/run_loop/xctools.rb', line 106

def xcode_version_gte_61?
  @xcode_gte_61 ||= xcode_version >= v61
end

#xcode_version_gte_62?Boolean

Are we running Xcode 6.2 or above?



99
100
101
# File 'lib/run_loop/xctools.rb', line 99

def xcode_version_gte_62?
  @xcode_gte_62 ||= xcode_version >= v62
end

#xcode_version_gte_63?Boolean

Are we running Xcode 6.3 or above?



92
93
94
# File 'lib/run_loop/xctools.rb', line 92

def xcode_version_gte_63?
  @xcode_gte_63 ||= xcode_version >= v63
end

#xcode_version_gte_64?Boolean

Are we running Xcode 6.4 or above?



85
86
87
# File 'lib/run_loop/xctools.rb', line 85

def xcode_version_gte_64?
  @xcode_gte_64 ||= xcode_version >= v64
end

#xcode_version_gte_6?Boolean

Are we running Xcode 6 or above?



113
114
115
# File 'lib/run_loop/xctools.rb', line 113

def xcode_version_gte_6?
  @xcode_gte_6 ||= xcode_version >= v60
end

#xcode_version_gte_7?Boolean

Are we running Xcode 7 or above?



120
121
122
# File 'lib/run_loop/xctools.rb', line 120

def xcode_version_gte_7?
  @xcode_gte_7 ||= xcode_version >= v70
end