Introduce CoreCaskTap class

This commit is contained in:
Bo Anderson 2023-07-13 19:28:39 +01:00
parent 1b7f3a7f1d
commit ba02c669e1
No known key found for this signature in database
GPG Key ID: 3DB94E204E137D65
11 changed files with 163 additions and 65 deletions

View File

@ -438,7 +438,7 @@ module Cask
end end
def self.default_path(token) def self.default_path(token)
Tap.default_cask_tap.cask_dir/"#{token.to_s.downcase}.rb" CoreCaskTap.instance.cask_dir/"#{token.to_s.downcase}.rb"
end end
def self.tap_paths(token, warn: true) def self.tap_paths(token, warn: true)

View File

@ -197,7 +197,11 @@ module Homebrew
formulae, casks = args.named.to_formulae_and_casks formulae, casks = args.named.to_formulae_and_casks
.partition { |formula_or_cask| formula_or_cask.is_a?(Formula) } .partition { |formula_or_cask| formula_or_cask.is_a?(Formula) }
rescue FormulaOrCaskUnavailableError, Cask::CaskUnavailableError rescue FormulaOrCaskUnavailableError, Cask::CaskUnavailableError
retry if Tap.install_default_cask_tap_if_necessary(force: args.cask?) cask_tap = CoreCaskTap.instance
if !cask_tap.installed? && (args.cask? || Tap.untapped_official_taps.exclude?(cask_tap.name))
cask_tap.ensure_installed!
retry if cask_tap.installed?
end
raise raise
end end

View File

@ -149,7 +149,7 @@ module Homebrew
updated_taps = [] updated_taps = []
Tap.each do |tap| Tap.each do |tap|
next if !tap.git? || tap.git_repo.origin_url.nil? next if !tap.git? || tap.git_repo.origin_url.nil?
next if (tap.core_tap? || tap == "homebrew/cask") && !Homebrew::EnvConfig.no_install_from_api? next if (tap.core_tap? || tap.core_cask_tap?) && !Homebrew::EnvConfig.no_install_from_api?
if ENV["HOMEBREW_MIGRATE_LINUXBREW_FORMULAE"].present? && tap.core_tap? && if ENV["HOMEBREW_MIGRATE_LINUXBREW_FORMULAE"].present? && tap.core_tap? &&
Settings.read("linuxbrewmigrated") != "true" Settings.read("linuxbrewmigrated") != "true"
@ -193,7 +193,7 @@ module Homebrew
unless Homebrew::EnvConfig.no_install_from_api? unless Homebrew::EnvConfig.no_install_from_api?
api_cache = Homebrew::API::HOMEBREW_CACHE_API api_cache = Homebrew::API::HOMEBREW_CACHE_API
core_tap = CoreTap.instance core_tap = CoreTap.instance
cask_tap = Tap.fetch("homebrew/cask") cask_tap = CoreCaskTap.instance
[ [
[:formula, core_tap, core_tap.formula_dir], [:formula, core_tap, core_tap.formula_dir],
[:cask, cask_tap, cask_tap.cask_dir], [:cask, cask_tap, cask_tap.cask_dir],
@ -402,7 +402,7 @@ module Homebrew
return if (HOMEBREW_PREFIX/".homebrewdocker").exist? return if (HOMEBREW_PREFIX/".homebrewdocker").exist?
core_tap = CoreTap.instance core_tap = CoreTap.instance
cask_tap = Tap.default_cask_tap cask_tap = CoreCaskTap.instance
return if !core_tap.installed? && !cask_tap.installed? return if !core_tap.installed? && !cask_tap.installed?
puts "Installing from the API is now the default behaviour!" puts "Installing from the API is now the default behaviour!"
@ -504,7 +504,7 @@ class Reporter
new_name = tap.cask_renames[old_name] new_name = tap.cask_renames[old_name]
next unless new_name next unless new_name
new_full_name = if tap.name == "homebrew/cask" new_full_name = if tap.core_cask_tap?
new_name new_name
else else
"#{tap}/#{new_name}" "#{tap}/#{new_name}"
@ -518,7 +518,7 @@ class Reporter
old_name = tap.cask_renames.key(new_name) old_name = tap.cask_renames.key(new_name)
next unless old_name next unless old_name
old_full_name = if tap.name == "homebrew/cask" old_full_name = if tap.core_cask_tap?
old_name old_name
else else
"#{tap}/#{old_name}" "#{tap}/#{old_name}"

View File

@ -42,7 +42,7 @@ module Homebrew
def generate_cask_api def generate_cask_api
args = generate_cask_api_args.parse args = generate_cask_api_args.parse
tap = Tap.default_cask_tap tap = CoreCaskTap.instance
raise TapUnavailableError, tap.name unless tap.installed? raise TapUnavailableError, tap.name unless tap.installed?
unless args.dry_run? unless args.dry_run?

View File

@ -531,7 +531,7 @@ module Homebrew
end end
def check_casktap_integrity def check_casktap_integrity
default_cask_tap = Tap.default_cask_tap default_cask_tap = CoreCaskTap.instance
return unless default_cask_tap.installed? return unless default_cask_tap.installed?
broken_tap(default_cask_tap) || examine_git_origin(default_cask_tap.git_repo, default_cask_tap.remote) broken_tap(default_cask_tap) || examine_git_origin(default_cask_tap.git_repo, default_cask_tap.remote)
@ -861,7 +861,7 @@ module Homebrew
return if Homebrew::EnvConfig.no_install_from_api? return if Homebrew::EnvConfig.no_install_from_api?
return if Homebrew::Settings.read("devcmdrun") == "true" return if Homebrew::Settings.read("devcmdrun") == "true"
cask_tap = Tap.fetch("homebrew", "cask") cask_tap = CoreCaskTap.instance
return unless cask_tap.installed? return unless cask_tap.installed?
<<~EOS <<~EOS
@ -921,7 +921,7 @@ module Homebrew
end end
def check_cask_taps def check_cask_taps
default_cask_tap = Tap.default_cask_tap default_cask_tap = CoreCaskTap.instance
alt_taps = Tap.select { |t| t.cask_dir.exist? && t != default_cask_tap } alt_taps = Tap.select { |t| t.cask_dir.exist? && t != default_cask_tap }
error_tap_paths = [] error_tap_paths = []

View File

@ -3,12 +3,15 @@
class Tap class Tap
def self.install_default_cask_tap_if_necessary(force: false) def self.install_default_cask_tap_if_necessary(force: false)
return false if default_cask_tap.installed? odeprecated "Tap.install_default_cask_tap_if_necessary", "CoreCaskTap.ensure_installed!"
cask_tap = CoreCaskTap.instance
return false if cask_tap.installed?
return false unless Homebrew::EnvConfig.no_install_from_api? return false unless Homebrew::EnvConfig.no_install_from_api?
return false if Homebrew::EnvConfig.automatically_set_no_install_from_api? return false if Homebrew::EnvConfig.automatically_set_no_install_from_api?
return false if !force && Tap.untapped_official_taps.include?(default_cask_tap.name) return false if !force && Tap.untapped_official_taps.include?(cask_tap.name)
default_cask_tap.install cask_tap.install
true true
end end
end end

View File

@ -52,6 +52,7 @@ class Tap
repo = repo.sub(HOMEBREW_OFFICIAL_REPO_PREFIXES_REGEX, "") repo = repo.sub(HOMEBREW_OFFICIAL_REPO_PREFIXES_REGEX, "")
return CoreTap.instance if ["Homebrew", "Linuxbrew"].include?(user) && ["core", "homebrew"].include?(repo) return CoreTap.instance if ["Homebrew", "Linuxbrew"].include?(user) && ["core", "homebrew"].include?(repo)
return CoreCaskTap.instance if user == "Homebrew" && repo == "cask"
cache_key = "#{user}/#{repo}".downcase cache_key = "#{user}/#{repo}".downcase
cache.fetch(cache_key) { |key| cache[key] = Tap.new(user, repo) } cache.fetch(cache_key) { |key| cache[key] = Tap.new(user, repo) }
@ -64,13 +65,17 @@ class Tap
fetch(match[:user], match[:repo]) fetch(match[:user], match[:repo])
end end
sig { returns(T.attached_class) } sig { returns(CoreCaskTap) }
def self.default_cask_tap def self.default_cask_tap
@default_cask_tap ||= fetch("Homebrew", "cask") odeprecated "Tap.default_cask_tap", "CoreCaskTap.instance"
CoreCaskTap.instance
end end
sig { params(force: T::Boolean).returns(T::Boolean) } sig { params(force: T::Boolean).returns(T::Boolean) }
def self.install_default_cask_tap_if_necessary(force: false) def self.install_default_cask_tap_if_necessary(force: false)
odeprecated "Tap.install_default_cask_tap_if_necessary", "CoreCaskTap.ensure_installed!"
false false
end end
@ -122,6 +127,7 @@ class Tap
@cask_dir = nil @cask_dir = nil
@command_dir = nil @command_dir = nil
@formula_files = nil @formula_files = nil
@cask_files = nil
@alias_dir = nil @alias_dir = nil
@alias_files = nil @alias_files = nil
@aliases = nil @aliases = nil
@ -138,6 +144,13 @@ class Tap
remove_instance_variable(:@private) if instance_variable_defined?(:@private) remove_instance_variable(:@private) if instance_variable_defined?(:@private)
end end
sig { void }
def ensure_installed!
return if installed?
install
end
# The remote path to this {Tap}. # The remote path to this {Tap}.
# e.g. `https://github.com/user/homebrew-repo` # e.g. `https://github.com/user/homebrew-repo`
def remote def remote
@ -245,6 +258,12 @@ class Tap
false false
end end
# @private
sig { returns(T::Boolean) }
def core_cask_tap?
false
end
# Install this {Tap}. # Install this {Tap}.
# #
# @param clone_target [String] If passed, it will be used as the clone remote. # @param clone_target [String] If passed, it will be used as the clone remote.
@ -299,7 +318,7 @@ class Tap
args << "-q" if quiet args << "-q" if quiet
path.cd { safe_system "git", *args } path.cd { safe_system "git", *args }
return return
elsif (core_tap? || name == "homebrew/cask") && !Homebrew::EnvConfig.no_install_from_api? && !force elsif (core_tap? || core_cask_tap?) && !Homebrew::EnvConfig.no_install_from_api? && !force
# odeprecated: move to odie in the next minor release. This may break some CI so we should give notice. # odeprecated: move to odie in the next minor release. This may break some CI so we should give notice.
opoo "Tapping #{name} is no longer typically necessary.\n" \ opoo "Tapping #{name} is no longer typically necessary.\n" \
"Add #{Formatter.option("--force")} if you are sure you need one." "Add #{Formatter.option("--force")} if you are sure you need one."
@ -553,13 +572,18 @@ class Tap
sig { params(tap: Tap).returns(T::Hash[String, Pathname]) } sig { params(tap: Tap).returns(T::Hash[String, Pathname]) }
def self.cask_files_by_name(tap) def self.cask_files_by_name(tap)
cache_key = "cask_files_by_name_#{tap}" cache_key = "cask_files_by_name_#{tap}"
cache.fetch(cache_key) do |key| cache.fetch(cache_key) do |key|
cache[key] = tap.cask_files.each_with_object({}) do |file, hash| cache[key] = tap.cask_files_by_name
# If there's more than one file with the same basename: intentionally end
# ignore the later ones here. end
hash[file.basename.to_s] ||= file
end # @private
sig { returns(T::Hash[String, Pathname]) }
def cask_files_by_name
cask_files.each_with_object({}) do |file, hash|
# If there's more than one file with the same basename: intentionally
# ignore the later ones here.
hash[file.basename.to_s] ||= file
end end
end end
@ -696,9 +720,7 @@ class Tap
# Hash with tap cask renames. # Hash with tap cask renames.
sig { returns(T::Hash[String, String]) } sig { returns(T::Hash[String, String]) }
def cask_renames def cask_renames
@cask_renames ||= if name == "homebrew/cask" && !Homebrew::EnvConfig.no_install_from_api? @cask_renames ||= if (rename_file = path/HOMEBREW_TAP_CASK_RENAMES_FILE).file?
Homebrew::API::Cask.all_renames
elsif (rename_file = path/HOMEBREW_TAP_CASK_RENAMES_FILE).file?
JSON.parse(rename_file.read) JSON.parse(rename_file.read)
else else
{} {}
@ -718,11 +740,7 @@ class Tap
# Hash with tap migrations. # Hash with tap migrations.
sig { returns(Hash) } sig { returns(Hash) }
def tap_migrations def tap_migrations
@tap_migrations ||= if name == "homebrew/cask" && !Homebrew::EnvConfig.no_install_from_api? @tap_migrations ||= if (migration_file = path/HOMEBREW_TAP_MIGRATIONS_FILE).file?
migrations, = Homebrew::API.fetch_json_api_file "cask_tap_migrations.jws.json",
stale_seconds: TAP_MIGRATIONS_STALE_SECONDS
migrations
elsif (migration_file = path/HOMEBREW_TAP_MIGRATIONS_FILE).file?
JSON.parse(migration_file.read) JSON.parse(migration_file.read)
else else
{} {}
@ -749,9 +767,7 @@ class Tap
# @private # @private
sig { returns(T::Boolean) } sig { returns(T::Boolean) }
def should_report_analytics? def should_report_analytics?
return !Homebrew::EnvConfig.no_install_from_api? && official? unless installed? installed? && !private?
!private?
end end
sig { params(other: T.nilable(T.any(String, Tap))).returns(T::Boolean) } sig { params(other: T.nilable(T.any(String, Tap))).returns(T::Boolean) }
@ -867,29 +883,51 @@ class Tap
end end
end end
class AbstractCoreTap < Tap
extend T::Helpers
abstract!
sig { returns(T.attached_class) }
def self.instance
@instance ||= T.unsafe(self).new
end
sig { override.void }
def ensure_installed!
return unless Homebrew::EnvConfig.no_install_from_api?
return if Homebrew::EnvConfig.automatically_set_no_install_from_api?
super
end
sig { void }
def self.ensure_installed!
instance.ensure_installed!
end
# @private
sig { override.returns(T::Boolean) }
def should_report_analytics?
return super if Homebrew::EnvConfig.no_install_from_api?
true
end
end
# A specialized {Tap} class for the core formulae. # A specialized {Tap} class for the core formulae.
class CoreTap < Tap class CoreTap < AbstractCoreTap
# @private # @private
sig { void } sig { void }
def initialize def initialize
super "Homebrew", "core" super "Homebrew", "core"
end end
sig { returns(CoreTap) } sig { override.void }
def self.instance def ensure_installed!
@instance ||= new
end
sig { void }
def self.ensure_installed!
return if instance.installed?
return unless Homebrew::EnvConfig.no_install_from_api?
return if Homebrew::EnvConfig.automatically_set_no_install_from_api?
# Tests override homebrew-core locations and we don't want to auto-tap in them.
return if ENV["HOMEBREW_TESTS"] return if ENV["HOMEBREW_TESTS"]
instance.install super
end end
sig { returns(String) } sig { returns(String) }
@ -1046,20 +1084,74 @@ class CoreTap < Tap
def formula_files_by_name def formula_files_by_name
return super if Homebrew::EnvConfig.no_install_from_api? return super if Homebrew::EnvConfig.no_install_from_api?
formula_names.each_with_object({}) do |name, hash| Homebrew::API::Formula.all_formulae.each_with_object({}) do |item, hash|
name, formula_hash = item
# If there's more than one file with the same basename: intentionally # If there's more than one file with the same basename: intentionally
# ignore the later ones here. # ignore the later ones here.
hash[name] ||= sharded_formula_path(name) hash[name] ||= path/formula_hash["ruby_source_path"]
end
end
end
# A specialized {Tap} class for homebrew-cask.
class CoreCaskTap < AbstractCoreTap
# @private
sig { void }
def initialize
super "Homebrew", "cask"
end
# @private
sig { override.returns(T::Boolean) }
def core_cask_tap?
true
end
sig { override.returns(T::Array[Pathname]) }
def cask_files
return super if Homebrew::EnvConfig.no_install_from_api? || installed?
raise TapUnavailableError, name
end
sig { override.returns(T::Array[String]) }
def cask_tokens
return super if Homebrew::EnvConfig.no_install_from_api?
Homebrew::API::Cask.all_casks.keys
end
# @private
sig { override.returns(T::Hash[String, Pathname]) }
def cask_files_by_name
return super if Homebrew::EnvConfig.no_install_from_api?
Homebrew::API::Cask.all_casks.each_with_object({}) do |item, hash|
name, cask_hash = item
# If there's more than one file with the same basename: intentionally
# ignore the later ones here.
hash[name] ||= path/cask_hash["ruby_source_path"]
end end
end end
private sig { override.returns(T::Hash[String, String]) }
def cask_renames
@cask_renames ||= if Homebrew::EnvConfig.no_install_from_api?
super
else
Homebrew::API::Cask.all_renames
end
end
# @private sig { override.returns(Hash) }
sig { params(name: String).returns(Pathname) } def tap_migrations
def sharded_formula_path(name) @tap_migrations ||= if Homebrew::EnvConfig.no_install_from_api?
# TODO: add sharding logic. super
formula_dir/"#{name}.rb" else
migrations, = Homebrew::API.fetch_json_api_file "cask_tap_migrations.jws.json",
stale_seconds: TAP_MIGRATIONS_STALE_SECONDS
migrations
end
end end
end end

View File

@ -3,10 +3,11 @@
describe Cask::CaskLoader::FromTapLoader do describe Cask::CaskLoader::FromTapLoader do
let(:cask_name) { "testball" } let(:cask_name) { "testball" }
let(:cask_full_name) { "homebrew/cask/#{cask_name}" } let(:cask_full_name) { "homebrew/cask/#{cask_name}" }
let(:cask_path) { Tap.default_cask_tap.cask_dir/"#{cask_name}.rb" } let(:cask_path) { CoreCaskTap.instance.cask_dir/"#{cask_name}.rb" }
describe "#load" do describe "#load" do
before do before do
CoreCaskTap.instance.clear_cache
cask_path.parent.mkpath cask_path.parent.mkpath
cask_path.write <<~RUBY cask_path.write <<~RUBY
cask '#{cask_name}' do cask '#{cask_name}' do
@ -24,7 +25,7 @@ describe Cask::CaskLoader::FromTapLoader do
end end
context "with sharded Cask directory" do context "with sharded Cask directory" do
let(:cask_path) { Tap.default_cask_tap.cask_dir/cask_name[0]/"#{cask_name}.rb" } let(:cask_path) { CoreCaskTap.instance.cask_dir/cask_name[0]/"#{cask_name}.rb" }
it "returns a Cask" do it "returns a Cask" do
expect(described_class.new(cask_full_name).load(config: nil)).to be_a(Cask::Cask) expect(described_class.new(cask_full_name).load(config: nil)).to be_a(Cask::Cask)

View File

@ -24,7 +24,7 @@ describe Cask::Cask, :cask do
end end
describe "load" do describe "load" do
let(:tap_path) { Tap.default_cask_tap.path } let(:tap_path) { CoreCaskTap.instance.path }
let(:file_dirname) { Pathname.new(__FILE__).dirname } let(:file_dirname) { Pathname.new(__FILE__).dirname }
let(:relative_tap_path) { tap_path.relative_path_from(file_dirname) } let(:relative_tap_path) { tap_path.relative_path_from(file_dirname) }

View File

@ -119,7 +119,7 @@ describe Homebrew::Diagnostic::Checks do
ENV.delete("HOMEBREW_DEVELOPER") ENV.delete("HOMEBREW_DEVELOPER")
ENV.delete("HOMEBREW_NO_INSTALL_FROM_API") ENV.delete("HOMEBREW_NO_INSTALL_FROM_API")
allow(CoreTap).to receive(:installed?).and_return(true) expect_any_instance_of(CoreTap).to receive(:installed?).and_return(true)
expect(checks.check_for_unnecessary_core_tap).to match("You have an unnecessary local Core tap") expect(checks.check_for_unnecessary_core_tap).to match("You have an unnecessary local Core tap")
end end
@ -128,9 +128,7 @@ describe Homebrew::Diagnostic::Checks do
ENV.delete("HOMEBREW_DEVELOPER") ENV.delete("HOMEBREW_DEVELOPER")
ENV.delete("HOMEBREW_NO_INSTALL_FROM_API") ENV.delete("HOMEBREW_NO_INSTALL_FROM_API")
cask_tap = Tap.new("homebrew", "cask") expect_any_instance_of(CoreCaskTap).to receive(:installed?).and_return(true)
allow(Tap).to receive(:fetch).with("homebrew", "cask").and_return(cask_tap)
allow(cask_tap).to receive(:installed?).and_return(true)
expect(checks.check_for_unnecessary_cask_tap).to match("unnecessary local Cask tap") expect(checks.check_for_unnecessary_cask_tap).to match("unnecessary local Cask tap")
end end

View File

@ -38,7 +38,7 @@ RSpec.shared_context "Homebrew Cask", :needs_macos do # rubocop:disable RSpec/Co
begin begin
Cask::Config::DEFAULT_DIRS_PATHNAMES.each_value(&:mkpath) Cask::Config::DEFAULT_DIRS_PATHNAMES.each_value(&:mkpath)
Tap.default_cask_tap.tap do |tap| CoreCaskTap.instance.tap do |tap|
FileUtils.mkdir_p tap.path.dirname FileUtils.mkdir_p tap.path.dirname
FileUtils.ln_sf TEST_FIXTURE_DIR.join("cask"), tap.path FileUtils.ln_sf TEST_FIXTURE_DIR.join("cask"), tap.path
end end
@ -52,7 +52,7 @@ RSpec.shared_context "Homebrew Cask", :needs_macos do # rubocop:disable RSpec/Co
ensure ensure
FileUtils.rm_rf Cask::Config::DEFAULT_DIRS_PATHNAMES.values FileUtils.rm_rf Cask::Config::DEFAULT_DIRS_PATHNAMES.values
FileUtils.rm_rf [Cask::Config.new.binarydir, Cask::Caskroom.path, Cask::Cache.path] FileUtils.rm_rf [Cask::Config.new.binarydir, Cask::Caskroom.path, Cask::Cache.path]
Tap.default_cask_tap.path.unlink CoreCaskTap.instance.path.unlink
third_party_tap.path.unlink third_party_tap.path.unlink
FileUtils.rm_rf third_party_tap.path.parent FileUtils.rm_rf third_party_tap.path.parent
end end