Homebrew / homebrew-bundle

📦 Bundler for non-Ruby dependencies from Homebrew, Homebrew Cask and the Mac App Store.
MIT License
5.33k stars 286 forks source link

brew bundle [install] --cleanup removes dependencies when reading from stdin #1378

Closed motohedgehog closed 5 months ago

motohedgehog commented 5 months ago

Overview

I did my best to find any similar bug reports, but couldn't, so here goes.

I've run into a weird brew bundle behaviour while working on declarative package management in my personal dotfiles repo (I use chezmoi).

How To Reproduce

I could reproduce the problem on a pristine macOS 14.5 running under UTM and a clean Homebrew installation.

Suppose I install one formula and one cask:

$ brew install -q tealdeer homebrew/cask/appcleaner

==> Installing Cask appcleaner
==> Moving App 'AppCleaner.app' to '/Applications/AppCleaner.app'
🍺  appcleaner was successfully installed!
==> Fetching tealdeer
==> Caveats
zsh completions have been installed to:
  /opt/homebrew/share/zsh/site-functions
==> Summary
🍺  /opt/homebrew/Cellar/tealdeer/1.6.1: 13 files, 3.9MB
== > Running `brew cleanup tealdeer`...
Dis able this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).
==> Caveats
==> tealdeer
zsh completions have been installed to:
  /opt/homebrew/share/zsh/site-functions

I then run brew bundle dump and get my Brewfile:

$ brew bundle dump
==> Tapping homebrew/bundle
Cloning into '/opt/homebrew/Library/Taps/homebrew/homebrew-bundle'...
remote: Enumerating objects: 7993, done.
remote: Counting objects: 100% (2078/2078), done.
remote: Compressing objects: 100% (370/370), done.
remote: Total 7993 (delta 1808), reused 1855 (delta 1702), pack-reused 5915
Receiving objects: 100% (7993/7993), 1.92 MiB | 11.21 MiB/s, done.
Resolving deltas: 100% (4683/4683), done.
Tapped 1 command (109 files, 2.4MB).
Using homebrew/bundle
Homebrew Bundle complete! 3 Brewfile dependencies now installed.

At this point, I expect brew bundle [install] --cleanup to be a no-op. At a first glance, it is:

$ brew bundle --cleanup
Using homebrew/bundle
Using tealdeer
Using appcleaner
Homebrew Bundle complete! 3 Brewfile dependencies now installed.

However, things change if I pass the very same Brewfile via stdin:

$ brew bundle --cleanup --file=- < Brewfile
Using homebrew/bundle
Using tealdeer
Using appcleaner
Homebrew Bundle complete! 3 Brewfile dependencies now installed.

##########################
# Why is this happening? #
##########################
==> Uninstalling Cask appcleaner
==> Removing launchctl service net.freemacsoft.AppCleaner-SmartDelete
Password:
==> Backing App 'AppCleaner.app' up to '/opt/homebrew/Caskroom/appcleaner/3.6.8/
==> Removing App '/Applications/AppCleaner.app'
==> Purging files for version 3.6.8 of Cask appcleaner
Uninstalled 1 cask
Uninstalling tealdeer... (13 files, 3.9MB)
Uninstalled 1 formula

Homebrew says Bundle complete, but then immediately proceeds to remove both the formula and the cask.

Running brew bundle again exhibits even more bizarre behaviour as it install the formula and the cask again, and then removes the cask only:

brew bundle --cleanup --file=-
Using homebrew/bundle
Installing tealdeer
Installing appcleaner
Homebrew Bundle complete! 3 Brewfile dependencies now installed.
==> Uninstalling Cask appcleaner
==> Removing launchctl service net.freemacsoft.AppCleaner-SmartDelete
==> Backing App 'AppCleaner.app' up to '/opt/homebrew/Caskroom/appcleaner/3.6.8/
==> Removing App '/Applications/AppCleaner.app'
==> Purging files for version 3.6.8 of Cask appcleaner
Uninstalled 1 cask

Debug Information

$ brew doctor
Your system is ready to brew.
$ brew config
HOMEBREW_VERSION: 4.3.3
ORIGIN: https://github.com/Homebrew/brew
HEAD: e130e47f23b8b806096f9ec4f2c193213b8ec908
Last commit: 4 days ago
Core tap JSON: 06 Jun 20:57 UTC
Core cask tap JSON: 06 Jun 20:57 UTC
HOMEBREW_PREFIX: /opt/homebrew
HOMEBREW_CASK_OPTS: []
HOMEBREW_MAKE_JOBS: 8
Homebrew Ruby: 3.3.2 => /opt/homebrew/Library/Homebrew/vendor/portable-ruby/3.3.2/bin/ruby
CPU: octa-core 64-bit dunno
Clang: 15.0.0 build 1500
Git: 2.39.3 => /Library/Developer/CommandLineTools/usr/bin/git
Curl: 8.6.0 => /usr/bin/curl
macOS: 14.5-arm64
CLT: 15.3.0.0.1.1708646388
Xcode: N/A
Rosetta 2: false

I hope I am just missing something obvious, but so far I've been puzzled, so any help would be appreciated!

jacobbednarz commented 5 months ago

i've got a feeling you're redirection isn't actually going to stdin in time. the < Brewfile won't be evaluated until after the command which means stdin would be empty and the behaviour here makes sense.

what does behaviour cat Brewfile | brew bundle --cleanup --file=- give you?

motohedgehog commented 5 months ago

@jacobbednarz my shell knowledge may be a bit rusty, but I don't think cat file | command vs command < file should make any difference here (a.k.a. "useless use of cat"). I tried you suggestion though, and the behaviour remains the same.

That said, I've got very similar suspicions: an empty Brewfile indeed makes brew bundle [install] --cleanup try and remove any installed packages, so I have a hunch that it may be working in two passes (install, then cleanup) and by the time it reaches the cleanup stage the stdin is already consumed thus making it purge everything. In any case, I don' think it is the expected/desired behaviour.

It's been a while since I did any Ruby so I haven't looked at the implementation TBH.

MikeMcQuaid commented 5 months ago

by the time it reaches the cleanup stage the stdin is already consumed thus making it purge everything

Exactly this. Fixed in #1379. Thanks for the great report @motohedgehog!

jacobbednarz commented 4 months ago

nice find!