Merge pull request #20033 from tyuwags/master

Refine ask‐option dependency resolution and strengthen tests
This commit is contained in:
Mike McQuaid 2025-06-26 10:52:46 +00:00 committed by GitHub
commit b87d2887fe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 287 additions and 218 deletions

View File

@ -310,9 +310,7 @@ module Homebrew
Install.perform_preinstall_checks_once Install.perform_preinstall_checks_once
Install.check_cc_argv(args.cc) Install.check_cc_argv(args.cc)
Install.ask_formulae(installed_formulae, args: args) if args.ask? formulae_installer = Install.formula_installers(
Install.install_formulae(
installed_formulae, installed_formulae,
installed_on_request: !args.as_dependency?, installed_on_request: !args.as_dependency?,
installed_as_dependency: args.as_dependency?, installed_as_dependency: args.as_dependency?,
@ -338,9 +336,10 @@ module Homebrew
skip_link: args.skip_link?, skip_link: args.skip_link?,
) )
Upgrade.check_installed_dependents( dependants = Upgrade.dependants(
installed_formulae, installed_formulae,
flags: args.flags_only, flags: args.flags_only,
ask: args.ask?,
installed_on_request: !args.as_dependency?, installed_on_request: !args.as_dependency?,
force_bottle: args.force_bottle?, force_bottle: args.force_bottle?,
build_from_source_formulae: args.build_from_source_formulae, build_from_source_formulae: args.build_from_source_formulae,
@ -354,6 +353,28 @@ module Homebrew
dry_run: args.dry_run?, dry_run: args.dry_run?,
) )
# Main block: if asking the user is enabled, show dependency and size information.
Install.ask_formulae(formulae_installer, dependants, args: args) if args.ask?
Install.install_formulae(formulae_installer,
dry_run: args.dry_run?,
verbose: args.verbose?)
Upgrade.upgrade_dependents(
dependants, installed_formulae,
flags: args.flags_only,
dry_run: args.dry_run?,
force_bottle: args.force_bottle?,
build_from_source_formulae: args.build_from_source_formulae,
interactive: args.interactive?,
keep_tmp: args.keep_tmp?,
debug_symbols: args.debug_symbols?,
force: args.force?,
debug: args.debug?,
quiet: args.quiet?,
verbose: args.verbose?
)
Cleanup.periodic_clean!(dry_run: args.dry_run?) Cleanup.periodic_clean!(dry_run: args.dry_run?)
Homebrew.messages.display_messages(display_times: args.display_times?) Homebrew.messages.display_messages(display_times: args.display_times?)

View File

@ -130,16 +130,13 @@ module Homebrew
unless formulae.empty? unless formulae.empty?
Install.perform_preinstall_checks_once Install.perform_preinstall_checks_once
# If asking the user is enabled, show dependency and size information. install_context = formulae.map do |formula|
Install.ask_formulae(formulae, args: args) if args.ask?
formulae.each do |formula|
if formula.pinned? if formula.pinned?
onoe "#{formula.full_name} is pinned. You must unpin it to reinstall." onoe "#{formula.full_name} is pinned. You must unpin it to reinstall."
next next
end end
Migrator.migrate_if_needed(formula, force: args.force?) Migrator.migrate_if_needed(formula, force: args.force?)
Homebrew::Reinstall.reinstall_formula( Homebrew::Reinstall.build_install_context(
formula, formula,
flags: args.flags_only, flags: args.flags_only,
force_bottle: args.force_bottle?, force_bottle: args.force_bottle?,
@ -153,12 +150,12 @@ module Homebrew
verbose: args.verbose?, verbose: args.verbose?,
git: args.git?, git: args.git?,
) )
Cleanup.install_formula_clean!(formula)
end end
Upgrade.check_installed_dependents( dependants = Upgrade.dependants(
formulae, formulae,
flags: args.flags_only, flags: args.flags_only,
ask: args.ask?,
force_bottle: args.force_bottle?, force_bottle: args.force_bottle?,
build_from_source_formulae: args.build_from_source_formulae, build_from_source_formulae: args.build_from_source_formulae,
interactive: args.interactive?, interactive: args.interactive?,
@ -169,6 +166,43 @@ module Homebrew
quiet: args.quiet?, quiet: args.quiet?,
verbose: args.verbose?, verbose: args.verbose?,
) )
formulae_installer = install_context.map(&:formula_installer)
# Main block: if asking the user is enabled, show dependency and size information.
Install.ask_formulae(formulae_installer, dependants, args: args) if args.ask?
install_context.each do |f|
Homebrew::Reinstall.reinstall_formula(
f,
flags: args.flags_only,
force_bottle: args.force_bottle?,
build_from_source_formulae: args.build_from_source_formulae,
interactive: args.interactive?,
keep_tmp: args.keep_tmp?,
debug_symbols: args.debug_symbols?,
force: args.force?,
debug: args.debug?,
quiet: args.quiet?,
verbose: args.verbose?,
git: args.git?,
)
Cleanup.install_formula_clean!(f.formula)
end
Upgrade.upgrade_dependents(
dependants, formulae,
flags: args.flags_only,
force_bottle: args.force_bottle?,
build_from_source_formulae: args.build_from_source_formulae,
interactive: args.interactive?,
keep_tmp: args.keep_tmp?,
debug_symbols: args.debug_symbols?,
force: args.force?,
debug: args.debug?,
quiet: args.quiet?,
verbose: args.verbose?
)
end end
if casks.any? if casks.any?

View File

@ -220,10 +220,7 @@ module Homebrew
Install.perform_preinstall_checks_once Install.perform_preinstall_checks_once
# Main block: if asking the user is enabled, show dependency and size information. formulae_installer = Upgrade.formula_installers(
Install.ask_formulae(formulae_to_install, args: args) if args.ask?
Upgrade.upgrade_formulae(
formulae_to_install, formulae_to_install,
flags: args.flags_only, flags: args.flags_only,
dry_run: args.dry_run?, dry_run: args.dry_run?,
@ -239,10 +236,33 @@ module Homebrew
verbose: args.verbose?, verbose: args.verbose?,
) )
Upgrade.check_installed_dependents( dependants = Upgrade.dependants(
formulae_to_install, formulae_to_install,
flags: args.flags_only, flags: args.flags_only,
dry_run: args.dry_run?, dry_run: args.dry_run?,
ask: args.ask?,
force_bottle: args.force_bottle?,
build_from_source_formulae: args.build_from_source_formulae,
interactive: args.interactive?,
keep_tmp: args.keep_tmp?,
debug_symbols: args.debug_symbols?,
force: args.force?,
debug: args.debug?,
quiet: args.quiet?,
verbose: args.verbose?,
)
# Main block: if asking the user is enabled, show dependency and size information.
Install.ask_formulae(formulae_installer, dependants, args: args) if args.ask?
Upgrade.upgrade_formulae(formulae_installer,
dry_run: args.dry_run?,
verbose: args.verbose?)
Upgrade.upgrade_dependents(
dependants, formulae_to_install,
flags: args.flags_only,
dry_run: args.dry_run?,
force_bottle: args.force_bottle?, force_bottle: args.force_bottle?,
build_from_source_formulae: args.build_from_source_formulae, build_from_source_formulae: args.build_from_source_formulae,
interactive: args.interactive?, interactive: args.interactive?,
@ -251,7 +271,7 @@ module Homebrew
force: args.force?, force: args.force?,
debug: args.debug?, debug: args.debug?,
quiet: args.quiet?, quiet: args.quiet?,
verbose: args.verbose?, verbose: args.verbose?
) )
true true

View File

@ -3207,11 +3207,11 @@ class Formula
patchlist.select(&:external?).each(&:fetch) patchlist.select(&:external?).each(&:fetch)
end end
sig { void } sig { params(quiet: T::Boolean).void }
def fetch_bottle_tab def fetch_bottle_tab(quiet: false)
return unless bottled? return unless bottled?
T.must(bottle).fetch_tab T.must(bottle).fetch_tab(quiet: quiet)
end end
sig { returns(T::Hash[String, T.untyped]) } sig { returns(T::Hash[String, T.untyped]) }

View File

@ -1356,12 +1356,12 @@ on_request: installed_on_request?, options:)
end end
end end
sig { void } sig { params(quiet: T::Boolean).void }
def fetch_bottle_tab def fetch_bottle_tab(quiet: false)
return if @fetch_bottle_tab return if @fetch_bottle_tab
begin begin
formula.fetch_bottle_tab formula.fetch_bottle_tab(quiet: quiet)
@bottle_tab_runtime_dependencies = formula.bottle_tab_attributes @bottle_tab_runtime_dependencies = formula.bottle_tab_attributes
.fetch("runtime_dependencies", []).then { |deps| deps || [] } .fetch("runtime_dependencies", []).then { |deps| deps || [] }
.each_with_object({}) { |dep, h| h[dep["full_name"]] = dep } .each_with_object({}) { |dep, h| h[dep["full_name"]] = dep }

View File

@ -231,7 +231,7 @@ module Homebrew
false false
end end
def install_formulae( def formula_installers(
formulae_to_install, formulae_to_install,
installed_on_request: true, installed_on_request: true,
installed_as_dependency: false, installed_as_dependency: false,
@ -256,11 +256,11 @@ module Homebrew
skip_post_install: false, skip_post_install: false,
skip_link: false skip_link: false
) )
formula_installers = formulae_to_install.filter_map do |formula| formulae_to_install.filter_map do |formula|
Migrator.migrate_if_needed(formula, force:, dry_run:) Migrator.migrate_if_needed(formula, force:, dry_run:)
build_options = formula.build build_options = formula.build
formula_installer = FormulaInstaller.new( FormulaInstaller.new(
formula, formula,
options: build_options.used_options, options: build_options.used_options,
installed_on_request:, installed_on_request:,
@ -285,24 +285,36 @@ module Homebrew
skip_post_install:, skip_post_install:,
skip_link:, skip_link:,
) )
begin
unless dry_run
formula_installer.prelude
formula_installer.fetch
end
formula_installer
rescue CannotInstallFormulaError => e
ofail e.message
nil
rescue UnsatisfiedRequirements, DownloadError, ChecksumMismatchError => e
ofail "#{formula}: #{e}"
nil
end
end end
end
def install_formulae(
formula_installers,
installed_on_request: true,
installed_as_dependency: false,
build_bottle: false,
force_bottle: false,
bottle_arch: nil,
ignore_deps: false,
only_deps: false,
include_test_formulae: [],
build_from_source_formulae: [],
cc: nil,
git: false,
interactive: false,
keep_tmp: false,
debug_symbols: false,
force: false,
overwrite: false,
debug: false,
quiet: false,
verbose: false,
dry_run: false,
skip_post_install: false,
skip_link: false
)
if dry_run if dry_run
if (formulae_name_to_install = formulae_to_install.map(&:name)) if (formulae_name_to_install = formula_installers.map(&:name))
ohai "Would install #{Utils.pluralize("formula", formulae_name_to_install.count, ohai "Would install #{Utils.pluralize("formula", formulae_name_to_install.count,
plural: "e", include_count: true)}:" plural: "e", include_count: true)}:"
puts formulae_name_to_install.join(" ") puts formulae_name_to_install.join(" ")
@ -315,6 +327,18 @@ module Homebrew
end end
formula_installers.each do |fi| formula_installers.each do |fi|
begin
unless dry_run
fi.prelude
fi.fetch
end
rescue CannotInstallFormulaError => e
ofail e.message
next
rescue UnsatisfiedRequirements, DownloadError, ChecksumMismatchError => e
ofail "#{formula}: #{e}"
next
end
install_formula(fi) install_formula(fi)
Cleanup.install_formula_clean!(fi.formula) Cleanup.install_formula_clean!(fi.formula)
end end
@ -330,16 +354,17 @@ module Homebrew
end end
# If asking the user is enabled, show dependency and size information. # If asking the user is enabled, show dependency and size information.
def ask_formulae(formulae, args:) def ask_formulae(formulae_installer, dependants, args:)
return if formulae.empty? return if formulae_installer.empty?
formulae = collect_dependencies(formulae_installer, dependants)
ohai "Looking for bottles..." ohai "Looking for bottles..."
sized_formulae = compute_sized_formulae(formulae, args: args) sizes = compute_total_sizes(formulae, debug: args.debug?)
sizes = compute_total_sizes(sized_formulae, debug: args.debug?)
puts "#{::Utils.pluralize("Formula", sized_formulae.count, plural: "e")} \ puts "#{::Utils.pluralize("Formula", formulae.count, plural: "e")} \
(#{sized_formulae.count}): #{sized_formulae.join(", ")}\n\n" (#{formulae.count}): #{formulae.join(", ")}\n\n"
puts "Download Size: #{disk_usage_readable(sizes[:download])}" puts "Download Size: #{disk_usage_readable(sizes[:download])}"
puts "Install Size: #{disk_usage_readable(sizes[:installed])}" puts "Install Size: #{disk_usage_readable(sizes[:installed])}"
puts "Net Install Size: #{disk_usage_readable(sizes[:net])}" if sizes[:net] != 0 puts "Net Install Size: #{disk_usage_readable(sizes[:net])}" if sizes[:net] != 0
@ -411,41 +436,6 @@ module Homebrew
end end
end end
# Build a unique list of formulae to size by including:
# 1. The original formulae to install.
# 2. Their outdated dependents (subject to pruning criteria).
# 3. Optionally, any installed formula that depends on one of these and is outdated.
def compute_sized_formulae(formulae, args:)
sized_formulae = formulae.flat_map do |formula|
# Always include the formula itself.
formula_list = [formula]
deps = args.build_from_source? ? formula.deps.build : formula.deps.required
outdated_dependents = deps.map(&:to_formula).reject(&:pinned?).select do |dep|
dep.installed_kegs.empty? || (dep.bottled? && dep.outdated?)
end
deps.map(&:to_formula).each do |f|
outdated_dependents.concat(f.recursive_dependencies.map(&:to_formula).reject(&:pinned?).select do |dep|
dep.installed_kegs.empty? || (dep.bottled? && dep.outdated?)
end)
end
formula_list.concat(outdated_dependents)
formula_list
end
# Add any installed formula that depends on one of the sized formulae and is outdated.
unless Homebrew::EnvConfig.no_installed_dependents_check?
sized_formulae.concat(Formula.installed.select do |installed_formula|
installed_formula.bottled? && installed_formula.outdated? &&
installed_formula.deps.required.map(&:to_formula).intersect?(sized_formulae)
end)
end
sized_formulae.uniq(&:to_s).compact
end
# Compute the total sizes (download, installed, and net) for the given formulae. # Compute the total sizes (download, installed, and net) for the given formulae.
def compute_total_sizes(sized_formulae, debug: false) def compute_total_sizes(sized_formulae, debug: false)
total_download_size = 0 total_download_size = 0
@ -471,6 +461,16 @@ module Homebrew
installed: total_installed_size, installed: total_installed_size,
net: total_net_size } net: total_net_size }
end end
def collect_dependencies(formulae_installer, dependants)
formulae_dependencies = formulae_installer.flat_map do |f|
[f.formula, f.compute_dependencies.flatten.filter do |c|
c.is_a? Dependency
end.flat_map(&:to_formula)]
end.flatten.uniq
formulae_dependencies.concat(dependants.upgradeable) if dependants&.upgradeable
formulae_dependencies
end
end end
end end
end end

View File

@ -7,7 +7,9 @@ require "messages"
module Homebrew module Homebrew
module Reinstall module Reinstall
def self.reinstall_formula( # struct to keep context of the current installer, keg, formula and option
InstallationContext = Struct.new(:formula_installer, :keg, :formula, :options)
def self.build_install_context(
formula, formula,
flags:, flags:,
force_bottle: false, force_bottle: false,
@ -61,16 +63,38 @@ module Homebrew
verbose:, verbose:,
}.compact, }.compact,
) )
fi.prelude InstallationContext.new(fi, keg, formula, options)
fi.fetch end
def self.reinstall_formula(
install_context,
flags:,
force_bottle: false,
build_from_source_formulae: [],
interactive: false,
keep_tmp: false,
debug_symbols: false,
force: false,
debug: false,
quiet: false,
verbose: false,
git: false
)
formula_installer = install_context.formula_installer
keg = install_context.keg
formula = install_context.formula
options = install_context.options
link_keg = keg.linked?
formula_installer.prelude
formula_installer.fetch
oh1 "Reinstalling #{Formatter.identifier(formula.full_name)} #{options.to_a.join " "}" oh1 "Reinstalling #{Formatter.identifier(formula.full_name)} #{options.to_a.join " "}"
fi.install formula_installer.install
fi.finish formula_installer.finish
rescue FormulaInstallationAlreadyAttemptedError rescue FormulaInstallationAlreadyAttemptedError
nil nil
# Any other exceptions we want to restore the previous keg and report the error. # Any other exceptions we want to restore the previous keg and report the error.
rescue Exception # rubocop:disable Lint/RescueException rescue Exception # rubocop:disable Lint/RescueException
ignore_interrupts { restore_backup(keg, link_keg, verbose:) } ignore_interrupts { restore_backup(keg, link_keg, verbose:) }
raise raise

View File

@ -95,34 +95,4 @@ RSpec.describe Homebrew::Cmd::InstallCmd do
expect(HOMEBREW_CELLAR/"testball1/0.1/bin/test").to be_a_file expect(HOMEBREW_CELLAR/"testball1/0.1/bin/test").to be_a_file
end end
it "installs with asking for user prompts with installed dependent checks", :integration_test do
setup_test_formula "testball1", <<~RUBY
depends_on "testball5"
# should work as its not building but test doesnt pass if dependant
# depends_on "build" => :build
depends_on "installed"
RUBY
setup_test_formula "installed"
setup_test_formula "testball5", <<~RUBY
depends_on "testball4"
RUBY
setup_test_formula "testball4", ""
setup_test_formula "hiop"
setup_test_formula "build"
# Mock `Formula#any_version_installed?` by creating the tab in a plausible keg directory
keg_dir = HOMEBREW_CELLAR/"installed"/"1.0"
keg_dir.mkpath
touch keg_dir/AbstractTab::FILENAME
expect do
brew "install", "--ask", "testball1"
end.to output(/.*Formulae\s*\(3\):\s*testball1\s*,?\s*testball5\s*,?\s*testball4.*/).to_stdout
.and not_to_output.to_stderr
expect(HOMEBREW_CELLAR/"testball1/0.1/bin/test").to be_a_file
expect(HOMEBREW_CELLAR/"testball4/0.1/bin/testball4").to be_a_file
expect(HOMEBREW_CELLAR/"testball5/0.1/bin/testball5").to be_a_file
end
end end

View File

@ -28,48 +28,4 @@ RSpec.describe Homebrew::Cmd::UpgradeCmd do
expect(HOMEBREW_CELLAR/"testball/0.1").to be_a_directory expect(HOMEBREW_CELLAR/"testball/0.1").to be_a_directory
expect(HOMEBREW_CELLAR/"testball/0.0.1").not_to exist expect(HOMEBREW_CELLAR/"testball/0.0.1").not_to exist
end end
it "upgrades with asking for user prompts with dependants checks", :integration_test do
setup_test_formula "testball", <<~RUBY
depends_on "testball5"
# should work as its not building but test doesnt pass if dependant
# depends_on "build" => :build
depends_on "installed"
RUBY
setup_test_formula "installed"
setup_test_formula "testball5", <<~RUBY
depends_on "testball4"
RUBY
setup_test_formula "testball4"
setup_test_formula "hiop"
setup_test_formula "build"
(HOMEBREW_CELLAR/"testball/0.0.1/foo").mkpath
(HOMEBREW_CELLAR/"testball5/0.0.1/foo").mkpath
(HOMEBREW_CELLAR/"testball4/0.0.1/foo").mkpath
keg_dir = HOMEBREW_CELLAR/"installed"/"1.0"
keg_dir.mkpath
touch keg_dir/AbstractTab::FILENAME
regex = /
Formulae\s*\(3\):\s*
(testball|testball5|testball4)
\s*,\s*
((?!\1)testball|testball5|testball4)
\s*,\s*
((?!\1|\2)testball|testball5|testball4)
/x
expect do
brew "upgrade", "--ask"
end.to output(regex)
.to_stdout.and not_to_output.to_stderr
expect(HOMEBREW_CELLAR/"testball/0.1").to be_a_directory
expect(HOMEBREW_CELLAR/"testball/0.0.1").not_to exist
expect(HOMEBREW_CELLAR/"testball5/0.1").to be_a_directory
expect(HOMEBREW_CELLAR/"testball5/0.0.1").not_to exist
expect(HOMEBREW_CELLAR/"testball4/0.1").to be_a_directory
expect(HOMEBREW_CELLAR/"testball4/0.0.1").not_to exist
end
end end

View File

@ -135,21 +135,13 @@ RSpec.shared_context "integration test" do # rubocop:disable RSpec/ContextWordin
bottle_block: nil, tab_attributes: nil) bottle_block: nil, tab_attributes: nil)
case name case name
when /^testball/ when /^testball/
case name # Use a different tarball for testball2 to avoid lock errors when writing concurrency tests
when "testball4", "testball5" prefix = (name == "testball2") ? "testball2" : "testball"
prefix = name tarball = if OS.linux?
program_name = name TEST_FIXTURE_DIR/"tarballs/#{prefix}-0.1-linux.tbz"
when "testball2"
prefix = name
program_name = "test"
else else
prefix = "testball" TEST_FIXTURE_DIR/"tarballs/#{prefix}-0.1.tbz"
program_name = "test"
end end
tarball_name = "#{prefix}-0.1#{"-linux" if OS.linux?}.tbz"
tarball = TEST_FIXTURE_DIR / "tarballs/#{tarball_name}"
content = <<~RUBY content = <<~RUBY
desc "Some test" desc "Some test"
homepage "https://brew.sh/#{name}" homepage "https://brew.sh/#{name}"
@ -159,12 +151,12 @@ RSpec.shared_context "integration test" do # rubocop:disable RSpec/ContextWordin
option "with-foo", "Build with foo" option "with-foo", "Build with foo"
#{bottle_block} #{bottle_block}
def install def install
(prefix/"foo"/"#{program_name}").write("#{program_name}") if build.with? "foo" (prefix/"foo"/"test").write("test") if build.with? "foo"
prefix.install Dir["*"] prefix.install Dir["*"]
(buildpath/"#{program_name}.c").write \ (buildpath/"test.c").write \
"#include <stdio.h>\\nint main(){printf(\\"#{program_name}\\");return 0;}" "#include <stdio.h>\\nint main(){printf(\\"test\\");return 0;}"
bin.mkpath bin.mkpath
system ENV.cc, "#{program_name}.c", "-o", bin/"#{program_name}" system ENV.cc, "test.c", "-o", bin/"test"
end end
#{content} #{content}

View File

@ -11,7 +11,9 @@ require "utils/topological_hash"
module Homebrew module Homebrew
# Helper functions for upgrading formulae. # Helper functions for upgrading formulae.
module Upgrade module Upgrade
def self.upgrade_formulae( Dependents = Struct.new(:upgradeable, :pinned, :skipped)
def self.formula_installers(
formulae_to_install, formulae_to_install,
flags:, flags:,
dry_run: false, dry_run: false,
@ -48,7 +50,7 @@ module Homebrew
raise CyclicDependencyError, dependency_graph.strongly_connected_components if Homebrew::EnvConfig.developer? raise CyclicDependencyError, dependency_graph.strongly_connected_components if Homebrew::EnvConfig.developer?
end end
formula_installers = formulae_to_install.filter_map do |formula| formulae_to_install.filter_map do |formula|
Migrator.migrate_if_needed(formula, force:, dry_run:) Migrator.migrate_if_needed(formula, force:, dry_run:)
begin begin
fi = create_formula_installer( fi = create_formula_installer(
@ -65,18 +67,19 @@ module Homebrew
quiet:, quiet:,
verbose:, verbose:,
) )
unless dry_run fi.fetch_bottle_tab(quiet: !debug)
fi.prelude
all_runtime_deps_installed = fi.bottle_tab_runtime_dependencies.presence&.all? do |dependency, hash|
minimum_version = Version.new(hash["version"]) if hash["version"].present?
Dependency.new(dependency).installed?(minimum_version:, minimum_revision: hash["revision"])
end
if !dry_run && dependents && all_runtime_deps_installed
# Don't need to install this bottle if all of the runtime # Don't need to install this bottle if all of the runtime
# dependencies have the same or newer version already installed. # dependencies have the same or newer version already installed.
next if dependents && fi.bottle_tab_runtime_dependencies.presence&.all? do |dependency, hash| next
minimum_version = Version.new(hash["version"]) if hash["version"].present?
Dependency.new(dependency).installed?(minimum_version:, minimum_revision: hash["revision"])
end
fi.fetch
end end
fi fi
rescue CannotInstallFormulaError => e rescue CannotInstallFormulaError => e
ofail e ofail e
@ -86,6 +89,19 @@ module Homebrew
nil nil
end end
end end
end
def self.upgrade_formulae(formula_installers, dry_run: false, verbose: false)
unless dry_run
formula_installers.each do |fi|
fi.prelude
fi.fetch
rescue CannotInstallFormulaError => e
ofail e
rescue UnsatisfiedRequirements, DownloadError => e
ofail "#{fi.formula.full_name}: #{e}"
end
end
formula_installers.each do |fi| formula_installers.each do |fi|
upgrade_formula(fi, dry_run:, verbose:) upgrade_formula(fi, dry_run:, verbose:)
@ -250,10 +266,11 @@ module Homebrew
@puts_no_installed_dependents_check_disable_message_if_not_already = true @puts_no_installed_dependents_check_disable_message_if_not_already = true
end end
def self.check_installed_dependents( def self.dependants(
formulae, formulae,
flags:, flags:,
dry_run: false, dry_run: false,
ask: false,
installed_on_request: false, installed_on_request: false,
force_bottle: false, force_bottle: false,
build_from_source_formulae: [], build_from_source_formulae: [],
@ -274,35 +291,26 @@ module Homebrew
end end
return return
end end
formulae_to_install = formulae.dup
formulae_to_install.reject! { |f| f.core_formula? && f.versioned_formula? }
return if formulae_to_install.empty?
installed_formulae = (dry_run ? formulae : FormulaInstaller.installed.to_a).dup already_broken_dependents = check_broken_dependents(formulae_to_install)
installed_formulae.reject! { |f| f.core_formula? && f.versioned_formula? }
return if installed_formulae.empty?
already_broken_dependents = check_broken_dependents(installed_formulae)
# TODO: this should be refactored to use FormulaInstaller new logic # TODO: this should be refactored to use FormulaInstaller new logic
outdated_dependents = outdated_dependents =
installed_formulae.flat_map(&:runtime_installed_formula_dependents) formulae_to_install.flat_map(&:runtime_installed_formula_dependents)
.uniq .uniq
.select(&:outdated?) .select(&:outdated?)
# Ensure we never attempt a source build for outdated dependents of upgraded formulae. # Ensure we never attempt a source build for outdated dependents of upgraded formulae.
outdated_dependents, skipped_dependents = outdated_dependents.partition do |dependent| outdated_dependents, skipped_dependents = outdated_dependents.partition do |dependent|
dependent.bottled? && dependent.deps.map(&:to_formula).all?(&:bottled?) dependent.bottled? && dependent.deps.map(&:to_formula).all?(&:bottled?)
end end
if skipped_dependents.present?
opoo <<~EOS
The following dependents of upgraded formulae are outdated but will not
be upgraded because they are not bottled:
#{skipped_dependents * "\n "}
EOS
end
return if outdated_dependents.blank? && already_broken_dependents.blank? return if outdated_dependents.blank? && already_broken_dependents.blank?
outdated_dependents -= installed_formulae if dry_run outdated_dependents -= formulae_to_install if dry_run
upgradeable_dependents = upgradeable_dependents =
outdated_dependents.reject(&:pinned?) outdated_dependents.reject(&:pinned?)
@ -311,24 +319,52 @@ module Homebrew
outdated_dependents.select(&:pinned?) outdated_dependents.select(&:pinned?)
.sort { |a, b| depends_on(a, b) } .sort { |a, b| depends_on(a, b) }
if pinned_dependents.present? Dependents.new(upgradeable_dependents, pinned_dependents, skipped_dependents)
plural = Utils.pluralize("dependent", pinned_dependents.count) end
opoo "Not upgrading #{pinned_dependents.count} pinned #{plural}:"
puts(pinned_dependents.map do |f| def self.upgrade_dependents(deps, formulae,
flags:,
dry_run: false,
installed_on_request: false,
force_bottle: false,
build_from_source_formulae: [],
interactive: false,
keep_tmp: false,
debug_symbols: false,
force: false,
debug: false,
quiet: false,
verbose: false)
return if deps.blank?
upgradeable = deps.upgradeable
pinned = deps.pinned
skipped = deps.skipped
if pinned.present?
plural = Utils.pluralize("dependent", pinned.count)
opoo "Not upgrading #{pinned.count} pinned #{plural}:"
puts(pinned.map do |f|
"#{f.full_specified_name} #{f.pkg_version}" "#{f.full_specified_name} #{f.pkg_version}"
end.join(", ")) end.join(", "))
end end
if skipped.present?
opoo <<~EOS
The following dependents of upgraded formulae are outdated but will not
be upgraded because they are not bottled:
#{skipped * "\n "}
EOS
end
# Print the upgradable dependents. # Print the upgradable dependents.
if upgradeable_dependents.blank? if upgradeable.blank?
ohai "No outdated dependents to upgrade!" unless dry_run ohai "No outdated dependents to upgrade!" unless dry_run
else else
installed_formulae = (dry_run ? formulae : FormulaInstaller.installed.to_a).dup
formula_plural = Utils.pluralize("formula", installed_formulae.count, plural: "e") formula_plural = Utils.pluralize("formula", installed_formulae.count, plural: "e")
upgrade_verb = dry_run ? "Would upgrade" : "Upgrading" upgrade_verb = dry_run ? "Would upgrade" : "Upgrading"
ohai "#{upgrade_verb} #{Utils.pluralize("dependent", upgradeable_dependents.count, ohai "#{upgrade_verb} #{Utils.pluralize("dependent", upgradeable.count,
include_count: true)} of upgraded #{formula_plural}:" include_count: true)} of upgraded #{formula_plural}:"
Upgrade.puts_no_installed_dependents_check_disable_message_if_not_already! Upgrade.puts_no_installed_dependents_check_disable_message_if_not_already!
formulae_upgrades = upgradeable_dependents.map do |f| formulae_upgrades = upgradeable.map do |f|
name = f.full_specified_name name = f.full_specified_name
if f.optlinked? if f.optlinked?
"#{name} #{Keg.new(f.opt_prefix).version} -> #{f.pkg_version}" "#{name} #{Keg.new(f.opt_prefix).version} -> #{f.pkg_version}"
@ -340,8 +376,8 @@ module Homebrew
end end
unless dry_run unless dry_run
upgrade_formulae( dependent_installers = formula_installers(
upgradeable_dependents, upgradeable,
flags:, flags:,
force_bottle:, force_bottle:,
build_from_source_formulae:, build_from_source_formulae:,
@ -354,6 +390,9 @@ module Homebrew
quiet:, quiet:,
verbose:, verbose:,
) )
puts "here", dependent_installers
puts "here", upgradeable
upgrade_formulae(dependent_installers, dry_run: dry_run, verbose: verbose)
end end
# Update installed formulae after upgrading # Update installed formulae after upgrading
@ -409,7 +448,7 @@ module Homebrew
return if dry_run return if dry_run
reinstallable_broken_dependents.each do |formula| reinstallable_broken_dependents.each do |formula|
Reinstall.reinstall_formula( formula_installer = Reinstall.build_install_context(
formula, formula,
flags:, flags:,
force_bottle:, force_bottle:,
@ -422,6 +461,19 @@ module Homebrew
quiet:, quiet:,
verbose:, verbose:,
) )
Reinstall.reinstall_formula(
formula_installer,
flags:,
force_bottle:,
build_from_source_formulae:,
interactive:,
keep_tmp:,
debug_symbols:,
force:,
debug:,
quiet:,
verbose:,
)
rescue FormulaInstallationAlreadyAttemptedError rescue FormulaInstallationAlreadyAttemptedError
# We already attempted to reinstall f as part of the dependency tree of # We already attempted to reinstall f as part of the dependency tree of
# another formula. In that case, don't generate an error, just move on. # another formula. In that case, don't generate an error, just move on.