diff --git a/Library/Homebrew/cask/quarantine.rb b/Library/Homebrew/cask/quarantine.rb index ea68bb239e..b558efb25e 100644 --- a/Library/Homebrew/cask/quarantine.rb +++ b/Library/Homebrew/cask/quarantine.rb @@ -40,11 +40,12 @@ module Cask end private_class_method :swift_target_args - sig { returns(Symbol) } + sig { returns([Symbol, T.nilable(String)]) } def self.check_quarantine_support odebug "Checking quarantine support" - if xattr.nil? || !system_command(xattr, args: ["-h"], print_stderr: false).success? + check_output = nil + status = if xattr.nil? || !system_command(xattr, args: ["-h"], print_stderr: false).success? odebug "There's no working version of `xattr` on this system." :xattr_broken elsif swift.nil? @@ -55,17 +56,37 @@ module Cask args: [*swift_target_args, QUARANTINE_SCRIPT], print_stderr: false) - case api_check.exit_status + exit_status = api_check.exit_status + check_output = api_check.merged_output.to_s.strip + error_output = api_check.stderr.to_s.strip + + case exit_status when 2 odebug "Quarantine is available." :quarantine_available + when 1 + # Swift script ran but failed (likely due to CLT issues) + odebug "Swift quarantine script failed: #{error_output}" + if error_output.include?("does not exist") || error_output.include?("No such file") + :swift_broken_clt + elsif error_output.include?("compiler") || error_output.include?("SDK") + :swift_compilation_failed + else + :swift_runtime_error + end + when 127 + # Command not found or execution failed + odebug "Swift execution failed with exit status 127" + :swift_not_executable else - odebug "Unknown support status" - :unknown + odebug "Swift returned unexpected exit status: #{exit_status}" + :swift_unexpected_error end end + [status, check_output] end + sig { returns(T::Boolean) } def self.available? @status ||= check_quarantine_support diff --git a/Library/Homebrew/diagnostic.rb b/Library/Homebrew/diagnostic.rb index 589e53b1c7..93d814396a 100644 --- a/Library/Homebrew/diagnostic.rb +++ b/Library/Homebrew/diagnostic.rb @@ -1040,21 +1040,6 @@ module Homebrew end end - def check_cask_quarantine_support - case Cask::Quarantine.check_quarantine_support - when :quarantine_available - nil - when :xattr_broken - "No Cask quarantine support available: there's no working version of `xattr` on this system." - when :no_swift - "No Cask quarantine support available: there's no available version of `swift` on this system." - when :linux - "No Cask quarantine support available: not available on Linux." - else - "No Cask quarantine support available: unknown reason." - end - end - def non_core_taps @non_core_taps ||= Tap.installed.reject(&:core_tap?).reject(&:core_cask_tap?) end diff --git a/Library/Homebrew/extend/os/linux/cask/quarantine.rb b/Library/Homebrew/extend/os/linux/cask/quarantine.rb index 5adcbd2eec..aa3faff110 100644 --- a/Library/Homebrew/extend/os/linux/cask/quarantine.rb +++ b/Library/Homebrew/extend/os/linux/cask/quarantine.rb @@ -10,9 +10,6 @@ module OS requires_ancestor { ::Cask::Quarantine } - sig { returns(Symbol) } - def check_quarantine_support = :linux - sig { returns(T::Boolean) } def available? = false end diff --git a/Library/Homebrew/extend/os/mac/diagnostic.rb b/Library/Homebrew/extend/os/mac/diagnostic.rb index cf5a15b2ba..145917e598 100644 --- a/Library/Homebrew/extend/os/mac/diagnostic.rb +++ b/Library/Homebrew/extend/os/mac/diagnostic.rb @@ -542,6 +542,52 @@ module OS We'd welcome a PR to automatically mitigate this instead of just warning about it. EOS end + + def check_cask_quarantine_support + status, check_output = ::Cask::Quarantine.check_quarantine_support + + case status + when :quarantine_available + nil + when :xattr_broken + "No Cask quarantine support available: there's no working version of `xattr` on this system." + when :no_swift + "No Cask quarantine support available: there's no available version of `swift` on this system." + when :swift_broken_clt + <<~EOS + No Cask quarantine support available: Swift is not working due to missing Command Line Tools. + #{MacOS::CLT.installation_then_reinstall_instructions} + EOS + when :swift_compilation_failed + <<~EOS + No Cask quarantine support available: Swift compilation failed. + This is usually due to a broken or incompatible Command Line Tools installation. + #{MacOS::CLT.installation_then_reinstall_instructions} + EOS + when :swift_runtime_error + <<~EOS + No Cask quarantine support available: Swift runtime error. + Your Command Line Tools installation may be broken or incomplete. + #{MacOS::CLT.installation_then_reinstall_instructions} + EOS + when :swift_not_executable + <<~EOS + No Cask quarantine support available: Swift is not executable. + Your Command Line Tools installation may be incomplete. + #{MacOS::CLT.installation_then_reinstall_instructions} + EOS + when :swift_unexpected_error + <<~EOS + No Cask quarantine support available: Swift returned an unexpected error: + #{check_output} + EOS + else + <<~EOS + No Cask quarantine support available: unknown reason: #{status.inspect}: + #{check_output} + EOS + end + end end end end diff --git a/Library/Homebrew/os/mac/xcode.rb b/Library/Homebrew/os/mac/xcode.rb index cec906f25e..2300f1489d 100644 --- a/Library/Homebrew/os/mac/xcode.rb +++ b/Library/Homebrew/os/mac/xcode.rb @@ -327,6 +327,19 @@ module OS end end + sig { params(reason: String).returns(String) } + def self.reinstall_instructions(reason: "resolve your issues") + <<~EOS + If that doesn't #{reason}, run: + sudo rm -rf /Library/Developer/CommandLineTools + sudo xcode-select --install + + Alternatively, manually download them from: + #{Formatter.url(MacOS::Xcode::APPLE_DEVELOPER_DOWNLOAD_URL)}. + You should download the Command Line Tools for Xcode #{MacOS::Xcode.latest_version}. + EOS + end + sig { returns(String) } def self.update_instructions return installation_instructions if OS::Mac.version.prerelease? @@ -342,13 +355,15 @@ module OS <<~EOS Update them from Software Update in #{software_update_location}. - If that doesn't show you any updates, run: - sudo rm -rf /Library/Developer/CommandLineTools - sudo xcode-select --install + #{reinstall_instructions(reason: "show you any updates")} + EOS + end - Alternatively, manually download them from: - #{Formatter.url(MacOS::Xcode::APPLE_DEVELOPER_DOWNLOAD_URL)}. - You should download the Command Line Tools for Xcode #{MacOS::Xcode.latest_version}. + sig { returns(String) } + def self.installation_then_reinstall_instructions + <<~EOS + #{installation_instructions} + #{reinstall_instructions} EOS end diff --git a/Library/Homebrew/test/os/mac/diagnostic_spec.rb b/Library/Homebrew/test/os/mac/diagnostic_spec.rb index fe7840eabf..0a8ba09c51 100644 --- a/Library/Homebrew/test/os/mac/diagnostic_spec.rb +++ b/Library/Homebrew/test/os/mac/diagnostic_spec.rb @@ -143,4 +143,53 @@ RSpec.describe Homebrew::Diagnostic::Checks do expect(checks.check_pkgconf_macos_sdk_mismatch).to include("brew reinstall pkgconf") end end + + describe "#check_cask_quarantine_support" do + it "returns nil when quarantine is available" do + allow(Cask::Quarantine).to receive(:check_quarantine_support).and_return([:quarantine_available, nil]) + expect(checks.check_cask_quarantine_support).to be_nil + end + + it "returns error when xattr is broken" do + allow(Cask::Quarantine).to receive(:check_quarantine_support).and_return([:xattr_broken, nil]) + expect(checks.check_cask_quarantine_support) + .to match("there's no working version of `xattr` on this system") + end + + it "returns error when swift is not available" do + allow(Cask::Quarantine).to receive(:check_quarantine_support).and_return([:no_swift, nil]) + expect(checks.check_cask_quarantine_support) + .to match("there's no available version of `swift` on this system") + end + + it "returns error when swift is broken due to missing CLT" do + allow(Cask::Quarantine).to receive(:check_quarantine_support).and_return([:swift_broken_clt, nil]) + expect(checks.check_cask_quarantine_support) + .to match("Swift is not working due to missing Command Line Tools") + end + + it "returns error when swift compilation failed" do + allow(Cask::Quarantine).to receive(:check_quarantine_support).and_return([:swift_compilation_failed, nil]) + expect(checks.check_cask_quarantine_support) + .to match("Swift compilation failed") + end + + it "returns error when swift runtime error occurs" do + allow(Cask::Quarantine).to receive(:check_quarantine_support).and_return([:swift_runtime_error, nil]) + expect(checks.check_cask_quarantine_support) + .to match("Swift runtime error") + end + + it "returns error when swift is not executable" do + allow(Cask::Quarantine).to receive(:check_quarantine_support).and_return([:swift_not_executable, nil]) + expect(checks.check_cask_quarantine_support) + .to match("Swift is not executable") + end + + it "returns error when swift returns unexpected error" do + allow(Cask::Quarantine).to receive(:check_quarantine_support).and_return([:swift_unexpected_error, "whoopsie"]) + expect(checks.check_cask_quarantine_support) + .to match("whoopsie") + end + end end