diff --git a/Library/Homebrew/extend/os/mac/development_tools.rb b/Library/Homebrew/extend/os/mac/development_tools.rb index f101a31056..6f25836ae3 100644 --- a/Library/Homebrew/extend/os/mac/development_tools.rb +++ b/Library/Homebrew/extend/os/mac/development_tools.rb @@ -51,10 +51,7 @@ class DevelopmentTools sig { returns(String) } def installation_instructions - <<~EOS - Install the Command Line Tools: - xcode-select --install - EOS + MacOS::CLT.installation_instructions end sig { returns(String) } diff --git a/Library/Homebrew/extend/os/mac/diagnostic.rb b/Library/Homebrew/extend/os/mac/diagnostic.rb index 71fe9f3c4a..69aa335b23 100644 --- a/Library/Homebrew/extend/os/mac/diagnostic.rb +++ b/Library/Homebrew/extend/os/mac/diagnostic.rb @@ -64,6 +64,7 @@ module Homebrew check_clt_minimum_version check_if_xcode_needs_clt_installed check_if_supported_sdk_available + check_broken_sdks ].freeze end @@ -425,7 +426,7 @@ module Homebrew source = if locator.source == :clt update_instructions = MacOS::CLT.update_instructions - "CLT" + "Command Line Tools (CLT)" else update_instructions = MacOS::Xcode.update_instructions "Xcode" @@ -438,6 +439,40 @@ module Homebrew #{update_instructions} EOS end + + # The CLT 10.x -> 11.x upgrade process on 10.14 contained a bug which broke the SDKs. + # Notably, MacOSX10.14.sdk would indirectly symlink to MacOSX10.15.sdk. + # This diagnostic was introduced to check for this and recommend a full reinstall. + def check_broken_sdks + locator = MacOS.sdk_locator + + return if locator.all_sdks.all? do |sdk| + path_version = sdk.path.basename.to_s[MacOS::SDK::VERSIONED_SDK_REGEX, 1] + next true if path_version.blank? + + sdk.version == MacOS::Version.new(path_version).strip_patch + end + + if locator.source == :clt + source = "Command Line Tools (CLT)" + path_to_remove = MacOS::CLT::PKG_PATH + installation_instructions = MacOS::CLT.installation_instructions + else + source = "Xcode" + path_to_remove = MacOS::Xcode.bundle_path + installation_instructions = MacOS::Xcode.installation_instructions + end + + <<~EOS + The contents of the SDKs in your #{source} installation do not match the SDK folder names. + A clean reinstall of #{source} should fix this. + + Remove the broken installation before reinstalling: + sudo rm -rf #{path_to_remove} + + #{installation_instructions} + EOS + end end end end diff --git a/Library/Homebrew/os/mac/xcode.rb b/Library/Homebrew/os/mac/xcode.rb index 9a5f765800..7de72b63e3 100644 --- a/Library/Homebrew/os/mac/xcode.rb +++ b/Library/Homebrew/os/mac/xcode.rb @@ -134,6 +134,19 @@ module OS sdk(v)&.path end + def installation_instructions + if OS::Mac.prerelease? + <<~EOS + Xcode can be installed from: + #{Formatter.url("https://developer.apple.com/download/more/")} + EOS + else + <<~EOS + Xcode can be installed from the App Store. + EOS + end + end + sig { returns(String) } def update_instructions if OS::Mac.prerelease? @@ -254,6 +267,21 @@ module OS sdk(v)&.path end + def installation_instructions + if MacOS.version == "10.14" + # This is not available from `xcode-select` + <<~EOS + Install the Command Line Tools for Xcode 11.3.1 from: + #{Formatter.url("https://developer.apple.com/download/more/")} + EOS + else + <<~EOS + Install the Command Line Tools: + xcode-select --install + EOS + end + end + sig { returns(String) } def update_instructions software_update_location = if MacOS.version >= "10.14" diff --git a/Library/Homebrew/test/os/mac/diagnostic_spec.rb b/Library/Homebrew/test/os/mac/diagnostic_spec.rb index f58aef8cff..d8c1436355 100644 --- a/Library/Homebrew/test/os/mac/diagnostic_spec.rb +++ b/Library/Homebrew/test/os/mac/diagnostic_spec.rb @@ -39,4 +39,79 @@ describe Homebrew::Diagnostic::Checks do expect(checks.check_ruby_version) .to match "Ruby version 1.8.6 is unsupported on 10.12" end + + describe "#check_if_supported_sdk_available" do + let(:macos_version) { OS::Mac::Version.new("11") } + + before do + allow(DevelopmentTools).to receive(:installed?).and_return(true) + allow(OS::Mac).to receive(:version).and_return(macos_version) + end + + it "doesn't trigger when SDK root is not needed" do + allow(OS::Mac).to receive(:sdk_root_needed?).and_return(false) + allow(OS::Mac).to receive(:sdk).and_return(nil) + + expect(checks.check_if_supported_sdk_available).to be_nil + end + + it "doesn't trigger when a valid SDK is present" do + allow(OS::Mac).to receive(:sdk_root_needed?).and_return(true) + allow(OS::Mac).to receive(:sdk).and_return(OS::Mac::SDK.new(macos_version, "/some/path/MacOSX.sdk", :clt)) + + expect(checks.check_if_supported_sdk_available).to be_nil + end + + it "triggers when a valid SDK is not present on CLT systems" do + allow(OS::Mac).to receive(:sdk_root_needed?).and_return(true) + allow(OS::Mac).to receive(:sdk).and_return(nil) + allow(OS::Mac).to receive(:sdk_locator).and_return(OS::Mac::CLT.sdk_locator) + + expect(checks.check_if_supported_sdk_available) + .to include("Your Command Line Tools (CLT) does not support macOS #{macos_version}") + end + + it "triggers when a valid SDK is not present on Xcode systems" do + allow(OS::Mac).to receive(:sdk_root_needed?).and_return(true) + allow(OS::Mac).to receive(:sdk).and_return(nil) + allow(OS::Mac).to receive(:sdk_locator).and_return(OS::Mac::Xcode.sdk_locator) + + expect(checks.check_if_supported_sdk_available) + .to include("Your Xcode does not support macOS #{macos_version}") + end + end + + describe "#check_broken_sdks" do + it "doesn't trigger when SDK versions are as expected" do + allow(OS::Mac).to receive(:sdk_locator).and_return(OS::Mac::CLT.sdk_locator) + allow_any_instance_of(OS::Mac::CLTSDKLocator).to receive(:all_sdks) + .and_return([ + OS::Mac::SDK.new(OS::Mac::Version.new("11"), "/some/path/MacOSX.sdk", :clt), + OS::Mac::SDK.new(OS::Mac::Version.new("10.15"), "/some/path/MacOSX10.15.sdk", :clt), + ]) + + expect(checks.check_broken_sdks).to be_nil + end + + it "triggers when the CLT SDK version doesn't match the folder name" do + allow_any_instance_of(OS::Mac::CLTSDKLocator).to receive(:all_sdks) + .and_return([ + OS::Mac::SDK.new(OS::Mac::Version.new("10.14"), "/some/path/MacOSX10.15.sdk", :clt), + ]) + + expect(checks.check_broken_sdks) + .to include("SDKs in your Command Line Tools (CLT) installation do not match the SDK folder names") + end + + it "triggers when the Xcode SDK version doesn't match the folder name" do + allow(OS::Mac).to receive(:sdk_locator).and_return(OS::Mac::Xcode.sdk_locator) + allow_any_instance_of(OS::Mac::XcodeSDKLocator).to receive(:all_sdks) + .and_return([ + OS::Mac::SDK.new(OS::Mac::Version.new("10.14"), "/some/path/MacOSX10.15.sdk", :xcode), + ]) + + expect(checks.check_broken_sdks) + .to include("The contents of the SDKs in your Xcode installation do not match the SDK folder names") + end + end end