upgrade formulae one at a time and surface failed upgrades
brew's batch upgrade --formula errors out on the first broken formula (no bottle for this macOS, deprecated, …), blocking every other pending formula — and the failure only showed in the logs, so the menu silently fell back to "N outdated" as if nothing had run. Formulae now upgrade individually like casks already did, failures of any kind (including a failed reinstall fallback) are collected, and the status line reports them once after the closing refetch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
44705cccae
commit
861c356e80
|
|
@ -47,6 +47,9 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
/// last known outdated list, shown in the Upgrade All confirmation
|
/// last known outdated list, shown in the Upgrade All confirmation
|
||||||
var cachedOutdated: OutdatedPackages = .none
|
var cachedOutdated: OutdatedPackages = .none
|
||||||
var lastNotifiedOutdated: Set<String> = []
|
var lastNotifiedOutdated: Set<String> = []
|
||||||
|
/// packages whose last upgrade attempt failed; reset when an upgrade
|
||||||
|
/// run starts, displayed once by updateStatus after the run's refetch
|
||||||
|
var failedUpgrades: [String] = []
|
||||||
|
|
||||||
let pathMonitor = NWPathMonitor()
|
let pathMonitor = NWPathMonitor()
|
||||||
/// false until the monitor's first update, so the launch refresh waits
|
/// false until the monitor's first update, so the launch refresh waits
|
||||||
|
|
@ -354,12 +357,15 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
|
|
||||||
statusMenuItem.title = "Upgrading..."
|
statusMenuItem.title = "Upgrading..."
|
||||||
setMenuBarIcon("brewbar-updating")
|
setMenuBarIcon("brewbar-updating")
|
||||||
|
failedUpgrades = []
|
||||||
|
|
||||||
runBrew("update") { _, _ in
|
runBrew("update") { _, _ in
|
||||||
// packages that turn outdated only after this brew update are not
|
// packages that turn outdated only after this brew update are not
|
||||||
// in cachedOutdated yet; the final refetch will surface them
|
// in cachedOutdated yet; the final refetch will surface them
|
||||||
|
let formulaNames = formulae ? self.cachedOutdated.formulae.map(\.name) : []
|
||||||
let caskNames = casks ? self.cachedOutdated.casks.map(\.name) : []
|
let caskNames = casks ? self.cachedOutdated.casks.map(\.name) : []
|
||||||
let upgradeCasksThenRefresh = {
|
|
||||||
|
self.upgradeFormulaeSequentially(formulaNames) {
|
||||||
self.upgradeCasksSequentially(caskNames) {
|
self.upgradeCasksSequentially(caskNames) {
|
||||||
self.cleanupAfterUpgradeIfEnabled {
|
self.cleanupAfterUpgradeIfEnabled {
|
||||||
// a manual upgrade just ran; don't chain an auto-upgrade
|
// a manual upgrade just ran; don't chain an auto-upgrade
|
||||||
|
|
@ -367,14 +373,24 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if formulae, !self.cachedOutdated.formulae.isEmpty {
|
/// Formulae also upgrade one at a time: a single broken formula (no
|
||||||
self.runBrew("upgrade --formula") { _, _ in
|
/// bottle for this macOS, deprecated, …) makes brew's batch upgrade
|
||||||
upgradeCasksThenRefresh()
|
/// error out, taking every other pending formula down with it.
|
||||||
|
func upgradeFormulaeSequentially(_ names: [String], completion: @escaping () -> Void) {
|
||||||
|
guard let next = names.first else {
|
||||||
|
completion()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
upgradeCasksThenRefresh()
|
statusMenuItem.title = "Upgrading \(next)..."
|
||||||
|
runBrew("upgrade --formula \(next)", askpassMessage: "upgrade \(next)") { _, success in
|
||||||
|
if !success {
|
||||||
|
self.failedUpgrades.append(next)
|
||||||
}
|
}
|
||||||
|
self.upgradeFormulaeSequentially(Array(names.dropFirst()), completion: completion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -398,12 +414,18 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
/// (An explicitly named cask upgrades even when self-updating, no
|
/// (An explicitly named cask upgrades even when self-updating, no
|
||||||
/// --greedy needed.)
|
/// --greedy needed.)
|
||||||
func upgradeCask(_ name: String, completion: @escaping () -> Void) {
|
func upgradeCask(_ name: String, completion: @escaping () -> Void) {
|
||||||
runBrew("upgrade --cask \(name)", askpassMessage: "upgrade \(name)") { output, _ in
|
runBrew("upgrade --cask \(name)", askpassMessage: "upgrade \(name)") { output, success in
|
||||||
guard output.contains("cannot be upgraded as-is") else {
|
guard output.contains("cannot be upgraded as-is") else {
|
||||||
|
if !success {
|
||||||
|
self.failedUpgrades.append(name)
|
||||||
|
}
|
||||||
completion()
|
completion()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.runBrew("reinstall --cask --force \(name)", askpassMessage: "reinstall \(name)") { _, _ in
|
self.runBrew("reinstall --cask --force \(name)", askpassMessage: "reinstall \(name)") { _, reinstalled in
|
||||||
|
if !reinstalled {
|
||||||
|
self.failedUpgrades.append(name)
|
||||||
|
}
|
||||||
completion()
|
completion()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -507,23 +529,18 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
func runAutoUpgrade(formulae: Bool, casks: Bool) {
|
func runAutoUpgrade(formulae: Bool, casks: Bool) {
|
||||||
statusMenuItem.title = "Auto-upgrading..."
|
statusMenuItem.title = "Auto-upgrading..."
|
||||||
setMenuBarIcon("brewbar-updating")
|
setMenuBarIcon("brewbar-updating")
|
||||||
|
failedUpgrades = []
|
||||||
|
|
||||||
|
let formulaNames = formulae ? cachedOutdated.formulae.map(\.name) : []
|
||||||
let caskNames = casks ? cachedOutdated.casks.map(\.name) : []
|
let caskNames = casks ? cachedOutdated.casks.map(\.name) : []
|
||||||
let upgradeCasksThenRefresh = {
|
|
||||||
|
upgradeFormulaeSequentially(formulaNames) {
|
||||||
self.upgradeCasksSequentially(caskNames) {
|
self.upgradeCasksSequentially(caskNames) {
|
||||||
self.cleanupAfterUpgradeIfEnabled {
|
self.cleanupAfterUpgradeIfEnabled {
|
||||||
self.fetchOutdated(allowAutoUpgrade: false)
|
self.fetchOutdated(allowAutoUpgrade: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if formulae {
|
|
||||||
runBrew("upgrade --formula") { _, _ in
|
|
||||||
upgradeCasksThenRefresh()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
upgradeCasksThenRefresh()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateOutdatedMenu(with outdated: OutdatedPackages) {
|
func updateOutdatedMenu(with outdated: OutdatedPackages) {
|
||||||
|
|
@ -552,7 +569,13 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateStatus(count: Int) {
|
func updateStatus(count: Int) {
|
||||||
if count == 0 {
|
// surface what the last upgrade run could not handle — brew only
|
||||||
|
// reports these in its output, which lands in the logs. Consumed
|
||||||
|
// here so the next plain refresh shows the normal state again.
|
||||||
|
if !failedUpgrades.isEmpty {
|
||||||
|
statusMenuItem.title = "⚠️ Upgrade failed: \(failedUpgrades.joined(separator: ", ")) — see Logs"
|
||||||
|
failedUpgrades = []
|
||||||
|
} else if count == 0 {
|
||||||
statusMenuItem.title = "✅ All up to date"
|
statusMenuItem.title = "✅ All up to date"
|
||||||
} else {
|
} else {
|
||||||
statusMenuItem.title = "⚠️ \(count) outdated"
|
statusMenuItem.title = "⚠️ \(count) outdated"
|
||||||
|
|
@ -564,6 +587,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
|
|
||||||
statusMenuItem.title = "Upgrading \(name)..."
|
statusMenuItem.title = "Upgrading \(name)..."
|
||||||
setMenuBarIcon("brewbar-updating")
|
setMenuBarIcon("brewbar-updating")
|
||||||
|
failedUpgrades = []
|
||||||
|
|
||||||
let finish = {
|
let finish = {
|
||||||
self.cleanupAfterUpgradeIfEnabled {
|
self.cleanupAfterUpgradeIfEnabled {
|
||||||
|
|
@ -576,7 +600,12 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
if cachedOutdated.casks.contains(where: { $0.name == name }) {
|
if cachedOutdated.casks.contains(where: { $0.name == name }) {
|
||||||
upgradeCask(name, completion: finish)
|
upgradeCask(name, completion: finish)
|
||||||
} else {
|
} else {
|
||||||
runBrew("upgrade \(name)", askpassMessage: "upgrade \(name)") { _, _ in finish() }
|
runBrew("upgrade \(name)", askpassMessage: "upgrade \(name)") { _, success in
|
||||||
|
if !success {
|
||||||
|
self.failedUpgrades.append(name)
|
||||||
|
}
|
||||||
|
finish()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue