Skip to content
Closed
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 @@ -231,7 +231,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! { |leaf| installed_on_request?(leaf) } if args.installed_on_request?
Expand Down
2 changes: 1 addition & 1 deletion Library/Homebrew/cmd/outdated.rb
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ def print_outdated(formulae_or_casks)
# There is a newer HEAD but the version number has not changed.
"latest HEAD"
else
f.pkg_version.to_s
f.latest_formula.pkg_version.to_s
end

outdated_versions = outdated_kegs.group_by { |keg| Formulary.from_keg(keg).full_name }
Expand Down
2 changes: 1 addition & 1 deletion Library/Homebrew/cmd/reinstall.rb
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ def run
end
Migrator.migrate_if_needed(formula, force: args.force?)
Homebrew::Reinstall.build_install_context(
formula,
formula.latest_formula,
flags: args.flags_only,
force_bottle: args.force_bottle?,
build_from_source_formulae: args.build_from_source_formulae,
Expand Down
14 changes: 10 additions & 4 deletions Library/Homebrew/dependency.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,22 @@ def hash
[name, tags].hash
end

def to_formula(prefer_stub: false)
formula = Formulary.factory(name, warn: false, prefer_stub:)
def to_installed_formula
formula = Formulary.resolve(name)
formula.build = BuildOptions.new(options, formula.options)
formula
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 +92,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
4 changes: 2 additions & 2 deletions Library/Homebrew/dev-cmd/bottle.rb
Original file line number Diff line number Diff line change
Expand Up @@ -661,8 +661,8 @@ def bottle_formula(formula)
"formula" => {
"name" => formula.name,
"pkg_version" => formula.pkg_version.to_s,
"path" => formula.path.to_s.delete_prefix("#{HOMEBREW_REPOSITORY}/"),
"tap_git_path" => formula.path.to_s.delete_prefix("#{tap_path}/"),
"path" => formula.tap_path.to_s.delete_prefix("#{HOMEBREW_REPOSITORY}/"),
"tap_git_path" => formula.tap_path.to_s.delete_prefix("#{tap_path}/"),
"tap_git_revision" => tap_git_revision,
"tap_git_remote" => tap_git_remote,
# descriptions can contain emoji. sigh.
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
71 changes: 50 additions & 21 deletions Library/Homebrew/formula.rb
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,13 @@ def installed_alias_name = installed_alias_path&.basename&.to_s
sig { returns(T.nilable(String)) }
def full_installed_alias_name = full_name_with_optional_tap(installed_alias_name)

sig { returns(Pathname) }
def tap_path
return path unless (t = tap)

t.new_formula_path(name)
end

# The path that was specified to find this formula.
sig { returns(T.nilable(Pathname)) }
def specified_path
Expand Down Expand Up @@ -1705,14 +1712,15 @@ def outdated_kegs(fetch_head: false)
Formula.cache[:outdated_kegs][cache_key] ||= begin
all_kegs = []
current_version = T.let(false, T::Boolean)
latest = latest_formula

installed_kegs.each do |keg|
all_kegs << keg
version = keg.version
next if version.head?

next if version_scheme > keg.version_scheme && pkg_version != version
next if version_scheme == keg.version_scheme && pkg_version > version
next if latest.version_scheme > keg.version_scheme && latest.pkg_version != version
next if latest.version_scheme == keg.version_scheme && latest.pkg_version > version

# don't consider this keg current if there's a newer formula available
next if follow_installed_alias? && new_formula_available?
Expand Down Expand Up @@ -1770,7 +1778,7 @@ def alias_changed?
# Otherwise, return self.
sig { returns(Formula) }
def latest_formula
installed_alias_target_changed? ? T.must(current_installed_alias_target) : self
installed_alias_target_changed? ? T.must(current_installed_alias_target) : Formulary.factory(name)
end

sig { returns(T::Array[Formula]) }
Expand Down Expand Up @@ -2481,23 +2489,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 @@ -2516,6 +2529,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 @@ -2526,7 +2555,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 @@ -2536,10 +2565,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.to_installed_formula.installed_prefixes.none?
end
# If we're still getting unavailable formulae at this stage the best we can
# do is just return no results.
Expand Down
2 changes: 1 addition & 1 deletion Library/Homebrew/formula_versions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class FormulaVersions
sig { params(formula: Formula).void }
def initialize(formula)
@name = formula.name
@path = formula.path
@path = formula.tap_path
@repository = T.must(formula.tap).path
@relative_path = @path.relative_path_from(repository).to_s
# Also look at e.g. older homebrew-core paths before sharding.
Expand Down
37 changes: 33 additions & 4 deletions Library/Homebrew/formulary.rb
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,15 @@ def self.try_new(ref, from: nil, warn: false)
return unless path.expand_path.exist?
return unless ::Utils::Path.loadable_package_path?(path, :formula)

# If the path is for an installed keg, use FromKegLoader instead
begin
keg = Keg.for(path)
loader = FromKegLoader.try_new(keg.name, from:, warn:)
return T.cast(loader, T.attached_class)
rescue NotAKegError
# Not a keg path, continue
end

if (tap = Tap.from_path(path))
# Only treat symlinks in taps as aliases.
if path.symlink?
Expand Down Expand Up @@ -936,9 +945,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, tap: keg.tab.tap)
end
end

Expand Down Expand Up @@ -1054,7 +1069,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 @@ -1223,7 +1243,16 @@ def self.from_keg(
flags:,
}.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)
else
begin
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 @@ -44,10 +44,10 @@ def find_some_installed_dependents(kegs, casks: [])
dependents_to_check.each do |dependent|
required = case dependent
when Formula
dependent.missing_dependencies(hide: keg_names)
dependent.missing_dependencies(hide: keg_names).map(&:to_installed_formula)
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.map(&:to_installed_formula)
end

required_kegs = required.filter_map do |f|
Expand Down
3 changes: 1 addition & 2 deletions Library/Homebrew/test/cmd/leaves_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,8 @@

context "when there are only installed Formulae without dependencies", :integration_test do
it "prints all installed Formulae" do
setup_test_formula "foo"
setup_test_formula "foo", tab_attributes: { installed_on_request: true }
setup_test_formula "bar"
(HOMEBREW_CELLAR/"foo/0.1/somedir").mkpath

expect { brew "leaves" }
.to output("foo\n").to_stdout
Expand Down
11 changes: 11 additions & 0 deletions Library/Homebrew/test/formula_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,8 @@ def post_install

expect(f3.runtime_dependencies.map(&:name)).to eq(["baz/qux/f2"])

described_class.clear_cache

f1_path = Tap.fetch("foo", "bar").path/"Formula/f1.rb"
stub_formula_loader(formula("f1", path: f1_path) { url("f1-1.0") }, "foo/bar/f1")

Expand Down Expand Up @@ -960,6 +962,7 @@ def post_install
sha256 cellar: :any, Utils::Bottles.tag.to_sym => TEST_SHA256
end
end
stub_formula_loader(f1)

h = f1.to_hash

Expand Down Expand Up @@ -1270,6 +1273,8 @@ def pour_bottle?
let(:alias_path) { CoreTap.instance.alias_dir/alias_name }

before do
stub_formula_loader(f)
stub_formula_loader(new_formula)
allow(described_class).to receive(:installed).and_return([f])

f.build = tab
Expand Down Expand Up @@ -1362,6 +1367,12 @@ def pour_bottle?
let(:alias_name) { "bar" }
let(:alias_path) { f.tap.alias_dir/alias_name }

before do
stub_formula_loader(f)
stub_formula_loader(old_formula)
stub_formula_loader(new_formula)
end

def setup_tab_for_prefix(prefix, options = {})
prefix.mkpath

Expand Down
3 changes: 3 additions & 0 deletions Library/Homebrew/test/migrator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@

before do |example|
allow(new_formula).to receive(:oldnames).and_return(["oldname"])
allow(Formulary).to receive(:factory).with("homebrew/core/oldname", any_args).and_return(old_formula)
allow(Formulary).to receive(:factory).with("oldname", any_args).and_return(old_formula)
allow(Formulary).to receive(:factory).with("newname", any_args).and_return(new_formula)

# do not create directories for error tests
next if example.metadata[:description].start_with?("raises an error")
Expand Down
2 changes: 1 addition & 1 deletion Library/Homebrew/test/support/helper/formula.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def formula(name = "formula_name", path: nil, spec: :stable, alias_path: nil, ta
def stub_formula_loader(formula, ref = formula.full_name, call_original: false)
allow(Formulary).to receive(:loader_for).and_call_original if call_original

loader = instance_double(Formulary::FormulaLoader, get_formula: formula)
loader = instance_double(Formulary::FormulaLoader, get_formula: formula, name: formula.name)
allow(Formulary).to receive(:loader_for).with(ref, any_args).and_return(loader)
end
end
Expand Down
Loading
Loading