mirror of
https://github.com/Homebrew/brew.git
synced 2026-10-28 09:34:42 +08:00
Stop inferring Xcode versions from clang
Signed-off-by: Patrick Linnane <patrick@linnane.io>
This commit is contained in:
6 files changed
+54
-86
No files matched your search
@@ -333,7 +333,7 @@ HOMEBREW_BOTTLE_DEFAULT_DOMAIN="https://ghcr.io/v2/homebrew/core"
|
||||
# - https://github.com/Homebrew/install/blob/HEAD/install.sh
|
||||
# - Library/Homebrew/os/mac.rb (latest_sdk_version)
|
||||
# - Library/Homebrew/os/mac/xcode.rb (latest_version), (minimum_version)
|
||||
# - Library/Homebrew/os/mac/xcode.rb (detect_version_from_clang_version), (latest_clang_version)
|
||||
# - Library/Homebrew/os/mac/xcode.rb (latest_clang_version)
|
||||
# and, if needed:
|
||||
# - MacOSVersion::RELEASES
|
||||
HOMEBREW_MACOS_NEWEST_UNSUPPORTED="28"
|
||||
|
||||
@@ -15,7 +15,7 @@ module OS
|
||||
def initialize
|
||||
super
|
||||
@xcode = T.let(nil, T.nilable(String))
|
||||
@clt = T.let(nil, T.nilable(Version))
|
||||
@clt = T.let(nil, T.nilable(String))
|
||||
end
|
||||
|
||||
sig { returns(String) }
|
||||
@@ -35,9 +35,11 @@ module OS
|
||||
end
|
||||
end
|
||||
|
||||
sig { returns(T.nilable(Version)) }
|
||||
sig { returns(T.nilable(String)) }
|
||||
def clt
|
||||
@clt ||= MacOS::CLT.version if MacOS::CLT.installed?
|
||||
@clt ||= if MacOS::CLT.installed?
|
||||
MacOS::CLT.version.to_s.presence || "unknown"
|
||||
end
|
||||
end
|
||||
|
||||
sig { returns(T.nilable(String)) }
|
||||
|
||||
@@ -170,9 +170,6 @@ module OS
|
||||
# @api internal
|
||||
sig { returns(::Version) }
|
||||
def self.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.
|
||||
if @version ||= T.let(detect_version, T.nilable(String))
|
||||
::Version.new @version
|
||||
else
|
||||
@@ -184,8 +181,6 @@ module OS
|
||||
def self.detect_version
|
||||
# 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.
|
||||
return if !MacOS::Xcode.installed? && !MacOS::CLT.installed?
|
||||
|
||||
if (xcode_prefix = prefix)
|
||||
# Fast path that will probably almost always work unless `xcode-select -p` is misconfigured
|
||||
version_plist = xcode_prefix.parent/"version.plist"
|
||||
@@ -210,40 +205,7 @@ module OS
|
||||
end
|
||||
end
|
||||
|
||||
detect_version_from_clang_version
|
||||
end
|
||||
|
||||
sig { params(version: ::Version, build: T.nilable(String)).returns(String) }
|
||||
def self.detect_version_from_clang_version(
|
||||
version = ::DevelopmentTools.clang_version,
|
||||
build = ::DevelopmentTools.clang_version_output&.[](/clang-(\d+(?:\.\d+)+)/, 1)
|
||||
)
|
||||
return "dunno" if version.null?
|
||||
|
||||
# This logic provides a fake Xcode version based on the
|
||||
# installed CLT version. This is useful as they are packaged
|
||||
# simultaneously so workarounds need to apply to both based on their
|
||||
# comparable version.
|
||||
case version
|
||||
when "11.0.0" then "11.3.1"
|
||||
when "11.0.3" then "11.7"
|
||||
when "12.0.0" then "12.4"
|
||||
when "12.0.5" then "12.5.1"
|
||||
when "13.0.0" then "13.2.1"
|
||||
when "13.1.6" then "13.4.1"
|
||||
when "14.0.0" then "14.2"
|
||||
when "14.0.3" then "14.3.1"
|
||||
when "15.0.0" then "15.4"
|
||||
when "16.0.0" then "16.2"
|
||||
when "17.0.0" then "26.3"
|
||||
else
|
||||
# Xcode 26.4 onwards ship clang 21.0.0 so map the full build to the newest matching Xcode.
|
||||
case build
|
||||
when /\A2100\.0\./ then "26.4.1"
|
||||
when /\A2100\.1\./ then "26.6"
|
||||
else "27.0"
|
||||
end
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
@@ -262,7 +224,7 @@ module OS
|
||||
# Returns true even if outdated tools are installed.
|
||||
sig { returns(T::Boolean) }
|
||||
def self.installed?
|
||||
!version.null?
|
||||
File.exist?("#{PKG_PATH}/usr/bin/clang")
|
||||
end
|
||||
|
||||
sig { returns(CLTSDKLocator) }
|
||||
@@ -377,7 +339,7 @@ module OS
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def self.below_minimum_version?
|
||||
return false unless installed?
|
||||
return false if version.null?
|
||||
|
||||
version < minimum_version
|
||||
end
|
||||
@@ -396,18 +358,10 @@ module OS
|
||||
version_output[/clang-(\d+(?:\.\d+)+)/, 1]
|
||||
end
|
||||
|
||||
sig { returns(T.nilable(String)) }
|
||||
def self.detect_version_from_clang_version
|
||||
clang_build = detect_clang_version
|
||||
return if clang_build.nil?
|
||||
|
||||
clang_version = clang_build.sub(/\A(\d+)(\d)(\d)\..*/, "\\1.\\2.\\3")
|
||||
MacOS::Xcode.detect_version_from_clang_version(Version.new(clang_version), clang_build)
|
||||
end
|
||||
|
||||
# Version string (a pretty long one) of the CLT package.
|
||||
# Note that the different ways of installing the CLTs lead to different
|
||||
# version numbers.
|
||||
# version numbers. Installations without a package receipt have an
|
||||
# unknown version (`Version::NULL`).
|
||||
#
|
||||
# @api internal
|
||||
sig { returns(::Version) }
|
||||
@@ -421,13 +375,9 @@ module OS
|
||||
|
||||
sig { returns(T.nilable(String)) }
|
||||
def self.detect_version
|
||||
version = T.let(nil, T.nilable(String))
|
||||
if File.exist?("#{PKG_PATH}/usr/bin/clang")
|
||||
version = MacOS.pkgutil_info(EXECUTABLE_PKG_ID)[/version: (.+)$/, 1]
|
||||
return version if version
|
||||
end
|
||||
return unless installed?
|
||||
|
||||
detect_version_from_clang_version
|
||||
MacOS.pkgutil_info(EXECUTABLE_PKG_ID)[/version: (.+)$/, 1]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -51,6 +51,17 @@ RSpec.describe Homebrew::Cmd::Config do
|
||||
])
|
||||
end
|
||||
|
||||
it "prints an unknown Command Line Tools version", :needs_macos do
|
||||
output = StringIO.new
|
||||
|
||||
allow(OS::Mac::CLT).to receive_messages(installed?: true, version: Version::NULL)
|
||||
allow(SystemConfig).to receive(:config_sections).and_return([:macos_config])
|
||||
|
||||
SystemConfig.dump_verbose_config(output)
|
||||
|
||||
expect(output.string).to include("CLT: unknown\n")
|
||||
end
|
||||
|
||||
it "reads the Windows version on WSL", :needs_linux do
|
||||
allow(OS).to receive(:wsl?).and_return(true)
|
||||
stub_const("ORIGINAL_PATHS", [windows_cmd.dirname])
|
||||
|
||||
@@ -40,18 +40,11 @@ RSpec.describe OS::Mac::Xcode, :needs_macos do
|
||||
end
|
||||
|
||||
describe ".detect_version" do
|
||||
test_each_hash({ "2100.0.123.102" => "26.4.1", "2100.1.1.101" => "26.6",
|
||||
"2100.3.34.2" => "27.0" }) do |build, xcode|
|
||||
it "infers Xcode #{xcode} from the clang-#{build} Command Line Tools compiler" do
|
||||
allow(described_class).to receive_messages(installed?: false, prefix: nil)
|
||||
allow(OS::Mac::CLT).to receive(:installed?).and_return(true)
|
||||
allow(DevelopmentTools).to receive_messages(
|
||||
clang_version: Version.new("21.0.0"),
|
||||
clang_version_output: "Apple clang version 21.0.0 (clang-#{build})\n",
|
||||
)
|
||||
it "does not infer a version from the Command Line Tools" do
|
||||
allow(described_class).to receive_messages(installed?: false, prefix: nil)
|
||||
allow(OS::Mac::CLT).to receive(:installed?).and_return(true)
|
||||
|
||||
expect(described_class.detect_version).to eq(xcode)
|
||||
end
|
||||
expect(described_class.detect_version).to be_nil
|
||||
end
|
||||
|
||||
it "loads Plist when version.plist exists" do
|
||||
@@ -73,12 +66,6 @@ RSpec.describe OS::Mac::Xcode, :needs_macos do
|
||||
end
|
||||
end
|
||||
|
||||
describe ".detect_version_from_clang_version" do
|
||||
it "preserves Xcode 26.3 for clang 17" do
|
||||
expect(described_class.detect_version_from_clang_version(Version.new("17.0.0"))).to eq("26.3")
|
||||
end
|
||||
end
|
||||
|
||||
describe OS::Mac::CLT do
|
||||
describe ".latest_version" do
|
||||
test_each_hash({ "27" => "27.0", "26" => "27.0", "15" => "26.3", "14" => "16.2",
|
||||
@@ -152,11 +139,30 @@ RSpec.describe OS::Mac::Xcode, :needs_macos do
|
||||
end
|
||||
end
|
||||
|
||||
describe ".detect_version_from_clang_version" do
|
||||
it "infers Xcode 26.6 from the Command Line Tools 26.6 compiler" do
|
||||
allow(described_class).to receive(:detect_clang_version).and_return("2100.1.1.101")
|
||||
describe ".installed?" do
|
||||
it "detects Command Line Tools without a package receipt" do
|
||||
allow(File).to receive(:exist?).and_call_original
|
||||
allow(File).to receive(:exist?).with("#{OS::Mac::CLT::PKG_PATH}/usr/bin/clang").and_return(true)
|
||||
allow(MacOS).to receive(:pkgutil_info).and_return("")
|
||||
|
||||
expect(described_class.detect_version_from_clang_version).to eq("26.6")
|
||||
expect(described_class.installed?).to be true
|
||||
end
|
||||
end
|
||||
|
||||
describe ".detect_version" do
|
||||
it "leaves the version unknown without a package receipt" do
|
||||
allow(described_class).to receive(:installed?).and_return(true)
|
||||
allow(MacOS).to receive(:pkgutil_info).and_return("")
|
||||
|
||||
expect(described_class.detect_version).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe ".below_minimum_version?" do
|
||||
it "does not treat an unknown version as below the minimum" do
|
||||
allow(described_class).to receive_messages(installed?: true, version: Version::NULL)
|
||||
|
||||
expect(described_class.below_minimum_version?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
+3
-4
@@ -22,10 +22,9 @@ When Apple publishes a new Xcode or Command Line Tools release:
|
||||
1. Confirm the version, build number, bundled Apple Clang version and supported macOS versions from Apple's release information.
|
||||
2. Update `OS::Mac::Xcode.latest_version` when the latest Xcode mapping changes.
|
||||
3. Update `OS::Mac::CLT.latest_clang_version` when the Command Line Tools compiler mapping changes.
|
||||
4. Update `OS::Mac::Xcode.detect_version_from_clang_version` when Homebrew must infer a new Xcode version from Apple Clang.
|
||||
5. Review minimum-version checks and diagnostic text in the same file for assumptions affected by the release.
|
||||
6. Add or update automated coverage for every changed mapping or inference.
|
||||
7. Verify `brew config` and the relevant `brew doctor` output on an affected macOS runner when one is available.
|
||||
4. Review minimum-version checks and diagnostic text in the same file for assumptions affected by the release.
|
||||
5. Add or update automated coverage for every changed mapping.
|
||||
6. Verify `brew config` and the relevant `brew doctor` output on an affected macOS runner when one is available.
|
||||
|
||||
Run the repository checks from the Homebrew/brew checkout:
|
||||
|
||||
|
||||
Reference in new issue
Block a user