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
21 changes: 18 additions & 3 deletions Library/Homebrew/cask/upgrade.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,12 @@ def self.greedy_casks
greedy: T.nilable(T::Boolean),
greedy_latest: T.nilable(T::Boolean),
greedy_auto_updates: T.nilable(T::Boolean),
summary_disabled: T.nilable(T::Array[String]),
).returns(T::Array[Cask])
}
def self.outdated_casks(casks, args:, force:, quiet:,
greedy: false, greedy_latest: false, greedy_auto_updates: false)
greedy: false, greedy_latest: false, greedy_auto_updates: false,
summary_disabled: nil)
# Validate mutually exclusive opt-in/opt-out env vars before we start
# selecting casks so `brew upgrade` errors consistently.
Homebrew::EnvConfig.upgrade_auto_updates_casks?
Expand All @@ -41,6 +43,7 @@ def self.outdated_casks(casks, args:, force:, quiet:,
if casks.empty?
Caskroom.casks(config: Config.from_args(args)).select do |cask|
if cask.disabled?
summary_disabled&.push(cask.full_name)
opoo "Not upgrading #{cask.token}, it is #{DeprecateDisable.message(cask)}" unless quiet
next false
end
Expand All @@ -54,6 +57,7 @@ def self.outdated_casks(casks, args:, force:, quiet:,
raise CaskNotInstalledError, cask if !cask.installed? && !force

if cask.disabled?
summary_disabled&.push(cask.full_name)
opoo "Not upgrading #{cask.token}, it is #{DeprecateDisable.message(cask)}" unless quiet
next false
end
Expand Down Expand Up @@ -98,6 +102,9 @@ def self.show_upgrade_summary(cask_upgrades, dry_run: false)
skip_prefetch: T::Boolean,
show_upgrade_summary: T::Boolean,
download_queue: T.nilable(Homebrew::DownloadQueue),
summary_upgrades: T.nilable(T::Array[String]),
summary_deprecated: T.nilable(T::Array[String]),
summary_disabled: T.nilable(T::Array[String]),
).returns(T::Boolean)
}
def self.upgrade_casks!(
Expand All @@ -116,12 +123,16 @@ def self.upgrade_casks!(
require_sha: nil,
skip_prefetch: false,
show_upgrade_summary: true,
download_queue: nil
download_queue: nil,
summary_upgrades: nil,
summary_deprecated: nil,
summary_disabled: nil
)
quarantine = true if quarantine.nil?

outdated_casks =
self.outdated_casks(casks, args:, greedy:, greedy_latest:, greedy_auto_updates:, force:, quiet:)
self.outdated_casks(casks, args:, greedy:, greedy_latest:, greedy_auto_updates:, force:, quiet:,
summary_disabled:)

manual_installer_casks = outdated_casks.select do |cask|
cask.artifacts.any? do |artifact|
Expand Down Expand Up @@ -187,6 +198,10 @@ def self.upgrade_casks!(
cask_upgrades = upgradable_casks.map do |(old_cask, new_cask)|
"#{new_cask.full_name} #{old_cask.version} -> #{new_cask.version}"
end
summary_upgrades&.concat(cask_upgrades)
summary_deprecated&.concat(upgradable_casks.filter_map do |(_, new_cask)|
new_cask.full_name if new_cask.deprecated?
end)

created_download_queue = T.let(false, T::Boolean)
download_queue ||= if !dry_run && !skip_prefetch
Expand Down
114 changes: 113 additions & 1 deletion Library/Homebrew/cmd/upgrade.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ class FormulaeUpgradeContext < T::Struct
const :formulae_to_install, T::Array[Formula]
const :formulae_installer, T::Array[FormulaInstaller]
const :dependants, Homebrew::Upgrade::Dependents
const :pinned_formulae, T::Array[Formula], default: []
end

class FinalUpgradeSummary < T::Struct
prop :version_changes, T::Array[String], default: []
prop :pinned_formulae, T::Array[String], default: []
prop :deprecated, T::Array[String], default: []
prop :disabled, T::Array[String], default: []
prop :source_build_formulae, T::Array[String], default: []
end

cmd_args do
Expand Down Expand Up @@ -147,6 +156,7 @@ def run
prefetched_formulae_upgrades = T.let([], T::Array[String])
prefetched_cask_names = T.let([], T::Array[String])
prefetched_cask_upgrades = T.let([], T::Array[String])
@final_upgrade_summary = T.let(FinalUpgradeSummary.new, T.nilable(FinalUpgradeSummary))

if args.named.present?
args.named.to_formulae_and_casks_and_unavailable(method: :resolve).each do |item|
Expand Down Expand Up @@ -237,6 +247,8 @@ def run
Homebrew::Reinstall.reinstall_pkgconf_if_needed!(dry_run: args.dry_run?)

Homebrew.messages.display_messages(display_times: args.display_times?)

show_final_upgrade_summary
end

private
Expand Down Expand Up @@ -324,7 +336,16 @@ def formulae_upgrade_context(formulae, show_upgrade_summary: true)
verbose: args.verbose?,
)

return if formulae_installer.blank?
if formulae_installer.blank?
return if pinned.blank?

return FormulaeUpgradeContext.new(
formulae_to_install:,
formulae_installer: formulae_installer,
dependants: Homebrew::Upgrade::Dependents.new(upgradeable: [], pinned: [], skipped: []),
pinned_formulae: pinned,
)
end

dependants = Upgrade.dependants(
formulae_to_install,
Expand All @@ -349,9 +370,94 @@ def formulae_upgrade_context(formulae, show_upgrade_summary: true)
formulae_to_install:,
formulae_installer: formulae_installer,
dependants:,
pinned_formulae: pinned,
)
end

sig { returns(FinalUpgradeSummary) }
def final_upgrade_summary
@final_upgrade_summary ||= T.let(FinalUpgradeSummary.new, T.nilable(FinalUpgradeSummary))
@final_upgrade_summary
end

sig { params(context: FormulaeUpgradeContext).void }
def record_formula_upgrade_summary(context)
summary = final_upgrade_summary
summary.version_changes.concat(formula_upgrade_descriptions(context.formulae_installer.map(&:formula)))
summary.version_changes.concat(formula_upgrade_descriptions(context.dependants.upgradeable))
summary.pinned_formulae.concat((context.pinned_formulae + context.dependants.pinned).map do |formula|
"#{formula.full_specified_name} #{formula.pkg_version}"
end)

formulae = context.formulae_to_install + context.pinned_formulae +
context.dependants.upgradeable + context.dependants.pinned
summary.deprecated.concat(formulae.filter_map do |formula|
formula.full_specified_name if formula.deprecated?
end)
summary.disabled.concat(formulae.filter_map do |formula|
formula.full_specified_name if formula.disabled?
end)
summary.source_build_formulae.concat(context.formulae_installer.filter_map do |formula_installer|
formula = formula_installer.formula
next unless formula.core_formula?
next if formula_installer.pour_bottle?

formula.full_specified_name
end)
end

sig { void }
def show_final_upgrade_summary
summary = final_upgrade_summary
return if summary.version_changes.empty? && summary.pinned_formulae.empty? &&
summary.deprecated.empty? && summary.disabled.empty? && summary.source_build_formulae.empty?

if summary.version_changes.present?
version_change_count = summary.version_changes.uniq.count
show_final_upgrade_summary_section(
"#{args.dry_run? ? "Would upgrade" : "Upgraded"} #{version_change_count} outdated " \
"#{Utils.pluralize("package", version_change_count)}",
summary.version_changes,
)
end
if summary.pinned_formulae.present?
pinned_count = summary.pinned_formulae.uniq.count
show_final_upgrade_summary_section(
"#{pinned_count} Pinned #{Utils.pluralize("formula", pinned_count)}",
summary.pinned_formulae,
)
end
deprecate_disable_summary = summary.deprecated.map { |item| "#{item} (deprecated)" } +
summary.disabled.map { |item| "#{item} (disabled)" }
deprecate_disable_count = deprecate_disable_summary.uniq.count
show_final_upgrade_summary_section(
"#{deprecate_disable_count} Deprecated or disabled #{Utils.pluralize("package", deprecate_disable_count)}",
deprecate_disable_summary,
)
source_build_count = summary.source_build_formulae.uniq.count
if args.dry_run?
show_final_upgrade_summary_section(
"#{source_build_count} homebrew/core " \
"#{Utils.pluralize("formula", source_build_count)} that would build from source",
summary.source_build_formulae,
)
else
show_final_upgrade_summary_section(
"#{source_build_count} homebrew/core #{Utils.pluralize("formula", source_build_count)} built from source",
summary.source_build_formulae,
)
end
end

sig { params(title: String, items: T::Array[String]).void }
def show_final_upgrade_summary_section(title, items)
items = items.uniq
return if items.empty?

oh1 title
puts items.join("\n")
end

sig { params(formulae: T::Array[Formula]).returns(T::Array[String]) }
def formula_upgrade_descriptions(formulae)
formulae.map do |formula|
Expand Down Expand Up @@ -406,10 +512,13 @@ def upgrade_outdated_formulae!(formulae, prefetch_only: false, use_prefetched: f
formulae_to_install: context.formulae_to_install,
formulae_installer: valid_formula_installers,
dependants: context.dependants,
pinned_formulae: context.pinned_formulae,
)
return valid_formula_installers.present?
end

record_formula_upgrade_summary(context)

Upgrade.upgrade_formulae(
context.formulae_installer,
dry_run: args.dry_run?,
Expand Down Expand Up @@ -523,6 +632,9 @@ def upgrade_outdated_casks!(casks, skip_prefetch: false, show_upgrade_summary: t
skip_prefetch:,
show_upgrade_summary:,
download_queue:,
summary_upgrades: final_upgrade_summary.version_changes,
summary_deprecated: final_upgrade_summary.deprecated,
summary_disabled: final_upgrade_summary.disabled,
args:,
)
rescue => e
Expand Down
22 changes: 21 additions & 1 deletion Library/Homebrew/test/cask/upgrade_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,24 @@ def write_info_plist(path, short_version:, bundle_version:)
described_class.upgrade_casks!(dry_run: true, args:)
end

it "records final cask upgrade summary details" do
summary_upgrades = []
summary_deprecated = []
allow(local_caffeine).to receive(:deprecated?).and_return(true)

described_class.upgrade_casks!(
local_caffeine,
dry_run: true,
show_upgrade_summary: false,
summary_upgrades:,
summary_deprecated:,
args:,
)

expect(summary_upgrades).to include("local-caffeine 1.2.2 -> 1.2.3")
expect(summary_deprecated).to include("local-caffeine")
end

it "would update only the Casks specified in the command line" do
expect(described_class).not_to receive(:upgrade_cask)
expect(described_class).to receive(:show_upgrade_summary)
Expand Down Expand Up @@ -476,12 +494,14 @@ def write_info_plist(path, short_version:, bundle_version:)
cask = Cask::CaskLoader.load(cask_path("livecheck/livecheck-disabled"))
InstallHelper.stub_cask_installation(cask)
allow(cask).to receive(:outdated?).with(greedy: true).and_return(true)
summary_disabled = []

expect(described_class).not_to receive(:upgrade_cask)

expect do
described_class.upgrade_casks!(cask, dry_run: true, args:)
described_class.upgrade_casks!(cask, dry_run: true, summary_disabled:, args:)
end.to output(/Not upgrading livecheck-disabled, it is disabled/).to_stderr
expect(summary_disabled).to eq(["livecheck-disabled"])
end

context "when an upgrade failed" do
Expand Down
98 changes: 98 additions & 0 deletions Library/Homebrew/test/cmd/upgrade_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -204,5 +204,103 @@
.to not_to_output(/Unexpected method 'discontinued' called during caveats on Cask local-caffeine\./).to_stderr
end

it "prints a narrow final upgrade summary" do
cmd = described_class.new([])
summary = described_class::FinalUpgradeSummary.new(
version_changes: ["testball 0.1 -> 0.2"],
pinned_formulae: ["pinnedball 1.0"],
deprecated: ["oldball"],
disabled: ["disabledball"],
source_build_formulae: ["sourceball"],
)

allow(cmd).to receive(:final_upgrade_summary).and_return(summary)

expect { cmd.send(:show_final_upgrade_summary) }.to output(<<~EOS).to_stdout
==> Upgraded 1 outdated package
testball 0.1 -> 0.2
==> 1 Pinned formula
pinnedball 1.0
==> 2 Deprecated or disabled packages
oldball (deprecated)
disabledball (disabled)
==> 1 homebrew/core formula built from source
sourceball
EOS
end

it "records final formula upgrade summary details" do
formula = formula("testball") do
url "https://brew.sh/testball-0.2"
end
pinned = formula("pinnedball") do
url "https://brew.sh/pinnedball-1.0"
end
deprecated = formula("oldball") do
url "https://brew.sh/oldball-1.0"
deprecate! date: "2020-01-01", because: :unmaintained
end
disabled = formula("disabledball") do
url "https://brew.sh/disabledball-1.0"
disable! date: "2020-01-01", because: :unsupported
end
source_build = formula("sourceball") do
url "https://brew.sh/sourceball-1.0"
end
old_keg = HOMEBREW_CELLAR/"testball/0.1"
old_keg.mkpath
allow(formula).to receive_messages(optlinked?: true, opt_prefix: old_keg)

cmd = described_class.new([])
context = described_class::FormulaeUpgradeContext.new(
formulae_to_install: [formula, deprecated, disabled, source_build],
formulae_installer: [
FormulaInstaller.new(formula),
FormulaInstaller.new(deprecated),
FormulaInstaller.new(disabled),
FormulaInstaller.new(source_build, build_from_source_formulae: [source_build.full_name]),
],
dependants: Homebrew::Upgrade::Dependents.new(upgradeable: [], pinned: [], skipped: []),
pinned_formulae: [pinned],
)

cmd.send(:record_formula_upgrade_summary, context)
summary = cmd.send(:final_upgrade_summary)

expect(summary.version_changes).to include("testball 0.1 -> 0.2")
expect(summary.pinned_formulae).to include("pinnedball 1.0")
expect(summary.deprecated).to include("oldball")
expect(summary.disabled).to include("disabledball")
expect(summary.source_build_formulae).to include("sourceball")
end

it "records formula upgrade versions before upgrading" do
formula = formula("testball") do
url "https://brew.sh/testball-0.2"
end
old_keg = HOMEBREW_CELLAR/"testball/0.1"
new_keg = HOMEBREW_CELLAR/"testball/0.2"
old_keg.mkpath
new_keg.mkpath
allow(formula).to receive_messages(optlinked?: true, opt_prefix: old_keg)
cmd = described_class.new([])

allow(cmd).to receive(:formulae_upgrade_context).and_return(
described_class::FormulaeUpgradeContext.new(
formulae_to_install: [formula],
formulae_installer: [FormulaInstaller.new(formula)],
dependants: Homebrew::Upgrade::Dependents.new(upgradeable: [], pinned: [], skipped: []),
),
)
allow(Homebrew::Upgrade).to receive(:upgrade_formulae) do
allow(formula).to receive(:opt_prefix).and_return(new_keg)
end
allow(Homebrew::Upgrade).to receive(:upgrade_dependents)

cmd.send(:upgrade_outdated_formulae!, [])

expect(cmd.send(:final_upgrade_summary).version_changes).to include("testball 0.1 -> 0.2")
end

it_behaves_like "reinstall_pkgconf_if_needed"
end
Loading