diff --git a/Library/Homebrew/cask_dependent.rb b/Library/Homebrew/cask_dependent.rb index 01bd0a7f19ff4..9c7127bd166b4 100644 --- a/Library/Homebrew/cask_dependent.rb +++ b/Library/Homebrew/cask_dependent.rb @@ -32,7 +32,7 @@ def full_name sig { returns(T::Array[Dependency]) } def runtime_dependencies - deps.flat_map { |dep| [dep, *dep.to_formula.runtime_dependencies] }.uniq + deps.flat_map { |dep| [dep, *dep.to_installed_formula.runtime_dependencies] }.uniq end sig { returns(T::Array[Dependency]) } diff --git a/Library/Homebrew/cleanup.rb b/Library/Homebrew/cleanup.rb index c48f2320fe2be..a30364d64399a 100644 --- a/Library/Homebrew/cleanup.rb +++ b/Library/Homebrew/cleanup.rb @@ -722,7 +722,7 @@ def self.autoremove(dry_run: false) # Remove formulae listed in HOMEBREW_NO_CLEANUP_FORMULAE and their dependencies. if Homebrew::EnvConfig.no_cleanup_formulae.present? formulae -= formulae.select { skip_clean_formula?(_1) } - .flat_map { |f| [f, *f.runtime_formula_dependencies] } + .flat_map { |f| [f, *f.installed_runtime_formula_dependencies] } end casks = Cask::Caskroom.casks diff --git a/Library/Homebrew/cmd/install.rb b/Library/Homebrew/cmd/install.rb index 5f187843c50c8..167e5072eb9f2 100644 --- a/Library/Homebrew/cmd/install.rb +++ b/Library/Homebrew/cmd/install.rb @@ -229,7 +229,6 @@ def run dep_names = CaskDependent.new(cask) .runtime_dependencies .reject(&:installed?) - .map(&:to_formula) .map(&:name) next if dep_names.blank? diff --git a/Library/Homebrew/cmd/leaves.rb b/Library/Homebrew/cmd/leaves.rb index 9ac093f6d4c2d..ec13935950417 100644 --- a/Library/Homebrew/cmd/leaves.rb +++ b/Library/Homebrew/cmd/leaves.rb @@ -24,9 +24,9 @@ class Leaves < AbstractCommand sig { override.void } def run - leaves_list = Formula.installed - Formula.installed.flat_map(&:runtime_formula_dependencies) + leaves_list = Formula.installed - Formula.installed.flat_map(&:installed_runtime_formula_dependencies) casks_runtime_dependencies = Cask::Caskroom.casks.flat_map do |cask| - CaskDependent.new(cask).runtime_dependencies.map(&:to_formula) + CaskDependent.new(cask).runtime_dependencies.map(&:to_installed_formula) end leaves_list -= casks_runtime_dependencies leaves_list.select! { installed_on_request?(_1) } if args.installed_on_request? diff --git a/Library/Homebrew/dependency.rb b/Library/Homebrew/dependency.rb index c766e750a2be2..d30df2342e657 100644 --- a/Library/Homebrew/dependency.rb +++ b/Library/Homebrew/dependency.rb @@ -36,8 +36,12 @@ def hash [name, tags].hash end - def to_formula(prefer_stub: false) - formula = Formulary.factory(name, warn: false, prefer_stub:) + def to_installed_formula + Formulary.from_installed(name) + end + + def to_formula + formula = Formulary.factory(name, warn: false) formula.build = BuildOptions.new(options, formula.options) formula end @@ -45,7 +49,7 @@ def to_formula(prefer_stub: false) sig { params(minimum_version: T.nilable(Version), minimum_revision: T.nilable(Integer)).returns(T::Boolean) } def installed?(minimum_version: nil, minimum_revision: nil) formula = begin - to_formula(prefer_stub: true) + to_installed_formula rescue FormulaUnavailableError nil end @@ -86,7 +90,7 @@ def satisfied?(inherited_options = [], minimum_version: nil, minimum_revision: n end def missing_options(inherited_options) - formula = to_formula(prefer_stub: true) + formula = to_installed_formula required = options required |= inherited_options required &= formula.options.to_a diff --git a/Library/Homebrew/diagnostic.rb b/Library/Homebrew/diagnostic.rb index b00517b55784f..7d0cf4daef7d5 100644 --- a/Library/Homebrew/diagnostic.rb +++ b/Library/Homebrew/diagnostic.rb @@ -21,7 +21,7 @@ module Diagnostic sig { params(formulae: T::Array[Formula], hide: T::Array[String], _block: T.nilable( - T.proc.params(formula_name: String, missing_dependencies: T::Array[Formula]).void, + T.proc.params(formula_name: String, missing_dependencies: T::Array[Dependency]).void, )).returns(T::Hash[String, T::Array[String]]) } def self.missing_deps(formulae, hide = [], &_block) diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index 33560d6e040e4..f6473e8776057 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -2322,7 +2322,7 @@ def self.installed_formula_names sig { returns(T::Array[Formula]) } def self.installed Formula.cache[:installed] ||= racks.flat_map do |rack| - Formulary.from_rack(rack) + Formulary.from_installed(rack.basename.to_s) rescue [] end.uniq(&:name) @@ -2478,23 +2478,28 @@ def any_installed_version # @api internal sig { params(read_from_tab: T::Boolean, undeclared: T::Boolean).returns(T::Array[Dependency]) } def runtime_dependencies(read_from_tab: true, undeclared: true) - deps = if read_from_tab && undeclared && - (tab_deps = any_installed_keg&.runtime_dependencies) - tab_deps.filter_map do |d| - full_name = d["full_name"] - next unless full_name + cache_key = "#{full_name}-#{read_from_tab}-#{undeclared}" + + Formula.cache[:runtime_dependencies] ||= {} + Formula.cache[:runtime_dependencies][cache_key] ||= begin + deps = if read_from_tab && undeclared && + (tab_deps = any_installed_keg&.runtime_dependencies) + tab_deps.filter_map do |d| + full_name = d["full_name"] + next unless full_name - Dependency.new full_name + Dependency.new full_name + end end + begin + deps ||= declared_runtime_dependencies unless undeclared + deps ||= (declared_runtime_dependencies | undeclared_runtime_dependencies) + rescue FormulaUnavailableError + onoe "Could not get runtime dependencies from #{path}!" + deps ||= [] + end + deps end - begin - deps ||= declared_runtime_dependencies unless undeclared - deps ||= (declared_runtime_dependencies | undeclared_runtime_dependencies) - rescue FormulaUnavailableError - onoe "Could not get runtime dependencies from #{path}!" - deps ||= [] - end - deps end # Returns a list of {Formula} objects that are required at runtime. @@ -2513,6 +2518,22 @@ def runtime_formula_dependencies(read_from_tab: true, undeclared: true) end end + # Returns a list of installed {Formula} objects that are required at runtime. + sig { params(read_from_tab: T::Boolean, undeclared: T::Boolean).returns(T::Array[Formula]) } + def installed_runtime_formula_dependencies(read_from_tab: true, undeclared: true) + cache_key = "#{full_name}-#{read_from_tab}-#{undeclared}" + + Formula.cache[:installed_runtime_formula_dependencies] ||= {} + Formula.cache[:installed_runtime_formula_dependencies][cache_key] ||= runtime_dependencies( + read_from_tab:, + undeclared:, + ).filter_map do |d| + d.to_installed_formula + rescue FormulaUnavailableError + nil + end + end + sig { returns(T::Array[Formula]) } def runtime_installed_formula_dependents # `any_installed_keg` and `runtime_dependencies` `select`s ensure @@ -2523,7 +2544,7 @@ def runtime_installed_formula_dependents .select(&:any_installed_keg) .select(&:runtime_dependencies) .select do |f| - f.runtime_formula_dependencies.any? do |dep| + f.installed_runtime_formula_dependencies.any? do |dep| full_name == dep.full_name rescue name == dep.name @@ -2533,10 +2554,10 @@ def runtime_installed_formula_dependents # Returns a list of formulae depended on by this formula that aren't # installed. - sig { params(hide: T::Array[String]).returns(T::Array[Formula]) } + sig { params(hide: T::Array[String]).returns(T::Array[Dependency]) } def missing_dependencies(hide: []) - runtime_formula_dependencies.select do |f| - hide.include?(f.name) || f.installed_prefixes.empty? + runtime_dependencies(read_from_tab: true, undeclared: true).select do |f| + hide.include?(f.name) || !f.installed? end # If we're still getting unavailable formulae at this stage the best we can # do is just return no results. diff --git a/Library/Homebrew/formulary.rb b/Library/Homebrew/formulary.rb index dcf95aeceea7f..7b223aab62194 100644 --- a/Library/Homebrew/formulary.rb +++ b/Library/Homebrew/formulary.rb @@ -551,7 +551,7 @@ def self.resolve( else rack = to_rack(name) alias_path = factory(name, force_bottle:, flags:, prefer_stub:).alias_path - f = from_rack(rack, *spec, alias_path:, force_bottle:, flags:) + f = from_rack(rack, *spec, alias_path:, force_bottle:, flags:, prefer_stub:) end # If this formula was installed with an alias that has since changed, @@ -949,9 +949,15 @@ class FromKegLoader < FormulaLoader def self.try_new(ref, from: nil, warn: false) ref = ref.to_s - return unless (keg_formula = HOMEBREW_PREFIX/"opt/#{ref}/.brew/#{ref}.rb").file? + keg_directory = HOMEBREW_PREFIX/"opt/#{ref}" + return unless keg_directory.directory? - new(ref, keg_formula) + # The formula file in `.brew` will use the canonical name, whereas `ref` can be an alias. + # Use `Keg#name` to get the canonical name. + keg = Keg.new(keg_directory) + return unless (keg_formula = HOMEBREW_PREFIX/"opt/#{ref}/.brew/#{keg.name}.rb").file? + + new(keg.name, keg_formula) end end @@ -1067,7 +1073,12 @@ def klass(flags:, ignore_errors:) sig { overridable.params(flags: T::Array[String]).void } def load_from_api(flags:) - json_formula = Homebrew::API::Formula.all_formulae[name] + json_formula = if Homebrew::EnvConfig.use_internal_api? + Homebrew::API::Formula.formula_json(name) + else + Homebrew::API::Formula.all_formulae[name] + end + raise FormulaUnavailableError, name if json_formula.nil? Formulary.load_formula_from_json!(name, json_formula, flags:) @@ -1177,9 +1188,10 @@ def self.factory( alias_path: T.any(NilClass, Pathname, String), force_bottle: T::Boolean, flags: T::Array[String], + prefer_stub: T::Boolean, ).returns(Formula) } - def self.from_rack(rack, spec = nil, alias_path: nil, force_bottle: false, flags: []) + def self.from_rack(rack, spec = nil, alias_path: nil, force_bottle: false, flags: [], prefer_stub: false) kegs = rack.directory? ? rack.subdirs.map { |d| Keg.new(d) } : [] keg = kegs.find(&:linked?) || kegs.find(&:optlinked?) || kegs.max_by(&:scheme_and_version) @@ -1187,6 +1199,7 @@ def self.from_rack(rack, spec = nil, alias_path: nil, force_bottle: false, flags alias_path:, force_bottle:, flags:, + prefer_stub:, }.compact if keg @@ -1199,7 +1212,7 @@ def self.from_rack(rack, spec = nil, alias_path: nil, force_bottle: false, flags # Return whether given rack is keg-only. sig { params(rack: Pathname).returns(T::Boolean) } def self.keg_only?(rack) - Formulary.from_rack(rack).keg_only? + Formulary.from_installed(rack.basename.to_s).keg_only? rescue FormulaUnavailableError, TapFormulaAmbiguityError false end @@ -1213,6 +1226,7 @@ def self.keg_only?(rack) alias_path: T.any(NilClass, Pathname, String), force_bottle: T::Boolean, flags: T::Array[String], + prefer_stub: T::Boolean, ).returns(Formula) } def self.from_keg( @@ -1220,7 +1234,8 @@ def self.from_keg( spec = nil, alias_path: nil, force_bottle: false, - flags: [] + flags: [], + prefer_stub: false ) tab = keg.tab tap = tab.tap @@ -1234,6 +1249,7 @@ def self.from_keg( warn: false, force_bottle:, flags:, + prefer_stub:, }.compact f = if tap.nil? @@ -1252,6 +1268,30 @@ def self.from_keg( f end + sig { + params( + name: String, + spec: Symbol, + alias_path: T.nilable(Pathname), + force_bottle: T::Boolean, + flags: T::Array[String], + ignore_errors: T::Boolean, + ).returns(Formula) + } + def self.from_installed( + name, + spec = :stable, + alias_path: nil, + force_bottle: false, + flags: [], + ignore_errors: false + ) + loader = FromKegLoader.try_new(name, warn: false) + raise FormulaUnavailableError, name unless loader + + loader.get_formula(spec, alias_path:, force_bottle:, flags:, ignore_errors:) + end + # Return a {Formula} instance directly from contents. sig { params( diff --git a/Library/Homebrew/installed_dependents.rb b/Library/Homebrew/installed_dependents.rb index 14eaf239a0eb2..56cb4f80dbadf 100644 --- a/Library/Homebrew/installed_dependents.rb +++ b/Library/Homebrew/installed_dependents.rb @@ -26,7 +26,7 @@ def find_some_installed_dependents(kegs, casks: []) kegs_by_source = kegs.group_by do |keg| # First, attempt to resolve the keg to a formula # to get up-to-date name and tap information. - f = keg.to_formula + f = keg.to_installed_formula keg_formulae << f [f.name, f.tap] rescue @@ -47,7 +47,7 @@ def find_some_installed_dependents(kegs, casks: []) dependent.missing_dependencies(hide: keg_names) when Cask::Cask # When checking for cask dependents, we don't care about missing or non-runtime dependencies - CaskDependent.new(dependent).runtime_dependencies.map(&:to_formula) + CaskDependent.new(dependent).runtime_dependencies end required_kegs = required.filter_map do |f| diff --git a/Library/Homebrew/keg.rb b/Library/Homebrew/keg.rb index 92e9b1796a946..ee30de93afccf 100644 --- a/Library/Homebrew/keg.rb +++ b/Library/Homebrew/keg.rb @@ -411,7 +411,7 @@ def scheme_and_version end def to_formula - Formulary.from_keg(self) + Formulary.from_installed(name) end def oldname_opt_records diff --git a/Library/Homebrew/uninstall.rb b/Library/Homebrew/uninstall.rb index 62c421cd7f800..09c726fb4de16 100644 --- a/Library/Homebrew/uninstall.rb +++ b/Library/Homebrew/uninstall.rb @@ -32,7 +32,7 @@ def self.uninstall_kegs(kegs_by_rack, casks: [], force: false, ignore_dependenci else kegs.each do |keg| begin - f = Formulary.from_rack(rack) + f = Formulary.from_installed(keg.name) if f.pinned? onoe "#{f.full_name} is pinned. You must unpin it to uninstall." break # exit keg loop and move on to next rack @@ -157,7 +157,7 @@ def are_required_by_deps end def self.rm_pin(rack) - Formulary.from_rack(rack).unpin + Formulary.from_installed(rack.basename.to_s).unpin rescue nil end diff --git a/Library/Homebrew/utils/autoremove.rb b/Library/Homebrew/utils/autoremove.rb index 3e0c136c09a1c..bb88754c3b8e2 100644 --- a/Library/Homebrew/utils/autoremove.rb +++ b/Library/Homebrew/utils/autoremove.rb @@ -23,10 +23,16 @@ def removable_formulae(formulae, casks) # @private sig { params(casks: T::Array[Cask::Cask]).returns(T::Array[Formula]) } def formulae_with_cask_dependents(casks) - casks.flat_map { |cask| cask.depends_on[:formula] } - .compact - .map { |f| Formula[f] } - .flat_map { |f| [f, *f.runtime_formula_dependencies].compact } + casks.flat_map { |cask| cask.depends_on[:formula] }.compact.flat_map do |name| + f = begin + Formulary.from_installed(name) + rescue FormulaUnavailableError + nil + end + next [] unless f + + [f, *f.installed_runtime_formula_dependencies].compact + end end # An array of all installed bottled {Formula} without runtime {Formula} @@ -37,7 +43,7 @@ def formulae_with_cask_dependents(casks) def bottled_formulae_with_no_formula_dependents(formulae) formulae_to_keep = T.let([], T::Array[Formula]) formulae.each do |formula| - formulae_to_keep += formula.runtime_formula_dependencies + formulae_to_keep += formula.installed_runtime_formula_dependencies if (tab = formula.any_installed_keg&.tab) # Ignore build dependencies when the formula is a bottle