Skip to content
Open
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
25 changes: 19 additions & 6 deletions Library/Homebrew/cmd/info.rb
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,9 @@ def info_formula(formula, shadowed_by: nil)
kegs = shadowing_formula ? [] : formula.installed_kegs
installed = kegs.any?
outdated = installed && formula.outdated?
missing_libraries, missing_library_deps = formula.missing_library_linkage if installed
missing_libraries ||= []
missing_library_deps ||= Set.new
if outdated && (upgrade_version = specs.first.presence)
installed_version = formula.linked_version ||
kegs.max_by(&:scheme_and_version)&.version
Expand All @@ -656,6 +659,7 @@ def info_formula(formula, shadowed_by: nil)
end
name_with_status = pretty_install_status(
title_name,
warning: missing_libraries.present?,
installed:,
outdated:,
deprecated: formula.deprecated?,
Expand Down Expand Up @@ -753,11 +757,17 @@ def info_formula(formula, shadowed_by: nil)

tab_deps = (kegs.any? && type != "build") ? tab_runtime_deps : nil
"#{type.capitalize} (#{deps.count}): " \
"#{decorate_dependencies(deps, tab_runtime_deps: tab_deps, mark_uninstalled: kegs.any?)}"
"#{decorate_dependencies(deps, tab_runtime_deps: tab_deps, mark_uninstalled: kegs.any?,
missing_library_deps:)}"
end
if dependency_lines.present? || tab_runtime_deps.present? || installed_dependents.any?
ohai "Dependencies"
puts dependency_lines
missing_library_names = missing_libraries.map { |lib| File.basename(lib) }.uniq
if missing_library_names.present?
decorated = missing_library_names.map { |lib| pretty_uninstalled(lib, bold: false) }.join(", ")
puts "Missing libraries (#{missing_library_names.count}): #{decorated}"
end
if tab_runtime_deps.present?
installed_count = tab_runtime_deps.count do |dep|
dep_name = dep["full_name"]&.then { Utils.name_from_full_name(it) }
Expand Down Expand Up @@ -869,11 +879,13 @@ def installed_section_lines(formula, verbose: false)
end

sig {
params(dependencies: T::Array[Dependency],
tab_runtime_deps: T.nilable(T::Array[T::Hash[String, T.untyped]]),
mark_uninstalled: T::Boolean).returns(String)
params(dependencies: T::Array[Dependency],
tab_runtime_deps: T.nilable(T::Array[T::Hash[String, T.untyped]]),
mark_uninstalled: T::Boolean,
missing_library_deps: T::Set[String]).returns(String)
}
def decorate_dependencies(dependencies, tab_runtime_deps: nil, mark_uninstalled: true)
def decorate_dependencies(dependencies, tab_runtime_deps: nil, mark_uninstalled: true,
missing_library_deps: Set.new)
dependencies.map do |dep|
display = dep_display_s(dep)
full_name = tab_runtime_deps&.find do |d|
Expand All @@ -889,7 +901,8 @@ def decorate_dependencies(dependencies, tab_runtime_deps: nil, mark_uninstalled:
end
installed ||= formula.any_version_installed? if !installed && formula
outdated = T.let(installed && formula&.outdated? == true, T::Boolean)
pretty_install_status(display, installed:, outdated:, mark_uninstalled:)
warning = missing_library_deps.include?(Utils.name_from_full_name(dep.name))
pretty_install_status(display, warning:, installed:, outdated:, mark_uninstalled:)
end.join(", ")
end

Expand Down
14 changes: 14 additions & 0 deletions Library/Homebrew/formula.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3441,6 +3441,20 @@ def undeclared_runtime_dependencies

public

sig { returns([T::Array[String], T::Set[String]]) }
def missing_library_linkage
keg = any_installed_keg
return [[], Set.new] unless keg&.directory?

CacheStoreDatabase.use(:linkage) do |db|
typed_db = T.cast(db, CacheStoreDatabase[String, T::Hash[T.any(String, Symbol), T.anything]])
linkage_checker = LinkageChecker.new(keg, self, cache_db: typed_db)
own_libraries = (linkage_checker.broken_deps.fetch(name, []) + linkage_checker.broken_dylibs.to_a).uniq.sort
dependency_names = linkage_checker.broken_deps.keys.reject { |dep| dep == name }.to_set
[own_libraries, dependency_names]
end
end

# To call out to the system, we use the `system` method and we prefer
# you give the args separately as in the line below, otherwise a subshell
# has to be opened first.
Expand Down
5 changes: 4 additions & 1 deletion Library/Homebrew/linkage_checker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ class LinkageChecker
attr_reader :indirect_deps, :undeclared_deps, :unwanted_system_dylibs

sig { returns(T::Set[String]) }
attr_reader :system_dylibs
attr_reader :system_dylibs, :broken_dylibs

sig { returns(T::Hash[String, T::Array[String]]) }
attr_reader :broken_deps

sig {
params(
Expand Down
38 changes: 19 additions & 19 deletions Library/Homebrew/test/cmd/info_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ def installed_info_cask
option "with-foo", "Build with foo"
end
allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb")
allow(formula).to receive(:core_formula?).and_return(false)
allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new])

expect { info.send(:info_formula, formula) }
.to output(/Installs from source: yes/).to_stdout
Expand Down Expand Up @@ -332,7 +332,7 @@ def installed_info_cask
deprecate! date: "2024-01-01", because: :versioned_formula
end
allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb")
allow(formula).to receive(:core_formula?).and_return(false)
allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new])

expect { info.send(:info_formula, formula) }
.to output(/==> .*testball.*\(deprecated\):/).to_stdout
Expand All @@ -350,7 +350,7 @@ def installed_info_cask
disable! date: "2024-01-01", because: :unmaintained
end
allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb")
allow(formula).to receive(:core_formula?).and_return(false)
allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new])

expect { info.send(:info_formula, formula) }
.to output(/==> .*testball.*\(disabled\):/).to_stdout
Expand Down Expand Up @@ -475,7 +475,7 @@ def installed_info_cask
url "https://brew.sh/testball-0.1.tar.gz"
end
allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb")
allow(formula).to receive(:core_formula?).and_return(false)
allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new])

expect { info.send(:info_formula, formula, shadowed_by: Tap.fetch("homebrew/core")) }
.to output(%r{Warning: `testball` shadows `homebrew/core/testball`}).to_stdout
Expand Down Expand Up @@ -595,7 +595,7 @@ def installed_info_cask
dependent_tab.write

allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb")
allow(formula).to receive(:core_formula?).and_return(false)
allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new])
allow(direct_dependency).to receive(:satisfied?).and_return(true)

expected_output = Regexp.new(
Expand Down Expand Up @@ -637,7 +637,7 @@ def installed_info_cask
end

allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb")
allow(formula).to receive(:core_formula?).and_return(false)
allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new])

expect { info.send(:info_formula, formula) }
.to output(/^Dependents \(2\): another-dependent, some-dependent$/).to_stdout
Expand Down Expand Up @@ -673,7 +673,7 @@ def installed_info_cask
installed_dep_tab.write

allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb")
allow(formula).to receive(:core_formula?).and_return(false)
allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new])
allow(direct_dependency).to receive(:satisfied?).and_return(true)

expect { info.send(:info_formula, formula) }
Expand Down Expand Up @@ -702,7 +702,7 @@ def installed_info_cask
tab.write

allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb")
allow(formula).to receive(:core_formula?).and_return(false)
allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new])

expect { info.send(:info_formula, formula) }
.to output(/Required \(1\): .*bar.*✘/).to_stdout
Expand Down Expand Up @@ -739,7 +739,7 @@ def installed_info_cask
allow(direct_dependency).to receive(:to_formula).and_return(bar_formula)

allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb")
allow(formula).to receive(:core_formula?).and_return(false)
allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new])

expect { info.send(:info_formula, formula) }
.to output(/Required \(1\): .*bar.*✔/).to_stdout
Expand Down Expand Up @@ -776,7 +776,7 @@ def installed_info_cask
allow(direct_dependency).to receive(:to_formula).and_return(bar_formula)

allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb")
allow(formula).to receive(:core_formula?).and_return(false)
allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new])

expect { info.send(:info_formula, formula) }
.to output(/Required \(1\): .*bar.*↑/).to_stdout
Expand Down Expand Up @@ -806,7 +806,7 @@ def installed_info_cask
allow(direct_dependency).to receive(:to_formula).and_return(bar_formula)

allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb")
allow(formula).to receive(:core_formula?).and_return(false)
allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new])

expect { info.send(:info_formula, formula) }
.to output(/Required \(1\): .*bar.*✔/).to_stdout
Expand Down Expand Up @@ -836,7 +836,7 @@ def installed_info_cask
allow(direct_dependency).to receive(:to_formula).and_return(bar_formula)

allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb")
allow(formula).to receive(:core_formula?).and_return(false)
allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new])

expect { info.send(:info_formula, formula) }
.to output(/Required \(1\): .*bar.*↑/).to_stdout
Expand Down Expand Up @@ -866,7 +866,7 @@ def installed_info_cask
allow(direct_dependency).to receive(:to_formula).and_return(pkgconf_formula)

allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb")
allow(formula).to receive(:core_formula?).and_return(false)
allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new])

expect { info.send(:info_formula, formula) }
.to output(/Required \(1\): .*pkg-config.*✔/).to_stdout
Expand All @@ -886,7 +886,7 @@ def installed_info_cask
end

allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb")
allow(formula).to receive(:core_formula?).and_return(false)
allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new])

expect { info.send(:info_formula, formula) }
.to output(/Required \(1\): bar\n/).to_stdout
Expand All @@ -913,7 +913,7 @@ def installed_info_cask
tab.write

allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb")
allow(formula).to receive(:core_formula?).and_return(false)
allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new])

expect { info.send(:info_formula, formula) }
.to output(/Required \(1\): .*bar.*✘/).to_stdout
Expand Down Expand Up @@ -946,7 +946,7 @@ def installed_info_cask
bar_tab.write

allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb")
allow(formula).to receive(:core_formula?).and_return(false)
allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new])

expect { info.send(:info_formula, formula) }
.to output(/Required \(1\): .*bar.*↑/).to_stdout
Expand Down Expand Up @@ -1071,7 +1071,7 @@ def installed_info_cask
tab.write

allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb")
allow(formula).to receive(:core_formula?).and_return(false)
allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new])

expect { info.send(:info_formula, formula) }
.to output(a_string_including("==> Binaries\nanother\ndaemon\ntestball\n")).to_stdout
Expand Down Expand Up @@ -1121,7 +1121,7 @@ def installed_info_cask
tab.write

allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb")
allow(formula).to receive(:core_formula?).and_return(false)
allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new])

expect { info.send(:info_formula, formula) }
.to not_to_output(/==> Binaries/).to_stdout
Expand All @@ -1144,7 +1144,7 @@ def installed_info_cask
tab.write

allow(info).to receive(:github_info).with(formula).and_return("https://example.com/testball.rb")
allow(formula).to receive(:core_formula?).and_return(false)
allow(formula).to receive_messages(core_formula?: false, missing_library_linkage: [[], Set.new])

expect { info.send(:info_formula, formula) }
.to not_to_output(/==> Binaries/).to_stdout
Expand Down
38 changes: 38 additions & 0 deletions Library/Homebrew/test/formula_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1625,6 +1625,44 @@ def post_install; end
end
end

describe "#missing_library_linkage" do
let(:f) do
formula("foo") do
T.bind(self, T.class_of(Formula))
url "foo-1.0"
end
end

it "returns empty when no keg is installed" do
allow(f).to receive(:any_installed_keg).and_return(nil)
expect(f.missing_library_linkage).to eq([[], Set.new])
end

it "returns only the formula's own and orphan libraries, excluding dependency-owned ones" do
keg = instance_double(Keg, directory?: true)
allow(f).to receive(:any_installed_keg).and_return(keg)
linkage_checker = instance_double(
LinkageChecker,
broken_deps: { "foo" => ["libfoo.1.dylib"], "gmp" => ["libgmp.10.dylib"] },
broken_dylibs: Set["liborphan.2.dylib"],
)
allow(LinkageChecker).to receive(:new).and_return(linkage_checker)
expect(f.missing_library_linkage.first).to eq(["libfoo.1.dylib", "liborphan.2.dylib"])
end

it "returns the dependency names that own missing libraries, excluding the formula itself" do
keg = instance_double(Keg, directory?: true)
allow(f).to receive(:any_installed_keg).and_return(keg)
linkage_checker = instance_double(
LinkageChecker,
broken_deps: { "foo" => ["libfoo.1.dylib"], "gmp" => ["libgmp.10.dylib"] },
broken_dylibs: Set.new,
)
allow(LinkageChecker).to receive(:new).and_return(linkage_checker)
expect(f.missing_library_linkage.last).to eq(Set["gmp"])
end
end

specify "requirements" do
# don't try to load/fetch gcc/glibc
allow(DevelopmentTools).to receive_messages(needs_libc_formula?: false, needs_compiler_formula?: false)
Expand Down
38 changes: 27 additions & 11 deletions Library/Homebrew/utils/output.rb
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ def pretty_installed(string)

sig { params(string: String, bold: T::Boolean).returns(String) }
def pretty_upgradable(string, bold: true)
weight = bold ? Tty.bold : ""
weight = bold ? Tty.bold.to_s : ""
if !$stdout.tty?
string
elsif Homebrew::EnvConfig.no_emoji?
Expand Down Expand Up @@ -294,29 +294,45 @@ def pretty_disabled(string)

# Keep status labels, colours and emoji in sync with
# `pretty_uninstalled` in Library/Homebrew/utils.sh.
sig { params(string: String).returns(String) }
def pretty_uninstalled(string)
sig { params(string: String, bold: T::Boolean).returns(String) }
def pretty_uninstalled(string, bold: true)
weight = bold ? Tty.bold.to_s : ""
if !$stdout.tty?
string
elsif Homebrew::EnvConfig.no_emoji?
Formatter.error("#{weight}#{string} (uninstalled)#{Tty.reset}")
else
"#{weight}#{string} #{Formatter.error("✘")}#{Tty.reset}"
end
end

sig { params(string: String, bold: T::Boolean).returns(String) }
def pretty_warning(string, bold: true)
weight = bold ? Tty.bold.to_s : ""
if !$stdout.tty?
string
elsif Homebrew::EnvConfig.no_emoji?
Formatter.error("#{Tty.bold}#{string} (uninstalled)#{Tty.reset}")
Formatter.warning("#{weight}#{string} (warning)#{Tty.reset}")
else
"#{Tty.bold}#{string} #{Formatter.error("✘")}#{Tty.reset}"
"#{weight}#{string} #{Formatter.warning("⚠")}#{Tty.reset}"
end
end

sig {
params(string: String, installed: T::Boolean, outdated: T::Boolean, deprecated: T::Boolean,
disabled: T::Boolean, mark_uninstalled: T::Boolean, bold: T::Boolean).returns(String)
params(string: String, installed: T::Boolean, warning: T::Boolean, outdated: T::Boolean,
deprecated: T::Boolean, disabled: T::Boolean, mark_uninstalled: T::Boolean,
bold: T::Boolean).returns(String)
}
def pretty_install_status(string, installed:, outdated: false, deprecated: false, disabled: false,
mark_uninstalled: true, bold: true)
status = if installed && outdated
def pretty_install_status(string, installed:, warning: false, outdated: false, deprecated: false,
disabled: false, mark_uninstalled: true, bold: true)
status = if warning
pretty_warning(string, bold:)
elsif installed && outdated
pretty_upgradable(string, bold:)
elsif installed
pretty_installed(string)
elsif mark_uninstalled
pretty_uninstalled(string)
pretty_uninstalled(string, bold:)
else
string
end
Expand Down
Loading