diff --git a/Library/Homebrew/cask/info.rb b/Library/Homebrew/cask/info.rb index 47e32f5835..a2913f672c 100644 --- a/Library/Homebrew/cask/info.rb +++ b/Library/Homebrew/cask/info.rb @@ -28,6 +28,8 @@ module Cask output << desc_info(cask) deps = deps_info(cask) output << deps if deps + requirements = requirements_info(cask) + output << requirements if requirements language = language_info(cask) output << language if language output << "#{artifact_info(cask)}\n" @@ -131,6 +133,47 @@ module Cask installed ? pretty_installed(dep) : pretty_uninstalled(dep) end + sig { params(cask: Cask).returns(T.nilable(String)) } + def self.requirements_info(cask) + require "cask_dependent" + + requirements = CaskDependent.new(cask).requirements.grep_v(CaskDependent::Requirement) + return if requirements.empty? + + supports_linux = cask.supports_linux? + oldest_allowed = MacOSVersion.new(HOMEBREW_MACOS_OLDEST_ALLOWED) + output = "#{ohai_title("Requirements")}\n" + %w[build required recommended optional].each do |type| + reqs = case type + when "build" + requirements.select(&:build?) + when "required" + requirements.select(&:required?) + when "recommended" + requirements.select(&:recommended?) + when "optional" + requirements.select(&:optional?) + else + [] + end + next if reqs.empty? + + output << "#{type.capitalize}: #{reqs.map do |requirement| + requires_macos = requirement.is_a?(MacOSRequirement) && !supports_linux + requirement_s = if requires_macos && requirement.comparator == ">=" && requirement.version == oldest_allowed + "macOS" + elsif requires_macos + requirement.display_s.delete_suffix(" (or Linux)") + else + requirement.display_s + end + installed = requirement.satisfied? + installed ? pretty_installed(requirement_s) : pretty_uninstalled(requirement_s) + end.join(", ")}\n" + end + output + end + sig { params(cask: Cask).returns(T.nilable(String)) } def self.language_info(cask) return if cask.languages.empty? diff --git a/Library/Homebrew/extend/os/linux/bundle/skipper.rb b/Library/Homebrew/extend/os/linux/bundle/skipper.rb index 8ae8259c05..82b1486b5f 100644 --- a/Library/Homebrew/extend/os/linux/bundle/skipper.rb +++ b/Library/Homebrew/extend/os/linux/bundle/skipper.rb @@ -2,42 +2,31 @@ # frozen_string_literal: true require "cask/cask_loader" -require "cask/installer" - module OS module Linux module Bundle module Skipper module ClassMethods sig { params(entry: Homebrew::Bundle::Dsl::Entry).returns(T::Boolean) } - def macos_only_entry?(entry) - entry.type == :mas - end - - sig { params(entry: Homebrew::Bundle::Dsl::Entry).returns(T::Boolean) } - def macos_only_cask?(entry) - return false if entry.type != :cask - - cask = ::Cask::CaskLoader.load(entry.name) - installer = ::Cask::Installer.new(cask) - installer.check_stanza_os_requirements - - false + def requires_macos?(entry) + case entry.type + when :mas + true + when :cask + !::Cask::CaskLoader.load(entry.name).supports_linux? + else + false + end rescue ::Cask::CaskError true end sig { params(entry: Homebrew::Bundle::Dsl::Entry, silent: T::Boolean).returns(T::Boolean) } def skip?(entry, silent: false) - if macos_only_entry?(entry) || macos_only_cask?(entry) - unless silent - $stdout.puts Formatter.warning "Skipping #{entry.type} #{entry.name} (unsupported on Linux)" - end + return super unless requires_macos?(entry) - true - else - super(entry) - end + $stdout.puts Formatter.warning("Skipping #{entry.type} #{entry.name} (requires macOS)") unless silent + true end end end diff --git a/Library/Homebrew/extend/os/linux/search.rb b/Library/Homebrew/extend/os/linux/search.rb new file mode 100644 index 0000000000..1213dc4f03 --- /dev/null +++ b/Library/Homebrew/extend/os/linux/search.rb @@ -0,0 +1,17 @@ +# typed: strict +# frozen_string_literal: true + +module OS + module Linux + module Search + module ClassMethods + sig { params(cask: ::Cask::Cask).returns(T::Boolean) } + def ignore_cask?(cask) + !cask.supports_linux? + end + end + end + end +end + +Homebrew::Search.singleton_class.prepend(OS::Linux::Search::ClassMethods) diff --git a/Library/Homebrew/extend/os/search.rb b/Library/Homebrew/extend/os/search.rb new file mode 100644 index 0000000000..275cc21fb2 --- /dev/null +++ b/Library/Homebrew/extend/os/search.rb @@ -0,0 +1,4 @@ +# typed: strict +# frozen_string_literal: true + +require "extend/os/linux/search" if OS.linux? diff --git a/Library/Homebrew/search.rb b/Library/Homebrew/search.rb index 28c8a08ed3..7478b088ee 100644 --- a/Library/Homebrew/search.rb +++ b/Library/Homebrew/search.rb @@ -46,6 +46,9 @@ module Homebrew raise "#{query} is not a valid regex." end + sig { params(_cask: Cask::Cask).returns(T::Boolean) } + def self.ignore_cask?(_cask) = false + T::Sig::WithoutRuntime.sig { params( string_or_regex: T.any(Regexp, String), @@ -148,7 +151,8 @@ module Homebrew def self.search_casks(string_or_regex) if string_or_regex.is_a?(String) && string_or_regex.match?(HOMEBREW_TAP_CASK_REGEX) return begin - [Cask::CaskLoader.load(string_or_regex).token] + matched_cask = Cask::CaskLoader.load(string_or_regex) + ignore_cask?(matched_cask) ? [] : [matched_cask.token] rescue Cask::CaskUnavailableError [] end @@ -169,8 +173,10 @@ module Homebrew .correct(string_or_regex) end - results.sort.map do |name| + results.sort.filter_map do |name| cask = Cask::CaskLoader.load(name) + next if ignore_cask?(cask) + display_name = if cask.installed? pretty_installed(cask.full_name) else @@ -245,3 +251,5 @@ module Homebrew end end end + +require "extend/os/search" diff --git a/Library/Homebrew/test/bundle/skipper_spec.rb b/Library/Homebrew/test/bundle/skipper_spec.rb index 2af5b31ae8..2c48e86905 100644 --- a/Library/Homebrew/test/bundle/skipper_spec.rb +++ b/Library/Homebrew/test/bundle/skipper_spec.rb @@ -70,6 +70,20 @@ RSpec.describe Homebrew::Bundle::Skipper do end end + context "with a cask that requires macOS", :needs_linux do + let(:entry) { Homebrew::Bundle::Dsl::Entry.new(:cask, "testball") } + + it "skips on Linux with warning" do + allow(Cask::CaskLoader).to receive(:load).with("testball").and_return( + instance_double(Cask::Cask, supports_linux?: false), + ) + expect($stdout).to receive(:puts).with( + Formatter.warning("Skipping cask testball (requires macOS)"), + ) + expect(skipper.skip?(entry)).to be true + end + end + context "with a listed formula in a failed tap" do let(:entry) { Homebrew::Bundle::Dsl::Entry.new(:brew, "org/repo/formula") } diff --git a/Library/Homebrew/test/cask/cask_spec.rb b/Library/Homebrew/test/cask/cask_spec.rb index 80e50e9276..50cf95d26d 100644 --- a/Library/Homebrew/test/cask/cask_spec.rb +++ b/Library/Homebrew/test/cask/cask_spec.rb @@ -392,6 +392,12 @@ RSpec.describe Cask::Cask, :cask do end end + describe "#supports_linux?" do + it "returns false when Linux support is not explicitly declared" do + expect(Cask::CaskLoader.load("with-non-executable-binary").supports_linux?).to be false + end + end + describe "#to_h" do let(:expected_json) { (TEST_FIXTURE_DIR/"cask/everything.json").read.strip } diff --git a/Library/Homebrew/test/cask/info_spec.rb b/Library/Homebrew/test/cask/info_spec.rb index bbf842ede5..f2590c2c04 100644 --- a/Library/Homebrew/test/cask/info_spec.rb +++ b/Library/Homebrew/test/cask/info_spec.rb @@ -17,6 +17,13 @@ RSpec.describe Cask::Info, :cask do "#{Tty.bold}#{string} #{Formatter.success("✔")}#{Tty.reset}" end + def requirements_section(string) + <<~EOS.chomp + #{ohai_title "Requirements"} + Required: #{string} + EOS + end + def mock_cask_installed(cask_name) cask = Cask::CaskLoader.load(cask_name) allow(cask).to receive(:installed?).and_return(true) @@ -44,6 +51,7 @@ RSpec.describe Cask::Info, :cask do Transmission ==> Description BitTorrent client + #{requirements_section("macOS")} ==> Artifacts Transmission.app (App) EOS @@ -65,6 +73,7 @@ RSpec.describe Cask::Info, :cask do #{Formatter.error("None")} #{ohai_title "Dependencies"} #{uninstalled("local-caffeine (cask)")}, #{installed("local-transmission-zip (cask)")} + #{requirements_section(installed("macOS"))} #{ohai_title "Artifacts"} Caffeine.app (App) EOS @@ -72,6 +81,12 @@ RSpec.describe Cask::Info, :cask do it "prints cask and formulas dependencies if the Cask has both" do allow_any_instance_of(StringIO).to receive(:tty?).and_return(true) + arch_requirements = if Hardware::CPU.arm? + "#{uninstalled("x86_64 architecture")}, #{installed("arm64 architecture")}" + else + "#{installed("x86_64 architecture")}, #{uninstalled("arm64 architecture")}" + end + expect do described_class.info(Cask::CaskLoader.load("with-depends-on-everything"), args:) end.to output(<<~EOS).to_stdout @@ -85,6 +100,7 @@ RSpec.describe Cask::Info, :cask do #{Formatter.error("None")} #{ohai_title "Dependencies"} #{uninstalled("unar")}, #{uninstalled("local-caffeine (cask)")}, #{uninstalled("with-depends-on-cask (cask)")} + #{requirements_section("#{arch_requirements}, #{installed("macOS")}")} #{ohai_title "Artifacts"} Caffeine.app (App) EOS @@ -102,6 +118,7 @@ RSpec.describe Cask::Info, :cask do AutoUpdates ==> Description None + #{requirements_section("macOS")} ==> Artifacts AutoUpdates.app (App) EOS @@ -119,6 +136,7 @@ RSpec.describe Cask::Info, :cask do None ==> Description None + #{requirements_section("macOS")} ==> Artifacts Caffeine.app (App) ==> Caveats @@ -146,6 +164,7 @@ RSpec.describe Cask::Info, :cask do None ==> Description None + #{requirements_section("macOS")} ==> Artifacts Caffeine.app (App) EOS @@ -163,6 +182,7 @@ RSpec.describe Cask::Info, :cask do None ==> Description None + #{requirements_section("macOS")} ==> Languages zh, en-US ==> Artifacts @@ -182,6 +202,7 @@ RSpec.describe Cask::Info, :cask do None ==> Description None + #{requirements_section("macOS")} ==> Artifacts Caffeine.app (App) EOS @@ -213,6 +234,7 @@ RSpec.describe Cask::Info, :cask do Transmission ==> Description BitTorrent client + #{requirements_section(installed("macOS"))} ==> Artifacts Transmission.app (App) EOS @@ -250,9 +272,28 @@ RSpec.describe Cask::Info, :cask do Transmission ==> Description BitTorrent client + #{requirements_section(installed("macOS"))} ==> Artifacts Transmission.app (App) EOS end end + + it "shows requirements" do + expect do + described_class.info(Cask::CaskLoader.load("with-non-executable-binary"), args:) + end.to output(<<~EOS).to_stdout + ==> with-non-executable-binary: 1.2.3 + https://brew.sh/with-binary + Not installed + From: https://github.com/Homebrew/homebrew-cask/blob/HEAD/Casks/w/with-non-executable-binary.rb + ==> Name + None + ==> Description + None + #{requirements_section("macOS")} + ==> Artifacts + naked_non_executable (Binary) + EOS + end end diff --git a/Library/Homebrew/test/search_spec.rb b/Library/Homebrew/test/search_spec.rb index 3fc330e1d8..a14fd7b432 100644 --- a/Library/Homebrew/test/search_spec.rb +++ b/Library/Homebrew/test/search_spec.rb @@ -95,7 +95,14 @@ RSpec.describe Homebrew::Search do describe "#search_casks" do let(:cask) do - instance_double(Cask::Cask, full_name: "testball", installed?: false, deprecated?: false, disabled?: false) + instance_double( + Cask::Cask, + full_name: "testball", + installed?: false, + deprecated?: false, + disabled?: false, + supports_linux?: true, + ) end before do @@ -117,6 +124,12 @@ RSpec.describe Homebrew::Search do it "does not annotate normal casks", :needs_macos do expect(described_class.search_casks(/testball/)).to eq(["testball"]) end + + it "hides macOS-only casks on Linux", :needs_linux do + allow(cask).to receive(:supports_linux?).and_return(false) + + expect(described_class.search_casks(/testball/)).to eq([]) + end end describe "#search_descriptions" do