2020-10-10 14:16:11 +02:00
|
|
|
# typed: false
|
2019-04-19 15:38:03 +09:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2013-10-18 12:56:51 -05:00
|
|
|
module OS
|
|
|
|
module Mac
|
2020-08-25 00:38:56 +02:00
|
|
|
# Helper module for querying Xcode information.
|
|
|
|
#
|
|
|
|
# @api private
|
2013-10-18 12:56:51 -05:00
|
|
|
module Xcode
|
2016-09-24 20:11:54 +02:00
|
|
|
module_function
|
2013-10-18 12:56:51 -05:00
|
|
|
|
2017-01-09 21:30:32 +00:00
|
|
|
DEFAULT_BUNDLE_PATH = Pathname.new("/Applications/Xcode.app").freeze
|
2019-04-19 15:38:03 +09:00
|
|
|
BUNDLE_ID = "com.apple.dt.Xcode"
|
|
|
|
OLD_BUNDLE_ID = "com.apple.Xcode"
|
2013-10-18 12:56:51 -05:00
|
|
|
|
2020-04-09 14:27:37 +01:00
|
|
|
# Bump these when a new version is available from the App Store and our
|
|
|
|
# CI systems have been updated.
|
|
|
|
# This may be a beta version for a beta macOS.
|
2013-10-18 12:56:51 -05:00
|
|
|
def latest_version
|
2020-10-22 21:16:54 +11:00
|
|
|
latest_stable = "12.1"
|
2013-10-18 12:56:51 -05:00
|
|
|
case MacOS.version
|
2020-10-22 21:16:54 +11:00
|
|
|
when "11.0" then "12.1"
|
2020-06-23 14:11:05 +01:00
|
|
|
when "10.15" then latest_stable
|
2020-04-09 14:27:37 +01:00
|
|
|
when "10.14" then "11.3.1"
|
|
|
|
when "10.13" then "10.1"
|
|
|
|
when "10.12" then "9.2"
|
|
|
|
when "10.11" then "8.2.1"
|
|
|
|
when "10.10" then "7.2.1"
|
|
|
|
when "10.9" then "6.2"
|
2013-10-18 12:56:51 -05:00
|
|
|
else
|
2016-09-23 22:02:23 +02:00
|
|
|
raise "macOS '#{MacOS.version}' is invalid" unless OS::Mac.prerelease?
|
|
|
|
|
2016-09-18 19:57:19 +01:00
|
|
|
# Default to newest known version of Xcode for unreleased macOS versions.
|
2020-06-23 14:11:05 +01:00
|
|
|
latest_stable
|
2013-10-18 12:56:51 -05:00
|
|
|
end
|
2013-06-04 13:58:08 +01:00
|
|
|
end
|
2012-08-17 17:18:17 -04:00
|
|
|
|
2020-04-09 14:27:37 +01:00
|
|
|
# Bump these if things are badly broken (e.g. no SDK for this macOS)
|
|
|
|
# without this. Generally this will be the first Xcode release on that
|
|
|
|
# macOS version (which may initially be a beta if that version of macOS is
|
|
|
|
# also in beta).
|
2016-11-05 10:35:39 -04:00
|
|
|
def minimum_version
|
|
|
|
case MacOS.version
|
2020-07-28 16:56:55 +01:00
|
|
|
when "11.0" then "12.0"
|
2019-06-04 16:11:18 -07:00
|
|
|
when "10.15" then "11.0"
|
2019-05-01 09:06:41 +01:00
|
|
|
when "10.14" then "10.2"
|
2017-06-06 04:33:55 +01:00
|
|
|
when "10.13" then "9.0"
|
2016-11-05 10:35:39 -04:00
|
|
|
when "10.12" then "8.0"
|
|
|
|
else "2.0"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-12-18 15:31:15 -08:00
|
|
|
def below_minimum_version?
|
2017-11-05 12:09:04 +00:00
|
|
|
return false unless installed?
|
2018-09-17 02:45:00 +02:00
|
|
|
|
2016-11-05 10:35:39 -04:00
|
|
|
version < minimum_version
|
|
|
|
end
|
|
|
|
|
2017-11-17 19:53:38 +00:00
|
|
|
def latest_sdk_version?
|
2019-01-08 00:26:22 -08:00
|
|
|
OS::Mac.version >= OS::Mac.latest_sdk_version
|
2017-11-17 19:53:38 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def needs_clt_installed?
|
|
|
|
return false if latest_sdk_version?
|
2018-09-17 02:45:00 +02:00
|
|
|
|
2017-11-17 19:53:38 +00:00
|
|
|
without_clt?
|
|
|
|
end
|
|
|
|
|
2013-10-18 12:56:51 -05:00
|
|
|
def outdated?
|
2017-11-05 12:09:04 +00:00
|
|
|
return false unless installed?
|
2018-09-17 02:45:00 +02:00
|
|
|
|
2017-11-05 12:09:04 +00:00
|
|
|
version < latest_version
|
2013-10-18 12:56:51 -05:00
|
|
|
end
|
2013-05-22 22:26:09 -05:00
|
|
|
|
2013-10-18 12:56:51 -05:00
|
|
|
def without_clt?
|
2019-01-26 17:13:14 +00:00
|
|
|
!MacOS::CLT.installed?
|
2013-10-18 12:56:51 -05:00
|
|
|
end
|
2013-08-09 20:47:28 -05:00
|
|
|
|
2016-03-16 22:51:39 -07:00
|
|
|
# Returns a Pathname object corresponding to Xcode.app's Developer
|
|
|
|
# directory or nil if Xcode.app is not installed
|
2013-10-18 12:56:51 -05:00
|
|
|
def prefix
|
2014-05-02 16:48:59 -05:00
|
|
|
@prefix ||=
|
|
|
|
begin
|
|
|
|
dir = MacOS.active_developer_dir
|
|
|
|
|
2017-01-09 21:30:20 +00:00
|
|
|
if dir.empty? || dir == CLT::PKG_PATH || !File.directory?(dir)
|
2014-05-02 16:48:59 -05:00
|
|
|
path = bundle_path
|
2017-01-09 21:30:20 +00:00
|
|
|
path/"Contents/Developer" if path
|
2014-05-02 16:48:59 -05:00
|
|
|
else
|
2016-03-15 23:40:59 -07:00
|
|
|
# Use cleanpath to avoid pathological trailing slash
|
|
|
|
Pathname.new(dir).cleanpath
|
2014-05-02 16:48:59 -05:00
|
|
|
end
|
2013-10-18 12:56:51 -05:00
|
|
|
end
|
2012-07-09 15:18:02 -05:00
|
|
|
end
|
|
|
|
|
2014-05-01 18:36:46 -05:00
|
|
|
def toolchain_path
|
2016-12-05 11:09:21 +00:00
|
|
|
Pathname.new("#{prefix}/Toolchains/XcodeDefault.xctoolchain")
|
2014-05-01 18:36:46 -05:00
|
|
|
end
|
|
|
|
|
2013-10-18 12:56:51 -05:00
|
|
|
def bundle_path
|
2017-01-09 21:30:32 +00:00
|
|
|
# Use the default location if it exists.
|
|
|
|
return DEFAULT_BUNDLE_PATH if DEFAULT_BUNDLE_PATH.exist?
|
|
|
|
|
|
|
|
# Ask Spotlight where Xcode is. If the user didn't install the
|
|
|
|
# helper tools and installed Xcode in a non-conventional place, this
|
|
|
|
# is our only option. See: https://superuser.com/questions/390757
|
|
|
|
MacOS.app_with_bundle_id(BUNDLE_ID, OLD_BUNDLE_ID)
|
2013-10-18 12:56:51 -05:00
|
|
|
end
|
2013-07-21 16:41:51 -05:00
|
|
|
|
2013-10-18 12:56:51 -05:00
|
|
|
def installed?
|
2015-08-03 13:09:07 +01:00
|
|
|
!prefix.nil?
|
2013-10-18 12:56:51 -05:00
|
|
|
end
|
2012-07-09 15:18:02 -05:00
|
|
|
|
2020-07-01 16:02:29 +01:00
|
|
|
def sdk_locator
|
|
|
|
@sdk_locator ||= XcodeSDKLocator.new
|
|
|
|
end
|
2018-07-26 17:57:11 -07:00
|
|
|
|
2020-07-01 16:02:29 +01:00
|
|
|
def sdk(v = nil)
|
|
|
|
sdk_locator.sdk_if_applicable(v)
|
2018-07-26 17:57:11 -07:00
|
|
|
end
|
|
|
|
|
|
|
|
def sdk_path(v = nil)
|
|
|
|
sdk(v)&.path
|
|
|
|
end
|
|
|
|
|
2016-07-16 21:01:34 +01:00
|
|
|
def update_instructions
|
2019-01-26 17:13:14 +00:00
|
|
|
if OS::Mac.prerelease?
|
2017-10-15 02:28:32 +02:00
|
|
|
<<~EOS
|
2019-01-26 17:13:14 +00:00
|
|
|
Xcode can be updated from:
|
2019-04-05 12:24:10 -04:00
|
|
|
#{Formatter.url("https://developer.apple.com/download/more/")}
|
2016-07-16 21:01:34 +01:00
|
|
|
EOS
|
|
|
|
else
|
2017-10-15 02:28:32 +02:00
|
|
|
<<~EOS
|
2019-01-26 17:13:14 +00:00
|
|
|
Xcode can be updated from the App Store.
|
2016-07-16 21:01:34 +01:00
|
|
|
EOS
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-10-18 12:56:51 -05:00
|
|
|
def version
|
|
|
|
# may return a version string
|
|
|
|
# that is guessed based on the compiler, so do not
|
|
|
|
# use it in order to check if Xcode is installed.
|
2017-11-05 12:09:04 +00:00
|
|
|
if @version ||= detect_version
|
|
|
|
::Version.new @version
|
|
|
|
else
|
|
|
|
::Version::NULL
|
|
|
|
end
|
2013-10-18 12:56:51 -05:00
|
|
|
end
|
2012-07-09 15:18:02 -05:00
|
|
|
|
2017-11-05 12:09:04 +00:00
|
|
|
def detect_version
|
2013-10-18 12:56:51 -05:00
|
|
|
# This is a separate function as you can't cache the value out of a block
|
|
|
|
# if return is used in the middle, which we do many times in here.
|
2017-11-05 12:09:04 +00:00
|
|
|
return if !MacOS::Xcode.installed? && !MacOS::CLT.installed?
|
2015-07-26 16:49:16 -04:00
|
|
|
|
2016-05-04 20:44:19 +02:00
|
|
|
%W[
|
|
|
|
#{prefix}/usr/bin/xcodebuild
|
|
|
|
#{which("xcodebuild")}
|
|
|
|
].uniq.each do |xcodebuild_path|
|
2016-09-11 17:49:27 +01:00
|
|
|
next unless File.executable? xcodebuild_path
|
2018-09-17 02:45:00 +02:00
|
|
|
|
2016-09-11 17:49:27 +01:00
|
|
|
xcodebuild_output = Utils.popen_read(xcodebuild_path, "-version")
|
2017-06-10 20:12:55 +03:00
|
|
|
next unless $CHILD_STATUS.success?
|
2016-05-04 20:44:19 +02:00
|
|
|
|
2018-03-05 19:44:11 -06:00
|
|
|
xcode_version = xcodebuild_output[/Xcode (\d+(\.\d+)*)/, 1]
|
2016-09-11 17:49:27 +01:00
|
|
|
return xcode_version if xcode_version
|
2014-04-17 08:23:02 -07:00
|
|
|
|
2016-09-11 17:49:27 +01:00
|
|
|
# Xcode 2.x's xcodebuild has a different version string
|
|
|
|
case xcodebuild_output[/DevToolsCore-(\d+\.\d)/, 1]
|
|
|
|
when "798.0" then return "2.5"
|
2020-04-09 14:27:37 +01:00
|
|
|
when "515.0" then return "2.0"
|
2014-04-15 14:18:45 -05:00
|
|
|
end
|
2013-10-18 12:56:51 -05:00
|
|
|
end
|
|
|
|
|
2017-12-08 14:59:15 +00:00
|
|
|
detect_version_from_clang_version
|
|
|
|
end
|
|
|
|
|
|
|
|
def detect_version_from_clang_version
|
2018-06-05 14:53:24 -07:00
|
|
|
return "dunno" if DevelopmentTools.clang_version.null?
|
2018-09-17 02:45:00 +02:00
|
|
|
|
2017-12-08 14:59:15 +00:00
|
|
|
# This logic provides a fake Xcode version based on the
|
2017-04-22 16:28:07 +01:00
|
|
|
# installed CLT version. This is useful as they are packaged
|
|
|
|
# simultaneously so workarounds need to apply to both based on their
|
|
|
|
# comparable version.
|
2020-10-03 23:53:32 +10:00
|
|
|
latest_stable = "12.0"
|
2016-05-22 09:40:08 +01:00
|
|
|
case (DevelopmentTools.clang_version.to_f * 10).to_i
|
2020-10-03 23:53:32 +10:00
|
|
|
when 120 then latest_stable
|
|
|
|
when 110 then "11.5"
|
2020-03-26 22:03:54 +00:00
|
|
|
when 100 then "10.3"
|
2020-04-09 14:27:37 +01:00
|
|
|
when 91 then "9.4"
|
|
|
|
when 90 then "9.2"
|
|
|
|
when 81 then "8.3"
|
|
|
|
when 80 then "8.0"
|
|
|
|
when 73 then "7.3"
|
|
|
|
when 70 then "7.0"
|
|
|
|
when 61 then "6.1"
|
|
|
|
when 60 then "6.0"
|
|
|
|
when 0 then "dunno"
|
2020-06-23 14:11:05 +01:00
|
|
|
else latest_stable
|
2013-10-18 12:56:51 -05:00
|
|
|
end
|
|
|
|
end
|
2012-08-06 13:46:02 -04:00
|
|
|
|
2013-10-18 12:56:51 -05:00
|
|
|
def default_prefix?
|
2019-01-26 17:13:14 +00:00
|
|
|
prefix.to_s == "/Applications/Xcode.app/Contents/Developer"
|
2012-07-09 15:18:02 -05:00
|
|
|
end
|
|
|
|
end
|
2012-07-09 15:19:30 -05:00
|
|
|
|
2020-08-25 00:40:10 +02:00
|
|
|
# Helper module for querying macOS Command Line Tools information.
|
|
|
|
#
|
|
|
|
# @api private
|
2013-10-18 12:56:51 -05:00
|
|
|
module CLT
|
2017-10-07 00:31:28 +02:00
|
|
|
module_function
|
2012-07-10 21:01:16 -05:00
|
|
|
|
2018-07-06 15:15:23 -07:00
|
|
|
# The original Mavericks CLT package ID
|
2019-04-19 15:38:03 +09:00
|
|
|
EXECUTABLE_PKG_ID = "com.apple.pkg.CLTools_Executables"
|
|
|
|
MAVERICKS_NEW_PKG_ID = "com.apple.pkg.CLTools_Base" # obsolete
|
|
|
|
PKG_PATH = "/Library/Developer/CommandLineTools"
|
2012-09-14 13:24:28 -05:00
|
|
|
|
2019-01-26 17:13:14 +00:00
|
|
|
# Returns true even if outdated tools are installed
|
2013-10-18 12:56:51 -05:00
|
|
|
def installed?
|
2017-11-05 12:09:04 +00:00
|
|
|
!version.null?
|
2013-10-18 12:56:51 -05:00
|
|
|
end
|
2013-07-21 16:41:50 -05:00
|
|
|
|
2018-06-12 14:30:27 -07:00
|
|
|
def separate_header_package?
|
2020-03-08 20:11:49 +00:00
|
|
|
version >= "10" && MacOS.version >= "10.14"
|
2018-06-12 14:30:27 -07:00
|
|
|
end
|
|
|
|
|
2018-07-26 17:56:47 -07:00
|
|
|
def provides_sdk?
|
|
|
|
version >= "8"
|
|
|
|
end
|
|
|
|
|
2020-07-01 16:02:29 +01:00
|
|
|
def sdk_locator
|
|
|
|
@sdk_locator ||= CLTSDKLocator.new
|
|
|
|
end
|
2018-07-26 17:57:11 -07:00
|
|
|
|
2020-07-01 16:02:29 +01:00
|
|
|
def sdk(v = nil)
|
|
|
|
sdk_locator.sdk_if_applicable(v)
|
2018-07-26 17:57:11 -07:00
|
|
|
end
|
|
|
|
|
|
|
|
def sdk_path(v = nil)
|
|
|
|
sdk(v)&.path
|
|
|
|
end
|
|
|
|
|
2016-07-16 21:01:34 +01:00
|
|
|
def update_instructions
|
2020-04-09 14:27:37 +01:00
|
|
|
software_update_location = if MacOS.version >= "10.14"
|
|
|
|
"System Preferences"
|
2019-01-26 17:13:14 +00:00
|
|
|
else
|
2020-04-09 14:27:37 +01:00
|
|
|
"the App Store"
|
2016-07-16 21:01:34 +01:00
|
|
|
end
|
2020-04-09 14:27:37 +01:00
|
|
|
|
|
|
|
<<~EOS
|
|
|
|
Update them from Software Update in #{software_update_location} or run:
|
|
|
|
softwareupdate --all --install --force
|
|
|
|
|
|
|
|
If that doesn't show you an update run:
|
|
|
|
sudo rm -rf /Library/Developer/CommandLineTools
|
|
|
|
sudo xcode-select --install
|
|
|
|
|
|
|
|
Alternatively, manually download them from:
|
|
|
|
#{Formatter.url("https://developer.apple.com/download/more/")}.
|
|
|
|
EOS
|
2016-07-16 21:01:34 +01:00
|
|
|
end
|
|
|
|
|
2020-04-09 14:27:37 +01:00
|
|
|
# Bump these when the new version is distributed through Software Update
|
|
|
|
# and our CI systems have been updated.
|
2019-12-11 13:50:21 +00:00
|
|
|
def latest_clang_version
|
2014-09-18 10:09:14 -05:00
|
|
|
case MacOS.version
|
2020-10-22 21:16:54 +11:00
|
|
|
when "11.0" then "1200.0.32.21"
|
|
|
|
when "10.15" then "1200.0.32.21"
|
|
|
|
when "10.14" then "1100.0.33.17"
|
2018-10-02 02:51:29 +08:00
|
|
|
when "10.13" then "1000.10.44.2"
|
2017-12-08 14:59:15 +00:00
|
|
|
when "10.12" then "900.0.39.2"
|
2017-03-28 11:16:02 -04:00
|
|
|
when "10.11" then "800.0.42.1"
|
2015-12-09 05:06:00 +00:00
|
|
|
when "10.10" then "700.1.81"
|
2019-01-26 17:13:14 +00:00
|
|
|
else "600.0.57"
|
2013-11-01 16:16:01 -05:00
|
|
|
end
|
2013-10-18 12:56:51 -05:00
|
|
|
end
|
2012-08-17 17:18:17 -04:00
|
|
|
|
2020-04-09 14:27:37 +01:00
|
|
|
# Bump these if things are badly broken (e.g. no SDK for this macOS)
|
|
|
|
# without this. Generally this will be the first stable CLT release on
|
|
|
|
# that macOS version.
|
2016-11-05 10:35:39 -04:00
|
|
|
def minimum_version
|
|
|
|
case MacOS.version
|
2020-07-28 16:56:55 +01:00
|
|
|
when "11.0" then "12.0.0"
|
2019-06-04 16:11:18 -07:00
|
|
|
when "10.15" then "11.0.0"
|
2018-08-14 00:02:19 +01:00
|
|
|
when "10.14" then "10.0.0"
|
2017-06-22 18:18:52 +01:00
|
|
|
when "10.13" then "9.0.0"
|
2016-11-05 10:35:39 -04:00
|
|
|
when "10.12" then "8.0.0"
|
2017-12-08 14:59:15 +00:00
|
|
|
else "1.0.0"
|
2016-11-05 10:35:39 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-12-18 15:31:15 -08:00
|
|
|
def below_minimum_version?
|
2017-11-05 12:09:04 +00:00
|
|
|
return false unless installed?
|
2018-09-17 02:45:00 +02:00
|
|
|
|
2016-11-05 10:35:39 -04:00
|
|
|
version < minimum_version
|
|
|
|
end
|
|
|
|
|
2013-10-18 12:56:51 -05:00
|
|
|
def outdated?
|
2017-11-05 12:09:04 +00:00
|
|
|
clang_version = detect_clang_version
|
|
|
|
return false unless clang_version
|
2018-09-17 02:45:00 +02:00
|
|
|
|
2019-12-11 13:50:21 +00:00
|
|
|
::Version.new(clang_version) < latest_clang_version
|
2017-11-05 12:09:04 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def detect_clang_version
|
2020-06-10 10:06:46 +01:00
|
|
|
version_output = Utils.popen_read("#{PKG_PATH}/usr/bin/clang --version")
|
2017-11-05 12:09:04 +00:00
|
|
|
version_output[/clang-(\d+\.\d+\.\d+(\.\d+)?)/, 1]
|
2013-10-18 12:56:51 -05:00
|
|
|
end
|
2013-05-22 22:26:09 -05:00
|
|
|
|
2019-12-11 13:49:56 +00:00
|
|
|
def detect_version_from_clang_version
|
|
|
|
detect_clang_version&.sub(/^(\d+)00\./, "\\1.")
|
|
|
|
end
|
|
|
|
|
2014-03-22 10:14:20 +00:00
|
|
|
# Version string (a pretty long one) of the CLT package.
|
2013-10-18 12:56:51 -05:00
|
|
|
# Note, that different ways to install the CLTs lead to different
|
|
|
|
# version numbers.
|
|
|
|
def version
|
2017-11-05 12:09:04 +00:00
|
|
|
if @version ||= detect_version
|
|
|
|
::Version.new @version
|
|
|
|
else
|
|
|
|
::Version::NULL
|
|
|
|
end
|
2013-10-18 12:56:51 -05:00
|
|
|
end
|
2013-07-21 20:09:55 -05:00
|
|
|
|
2013-10-18 12:56:51 -05:00
|
|
|
def detect_version
|
2017-11-05 12:09:04 +00:00
|
|
|
version = nil
|
2019-01-26 17:13:14 +00:00
|
|
|
[EXECUTABLE_PKG_ID, MAVERICKS_NEW_PKG_ID].each do |id|
|
|
|
|
next unless File.exist?("#{PKG_PATH}/usr/bin/clang")
|
2019-02-19 13:12:52 +00:00
|
|
|
|
2013-10-18 12:56:51 -05:00
|
|
|
version = MacOS.pkgutil_info(id)[/version: (.+)$/, 1]
|
2019-10-16 16:26:57 +01:00
|
|
|
return version if version
|
2013-10-18 12:56:51 -05:00
|
|
|
end
|
2019-10-16 16:26:57 +01:00
|
|
|
|
2019-12-11 13:49:56 +00:00
|
|
|
detect_version_from_clang_version
|
2013-10-18 12:56:51 -05:00
|
|
|
end
|
2013-07-21 20:09:55 -05:00
|
|
|
end
|
2012-07-09 15:18:02 -05:00
|
|
|
end
|
|
|
|
end
|