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 >

Parameters:

  • cmd (Version) (defaults to: nil)

    controls the return value. currently accepts ‘nil`, `:sims`, `:templates`, and `:version` as valid parameters

Returns:

  • (String, Array, Version)

    based on the value of ‘cmd` version, a list known simulators, the version of current instruments tool, or the path to the instruments binary.

Raises:

  • (ArgumentError)

    if invalid ‘cmd` is passed



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/run_loop/xctools.rb', line 107

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

Parameters:

  • version (String, Version) (defaults to: instruments(:version))

    (instruments(:version)) a major.minor version string or a Version object

Returns:

  • (Boolean)

    true if the version is >= 5.*



196
197
198
199
200
201
202
203
204
205
# File 'lib/run_loop/xctools.rb', line 196

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.

Returns:



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

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.

Returns:



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

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.

Returns:



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

def v60
  @xc60 ||= RunLoop::Version.new('6.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. “`

Returns:

  • (String)

    path to current developer directory



80
81
82
83
84
85
86
87
88
# File 'lib/run_loop/xctools.rb', line 80

def xcode_developer_dir
  @xcode_developer_dir ||=
        if ENV['DEVELOPER_DIR']
          ENV['DEVELOPER_DIR']
        else
          # fall back to xcode-select
          `xcode-select --print-path`.chomp
        end
end

#xcode_versionRunLoop::Version

Returns the current version of Xcode.

Returns:

  • (RunLoop::Version)

    The current version of Xcode as reported by ‘xcrun xcodebuild -version`.



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

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?

Returns:

  • (Boolean)

    ‘true` if the current Xcode version is >= 5.1



52
53
54
# File 'lib/run_loop/xctools.rb', line 52

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

#xcode_version_gte_6?Boolean

Are we running Xcode 6 or above?

Returns:

  • (Boolean)

    ‘true` if the current Xcode version is >= 6.0



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

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