Merge pull request #16684 from Homebrew/revert-16623-formulary-loader-for

Revert "Refactor `Formulary::loader_for`."
This commit is contained in:
Mike McQuaid 2024-02-16 13:52:19 +00:00 committed by GitHub
commit a4ffd6761e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 229 additions and 377 deletions

View File

@ -59,7 +59,7 @@ module Homebrew
end end
private :download_and_cache_data! private :download_and_cache_data!
sig { returns(T::Hash[String, Hash]) } sig { returns(Hash) }
def all_formulae def all_formulae
unless cache.key?("formulae") unless cache.key?("formulae")
json_updated = download_and_cache_data! json_updated = download_and_cache_data!
@ -69,7 +69,7 @@ module Homebrew
cache["formulae"] cache["formulae"]
end end
sig { returns(T::Hash[String, String]) } sig { returns(Hash) }
def all_aliases def all_aliases
unless cache.key?("aliases") unless cache.key?("aliases")
json_updated = download_and_cache_data! json_updated = download_and_cache_data!

View File

@ -130,7 +130,7 @@ module Homebrew
formula = begin formula = begin
Formulary.from_rack(HOMEBREW_CELLAR/formula_name) Formulary.from_rack(HOMEBREW_CELLAR/formula_name)
rescue FormulaUnavailableError, TapFormulaAmbiguityError rescue FormulaUnavailableError, TapFormulaAmbiguityError, TapFormulaWithOldnameAmbiguityError
nil nil
end end
@ -300,7 +300,7 @@ module Homebrew
args.each do |arg| args.each do |arg|
formula = begin formula = begin
Formulary.resolve(arg) Formulary.resolve(arg)
rescue FormulaUnavailableError, TapFormulaAmbiguityError rescue FormulaUnavailableError, TapFormulaAmbiguityError, TapFormulaWithOldnameAmbiguityError
nil nil
end end

View File

@ -733,7 +733,8 @@ module Homebrew
rescue FormulaUnreadableError, FormulaClassUnavailableError, rescue FormulaUnreadableError, FormulaClassUnavailableError,
TapFormulaUnreadableError, TapFormulaClassUnavailableError => e TapFormulaUnreadableError, TapFormulaClassUnavailableError => e
formula_unavailable_exceptions << e formula_unavailable_exceptions << e
rescue FormulaUnavailableError, TapFormulaAmbiguityError rescue FormulaUnavailableError,
TapFormulaAmbiguityError, TapFormulaWithOldnameAmbiguityError
nil nil
end end
return if formula_unavailable_exceptions.empty? return if formula_unavailable_exceptions.empty?
@ -751,7 +752,7 @@ module Homebrew
else else
begin begin
Formulary.from_rack(rack).keg_only? Formulary.from_rack(rack).keg_only?
rescue FormulaUnavailableError, TapFormulaAmbiguityError rescue FormulaUnavailableError, TapFormulaAmbiguityError, TapFormulaWithOldnameAmbiguityError
false false
end end
end end
@ -834,30 +835,16 @@ module Homebrew
kegs = Keg.all kegs = Keg.all
deleted_formulae = kegs.map do |keg| deleted_formulae = kegs.map do |keg|
tap = Tab.for_keg(keg).tap next if Formulary.tap_paths(keg.name).any?
loadable = [ unless EnvConfig.no_install_from_api?
Formulary::FromAPILoader, # Formulae installed from the API should not count as deleted formulae
Formulary::FromDefaultNameLoader, # but may not have a tap listed in their tab
Formulary::FromNameLoader, tap = Tab.for_keg(keg).tap
].any? do |loader_class| next if (tap.blank? || tap.core_tap?) && Homebrew::API::Formula.all_formulae.key?(keg.name)
loader = begin
loader_class.try_new(keg.name, warn: false)
rescue TapFormulaAmbiguityError => e
e.loaders.first
end
if loader
# If we know the tap, ignore all other taps.
next false if tap && loader.tap != tap
next true
end
false
end end
keg.name unless loadable keg.name
end.compact.uniq end.compact.uniq
return if deleted_formulae.blank? return if deleted_formulae.blank?

View File

@ -259,20 +259,40 @@ end
# Raised when a formula with the same name is found in multiple taps. # Raised when a formula with the same name is found in multiple taps.
class TapFormulaAmbiguityError < RuntimeError class TapFormulaAmbiguityError < RuntimeError
attr_reader :name, :taps, :loaders attr_reader :name, :paths, :formulae
def initialize(name, loaders) def initialize(name, paths)
@name = name @name = name
@loaders = loaders @paths = paths
@taps = loaders.map(&:tap) @formulae = paths.map do |path|
"#{Tap.from_path(path).name}/#{path.basename(".rb")}"
formulae = taps.map { |tap| "#{tap}/#{name}" } end
formula_list = formulae.map { |f| "\n * #{f}" }.join
super <<~EOS super <<~EOS
Formulae found in multiple taps:#{formula_list} Formulae found in multiple taps: #{formulae.map { |f| "\n * #{f}" }.join}
Please use the fully-qualified name (e.g. #{formulae.first}) to refer to a specific formula. Please use the fully-qualified name (e.g. #{formulae.first}) to refer to the formula.
EOS
end
end
# Raised when a formula's old name in a specific tap is found in multiple taps.
class TapFormulaWithOldnameAmbiguityError < RuntimeError
attr_reader :name, :possible_tap_newname_formulae, :taps
def initialize(name, possible_tap_newname_formulae)
@name = name
@possible_tap_newname_formulae = possible_tap_newname_formulae
@taps = possible_tap_newname_formulae.map do |newname|
newname =~ HOMEBREW_TAP_FORMULA_REGEX
"#{Regexp.last_match(1)}/#{Regexp.last_match(2)}"
end
super <<~EOS
Formulae with '#{name}' old name found in multiple taps: #{taps.map { |t| "\n * #{t}" }.join}
Please use the fully-qualified name (e.g. #{taps.first}/#{name}) to refer to the formula or use its new name.
EOS EOS
end end
end end

View File

@ -77,7 +77,7 @@ class Formula
# The path to the alias that was used to identify this {Formula}. # The path to the alias that was used to identify this {Formula}.
# e.g. `/usr/local/Library/Taps/homebrew/homebrew-core/Aliases/another-name-for-this-formula` # e.g. `/usr/local/Library/Taps/homebrew/homebrew-core/Aliases/another-name-for-this-formula`
sig { returns(T.nilable(Pathname)) } sig { returns(T.any(NilClass, Pathname, String)) }
attr_reader :alias_path attr_reader :alias_path
# The name of the alias that was used to identify this {Formula}. # The name of the alias that was used to identify this {Formula}.
@ -199,7 +199,7 @@ class Formula
# @private # @private
sig { sig {
params(name: String, path: Pathname, spec: Symbol, alias_path: T.nilable(Pathname), params(name: String, path: Pathname, spec: Symbol, alias_path: T.any(NilClass, Pathname, String),
tap: T.nilable(Tap), force_bottle: T::Boolean).void tap: T.nilable(Tap), force_bottle: T::Boolean).void
} }
def initialize(name, path, spec, alias_path: nil, tap: nil, force_bottle: false) def initialize(name, path, spec, alias_path: nil, tap: nil, force_bottle: false)
@ -326,22 +326,18 @@ class Formula
# The alias path that was used to install this formula, if it exists. # The alias path that was used to install this formula, if it exists.
# Can differ from {#alias_path}, which is the alias used to find the formula, # Can differ from {#alias_path}, which is the alias used to find the formula,
# and is specified to this instance. # and is specified to this instance.
sig { returns(T.nilable(Pathname)) }
def installed_alias_path def installed_alias_path
build_tab = build build_tab = build
path = build_tab.source["path"] if build_tab.is_a?(Tab) path = build_tab.source["path"] if build_tab.is_a?(Tab)
return unless path&.match?(%r{#{HOMEBREW_TAP_DIR_REGEX}/Aliases}o) return unless path&.match?(%r{#{HOMEBREW_TAP_DIR_REGEX}/Aliases}o)
return unless File.symlink?(path)
path = Pathname(path)
return unless path.symlink?
path path
end end
sig { returns(T.nilable(String)) } sig { returns(T.nilable(String)) }
def installed_alias_name def installed_alias_name
installed_alias_path&.basename&.to_s File.basename(installed_alias_path) if installed_alias_path
end end
def full_installed_alias_name def full_installed_alias_name
@ -350,13 +346,14 @@ class Formula
# The path that was specified to find this formula. # The path that was specified to find this formula.
def specified_path def specified_path
return alias_path if alias_path&.exist? alias_pathname = Pathname(T.must(alias_path)) if alias_path.present?
return alias_pathname if alias_pathname&.exist?
return @unresolved_path if @unresolved_path.exist? return @unresolved_path if @unresolved_path.exist?
return local_bottle_path if local_bottle_path.presence&.exist? return local_bottle_path if local_bottle_path.presence&.exist?
alias_path || @unresolved_path alias_pathname || @unresolved_path
end end
# The name specified to find this formula. # The name specified to find this formula.
@ -1318,7 +1315,7 @@ class Formula
f = Formulary.factory(keg.name) f = Formulary.factory(keg.name)
rescue FormulaUnavailableError rescue FormulaUnavailableError
# formula for this keg is deleted, so defer to allowlist # formula for this keg is deleted, so defer to allowlist
rescue TapFormulaAmbiguityError rescue TapFormulaAmbiguityError, TapFormulaWithOldnameAmbiguityError
return false # this keg belongs to another formula return false # this keg belongs to another formula
else else
# this keg belongs to another unrelated formula # this keg belongs to another unrelated formula
@ -2365,7 +2362,7 @@ class Formula
# Take from API, merging in local install status. # Take from API, merging in local install status.
if loaded_from_api? && !Homebrew::EnvConfig.no_install_from_api? if loaded_from_api? && !Homebrew::EnvConfig.no_install_from_api?
json_formula = Homebrew::API::Formula.all_formulae.fetch(name).dup json_formula = Homebrew::API::Formula.all_formulae[name].dup
return json_formula.merge( return json_formula.merge(
hash.slice("name", "installed", "linked_keg", "pinned", "outdated"), hash.slice("name", "installed", "linked_keg", "pinned", "outdated"),
) )

View File

@ -64,7 +64,8 @@ module Homebrew
unversioned_formula = begin unversioned_formula = begin
Formulary.factory(full_name).path Formulary.factory(full_name).path
rescue FormulaUnavailableError, TapFormulaAmbiguityError rescue FormulaUnavailableError, TapFormulaAmbiguityError,
TapFormulaWithOldnameAmbiguityError
Pathname.new formula.path.to_s.gsub(/@.*\.rb$/, ".rb") Pathname.new formula.path.to_s.gsub(/@.*\.rb$/, ".rb")
end end
unless unversioned_formula.exist? unless unversioned_formula.exist?
@ -284,6 +285,9 @@ module Homebrew
rescue TapFormulaAmbiguityError rescue TapFormulaAmbiguityError
problem "Ambiguous dependency '#{dep.name}'." problem "Ambiguous dependency '#{dep.name}'."
next next
rescue TapFormulaWithOldnameAmbiguityError
problem "Ambiguous oldname dependency '#{dep.name.inspect}'."
next
end end
if dep_f.oldnames.include?(dep.name.split("/").last) if dep_f.oldnames.include?(dep.name.split("/").last)
@ -457,7 +461,7 @@ module Homebrew
next next
rescue FormulaUnavailableError rescue FormulaUnavailableError
problem "Can't find conflicting formula #{conflict.name.inspect}." problem "Can't find conflicting formula #{conflict.name.inspect}."
rescue TapFormulaAmbiguityError rescue TapFormulaAmbiguityError, TapFormulaWithOldnameAmbiguityError
problem "Ambiguous conflicting formula #{conflict.name.inspect}." problem "Ambiguous conflicting formula #{conflict.name.inspect}."
end end
end end

View File

@ -16,7 +16,6 @@ require "extend/hash/keys"
# #
# @api private # @api private
module Formulary module Formulary
extend Context
extend Cachable extend Cachable
URL_START_REGEX = %r{(https?|ftp|file)://} URL_START_REGEX = %r{(https?|ftp|file)://}
@ -481,26 +480,17 @@ module Formulary
include Context include Context
# The formula's name # The formula's name
sig { returns(String) }
attr_reader :name attr_reader :name
# The formula's ruby file's path or filename # The formula's ruby file's path or filename
sig { returns(Pathname) }
attr_reader :path attr_reader :path
# The name used to install the formula # The name used to install the formula
sig { returns(T.nilable(Pathname)) }
attr_reader :alias_path attr_reader :alias_path
# The formula's tap (nil if it should be implicitly determined) # The formula's tap (nil if it should be implicitly determined)
sig { returns(T.nilable(Tap)) }
attr_reader :tap attr_reader :tap
sig { params(name: String, path: Pathname, alias_path: Pathname, tap: Tap).void } def initialize(name, path, tap: nil)
def initialize(name, path, alias_path: T.unsafe(nil), tap: T.unsafe(nil))
@name = name @name = name
@path = path @path = path
@alias_path = alias_path
@tap = tap @tap = tap
end end
@ -521,6 +511,7 @@ module Formulary
private private
def load_file(flags:, ignore_errors:) def load_file(flags:, ignore_errors:)
$stderr.puts "#{$PROGRAM_NAME} (#{self.class.name}): loading #{path}" if debug?
raise FormulaUnavailableError, name unless path.file? raise FormulaUnavailableError, name unless path.file?
Formulary.load_formula_from_path(name, path, flags: flags, ignore_errors: ignore_errors) Formulary.load_formula_from_path(name, path, flags: flags, ignore_errors: ignore_errors)
@ -528,17 +519,7 @@ module Formulary
end end
# Loads a formula from a bottle. # Loads a formula from a bottle.
class FromBottleLoader < FormulaLoader class BottleLoader < FormulaLoader
sig {
params(ref: T.any(String, Pathname, URI::Generic), from: Symbol, warn: T::Boolean)
.returns(T.nilable(T.attached_class))
}
def self.try_new(ref, from: T.unsafe(nil), warn: false)
ref = ref.to_s
new(ref) if HOMEBREW_BOTTLES_EXTNAME_REGEX.match?(ref)
end
def initialize(bottle_name) def initialize(bottle_name)
case bottle_name case bottle_name
when URL_START_REGEX when URL_START_REGEX
@ -581,67 +562,27 @@ module Formulary
end end
end end
# Loads formulae from disk using a path. # Loads a formula from a path to an alias.
class FromPathLoader < FormulaLoader class AliasLoader < FormulaLoader
sig { def initialize(alias_path)
params(ref: T.any(String, Pathname, URI::Generic), from: Symbol, warn: T::Boolean) path = alias_path.resolved_path
.returns(T.nilable(T.attached_class))
}
def self.try_new(ref, from: T.unsafe(nil), warn: false)
path = case ref
when String
Pathname(ref)
when Pathname
ref
else
return
end
return unless path.expand_path.exist?
options = if path.symlink?
alias_path = path
path = alias_path.resolved_path
{ alias_path: alias_path }
else
{}
end
return if path.extname != ".rb"
new(path, **options)
end
sig { params(path: T.any(Pathname, String), alias_path: Pathname).void }
def initialize(path, alias_path: T.unsafe(nil))
path = Pathname(path).expand_path
name = path.basename(".rb").to_s name = path.basename(".rb").to_s
alias_path = alias_path&.expand_path super name, path
alias_dir = alias_path&.dirname @alias_path = alias_path.to_s
tap = Tap.from_path(path) || Homebrew::API.tap_from_source_download(path)
options = {
alias_path: (alias_path if alias_dir == tap&.alias_dir),
tap: tap,
}.compact
super(name, path, **options)
end end
end end
# Loads formula from a URI. # Loads formulae from disk using a path.
class FromURILoader < FormulaLoader class FromPathLoader < FormulaLoader
sig { def initialize(path)
params(ref: T.any(String, Pathname, URI::Generic), from: Symbol, warn: T::Boolean) path = Pathname.new(path).expand_path
.returns(T.nilable(T.attached_class)) name = path.basename(".rb").to_s
} super name, path, tap: Homebrew::API.tap_from_source_download(path)
def self.try_new(ref, from: T.unsafe(nil), warn: false)
ref = ref.to_s
new(ref, from: from) if URL_START_REGEX.match?(ref)
end end
end
# Loads formulae from URLs.
class FromUrlLoader < FormulaLoader
attr_reader :url attr_reader :url
sig { params(url: T.any(URI::Generic, String), from: T.nilable(Symbol)).void } sig { params(url: T.any(URI::Generic, String), from: T.nilable(Symbol)).void }
@ -680,45 +621,7 @@ module Formulary
end end
# Loads tapped formulae. # Loads tapped formulae.
class FromTapLoader < FormulaLoader class TapLoader < FormulaLoader
sig { returns(Tap) }
attr_reader :tap
sig { returns(Pathname) }
attr_reader :path
sig {
params(ref: T.any(String, Pathname, URI::Generic), from: Symbol, warn: T::Boolean)
.returns(T.nilable(T.attached_class))
}
def self.try_new(ref, from: T.unsafe(nil), warn: false)
ref = ref.to_s
return unless (name = ref[HOMEBREW_TAP_FORMULA_REGEX, :name])
alias_name = name
name, tap, type = Formulary.tap_formula_name_type(ref, warn: warn)
path = Formulary.find_formula_in_tap(name, tap)
options = if type == :alias
{ alias_name: alias_name.downcase }
else
{}
end
new(name, path, tap: tap, **options)
end
sig { params(name: String, path: Pathname, tap: Tap, alias_name: String).void }
def initialize(name, path, tap:, alias_name: T.unsafe(nil))
options = {
alias_path: (tap.alias_dir/alias_name if alias_name),
tap: tap,
}.compact
super(name, path, **options)
end
def get_formula(spec, alias_path: nil, force_bottle: false, flags: [], ignore_errors: false) def get_formula(spec, alias_path: nil, force_bottle: false, flags: [], ignore_errors: false)
super super
rescue FormulaUnreadableError => e rescue FormulaUnreadableError => e
@ -735,94 +638,17 @@ module Formulary
e.issues_url = tap.issues_url || tap.to_s e.issues_url = tap.issues_url || tap.to_s
raise raise
end end
end
class FromDefaultNameLoader < FromTapLoader private
sig {
params(ref: T.any(String, Pathname, URI::Generic), from: Symbol, warn: T::Boolean)
.returns(T.nilable(T.attached_class))
}
def self.try_new(ref, from: T.unsafe(nil), warn: false)
return unless ref.is_a?(String)
return unless (name = ref[HOMEBREW_DEFAULT_TAP_FORMULA_REGEX, :name])
return unless (tap = CoreTap.instance).installed?
return unless (loader = super("#{tap}/#{name}", warn: warn)) def find_formula_from_name(name, tap)
Formulary.find_formula_in_tap(name, tap)
loader if loader.path.exist?
end
end
# Loads a formula from a name, as long as it exists only in a single tap.
class FromNameLoader < FromTapLoader
sig {
params(ref: T.any(String, Pathname, URI::Generic), from: Symbol, warn: T::Boolean)
.returns(T.nilable(T.attached_class))
}
def self.try_new(ref, from: T.unsafe(nil), warn: false)
return unless ref.is_a?(String)
return if ref.include?("/")
name = ref
loaders = Tap.map { |tap| super("#{tap}/#{name}") }
.compact
.select { _1.path.exist? }
case loaders.count
when 1
loaders.first
when 2..Float::INFINITY
raise TapFormulaAmbiguityError.new(name, loaders)
end
end
end
# Loads a formula from a formula file in a keg.
class FromKegLoader < FormulaLoader
sig {
params(ref: T.any(String, Pathname, URI::Generic), from: Symbol, warn: T::Boolean)
.returns(T.nilable(T.attached_class))
}
def self.try_new(ref, from: T.unsafe(nil), warn: false)
ref = ref.to_s
return unless (keg_formula = HOMEBREW_PREFIX/"opt/#{ref}/.brew/#{ref}.rb").file?
new(ref, keg_formula)
end
end
# Loads a formula from a cached formula file.
class FromCacheLoader < FormulaLoader
sig {
params(ref: T.any(String, Pathname, URI::Generic), from: Symbol, warn: T::Boolean)
.returns(T.nilable(T.attached_class))
}
def self.try_new(ref, from: T.unsafe(nil), warn: false)
ref = ref.to_s
return unless (cached_formula = HOMEBREW_CACHE_FORMULA/"#{ref}.rb").file?
new(ref, cached_formula)
end end
end end
# Pseudo-loader which will raise a {FormulaUnavailableError} when trying to load the corresponding formula. # Pseudo-loader which will raise a {FormulaUnavailableError} when trying to load the corresponding formula.
class NullLoader < FormulaLoader class NullLoader < FormulaLoader
sig { def initialize(name)
params(ref: T.any(String, Pathname, URI::Generic), from: Symbol, warn: T::Boolean)
.returns(T.nilable(T.attached_class))
}
def self.try_new(ref, from: T.unsafe(nil), warn: false)
return if ref.is_a?(URI::Generic)
new(ref)
end
sig { params(ref: T.any(String, Pathname)).void }
def initialize(ref)
name = File.basename(ref, ".rb")
super name, Formulary.core_path(name) super name, Formulary.core_path(name)
end end
@ -842,50 +668,16 @@ module Formulary
end end
def klass(flags:, ignore_errors:) def klass(flags:, ignore_errors:)
$stderr.puts "#{$PROGRAM_NAME} (#{self.class.name}): loading #{path}" if debug?
namespace = "FormulaNamespace#{Digest::MD5.hexdigest(contents.to_s)}" namespace = "FormulaNamespace#{Digest::MD5.hexdigest(contents.to_s)}"
Formulary.load_formula(name, path, contents, namespace, flags: flags, ignore_errors: ignore_errors) Formulary.load_formula(name, path, contents, namespace, flags: flags, ignore_errors: ignore_errors)
end end
end end
# Load a formula from the API. # Load formulae from the API.
class FromAPILoader < FormulaLoader class FormulaAPILoader < FormulaLoader
sig { def initialize(name)
params(ref: T.any(String, Pathname, URI::Generic), from: Symbol, warn: T::Boolean) super name, Formulary.core_path(name)
.returns(T.nilable(T.attached_class))
}
def self.try_new(ref, from: T.unsafe(nil), warn: false)
return if Homebrew::EnvConfig.no_install_from_api?
return unless ref.is_a?(String)
return unless (name = ref[HOMEBREW_DEFAULT_TAP_FORMULA_REGEX, :name])
if !Homebrew::API::Formula.all_formulae.key?(name) &&
!Homebrew::API::Formula.all_aliases.key?(name) &&
!Homebrew::API::Formula.all_renames.key?(name)
return
end
alias_name = name
ref = "#{CoreTap.instance}/#{name}"
name, tap, type = Formulary.tap_formula_name_type(ref, warn: warn)
options = if type == :alias
{ alias_name: alias_name.downcase }
else
{}
end
new(name, tap: tap, **options)
end
sig { params(name: String, tap: Tap, alias_name: String).void }
def initialize(name, tap: T.unsafe(nil), alias_name: T.unsafe(nil))
options = {
alias_path: (CoreTap.instance.alias_dir/alias_name if alias_name),
tap: tap,
}.compact
super(name, Formulary.core_path(name), **options)
end end
def klass(flags:, ignore_errors:) def klass(flags:, ignore_errors:)
@ -896,10 +688,20 @@ module Formulary
private private
def load_from_api(flags:) def load_from_api(flags:)
$stderr.puts "#{$PROGRAM_NAME} (#{self.class.name}): loading #{name} from API" if debug?
Formulary.load_formula_from_api(name, flags: flags) Formulary.load_formula_from_api(name, flags: flags)
end end
end end
# Load aliases from the API.
class AliasAPILoader < FormulaAPILoader
def initialize(alias_name)
super Homebrew::API::Formula.all_aliases[alias_name]
@alias_path = Formulary.core_alias_path(alias_name).to_s
end
end
# Return a {Formula} instance for the given reference. # Return a {Formula} instance for the given reference.
# `ref` is a string containing: # `ref` is a string containing:
# #
@ -939,7 +741,6 @@ module Formulary
force_bottle: force_bottle, force_bottle: force_bottle,
flags: flags, flags: flags,
ignore_errors: ignore_errors }.compact ignore_errors: ignore_errors }.compact
formula = loader_for(ref, **loader_options) formula = loader_for(ref, **loader_options)
.get_formula(spec, **formula_options) .get_formula(spec, **formula_options)
@ -992,7 +793,7 @@ module Formulary
# Return whether given rack is keg-only. # Return whether given rack is keg-only.
def self.keg_only?(rack) def self.keg_only?(rack)
Formulary.from_rack(rack).keg_only? Formulary.from_rack(rack).keg_only?
rescue FormulaUnavailableError, TapFormulaAmbiguityError rescue FormulaUnavailableError, TapFormulaAmbiguityError, TapFormulaWithOldnameAmbiguityError
false false
end end
@ -1105,13 +906,9 @@ module Formulary
user, repo, name = tapped_name.split("/", 3).map(&:downcase) user, repo, name = tapped_name.split("/", 3).map(&:downcase)
tap = Tap.fetch(user, repo) tap = Tap.fetch(user, repo)
type = nil type = nil
alias_name = tap.core_tap? ? name : "#{tap}/#{name}"
# FIXME: Remove the need to do this here. if (possible_alias = tap.alias_table[alias_name].presence)
alias_table_key = tap.core_tap? ? name : "#{tap}/#{name}"
if (possible_alias = tap.alias_table[alias_table_key].presence)
# FIXME: Remove the need to split the name and instead make
# the alias table only contain short names.
name = possible_alias.split("/").last name = possible_alias.split("/").last
type = :alias type = :alias
elsif (new_name = tap.formula_renames[name].presence) elsif (new_name = tap.formula_renames[name].presence)
@ -1141,32 +938,103 @@ module Formulary
[name, tap, type] [name, tap, type]
end end
def self.loader_for(ref, from: T.unsafe(nil), warn: true) def self.tap_loader_for(tapped_name, warn:)
options = { from: from, warn: warn }.compact name, tap, type = Formulary.tap_formula_name_type(tapped_name, warn: warn)
[ if tap.core_tap? && !Homebrew::EnvConfig.no_install_from_api?
FromBottleLoader, if type == :alias
FromURILoader, return AliasAPILoader.new(name)
FromAPILoader, elsif Homebrew::API::Formula.all_formulae.key?(name)
FromTapLoader, return FormulaAPILoader.new(name)
FromPathLoader,
FromDefaultNameLoader,
FromNameLoader,
FromKegLoader,
FromCacheLoader,
NullLoader,
].each do |loader_class|
if (loader = loader_class.try_new(ref, **options))
$stderr.puts "#{$PROGRAM_NAME} (#{loader_class}): loading #{ref}" if debug?
return loader
end end
end end
path = find_formula_in_tap(name, tap)
TapLoader.new(name, path, tap: tap)
end
def self.loader_for(ref, from: nil, warn: true)
case ref
when HOMEBREW_BOTTLES_EXTNAME_REGEX
return BottleLoader.new(ref)
when URL_START_REGEX
return FromUrlLoader.new(ref, from: from)
when HOMEBREW_TAP_FORMULA_REGEX
return Formulary.tap_loader_for(ref, warn: warn)
end
pathname_ref = Pathname.new(ref)
return FromPathLoader.new(ref) if File.extname(ref) == ".rb" && pathname_ref.expand_path.exist?
unless Homebrew::EnvConfig.no_install_from_api?
return FormulaAPILoader.new(ref) if Homebrew::API::Formula.all_formulae.key?(ref)
return AliasAPILoader.new(ref) if Homebrew::API::Formula.all_aliases.key?(ref)
end
formula_with_that_name = core_path(ref)
return FormulaLoader.new(ref, formula_with_that_name) if formula_with_that_name.file?
possible_alias = if pathname_ref.absolute?
pathname_ref
else
core_alias_path(ref)
end
return AliasLoader.new(possible_alias) if possible_alias.symlink?
case (possible_tap_formulae = tap_paths(ref)).count
when 1
path = possible_tap_formulae.first.resolved_path
name = path.basename(".rb").to_s
return FormulaLoader.new(name, path)
when 2..Float::INFINITY
raise TapFormulaAmbiguityError.new(ref, possible_tap_formulae)
end
if CoreTap.instance.formula_renames.key?(ref)
return Formulary.tap_loader_for("#{CoreTap.instance}/#{ref}", warn: warn)
end
possible_taps = Tap.select { |tap| tap.formula_renames.key?(ref) }
case possible_taps.count
when 1
return Formulary.tap_loader_for("#{possible_taps.first}/#{ref}", warn: warn)
when 2..Float::INFINITY
possible_tap_newname_formulae = possible_taps.map { |tap| "#{tap}/#{tap.formula_renames[ref]}" }
raise TapFormulaWithOldnameAmbiguityError.new(ref, possible_tap_newname_formulae)
end
if (keg_formula = HOMEBREW_PREFIX/"opt/#{ref}/.brew/#{ref}.rb").file?
return FormulaLoader.new(ref, keg_formula)
end
if (cached_formula = HOMEBREW_CACHE_FORMULA/"#{ref}.rb").file?
return FormulaLoader.new(ref, cached_formula)
end
NullLoader.new(ref)
end end
def self.core_path(name) def self.core_path(name)
find_formula_in_tap(name.to_s.downcase, CoreTap.instance) find_formula_in_tap(name.to_s.downcase, CoreTap.instance)
end end
def self.core_alias_path(name)
CoreTap.instance.alias_dir/name.to_s.downcase
end
def self.tap_paths(name)
name = name.to_s.downcase
Tap.map do |tap|
formula_path = find_formula_in_tap(name, tap)
alias_path = tap.alias_dir/name
next alias_path if !formula_path.exist? && alias_path.exist?
formula_path
end.select(&:file?)
end
sig { params(name: String, tap: Tap).returns(Pathname) } sig { params(name: String, tap: Tap).returns(Pathname) }
def self.find_formula_in_tap(name, tap) def self.find_formula_in_tap(name, tap)
filename = if name.end_with?(".rb") filename = if name.end_with?(".rb")

View File

@ -1,42 +1,22 @@
# typed: strict # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
# Match a formula name. # Match taps' formulae, e.g. `someuser/sometap/someformula`
HOMEBREW_TAP_FORMULA_NAME_REGEX = T.let(/(?<name>[\w+\-.@]+)/, Regexp) HOMEBREW_TAP_FORMULA_REGEX = T.let(%r{^([\w-]+)/([\w-]+)/([\w+-.@]+)$}, Regexp)
# Match taps' formulae, e.g. `someuser/sometap/someformula`. # Match taps' casks, e.g. `someuser/sometap/somecask`
HOMEBREW_TAP_FORMULA_REGEX = T.let( HOMEBREW_TAP_CASK_REGEX = T.let(%r{^([\w-]+)/([\w-]+)/([a-z0-9\-_]+)$}, Regexp)
%r{\A(?<user>[\w-]+)/(?<repo>[\w-]+)/#{HOMEBREW_TAP_FORMULA_NAME_REGEX.source}\Z}, # Match default cask taps' casks, e.g. `homebrew/cask/somecask` or `somecask`
Regexp,
)
# Match default formula taps' formulae, e.g. `homebrew/core/someformula` or `someformula`.
HOMEBREW_DEFAULT_TAP_FORMULA_REGEX = T.let(
%r{\A(?:[Hh]omebrew/(?:homebrew-)?core/)?(?<name>#{HOMEBREW_TAP_FORMULA_NAME_REGEX.source})\Z},
Regexp,
)
# Match a cask token.
HOMEBREW_TAP_CASK_TOKEN_REGEX = T.let(/(?<token>[a-z0-9\-_]+(?:@[a-z0-9\-_.]+)?)/, Regexp)
# Match taps' casks, e.g. `someuser/sometap/somecask`.
HOMEBREW_TAP_CASK_REGEX = T.let(
%r{\A(?<user>[\w-]+)/(?<repo>[\w-]+)/#{HOMEBREW_TAP_CASK_TOKEN_REGEX.source}\Z},
Regexp,
)
# Match default cask taps' casks, e.g. `homebrew/cask/somecask` or `somecask`.
HOMEBREW_DEFAULT_TAP_CASK_REGEX = T.let( HOMEBREW_DEFAULT_TAP_CASK_REGEX = T.let(
%r{\A(?:[Hh]omebrew/(?:homebrew-)?cask/)?#{HOMEBREW_TAP_CASK_TOKEN_REGEX.source}\Z}, %r{^(?:[Hh]omebrew/(?:homebrew-)?cask/)?(?<token>[a-z0-9\-_]+)$}, Regexp
Regexp,
) )
# Match taps' directory paths, e.g. `HOMEBREW_LIBRARY/Taps/someuser/sometap`
# Match taps' directory paths, e.g. `HOMEBREW_LIBRARY/Taps/someuser/sometap`.
HOMEBREW_TAP_DIR_REGEX = T.let( HOMEBREW_TAP_DIR_REGEX = T.let(
%r{#{Regexp.escape(HOMEBREW_LIBRARY.to_s)}/Taps/(?<user>[\w-]+)/(?<repo>[\w-]+)}, %r{#{Regexp.escape(HOMEBREW_LIBRARY.to_s)}/Taps/(?<user>[\w-]+)/(?<repo>[\w-]+)}, Regexp
Regexp,
) )
# Match taps' formula paths, e.g. `HOMEBREW_LIBRARY/Taps/someuser/sometap/someformula`. # Match taps' formula paths, e.g. `HOMEBREW_LIBRARY/Taps/someuser/sometap/someformula`
HOMEBREW_TAP_PATH_REGEX = T.let(Regexp.new(HOMEBREW_TAP_DIR_REGEX.source + %r{(?:/.*)?\Z}.source).freeze, Regexp) HOMEBREW_TAP_PATH_REGEX = T.let(Regexp.new(HOMEBREW_TAP_DIR_REGEX.source + %r{(?:/.*)?$}.source).freeze, Regexp)
# Match official taps' casks, e.g. `homebrew/cask/somecask or homebrew/cask-versions/somecask`. # Match official taps' casks, e.g. `homebrew/cask/somecask or homebrew/cask-versions/somecask`
HOMEBREW_CASK_TAP_CASK_REGEX = T.let( HOMEBREW_CASK_TAP_CASK_REGEX =
%r{\A(?:([Cc]askroom)/(cask|versions)|([Hh]omebrew)/(?:homebrew-)?(cask|cask-[\w-]+))/([\w+-.]+)\Z}, T.let(%r{^(?:([Cc]askroom)/(cask|versions)|([Hh]omebrew)/(?:homebrew-)?(cask|cask-[\w-]+))/([\w+-.]+)$},
Regexp, Regexp)
) HOMEBREW_OFFICIAL_REPO_PREFIXES_REGEX = T.let(/^(home|linux)brew-/, Regexp)
HOMEBREW_OFFICIAL_REPO_PREFIXES_REGEX = T.let(/\A(home|linux)brew-/, Regexp)

View File

@ -28,7 +28,7 @@ describe Formula do
let(:path) { Formulary.core_path(name) } let(:path) { Formulary.core_path(name) }
let(:spec) { :stable } let(:spec) { :stable }
let(:alias_name) { "baz@1" } let(:alias_name) { "baz@1" }
let(:alias_path) { CoreTap.instance.alias_dir/alias_name } let(:alias_path) { (CoreTap.instance.alias_dir/alias_name).to_s }
let(:f) { klass.new(name, path, spec) } let(:f) { klass.new(name, path, spec) }
let(:f_alias) { klass.new(name, path, spec, alias_path: alias_path) } let(:f_alias) { klass.new(name, path, spec, alias_path: alias_path) }
@ -190,11 +190,11 @@ describe Formula do
end end
alias_name = "bar" alias_name = "bar"
alias_path = CoreTap.instance.alias_dir/alias_name alias_path = "#{CoreTap.instance.alias_dir}/#{alias_name}"
CoreTap.instance.alias_dir.mkpath CoreTap.instance.alias_dir.mkpath
FileUtils.ln_sf f.path, alias_path FileUtils.ln_sf f.path, alias_path
f.build = Tab.new(source: { "path" => alias_path.to_s }) f.build = Tab.new(source: { "path" => alias_path })
expect(f.installed_alias_path).to eq(alias_path) expect(f.installed_alias_path).to eq(alias_path)
expect(f.installed_alias_name).to eq(alias_name) expect(f.installed_alias_name).to eq(alias_name)
@ -225,12 +225,12 @@ describe Formula do
end end
alias_name = "bar" alias_name = "bar"
alias_path = tap.alias_dir/alias_name
full_alias_name = "#{tap.user}/#{tap.repo}/#{alias_name}" full_alias_name = "#{tap.user}/#{tap.repo}/#{alias_name}"
alias_path = "#{tap.alias_dir}/#{alias_name}"
tap.alias_dir.mkpath tap.alias_dir.mkpath
FileUtils.ln_sf f.path, alias_path FileUtils.ln_sf f.path, alias_path
f.build = Tab.new(source: { "path" => alias_path.to_s }) f.build = Tab.new(source: { "path" => alias_path })
expect(f.installed_alias_path).to eq(alias_path) expect(f.installed_alias_path).to eq(alias_path)
expect(f.installed_alias_name).to eq(alias_name) expect(f.installed_alias_name).to eq(alias_name)
@ -451,7 +451,7 @@ describe Formula do
FileUtils.ln_sf f.path, source_path FileUtils.ln_sf f.path, source_path
expect(f.alias_path).to eq(alias_path) expect(f.alias_path).to eq(alias_path)
expect(f.installed_alias_path).to eq(source_path) expect(f.installed_alias_path).to eq(source_path.to_s)
end end
end end
@ -491,14 +491,14 @@ describe Formula do
end end
specify "with alias path with a path" do specify "with alias path with a path" do
alias_path = CoreTap.instance.alias_dir/"alias" alias_path = "#{CoreTap.instance.alias_dir}/alias"
different_alias_path = CoreTap.instance.alias_dir/"another_alias" different_alias_path = "#{CoreTap.instance.alias_dir}/another_alias"
formula_with_alias = formula "foo" do formula_with_alias = formula "foo" do
url "foo-1.0" url "foo-1.0"
end end
formula_with_alias.build = Tab.empty formula_with_alias.build = Tab.empty
formula_with_alias.build.source["path"] = alias_path.to_s formula_with_alias.build.source["path"] = alias_path
formula_without_alias = formula "bar" do formula_without_alias = formula "bar" do
url "bar-1.0" url "bar-1.0"
@ -510,7 +510,7 @@ describe Formula do
url "baz-1.0" url "baz-1.0"
end end
formula_with_different_alias.build = Tab.empty formula_with_different_alias.build = Tab.empty
formula_with_different_alias.build.source["path"] = different_alias_path.to_s formula_with_different_alias.build.source["path"] = different_alias_path
formulae = [ formulae = [
formula_with_alias, formula_with_alias,
@ -1239,8 +1239,8 @@ describe Formula do
end end
let(:tab) { Tab.empty } let(:tab) { Tab.empty }
let(:alias_path) { "#{CoreTap.instance.alias_dir}/bar" }
let(:alias_name) { "bar" } let(:alias_name) { "bar" }
let(:alias_path) { CoreTap.instance.alias_dir/alias_name }
before do before do
allow(described_class).to receive(:installed).and_return([f]) allow(described_class).to receive(:installed).and_return([f])
@ -1261,7 +1261,7 @@ describe Formula do
end end
specify "alias changes when not changed" do specify "alias changes when not changed" do
tab.source["path"] = alias_path.to_s tab.source["path"] = alias_path
stub_formula_loader(f, alias_name) stub_formula_loader(f, alias_name)
CoreTap.instance.alias_dir.mkpath CoreTap.instance.alias_dir.mkpath
@ -1276,7 +1276,7 @@ describe Formula do
end end
specify "alias changes when new alias target" do specify "alias changes when new alias target" do
tab.source["path"] = alias_path.to_s tab.source["path"] = alias_path
stub_formula_loader(new_formula, alias_name) stub_formula_loader(new_formula, alias_name)
CoreTap.instance.alias_dir.mkpath CoreTap.instance.alias_dir.mkpath
@ -1291,7 +1291,7 @@ describe Formula do
end end
specify "alias changes when old formulae installed" do specify "alias changes when old formulae installed" do
tab.source["path"] = alias_path.to_s tab.source["path"] = alias_path
stub_formula_loader(new_formula, alias_name) stub_formula_loader(new_formula, alias_name)
CoreTap.instance.alias_dir.mkpath CoreTap.instance.alias_dir.mkpath
@ -1332,8 +1332,8 @@ describe Formula do
end end
end end
let(:alias_path) { "#{f.tap.alias_dir}/bar" }
let(:alias_name) { "bar" } let(:alias_name) { "bar" }
let(:alias_path) { f.tap.alias_dir/alias_name }
def setup_tab_for_prefix(prefix, options = {}) def setup_tab_for_prefix(prefix, options = {})
prefix.mkpath prefix.mkpath

View File

@ -111,7 +111,7 @@ describe Formulary do
it "raises an error" do it "raises an error" do
expect do expect do
described_class.factory(formula_name) described_class.factory(formula_name)
end.to raise_error(TapFormulaClassUnavailableError) end.to raise_error(FormulaClassUnavailableError)
end end
end end
@ -139,13 +139,12 @@ describe Formulary do
context "when given an alias" do context "when given an alias" do
subject(:formula) { described_class.factory("foo") } subject(:formula) { described_class.factory("foo") }
let(:alias_dir) { CoreTap.instance.alias_dir } let(:alias_dir) { CoreTap.instance.alias_dir.tap(&:mkpath) }
let(:alias_path) { alias_dir/"foo" } let(:alias_path) { alias_dir/"foo" }
before do before do
alias_dir.mkpath alias_dir.mkpath
FileUtils.ln_s formula_path, alias_path FileUtils.ln_s formula_path, alias_path
CoreTap.instance.clear_cache
end end
it "returns a Formula" do it "returns a Formula" do
@ -153,7 +152,7 @@ describe Formulary do
end end
it "calling #alias_path on the returned Formula returns the alias path" do it "calling #alias_path on the returned Formula returns the alias path" do
expect(formula.alias_path).to eq(alias_path) expect(formula.alias_path).to eq(alias_path.to_s)
end end
end end
@ -232,26 +231,23 @@ describe Formulary do
let(:tap) { Tap.new("homebrew", "foo") } let(:tap) { Tap.new("homebrew", "foo") }
let(:another_tap) { Tap.new("homebrew", "bar") } let(:another_tap) { Tap.new("homebrew", "bar") }
let(:formula_path) { tap.path/"Formula/#{formula_name}.rb" } let(:formula_path) { tap.path/"Formula/#{formula_name}.rb" }
let(:alias_name) { "bar" }
let(:alias_dir) { tap.alias_dir }
let(:alias_path) { alias_dir/alias_name }
before do
alias_dir.mkpath
FileUtils.ln_s formula_path, alias_path
tap.clear_cache
end
it "returns a Formula when given a name" do it "returns a Formula when given a name" do
expect(described_class.factory(formula_name)).to be_a(Formula) expect(described_class.factory(formula_name)).to be_a(Formula)
end end
it "returns a Formula from an Alias path" do it "returns a Formula from an Alias path" do
expect(described_class.factory(alias_name)).to be_a(Formula) alias_dir = tap.path/"Aliases"
alias_dir.mkpath
FileUtils.ln_s formula_path, alias_dir/"bar"
expect(described_class.factory("bar")).to be_a(Formula)
end end
it "returns a Formula from a fully qualified Alias path" do it "returns a Formula from a fully qualified Alias path" do
expect(described_class.factory("#{tap.name}/#{alias_name}")).to be_a(Formula) alias_dir = tap.path/"Aliases"
alias_dir.mkpath
FileUtils.ln_s formula_path, alias_dir/"bar"
expect(described_class.factory("#{tap}/bar")).to be_a(Formula)
end end
it "raises an error when the Formula cannot be found" do it "raises an error when the Formula cannot be found" do
@ -382,7 +378,7 @@ describe Formulary do
end end
before do before do
allow(described_class).to receive(:loader_for).and_return(described_class::FromAPILoader.new(formula_name)) allow(described_class).to receive(:loader_for).and_return(described_class::FormulaAPILoader.new(formula_name))
# don't try to load/fetch gcc/glibc # don't try to load/fetch gcc/glibc
allow(DevelopmentTools).to receive_messages(needs_libc_formula?: false, needs_compiler_formula?: false) allow(DevelopmentTools).to receive_messages(needs_libc_formula?: false, needs_compiler_formula?: false)