Skip to content

[bin+brew] brew-upgrade-safe: Explore safer brew upgrade helper to prevent cascading dependent upgrades #33

@0xdevalias

Description

@0xdevalias

This issue explores an experimental helper script (bin/brew-upgrade-safe) designed to reduce the risk of unexpected upgrade cascades in Homebrew. When widely-depended-on packages (like python, openssl, etc.) are upgraded, they can trigger upgrades of many other formulae.

The goal of this script is to intercept that situation by checking for outdated dependencies and reverse-dependents before proceeding. It prompts the user when an upgrade could cascade into multiple dependent upgrades, offering a chance to cancel instead of being surprised.

Over time, this could evolve into a more robust external command aligned with Homebrew’s extension practices.

As one motivation; I often like to review the CHANGELOGs when upgrading versions of things; and the current Homebrew method of 'upgrade all the things!' doesn't really play nicely with that a lot of the time. See the following issue for a more specific solution to this:

Current Local Hacky WIP

bin/brew-upgrade-safe:

#!/usr/bin/env zsh

# ChatGPT Sources:
#   4o           : https://chatgpt.com/c/67abebae-df3c-8008-be03-f7e0a1de992c
#   o3-mini-high : https://chatgpt.com/c/67abf2d3-306c-8008-bf21-edc1440ee692

# TODO: This script is currently a bit of a hacky WIP

# TODO: Potentially relevant tabs I had open:
#   https://www.google.com/search?q=HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK.&oq=HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK.&gs_lcrp=EgZjaHJvbWUyBggAEEUYOdIBBzUzMGowajGoAgCwAgA&sourceid=chrome&ie=UTF-8
#   https://github.com/Homebrew/brew/issues/9285
#   https://apple.stackexchange.com/questions/422995/running-homebrew-upgrade-specific-formulae-upgraded-a-lot-more-why
#   https://chatgpt.com/c/67abebae-df3c-8008-be03-f7e0a1de992c
#   https://docs.brew.sh/External-Commands
#   https://github.com/Homebrew/brew/blob/cf7def0c68903814c6b4e04a55fe8f3cb3f5605e/Library/Homebrew/cmd/update.sh#L1-L10

# TODO: Potentially relevant:
#   ⇒ brew deps --installed --include-build yt-dlp
#   ca-certificates
#   certifi
#   expat
#   mpdecimal
#   openssl@3
#   pkgconf
#   python@3.12
#   python@3.13
#   readline
#   sqlite
#   xz

# ⇒ brew uses -h
# Usage: brew uses [options] formula [...]
#
# Show formulae and casks that specify formula as a dependency; that is, show
# dependents of formula. When given multiple formula arguments, show the
# intersection of formulae that use formula. By default, uses shows all
# formulae and casks that specify formula as a required or recommended
# dependency for their stable builds.
#
# Note: --missing and --skip-recommended have precedence over --include-*.
#
#       --recursive                  Resolve more than one level of dependencies.
#       --installed                  Only list formulae and casks that are
#                                    currently installed.
#       --missing                    Only list formulae and casks that are not
#                                    currently installed.
#       --eval-all                   Evaluate all available formulae and casks,
#                                    whether installed or not, to show their
#                                    dependents.
#       --include-implicit           Include formulae that have formula as an
#                                    implicit dependency for downloading and
#                                    unpacking source files.
#       --include-build              Include formulae that specify formula as a
#                                    :build dependency.
#       --include-test               Include formulae that specify formula as a
#                                    :test dependency.
#       --include-optional           Include formulae that specify formula as an
#                                    :optional dependency.
#       --skip-recommended           Skip all formulae that specify formula as a
#                                    :recommended dependency.
#       --formula, --formulae        Include only formulae.
#       --cask, --casks              Include only casks.
#   -d, --debug                      Display any debugging information.
#   -q, --quiet                      Make some output more quiet.
#   -v, --verbose                    Make some output more verbose.
#   -h, --help                       Show this message.

#:  * `upgrade-safe` <package>
#:
#:  Check if a package has outdated dependencies and if upgrading it will trigger other upgrades.
#:  If other installed packages depend on those outdated dependencies, prompt before upgrading.
#:
#:  Examples:
#:    brew safe-upgrade yt-dlp
#:
#:  Options:
#:      -h, --help    Show this help message.

# Ensure a package name is provided
if [[ -z "$1" ]]; then
  echo "Usage: $0 <package>"
  exit 1
fi

package="$1"

echo "Checking if any dependencies of $package are outdated..."
deps_to_upgrade=($(brew deps --installed --include-build --include-test "$package" | xargs brew outdated --formula | awk '{print $1}'))

if [[ ${#deps_to_upgrade[@]} -eq 0 ]]; then
  echo "No dependencies of $package are outdated. Safe to upgrade."
  brew upgrade "$package"
  exit 0
fi

echo "The following dependencies of $package are outdated and will be upgraded:"
printf '  - %s\n' "${deps_to_upgrade[@]}"

echo "Checking for installed packages that depend on these dependencies..."
affected_packages=($(brew uses --installed --recursive "${deps_to_upgrade[@]}" | sort -u))

if [[ ${#affected_packages[@]} -eq 0 ]]; then
  echo "No other installed packages depend on the outdated dependencies. Safe to upgrade."
  brew upgrade "$package"
  exit 0
fi

echo "The following installed packages depend on outdated dependencies:"
printf '  - %s\n' "${affected_packages[@]}"

read -p "Do you still want to upgrade $package? (y/N) " choice
if [[ "$choice" =~ ^[Yy]$ ]]; then
  brew upgrade "$package"
else
  echo "Upgrade cancelled."
fi

See Also

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions