require 'open3' module RunLoop # A model of the active Xcode version. # # @note All command line tools are run in the context of `xcrun`. # # Throughout this class's documentation, there are references to the # _active version of Xcode_. The active 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`. class Xcode include RunLoop::Regex # Returns a String representation. def to_s "#" end # Returns debug String representation def inspect to_s end # Returns a version instance for `Xcode 7.0`; used to check for the # availability of features and paths to various items on the filesystem. # # @return [RunLoop::Version] 7.0 def v70 fetch_version(:v70) end # Returns a version instance for `Xcode 6.4`; used to check for the # availability of features and paths to various items on the filesystem. # # @return [RunLoop::Version] 6.4 def v64 fetch_version(:v64) end # Returns a version instance for `Xcode 6.3`; used to check for the # availability of features and paths to various items on the filesystem. # # @return [RunLoop::Version] 6.3 def v63 fetch_version(:v63) end # Returns a version instance for `Xcode 6.2`; used to check for the # availability of features and paths to various items on the filesystem. # # @return [RunLoop::Version] 6.2 def v62 fetch_version(:v62) end # Returns a version instance for `Xcode 6.1`; used to check for the # availability of features and paths to various items on the filesystem. # # @return [RunLoop::Version] 6.1 def v61 fetch_version(:v61) end # Returns a version instance for `Xcode 6.0`; used to check for the # availability of features and paths to various items on the filesystem. # # @return [RunLoop::Version] 6.0 def v60 fetch_version(:v60) end # Returns a version instance for `Xcode 5.1`; used to check for the # availability of features and paths to various items on the filesystem. # # @return [RunLoop::Version] 5.1 def v51 fetch_version(:v51) end # Returns a version instance for `Xcode 5.0`; used to check for the # availability of features and paths to various items on the filesystem. # # @return [RunLoop::Version] 5.0 def v50 fetch_version(:v50) end # Is the active Xcode version 7 or above? # # @return [Boolean] `true` if the current Xcode version is >= 7.0 def version_gte_7? version >= v70 end # Is the active Xcode version 6.4 or above? # # @return [Boolean] `true` if the current Xcode version is >= 6.4 def version_gte_64? version >= v64 end # Is the active Xcode version 6.3 or above? # # @return [Boolean] `true` if the current Xcode version is >= 6.3 def version_gte_63? version >= v63 end # Is the active Xcode version 6.2 or above? # # @return [Boolean] `true` if the current Xcode version is >= 6.2 def version_gte_62? version >= v62 end # Is the active Xcode version 6.1 or above? # # @return [Boolean] `true` if the current Xcode version is >= 6.1 def version_gte_61? version >= v61 end # Is the active Xcode version 6 or above? # # @return [Boolean] `true` if the current Xcode version is >= 6.0 def version_gte_6? version >= v60 end # Is the active Xcode version 5.1 or above? # # @return [Boolean] `true` if the current Xcode version is >= 5.1 def version_gte_51? version >= v51 end # Returns the current version of Xcode. # # @return [RunLoop::Version] The current version of Xcode as reported by # `xcrun xcodebuild -version`. def version @xcode_version ||= lambda do execute_command(['-version']) do |stdout, _, _| version_string = stdout.read.chomp[VERSION_REGEX, 0] RunLoop::Version.new(version_string) end end.call end # Is this a beta version of Xcode? # # @note Relies on Xcode beta versions having and app bundle named Xcode-Beta.app # @return [Boolean] True if the Xcode version is beta. def beta? developer_dir[/Xcode-[Bb]eta.app/, 0] end # 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. #``` # # @return [String] path to current developer directory def developer_dir @xcode_developer_dir ||= if RunLoop::Environment.developer_dir RunLoop::Environment.developer_dir else xcode_select_path end end private attr_reader :xcode_versions def xcode_versions @xcode_versions ||= {} end def fetch_version(key) ensure_valid_version_key key value = xcode_versions[key] return value if value string = key.to_s string[0] = '' version_string = string.split(/(?!^)/).join('.') version = RunLoop::Version.new(version_string) xcode_versions[key] = version version end def ensure_valid_version_key(key) string = key.to_s if string.length != 3 raise "Expected version key '#{key}' to have exactly 3 characters" end unless string[/v\d{2}/, 0] raise "Expected version key '#{key}' to match vXX pattern" end end def execute_command(args) Open3.popen3('xcrun', 'xcodebuild', *args) do |_, stdout, stderr, wait_thr| yield stdout, stderr, wait_thr end end def xcode_select_path Open3.popen3('xcode-select', '--print-path') do |_, stdout, _, _| stdout.read.chomp end end end end