Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Library/Homebrew/cask_dependent.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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]) }
Expand Down
2 changes: 1 addition & 1 deletion Library/Homebrew/cleanup.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 0 additions & 1 deletion Library/Homebrew/cmd/install.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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?

Expand Down
4 changes: 2 additions & 2 deletions Library/Homebrew/cmd/leaves.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down
12 changes: 8 additions & 4 deletions Library/Homebrew/dependency.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,20 @@ 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

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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Library/Homebrew/diagnostic.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
59 changes: 40 additions & 19 deletions Library/Homebrew/formula.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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.
Expand Down
54 changes: 47 additions & 7 deletions Library/Homebrew/formulary.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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:)
Expand Down Expand Up @@ -1177,16 +1188,18 @@ 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)

options = {
alias_path:,
force_bottle:,
flags:,
prefer_stub:,
}.compact

if keg
Expand All @@ -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
Expand All @@ -1213,14 +1226,16 @@ 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(
keg,
spec = nil,
alias_path: nil,
force_bottle: false,
flags: []
flags: [],
prefer_stub: false
)
tab = keg.tab
tap = tab.tap
Expand All @@ -1234,6 +1249,7 @@ def self.from_keg(
warn: false,
force_bottle:,
flags:,
prefer_stub:,
}.compact

f = if tap.nil?
Expand All @@ -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(
Expand Down
4 changes: 2 additions & 2 deletions Library/Homebrew/installed_dependents.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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|
Expand Down
2 changes: 1 addition & 1 deletion Library/Homebrew/keg.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions Library/Homebrew/uninstall.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
16 changes: 11 additions & 5 deletions Library/Homebrew/utils/autoremove.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand All @@ -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
Expand Down
Loading