Set correct tap when loading installed casks

This commit is contained in:
Rylan Polster 2024-07-20 21:41:34 -04:00
parent c0f08f82b9
commit 8cb62b1398
No known key found for this signature in database
7 changed files with 170 additions and 4 deletions

View File

@ -503,13 +503,26 @@ module Cask
.returns(T.nilable(T.attached_class))
}
def self.try_new(ref, warn: false)
return unless ref.is_a?(String)
token = if ref.is_a?(String)
ref
elsif ref.is_a?(Pathname)
ref.basename(ref.extname).to_s
end
return unless token
possible_installed_cask = Cask.new(ref)
possible_installed_cask = Cask.new(token)
return unless (installed_caskfile = possible_installed_cask.installed_caskfile)
new(installed_caskfile)
end
sig { params(path: T.any(Pathname, String), token: String).void }
def initialize(path, token: T.unsafe(nil))
super
installed_tap = Cask.new(@token).tab.tap
@tap = installed_tap if installed_tap
end
end
# Pseudo-loader which raises an error when trying to load the corresponding cask.
@ -598,6 +611,32 @@ module Cask
end
end
sig { params(ref: String, config: T.nilable(Config), warn: T::Boolean).returns(Cask) }
def self.load_installed_cask(ref, config: nil, warn: true)
tap, token = Tap.with_cask_token(ref)
token ||= ref
tap ||= Cask.new(ref).tab.tap
if tap.nil?
self.load(token, config:, warn:)
else
begin
self.load("#{tap}/#{token}", config:, warn:)
rescue CaskUnavailableError
# cask may be migrated to different tap. Try to search in all taps.
self.load(token, config:, warn:)
end
end
end
sig { params(path: Pathname, config: T.nilable(Config), warn: T::Boolean).returns(Cask) }
def self.load_from_installed_caskfile(path, config: nil, warn: true)
loader = FromInstalledPathLoader.try_new(path, warn:)
loader ||= NullLoader.new(path)
loader.load(config:)
end
def self.default_path(token)
find_cask_in_tap(token.to_s.downcase, CoreCaskTap.instance)
end

View File

@ -56,7 +56,7 @@ module Cask
sig { params(config: T.nilable(Config)).returns(T::Array[Cask]) }
def self.casks(config: nil)
tokens.sort.filter_map do |token|
CaskLoader.load(token, config:, warn: false)
CaskLoader.load_installed_cask(token, config:, warn: false)
rescue TapCaskAmbiguityError => e
T.must(e.loaders.first).load(config:)
rescue

View File

@ -189,7 +189,18 @@ module Homebrew
if want_keg_like_cask &&
(installed_caskfile = candidate_cask.installed_caskfile) &&
installed_caskfile.exist?
Cask::CaskLoader.load(installed_caskfile)
cask = Cask::CaskLoader.load_from_installed_caskfile(installed_caskfile)
requested_tap, requested_token = Tap.with_cask_token(name)
if requested_tap && requested_token
installed_cask_tap = cask.tab.tap
if installed_cask_tap && installed_cask_tap != requested_tap
raise Cask::TapCaskUnavailableError.new(requested_tap, requested_token)
end
end
cask
else
candidate_cask
end
@ -405,6 +416,16 @@ module Homebrew
rack = Formulary.to_rack(name.downcase)
kegs = rack.directory? ? rack.subdirs.map { |d| Keg.new(d) } : []
requested_tap, requested_formula = Tap.with_formula_name(name)
if requested_tap && requested_formula
kegs = kegs.select do |keg|
keg.tab.tap == requested_tap
end
raise NoSuchKegFromTapError.new(requested_formula, requested_tap) if kegs.none?
end
raise NoSuchKegError, name if kegs.none?
[rack, kegs]

View File

@ -61,6 +61,18 @@ class NoSuchKegError < RuntimeError
end
end
# Raised when a keg from a specific tap doesn't exist.
class NoSuchKegFromTapError < RuntimeError
attr_reader :name, :tap
sig { params(name: String, tap: Tap).void }
def initialize(name, tap)
@name = name
@tap = tap
super "No such keg: #{HOMEBREW_CELLAR}/#{name} from tap #{tap}"
end
end
# Raised when an invalid attribute is used in a formula.
class FormulaValidationError < StandardError
attr_reader :attr, :formula

View File

@ -184,4 +184,60 @@ RSpec.describe Cask::CaskLoader, :cask do
end
end
end
describe "::load_installed_cask" do
let(:foo_tap) { Tap.fetch("user", "foo") }
let(:bar_tap) { Tap.fetch("user", "bar") }
let(:blank_tab) { instance_double(Cask::Tab, tap: nil) }
let(:installed_tab) { instance_double(Cask::Tab, tap: bar_tap) }
let(:cask_with_foo_tap) { instance_double(Cask::Cask, token: "test-cask", tap: foo_tap) }
let(:cask_with_bar_tap) { instance_double(Cask::Cask, token: "test-cask", tap: bar_tap) }
let(:load_args) { { config: nil, warn: true } }
before do
allow(described_class).to receive(:load).with("test-cask", load_args).and_return(cask_with_foo_tap)
allow(described_class).to receive(:load).with("user/foo/test-cask", load_args).and_return(cask_with_foo_tap)
allow(described_class).to receive(:load).with("user/bar/test-cask", load_args).and_return(cask_with_bar_tap)
end
it "returns the correct cask when no tap is specified and no tab exists" do
allow_any_instance_of(Cask::Cask).to receive(:tab).and_return(blank_tab)
expect(described_class).to receive(:load).with("test-cask", load_args)
expect(described_class.load_installed_cask("test-cask").tap).to eq(foo_tap)
end
it "returns the correct cask when no tap is specified but a tab exists" do
allow_any_instance_of(Cask::Cask).to receive(:tab).and_return(installed_tab)
expect(described_class).to receive(:load).with("user/bar/test-cask", load_args)
expect(described_class.load_installed_cask("test-cask").tap).to eq(bar_tap)
end
it "returns the correct cask when a tap is specified and no tab exists" do
allow_any_instance_of(Cask::Cask).to receive(:tab).and_return(blank_tab)
expect(described_class).to receive(:load).with("user/bar/test-cask", load_args)
expect(described_class.load_installed_cask("user/bar/test-cask").tap).to eq(bar_tap)
end
it "returns the correct cask when no tap is specified and a tab exists" do
allow_any_instance_of(Cask::Cask).to receive(:tab).and_return(installed_tab)
expect(described_class).to receive(:load).with("user/foo/test-cask", load_args)
expect(described_class.load_installed_cask("user/foo/test-cask").tap).to eq(foo_tap)
end
it "returns the correct cask when no tap is specified and the tab lists an tap that isn't installed" do
allow_any_instance_of(Cask::Cask).to receive(:tab).and_return(installed_tab)
expect(described_class).to receive(:load).with("user/bar/test-cask", load_args)
.and_raise(Cask::CaskUnavailableError.new("test-cask", bar_tap))
expect(described_class).to receive(:load).with("test-cask", load_args)
expect(described_class.load_installed_cask("test-cask").tap).to eq(foo_tap)
end
end
end

View File

@ -217,6 +217,36 @@ RSpec.describe Homebrew::CLI::NamedArgs do
it "when there are no matching kegs returns an empty array" do
expect(described_class.new.to_kegs).to be_empty
end
it "raises an error when a Keg is unavailable" do
expect { described_class.new("baz").to_kegs }.to raise_error NoSuchKegError
end
context "when a keg specifies a tap" do
let(:tab) { instance_double(Tab, tap: Tap.fetch("user", "repo")) }
before do
allow_any_instance_of(Keg).to receive(:tab).and_return(tab)
end
it "returns kegs if no tap is specified" do
stub_formula_loader bar, "user/repo/bar"
expect(described_class.new("bar").to_kegs.map(&:name)).to eq ["bar"]
end
it "returns kegs if the tap is specified" do
stub_formula_loader bar, "user/repo/bar"
expect(described_class.new("user/repo/bar").to_kegs.map(&:name)).to eq ["bar"]
end
it "raises an error if there is no tap match" do
stub_formula_loader bar, "other/tap/bar"
expect { described_class.new("other/tap/bar").to_kegs }.to raise_error(NoSuchKegFromTapError)
end
end
end
describe "#to_default_kegs" do

View File

@ -19,6 +19,14 @@ RSpec.describe "Exception" do
end
end
describe NoSuchKegFromTapError do
subject(:error) { described_class.new("foo", tap) }
let(:tap) { instance_double(Tap, to_s: "u/r") }
it(:to_s) { expect(error.to_s).to eq("No such keg: #{HOMEBREW_CELLAR}/foo from tap u/r") }
end
describe NoSuchKegError do
subject(:error) { described_class.new("foo") }