290 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
			
		
		
	
	
			290 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
| # typed: true # rubocop:todo Sorbet/StrictSigil
 | |
| # frozen_string_literal: true
 | |
| 
 | |
| module Homebrew
 | |
|   module Bundle
 | |
|     class BrewInstaller
 | |
|       def self.reset!
 | |
|         @installed_formulae = nil
 | |
|         @outdated_formulae = nil
 | |
|         @pinned_formulae = nil
 | |
|       end
 | |
| 
 | |
|       def self.preinstall(name, no_upgrade: false, verbose: false, **options)
 | |
|         new(name, options).preinstall(no_upgrade:, verbose:)
 | |
|       end
 | |
| 
 | |
|       def self.install(name, preinstall: true, no_upgrade: false, verbose: false, force: false, **options)
 | |
|         new(name, options).install(preinstall:, no_upgrade:, verbose:, force:)
 | |
|       end
 | |
| 
 | |
|       def initialize(name, options = {})
 | |
|         @full_name = name
 | |
|         @name = name.split("/").last
 | |
|         @args = options.fetch(:args, []).map { |arg| "--#{arg}" }
 | |
|         @conflicts_with_arg = options.fetch(:conflicts_with, [])
 | |
|         @restart_service = options[:restart_service]
 | |
|         @start_service = options.fetch(:start_service, @restart_service)
 | |
|         @link = options.fetch(:link, nil)
 | |
|         @postinstall = options.fetch(:postinstall, nil)
 | |
|         @changed = nil
 | |
|       end
 | |
| 
 | |
|       def preinstall(no_upgrade: false, verbose: false)
 | |
|         if installed? && (no_upgrade || !upgradable?)
 | |
|           puts "Skipping install of #{@name} formula. It is already installed." if verbose
 | |
|           @changed = nil
 | |
|           return false
 | |
|         end
 | |
| 
 | |
|         true
 | |
|       end
 | |
| 
 | |
|       def install(preinstall: true, no_upgrade: false, verbose: false, force: false)
 | |
|         install_result = if preinstall
 | |
|           install_change_state!(no_upgrade:, verbose:, force:)
 | |
|         else
 | |
|           true
 | |
|         end
 | |
|         result = install_result
 | |
| 
 | |
|         if installed?
 | |
|           service_result = service_change_state!(verbose:)
 | |
|           result &&= service_result
 | |
| 
 | |
|           link_result = link_change_state!(verbose:)
 | |
|           result &&= link_result
 | |
| 
 | |
|           postinstall_result = postinstall_change_state!(verbose:)
 | |
|           result &&= postinstall_result
 | |
|         end
 | |
| 
 | |
|         result
 | |
|       end
 | |
| 
 | |
|       def install_change_state!(no_upgrade:, verbose:, force:)
 | |
|         return false unless resolve_conflicts!(verbose:)
 | |
| 
 | |
|         if installed?
 | |
|           upgrade!(verbose:, force:)
 | |
|         else
 | |
|           install!(verbose:, force:)
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       def start_service?
 | |
|         @start_service.present?
 | |
|       end
 | |
| 
 | |
|       def start_service_needed?
 | |
|         start_service? && !BrewServices.started?(@full_name)
 | |
|       end
 | |
| 
 | |
|       def restart_service?
 | |
|         @restart_service.present?
 | |
|       end
 | |
| 
 | |
|       def restart_service_needed?
 | |
|         return false unless restart_service?
 | |
| 
 | |
|         # Restart if `restart_service: :always`, or if the formula was installed or upgraded
 | |
|         @restart_service.to_s == "always" || changed?
 | |
|       end
 | |
| 
 | |
|       def changed?
 | |
|         @changed.present?
 | |
|       end
 | |
| 
 | |
|       def service_change_state!(verbose:)
 | |
|         if restart_service_needed?
 | |
|           puts "Restarting #{@name} service." if verbose
 | |
|           BrewServices.restart(@full_name, verbose:)
 | |
|         elsif start_service_needed?
 | |
|           puts "Starting #{@name} service." if verbose
 | |
|           BrewServices.start(@full_name, verbose:)
 | |
|         else
 | |
|           true
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       def link_change_state!(verbose: false)
 | |
|         link_args = []
 | |
|         link_args << "--force" if unlinked_and_keg_only?
 | |
| 
 | |
|         cmd = case @link
 | |
|         when :overwrite
 | |
|           link_args << "--overwrite"
 | |
|           "link" unless linked?
 | |
|         when true
 | |
|           "link" unless linked?
 | |
|         when false
 | |
|           "unlink" if linked?
 | |
|         when nil
 | |
|           if keg_only?
 | |
|             "unlink" if linked?
 | |
|           else
 | |
|             "link" unless linked?
 | |
|           end
 | |
|         end
 | |
| 
 | |
|         if cmd.present?
 | |
|           verb = "#{cmd}ing".capitalize
 | |
|           with_args = " with #{link_args.join(" ")}" if link_args.present?
 | |
|           puts "#{verb} #{@name} formula#{with_args}." if verbose
 | |
|           return Bundle.brew(cmd, *link_args, @name, verbose:)
 | |
|         end
 | |
| 
 | |
|         true
 | |
|       end
 | |
| 
 | |
|       def postinstall_change_state!(verbose:)
 | |
|         return true if @postinstall.blank?
 | |
|         return true unless changed?
 | |
| 
 | |
|         puts "Running postinstall for #{@name}: #{@postinstall}" if verbose
 | |
|         Kernel.system(@postinstall)
 | |
|       end
 | |
| 
 | |
|       def self.formula_installed_and_up_to_date?(formula, no_upgrade: false)
 | |
|         return false unless formula_installed?(formula)
 | |
|         return true if no_upgrade
 | |
| 
 | |
|         !formula_upgradable?(formula)
 | |
|       end
 | |
| 
 | |
|       def self.formula_in_array?(formula, array)
 | |
|         return true if array.include?(formula)
 | |
|         return true if array.include?(formula.split("/").last)
 | |
| 
 | |
|         old_names = Homebrew::Bundle::BrewDumper.formula_oldnames
 | |
|         old_name = old_names[formula]
 | |
|         old_name ||= old_names[formula.split("/").last]
 | |
|         return true if old_name && array.include?(old_name)
 | |
| 
 | |
|         resolved_full_name = Homebrew::Bundle::BrewDumper.formula_aliases[formula]
 | |
|         return false unless resolved_full_name
 | |
|         return true if array.include?(resolved_full_name)
 | |
|         return true if array.include?(resolved_full_name.split("/").last)
 | |
| 
 | |
|         false
 | |
|       end
 | |
| 
 | |
|       def self.formula_installed?(formula)
 | |
|         formula_in_array?(formula, installed_formulae)
 | |
|       end
 | |
| 
 | |
|       def self.formula_upgradable?(formula)
 | |
|         # Check local cache first and then authoritative Homebrew source.
 | |
|         formula_in_array?(formula, upgradable_formulae) && Formula[formula].outdated?
 | |
|       end
 | |
| 
 | |
|       def self.installed_formulae
 | |
|         @installed_formulae ||= formulae.map { |f| f[:name] }
 | |
|       end
 | |
| 
 | |
|       def self.upgradable_formulae
 | |
|         outdated_formulae - pinned_formulae
 | |
|       end
 | |
| 
 | |
|       def self.outdated_formulae
 | |
|         @outdated_formulae ||= formulae.filter_map { |f| f[:name] if f[:outdated?] }
 | |
|       end
 | |
| 
 | |
|       def self.pinned_formulae
 | |
|         @pinned_formulae ||= formulae.filter_map { |f| f[:name] if f[:pinned?] }
 | |
|       end
 | |
| 
 | |
|       def self.formulae
 | |
|         Homebrew::Bundle::BrewDumper.formulae
 | |
|       end
 | |
| 
 | |
|       private
 | |
| 
 | |
|       def installed?
 | |
|         BrewInstaller.formula_installed?(@name)
 | |
|       end
 | |
| 
 | |
|       def linked?
 | |
|         Formula[@full_name].linked?
 | |
|       end
 | |
| 
 | |
|       def keg_only?
 | |
|         Formula[@full_name].keg_only?
 | |
|       end
 | |
| 
 | |
|       def unlinked_and_keg_only?
 | |
|         !linked? && keg_only?
 | |
|       end
 | |
| 
 | |
|       def upgradable?
 | |
|         BrewInstaller.formula_upgradable?(@name)
 | |
|       end
 | |
| 
 | |
|       def conflicts_with
 | |
|         @conflicts_with ||= begin
 | |
|           conflicts_with = Set.new
 | |
|           conflicts_with += @conflicts_with_arg
 | |
| 
 | |
|           if (formula = Homebrew::Bundle::BrewDumper.formulae_by_full_name(@full_name)) &&
 | |
|              (formula_conflicts_with = formula[:conflicts_with])
 | |
|             conflicts_with += formula_conflicts_with
 | |
|           end
 | |
| 
 | |
|           conflicts_with.to_a
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       def resolve_conflicts!(verbose:)
 | |
|         conflicts_with.each do |conflict|
 | |
|           next unless BrewInstaller.formula_installed?(conflict)
 | |
| 
 | |
|           if verbose
 | |
|             puts <<~EOS
 | |
|               Unlinking #{conflict} formula.
 | |
|               It is currently installed and conflicts with #{@name}.
 | |
|             EOS
 | |
|           end
 | |
|           return false unless Bundle.brew("unlink", conflict, verbose:)
 | |
| 
 | |
|           if restart_service?
 | |
|             puts "Stopping #{conflict} service (if it is running)." if verbose
 | |
|             BrewServices.stop(conflict, verbose:)
 | |
|           end
 | |
|         end
 | |
| 
 | |
|         true
 | |
|       end
 | |
| 
 | |
|       def install!(verbose:, force:)
 | |
|         install_args = @args.dup
 | |
|         install_args << "--force" << "--overwrite" if force
 | |
|         install_args << "--skip-link" if @link == false
 | |
|         with_args = " with #{install_args.join(" ")}" if install_args.present?
 | |
|         puts "Installing #{@name} formula#{with_args}. It is not currently installed." if verbose
 | |
|         unless Bundle.brew("install", "--formula", @full_name, *install_args, verbose:)
 | |
|           @changed = nil
 | |
|           return false
 | |
|         end
 | |
| 
 | |
|         BrewInstaller.installed_formulae << @name
 | |
|         @changed = true
 | |
|         true
 | |
|       end
 | |
| 
 | |
|       def upgrade!(verbose:, force:)
 | |
|         upgrade_args = []
 | |
|         upgrade_args << "--force" if force
 | |
|         with_args = " with #{upgrade_args.join(" ")}" if upgrade_args.present?
 | |
|         puts "Upgrading #{@name} formula#{with_args}. It is installed but not up-to-date." if verbose
 | |
|         unless Bundle.brew("upgrade", "--formula", @name, *upgrade_args, verbose:)
 | |
|           @changed = nil
 | |
|           return false
 | |
|         end
 | |
| 
 | |
|         @changed = true
 | |
|         true
 | |
|       end
 | |
|     end
 | |
|   end
 | |
| end
 | 
