diff --git a/Library/Homebrew/api/formula.rb b/Library/Homebrew/api/formula.rb index a8d2215295..8704ec6ad4 100644 --- a/Library/Homebrew/api/formula.rb +++ b/Library/Homebrew/api/formula.rb @@ -59,7 +59,7 @@ module Homebrew end private :download_and_cache_data! - sig { returns(Hash) } + sig { returns(T::Hash[String, Hash]) } def all_formulae unless cache.key?("formulae") json_updated = download_and_cache_data! @@ -69,7 +69,7 @@ module Homebrew cache["formulae"] end - sig { returns(Hash) } + sig { returns(T::Hash[String, String]) } def all_aliases unless cache.key?("aliases") json_updated = download_and_cache_data! diff --git a/Library/Homebrew/cleanup.rb b/Library/Homebrew/cleanup.rb index 19348c6a4c..66937919c4 100644 --- a/Library/Homebrew/cleanup.rb +++ b/Library/Homebrew/cleanup.rb @@ -130,7 +130,7 @@ module Homebrew formula = begin Formulary.from_rack(HOMEBREW_CELLAR/formula_name) - rescue FormulaUnavailableError, TapFormulaAmbiguityError, TapFormulaWithOldnameAmbiguityError + rescue FormulaUnavailableError, TapFormulaAmbiguityError nil end @@ -300,7 +300,7 @@ module Homebrew args.each do |arg| formula = begin Formulary.resolve(arg) - rescue FormulaUnavailableError, TapFormulaAmbiguityError, TapFormulaWithOldnameAmbiguityError + rescue FormulaUnavailableError, TapFormulaAmbiguityError nil end diff --git a/Library/Homebrew/diagnostic.rb b/Library/Homebrew/diagnostic.rb index bcfcfdf4c0..23f1b53e22 100644 --- a/Library/Homebrew/diagnostic.rb +++ b/Library/Homebrew/diagnostic.rb @@ -733,8 +733,7 @@ module Homebrew rescue FormulaUnreadableError, FormulaClassUnavailableError, TapFormulaUnreadableError, TapFormulaClassUnavailableError => e formula_unavailable_exceptions << e - rescue FormulaUnavailableError, - TapFormulaAmbiguityError, TapFormulaWithOldnameAmbiguityError + rescue FormulaUnavailableError, TapFormulaAmbiguityError nil end return if formula_unavailable_exceptions.empty? @@ -752,7 +751,7 @@ module Homebrew else begin Formulary.from_rack(rack).keg_only? - rescue FormulaUnavailableError, TapFormulaAmbiguityError, TapFormulaWithOldnameAmbiguityError + rescue FormulaUnavailableError, TapFormulaAmbiguityError false end end @@ -835,16 +834,30 @@ module Homebrew kegs = Keg.all deleted_formulae = kegs.map do |keg| - next if Formulary.tap_paths(keg.name).any? + tap = Tab.for_keg(keg).tap - unless EnvConfig.no_install_from_api? - # Formulae installed from the API should not count as deleted formulae - # but may not have a tap listed in their tab - tap = Tab.for_keg(keg).tap - next if (tap.blank? || tap.core_tap?) && Homebrew::API::Formula.all_formulae.key?(keg.name) + loadable = [ + Formulary::FromAPILoader, + Formulary::FromDefaultNameLoader, + Formulary::FromNameLoader, + ].any? do |loader_class| + 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 - keg.name + keg.name unless loadable end.compact.uniq return if deleted_formulae.blank? diff --git a/Library/Homebrew/exceptions.rb b/Library/Homebrew/exceptions.rb index e0861796c5..1094c7e517 100644 --- a/Library/Homebrew/exceptions.rb +++ b/Library/Homebrew/exceptions.rb @@ -259,40 +259,20 @@ end # Raised when a formula with the same name is found in multiple taps. class TapFormulaAmbiguityError < RuntimeError - attr_reader :name, :paths, :formulae + attr_reader :name, :taps, :loaders - def initialize(name, paths) + def initialize(name, loaders) @name = name - @paths = paths - @formulae = paths.map do |path| - "#{Tap.from_path(path).name}/#{path.basename(".rb")}" - end + @loaders = loaders + @taps = loaders.map(&:tap) + + formulae = taps.map { |tap| "#{tap}/#{name}" } + formula_list = formulae.map { |f| "\n * #{f}" }.join super <<~EOS - Formulae found in multiple taps: #{formulae.map { |f| "\n * #{f}" }.join} + Formulae found in multiple taps:#{formula_list} - 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. + Please use the fully-qualified name (e.g. #{formulae.first}) to refer to a specific formula. EOS end end diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index 17c7f2ee77..9dd88c0365 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -77,7 +77,7 @@ class 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` - sig { returns(T.any(NilClass, Pathname, String)) } + sig { returns(T.nilable(Pathname)) } attr_reader :alias_path # The name of the alias that was used to identify this {Formula}. @@ -199,7 +199,7 @@ class Formula # @private sig { - params(name: String, path: Pathname, spec: Symbol, alias_path: T.any(NilClass, Pathname, String), + params(name: String, path: Pathname, spec: Symbol, alias_path: T.nilable(Pathname), tap: T.nilable(Tap), force_bottle: T::Boolean).void } def initialize(name, path, spec, alias_path: nil, tap: nil, force_bottle: false) @@ -326,18 +326,22 @@ class Formula # 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, # and is specified to this instance. + sig { returns(T.nilable(Pathname)) } def installed_alias_path build_tab = build path = build_tab.source["path"] if build_tab.is_a?(Tab) + return unless path&.match?(%r{#{HOMEBREW_TAP_DIR_REGEX}/Aliases}o) - return unless File.symlink?(path) + + path = Pathname(path) + return unless path.symlink? path end sig { returns(T.nilable(String)) } def installed_alias_name - File.basename(installed_alias_path) if installed_alias_path + installed_alias_path&.basename&.to_s end def full_installed_alias_name @@ -346,14 +350,13 @@ class Formula # The path that was specified to find this formula. def specified_path - alias_pathname = Pathname(T.must(alias_path)) if alias_path.present? - return alias_pathname if alias_pathname&.exist? + return alias_path if alias_path&.exist? return @unresolved_path if @unresolved_path.exist? return local_bottle_path if local_bottle_path.presence&.exist? - alias_pathname || @unresolved_path + alias_path || @unresolved_path end # The name specified to find this formula. @@ -1315,7 +1318,7 @@ class Formula f = Formulary.factory(keg.name) rescue FormulaUnavailableError # formula for this keg is deleted, so defer to allowlist - rescue TapFormulaAmbiguityError, TapFormulaWithOldnameAmbiguityError + rescue TapFormulaAmbiguityError return false # this keg belongs to another formula else # this keg belongs to another unrelated formula @@ -2362,7 +2365,7 @@ class Formula # Take from API, merging in local install status. if loaded_from_api? && !Homebrew::EnvConfig.no_install_from_api? - json_formula = Homebrew::API::Formula.all_formulae[name].dup + json_formula = Homebrew::API::Formula.all_formulae.fetch(name).dup return json_formula.merge( hash.slice("name", "installed", "linked_keg", "pinned", "outdated"), ) diff --git a/Library/Homebrew/formula_auditor.rb b/Library/Homebrew/formula_auditor.rb index b0b8407862..b094171b22 100644 --- a/Library/Homebrew/formula_auditor.rb +++ b/Library/Homebrew/formula_auditor.rb @@ -64,8 +64,7 @@ module Homebrew unversioned_formula = begin Formulary.factory(full_name).path - rescue FormulaUnavailableError, TapFormulaAmbiguityError, - TapFormulaWithOldnameAmbiguityError + rescue FormulaUnavailableError, TapFormulaAmbiguityError Pathname.new formula.path.to_s.gsub(/@.*\.rb$/, ".rb") end unless unversioned_formula.exist? @@ -285,9 +284,6 @@ module Homebrew rescue TapFormulaAmbiguityError problem "Ambiguous dependency '#{dep.name}'." next - rescue TapFormulaWithOldnameAmbiguityError - problem "Ambiguous oldname dependency '#{dep.name.inspect}'." - next end if dep_f.oldnames.include?(dep.name.split("/").last) @@ -461,7 +457,7 @@ module Homebrew next rescue FormulaUnavailableError problem "Can't find conflicting formula #{conflict.name.inspect}." - rescue TapFormulaAmbiguityError, TapFormulaWithOldnameAmbiguityError + rescue TapFormulaAmbiguityError problem "Ambiguous conflicting formula #{conflict.name.inspect}." end end diff --git a/Library/Homebrew/formulary.rb b/Library/Homebrew/formulary.rb index e6f8a357df..f1fe205448 100644 --- a/Library/Homebrew/formulary.rb +++ b/Library/Homebrew/formulary.rb @@ -16,6 +16,7 @@ require "extend/hash/keys" # # @api private module Formulary + extend Context extend Cachable URL_START_REGEX = %r{(https?|ftp|file)://} @@ -480,17 +481,26 @@ module Formulary include Context # The formula's name + sig { returns(String) } attr_reader :name + # The formula's ruby file's path or filename + sig { returns(Pathname) } attr_reader :path + # The name used to install the formula + sig { returns(T.nilable(Pathname)) } attr_reader :alias_path + # The formula's tap (nil if it should be implicitly determined) + sig { returns(T.nilable(Tap)) } attr_reader :tap - def initialize(name, path, tap: nil) + sig { params(name: String, path: Pathname, alias_path: Pathname, tap: Tap).void } + def initialize(name, path, alias_path: T.unsafe(nil), tap: T.unsafe(nil)) @name = name @path = path + @alias_path = alias_path @tap = tap end @@ -511,7 +521,6 @@ module Formulary private def load_file(flags:, ignore_errors:) - $stderr.puts "#{$PROGRAM_NAME} (#{self.class.name}): loading #{path}" if debug? raise FormulaUnavailableError, name unless path.file? Formulary.load_formula_from_path(name, path, flags: flags, ignore_errors: ignore_errors) @@ -519,7 +528,17 @@ module Formulary end # Loads a formula from a bottle. - class BottleLoader < FormulaLoader + class FromBottleLoader < 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) case bottle_name when URL_START_REGEX @@ -562,27 +581,67 @@ module Formulary end end - # Loads a formula from a path to an alias. - class AliasLoader < FormulaLoader - def initialize(alias_path) - path = alias_path.resolved_path - name = path.basename(".rb").to_s - super name, path - @alias_path = alias_path.to_s - end - end - # Loads formulae from disk using a path. class FromPathLoader < FormulaLoader - def initialize(path) - path = Pathname.new(path).expand_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) + 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 - super name, path, tap: Homebrew::API.tap_from_source_download(path) + alias_path = alias_path&.expand_path + alias_dir = alias_path&.dirname + + 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 - # Loads formulae from URLs. - class FromUrlLoader < FormulaLoader + # Loads formula from a URI. + class FromURILoader < 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, from: from) if URL_START_REGEX.match?(ref) + end + attr_reader :url sig { params(url: T.any(URI::Generic, String), from: T.nilable(Symbol)).void } @@ -621,7 +680,45 @@ module Formulary end # Loads tapped formulae. - class TapLoader < FormulaLoader + class FromTapLoader < 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) super rescue FormulaUnreadableError => e @@ -638,17 +735,94 @@ module Formulary e.issues_url = tap.issues_url || tap.to_s raise end + end - private + class FromDefaultNameLoader < 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 unless (name = ref[HOMEBREW_DEFAULT_TAP_FORMULA_REGEX, :name]) + return unless (tap = CoreTap.instance).installed? - def find_formula_from_name(name, tap) - Formulary.find_formula_in_tap(name, tap) + return unless (loader = super("#{tap}/#{name}", warn: warn)) + + 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 # Pseudo-loader which will raise a {FormulaUnavailableError} when trying to load the corresponding formula. class NullLoader < FormulaLoader - def initialize(name) + 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 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) end @@ -668,16 +842,50 @@ module Formulary end def klass(flags:, ignore_errors:) - $stderr.puts "#{$PROGRAM_NAME} (#{self.class.name}): loading #{path}" if debug? namespace = "FormulaNamespace#{Digest::MD5.hexdigest(contents.to_s)}" Formulary.load_formula(name, path, contents, namespace, flags: flags, ignore_errors: ignore_errors) end end - # Load formulae from the API. - class FormulaAPILoader < FormulaLoader - def initialize(name) - super name, Formulary.core_path(name) + # Load a formula from the API. + class FromAPILoader < 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) + 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 def klass(flags:, ignore_errors:) @@ -688,20 +896,10 @@ module Formulary private 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) 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. # `ref` is a string containing: # @@ -741,6 +939,7 @@ module Formulary force_bottle: force_bottle, flags: flags, ignore_errors: ignore_errors }.compact + formula = loader_for(ref, **loader_options) .get_formula(spec, **formula_options) @@ -793,7 +992,7 @@ module Formulary # Return whether given rack is keg-only. def self.keg_only?(rack) Formulary.from_rack(rack).keg_only? - rescue FormulaUnavailableError, TapFormulaAmbiguityError, TapFormulaWithOldnameAmbiguityError + rescue FormulaUnavailableError, TapFormulaAmbiguityError false end @@ -906,9 +1105,13 @@ module Formulary user, repo, name = tapped_name.split("/", 3).map(&:downcase) tap = Tap.fetch(user, repo) type = nil - alias_name = tap.core_tap? ? name : "#{tap}/#{name}" - if (possible_alias = tap.alias_table[alias_name].presence) + # FIXME: Remove the need to do this here. + 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 type = :alias elsif (new_name = tap.formula_renames[name].presence) @@ -938,103 +1141,32 @@ module Formulary [name, tap, type] end - def self.tap_loader_for(tapped_name, warn:) - name, tap, type = Formulary.tap_formula_name_type(tapped_name, warn: warn) + def self.loader_for(ref, from: T.unsafe(nil), warn: true) + options = { from: from, warn: warn }.compact - if tap.core_tap? && !Homebrew::EnvConfig.no_install_from_api? - if type == :alias - return AliasAPILoader.new(name) - elsif Homebrew::API::Formula.all_formulae.key?(name) - return FormulaAPILoader.new(name) + [ + FromBottleLoader, + FromURILoader, + FromAPILoader, + FromTapLoader, + 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 - - 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 def self.core_path(name) find_formula_in_tap(name.to_s.downcase, CoreTap.instance) 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.select(&:installed?).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) } def self.find_formula_in_tap(name, tap) filename = if name.end_with?(".rb") diff --git a/Library/Homebrew/tap_constants.rb b/Library/Homebrew/tap_constants.rb index 6f102bfcbf..e3af9328ae 100644 --- a/Library/Homebrew/tap_constants.rb +++ b/Library/Homebrew/tap_constants.rb @@ -1,22 +1,42 @@ # typed: strict # frozen_string_literal: true -# Match taps' formulae, e.g. `someuser/sometap/someformula` -HOMEBREW_TAP_FORMULA_REGEX = T.let(%r{^([\w-]+)/([\w-]+)/([\w+-.@]+)$}, Regexp) -# Match taps' casks, e.g. `someuser/sometap/somecask` -HOMEBREW_TAP_CASK_REGEX = T.let(%r{^([\w-]+)/([\w-]+)/([a-z0-9\-_]+)$}, Regexp) -# Match default cask taps' casks, e.g. `homebrew/cask/somecask` or `somecask` +# Match a formula name. +HOMEBREW_TAP_FORMULA_NAME_REGEX = T.let(/(?[\w+\-.@]+)/, Regexp) +# Match taps' formulae, e.g. `someuser/sometap/someformula`. +HOMEBREW_TAP_FORMULA_REGEX = T.let( + %r{\A(?[\w-]+)/(?[\w-]+)/#{HOMEBREW_TAP_FORMULA_NAME_REGEX.source}\Z}, + 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/)?(?#{HOMEBREW_TAP_FORMULA_NAME_REGEX.source})\Z}, + Regexp, +) + +# Match a cask token. +HOMEBREW_TAP_CASK_TOKEN_REGEX = T.let(/(?[a-z0-9\-_]+(?:@[a-z0-9\-_.]+)?)/, Regexp) +# Match taps' casks, e.g. `someuser/sometap/somecask`. +HOMEBREW_TAP_CASK_REGEX = T.let( + %r{\A(?[\w-]+)/(?[\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( - %r{^(?:[Hh]omebrew/(?:homebrew-)?cask/)?(?[a-z0-9\-_]+)$}, Regexp + %r{\A(?:[Hh]omebrew/(?:homebrew-)?cask/)?#{HOMEBREW_TAP_CASK_TOKEN_REGEX.source}\Z}, + 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( - %r{#{Regexp.escape(HOMEBREW_LIBRARY.to_s)}/Taps/(?[\w-]+)/(?[\w-]+)}, Regexp + %r{#{Regexp.escape(HOMEBREW_LIBRARY.to_s)}/Taps/(?[\w-]+)/(?[\w-]+)}, + Regexp, ) -# 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{(?:/.*)?$}.source).freeze, Regexp) -# Match official taps' casks, e.g. `homebrew/cask/somecask or homebrew/cask-versions/somecask` -HOMEBREW_CASK_TAP_CASK_REGEX = - T.let(%r{^(?:([Cc]askroom)/(cask|versions)|([Hh]omebrew)/(?:homebrew-)?(cask|cask-[\w-]+))/([\w+-.]+)$}, - Regexp) -HOMEBREW_OFFICIAL_REPO_PREFIXES_REGEX = T.let(/^(home|linux)brew-/, Regexp) +# 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) +# Match official taps' casks, e.g. `homebrew/cask/somecask or homebrew/cask-versions/somecask`. +HOMEBREW_CASK_TAP_CASK_REGEX = T.let( + %r{\A(?:([Cc]askroom)/(cask|versions)|([Hh]omebrew)/(?:homebrew-)?(cask|cask-[\w-]+))/([\w+-.]+)\Z}, + Regexp, +) +HOMEBREW_OFFICIAL_REPO_PREFIXES_REGEX = T.let(/\A(home|linux)brew-/, Regexp) diff --git a/Library/Homebrew/test/formula_spec.rb b/Library/Homebrew/test/formula_spec.rb index 3175505bc6..9d8f5163f7 100644 --- a/Library/Homebrew/test/formula_spec.rb +++ b/Library/Homebrew/test/formula_spec.rb @@ -28,7 +28,7 @@ RSpec.describe Formula do let(:path) { Formulary.core_path(name) } let(:spec) { :stable } let(:alias_name) { "baz@1" } - let(:alias_path) { (CoreTap.instance.alias_dir/alias_name).to_s } + let(:alias_path) { CoreTap.instance.alias_dir/alias_name } let(:f) { klass.new(name, path, spec) } let(:f_alias) { klass.new(name, path, spec, alias_path: alias_path) } @@ -190,11 +190,11 @@ RSpec.describe Formula do end alias_name = "bar" - alias_path = "#{CoreTap.instance.alias_dir}/#{alias_name}" + alias_path = CoreTap.instance.alias_dir/alias_name CoreTap.instance.alias_dir.mkpath FileUtils.ln_sf f.path, alias_path - f.build = Tab.new(source: { "path" => alias_path }) + f.build = Tab.new(source: { "path" => alias_path.to_s }) expect(f.installed_alias_path).to eq(alias_path) expect(f.installed_alias_name).to eq(alias_name) @@ -225,12 +225,12 @@ RSpec.describe Formula do end alias_name = "bar" + alias_path = tap.alias_dir/alias_name full_alias_name = "#{tap.user}/#{tap.repo}/#{alias_name}" - alias_path = "#{tap.alias_dir}/#{alias_name}" tap.alias_dir.mkpath FileUtils.ln_sf f.path, alias_path - f.build = Tab.new(source: { "path" => alias_path }) + f.build = Tab.new(source: { "path" => alias_path.to_s }) expect(f.installed_alias_path).to eq(alias_path) expect(f.installed_alias_name).to eq(alias_name) @@ -451,7 +451,7 @@ RSpec.describe Formula do FileUtils.ln_sf f.path, source_path expect(f.alias_path).to eq(alias_path) - expect(f.installed_alias_path).to eq(source_path.to_s) + expect(f.installed_alias_path).to eq(source_path) end end @@ -491,14 +491,14 @@ RSpec.describe Formula do end specify "with alias path with a path" do - alias_path = "#{CoreTap.instance.alias_dir}/alias" - different_alias_path = "#{CoreTap.instance.alias_dir}/another_alias" + alias_path = CoreTap.instance.alias_dir/"alias" + different_alias_path = CoreTap.instance.alias_dir/"another_alias" formula_with_alias = formula "foo" do url "foo-1.0" end formula_with_alias.build = Tab.empty - formula_with_alias.build.source["path"] = alias_path + formula_with_alias.build.source["path"] = alias_path.to_s formula_without_alias = formula "bar" do url "bar-1.0" @@ -510,7 +510,7 @@ RSpec.describe Formula do url "baz-1.0" end formula_with_different_alias.build = Tab.empty - formula_with_different_alias.build.source["path"] = different_alias_path + formula_with_different_alias.build.source["path"] = different_alias_path.to_s formulae = [ formula_with_alias, @@ -1239,8 +1239,8 @@ RSpec.describe Formula do end let(:tab) { Tab.empty } - let(:alias_path) { "#{CoreTap.instance.alias_dir}/bar" } let(:alias_name) { "bar" } + let(:alias_path) { CoreTap.instance.alias_dir/alias_name } before do allow(described_class).to receive(:installed).and_return([f]) @@ -1261,7 +1261,7 @@ RSpec.describe Formula do end specify "alias changes when not changed" do - tab.source["path"] = alias_path + tab.source["path"] = alias_path.to_s stub_formula_loader(f, alias_name) CoreTap.instance.alias_dir.mkpath @@ -1276,7 +1276,7 @@ RSpec.describe Formula do end specify "alias changes when new alias target" do - tab.source["path"] = alias_path + tab.source["path"] = alias_path.to_s stub_formula_loader(new_formula, alias_name) CoreTap.instance.alias_dir.mkpath @@ -1291,7 +1291,7 @@ RSpec.describe Formula do end specify "alias changes when old formulae installed" do - tab.source["path"] = alias_path + tab.source["path"] = alias_path.to_s stub_formula_loader(new_formula, alias_name) CoreTap.instance.alias_dir.mkpath @@ -1332,8 +1332,8 @@ RSpec.describe Formula do end end - let(:alias_path) { "#{f.tap.alias_dir}/bar" } let(:alias_name) { "bar" } + let(:alias_path) { f.tap.alias_dir/alias_name } def setup_tab_for_prefix(prefix, options = {}) prefix.mkpath diff --git a/Library/Homebrew/test/formulary_spec.rb b/Library/Homebrew/test/formulary_spec.rb index 9b2e7295ad..7d7535303b 100644 --- a/Library/Homebrew/test/formulary_spec.rb +++ b/Library/Homebrew/test/formulary_spec.rb @@ -111,7 +111,7 @@ RSpec.describe Formulary do it "raises an error" do expect do described_class.factory(formula_name) - end.to raise_error(FormulaClassUnavailableError) + end.to raise_error(TapFormulaClassUnavailableError) end end @@ -139,7 +139,7 @@ RSpec.describe Formulary do context "when given an alias" do subject(:formula) { described_class.factory("foo") } - let(:alias_dir) { CoreTap.instance.alias_dir.tap(&:mkpath) } + let(:alias_dir) { CoreTap.instance.alias_dir } let(:alias_path) { alias_dir/"foo" } before do @@ -152,7 +152,7 @@ RSpec.describe Formulary do end it "calling #alias_path on the returned Formula returns the alias path" do - expect(formula.alias_path).to eq(alias_path.to_s) + expect(formula.alias_path).to eq(alias_path) end end @@ -229,23 +229,26 @@ RSpec.describe Formulary do let(:tap) { Tap.new("homebrew", "foo") } let(:another_tap) { Tap.new("homebrew", "bar") } 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 expect(described_class.factory(formula_name)).to be_a(Formula) end it "returns a Formula from an Alias path" do - 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) + expect(described_class.factory(alias_name)).to be_a(Formula) end it "returns a Formula from a fully qualified Alias path" do - 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) + expect(described_class.factory("#{tap.name}/#{alias_name}")).to be_a(Formula) end it "raises an error when the Formula cannot be found" do