mirror of
https://github.com/Homebrew/brew.git
synced 2026-08-12 22:29:27 +04:00
Make Linux cask requirements explicit
- stop surfacing casks on Linux unless `supports_linux?` says they are safe to advertise, so `search` and `bundle` stay conservative about unknown support - move cask platform output into a `Requirements` section so `brew info` explains macOS-only casks the same way on macOS and Linux - reuse `requires macOS` in `brew bundle` for both `brew` and `cask` entries so platform warnings use the same language across commands - cover the shared policy with regressions around `search`, `cask/info`, `bundle/skipper`, and `Cask#supports_linux?`
This commit is contained in:
@@ -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?
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -0,0 +1,4 @@
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "extend/os/linux/search" if OS.linux?
|
||||
@@ -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"
|
||||
|
||||
@@ -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") }
|
||||
|
||||
|
||||
@@ -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 }
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user