Install and uninstall formulae with the internal API

This commit is contained in:
Rylan Polster 2025-08-29 05:18:03 -04:00
parent 51a98eb950
commit 7c71554e5d
No known key found for this signature in database
10 changed files with 89 additions and 39 deletions

View File

@ -32,7 +32,7 @@ class CaskDependent
sig { returns(T::Array[Dependency]) } sig { returns(T::Array[Dependency]) }
def runtime_dependencies 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 end
sig { returns(T::Array[Dependency]) } sig { returns(T::Array[Dependency]) }

View File

@ -722,7 +722,7 @@ module Homebrew
# Remove formulae listed in HOMEBREW_NO_CLEANUP_FORMULAE and their dependencies. # Remove formulae listed in HOMEBREW_NO_CLEANUP_FORMULAE and their dependencies.
if Homebrew::EnvConfig.no_cleanup_formulae.present? if Homebrew::EnvConfig.no_cleanup_formulae.present?
formulae -= formulae.select { skip_clean_formula?(_1) } 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 end
casks = Cask::Caskroom.casks casks = Cask::Caskroom.casks

View File

@ -229,7 +229,6 @@ module Homebrew
dep_names = CaskDependent.new(cask) dep_names = CaskDependent.new(cask)
.runtime_dependencies .runtime_dependencies
.reject(&:installed?) .reject(&:installed?)
.map(&:to_formula)
.map(&:name) .map(&:name)
next if dep_names.blank? next if dep_names.blank?

View File

@ -24,9 +24,9 @@ module Homebrew
sig { override.void } sig { override.void }
def run 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| 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 end
leaves_list -= casks_runtime_dependencies leaves_list -= casks_runtime_dependencies
leaves_list.select! { installed_on_request?(_1) } if args.installed_on_request? leaves_list.select! { installed_on_request?(_1) } if args.installed_on_request?

View File

@ -36,8 +36,12 @@ class Dependency
[name, tags].hash [name, tags].hash
end end
def to_formula(prefer_stub: false) def to_installed_formula
formula = Formulary.factory(name, warn: false, prefer_stub:) Formulary.from_rack(HOMEBREW_CELLAR/name)
end
def to_formula
formula = Formulary.factory(name, warn: false)
formula.build = BuildOptions.new(options, formula.options) formula.build = BuildOptions.new(options, formula.options)
formula formula
end end
@ -45,7 +49,7 @@ class Dependency
sig { params(minimum_version: T.nilable(Version), minimum_revision: T.nilable(Integer)).returns(T::Boolean) } sig { params(minimum_version: T.nilable(Version), minimum_revision: T.nilable(Integer)).returns(T::Boolean) }
def installed?(minimum_version: nil, minimum_revision: nil) def installed?(minimum_version: nil, minimum_revision: nil)
formula = begin formula = begin
to_formula(prefer_stub: true) to_installed_formula
rescue FormulaUnavailableError rescue FormulaUnavailableError
nil nil
end end
@ -86,7 +90,7 @@ class Dependency
end end
def missing_options(inherited_options) def missing_options(inherited_options)
formula = to_formula(prefer_stub: true) formula = to_installed_formula
required = options required = options
required |= inherited_options required |= inherited_options
required &= formula.options.to_a required &= formula.options.to_a

View File

@ -21,7 +21,7 @@ module Homebrew
sig { sig {
params(formulae: T::Array[Formula], hide: T::Array[String], _block: T.nilable( 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]]) )).returns(T::Hash[String, T::Array[String]])
} }
def self.missing_deps(formulae, hide = [], &_block) def self.missing_deps(formulae, hide = [], &_block)

View File

@ -2478,23 +2478,28 @@ class Formula
# @api internal # @api internal
sig { params(read_from_tab: T::Boolean, undeclared: T::Boolean).returns(T::Array[Dependency]) } sig { params(read_from_tab: T::Boolean, undeclared: T::Boolean).returns(T::Array[Dependency]) }
def runtime_dependencies(read_from_tab: true, undeclared: true) def runtime_dependencies(read_from_tab: true, undeclared: true)
deps = if read_from_tab && undeclared && cache_key = "#{full_name}-#{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 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
end
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 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 end
# Returns a list of {Formula} objects that are required at runtime. # Returns a list of {Formula} objects that are required at runtime.
@ -2513,6 +2518,22 @@ class Formula
end end
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]) } sig { returns(T::Array[Formula]) }
def runtime_installed_formula_dependents def runtime_installed_formula_dependents
# `any_installed_keg` and `runtime_dependencies` `select`s ensure # `any_installed_keg` and `runtime_dependencies` `select`s ensure
@ -2523,7 +2544,7 @@ class Formula
.select(&:any_installed_keg) .select(&:any_installed_keg)
.select(&:runtime_dependencies) .select(&:runtime_dependencies)
.select do |f| .select do |f|
f.runtime_formula_dependencies.any? do |dep| f.installed_runtime_formula_dependencies.any? do |dep|
full_name == dep.full_name full_name == dep.full_name
rescue rescue
name == dep.name name == dep.name
@ -2533,10 +2554,10 @@ class Formula
# Returns a list of formulae depended on by this formula that aren't # Returns a list of formulae depended on by this formula that aren't
# installed. # 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: []) def missing_dependencies(hide: [])
runtime_formula_dependencies.select do |f| runtime_dependencies(read_from_tab: true, undeclared: true).select do |f|
hide.include?(f.name) || f.installed_prefixes.empty? hide.include?(f.name) || !f.installed?
end end
# If we're still getting unavailable formulae at this stage the best we can # If we're still getting unavailable formulae at this stage the best we can
# do is just return no results. # do is just return no results.

View File

@ -949,9 +949,15 @@ module Formulary
def self.try_new(ref, from: nil, warn: false) def self.try_new(ref, from: nil, warn: false)
ref = ref.to_s 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
end end
@ -1067,7 +1073,12 @@ module Formulary
sig { overridable.params(flags: T::Array[String]).void } sig { overridable.params(flags: T::Array[String]).void }
def load_from_api(flags:) 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? raise FormulaUnavailableError, name if json_formula.nil?
Formulary.load_formula_from_json!(name, json_formula, flags:) Formulary.load_formula_from_json!(name, json_formula, flags:)
@ -1236,7 +1247,16 @@ module Formulary
flags:, flags:,
}.compact }.compact
f = if tap.nil? loader = FromKegLoader.try_new(keg.name, warn: false)
f = if loader.present?
begin
loader.get_formula(spec, alias_path:, force_bottle:, flags:, ignore_errors: true)
rescue FormulaUnreadableError
nil
end
end
f ||= if tap.nil?
factory(formula_name, spec, **options) factory(formula_name, spec, **options)
else else
begin begin

View File

@ -26,7 +26,7 @@ module InstalledDependents
kegs_by_source = kegs.group_by do |keg| kegs_by_source = kegs.group_by do |keg|
# First, attempt to resolve the keg to a formula # First, attempt to resolve the keg to a formula
# to get up-to-date name and tap information. # to get up-to-date name and tap information.
f = keg.to_formula f = keg.to_installed_formula
keg_formulae << f keg_formulae << f
[f.name, f.tap] [f.name, f.tap]
rescue rescue
@ -47,7 +47,7 @@ module InstalledDependents
dependent.missing_dependencies(hide: keg_names) dependent.missing_dependencies(hide: keg_names)
when Cask::Cask when Cask::Cask
# When checking for cask dependents, we don't care about missing or non-runtime dependencies # 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 end
required_kegs = required.filter_map do |f| required_kegs = required.filter_map do |f|

View File

@ -23,10 +23,16 @@ module Utils
# @private # @private
sig { params(casks: T::Array[Cask::Cask]).returns(T::Array[Formula]) } sig { params(casks: T::Array[Cask::Cask]).returns(T::Array[Formula]) }
def formulae_with_cask_dependents(casks) def formulae_with_cask_dependents(casks)
casks.flat_map { |cask| cask.depends_on[:formula] } casks.flat_map { |cask| cask.depends_on[:formula] }.compact.flat_map do |name|
.compact f = begin
.map { |f| Formula[f] } Formulary.from_rack(HOMEBREW_CELLAR/name)
.flat_map { |f| [f, *f.runtime_formula_dependencies].compact } rescue FormulaUnavailableError
nil
end
next [] unless f
[f, *f.installed_runtime_formula_dependencies].compact
end
end end
# An array of all installed bottled {Formula} without runtime {Formula} # An array of all installed bottled {Formula} without runtime {Formula}
@ -37,7 +43,7 @@ module Utils
def bottled_formulae_with_no_formula_dependents(formulae) def bottled_formulae_with_no_formula_dependents(formulae)
formulae_to_keep = T.let([], T::Array[Formula]) formulae_to_keep = T.let([], T::Array[Formula])
formulae.each do |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) if (tab = formula.any_installed_keg&.tab)
# Ignore build dependencies when the formula is a bottle # Ignore build dependencies when the formula is a bottle