From 5d0854b1522aec05079fe44ccc94e14d573a3f9a Mon Sep 17 00:00:00 2001 From: Nick LaMuro Date: Mon, 18 Mar 2019 13:33:59 -0500 Subject: [PATCH] [homebrew] check for existing packages first When the `brew` `$package_manager` is detected and used for `ruby-install`, it will attempt two things when installing dependencies: - `$ brew install $@` - `$ brew upgrade $@` The first will not install packages that already exist (will installs ones that do not), and will raise an error code if any packages attempting to be installed are already installed. The second will be triggered on a non-zero exit code of the first and upgrade all packages and dependencies of those packages that need it. While on the surface, this seems okay since newer dependencies are usually a good thing, in practice this usually leads to "bricking" all other older ruby installations since `readline` is usually upgraded as a result. Which in a dev environment, is probably not necessary, and leads to the following when attempting to use `irb`: $ irb ~/.rubies/ruby-2.3.3/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb:55:in `require': dlopen(~/.rubies/ruby-2.3.3/lib/ruby/2.3.0/x86_64-darwin15/readline.bundle, 9): Library not loaded: /usr/local/opt/readline/lib/libreadline.7.dylib (LoadError) Referenced from: ~/.rubies/ruby-2.3.3/lib/ruby/2.3.0/x86_64-darwin15/readline.bundle Reason: image not found - ~/.rubies/ruby-2.3.3/lib/ruby/2.3.0/x86_64-darwin15/readline.bundle from ~/.rubies/ruby-2.3.3/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb:55:in `require' from ~/.rubies/ruby-2.3.3/lib/ruby/2.3.0/irb/ext/save-history.rb:12:in `' from ~/.rubies/ruby-2.3.3/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb:55:in `require' from ~/.rubies/ruby-2.3.3/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb:55:in `require' from ~/.rubies/ruby-2.3.3/lib/ruby/2.3.0/irb/extend-command.rb:243:in `save_history=' from ~/.rubies/ruby-2.3.3/lib/ruby/2.3.0/irb/context.rb:91:in `initialize' from ~/.rubies/ruby-2.3.3/lib/ruby/2.3.0/irb.rb:426:in `new' from ~/.rubies/ruby-2.3.3/lib/ruby/2.3.0/irb.rb:426:in `initialize' from ~/.rubies/ruby-2.3.3/lib/ruby/2.3.0/irb.rb:383:in `new' from ~/.rubies/ruby-2.3.3/lib/ruby/2.3.0/irb.rb:383:in `start' from ~/.rubies/ruby-2.3.3/bin/irb:11:in `
' After a re-install (for the example above: `$ ruby-install ruby-2.3.3`), running `irb` them greets you with the following: $ irb Ignoring bcrypt-3.1.12 because its extensions are not built. Try: gem pristine bcrypt --version 3.1.12 Ignoring bindex-0.5.0 because its extensions are not built. Try: gem pristine bindex --version 0.5.0 Ignoring bootsnap-1.1.2 because its extensions are not built. Try: gem pristine bootsnap --version 1.1.2 Ignoring byebug-10.0.2 because its extensions are not built. Try: gem pristine byebug --version 10.0.2 Ignoring curses-1.0.2 because its extensions are not built. Try: gem pristine curses --version 1.0.2 ---- x45 other gems in my case ---- irb(main):001:0> The Fix: Install missing packages only --------------------------------------- Like the `pacman` package manager case below this, this code adds additional steps to determine the `$missing_pkgs`, and only attempts to install those. Unfortunately, `brew` lacks the functionality to accurately print out a list of missing formule or only install missing from a list, so the patch here does it manually. Alternate approach (not taken) ------------------------------ An alternative command form of the `$ brew list` command exists: $ brew list --versions` which will list the passed in packages (along with their respective version numbers) to `STDOUT`. An undocumented part of this feature is that any missing packages that are not included in the output result the command in returning an non-zero exit code, causing the previous commands to continue as they did. The diff ends up being a one line change: brew) local brew_owner="$(/usr/bin/stat -f %Su "$(command -v brew)")" + sudo -u "$brew_owner" brew list --versions "$@" || sudo -u "$brew_owner" brew install "$@" || sudo -u "$brew_owner" brew upgrade "$@" || return $? Unfortunately, this is an all or nothing approach, that when one of the packages is missing, the previous functionality would be still triggered for every package passed in. The `$missing_pkgs` approach was favored over this approach because it was more granular, and almost entirely prevents the issue mentioned above. --- share/ruby-install/package_manager.sh | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/share/ruby-install/package_manager.sh b/share/ruby-install/package_manager.sh index e4030c63..fe4d1a9b 100644 --- a/share/ruby-install/package_manager.sh +++ b/share/ruby-install/package_manager.sh @@ -38,8 +38,17 @@ function install_packages() pkg) $sudo pkg install -y "$@" || return $? ;; brew) local brew_owner="$(/usr/bin/stat -f %Su "$(command -v brew)")" - sudo -u "$brew_owner" brew install "$@" || - sudo -u "$brew_owner" brew upgrade "$@" || return $? + local installed=$(sudo -u "$brew_owner" brew list -1) + local -a missing_pkgs + + for dep in "$@"; do + [[ "$installed" =~ [[:space:]]"$dep" ]] || missing_pkgs+=($dep) + done + + if (( ${#missing_pkgs[@]} > 0 )); then + sudo -u "$brew_owner" brew install "${missing_pkgs[@]}" || + sudo -u "$brew_owner" brew upgrade "${missing_pkgs[@]}" || return $? + fi ;; pacman) local missing_pkgs=($(pacman -T "$@"))