mirror of
https://github.com/Homebrew/brew.git
synced 2026-10-28 17:44:43 +08:00
bundle: refactor brew, cask, and tap classes
- Move `brew`, `cask`, and `tap` into single implementation files while keeping the existing checker, dumper, and installer entrypoints as compatibility requires for the current tests and callers. - Keep the shared bundle checker, dumper, and installer flows generic over registered package types and extensions, and preserve the existing App check output when `--no-upgrade` is used.
This commit is contained in:
22 files changed
+1399
-1173
No files matched your search
@@ -22,7 +22,7 @@ module Homebrew
|
||||
when :brew
|
||||
Formulary.factory(arg)
|
||||
when :cask
|
||||
Cask::CaskLoader.load(arg)
|
||||
::Cask::CaskLoader.load(arg)
|
||||
end
|
||||
|
||||
"#{type} \"#{arg}\""
|
||||
|
||||
@@ -0,0 +1,603 @@
|
||||
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "json"
|
||||
require "tsort"
|
||||
require "utils/output"
|
||||
require "bundle/package_type"
|
||||
|
||||
module Homebrew
|
||||
module Bundle
|
||||
# TODO: refactor into multiple modules
|
||||
class Brew < Homebrew::Bundle::PackageType
|
||||
extend Utils::Output::Mixin
|
||||
|
||||
PACKAGE_TYPE = :brew
|
||||
PACKAGE_TYPE_NAME = "Formula"
|
||||
|
||||
class << self
|
||||
def inherited(subclass)
|
||||
return if subclass.name == "Homebrew::Bundle::Brew::Services"
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
def reset!
|
||||
require "bundle/brew_services"
|
||||
|
||||
Homebrew::Bundle::Brew::Services.reset!
|
||||
@installed_formulae = nil
|
||||
@outdated_formulae = nil
|
||||
@pinned_formulae = nil
|
||||
@formulae = nil
|
||||
@formulae_by_full_name = nil
|
||||
@formulae_by_name = nil
|
||||
@formula_aliases = nil
|
||||
@formula_oldnames = nil
|
||||
end
|
||||
|
||||
def preinstall!(name, no_upgrade: false, verbose: false, **options)
|
||||
new(name, options).preinstall!(no_upgrade:, verbose:)
|
||||
end
|
||||
|
||||
def install!(name, preinstall: true, no_upgrade: false, verbose: false, force: false, **options)
|
||||
new(name, options).install!(preinstall:, no_upgrade:, verbose:, force:)
|
||||
end
|
||||
|
||||
def install_verb(name, options = {})
|
||||
_ = options
|
||||
|
||||
return "Installing" unless formula_upgradable?(name)
|
||||
|
||||
"Upgrading"
|
||||
end
|
||||
|
||||
def formula_installed_and_up_to_date?(formula, no_upgrade: false)
|
||||
return false unless formula_installed?(formula)
|
||||
return true if no_upgrade_with_args?(no_upgrade, formula)
|
||||
|
||||
!formula_upgradable?(formula)
|
||||
end
|
||||
|
||||
def no_upgrade_with_args?(no_upgrade, formula_name)
|
||||
no_upgrade && Bundle.upgrade_formulae.exclude?(formula_name)
|
||||
end
|
||||
|
||||
def formula_in_array?(formula, array)
|
||||
return true if array.include?(formula)
|
||||
return true if array.include?(formula.split("/").last)
|
||||
|
||||
old_name = formula_oldnames[formula]
|
||||
old_name ||= formula_oldnames[formula.split("/").last]
|
||||
return true if old_name && array.include?(old_name)
|
||||
|
||||
resolved_full_name = 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 formula_installed?(formula)
|
||||
formula_in_array?(formula, installed_formulae)
|
||||
end
|
||||
|
||||
def formula_upgradable?(formula)
|
||||
# Check local cache first and then authoritative Homebrew source.
|
||||
formula_in_array?(formula, upgradable_formulae) && Formula[formula].outdated?
|
||||
end
|
||||
|
||||
def installed_formulae
|
||||
@installed_formulae ||= formulae.map { |f| f[:name] }
|
||||
end
|
||||
|
||||
def upgradable_formulae
|
||||
outdated_formulae - pinned_formulae
|
||||
end
|
||||
|
||||
def outdated_formulae
|
||||
@outdated_formulae ||= formulae.filter_map { |f| f[:name] if f[:outdated?] }
|
||||
end
|
||||
|
||||
def pinned_formulae
|
||||
@pinned_formulae ||= formulae.filter_map { |f| f[:name] if f[:pinned?] }
|
||||
end
|
||||
|
||||
def formulae
|
||||
return @formulae if @formulae
|
||||
|
||||
formulae_by_full_name
|
||||
@formulae
|
||||
end
|
||||
|
||||
def formulae_by_full_name(name = nil)
|
||||
return @formulae_by_full_name[name] if name.present? && @formulae_by_full_name&.key?(name)
|
||||
|
||||
require "formula"
|
||||
require "formulary"
|
||||
Formulary.enable_factory_cache!
|
||||
|
||||
@formulae_by_name ||= {}
|
||||
@formulae_by_full_name ||= {}
|
||||
|
||||
if name.nil?
|
||||
formulae = Formula.installed.map { add_formula(it) }
|
||||
sort!(formulae)
|
||||
return @formulae_by_full_name
|
||||
end
|
||||
|
||||
formula = Formula[name]
|
||||
add_formula(formula)
|
||||
rescue FormulaUnavailableError => e
|
||||
opoo "'#{name}' formula is unreadable: #{e}"
|
||||
{}
|
||||
end
|
||||
|
||||
def formulae_by_name(name)
|
||||
formulae_by_full_name(name) || @formulae_by_name[name]
|
||||
end
|
||||
|
||||
def dump(describe: false, no_restart: false)
|
||||
require "bundle/brew_services"
|
||||
|
||||
requested_formula = formulae.select do |f|
|
||||
f[:installed_on_request?] || !f[:installed_as_dependency?]
|
||||
end
|
||||
requested_formula.map do |f|
|
||||
brewline = if describe && f[:desc].present?
|
||||
f[:desc].split("\n").map { |s| "# #{s}\n" }.join
|
||||
else
|
||||
""
|
||||
end
|
||||
brewline += "brew \"#{f[:full_name]}\""
|
||||
|
||||
args = f[:args].map { |arg| "\"#{arg}\"" }.sort.join(", ")
|
||||
brewline += ", args: [#{args}]" unless f[:args].empty?
|
||||
brewline += ", restart_service: :changed" if !no_restart && BrewServices.started?(f[:full_name])
|
||||
brewline += ", link: #{f[:link?]}" unless f[:link?].nil?
|
||||
brewline
|
||||
end.join("\n")
|
||||
end
|
||||
|
||||
def dump_output(describe: false, no_restart: false)
|
||||
dump(describe:, no_restart:)
|
||||
end
|
||||
|
||||
def fetchable_name(name, options = {}, no_upgrade: false)
|
||||
_ = options
|
||||
|
||||
user, repository, = name.split("/", 3)
|
||||
return if user.present? && repository.present? &&
|
||||
Homebrew::Bundle::Tap.installed_taps.exclude?("#{user}/#{repository}")
|
||||
return if formula_installed_and_up_to_date?(name, no_upgrade:)
|
||||
|
||||
name
|
||||
end
|
||||
|
||||
def formula_aliases
|
||||
return @formula_aliases if @formula_aliases
|
||||
|
||||
@formula_aliases = {}
|
||||
formulae.each do |f|
|
||||
aliases = f[:aliases]
|
||||
next if aliases.blank?
|
||||
|
||||
aliases.each do |a|
|
||||
@formula_aliases[a] = f[:full_name]
|
||||
if f[:full_name].include? "/" # tap formula
|
||||
tap_name = f[:full_name].rpartition("/").first
|
||||
@formula_aliases["#{tap_name}/#{a}"] = f[:full_name]
|
||||
end
|
||||
end
|
||||
end
|
||||
@formula_aliases
|
||||
end
|
||||
|
||||
def formula_oldnames
|
||||
return @formula_oldnames if @formula_oldnames
|
||||
|
||||
@formula_oldnames = {}
|
||||
formulae.each do |f|
|
||||
oldnames = f[:oldnames]
|
||||
next if oldnames.blank?
|
||||
|
||||
oldnames.each do |oldname|
|
||||
@formula_oldnames[oldname] = f[:full_name]
|
||||
if f[:full_name].include? "/" # tap formula
|
||||
tap_name = f[:full_name].rpartition("/").first
|
||||
@formula_oldnames["#{tap_name}/#{oldname}"] = f[:full_name]
|
||||
end
|
||||
end
|
||||
end
|
||||
@formula_oldnames
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def add_formula(formula)
|
||||
hash = formula_to_hash formula
|
||||
|
||||
@formulae_by_name[hash[:name]] = hash
|
||||
@formulae_by_full_name[hash[:full_name]] = hash
|
||||
|
||||
hash
|
||||
end
|
||||
|
||||
def formula_to_hash(formula)
|
||||
keg = if formula.linked?
|
||||
link = true if formula.keg_only?
|
||||
formula.linked_keg
|
||||
else
|
||||
link = false unless formula.keg_only?
|
||||
formula.any_installed_prefix
|
||||
end
|
||||
|
||||
if keg
|
||||
require "tab"
|
||||
|
||||
tab = Tab.for_keg(keg)
|
||||
args = tab.used_options.map(&:name)
|
||||
version = begin
|
||||
keg.realpath.basename
|
||||
rescue
|
||||
# silently handle broken symlinks
|
||||
nil
|
||||
end.to_s
|
||||
args << "HEAD" if version.start_with?("HEAD")
|
||||
installed_as_dependency = tab.installed_as_dependency
|
||||
installed_on_request = tab.installed_on_request
|
||||
runtime_dependencies = if (runtime_deps = tab.runtime_dependencies)
|
||||
runtime_deps.filter_map { |d| d["full_name"] }
|
||||
|
||||
end
|
||||
poured_from_bottle = tab.poured_from_bottle
|
||||
end
|
||||
|
||||
runtime_dependencies ||= formula.runtime_dependencies.map(&:name)
|
||||
|
||||
bottled = if (stable = formula.stable) && stable.bottle_defined?
|
||||
bottle_hash = formula.bottle_hash.deep_symbolize_keys
|
||||
stable.bottled?
|
||||
end
|
||||
|
||||
{
|
||||
name: formula.name,
|
||||
desc: formula.desc,
|
||||
oldnames: formula.oldnames,
|
||||
full_name: formula.full_name,
|
||||
aliases: formula.aliases,
|
||||
any_version_installed?: formula.any_version_installed?,
|
||||
args: Array(args).uniq,
|
||||
version:,
|
||||
installed_as_dependency?: installed_as_dependency || false,
|
||||
installed_on_request?: installed_on_request || false,
|
||||
dependencies: runtime_dependencies,
|
||||
build_dependencies: formula.deps.select(&:build?).map(&:name).uniq,
|
||||
conflicts_with: formula.conflicts.map(&:name),
|
||||
pinned?: formula.pinned? || false,
|
||||
outdated?: formula.outdated? || false,
|
||||
link?: link,
|
||||
poured_from_bottle?: poured_from_bottle || false,
|
||||
bottle: bottle_hash || false,
|
||||
bottled: bottled || false,
|
||||
official_tap: formula.tap&.official? || false,
|
||||
}
|
||||
end
|
||||
|
||||
def sort!(formulae)
|
||||
# Step 1: Sort by formula full name while putting tap formulae behind core formulae.
|
||||
# So we can have a nicer output.
|
||||
formulae = formulae.sort do |a, b|
|
||||
if a[:full_name].exclude?("/") && b[:full_name].include?("/")
|
||||
-1
|
||||
elsif a[:full_name].include?("/") && b[:full_name].exclude?("/")
|
||||
1
|
||||
else
|
||||
a[:full_name] <=> b[:full_name]
|
||||
end
|
||||
end
|
||||
|
||||
# Step 2: Sort by formula dependency topology.
|
||||
topo = Topo.new
|
||||
formulae.each do |f|
|
||||
topo[f[:name]] = topo[f[:full_name]] = f[:dependencies].filter_map do |dep|
|
||||
ff = formulae_by_name(dep)
|
||||
next if ff.blank?
|
||||
next unless ff[:any_version_installed?]
|
||||
|
||||
ff[:full_name]
|
||||
end
|
||||
end
|
||||
@formulae = topo.tsort
|
||||
.map { |name| @formulae_by_full_name[name] || @formulae_by_name[name] }
|
||||
.uniq { |f| f[:full_name] }
|
||||
rescue TSort::Cyclic => e
|
||||
e.message =~ /\["([^"]*)".*"([^"]*)"\]/
|
||||
cycle_first = Regexp.last_match(1)
|
||||
cycle_last = Regexp.last_match(2)
|
||||
odie e.message if !cycle_first || !cycle_last
|
||||
|
||||
odie <<~EOS
|
||||
Formulae dependency graph sorting failed (likely due to a circular dependency):
|
||||
#{cycle_first}: #{topo[cycle_first] if topo}
|
||||
#{cycle_last}: #{topo[cycle_last] if topo}
|
||||
Please run the following commands and try again:
|
||||
brew update
|
||||
brew uninstall --ignore-dependencies --force #{cycle_first} #{cycle_last}
|
||||
brew install #{cycle_first} #{cycle_last}
|
||||
EOS
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(name = "", options = {})
|
||||
super()
|
||||
@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)
|
||||
@version_file = options.fetch(:version_file, nil)
|
||||
@changed = nil
|
||||
end
|
||||
|
||||
def installed_and_up_to_date?(formula, no_upgrade: false)
|
||||
self.class.formula_installed_and_up_to_date?(formula, no_upgrade:)
|
||||
end
|
||||
|
||||
def preinstall!(no_upgrade: false, verbose: false)
|
||||
if installed? && (self.class.no_upgrade_with_args?(no_upgrade, @name) || !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
|
||||
|
||||
if result && @version_file.present?
|
||||
# Use the version from the environment if it hasn't changed.
|
||||
# Strip the revision number because it's not part of the non-Homebrew version.
|
||||
version = if !changed? && (env_version = Bundle.formula_versions_from_env(@name))
|
||||
PkgVersion.parse(env_version).version
|
||||
else
|
||||
Formula[@full_name].version
|
||||
end.to_s
|
||||
File.write(@version_file, "#{version}\n")
|
||||
|
||||
puts "Wrote #{@name} version #{version} to #{@version_file}" if verbose
|
||||
end
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def install_change_state!(no_upgrade:, verbose:, force:)
|
||||
require "tap"
|
||||
if (tap_with_name = ::Tap.with_formula_name(@full_name))
|
||||
tap, = tap_with_name
|
||||
tap.ensure_installed!
|
||||
end
|
||||
|
||||
return false unless resolve_conflicts!(verbose:)
|
||||
|
||||
if installed?
|
||||
upgrade_formula!(verbose:, force:)
|
||||
else
|
||||
install_formula!(verbose:, force:)
|
||||
end
|
||||
end
|
||||
|
||||
def start_service?
|
||||
@start_service.present?
|
||||
end
|
||||
|
||||
def start_service_needed?
|
||||
require "bundle/brew_services"
|
||||
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:)
|
||||
require "bundle/brew_services"
|
||||
|
||||
file = BrewServices.versioned_service_file(@name)
|
||||
|
||||
if restart_service_needed?
|
||||
puts "Restarting #{@name} service." if verbose
|
||||
BrewServices.restart(@full_name, file:, verbose:)
|
||||
elsif start_service_needed?
|
||||
puts "Starting #{@name} service." if verbose
|
||||
BrewServices.start(@full_name, file:, 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
|
||||
|
||||
private
|
||||
|
||||
def installed?
|
||||
self.class.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?
|
||||
self.class.formula_upgradable?(@full_name)
|
||||
end
|
||||
|
||||
def conflicts_with
|
||||
@conflicts_with ||= begin
|
||||
conflicts_with = Set.new
|
||||
conflicts_with += @conflicts_with_arg
|
||||
|
||||
if (formula = self.class.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 self.class.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:)
|
||||
|
||||
next unless restart_service?
|
||||
|
||||
require "bundle/brew_services"
|
||||
puts "Stopping #{conflict} service (if it is running)." if verbose
|
||||
BrewServices.stop(conflict, verbose:)
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def install_formula!(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
|
||||
|
||||
self.class.installed_formulae << @name
|
||||
@changed = true
|
||||
true
|
||||
end
|
||||
|
||||
def upgrade_formula!(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
|
||||
|
||||
class Topo < Hash
|
||||
include TSort
|
||||
|
||||
def each_key(&block)
|
||||
keys.each(&block)
|
||||
end
|
||||
alias tsort_each_node each_key
|
||||
|
||||
def tsort_each_child(node, &block)
|
||||
fetch(node.downcase).sort.each(&block)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: Remove these compatibility aliases once bundle callers and tests
|
||||
# stop requiring separate brew dumper/installer/checker constants.
|
||||
FormulaInstaller = Brew
|
||||
FormulaDumper = Brew
|
||||
|
||||
module Checker
|
||||
# TODO: Remove this compatibility alias once bundle callers and tests stop
|
||||
# requiring a separate brew checker constant.
|
||||
BrewChecker = Homebrew::Bundle::Brew
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,20 +1,4 @@
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "bundle/formula_installer"
|
||||
|
||||
module Homebrew
|
||||
module Bundle
|
||||
module Checker
|
||||
class BrewChecker < Homebrew::Bundle::Checker::Base
|
||||
PACKAGE_TYPE = :brew
|
||||
PACKAGE_TYPE_NAME = "Formula"
|
||||
|
||||
sig { params(formula: String, no_upgrade: T::Boolean).returns(T::Boolean) }
|
||||
def installed_and_up_to_date?(formula, no_upgrade: false)
|
||||
Homebrew::Bundle::FormulaInstaller.formula_installed_and_up_to_date?(formula, no_upgrade:)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
require "bundle/brew"
|
||||
@@ -1,54 +1,4 @@
|
||||
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Homebrew
|
||||
module Bundle
|
||||
module Checker
|
||||
class BrewServiceChecker < Homebrew::Bundle::Checker::Base
|
||||
PACKAGE_TYPE = :brew
|
||||
PACKAGE_TYPE_NAME = "Service"
|
||||
PACKAGE_ACTION_PREDICATE = "needs to be started."
|
||||
|
||||
def failure_reason(name, no_upgrade:)
|
||||
"#{PACKAGE_TYPE_NAME} #{name} needs to be started."
|
||||
end
|
||||
|
||||
def installed_and_up_to_date?(formula, no_upgrade: false)
|
||||
return true unless formula_needs_to_start?(entry_to_formula(formula))
|
||||
return true if service_is_started?(formula.name)
|
||||
|
||||
old_name = lookup_old_name(formula.name)
|
||||
return true if old_name && service_is_started?(old_name)
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
def entry_to_formula(entry)
|
||||
require "bundle/formula_installer"
|
||||
Homebrew::Bundle::FormulaInstaller.new(entry.name, entry.options)
|
||||
end
|
||||
|
||||
def formula_needs_to_start?(formula)
|
||||
formula.start_service? || formula.restart_service?
|
||||
end
|
||||
|
||||
def service_is_started?(service_name)
|
||||
require "bundle/brew_services"
|
||||
Homebrew::Bundle::BrewServices.started?(service_name)
|
||||
end
|
||||
|
||||
def lookup_old_name(service_name)
|
||||
require "bundle/formula_dumper"
|
||||
@old_names ||= Homebrew::Bundle::FormulaDumper.formula_oldnames
|
||||
old_name = @old_names[service_name]
|
||||
old_name ||= @old_names[service_name.split("/").last]
|
||||
old_name
|
||||
end
|
||||
|
||||
def format_checkable(entries)
|
||||
checkable_entries(entries)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
require "bundle/brew_services"
|
||||
@@ -3,89 +3,139 @@
|
||||
|
||||
require "services/system"
|
||||
require "utils/output"
|
||||
require "bundle/brew"
|
||||
|
||||
module Homebrew
|
||||
module Bundle
|
||||
module BrewServices
|
||||
extend Utils::Output::Mixin
|
||||
class Brew
|
||||
class Services < Homebrew::Bundle::Brew
|
||||
extend Utils::Output::Mixin
|
||||
|
||||
def self.reset!
|
||||
@started_services = nil
|
||||
end
|
||||
|
||||
def self.stop(name, keep: false, verbose: false)
|
||||
return true unless started?(name)
|
||||
|
||||
args = ["services", "stop", name]
|
||||
args << "--keep" if keep
|
||||
return unless Bundle.brew(*args, verbose:)
|
||||
|
||||
started_services.delete(name)
|
||||
true
|
||||
end
|
||||
|
||||
def self.start(name, file: nil, verbose: false)
|
||||
args = ["services", "start", name]
|
||||
args << "--file=#{file}" if file
|
||||
return unless Bundle.brew(*args, verbose:)
|
||||
|
||||
started_services << name
|
||||
true
|
||||
end
|
||||
|
||||
def self.run(name, file: nil, verbose: false)
|
||||
args = ["services", "run", name]
|
||||
args << "--file=#{file}" if file
|
||||
return unless Bundle.brew(*args, verbose:)
|
||||
|
||||
started_services << name
|
||||
true
|
||||
end
|
||||
|
||||
def self.restart(name, file: nil, verbose: false)
|
||||
args = ["services", "restart", name]
|
||||
args << "--file=#{file}" if file
|
||||
return unless Bundle.brew(*args, verbose:)
|
||||
|
||||
started_services << name
|
||||
true
|
||||
end
|
||||
|
||||
def self.started?(name)
|
||||
started_services.include? name
|
||||
end
|
||||
|
||||
def self.started_services
|
||||
@started_services ||= begin
|
||||
if !Homebrew::Services::System.launchctl? && !Homebrew::Services::System.systemctl?
|
||||
odie Homebrew::Services::System::MISSING_DAEMON_MANAGER_EXCEPTION_MESSAGE
|
||||
class << self
|
||||
def reset!
|
||||
@started_services = nil
|
||||
end
|
||||
states_to_skip = %w[stopped none]
|
||||
Utils.safe_popen_read(HOMEBREW_BREW_FILE, "services", "list").lines.filter_map do |line|
|
||||
name, state, _plist = line.split(/\s+/)
|
||||
next if states_to_skip.include? state
|
||||
|
||||
name
|
||||
def stop(name, keep: false, verbose: false)
|
||||
return true unless started?(name)
|
||||
|
||||
args = ["services", "stop", name]
|
||||
args << "--keep" if keep
|
||||
return unless Bundle.brew(*args, verbose:)
|
||||
|
||||
started_services.delete(name)
|
||||
true
|
||||
end
|
||||
|
||||
def start(name, file: nil, verbose: false)
|
||||
args = ["services", "start", name]
|
||||
args << "--file=#{file}" if file
|
||||
return unless Bundle.brew(*args, verbose:)
|
||||
|
||||
started_services << name
|
||||
true
|
||||
end
|
||||
|
||||
def run(name, file: nil, verbose: false)
|
||||
args = ["services", "run", name]
|
||||
args << "--file=#{file}" if file
|
||||
return unless Bundle.brew(*args, verbose:)
|
||||
|
||||
started_services << name
|
||||
true
|
||||
end
|
||||
|
||||
def restart(name, file: nil, verbose: false)
|
||||
args = ["services", "restart", name]
|
||||
args << "--file=#{file}" if file
|
||||
return unless Bundle.brew(*args, verbose:)
|
||||
|
||||
started_services << name
|
||||
true
|
||||
end
|
||||
|
||||
def started?(name)
|
||||
started_services.include? name
|
||||
end
|
||||
|
||||
def started_services
|
||||
@started_services ||= begin
|
||||
if !Homebrew::Services::System.launchctl? && !Homebrew::Services::System.systemctl?
|
||||
odie Homebrew::Services::System::MISSING_DAEMON_MANAGER_EXCEPTION_MESSAGE
|
||||
end
|
||||
states_to_skip = %w[stopped none]
|
||||
Utils.safe_popen_read(HOMEBREW_BREW_FILE, "services", "list").lines.filter_map do |line|
|
||||
name, state, _plist = line.split(/\s+/)
|
||||
next if states_to_skip.include? state
|
||||
|
||||
name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def versioned_service_file(name)
|
||||
env_version = Bundle.formula_versions_from_env(name)
|
||||
return if env_version.nil?
|
||||
|
||||
formula = Formula[name]
|
||||
prefix = formula.rack/env_version
|
||||
return unless prefix.directory?
|
||||
|
||||
service_file = if Homebrew::Services::System.launchctl?
|
||||
prefix/"#{formula.plist_name}.plist"
|
||||
else
|
||||
prefix/"#{formula.service_name}.service"
|
||||
end
|
||||
|
||||
service_file if service_file.file?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.versioned_service_file(name)
|
||||
env_version = Bundle.formula_versions_from_env(name)
|
||||
return if env_version.nil?
|
||||
def failure_reason(name, no_upgrade:)
|
||||
_ = no_upgrade
|
||||
|
||||
formula = Formula[name]
|
||||
prefix = formula.rack/env_version
|
||||
return unless prefix.directory?
|
||||
|
||||
service_file = if Homebrew::Services::System.launchctl?
|
||||
prefix/"#{formula.plist_name}.plist"
|
||||
else
|
||||
prefix/"#{formula.service_name}.service"
|
||||
"Service #{name} needs to be started."
|
||||
end
|
||||
|
||||
service_file if service_file.file?
|
||||
def installed_and_up_to_date?(formula, no_upgrade: false)
|
||||
_ = no_upgrade
|
||||
|
||||
return true unless formula_needs_to_start?(entry_to_formula(formula))
|
||||
return true if self.class.started?(formula.name)
|
||||
|
||||
old_name = lookup_old_name(formula.name)
|
||||
return true if old_name && self.class.started?(old_name)
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
def entry_to_formula(entry)
|
||||
Homebrew::Bundle::Brew.new(entry.name, entry.options)
|
||||
end
|
||||
|
||||
def formula_needs_to_start?(formula)
|
||||
formula.start_service? || formula.restart_service?
|
||||
end
|
||||
|
||||
def lookup_old_name(service_name)
|
||||
@old_names ||= Homebrew::Bundle::Brew.formula_oldnames
|
||||
old_name = @old_names[service_name]
|
||||
old_name ||= @old_names[service_name.split("/").last]
|
||||
old_name
|
||||
end
|
||||
|
||||
def format_checkable(entries)
|
||||
checkable_entries(entries)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
BrewServices = Brew::Services
|
||||
|
||||
module Checker
|
||||
# TODO: Remove this compatibility alias once bundle callers and tests stop
|
||||
# requiring a separate brew service checker constant.
|
||||
BrewServiceChecker = Homebrew::Bundle::Brew::Services
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,251 @@
|
||||
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "utils/output"
|
||||
require "bundle/package_type"
|
||||
|
||||
module Homebrew
|
||||
module Bundle
|
||||
class Cask < Homebrew::Bundle::PackageType
|
||||
extend ::Utils::Output::Mixin
|
||||
|
||||
PACKAGE_TYPE = :cask
|
||||
PACKAGE_TYPE_NAME = "Cask"
|
||||
|
||||
class << self
|
||||
def reset!
|
||||
@casks = nil
|
||||
@cask_names = nil
|
||||
@cask_oldnames = nil
|
||||
@installed_casks = nil
|
||||
@outdated_casks = nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def upgrading?(no_upgrade, name, options)
|
||||
return false if no_upgrade
|
||||
return true if cask_upgradable?(name)
|
||||
return false unless options[:greedy]
|
||||
|
||||
cask_is_outdated_using_greedy?(name)
|
||||
end
|
||||
|
||||
def postinstall_change_state!(name:, options:, verbose:)
|
||||
postinstall = options.fetch(:postinstall, nil)
|
||||
return true if postinstall.blank?
|
||||
|
||||
puts "Running postinstall for #{@name}: #{postinstall}" if verbose
|
||||
Kernel.system(postinstall)
|
||||
end
|
||||
|
||||
def casks
|
||||
return [] unless Bundle.cask_installed?
|
||||
|
||||
require "cask/caskroom"
|
||||
@casks ||= ::Cask::Caskroom.casks
|
||||
end
|
||||
|
||||
def explicit_s(cask_config)
|
||||
cask_config.explicit.map do |key, value|
|
||||
# inverse of #env - converts :languages config key back to --language flag
|
||||
if key == :languages
|
||||
key = "language"
|
||||
value = Array(cask_config.explicit.fetch(:languages, [])).join(",")
|
||||
end
|
||||
"#{key}: \"#{value.to_s.sub(/^#{Dir.home}/, "~")}\""
|
||||
end.join(", ")
|
||||
end
|
||||
|
||||
public
|
||||
|
||||
def install_verb(name, options = {})
|
||||
return "Installing" if !cask_installed?(name) || !upgrading?(false, name, options)
|
||||
|
||||
"Upgrading"
|
||||
end
|
||||
|
||||
def preinstall!(name, no_upgrade: false, verbose: false, **options)
|
||||
if cask_installed?(name) && !upgrading?(no_upgrade, name, options)
|
||||
puts "Skipping install of #{name} cask. It is already installed." if verbose
|
||||
return false
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def install!(name, preinstall: true, no_upgrade: false, verbose: false, force: false, **options)
|
||||
return true unless preinstall
|
||||
|
||||
full_name = options.fetch(:full_name, name)
|
||||
|
||||
install_result = if cask_installed?(name) && upgrading?(no_upgrade, name, options)
|
||||
status = "#{options[:greedy] ? "may not be" : "not"} up-to-date"
|
||||
puts "Upgrading #{name} cask. It is installed but #{status}." if verbose
|
||||
Bundle.brew("upgrade", "--cask", full_name, verbose:)
|
||||
else
|
||||
args = options.fetch(:args, []).filter_map do |k, v|
|
||||
case v
|
||||
when TrueClass
|
||||
"--#{k}"
|
||||
when FalseClass, NilClass
|
||||
nil
|
||||
else
|
||||
"--#{k}=#{v}"
|
||||
end
|
||||
end
|
||||
|
||||
args << "--force" if force
|
||||
args << "--adopt" unless args.include?("--force")
|
||||
args.uniq!
|
||||
|
||||
with_args = " with #{args.join(" ")}" if args.present?
|
||||
puts "Installing #{name} cask#{with_args}. It is not currently installed." if verbose
|
||||
|
||||
if Bundle.brew("install", "--cask", full_name, *args, verbose:)
|
||||
installed_casks << name
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
result = install_result
|
||||
|
||||
if cask_installed?(name)
|
||||
postinstall_result = postinstall_change_state!(name:, options:, verbose:)
|
||||
result &&= postinstall_result
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def installable_or_upgradable?(name, no_upgrade: false, **options)
|
||||
!cask_installed?(name) || upgrading?(no_upgrade, name, options)
|
||||
end
|
||||
|
||||
def fetchable_name(name, options = {}, no_upgrade: false)
|
||||
full_name = T.cast(options.fetch(:full_name, name), String)
|
||||
user, repository, = full_name.split("/", 3)
|
||||
return if user.present? && repository.present? &&
|
||||
Homebrew::Bundle::Tap.installed_taps.exclude?("#{user}/#{repository}")
|
||||
return unless installable_or_upgradable?(name, no_upgrade:, **options)
|
||||
|
||||
full_name
|
||||
end
|
||||
|
||||
def cask_installed_and_up_to_date?(cask, no_upgrade: false)
|
||||
return false unless cask_installed?(cask)
|
||||
return true if no_upgrade
|
||||
|
||||
!cask_upgradable?(cask)
|
||||
end
|
||||
|
||||
def cask_in_array?(cask, array)
|
||||
return true if array.include?(cask)
|
||||
|
||||
array.include?(cask.split("/").last)
|
||||
end
|
||||
|
||||
def cask_installed?(cask)
|
||||
return true if cask_in_array?(cask, installed_casks)
|
||||
|
||||
old_name = cask_oldnames[cask]
|
||||
old_name ||= cask_oldnames[cask.split("/").last]
|
||||
return false unless old_name
|
||||
return false unless cask_in_array?(old_name, installed_casks)
|
||||
|
||||
opoo "#{cask} was renamed to #{old_name}"
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def cask_upgradable?(cask)
|
||||
cask_in_array?(cask, outdated_casks)
|
||||
end
|
||||
|
||||
def installed_casks
|
||||
@installed_casks ||= cask_names
|
||||
end
|
||||
|
||||
def outdated_casks
|
||||
@outdated_casks ||= outdated_cask_names
|
||||
end
|
||||
|
||||
def cask_names
|
||||
@cask_names ||= casks.map(&:to_s)
|
||||
end
|
||||
|
||||
def outdated_cask_names
|
||||
return [] unless Bundle.cask_installed?
|
||||
|
||||
casks.select { |c| c.outdated?(greedy: false) }
|
||||
.map(&:to_s)
|
||||
end
|
||||
|
||||
def cask_is_outdated_using_greedy?(cask_name)
|
||||
return false unless Bundle.cask_installed?
|
||||
|
||||
cask = casks.find { |installed_cask| installed_cask.to_s == cask_name }
|
||||
return false if cask.nil?
|
||||
|
||||
cask.outdated?(greedy: true)
|
||||
end
|
||||
|
||||
def dump(describe: false)
|
||||
casks.map do |cask|
|
||||
description = "# #{cask.desc}\n" if describe && cask.desc.present?
|
||||
config = ", args: { #{explicit_s(cask.config)} }" if cask.config.present? && cask.config.explicit.present?
|
||||
"#{description}cask \"#{cask.full_name}\"#{config}"
|
||||
end.join("\n")
|
||||
end
|
||||
|
||||
def dump_output(describe: false, no_restart: false)
|
||||
_ = no_restart
|
||||
|
||||
dump(describe:)
|
||||
end
|
||||
|
||||
def cask_oldnames
|
||||
@cask_oldnames ||= casks.each_with_object({}) do |c, hash|
|
||||
oldnames = c.old_tokens
|
||||
next if oldnames.blank?
|
||||
|
||||
oldnames.each do |oldname|
|
||||
hash[oldname] = c.full_name
|
||||
if c.full_name.include? "/" # tap cask
|
||||
tap_name = c.full_name.rpartition("/").first
|
||||
hash["#{tap_name}/#{oldname}"] = c.full_name
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def formula_dependencies(cask_list)
|
||||
return [] unless Bundle.cask_installed?
|
||||
return [] if cask_list.blank?
|
||||
|
||||
casks.flat_map do |cask|
|
||||
next unless cask_list.include?(cask.to_s)
|
||||
|
||||
cask.depends_on[:formula]
|
||||
end.compact
|
||||
end
|
||||
end
|
||||
|
||||
def installed_and_up_to_date?(cask, no_upgrade: false)
|
||||
self.class.cask_installed_and_up_to_date?(cask, no_upgrade:)
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: Remove these compatibility aliases once bundle callers and tests
|
||||
# stop requiring separate cask dumper/installer/checker constants.
|
||||
CaskInstaller = Cask
|
||||
CaskDumper = Cask
|
||||
|
||||
module Checker
|
||||
# TODO: Remove this compatibility alias once bundle callers and tests stop
|
||||
# requiring a separate cask checker constant.
|
||||
CaskChecker = Homebrew::Bundle::Cask
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,20 +1,4 @@
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "bundle/cask_installer"
|
||||
|
||||
module Homebrew
|
||||
module Bundle
|
||||
module Checker
|
||||
class CaskChecker < Homebrew::Bundle::Checker::Base
|
||||
PACKAGE_TYPE = :cask
|
||||
PACKAGE_TYPE_NAME = "Cask"
|
||||
|
||||
sig { params(cask: String, no_upgrade: T::Boolean).returns(T::Boolean) }
|
||||
def installed_and_up_to_date?(cask, no_upgrade: false)
|
||||
Homebrew::Bundle::CaskInstaller.cask_installed_and_up_to_date?(cask, no_upgrade:)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
require "bundle/cask"
|
||||
@@ -1,95 +1,4 @@
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Homebrew
|
||||
module Bundle
|
||||
module CaskDumper
|
||||
sig { void }
|
||||
def self.reset!
|
||||
@casks = nil
|
||||
@cask_names = nil
|
||||
@cask_oldnames = nil
|
||||
end
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
def self.cask_names
|
||||
@cask_names ||= T.let(casks.map(&:to_s), T.nilable(T::Array[String]))
|
||||
end
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
def self.outdated_cask_names
|
||||
return [] unless Bundle.cask_installed?
|
||||
|
||||
casks.select { |c| c.outdated?(greedy: false) }
|
||||
.map(&:to_s)
|
||||
end
|
||||
|
||||
sig { params(cask_name: String).returns(T::Boolean) }
|
||||
def self.cask_is_outdated_using_greedy?(cask_name)
|
||||
return false unless Bundle.cask_installed?
|
||||
|
||||
cask = casks.find { |c| c.to_s == cask_name }
|
||||
return false if cask.nil?
|
||||
|
||||
cask.outdated?(greedy: true)
|
||||
end
|
||||
|
||||
sig { params(describe: T::Boolean).returns(String) }
|
||||
def self.dump(describe: false)
|
||||
casks.map do |cask|
|
||||
description = "# #{cask.desc}\n" if describe && cask.desc.present?
|
||||
config = ", args: { #{explicit_s(cask.config)} }" if cask.config.present? && cask.config.explicit.present?
|
||||
"#{description}cask \"#{cask.full_name}\"#{config}"
|
||||
end.join("\n")
|
||||
end
|
||||
|
||||
sig { returns(T::Hash[String, String]) }
|
||||
def self.cask_oldnames
|
||||
@cask_oldnames ||= T.let(casks.each_with_object({}) do |c, hash|
|
||||
oldnames = c.old_tokens
|
||||
next if oldnames.blank?
|
||||
|
||||
oldnames.each do |oldname|
|
||||
hash[oldname] = c.full_name
|
||||
if c.full_name.include? "/" # tap cask
|
||||
tap_name = c.full_name.rpartition("/").first
|
||||
hash["#{tap_name}/#{oldname}"] = c.full_name
|
||||
end
|
||||
end
|
||||
end, T.nilable(T::Hash[String, String]))
|
||||
end
|
||||
|
||||
sig { params(cask_list: T::Array[String]).returns(T::Array[String]) }
|
||||
def self.formula_dependencies(cask_list)
|
||||
return [] unless Bundle.cask_installed?
|
||||
return [] if cask_list.blank?
|
||||
|
||||
casks.flat_map do |cask|
|
||||
next unless cask_list.include?(cask.to_s)
|
||||
|
||||
cask.depends_on[:formula]
|
||||
end.compact
|
||||
end
|
||||
|
||||
sig { returns(T::Array[Cask::Cask]) }
|
||||
private_class_method def self.casks
|
||||
return [] unless Bundle.cask_installed?
|
||||
|
||||
require "cask/caskroom"
|
||||
@casks ||= T.let(Cask::Caskroom.casks, T.nilable(T::Array[Cask::Cask]))
|
||||
end
|
||||
|
||||
sig { params(cask_config: Cask::Config).returns(String) }
|
||||
private_class_method def self.explicit_s(cask_config)
|
||||
cask_config.explicit.map do |key, value|
|
||||
# inverse of #env - converts :languages config key back to --language flag
|
||||
if key == :languages
|
||||
key = "language"
|
||||
value = Array(cask_config.explicit.fetch(:languages, [])).join(",")
|
||||
end
|
||||
"#{key}: \"#{value.to_s.sub(/^#{Dir.home}/, "~")}\""
|
||||
end.join(", ")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
require "bundle/cask"
|
||||
@@ -1,132 +1,4 @@
|
||||
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Homebrew
|
||||
module Bundle
|
||||
module CaskInstaller
|
||||
extend ::Utils::Output::Mixin
|
||||
|
||||
def self.reset!
|
||||
@installed_casks = nil
|
||||
@outdated_casks = nil
|
||||
end
|
||||
|
||||
private_class_method def self.upgrading?(no_upgrade, name, options)
|
||||
return false if no_upgrade
|
||||
return true if cask_upgradable?(name)
|
||||
return false unless options[:greedy]
|
||||
|
||||
require "bundle/cask_dumper"
|
||||
Homebrew::Bundle::CaskDumper.cask_is_outdated_using_greedy?(name)
|
||||
end
|
||||
|
||||
def self.preinstall!(name, no_upgrade: false, verbose: false, **options)
|
||||
if cask_installed?(name) && !upgrading?(no_upgrade, name, options)
|
||||
puts "Skipping install of #{name} cask. It is already installed." if verbose
|
||||
return false
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def self.install!(name, preinstall: true, no_upgrade: false, verbose: false, force: false, **options)
|
||||
return true unless preinstall
|
||||
|
||||
full_name = options.fetch(:full_name, name)
|
||||
|
||||
install_result = if cask_installed?(name) && upgrading?(no_upgrade, name, options)
|
||||
status = "#{options[:greedy] ? "may not be" : "not"} up-to-date"
|
||||
puts "Upgrading #{name} cask. It is installed but #{status}." if verbose
|
||||
Bundle.brew("upgrade", "--cask", full_name, verbose:)
|
||||
else
|
||||
args = options.fetch(:args, []).filter_map do |k, v|
|
||||
case v
|
||||
when TrueClass
|
||||
"--#{k}"
|
||||
when FalseClass, NilClass
|
||||
nil
|
||||
else
|
||||
"--#{k}=#{v}"
|
||||
end
|
||||
end
|
||||
|
||||
args << "--force" if force
|
||||
args << "--adopt" unless args.include?("--force")
|
||||
args.uniq!
|
||||
|
||||
with_args = " with #{args.join(" ")}" if args.present?
|
||||
puts "Installing #{name} cask#{with_args}. It is not currently installed." if verbose
|
||||
|
||||
if Bundle.brew("install", "--cask", full_name, *args, verbose:)
|
||||
installed_casks << name
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
result = install_result
|
||||
|
||||
if cask_installed?(name)
|
||||
postinstall_result = postinstall_change_state!(name:, options:, verbose:)
|
||||
result &&= postinstall_result
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def self.installable_or_upgradable?(name, no_upgrade: false, **options)
|
||||
!cask_installed?(name) || upgrading?(no_upgrade, name, options)
|
||||
end
|
||||
|
||||
private_class_method def self.postinstall_change_state!(name:, options:, verbose:)
|
||||
postinstall = options.fetch(:postinstall, nil)
|
||||
return true if postinstall.blank?
|
||||
|
||||
puts "Running postinstall for #{@name}: #{postinstall}" if verbose
|
||||
Kernel.system(postinstall)
|
||||
end
|
||||
|
||||
def self.cask_installed_and_up_to_date?(cask, no_upgrade: false)
|
||||
return false unless cask_installed?(cask)
|
||||
return true if no_upgrade
|
||||
|
||||
!cask_upgradable?(cask)
|
||||
end
|
||||
|
||||
def self.cask_in_array?(cask, array)
|
||||
return true if array.include?(cask)
|
||||
|
||||
array.include?(cask.split("/").last)
|
||||
end
|
||||
|
||||
def self.cask_installed?(cask)
|
||||
return true if cask_in_array?(cask, installed_casks)
|
||||
|
||||
require "bundle/cask_dumper"
|
||||
old_names = Homebrew::Bundle::CaskDumper.cask_oldnames
|
||||
old_name = old_names[cask]
|
||||
old_name ||= old_names[cask.split("/").last]
|
||||
return false unless old_name
|
||||
return false unless cask_in_array?(old_name, installed_casks)
|
||||
|
||||
opoo "#{cask} was renamed to #{old_name}"
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def self.cask_upgradable?(cask)
|
||||
cask_in_array?(cask, outdated_casks)
|
||||
end
|
||||
|
||||
def self.installed_casks
|
||||
require "bundle/cask_dumper"
|
||||
@installed_casks ||= Homebrew::Bundle::CaskDumper.cask_names
|
||||
end
|
||||
|
||||
def self.outdated_casks
|
||||
require "bundle/cask_dumper"
|
||||
@outdated_casks ||= Homebrew::Bundle::CaskDumper.outdated_cask_names
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
require "bundle/cask"
|
||||
@@ -3,7 +3,8 @@
|
||||
|
||||
require "bundle/checker/base"
|
||||
require "bundle/dsl"
|
||||
require "bundle/extensions"
|
||||
require "bundle/package_types"
|
||||
require "bundle/brew_services"
|
||||
|
||||
module Homebrew
|
||||
module Bundle
|
||||
@@ -15,6 +16,7 @@ module Homebrew
|
||||
:taps_to_tap,
|
||||
:casks_to_install,
|
||||
:registered_extensions_to_install,
|
||||
:apps_to_install,
|
||||
:formulae_to_install,
|
||||
:formulae_to_start,
|
||||
].freeze, T::Array[CheckStep])
|
||||
@@ -38,38 +40,8 @@ module Homebrew
|
||||
CheckResult.new work_to_be_done, errors
|
||||
end
|
||||
|
||||
def self.casks_to_install(exit_on_first_error: false, no_upgrade: false, verbose: false)
|
||||
require "bundle/cask_checker"
|
||||
Homebrew::Bundle::Checker::CaskChecker.new.find_actionable(
|
||||
@dsl.entries,
|
||||
exit_on_first_error:, no_upgrade:, verbose:,
|
||||
)
|
||||
end
|
||||
|
||||
def self.formulae_to_install(exit_on_first_error: false, no_upgrade: false, verbose: false)
|
||||
require "bundle/brew_checker"
|
||||
Homebrew::Bundle::Checker::BrewChecker.new.find_actionable(
|
||||
@dsl.entries,
|
||||
exit_on_first_error:, no_upgrade:, verbose:,
|
||||
)
|
||||
end
|
||||
|
||||
def self.taps_to_tap(exit_on_first_error: false, no_upgrade: false, verbose: false)
|
||||
require "bundle/tap_checker"
|
||||
Homebrew::Bundle::Checker::TapChecker.new.find_actionable(
|
||||
@dsl.entries,
|
||||
exit_on_first_error:, no_upgrade:, verbose:,
|
||||
)
|
||||
end
|
||||
|
||||
def self.apps_to_install(exit_on_first_error: false, no_upgrade: false, verbose: false)
|
||||
_ = exit_on_first_error
|
||||
_ = no_upgrade
|
||||
_ = verbose
|
||||
|
||||
# TODO: Remove this legacy no-op once callers and tests stop referencing
|
||||
# the old dedicated app check phase.
|
||||
[]
|
||||
extension_errors(:apps_to_install, exit_on_first_error:, no_upgrade:, verbose:)
|
||||
end
|
||||
|
||||
def self.extensions_to_install(exit_on_first_error: false, no_upgrade: false, verbose: false)
|
||||
@@ -83,17 +55,52 @@ module Homebrew
|
||||
end
|
||||
|
||||
def self.formulae_to_start(exit_on_first_error: false, no_upgrade: false, verbose: false)
|
||||
require "bundle/brew_service_checker"
|
||||
Homebrew::Bundle::Checker::BrewServiceChecker.new.find_actionable(
|
||||
# TODO: Keep this separate until service checks can be modeled as part of
|
||||
# the `brew` package type without pretending services are standalone
|
||||
# package entries.
|
||||
Homebrew::Bundle::Brew::Services.new.find_actionable(
|
||||
@dsl.entries,
|
||||
exit_on_first_error:, no_upgrade:, verbose:,
|
||||
)
|
||||
end
|
||||
|
||||
def self.taps_to_tap(exit_on_first_error: false, no_upgrade: false, verbose: false)
|
||||
# TODO: Remove this legacy wrapper once callers and tests stop
|
||||
# referencing the old dedicated tap check phase.
|
||||
package_type_errors(:tap, exit_on_first_error:, no_upgrade:, verbose:)
|
||||
end
|
||||
|
||||
def self.casks_to_install(exit_on_first_error: false, no_upgrade: false, verbose: false)
|
||||
# TODO: Remove this legacy wrapper once callers and tests stop
|
||||
# referencing the old dedicated cask check phase.
|
||||
package_type_errors(:cask, exit_on_first_error:, no_upgrade:, verbose:)
|
||||
end
|
||||
|
||||
def self.formulae_to_install(exit_on_first_error: false, no_upgrade: false, verbose: false)
|
||||
# TODO: Remove this legacy wrapper once callers and tests stop
|
||||
# referencing the old dedicated formula check phase.
|
||||
package_type_errors(:brew, exit_on_first_error:, no_upgrade:, verbose:)
|
||||
end
|
||||
|
||||
def self.registered_extensions_to_install(exit_on_first_error: false, no_upgrade: false, verbose: false)
|
||||
# TODO: Remove this legacy wrapper once callers and tests stop
|
||||
# referencing the old dedicated extension check phase.
|
||||
extension_errors(:registered_extensions_to_install, exit_on_first_error:, no_upgrade:, verbose:)
|
||||
end
|
||||
|
||||
sig {
|
||||
params(
|
||||
step: Symbol,
|
||||
exit_on_first_error: T::Boolean,
|
||||
no_upgrade: T::Boolean,
|
||||
verbose: T::Boolean,
|
||||
).returns(T::Array[Object])
|
||||
}
|
||||
def self.extension_errors(step, exit_on_first_error:, no_upgrade:, verbose:)
|
||||
matching_extensions = Homebrew::Bundle.extensions.select { |extension| extension.legacy_check_step == step }
|
||||
errors = T.let([], T::Array[Object])
|
||||
|
||||
Homebrew::Bundle.extensions.each do |extension|
|
||||
matching_extensions.each do |extension|
|
||||
check_errors = extension.check(
|
||||
@dsl.entries,
|
||||
exit_on_first_error:, no_upgrade:, verbose:,
|
||||
@@ -109,16 +116,8 @@ module Homebrew
|
||||
end
|
||||
|
||||
def self.reset!
|
||||
require "bundle/cask_dumper"
|
||||
require "bundle/formula_dumper"
|
||||
require "bundle/tap_dumper"
|
||||
require "bundle/brew_services"
|
||||
|
||||
@dsl = nil
|
||||
Homebrew::Bundle::CaskDumper.reset!
|
||||
Homebrew::Bundle::FormulaDumper.reset!
|
||||
Homebrew::Bundle::TapDumper.reset!
|
||||
Homebrew::Bundle::BrewServices.reset!
|
||||
Homebrew::Bundle.package_types.each(&:reset!)
|
||||
Homebrew::Bundle.extensions.each(&:reset!)
|
||||
end
|
||||
|
||||
@@ -127,6 +126,21 @@ module Homebrew
|
||||
CORE_CHECKS
|
||||
end
|
||||
|
||||
sig {
|
||||
params(
|
||||
type: Symbol,
|
||||
exit_on_first_error: T::Boolean,
|
||||
no_upgrade: T::Boolean,
|
||||
verbose: T::Boolean,
|
||||
).returns(T::Array[Object])
|
||||
}
|
||||
def self.package_type_errors(type, exit_on_first_error:, no_upgrade:, verbose:)
|
||||
package_type = Homebrew::Bundle.package_type(type)
|
||||
return [] if package_type.nil?
|
||||
|
||||
package_type.check(@dsl.entries, exit_on_first_error:, no_upgrade:, verbose:)
|
||||
end
|
||||
|
||||
sig {
|
||||
params(
|
||||
check_step: CheckStep,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
require "fileutils"
|
||||
require "bundle/dsl"
|
||||
require "bundle/extensions"
|
||||
require "bundle/package_types"
|
||||
|
||||
module Homebrew
|
||||
module Bundle
|
||||
@@ -28,21 +28,20 @@ module Homebrew
|
||||
}
|
||||
def self.build_brewfile(describe:, no_restart:, formulae:, taps:, casks:, extension_types: {},
|
||||
**extra_extension_types)
|
||||
require "bundle/tap_dumper"
|
||||
require "bundle/formula_dumper"
|
||||
require "bundle/cask_dumper"
|
||||
|
||||
# TODO: Remove `extra_extension_types` once all callers pass a single
|
||||
# `extension_types:` hash instead of legacy per-extension keywords.
|
||||
extension_types = extension_types.merge(extra_extension_types)
|
||||
selected_package_types = extension_types.merge(extra_extension_types)
|
||||
# TODO: Remove this legacy core-type mapping once the dump command
|
||||
# passes a single package-type selection hash instead of separate
|
||||
# `taps`, `formulae`, and `casks` booleans.
|
||||
selected_package_types[:tap] = taps
|
||||
selected_package_types[:brew] = formulae
|
||||
selected_package_types[:cask] = casks
|
||||
content = []
|
||||
content << TapDumper.dump if taps
|
||||
content << FormulaDumper.dump(describe:, no_restart:) if formulae
|
||||
content << CaskDumper.dump(describe:) if casks
|
||||
Homebrew::Bundle.extensions.select(&:dump_supported?).each do |extension|
|
||||
next unless extension_types.fetch(extension.type, false)
|
||||
Homebrew::Bundle.dump_package_types.select(&:dump_supported?).each do |package_type|
|
||||
next unless selected_package_types.fetch(package_type.type, false)
|
||||
|
||||
content << extension.dump
|
||||
content << package_type.dump_output(describe:, no_restart:)
|
||||
end
|
||||
"#{content.reject(&:empty?).join("\n")}\n"
|
||||
end
|
||||
|
||||
@@ -83,17 +83,137 @@ module Homebrew
|
||||
|
||||
ExtensionTypes = T.type_alias { T::Hash[Symbol, T::Boolean] }
|
||||
|
||||
class Extension < Homebrew::Bundle::Checker::Base
|
||||
class PackageType < Homebrew::Bundle::Checker::Base
|
||||
extend T::Helpers
|
||||
|
||||
abstract!
|
||||
|
||||
sig { params(subclass: T.class_of(Homebrew::Bundle::PackageType)).void }
|
||||
def self.inherited(subclass)
|
||||
super
|
||||
return if subclass.name == "Homebrew::Bundle::Extension"
|
||||
|
||||
Homebrew::Bundle.register_package_type(subclass)
|
||||
end
|
||||
|
||||
sig { returns(Symbol) }
|
||||
def self.type
|
||||
T.cast(const_get(:PACKAGE_TYPE), Symbol)
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def self.dump_supported?
|
||||
true
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def self.install_supported?
|
||||
true
|
||||
end
|
||||
|
||||
sig { params(_name: String, _options: T::Hash[Symbol, Object]).returns(String) }
|
||||
def self.install_verb(_name = "", _options = {})
|
||||
"Installing"
|
||||
end
|
||||
|
||||
sig {
|
||||
params(
|
||||
name: String,
|
||||
options: T::Hash[Symbol, Object],
|
||||
no_upgrade: T::Boolean,
|
||||
).returns(T.nilable(String))
|
||||
}
|
||||
def self.fetchable_name(name, options = {}, no_upgrade: false)
|
||||
_ = name
|
||||
_ = options
|
||||
_ = no_upgrade
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def self.check_supported?
|
||||
true
|
||||
end
|
||||
|
||||
sig { abstract.void }
|
||||
def self.reset!; end
|
||||
|
||||
sig {
|
||||
params(
|
||||
entries: T::Array[Object],
|
||||
exit_on_first_error: T::Boolean,
|
||||
no_upgrade: T::Boolean,
|
||||
verbose: T::Boolean,
|
||||
).returns(T::Array[Object])
|
||||
}
|
||||
def self.check(entries, exit_on_first_error: false, no_upgrade: false, verbose: false)
|
||||
new.find_actionable(entries, exit_on_first_error:, no_upgrade:, verbose:)
|
||||
end
|
||||
|
||||
sig { abstract.returns(String) }
|
||||
def self.dump; end
|
||||
|
||||
sig { params(describe: T::Boolean, no_restart: T::Boolean).returns(String) }
|
||||
def self.dump_output(describe: false, no_restart: false)
|
||||
_ = describe
|
||||
_ = no_restart
|
||||
|
||||
dump
|
||||
end
|
||||
end
|
||||
|
||||
class << self
|
||||
sig { params(package_type: T.class_of(PackageType)).void }
|
||||
def register_package_type(package_type)
|
||||
@package_types ||= T.let([], T.nilable(T::Array[T.class_of(PackageType)]))
|
||||
@package_types.reject! { |registered| registered.name == package_type.name }
|
||||
@package_types << package_type
|
||||
end
|
||||
|
||||
sig { returns(T::Array[T.class_of(PackageType)]) }
|
||||
def package_types
|
||||
@package_types ||= T.let([], T.nilable(T::Array[T.class_of(PackageType)]))
|
||||
@package_types
|
||||
end
|
||||
|
||||
sig { params(type: T.any(Symbol, String)).returns(T.nilable(T.class_of(PackageType))) }
|
||||
def package_type(type)
|
||||
requested_type = type.to_sym
|
||||
package_types.find { |registered| registered.type == requested_type }
|
||||
end
|
||||
|
||||
sig { returns(T::Array[T.class_of(PackageType)]) }
|
||||
def dump_package_types
|
||||
core_package_types = [:tap, :brew, :cask].filter_map { |type| package_type(type) }
|
||||
(core_package_types + (package_types - core_package_types)).uniq
|
||||
end
|
||||
|
||||
sig { returns(T::Array[T.class_of(PackageType)]) }
|
||||
def check_package_types
|
||||
# TODO: Remove this legacy ordering shim once package-type order can be
|
||||
# inferred entirely from registration without changing `brew bundle check`
|
||||
# behavior for taps, casks, extensions, and formulae.
|
||||
taps = package_type(:tap)
|
||||
casks = package_type(:cask)
|
||||
formulae = package_type(:brew)
|
||||
core_package_types = [taps, casks, formulae].compact
|
||||
|
||||
[taps, casks, *(package_types - core_package_types), formulae].compact
|
||||
end
|
||||
end
|
||||
|
||||
class Extension < Homebrew::Bundle::PackageType
|
||||
extend T::Helpers
|
||||
|
||||
EntryOptions = T.type_alias { T::Hash[Symbol, Object] }
|
||||
|
||||
abstract!
|
||||
|
||||
sig { params(subclass: T.class_of(Homebrew::Bundle::Extension)).void }
|
||||
sig { override.params(subclass: T.class_of(Homebrew::Bundle::PackageType)).void }
|
||||
def self.inherited(subclass)
|
||||
super
|
||||
Homebrew::Bundle.register_extension(subclass)
|
||||
Homebrew::Bundle.register_extension(T.cast(subclass, T.class_of(Homebrew::Bundle::Extension)))
|
||||
end
|
||||
|
||||
sig { returns(Symbol) }
|
||||
@@ -199,6 +319,26 @@ module Homebrew
|
||||
true
|
||||
end
|
||||
|
||||
sig { params(_name: String, _options: T::Hash[Symbol, Object]).returns(String) }
|
||||
def self.install_verb(_name = "", _options = {})
|
||||
"Installing"
|
||||
end
|
||||
|
||||
sig {
|
||||
params(
|
||||
name: String,
|
||||
options: T::Hash[Symbol, Object],
|
||||
no_upgrade: T::Boolean,
|
||||
).returns(T.nilable(String))
|
||||
}
|
||||
def self.fetchable_name(name, options = {}, no_upgrade: false)
|
||||
_ = name
|
||||
_ = options
|
||||
_ = no_upgrade
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
sig { returns(T.nilable(String)) }
|
||||
def self.cleanup_heading
|
||||
nil
|
||||
@@ -245,11 +385,19 @@ module Homebrew
|
||||
nil
|
||||
end
|
||||
|
||||
sig { returns(String) }
|
||||
sig { override.returns(String) }
|
||||
def self.dump
|
||||
packages.map { |package| dump_entry(package) }.join("\n")
|
||||
end
|
||||
|
||||
sig { params(describe: T::Boolean, no_restart: T::Boolean).returns(String) }
|
||||
def self.dump_output(describe: false, no_restart: false)
|
||||
_ = describe
|
||||
_ = no_restart
|
||||
|
||||
dump
|
||||
end
|
||||
|
||||
sig {
|
||||
params(
|
||||
entries: T::Array[Object],
|
||||
@@ -272,6 +420,11 @@ module Homebrew
|
||||
nil
|
||||
end
|
||||
|
||||
sig { returns(Symbol) }
|
||||
def self.legacy_check_step
|
||||
:registered_extensions_to_install
|
||||
end
|
||||
|
||||
sig { params(_items: T::Array[String]).void }
|
||||
def self.cleanup!(_items); end
|
||||
|
||||
@@ -395,6 +548,15 @@ module Homebrew
|
||||
requested_type = type.to_sym
|
||||
extensions.find { |registered| registered.type == requested_type }
|
||||
end
|
||||
|
||||
sig {
|
||||
params(
|
||||
type: T.any(Symbol, String),
|
||||
).returns(T.nilable(T.any(T.class_of(PackageType), T.class_of(Extension))))
|
||||
}
|
||||
def installable(type)
|
||||
package_type(type) || extension(type)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -13,6 +13,11 @@ module Homebrew
|
||||
BANNER_NAME = "Mac App Store dependencies"
|
||||
|
||||
class << self
|
||||
sig { override.returns(Symbol) }
|
||||
def legacy_check_step
|
||||
:apps_to_install
|
||||
end
|
||||
|
||||
sig { override.returns(T::Boolean) }
|
||||
def add_supported?
|
||||
false
|
||||
|
||||
@@ -1,247 +1,4 @@
|
||||
# typed: true
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "json"
|
||||
require "tsort"
|
||||
require "utils/output"
|
||||
|
||||
module Homebrew
|
||||
module Bundle
|
||||
# TODO: refactor into multiple modules
|
||||
module FormulaDumper
|
||||
extend Utils::Output::Mixin
|
||||
|
||||
def self.reset!
|
||||
require "bundle/brew_services"
|
||||
|
||||
Homebrew::Bundle::BrewServices.reset!
|
||||
@formulae = nil
|
||||
@formulae_by_full_name = nil
|
||||
@formulae_by_name = nil
|
||||
@formula_aliases = nil
|
||||
@formula_oldnames = nil
|
||||
end
|
||||
|
||||
def self.formulae
|
||||
return @formulae if @formulae
|
||||
|
||||
formulae_by_full_name
|
||||
@formulae
|
||||
end
|
||||
|
||||
def self.formulae_by_full_name(name = nil)
|
||||
return @formulae_by_full_name[name] if name.present? && @formulae_by_full_name&.key?(name)
|
||||
|
||||
require "formula"
|
||||
require "formulary"
|
||||
Formulary.enable_factory_cache!
|
||||
|
||||
@formulae_by_name ||= {}
|
||||
@formulae_by_full_name ||= {}
|
||||
|
||||
if name.nil?
|
||||
formulae = Formula.installed.map { add_formula(it) }
|
||||
sort!(formulae)
|
||||
return @formulae_by_full_name
|
||||
end
|
||||
|
||||
formula = Formula[name]
|
||||
add_formula(formula)
|
||||
rescue FormulaUnavailableError => e
|
||||
opoo "'#{name}' formula is unreadable: #{e}"
|
||||
{}
|
||||
end
|
||||
|
||||
def self.formulae_by_name(name)
|
||||
formulae_by_full_name(name) || @formulae_by_name[name]
|
||||
end
|
||||
|
||||
def self.dump(describe: false, no_restart: false)
|
||||
require "bundle/brew_services"
|
||||
|
||||
requested_formula = formulae.select do |f|
|
||||
f[:installed_on_request?] || !f[:installed_as_dependency?]
|
||||
end
|
||||
requested_formula.map do |f|
|
||||
brewline = if describe && f[:desc].present?
|
||||
f[:desc].split("\n").map { |s| "# #{s}\n" }.join
|
||||
else
|
||||
""
|
||||
end
|
||||
brewline += "brew \"#{f[:full_name]}\""
|
||||
|
||||
args = f[:args].map { |arg| "\"#{arg}\"" }.sort.join(", ")
|
||||
brewline += ", args: [#{args}]" unless f[:args].empty?
|
||||
brewline += ", restart_service: :changed" if !no_restart && BrewServices.started?(f[:full_name])
|
||||
brewline += ", link: #{f[:link?]}" unless f[:link?].nil?
|
||||
brewline
|
||||
end.join("\n")
|
||||
end
|
||||
|
||||
def self.formula_aliases
|
||||
return @formula_aliases if @formula_aliases
|
||||
|
||||
@formula_aliases = {}
|
||||
formulae.each do |f|
|
||||
aliases = f[:aliases]
|
||||
next if aliases.blank?
|
||||
|
||||
aliases.each do |a|
|
||||
@formula_aliases[a] = f[:full_name]
|
||||
if f[:full_name].include? "/" # tap formula
|
||||
tap_name = f[:full_name].rpartition("/").first
|
||||
@formula_aliases["#{tap_name}/#{a}"] = f[:full_name]
|
||||
end
|
||||
end
|
||||
end
|
||||
@formula_aliases
|
||||
end
|
||||
|
||||
def self.formula_oldnames
|
||||
return @formula_oldnames if @formula_oldnames
|
||||
|
||||
@formula_oldnames = {}
|
||||
formulae.each do |f|
|
||||
oldnames = f[:oldnames]
|
||||
next if oldnames.blank?
|
||||
|
||||
oldnames.each do |oldname|
|
||||
@formula_oldnames[oldname] = f[:full_name]
|
||||
if f[:full_name].include? "/" # tap formula
|
||||
tap_name = f[:full_name].rpartition("/").first
|
||||
@formula_oldnames["#{tap_name}/#{oldname}"] = f[:full_name]
|
||||
end
|
||||
end
|
||||
end
|
||||
@formula_oldnames
|
||||
end
|
||||
|
||||
private_class_method def self.add_formula(formula)
|
||||
hash = formula_to_hash formula
|
||||
|
||||
@formulae_by_name[hash[:name]] = hash
|
||||
@formulae_by_full_name[hash[:full_name]] = hash
|
||||
|
||||
hash
|
||||
end
|
||||
|
||||
private_class_method def self.formula_to_hash(formula)
|
||||
keg = if formula.linked?
|
||||
link = true if formula.keg_only?
|
||||
formula.linked_keg
|
||||
else
|
||||
link = false unless formula.keg_only?
|
||||
formula.any_installed_prefix
|
||||
end
|
||||
|
||||
if keg
|
||||
require "tab"
|
||||
|
||||
tab = Tab.for_keg(keg)
|
||||
args = tab.used_options.map(&:name)
|
||||
version = begin
|
||||
keg.realpath.basename
|
||||
rescue
|
||||
# silently handle broken symlinks
|
||||
nil
|
||||
end.to_s
|
||||
args << "HEAD" if version.start_with?("HEAD")
|
||||
installed_as_dependency = tab.installed_as_dependency
|
||||
installed_on_request = tab.installed_on_request
|
||||
runtime_dependencies = if (runtime_deps = tab.runtime_dependencies)
|
||||
runtime_deps.filter_map { |d| d["full_name"] }
|
||||
|
||||
end
|
||||
poured_from_bottle = tab.poured_from_bottle
|
||||
end
|
||||
|
||||
runtime_dependencies ||= formula.runtime_dependencies.map(&:name)
|
||||
|
||||
bottled = if (stable = formula.stable) && stable.bottle_defined?
|
||||
bottle_hash = formula.bottle_hash.deep_symbolize_keys
|
||||
stable.bottled?
|
||||
end
|
||||
|
||||
{
|
||||
name: formula.name,
|
||||
desc: formula.desc,
|
||||
oldnames: formula.oldnames,
|
||||
full_name: formula.full_name,
|
||||
aliases: formula.aliases,
|
||||
any_version_installed?: formula.any_version_installed?,
|
||||
args: Array(args).uniq,
|
||||
version:,
|
||||
installed_as_dependency?: installed_as_dependency || false,
|
||||
installed_on_request?: installed_on_request || false,
|
||||
dependencies: runtime_dependencies,
|
||||
build_dependencies: formula.deps.select(&:build?).map(&:name).uniq,
|
||||
conflicts_with: formula.conflicts.map(&:name),
|
||||
pinned?: formula.pinned? || false,
|
||||
outdated?: formula.outdated? || false,
|
||||
link?: link,
|
||||
poured_from_bottle?: poured_from_bottle || false,
|
||||
bottle: bottle_hash || false,
|
||||
bottled: bottled || false,
|
||||
official_tap: formula.tap&.official? || false,
|
||||
}
|
||||
end
|
||||
|
||||
class Topo < Hash
|
||||
include TSort
|
||||
|
||||
def each_key(&block)
|
||||
keys.each(&block)
|
||||
end
|
||||
alias tsort_each_node each_key
|
||||
|
||||
def tsort_each_child(node, &block)
|
||||
fetch(node.downcase).sort.each(&block)
|
||||
end
|
||||
end
|
||||
|
||||
private_class_method def self.sort!(formulae)
|
||||
# Step 1: Sort by formula full name while putting tap formulae behind core formulae.
|
||||
# So we can have a nicer output.
|
||||
formulae = formulae.sort do |a, b|
|
||||
if a[:full_name].exclude?("/") && b[:full_name].include?("/")
|
||||
-1
|
||||
elsif a[:full_name].include?("/") && b[:full_name].exclude?("/")
|
||||
1
|
||||
else
|
||||
a[:full_name] <=> b[:full_name]
|
||||
end
|
||||
end
|
||||
|
||||
# Step 2: Sort by formula dependency topology.
|
||||
topo = Topo.new
|
||||
formulae.each do |f|
|
||||
topo[f[:name]] = topo[f[:full_name]] = f[:dependencies].filter_map do |dep|
|
||||
ff = formulae_by_name(dep)
|
||||
next if ff.blank?
|
||||
next unless ff[:any_version_installed?]
|
||||
|
||||
ff[:full_name]
|
||||
end
|
||||
end
|
||||
@formulae = topo.tsort
|
||||
.map { |name| @formulae_by_full_name[name] || @formulae_by_name[name] }
|
||||
.uniq { |f| f[:full_name] }
|
||||
rescue TSort::Cyclic => e
|
||||
e.message =~ /\["([^"]*)".*"([^"]*)"\]/
|
||||
cycle_first = Regexp.last_match(1)
|
||||
cycle_last = Regexp.last_match(2)
|
||||
odie e.message if !cycle_first || !cycle_last
|
||||
|
||||
odie <<~EOS
|
||||
Formulae dependency graph sorting failed (likely due to a circular dependency):
|
||||
#{cycle_first}: #{topo[cycle_first] if topo}
|
||||
#{cycle_last}: #{topo[cycle_last] if topo}
|
||||
Please run the following commands and try again:
|
||||
brew update
|
||||
brew uninstall --ignore-dependencies --force #{cycle_first} #{cycle_last}
|
||||
brew install #{cycle_first} #{cycle_last}
|
||||
EOS
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
require "bundle/brew"
|
||||
@@ -1,321 +1,4 @@
|
||||
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Homebrew
|
||||
module Bundle
|
||||
class FormulaInstaller
|
||||
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)
|
||||
@version_file = options.fetch(:version_file, nil)
|
||||
@changed = nil
|
||||
end
|
||||
|
||||
def preinstall!(no_upgrade: false, verbose: false)
|
||||
if installed? && (self.class.no_upgrade_with_args?(no_upgrade, @name) || !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
|
||||
|
||||
if result && @version_file.present?
|
||||
# Use the version from the environment if it hasn't changed.
|
||||
# Strip the revision number because it's not part of the non-Homebrew version.
|
||||
version = if !changed? && (env_version = Bundle.formula_versions_from_env(@name))
|
||||
PkgVersion.parse(env_version).version
|
||||
else
|
||||
Formula[@full_name].version
|
||||
end.to_s
|
||||
File.write(@version_file, "#{version}\n")
|
||||
|
||||
puts "Wrote #{@name} version #{version} to #{@version_file}" if verbose
|
||||
end
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def install_change_state!(no_upgrade:, verbose:, force:)
|
||||
if (tap_with_name = Tap.with_formula_name(@full_name))
|
||||
tap, = tap_with_name
|
||||
tap.ensure_installed!
|
||||
end
|
||||
|
||||
return false unless resolve_conflicts!(verbose:)
|
||||
|
||||
if installed?
|
||||
upgrade_formula!(verbose:, force:)
|
||||
else
|
||||
install_formula!(verbose:, force:)
|
||||
end
|
||||
end
|
||||
|
||||
def start_service?
|
||||
@start_service.present?
|
||||
end
|
||||
|
||||
def start_service_needed?
|
||||
require "bundle/brew_services"
|
||||
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:)
|
||||
require "bundle/brew_services"
|
||||
|
||||
file = Bundle::BrewServices.versioned_service_file(@name)
|
||||
|
||||
if restart_service_needed?
|
||||
puts "Restarting #{@name} service." if verbose
|
||||
BrewServices.restart(@full_name, file:, verbose:)
|
||||
elsif start_service_needed?
|
||||
puts "Starting #{@name} service." if verbose
|
||||
BrewServices.start(@full_name, file:, 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_with_args?(no_upgrade, formula)
|
||||
|
||||
!formula_upgradable?(formula)
|
||||
end
|
||||
|
||||
def self.no_upgrade_with_args?(no_upgrade, formula_name)
|
||||
no_upgrade && Bundle.upgrade_formulae.exclude?(formula_name)
|
||||
end
|
||||
|
||||
def self.formula_in_array?(formula, array)
|
||||
return true if array.include?(formula)
|
||||
return true if array.include?(formula.split("/").last)
|
||||
|
||||
require "bundle/formula_dumper"
|
||||
old_names = Homebrew::Bundle::FormulaDumper.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::FormulaDumper.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
|
||||
require "bundle/formula_dumper"
|
||||
Homebrew::Bundle::FormulaDumper.formulae
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def installed?
|
||||
FormulaInstaller.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?
|
||||
FormulaInstaller.formula_upgradable?(@full_name)
|
||||
end
|
||||
|
||||
def conflicts_with
|
||||
@conflicts_with ||= begin
|
||||
conflicts_with = Set.new
|
||||
conflicts_with += @conflicts_with_arg
|
||||
|
||||
require "bundle/formula_dumper"
|
||||
if (formula = Homebrew::Bundle::FormulaDumper.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 FormulaInstaller.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:)
|
||||
|
||||
next unless restart_service?
|
||||
|
||||
require "bundle/brew_services"
|
||||
puts "Stopping #{conflict} service (if it is running)." if verbose
|
||||
BrewServices.stop(conflict, verbose:)
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def install_formula!(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
|
||||
|
||||
FormulaInstaller.installed_formulae << @name
|
||||
@changed = true
|
||||
true
|
||||
end
|
||||
|
||||
def upgrade_formula!(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
|
||||
require "bundle/brew"
|
||||
@@ -2,10 +2,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "bundle/dsl"
|
||||
require "bundle/formula_installer"
|
||||
require "bundle/cask_installer"
|
||||
require "bundle/tap_installer"
|
||||
require "bundle/extensions"
|
||||
require "bundle/package_types"
|
||||
require "bundle/skipper"
|
||||
|
||||
module Homebrew
|
||||
@@ -33,32 +30,12 @@ module Homebrew
|
||||
|
||||
name = entry.name
|
||||
args = [name]
|
||||
options = {}
|
||||
verb = "Installing"
|
||||
options = T.cast(entry.options, T::Hash[Symbol, Object])
|
||||
type = entry.type
|
||||
cls = case type
|
||||
when :brew
|
||||
options = entry.options
|
||||
verb = "Upgrading" if Homebrew::Bundle::FormulaInstaller.formula_upgradable?(name)
|
||||
Homebrew::Bundle::FormulaInstaller
|
||||
when :cask
|
||||
options = entry.options
|
||||
verb = "Upgrading" if Homebrew::Bundle::CaskInstaller.cask_upgradable?(name)
|
||||
Homebrew::Bundle::CaskInstaller
|
||||
when :tap
|
||||
verb = "Tapping"
|
||||
options = entry.options
|
||||
Homebrew::Bundle::TapInstaller
|
||||
else
|
||||
extension = Homebrew::Bundle.extension(type)
|
||||
next if extension.nil? || !extension.install_supported?
|
||||
cls = Homebrew::Bundle.installable(type)
|
||||
next if cls.nil? || !cls.install_supported?
|
||||
|
||||
options = entry.options
|
||||
extension
|
||||
end
|
||||
next if cls.nil?
|
||||
|
||||
{ name:, args:, options:, verb:, type:, cls: }
|
||||
{ name:, args:, options:, verb: cls.install_verb(name, options), type:, cls: }
|
||||
end
|
||||
|
||||
if (fetchable_names = fetchable_formulae_and_casks(installable_entries, no_upgrade:).presence)
|
||||
@@ -114,10 +91,11 @@ module Homebrew
|
||||
params(
|
||||
entries: T::Array[{ name: String,
|
||||
args: T::Array[T.anything],
|
||||
options: T::Hash[Symbol, T.untyped],
|
||||
options: T::Hash[Symbol, Object],
|
||||
verb: String,
|
||||
type: Symbol,
|
||||
cls: T::Module[T.anything] }],
|
||||
cls: T.any(T.class_of(Homebrew::Bundle::PackageType),
|
||||
T.class_of(Homebrew::Bundle::Extension)) }],
|
||||
no_upgrade: T::Boolean,
|
||||
).returns(T::Array[String])
|
||||
}
|
||||
@@ -125,30 +103,10 @@ module Homebrew
|
||||
entries.filter_map do |entry|
|
||||
name = entry.fetch(:name)
|
||||
options = entry.fetch(:options)
|
||||
|
||||
case entry.fetch(:type)
|
||||
when :brew
|
||||
next unless tap_installed?(name)
|
||||
next if Homebrew::Bundle::FormulaInstaller.formula_installed_and_up_to_date?(name, no_upgrade:)
|
||||
|
||||
name
|
||||
when :cask
|
||||
full_name = options.fetch(:full_name, name)
|
||||
next unless tap_installed?(full_name)
|
||||
next unless Homebrew::Bundle::CaskInstaller.installable_or_upgradable?(name, no_upgrade:, **options)
|
||||
|
||||
full_name
|
||||
end
|
||||
cls = entry.fetch(:cls)
|
||||
cls.fetchable_name(name, options, no_upgrade:)
|
||||
end
|
||||
end
|
||||
|
||||
sig { params(package_full_name: String).returns(T::Boolean) }
|
||||
def self.tap_installed?(package_full_name)
|
||||
user, repository, = package_full_name.split("/", 3)
|
||||
return true if user.blank? || repository.blank?
|
||||
|
||||
Homebrew::Bundle::TapInstaller.installed_taps.include?("#{user}/#{repository}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,6 @@
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
# TODO: Remove this compatibility require once bundle callers load
|
||||
# `bundle/extensions/extension` directly.
|
||||
require "bundle/extensions/extension"
|
||||
@@ -0,0 +1,8 @@
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "bundle/package_type"
|
||||
require "bundle/tap"
|
||||
require "bundle/brew"
|
||||
require "bundle/cask"
|
||||
require "bundle/extensions"
|
||||
@@ -0,0 +1,150 @@
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "json"
|
||||
require "bundle/package_type"
|
||||
|
||||
module Homebrew
|
||||
module Bundle
|
||||
class Tap < Homebrew::Bundle::PackageType
|
||||
PACKAGE_TYPE = :tap
|
||||
PACKAGE_TYPE_NAME = "Tap"
|
||||
|
||||
class << self
|
||||
sig { override.void }
|
||||
def reset!
|
||||
@taps = T.let(nil, T.nilable(T::Array[::Tap]))
|
||||
@installed_taps = T.let(nil, T.nilable(T::Array[String]))
|
||||
end
|
||||
|
||||
sig { params(name: String, verbose: T::Boolean, _options: T.anything).returns(T::Boolean) }
|
||||
def preinstall!(name, verbose: false, **_options)
|
||||
if installed_taps.include? name
|
||||
puts "Skipping install of #{name} tap. It is already installed." if verbose
|
||||
return false
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
sig {
|
||||
params(
|
||||
name: String,
|
||||
preinstall: T::Boolean,
|
||||
verbose: T::Boolean,
|
||||
force: T::Boolean,
|
||||
clone_target: T.nilable(String),
|
||||
_options: T.anything,
|
||||
).returns(T::Boolean)
|
||||
}
|
||||
def install!(name, preinstall: true, verbose: false, force: false, clone_target: nil, **_options)
|
||||
return true unless preinstall
|
||||
|
||||
puts "Installing #{name} tap. It is not currently installed." if verbose
|
||||
args = []
|
||||
official_tap = name.downcase.start_with? "homebrew/"
|
||||
args << "--force" if force || (official_tap && Homebrew::EnvConfig.developer?)
|
||||
|
||||
success = if clone_target
|
||||
Bundle.brew("tap", name, clone_target, *args, verbose:)
|
||||
else
|
||||
Bundle.brew("tap", name, *args, verbose:)
|
||||
end
|
||||
|
||||
unless success
|
||||
require "bundle/skipper"
|
||||
Homebrew::Bundle::Skipper.tap_failed!(name)
|
||||
return false
|
||||
end
|
||||
|
||||
installed_taps << name
|
||||
true
|
||||
end
|
||||
|
||||
sig { override.params(_name: String, _options: T::Hash[Symbol, Object]).returns(String) }
|
||||
def install_verb(_name = "", _options = {})
|
||||
"Tapping"
|
||||
end
|
||||
|
||||
sig { override.returns(String) }
|
||||
def dump
|
||||
taps.map do |tap|
|
||||
remote = if tap.custom_remote? && (tap_remote = tap.remote)
|
||||
if (api_token = ENV.fetch("HOMEBREW_GITHUB_API_TOKEN", false).presence)
|
||||
# Replace the API token in the remote URL with interpolation.
|
||||
# Rubocop's warning here is wrong; we intentionally want to not
|
||||
# evaluate this string until the Brewfile is evaluated.
|
||||
# rubocop:disable Lint/InterpolationCheck
|
||||
tap_remote = tap_remote.gsub api_token, '#{ENV.fetch("HOMEBREW_GITHUB_API_TOKEN")}'
|
||||
# rubocop:enable Lint/InterpolationCheck
|
||||
end
|
||||
", \"#{tap_remote}\""
|
||||
end
|
||||
"tap \"#{tap.name}\"#{remote}"
|
||||
end.sort.uniq.join("\n")
|
||||
end
|
||||
|
||||
sig { override.params(describe: T::Boolean, no_restart: T::Boolean).returns(String) }
|
||||
def dump_output(describe: false, no_restart: false)
|
||||
_ = describe
|
||||
_ = no_restart
|
||||
|
||||
dump
|
||||
end
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
def tap_names
|
||||
taps.map(&:name)
|
||||
end
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
def installed_taps
|
||||
@installed_taps ||= T.let(tap_names, T.nilable(T::Array[String]))
|
||||
end
|
||||
|
||||
sig { returns(T::Array[::Tap]) }
|
||||
def taps
|
||||
@taps ||= begin
|
||||
require "tap"
|
||||
::Tap.select(&:installed?).to_a
|
||||
end
|
||||
end
|
||||
private :taps
|
||||
end
|
||||
|
||||
sig {
|
||||
override.params(entries: T::Array[Object], exit_on_first_error: T::Boolean,
|
||||
no_upgrade: T::Boolean, verbose: T::Boolean).returns(T::Array[String])
|
||||
}
|
||||
def find_actionable(entries, exit_on_first_error: false, no_upgrade: false, verbose: false)
|
||||
_ = exit_on_first_error
|
||||
_ = no_upgrade
|
||||
_ = verbose
|
||||
|
||||
requested_taps = format_checkable(entries)
|
||||
return [] if requested_taps.empty?
|
||||
|
||||
current_taps = self.class.tap_names
|
||||
(requested_taps - current_taps).map { |entry| "Tap #{entry} needs to be tapped." }
|
||||
end
|
||||
|
||||
sig { override.params(package: T.untyped, no_upgrade: T::Boolean).returns(T::Boolean) }
|
||||
def installed_and_up_to_date?(package, no_upgrade: false)
|
||||
_ = no_upgrade
|
||||
|
||||
self.class.installed_taps.include?(T.cast(package, String))
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: Remove these compatibility aliases once bundle callers and tests
|
||||
# stop requiring separate tap dumper/installer/checker constants.
|
||||
TapInstaller = Tap
|
||||
TapDumper = Tap
|
||||
|
||||
module Checker
|
||||
# TODO: Remove this compatibility alias once bundle callers and tests stop
|
||||
# requiring a separate tap checker constant.
|
||||
TapChecker = Homebrew::Bundle::Tap
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,26 +1,4 @@
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Homebrew
|
||||
module Bundle
|
||||
module Checker
|
||||
class TapChecker < Homebrew::Bundle::Checker::Base
|
||||
PACKAGE_TYPE = :tap
|
||||
PACKAGE_TYPE_NAME = "Tap"
|
||||
|
||||
sig {
|
||||
params(entries: T::Array[Object], exit_on_first_error: T::Boolean,
|
||||
no_upgrade: T::Boolean, verbose: T::Boolean).returns(T::Array[String])
|
||||
}
|
||||
def find_actionable(entries, exit_on_first_error: false, no_upgrade: false, verbose: false)
|
||||
requested_taps = format_checkable(entries)
|
||||
return [] if requested_taps.empty?
|
||||
|
||||
require "bundle/tap_dumper"
|
||||
current_taps = Homebrew::Bundle::TapDumper.tap_names
|
||||
(requested_taps - current_taps).map { |entry| "Tap #{entry} needs to be tapped." }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
require "bundle/tap"
|
||||
@@ -1,47 +1,4 @@
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "json"
|
||||
|
||||
module Homebrew
|
||||
module Bundle
|
||||
module TapDumper
|
||||
sig { void }
|
||||
def self.reset!
|
||||
@taps = nil
|
||||
end
|
||||
|
||||
sig { returns(String) }
|
||||
def self.dump
|
||||
taps.map do |tap|
|
||||
remote = if tap.custom_remote? && (tap_remote = tap.remote)
|
||||
if (api_token = ENV.fetch("HOMEBREW_GITHUB_API_TOKEN", false).presence)
|
||||
# Replace the API token in the remote URL with interpolation.
|
||||
# Rubocop's warning here is wrong; we intentionally want to not
|
||||
# evaluate this string until the Brewfile is evaluated.
|
||||
# rubocop:disable Lint/InterpolationCheck
|
||||
tap_remote = tap_remote.gsub api_token, '#{ENV.fetch("HOMEBREW_GITHUB_API_TOKEN")}'
|
||||
# rubocop:enable Lint/InterpolationCheck
|
||||
end
|
||||
", \"#{tap_remote}\""
|
||||
end
|
||||
"tap \"#{tap.name}\"#{remote}"
|
||||
end.sort.uniq.join("\n")
|
||||
end
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
def self.tap_names
|
||||
taps.map(&:name)
|
||||
end
|
||||
|
||||
sig { returns(T::Array[Tap]) }
|
||||
private_class_method def self.taps
|
||||
@taps ||= T.let(nil, T.nilable(T::Array[Tap]))
|
||||
@taps ||= begin
|
||||
require "tap"
|
||||
Tap.select(&:installed?).to_a
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
require "bundle/tap"
|
||||
@@ -1,58 +1,4 @@
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Homebrew
|
||||
module Bundle
|
||||
module TapInstaller
|
||||
sig { params(name: String, verbose: T::Boolean, _options: T.anything).returns(T::Boolean) }
|
||||
def self.preinstall!(name, verbose: false, **_options)
|
||||
if installed_taps.include? name
|
||||
puts "Skipping install of #{name} tap. It is already installed." if verbose
|
||||
return false
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
sig {
|
||||
params(
|
||||
name: String,
|
||||
preinstall: T::Boolean,
|
||||
verbose: T::Boolean,
|
||||
force: T::Boolean,
|
||||
clone_target: T.nilable(String),
|
||||
_options: T.anything,
|
||||
).returns(T::Boolean)
|
||||
}
|
||||
def self.install!(name, preinstall: true, verbose: false, force: false, clone_target: nil, **_options)
|
||||
return true unless preinstall
|
||||
|
||||
puts "Installing #{name} tap. It is not currently installed." if verbose
|
||||
args = []
|
||||
official_tap = name.downcase.start_with? "homebrew/"
|
||||
args << "--force" if force || (official_tap && Homebrew::EnvConfig.developer?)
|
||||
|
||||
success = if clone_target
|
||||
Bundle.brew("tap", name, clone_target, *args, verbose:)
|
||||
else
|
||||
Bundle.brew("tap", name, *args, verbose:)
|
||||
end
|
||||
|
||||
unless success
|
||||
require "bundle/skipper"
|
||||
Homebrew::Bundle::Skipper.tap_failed!(name)
|
||||
return false
|
||||
end
|
||||
|
||||
installed_taps << name
|
||||
true
|
||||
end
|
||||
|
||||
sig { returns(T::Array[String]) }
|
||||
def self.installed_taps
|
||||
require "bundle/tap_dumper"
|
||||
@installed_taps ||= T.let(Homebrew::Bundle::TapDumper.tap_names, T.nilable(T::Array[String]))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
require "bundle/tap"
|
||||
Reference in new issue
Block a user