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
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
def self.tap_paths(token, warn: true)

View File

@ -197,7 +197,11 @@ module Homebrew
formulae, casks = args.named.to_formulae_and_casks
.partition { |formula_or_cask| formula_or_cask.is_a?(Formula) }
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
end

View File

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

View File

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

View File

@ -531,7 +531,7 @@ module Homebrew
end
def check_casktap_integrity
default_cask_tap = Tap.default_cask_tap
default_cask_tap = CoreCaskTap.instance
return unless default_cask_tap.installed?
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::Settings.read("devcmdrun") == "true"
cask_tap = Tap.fetch("homebrew", "cask")
cask_tap = CoreCaskTap.instance
return unless cask_tap.installed?
<<~EOS
@ -921,7 +921,7 @@ module Homebrew
end
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 }
error_tap_paths = []

View File

@ -3,12 +3,15 @@
class Tap
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 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
end
end

View File

@ -52,6 +52,7 @@ class Tap
repo = repo.sub(HOMEBREW_OFFICIAL_REPO_PREFIXES_REGEX, "")
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.fetch(cache_key) { |key| cache[key] = Tap.new(user, repo) }
@ -64,13 +65,17 @@ class Tap
fetch(match[:user], match[:repo])
end
sig { returns(T.attached_class) }
sig { returns(CoreCaskTap) }
def self.default_cask_tap
@default_cask_tap ||= fetch("Homebrew", "cask")
odeprecated "Tap.default_cask_tap", "CoreCaskTap.instance"
CoreCaskTap.instance
end
sig { params(force: T::Boolean).returns(T::Boolean) }
def self.install_default_cask_tap_if_necessary(force: false)
odeprecated "Tap.install_default_cask_tap_if_necessary", "CoreCaskTap.ensure_installed!"
false
end
@ -122,6 +127,7 @@ class Tap
@cask_dir = nil
@command_dir = nil
@formula_files = nil
@cask_files = nil
@alias_dir = nil
@alias_files = nil
@aliases = nil
@ -138,6 +144,13 @@ class Tap
remove_instance_variable(:@private) if instance_variable_defined?(:@private)
end
sig { void }
def ensure_installed!
return if installed?
install
end
# The remote path to this {Tap}.
# e.g. `https://github.com/user/homebrew-repo`
def remote
@ -245,6 +258,12 @@ class Tap
false
end
# @private
sig { returns(T::Boolean) }
def core_cask_tap?
false
end
# Install this {Tap}.
#
# @param clone_target [String] If passed, it will be used as the clone remote.
@ -299,7 +318,7 @@ class Tap
args << "-q" if quiet
path.cd { safe_system "git", *args }
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.
opoo "Tapping #{name} is no longer typically necessary.\n" \
"Add #{Formatter.option("--force")} if you are sure you need one."
@ -553,15 +572,20 @@ class Tap
sig { params(tap: Tap).returns(T::Hash[String, Pathname]) }
def self.cask_files_by_name(tap)
cache_key = "cask_files_by_name_#{tap}"
cache.fetch(cache_key) do |key|
cache[key] = tap.cask_files.each_with_object({}) do |file, hash|
cache[key] = tap.cask_files_by_name
end
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
# returns true if the file has a Ruby extension
# @private
@ -696,9 +720,7 @@ class Tap
# Hash with tap cask renames.
sig { returns(T::Hash[String, String]) }
def cask_renames
@cask_renames ||= if name == "homebrew/cask" && !Homebrew::EnvConfig.no_install_from_api?
Homebrew::API::Cask.all_renames
elsif (rename_file = path/HOMEBREW_TAP_CASK_RENAMES_FILE).file?
@cask_renames ||= if (rename_file = path/HOMEBREW_TAP_CASK_RENAMES_FILE).file?
JSON.parse(rename_file.read)
else
{}
@ -718,11 +740,7 @@ class Tap
# Hash with tap migrations.
sig { returns(Hash) }
def tap_migrations
@tap_migrations ||= if name == "homebrew/cask" && !Homebrew::EnvConfig.no_install_from_api?
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?
@tap_migrations ||= if (migration_file = path/HOMEBREW_TAP_MIGRATIONS_FILE).file?
JSON.parse(migration_file.read)
else
{}
@ -749,9 +767,7 @@ class Tap
# @private
sig { returns(T::Boolean) }
def should_report_analytics?
return !Homebrew::EnvConfig.no_install_from_api? && official? unless installed?
!private?
installed? && !private?
end
sig { params(other: T.nilable(T.any(String, Tap))).returns(T::Boolean) }
@ -867,29 +883,51 @@ class Tap
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.
class CoreTap < Tap
class CoreTap < AbstractCoreTap
# @private
sig { void }
def initialize
super "Homebrew", "core"
end
sig { returns(CoreTap) }
def self.instance
@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.
sig { override.void }
def ensure_installed!
return if ENV["HOMEBREW_TESTS"]
instance.install
super
end
sig { returns(String) }
@ -1046,20 +1084,74 @@ class CoreTap < Tap
def formula_files_by_name
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
# ignore the later ones here.
hash[name] ||= sharded_formula_path(name)
hash[name] ||= path/formula_hash["ruby_source_path"]
end
end
end
private
# A specialized {Tap} class for homebrew-cask.
class CoreCaskTap < AbstractCoreTap
# @private
sig { void }
def initialize
super "Homebrew", "cask"
end
# @private
sig { params(name: String).returns(Pathname) }
def sharded_formula_path(name)
# TODO: add sharding logic.
formula_dir/"#{name}.rb"
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
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
sig { override.returns(Hash) }
def tap_migrations
@tap_migrations ||= if Homebrew::EnvConfig.no_install_from_api?
super
else
migrations, = Homebrew::API.fetch_json_api_file "cask_tap_migrations.jws.json",
stale_seconds: TAP_MIGRATIONS_STALE_SECONDS
migrations
end
end
end

View File

@ -3,10 +3,11 @@
describe Cask::CaskLoader::FromTapLoader do
let(:cask_name) { "testball" }
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
before do
CoreCaskTap.instance.clear_cache
cask_path.parent.mkpath
cask_path.write <<~RUBY
cask '#{cask_name}' do
@ -24,7 +25,7 @@ describe Cask::CaskLoader::FromTapLoader do
end
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
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
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(: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_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")
end
@ -128,9 +128,7 @@ describe Homebrew::Diagnostic::Checks do
ENV.delete("HOMEBREW_DEVELOPER")
ENV.delete("HOMEBREW_NO_INSTALL_FROM_API")
cask_tap = Tap.new("homebrew", "cask")
allow(Tap).to receive(:fetch).with("homebrew", "cask").and_return(cask_tap)
allow(cask_tap).to receive(:installed?).and_return(true)
expect_any_instance_of(CoreCaskTap).to receive(:installed?).and_return(true)
expect(checks.check_for_unnecessary_cask_tap).to match("unnecessary local Cask tap")
end

View File

@ -38,7 +38,7 @@ RSpec.shared_context "Homebrew Cask", :needs_macos do # rubocop:disable RSpec/Co
begin
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.ln_sf TEST_FIXTURE_DIR.join("cask"), tap.path
end
@ -52,7 +52,7 @@ RSpec.shared_context "Homebrew Cask", :needs_macos do # rubocop:disable RSpec/Co
ensure
FileUtils.rm_rf Cask::Config::DEFAULT_DIRS_PATHNAMES.values
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
FileUtils.rm_rf third_party_tap.path.parent
end