diff --git a/Library/Homebrew/api/formula.rb b/Library/Homebrew/api/formula.rb index 8704ec6ad4..a8d2215295 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(T::Hash[String, Hash]) } + sig { returns(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(T::Hash[String, String]) } + sig { returns(Hash) } 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 66937919c4..19348c6a4c 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 + rescue FormulaUnavailableError, TapFormulaAmbiguityError, TapFormulaWithOldnameAmbiguityError nil end @@ -300,7 +300,7 @@ module Homebrew args.each do |arg| formula = begin Formulary.resolve(arg) - rescue FormulaUnavailableError, TapFormulaAmbiguityError + rescue FormulaUnavailableError, TapFormulaAmbiguityError, TapFormulaWithOldnameAmbiguityError nil end diff --git a/Library/Homebrew/diagnostic.rb b/Library/Homebrew/diagnostic.rb index 7debb69b38..b039069da9 100644 --- a/Library/Homebrew/diagnostic.rb +++ b/Library/Homebrew/diagnostic.rb @@ -733,7 +733,8 @@ module Homebrew rescue FormulaUnreadableError, FormulaClassUnavailableError, TapFormulaUnreadableError, TapFormulaClassUnavailableError => e formula_unavailable_exceptions << e - rescue FormulaUnavailableError, TapFormulaAmbiguityError + rescue FormulaUnavailableError, + TapFormulaAmbiguityError, TapFormulaWithOldnameAmbiguityError nil end return if formula_unavailable_exceptions.empty? @@ -751,7 +752,7 @@ module Homebrew else begin Formulary.from_rack(rack).keg_only? - rescue FormulaUnavailableError, TapFormulaAmbiguityError + rescue FormulaUnavailableError, TapFormulaAmbiguityError, TapFormulaWithOldnameAmbiguityError false end end @@ -834,30 +835,16 @@ module Homebrew kegs = Keg.all deleted_formulae = kegs.map do |keg| - tap = Tab.for_keg(keg).tap + next if Formulary.tap_paths(keg.name).any? - 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 + 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) end - keg.name unless loadable + keg.name end.compact.uniq return if deleted_formulae.blank? diff --git a/Library/Homebrew/exceptions.rb b/Library/Homebrew/exceptions.rb index 1094c7e517..e0861796c5 100644 --- a/Library/Homebrew/exceptions.rb +++ b/Library/Homebrew/exceptions.rb @@ -259,20 +259,40 @@ end # Raised when a formula with the same name is found in multiple taps. class TapFormulaAmbiguityError < RuntimeError - attr_reader :name, :taps, :loaders + attr_reader :name, :paths, :formulae - def initialize(name, loaders) + def initialize(name, paths) @name = name - @loaders = loaders - @taps = loaders.map(&:tap) - - formulae = taps.map { |tap| "#{tap}/#{name}" } - formula_list = formulae.map { |f| "\n * #{f}" }.join + @paths = paths + @formulae = paths.map do |path| + "#{Tap.from_path(path).name}/#{path.basename(".rb")}" + end 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 end end diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index 9dd88c0365..17c7f2ee77 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.nilable(Pathname)) } + sig { returns(T.any(NilClass, Pathname, String)) } 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.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 } 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. # 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) - - path = Pathname(path) - return unless path.symlink? + return unless File.symlink?(path) path end sig { returns(T.nilable(String)) } def installed_alias_name - installed_alias_path&.basename&.to_s + File.basename(installed_alias_path) if installed_alias_path end def full_installed_alias_name @@ -350,13 +346,14 @@ class Formula # The path that was specified to find this formula. 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 local_bottle_path if local_bottle_path.presence&.exist? - alias_path || @unresolved_path + alias_pathname || @unresolved_path end # The name specified to find this formula. @@ -1318,7 +1315,7 @@ class Formula f = Formulary.factory(keg.name) rescue FormulaUnavailableError # formula for this keg is deleted, so defer to allowlist - rescue TapFormulaAmbiguityError + rescue TapFormulaAmbiguityError, TapFormulaWithOldnameAmbiguityError return false # this keg belongs to another formula else # this keg belongs to another unrelated formula @@ -2365,7 +2362,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.fetch(name).dup + json_formula = Homebrew::API::Formula.all_formulae[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 b094171b22..b0b8407862 100644 --- a/Library/Homebrew/formula_auditor.rb +++ b/Library/Homebrew/formula_auditor.rb @@ -64,7 +64,8 @@ module Homebrew unversioned_formula = begin Formulary.factory(full_name).path - rescue FormulaUnavailableError, TapFormulaAmbiguityError + rescue FormulaUnavailableError, TapFormulaAmbiguityError, + TapFormulaWithOldnameAmbiguityError Pathname.new formula.path.to_s.gsub(/@.*\.rb$/, ".rb") end unless unversioned_formula.exist? @@ -284,6 +285,9 @@ 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) @@ -457,7 +461,7 @@ module Homebrew next rescue FormulaUnavailableError problem "Can't find conflicting formula #{conflict.name.inspect}." - rescue TapFormulaAmbiguityError + rescue TapFormulaAmbiguityError, TapFormulaWithOldnameAmbiguityError problem "Ambiguous conflicting formula #{conflict.name.inspect}." end end diff --git a/Library/Homebrew/formulary.rb b/Library/Homebrew/formulary.rb index ea73296aa8..978f06fde8 100644 --- a/Library/Homebrew/formulary.rb +++ b/Library/Homebrew/formulary.rb @@ -16,7 +16,6 @@ require "extend/hash/keys" # # @api private module Formulary - extend Context extend Cachable URL_START_REGEX = %r{(https?|ftp|file)://} @@ -481,26 +480,17 @@ 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 - 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)) + def initialize(name, path, tap: nil) @name = name @path = path - @alias_path = alias_path @tap = tap end @@ -521,6 +511,7 @@ 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) @@ -528,17 +519,7 @@ module Formulary end # Loads a formula from a bottle. - 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 - + class BottleLoader < FormulaLoader def initialize(bottle_name) case bottle_name when URL_START_REGEX @@ -581,67 +562,27 @@ module Formulary end end - # Loads formulae from disk using a path. - class FromPathLoader < 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) - 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 + # 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 - 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) + super name, path + @alias_path = alias_path.to_s end end - # 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) + # Loads formulae from disk using a path. + class FromPathLoader < FormulaLoader + def initialize(path) + path = Pathname.new(path).expand_path + name = path.basename(".rb").to_s + super name, path, tap: Homebrew::API.tap_from_source_download(path) end + end + # Loads formulae from URLs. + class FromUrlLoader < FormulaLoader attr_reader :url sig { params(url: T.any(URI::Generic, String), from: T.nilable(Symbol)).void } @@ -680,45 +621,7 @@ module Formulary end # Loads tapped formulae. - 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 - + class TapLoader < FormulaLoader def get_formula(spec, alias_path: nil, force_bottle: false, flags: [], ignore_errors: false) super rescue FormulaUnreadableError => e @@ -735,94 +638,17 @@ module Formulary e.issues_url = tap.issues_url || tap.to_s raise end - end - 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? + private - 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) + def find_formula_from_name(name, tap) + Formulary.find_formula_in_tap(name, tap) end end # Pseudo-loader which will raise a {FormulaUnavailableError} when trying to load the corresponding formula. class NullLoader < 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 ref.is_a?(URI::Generic) - - new(ref) - end - - sig { params(ref: T.any(String, Pathname)).void } - def initialize(ref) - name = File.basename(ref, ".rb") + def initialize(name) super name, Formulary.core_path(name) end @@ -842,50 +668,16 @@ 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 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) + # Load formulae from the API. + class FormulaAPILoader < FormulaLoader + def initialize(name) + super name, Formulary.core_path(name) end def klass(flags:, ignore_errors:) @@ -896,10 +688,20 @@ 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: # @@ -939,7 +741,6 @@ module Formulary force_bottle: force_bottle, flags: flags, ignore_errors: ignore_errors }.compact - formula = loader_for(ref, **loader_options) .get_formula(spec, **formula_options) @@ -992,7 +793,7 @@ module Formulary # Return whether given rack is keg-only. def self.keg_only?(rack) Formulary.from_rack(rack).keg_only? - rescue FormulaUnavailableError, TapFormulaAmbiguityError + rescue FormulaUnavailableError, TapFormulaAmbiguityError, TapFormulaWithOldnameAmbiguityError false end @@ -1105,13 +906,9 @@ 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}" - # 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. + if (possible_alias = tap.alias_table[alias_name].presence) name = possible_alias.split("/").last type = :alias elsif (new_name = tap.formula_renames[name].presence) @@ -1141,32 +938,103 @@ module Formulary [name, tap, type] end - def self.loader_for(ref, from: T.unsafe(nil), warn: true) - options = { from: from, warn: warn }.compact + def self.tap_loader_for(tapped_name, warn:) + name, tap, type = Formulary.tap_formula_name_type(tapped_name, warn: warn) - [ - 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 + 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) 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.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 e3af9328ae..6f102bfcbf 100644 --- a/Library/Homebrew/tap_constants.rb +++ b/Library/Homebrew/tap_constants.rb @@ -1,42 +1,22 @@ # typed: strict # frozen_string_literal: true -# 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`. +# 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` HOMEBREW_DEFAULT_TAP_CASK_REGEX = T.let( - %r{\A(?:[Hh]omebrew/(?:homebrew-)?cask/)?#{HOMEBREW_TAP_CASK_TOKEN_REGEX.source}\Z}, - Regexp, + %r{^(?:[Hh]omebrew/(?:homebrew-)?cask/)?(?[a-z0-9\-_]+)$}, 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{(?:/.*)?\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) +# 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) diff --git a/Library/Homebrew/test/formula_spec.rb b/Library/Homebrew/test/formula_spec.rb index de9eba1357..414b6004dd 100644 --- a/Library/Homebrew/test/formula_spec.rb +++ b/Library/Homebrew/test/formula_spec.rb @@ -28,7 +28,7 @@ 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 } + let(:alias_path) { (CoreTap.instance.alias_dir/alias_name).to_s } let(:f) { klass.new(name, path, spec) } let(:f_alias) { klass.new(name, path, spec, alias_path: alias_path) } @@ -190,11 +190,11 @@ 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.to_s }) + f.build = Tab.new(source: { "path" => alias_path }) expect(f.installed_alias_path).to eq(alias_path) expect(f.installed_alias_name).to eq(alias_name) @@ -225,12 +225,12 @@ 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.to_s }) + f.build = Tab.new(source: { "path" => alias_path }) expect(f.installed_alias_path).to eq(alias_path) expect(f.installed_alias_name).to eq(alias_name) @@ -451,7 +451,7 @@ 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) + expect(f.installed_alias_path).to eq(source_path.to_s) end end @@ -491,14 +491,14 @@ 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.to_s + formula_with_alias.build.source["path"] = alias_path formula_without_alias = formula "bar" do url "bar-1.0" @@ -510,7 +510,7 @@ 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.to_s + formula_with_different_alias.build.source["path"] = different_alias_path formulae = [ formula_with_alias, @@ -1239,8 +1239,8 @@ 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 @@ describe Formula do end 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) CoreTap.instance.alias_dir.mkpath @@ -1276,7 +1276,7 @@ describe Formula do end 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) CoreTap.instance.alias_dir.mkpath @@ -1291,7 +1291,7 @@ describe Formula do end 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) CoreTap.instance.alias_dir.mkpath @@ -1332,8 +1332,8 @@ 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 2e70d2728d..33a0355729 100644 --- a/Library/Homebrew/test/formulary_spec.rb +++ b/Library/Homebrew/test/formulary_spec.rb @@ -111,7 +111,7 @@ describe Formulary do it "raises an error" do expect do described_class.factory(formula_name) - end.to raise_error(TapFormulaClassUnavailableError) + end.to raise_error(FormulaClassUnavailableError) end end @@ -139,13 +139,12 @@ describe Formulary do context "when given an alias" do 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" } before do alias_dir.mkpath FileUtils.ln_s formula_path, alias_path - CoreTap.instance.clear_cache end it "returns a Formula" do @@ -153,7 +152,7 @@ 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) + expect(formula.alias_path).to eq(alias_path.to_s) end end @@ -232,26 +231,23 @@ 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 - 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 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 it "raises an error when the Formula cannot be found" do @@ -382,7 +378,7 @@ describe Formulary do end 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 allow(DevelopmentTools).to receive_messages(needs_libc_formula?: false, needs_compiler_formula?: false)