mirror of
https://github.com/Homebrew/brew.git
synced 2025-07-14 16:09:03 +08:00
Merge pull request #19487 from Homebrew/bundle
Migrate Homebrew/bundle to Homebrew/brew
This commit is contained in:
commit
81313133f1
@ -6,15 +6,6 @@
|
|||||||
"workspaceMount": "source=${localWorkspaceFolder},target=/home/linuxbrew/.linuxbrew/Homebrew,type=bind,consistency=cached",
|
"workspaceMount": "source=${localWorkspaceFolder},target=/home/linuxbrew/.linuxbrew/Homebrew,type=bind,consistency=cached",
|
||||||
"onCreateCommand": ".devcontainer/on-create-command.sh",
|
"onCreateCommand": ".devcontainer/on-create-command.sh",
|
||||||
"customizations": {
|
"customizations": {
|
||||||
"codespaces": {
|
|
||||||
"repositories": {
|
|
||||||
"Homebrew/homebrew-bundle": {
|
|
||||||
"permissions": {
|
|
||||||
"contents": "write"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"vscode": {
|
"vscode": {
|
||||||
// Installing all necessary extensions for vscode
|
// Installing all necessary extensions for vscode
|
||||||
// Taken from: .vscode/extensions.json
|
// Taken from: .vscode/extensions.json
|
||||||
|
@ -23,8 +23,6 @@ brew cleanup
|
|||||||
|
|
||||||
# actually tap homebrew/core, no longer done by default
|
# actually tap homebrew/core, no longer done by default
|
||||||
brew tap --force homebrew/core
|
brew tap --force homebrew/core
|
||||||
# tap some other repos so codespaces can be used for developing multiple taps
|
|
||||||
brew tap homebrew/bundle
|
|
||||||
|
|
||||||
# install some useful development things
|
# install some useful development things
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
|
4
.github/workflows/tests.yml
vendored
4
.github/workflows/tests.yml
vendored
@ -113,7 +113,6 @@ jobs:
|
|||||||
|
|
||||||
- name: Set up all Homebrew taps
|
- name: Set up all Homebrew taps
|
||||||
run: |
|
run: |
|
||||||
brew tap homebrew/bundle
|
|
||||||
brew tap homebrew/command-not-found
|
brew tap homebrew/command-not-found
|
||||||
brew tap homebrew/portable-ruby
|
brew tap homebrew/portable-ruby
|
||||||
|
|
||||||
@ -122,8 +121,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Run brew style on official taps
|
- name: Run brew style on official taps
|
||||||
run: |
|
run: |
|
||||||
brew style homebrew/bundle \
|
brew style homebrew/test-bot
|
||||||
homebrew/test-bot
|
|
||||||
|
|
||||||
brew style homebrew/command-not-found \
|
brew style homebrew/command-not-found \
|
||||||
homebrew/portable-ruby
|
homebrew/portable-ruby
|
||||||
|
@ -297,6 +297,7 @@ Sorbet/StrictSigil:
|
|||||||
- "Homebrew/utils/ruby_check_version_script.rb" # A standalone script.
|
- "Homebrew/utils/ruby_check_version_script.rb" # A standalone script.
|
||||||
- "Homebrew/{standalone,startup}/*.rb" # These are loaded before sorbet-runtime
|
- "Homebrew/{standalone,startup}/*.rb" # These are loaded before sorbet-runtime
|
||||||
- "Homebrew/test/**/*.rb"
|
- "Homebrew/test/**/*.rb"
|
||||||
|
- "Homebrew/bundle/{brew_dumper,checker,commands/exec}.rb" # These aren't typed: true yet.
|
||||||
|
|
||||||
Sorbet/TrueSigil:
|
Sorbet/TrueSigil:
|
||||||
Enabled: true
|
Enabled: true
|
||||||
|
40
Library/Homebrew/bundle.rb
Normal file
40
Library/Homebrew/bundle.rb
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# typed: strict
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "bundle/brewfile"
|
||||||
|
require "bundle/bundle"
|
||||||
|
require "bundle/dsl"
|
||||||
|
require "bundle/adder"
|
||||||
|
require "bundle/checker"
|
||||||
|
require "bundle/remover"
|
||||||
|
require "bundle/skipper"
|
||||||
|
require "bundle/brew_services"
|
||||||
|
require "bundle/brew_service_checker"
|
||||||
|
require "bundle/brew_installer"
|
||||||
|
require "bundle/brew_checker"
|
||||||
|
require "bundle/cask_installer"
|
||||||
|
require "bundle/mac_app_store_installer"
|
||||||
|
require "bundle/mac_app_store_checker"
|
||||||
|
require "bundle/tap_installer"
|
||||||
|
require "bundle/brew_dumper"
|
||||||
|
require "bundle/cask_dumper"
|
||||||
|
require "bundle/cask_checker"
|
||||||
|
require "bundle/mac_app_store_dumper"
|
||||||
|
require "bundle/tap_dumper"
|
||||||
|
require "bundle/tap_checker"
|
||||||
|
require "bundle/dumper"
|
||||||
|
require "bundle/installer"
|
||||||
|
require "bundle/lister"
|
||||||
|
require "bundle/commands/install"
|
||||||
|
require "bundle/commands/dump"
|
||||||
|
require "bundle/commands/cleanup"
|
||||||
|
require "bundle/commands/check"
|
||||||
|
require "bundle/commands/exec"
|
||||||
|
require "bundle/commands/list"
|
||||||
|
require "bundle/commands/add"
|
||||||
|
require "bundle/commands/remove"
|
||||||
|
require "bundle/whalebrew_installer"
|
||||||
|
require "bundle/whalebrew_dumper"
|
||||||
|
require "bundle/vscode_extension_checker"
|
||||||
|
require "bundle/vscode_extension_dumper"
|
||||||
|
require "bundle/vscode_extension_installer"
|
31
Library/Homebrew/bundle/adder.rb
Normal file
31
Library/Homebrew/bundle/adder.rb
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module Bundle
|
||||||
|
module Adder
|
||||||
|
module_function
|
||||||
|
|
||||||
|
def add(*args, type:, global:, file:)
|
||||||
|
brewfile = Brewfile.read(global:, file:)
|
||||||
|
content = brewfile.input
|
||||||
|
# TODO: - support `:describe`
|
||||||
|
new_content = args.map do |arg|
|
||||||
|
case type
|
||||||
|
when :brew
|
||||||
|
Formulary.factory(arg)
|
||||||
|
when :cask
|
||||||
|
Cask::CaskLoader.load(arg)
|
||||||
|
end
|
||||||
|
|
||||||
|
"#{type} \"#{arg}\""
|
||||||
|
end
|
||||||
|
|
||||||
|
content << new_content.join("\n") << "\n"
|
||||||
|
path = Dumper.brewfile_path(global:, file:)
|
||||||
|
|
||||||
|
Dumper.write_file path, content
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
17
Library/Homebrew/bundle/brew_checker.rb
Normal file
17
Library/Homebrew/bundle/brew_checker.rb
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module Bundle
|
||||||
|
module Checker
|
||||||
|
class BrewChecker < Homebrew::Bundle::Checker::Base
|
||||||
|
PACKAGE_TYPE = :brew
|
||||||
|
PACKAGE_TYPE_NAME = "Formula"
|
||||||
|
|
||||||
|
def installed_and_up_to_date?(formula, no_upgrade: false)
|
||||||
|
Homebrew::Bundle::BrewInstaller.formula_installed_and_up_to_date?(formula, no_upgrade:)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
240
Library/Homebrew/bundle/brew_dumper.rb
Normal file
240
Library/Homebrew/bundle/brew_dumper.rb
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
# typed: false # rubocop:todo Sorbet/TrueSigil
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "json"
|
||||||
|
require "tsort"
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module Bundle
|
||||||
|
# TODO: refactor into multiple modules
|
||||||
|
module BrewDumper
|
||||||
|
module_function
|
||||||
|
|
||||||
|
def reset!
|
||||||
|
Homebrew::Bundle::BrewServices.reset!
|
||||||
|
@formulae = nil
|
||||||
|
@formulae_by_full_name = nil
|
||||||
|
@formulae_by_name = nil
|
||||||
|
@formula_aliases = nil
|
||||||
|
@formula_oldnames = nil
|
||||||
|
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(&method(:add_formula))
|
||||||
|
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)
|
||||||
|
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 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
|
||||||
|
|
||||||
|
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
|
||||||
|
private_class_method :add_formula
|
||||||
|
|
||||||
|
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
|
||||||
|
private_class_method :formula_to_hash
|
||||||
|
|
||||||
|
class Topo < Hash
|
||||||
|
include TSort
|
||||||
|
alias tsort_each_node each_key
|
||||||
|
def tsort_each_child(node, &block)
|
||||||
|
fetch(node.downcase).sort.each(&block)
|
||||||
|
end
|
||||||
|
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]}
|
||||||
|
#{cycle_last}: #{topo[cycle_last]}
|
||||||
|
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
|
||||||
|
private_class_method :sort!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
289
Library/Homebrew/bundle/brew_installer.rb
Normal file
289
Library/Homebrew/bundle/brew_installer.rb
Normal file
@ -0,0 +1,289 @@
|
|||||||
|
# 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
|
51
Library/Homebrew/bundle/brew_service_checker.rb
Normal file
51
Library/Homebrew/bundle/brew_service_checker.rb
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
# 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)
|
||||||
|
Homebrew::Bundle::BrewInstaller.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)
|
||||||
|
Homebrew::Bundle::BrewServices.started?(service_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def lookup_old_name(service_name)
|
||||||
|
@old_names ||= Homebrew::Bundle::BrewDumper.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
|
55
Library/Homebrew/bundle/brew_services.rb
Normal file
55
Library/Homebrew/bundle/brew_services.rb
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module Bundle
|
||||||
|
module BrewServices
|
||||||
|
module_function
|
||||||
|
|
||||||
|
def reset!
|
||||||
|
@started_services = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def stop(name, verbose: false)
|
||||||
|
return true unless started?(name)
|
||||||
|
|
||||||
|
return unless Bundle.brew("services", "stop", name, verbose:)
|
||||||
|
|
||||||
|
started_services.delete(name)
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def start(name, verbose: false)
|
||||||
|
return unless Bundle.brew("services", "start", name, verbose:)
|
||||||
|
|
||||||
|
started_services << name
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def restart(name, verbose: false)
|
||||||
|
return unless Bundle.brew("services", "restart", name, verbose:)
|
||||||
|
|
||||||
|
started_services << name
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def started?(name)
|
||||||
|
started_services.include? name
|
||||||
|
end
|
||||||
|
|
||||||
|
def started_services
|
||||||
|
@started_services ||= if Bundle.services_installed?
|
||||||
|
states_to_skip = %w[stopped none]
|
||||||
|
Utils.safe_popen_read("brew", "services", "list").lines.filter_map do |line|
|
||||||
|
name, state, _plist = line.split(/\s+/)
|
||||||
|
next if states_to_skip.include? state
|
||||||
|
|
||||||
|
name
|
||||||
|
end
|
||||||
|
else
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
56
Library/Homebrew/bundle/brewfile.rb
Normal file
56
Library/Homebrew/bundle/brewfile.rb
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module Bundle
|
||||||
|
module Brewfile
|
||||||
|
module_function
|
||||||
|
|
||||||
|
def path(dash_writes_to_stdout: false, global: false, file: nil)
|
||||||
|
env_bundle_file_global = ENV.fetch("HOMEBREW_BUNDLE_FILE_GLOBAL", nil)
|
||||||
|
env_bundle_file = ENV.fetch("HOMEBREW_BUNDLE_FILE", nil)
|
||||||
|
user_config_home = ENV.fetch("HOMEBREW_USER_CONFIG_HOME", nil)
|
||||||
|
|
||||||
|
filename = if global
|
||||||
|
if env_bundle_file_global.present?
|
||||||
|
env_bundle_file_global
|
||||||
|
else
|
||||||
|
raise "'HOMEBREW_BUNDLE_FILE' cannot be specified with '--global'" if env_bundle_file.present?
|
||||||
|
|
||||||
|
if user_config_home && File.exist?("#{user_config_home}/Brewfile")
|
||||||
|
"#{user_config_home}/Brewfile"
|
||||||
|
else
|
||||||
|
Bundle.exchange_uid_if_needed! do
|
||||||
|
"#{Dir.home}/.Brewfile"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elsif file.present?
|
||||||
|
handle_file_value(file, dash_writes_to_stdout)
|
||||||
|
elsif env_bundle_file.present?
|
||||||
|
env_bundle_file
|
||||||
|
else
|
||||||
|
"Brewfile"
|
||||||
|
end
|
||||||
|
|
||||||
|
Pathname.new(filename).expand_path(Dir.pwd)
|
||||||
|
end
|
||||||
|
|
||||||
|
def read(global: false, file: nil)
|
||||||
|
Homebrew::Bundle::Dsl.new(Brewfile.path(global:, file:))
|
||||||
|
rescue Errno::ENOENT
|
||||||
|
raise "No Brewfile found"
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_file_value(filename, dash_writes_to_stdout)
|
||||||
|
if filename != "-"
|
||||||
|
filename
|
||||||
|
elsif dash_writes_to_stdout
|
||||||
|
"/dev/stdout"
|
||||||
|
else
|
||||||
|
"/dev/stdin"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
7
Library/Homebrew/bundle/brewfile.rbi
Normal file
7
Library/Homebrew/bundle/brewfile.rbi
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# typed: strict
|
||||||
|
|
||||||
|
module Homebrew::Bundle
|
||||||
|
module Brewfile
|
||||||
|
include Kernel
|
||||||
|
end
|
||||||
|
end
|
86
Library/Homebrew/bundle/bundle.rb
Normal file
86
Library/Homebrew/bundle/bundle.rb
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "English"
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module Bundle
|
||||||
|
class << self
|
||||||
|
def system(cmd, *args, verbose: false)
|
||||||
|
return super cmd, *args if verbose
|
||||||
|
|
||||||
|
logs = []
|
||||||
|
success = T.let(nil, T.nilable(T::Boolean))
|
||||||
|
IO.popen([cmd, *args], err: [:child, :out]) do |pipe|
|
||||||
|
while (buf = pipe.gets)
|
||||||
|
logs << buf
|
||||||
|
end
|
||||||
|
Process.wait(pipe.pid)
|
||||||
|
success = $CHILD_STATUS.success?
|
||||||
|
pipe.close
|
||||||
|
end
|
||||||
|
puts logs.join unless success
|
||||||
|
success
|
||||||
|
end
|
||||||
|
|
||||||
|
def brew(*args, verbose: false)
|
||||||
|
system(HOMEBREW_BREW_FILE, *args, verbose:)
|
||||||
|
end
|
||||||
|
|
||||||
|
def mas_installed?
|
||||||
|
@mas_installed ||= which_formula("mas")
|
||||||
|
end
|
||||||
|
|
||||||
|
def vscode_installed?
|
||||||
|
@vscode_installed ||= which("code").present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def whalebrew_installed?
|
||||||
|
@whalebrew_installed ||= which_formula("whalebrew")
|
||||||
|
end
|
||||||
|
|
||||||
|
def cask_installed?
|
||||||
|
@cask_installed ||= File.directory?("#{HOMEBREW_PREFIX}/Caskroom") &&
|
||||||
|
(File.directory?("#{HOMEBREW_LIBRARY}/Taps/homebrew/homebrew-cask") ||
|
||||||
|
!Homebrew::EnvConfig.no_install_from_api?)
|
||||||
|
end
|
||||||
|
|
||||||
|
def services_installed?
|
||||||
|
@services_installed ||= which("services.rb").present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def which_formula(name)
|
||||||
|
formula = Formulary.factory(name)
|
||||||
|
ENV["PATH"] = "#{formula.opt_bin}:#{ENV.fetch("PATH", nil)}" if formula.any_version_installed?
|
||||||
|
which(name).present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def exchange_uid_if_needed!(&block)
|
||||||
|
euid = Process.euid
|
||||||
|
uid = Process.uid
|
||||||
|
return yield if euid == uid
|
||||||
|
|
||||||
|
old_euid = euid
|
||||||
|
process_reexchangeable = Process::UID.re_exchangeable?
|
||||||
|
if process_reexchangeable
|
||||||
|
Process::UID.re_exchange
|
||||||
|
else
|
||||||
|
Process::Sys.seteuid(uid)
|
||||||
|
end
|
||||||
|
|
||||||
|
home = T.must(Etc.getpwuid(Process.uid)).dir
|
||||||
|
return_value = with_env("HOME" => home, &block)
|
||||||
|
|
||||||
|
if process_reexchangeable
|
||||||
|
Process::UID.re_exchange
|
||||||
|
else
|
||||||
|
Process::Sys.seteuid(old_euid)
|
||||||
|
end
|
||||||
|
|
||||||
|
return_value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
require "extend/os/bundle/bundle"
|
17
Library/Homebrew/bundle/cask_checker.rb
Normal file
17
Library/Homebrew/bundle/cask_checker.rb
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module Bundle
|
||||||
|
module Checker
|
||||||
|
class CaskChecker < Homebrew::Bundle::Checker::Base
|
||||||
|
PACKAGE_TYPE = :cask
|
||||||
|
PACKAGE_TYPE_NAME = "Cask"
|
||||||
|
|
||||||
|
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
|
74
Library/Homebrew/bundle/cask_dumper.rb
Normal file
74
Library/Homebrew/bundle/cask_dumper.rb
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module Bundle
|
||||||
|
module CaskDumper
|
||||||
|
module_function
|
||||||
|
|
||||||
|
def reset!
|
||||||
|
@casks = nil
|
||||||
|
@cask_names = nil
|
||||||
|
@cask_hash = nil
|
||||||
|
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 { |c| c.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}\"#{config}"
|
||||||
|
end.join("\n")
|
||||||
|
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
|
||||||
|
|
||||||
|
def casks
|
||||||
|
return [] unless Bundle.cask_installed?
|
||||||
|
|
||||||
|
require "cask/caskroom"
|
||||||
|
@casks ||= Cask::Caskroom.casks
|
||||||
|
end
|
||||||
|
private_class_method :casks
|
||||||
|
|
||||||
|
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 = cask_config.explicit.fetch(:languages, []).join(",")
|
||||||
|
end
|
||||||
|
"#{key}: \"#{value.to_s.sub(/^#{Dir.home}/, "~")}\""
|
||||||
|
end.join(", ")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
7
Library/Homebrew/bundle/cask_dumper.rbi
Normal file
7
Library/Homebrew/bundle/cask_dumper.rbi
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# typed: strict
|
||||||
|
|
||||||
|
module Homebrew::Bundle
|
||||||
|
module CaskDumper
|
||||||
|
include Kernel
|
||||||
|
end
|
||||||
|
end
|
110
Library/Homebrew/bundle/cask_installer.rb
Normal file
110
Library/Homebrew/bundle/cask_installer.rb
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module Bundle
|
||||||
|
module CaskInstaller
|
||||||
|
module_function
|
||||||
|
|
||||||
|
def reset!
|
||||||
|
@installed_casks = nil
|
||||||
|
@outdated_casks = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def upgrading?(no_upgrade, name, options)
|
||||||
|
return false if no_upgrade
|
||||||
|
return true if outdated_casks.include?(name)
|
||||||
|
return false unless options[:greedy]
|
||||||
|
|
||||||
|
Homebrew::Bundle::CaskDumper.cask_is_outdated_using_greedy?(name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def preinstall(name, no_upgrade: false, verbose: false, **options)
|
||||||
|
if installed_casks.include?(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)
|
||||||
|
|
||||||
|
p [:installed_casks, installed_casks]
|
||||||
|
p [:upgrading?, upgrading?(no_upgrade, name, options)]
|
||||||
|
install_result = if installed_casks.include?(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
|
||||||
|
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 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 cask_installed?(cask)
|
||||||
|
installed_casks.include? cask
|
||||||
|
end
|
||||||
|
|
||||||
|
def cask_upgradable?(cask)
|
||||||
|
outdated_casks.include? cask
|
||||||
|
end
|
||||||
|
|
||||||
|
def installed_casks
|
||||||
|
@installed_casks ||= Homebrew::Bundle::CaskDumper.cask_names
|
||||||
|
end
|
||||||
|
|
||||||
|
def outdated_casks
|
||||||
|
@outdated_casks ||= Homebrew::Bundle::CaskDumper.outdated_cask_names
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
7
Library/Homebrew/bundle/cask_installer.rbi
Normal file
7
Library/Homebrew/bundle/cask_installer.rbi
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# typed: strict
|
||||||
|
|
||||||
|
module Homebrew::Bundle
|
||||||
|
module CaskInstaller
|
||||||
|
include Kernel
|
||||||
|
end
|
||||||
|
end
|
144
Library/Homebrew/bundle/checker.rb
Normal file
144
Library/Homebrew/bundle/checker.rb
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
# typed: false # rubocop:todo Sorbet/TrueSigil
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module Bundle
|
||||||
|
module Checker
|
||||||
|
class Base
|
||||||
|
# Implement these in any subclass
|
||||||
|
# PACKAGE_TYPE = :pkg
|
||||||
|
# PACKAGE_TYPE_NAME = "Package"
|
||||||
|
|
||||||
|
def exit_early_check(packages, no_upgrade:)
|
||||||
|
work_to_be_done = packages.find do |pkg|
|
||||||
|
!installed_and_up_to_date?(pkg, no_upgrade:)
|
||||||
|
end
|
||||||
|
|
||||||
|
Array(work_to_be_done)
|
||||||
|
end
|
||||||
|
|
||||||
|
def failure_reason(name, no_upgrade:)
|
||||||
|
reason = if no_upgrade
|
||||||
|
"needs to be installed."
|
||||||
|
else
|
||||||
|
"needs to be installed or updated."
|
||||||
|
end
|
||||||
|
"#{self.class::PACKAGE_TYPE_NAME} #{name} #{reason}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def full_check(packages, no_upgrade:)
|
||||||
|
packages.reject { |pkg| installed_and_up_to_date?(pkg, no_upgrade:) }
|
||||||
|
.map { |pkg| failure_reason(pkg, no_upgrade:) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def checkable_entries(all_entries)
|
||||||
|
all_entries.select { |e| e.type == self.class::PACKAGE_TYPE }
|
||||||
|
.reject(&Bundle::Skipper.method(:skip?))
|
||||||
|
end
|
||||||
|
|
||||||
|
def format_checkable(entries)
|
||||||
|
checkable_entries(entries).map(&:name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def installed_and_up_to_date?(_pkg, no_upgrade: false)
|
||||||
|
raise NotImplementedError
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_actionable(entries, exit_on_first_error: false, no_upgrade: false, verbose: false)
|
||||||
|
requested = format_checkable entries
|
||||||
|
|
||||||
|
if exit_on_first_error
|
||||||
|
exit_early_check(requested, no_upgrade:)
|
||||||
|
else
|
||||||
|
full_check(requested, no_upgrade:)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module_function
|
||||||
|
|
||||||
|
CheckResult = Struct.new :work_to_be_done, :errors
|
||||||
|
|
||||||
|
CHECKS = {
|
||||||
|
taps_to_tap: "Taps",
|
||||||
|
casks_to_install: "Casks",
|
||||||
|
extensions_to_install: "VSCode Extensions",
|
||||||
|
apps_to_install: "Apps",
|
||||||
|
formulae_to_install: "Formulae",
|
||||||
|
formulae_to_start: "Services",
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
def check(global: false, file: nil, exit_on_first_error: false, no_upgrade: false, verbose: false)
|
||||||
|
@dsl ||= Brewfile.read(global:, file:)
|
||||||
|
|
||||||
|
check_method_names = CHECKS.keys
|
||||||
|
|
||||||
|
errors = []
|
||||||
|
enumerator = exit_on_first_error ? :find : :map
|
||||||
|
|
||||||
|
work_to_be_done = check_method_names.public_send(enumerator) do |check_method|
|
||||||
|
check_errors =
|
||||||
|
send(check_method, exit_on_first_error:, no_upgrade:, verbose:)
|
||||||
|
any_errors = check_errors.any?
|
||||||
|
errors.concat(check_errors) if any_errors
|
||||||
|
any_errors
|
||||||
|
end
|
||||||
|
|
||||||
|
work_to_be_done = Array(work_to_be_done).flatten.any?
|
||||||
|
|
||||||
|
CheckResult.new work_to_be_done, errors
|
||||||
|
end
|
||||||
|
|
||||||
|
def casks_to_install(exit_on_first_error: false, no_upgrade: false, verbose: false)
|
||||||
|
Homebrew::Bundle::Checker::CaskChecker.new.find_actionable(
|
||||||
|
@dsl.entries,
|
||||||
|
exit_on_first_error:, no_upgrade:, verbose:,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def formulae_to_install(exit_on_first_error: false, no_upgrade: false, verbose: false)
|
||||||
|
Homebrew::Bundle::Checker::BrewChecker.new.find_actionable(
|
||||||
|
@dsl.entries,
|
||||||
|
exit_on_first_error:, no_upgrade:, verbose:,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def taps_to_tap(exit_on_first_error: false, no_upgrade: false, verbose: false)
|
||||||
|
Homebrew::Bundle::Checker::TapChecker.new.find_actionable(
|
||||||
|
@dsl.entries,
|
||||||
|
exit_on_first_error:, no_upgrade:, verbose:,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def apps_to_install(exit_on_first_error: false, no_upgrade: false, verbose: false)
|
||||||
|
Homebrew::Bundle::Checker::MacAppStoreChecker.new.find_actionable(
|
||||||
|
@dsl.entries,
|
||||||
|
exit_on_first_error:, no_upgrade:, verbose:,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def extensions_to_install(exit_on_first_error: false, no_upgrade: false, verbose: false)
|
||||||
|
Homebrew::Bundle::Checker::VscodeExtensionChecker.new.find_actionable(
|
||||||
|
@dsl.entries,
|
||||||
|
exit_on_first_error:, no_upgrade:, verbose:,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def formulae_to_start(exit_on_first_error: false, no_upgrade: false, verbose: false)
|
||||||
|
Homebrew::Bundle::Checker::BrewServiceChecker.new.find_actionable(
|
||||||
|
@dsl.entries,
|
||||||
|
exit_on_first_error:, no_upgrade:, verbose:,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def reset!
|
||||||
|
@dsl = nil
|
||||||
|
Homebrew::Bundle::CaskDumper.reset!
|
||||||
|
Homebrew::Bundle::BrewDumper.reset!
|
||||||
|
Homebrew::Bundle::MacAppStoreDumper.reset!
|
||||||
|
Homebrew::Bundle::TapDumper.reset!
|
||||||
|
Homebrew::Bundle::BrewServices.reset!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
16
Library/Homebrew/bundle/commands/add.rb
Normal file
16
Library/Homebrew/bundle/commands/add.rb
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module Bundle
|
||||||
|
module Commands
|
||||||
|
module Add
|
||||||
|
module_function
|
||||||
|
|
||||||
|
def run(*args, type:, global:, file:)
|
||||||
|
Homebrew::Bundle::Adder.add(*args, type:, global:, file:)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
34
Library/Homebrew/bundle/commands/check.rb
Normal file
34
Library/Homebrew/bundle/commands/check.rb
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module Bundle
|
||||||
|
module Commands
|
||||||
|
module Check
|
||||||
|
module_function
|
||||||
|
|
||||||
|
ARROW = "→"
|
||||||
|
FAILURE_MESSAGE = "brew bundle can't satisfy your Brewfile's dependencies."
|
||||||
|
|
||||||
|
def run(global: false, file: nil, no_upgrade: false, verbose: false)
|
||||||
|
output_errors = verbose
|
||||||
|
exit_on_first_error = !verbose
|
||||||
|
check_result = Homebrew::Bundle::Checker.check(
|
||||||
|
global:, file:,
|
||||||
|
exit_on_first_error:, no_upgrade:, verbose:
|
||||||
|
)
|
||||||
|
|
||||||
|
if check_result.work_to_be_done
|
||||||
|
puts FAILURE_MESSAGE
|
||||||
|
|
||||||
|
check_result.errors.each { |package| puts "#{ARROW} #{package}" } if output_errors
|
||||||
|
puts "Satisfy missing dependencies with `brew bundle install`."
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
puts "The Brewfile's dependencies are satisfied."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
191
Library/Homebrew/bundle/commands/cleanup.rb
Normal file
191
Library/Homebrew/bundle/commands/cleanup.rb
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "utils/formatter"
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module Bundle
|
||||||
|
module Commands
|
||||||
|
# TODO: refactor into multiple modules
|
||||||
|
module Cleanup
|
||||||
|
module_function
|
||||||
|
|
||||||
|
def reset!
|
||||||
|
@dsl = nil
|
||||||
|
@kept_casks = nil
|
||||||
|
@kept_formulae = nil
|
||||||
|
Homebrew::Bundle::CaskDumper.reset!
|
||||||
|
Homebrew::Bundle::BrewDumper.reset!
|
||||||
|
Homebrew::Bundle::TapDumper.reset!
|
||||||
|
Homebrew::Bundle::VscodeExtensionDumper.reset!
|
||||||
|
Homebrew::Bundle::BrewServices.reset!
|
||||||
|
end
|
||||||
|
|
||||||
|
def run(global: false, file: nil, force: false, zap: false, dsl: nil)
|
||||||
|
@dsl ||= dsl
|
||||||
|
|
||||||
|
casks = casks_to_uninstall(global:, file:)
|
||||||
|
formulae = formulae_to_uninstall(global:, file:)
|
||||||
|
taps = taps_to_untap(global:, file:)
|
||||||
|
vscode_extensions = vscode_extensions_to_uninstall(global:, file:)
|
||||||
|
if force
|
||||||
|
if casks.any?
|
||||||
|
args = zap ? ["--zap"] : []
|
||||||
|
Kernel.system HOMEBREW_BREW_FILE, "uninstall", "--cask", *args, "--force", *casks
|
||||||
|
puts "Uninstalled #{casks.size} cask#{(casks.size == 1) ? "" : "s"}"
|
||||||
|
end
|
||||||
|
|
||||||
|
if formulae.any?
|
||||||
|
Kernel.system HOMEBREW_BREW_FILE, "uninstall", "--formula", "--force", *formulae
|
||||||
|
puts "Uninstalled #{formulae.size} formula#{(formulae.size == 1) ? "" : "e"}"
|
||||||
|
end
|
||||||
|
|
||||||
|
Kernel.system HOMEBREW_BREW_FILE, "untap", *taps if taps.any?
|
||||||
|
|
||||||
|
Bundle.exchange_uid_if_needed! do
|
||||||
|
vscode_extensions.each do |extension|
|
||||||
|
Kernel.system "code", "--uninstall-extension", extension
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
cleanup = system_output_no_stderr(HOMEBREW_BREW_FILE, "cleanup")
|
||||||
|
puts cleanup unless cleanup.empty?
|
||||||
|
else
|
||||||
|
would_uninstall = false
|
||||||
|
|
||||||
|
if casks.any?
|
||||||
|
puts "Would uninstall casks:"
|
||||||
|
puts Formatter.columns casks
|
||||||
|
would_uninstall = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if formulae.any?
|
||||||
|
puts "Would uninstall formulae:"
|
||||||
|
puts Formatter.columns formulae
|
||||||
|
would_uninstall = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if taps.any?
|
||||||
|
puts "Would untap:"
|
||||||
|
puts Formatter.columns taps
|
||||||
|
would_uninstall = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if vscode_extensions.any?
|
||||||
|
puts "Would uninstall VSCode extensions:"
|
||||||
|
puts Formatter.columns vscode_extensions
|
||||||
|
would_uninstall = true
|
||||||
|
end
|
||||||
|
|
||||||
|
cleanup = system_output_no_stderr(HOMEBREW_BREW_FILE, "cleanup", "--dry-run")
|
||||||
|
unless cleanup.empty?
|
||||||
|
puts "Would `brew cleanup`:"
|
||||||
|
puts cleanup
|
||||||
|
end
|
||||||
|
|
||||||
|
puts "Run `brew bundle cleanup --force` to make these changes." if would_uninstall || !cleanup.empty?
|
||||||
|
exit 1 if would_uninstall
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def casks_to_uninstall(global: false, file: nil)
|
||||||
|
Homebrew::Bundle::CaskDumper.cask_names - kept_casks(global:, file:)
|
||||||
|
end
|
||||||
|
|
||||||
|
def formulae_to_uninstall(global: false, file: nil)
|
||||||
|
kept_formulae = self.kept_formulae(global:, file:)
|
||||||
|
|
||||||
|
current_formulae = Homebrew::Bundle::BrewDumper.formulae
|
||||||
|
current_formulae.reject! do |f|
|
||||||
|
Homebrew::Bundle::BrewInstaller.formula_in_array?(f[:full_name], kept_formulae)
|
||||||
|
end
|
||||||
|
current_formulae.map { |f| f[:full_name] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def kept_formulae(global: false, file: nil)
|
||||||
|
@kept_formulae ||= begin
|
||||||
|
@dsl ||= Brewfile.read(global:, file:)
|
||||||
|
|
||||||
|
kept_formulae = @dsl.entries.select { |e| e.type == :brew }.map(&:name)
|
||||||
|
kept_formulae += Homebrew::Bundle::CaskDumper.formula_dependencies(kept_casks)
|
||||||
|
kept_formulae.map! do |f|
|
||||||
|
Homebrew::Bundle::BrewDumper.formula_aliases[f] ||
|
||||||
|
Homebrew::Bundle::BrewDumper.formula_oldnames[f] ||
|
||||||
|
f
|
||||||
|
end
|
||||||
|
|
||||||
|
kept_formulae + recursive_dependencies(Homebrew::Bundle::BrewDumper.formulae, kept_formulae)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def kept_casks(global: false, file: nil)
|
||||||
|
return @kept_casks if @kept_casks
|
||||||
|
|
||||||
|
@dsl ||= Brewfile.read(global:, file:)
|
||||||
|
@kept_casks = @dsl.entries.select { |e| e.type == :cask }.map(&:name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def recursive_dependencies(current_formulae, formulae_names, top_level: true)
|
||||||
|
@checked_formulae_names = [] if top_level
|
||||||
|
dependencies = T.let([], T::Array[Formula])
|
||||||
|
|
||||||
|
formulae_names.each do |name|
|
||||||
|
next if @checked_formulae_names.include?(name)
|
||||||
|
|
||||||
|
formula = current_formulae.find { |f| f[:full_name] == name }
|
||||||
|
next unless formula
|
||||||
|
|
||||||
|
f_deps = formula[:dependencies]
|
||||||
|
unless formula[:poured_from_bottle?]
|
||||||
|
f_deps += formula[:build_dependencies]
|
||||||
|
f_deps.uniq!
|
||||||
|
end
|
||||||
|
next unless f_deps
|
||||||
|
next if f_deps.empty?
|
||||||
|
|
||||||
|
@checked_formulae_names << name
|
||||||
|
f_deps += recursive_dependencies(current_formulae, f_deps, top_level: false)
|
||||||
|
dependencies += f_deps
|
||||||
|
end
|
||||||
|
|
||||||
|
dependencies.uniq
|
||||||
|
end
|
||||||
|
|
||||||
|
IGNORED_TAPS = %w[homebrew/core homebrew/bundle].freeze
|
||||||
|
|
||||||
|
def taps_to_untap(global: false, file: nil)
|
||||||
|
@dsl ||= Brewfile.read(global:, file:)
|
||||||
|
kept_formulae = self.kept_formulae(global:, file:).filter_map(&method(:lookup_formula))
|
||||||
|
kept_taps = @dsl.entries.select { |e| e.type == :tap }.map(&:name)
|
||||||
|
kept_taps += kept_formulae.filter_map(&:tap).map(&:name)
|
||||||
|
current_taps = Homebrew::Bundle::TapDumper.tap_names
|
||||||
|
current_taps - kept_taps - IGNORED_TAPS
|
||||||
|
end
|
||||||
|
|
||||||
|
def lookup_formula(formula)
|
||||||
|
Formulary.factory(formula)
|
||||||
|
rescue TapFormulaUnavailableError
|
||||||
|
# ignore these as an unavailable formula implies there is no tap to worry about
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def vscode_extensions_to_uninstall(global: false, file: nil)
|
||||||
|
@dsl ||= Brewfile.read(global:, file:)
|
||||||
|
kept_extensions = @dsl.entries.select { |e| e.type == :vscode }.map { |x| x.name.downcase }
|
||||||
|
|
||||||
|
# To provide a graceful migration from `Brewfile`s that don't yet or
|
||||||
|
# don't want to use `vscode`: don't remove any extensions if we don't
|
||||||
|
# find any in the `Brewfile`.
|
||||||
|
return [].freeze if kept_extensions.empty?
|
||||||
|
|
||||||
|
current_extensions = Homebrew::Bundle::VscodeExtensionDumper.extensions
|
||||||
|
current_extensions - kept_extensions
|
||||||
|
end
|
||||||
|
|
||||||
|
def system_output_no_stderr(cmd, *args)
|
||||||
|
IO.popen([cmd, *args], err: :close).read
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
29
Library/Homebrew/bundle/commands/commands.rbi
Normal file
29
Library/Homebrew/bundle/commands/commands.rbi
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# typed: strict
|
||||||
|
|
||||||
|
module Homebrew::Bundle
|
||||||
|
module Commands
|
||||||
|
module Check
|
||||||
|
include Kernel
|
||||||
|
end
|
||||||
|
|
||||||
|
module Cleanup
|
||||||
|
include Kernel
|
||||||
|
end
|
||||||
|
|
||||||
|
module Dump
|
||||||
|
include Kernel
|
||||||
|
end
|
||||||
|
|
||||||
|
module Exec
|
||||||
|
include Kernel
|
||||||
|
end
|
||||||
|
|
||||||
|
module Install
|
||||||
|
include Kernel
|
||||||
|
end
|
||||||
|
|
||||||
|
module List
|
||||||
|
include Kernel
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
18
Library/Homebrew/bundle/commands/dump.rb
Normal file
18
Library/Homebrew/bundle/commands/dump.rb
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module Bundle
|
||||||
|
module Commands
|
||||||
|
module Dump
|
||||||
|
module_function
|
||||||
|
|
||||||
|
def run(global:, file:, describe:, force:, no_restart:, taps:, brews:, casks:, mas:, whalebrew:, vscode:)
|
||||||
|
Homebrew::Bundle::Dumper.dump_brewfile(
|
||||||
|
global:, file:, describe:, force:, no_restart:, taps:, brews:, casks:, mas:, whalebrew:, vscode:,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
170
Library/Homebrew/bundle/commands/exec.rb
Normal file
170
Library/Homebrew/bundle/commands/exec.rb
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
# typed: false # rubocop:todo Sorbet/TrueSigil
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "exceptions"
|
||||||
|
require "extend/ENV"
|
||||||
|
require "utils"
|
||||||
|
require "PATH"
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module Bundle
|
||||||
|
module Commands
|
||||||
|
module Exec
|
||||||
|
module_function
|
||||||
|
|
||||||
|
# Homebrew's global environment variables that we don't want to leak into
|
||||||
|
# the `brew bundle exec` environment.
|
||||||
|
HOMEBREW_ENV_CLEANUP = %w[
|
||||||
|
HOMEBREW_HELP_MESSAGE
|
||||||
|
HOMEBREW_API_DEFAULT_DOMAIN
|
||||||
|
HOMEBREW_BOTTLE_DEFAULT_DOMAIN
|
||||||
|
HOMEBREW_BREW_DEFAULT_GIT_REMOTE
|
||||||
|
HOMEBREW_CORE_DEFAULT_GIT_REMOTE
|
||||||
|
HOMEBREW_DEFAULT_CACHE
|
||||||
|
HOMEBREW_DEFAULT_LOGS
|
||||||
|
HOMEBREW_DEFAULT_TEMP
|
||||||
|
HOMEBREW_REQUIRED_RUBY_VERSION
|
||||||
|
HOMEBREW_PRODUCT
|
||||||
|
HOMEBREW_SYSTEM
|
||||||
|
HOMEBREW_PROCESSOR
|
||||||
|
HOMEBREW_PHYSICAL_PROCESSOR
|
||||||
|
HOMEBREW_BREWED_CURL_PATH
|
||||||
|
HOMEBREW_USER_AGENT_CURL
|
||||||
|
HOMEBREW_USER_AGENT
|
||||||
|
HOMEBREW_GENERIC_DEFAULT_PREFIX
|
||||||
|
HOMEBREW_GENERIC_DEFAULT_REPOSITORY
|
||||||
|
HOMEBREW_DEFAULT_PREFIX
|
||||||
|
HOMEBREW_DEFAULT_REPOSITORY
|
||||||
|
HOMEBREW_AUTO_UPDATE_COMMAND
|
||||||
|
HOMEBREW_BREW_GIT_REMOTE
|
||||||
|
HOMEBREW_COMMAND_DEPTH
|
||||||
|
HOMEBREW_CORE_GIT_REMOTE
|
||||||
|
HOMEBREW_MACOS_VERSION_NUMERIC
|
||||||
|
HOMEBREW_MINIMUM_GIT_VERSION
|
||||||
|
HOMEBREW_MACOS_NEWEST_UNSUPPORTED
|
||||||
|
HOMEBREW_MACOS_OLDEST_SUPPORTED
|
||||||
|
HOMEBREW_MACOS_OLDEST_ALLOWED
|
||||||
|
HOMEBREW_GITHUB_PACKAGES_AUTH
|
||||||
|
].freeze
|
||||||
|
|
||||||
|
PATH_LIKE_ENV_REGEX = /.+#{File::PATH_SEPARATOR}/
|
||||||
|
|
||||||
|
def run(*args, global: false, file: nil, subcommand: "")
|
||||||
|
# Cleanup Homebrew's global environment
|
||||||
|
HOMEBREW_ENV_CLEANUP.each { |key| ENV.delete(key) }
|
||||||
|
|
||||||
|
# Setup Homebrew's ENV extensions
|
||||||
|
ENV.activate_extensions!
|
||||||
|
raise UsageError, "No command to execute was specified!" if args.blank?
|
||||||
|
|
||||||
|
command = args.first
|
||||||
|
|
||||||
|
# For commands which aren't either absolute or relative
|
||||||
|
if command.exclude? "/"
|
||||||
|
# Save the command path, since this will be blown away by superenv
|
||||||
|
command_path = which(command)
|
||||||
|
raise "command was not found in your PATH: #{command}" if command_path.blank?
|
||||||
|
|
||||||
|
command_path = command_path.dirname.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
@dsl = Brewfile.read(global:, file:)
|
||||||
|
|
||||||
|
require "formula"
|
||||||
|
require "formulary"
|
||||||
|
|
||||||
|
ENV.deps = @dsl.entries.filter_map do |entry|
|
||||||
|
next if entry.type != :brew
|
||||||
|
|
||||||
|
Formulary.factory(entry.name)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Allow setting all dependencies to be keg-only
|
||||||
|
# (i.e. should be explicitly in HOMEBREW_*PATHs ahead of HOMEBREW_PREFIX)
|
||||||
|
ENV.keg_only_deps = if ENV["HOMEBREW_BUNDLE_EXEC_ALL_KEG_ONLY_DEPS"].present?
|
||||||
|
ENV.delete("HOMEBREW_BUNDLE_EXEC_ALL_KEG_ONLY_DEPS")
|
||||||
|
ENV.deps
|
||||||
|
else
|
||||||
|
ENV.deps.select(&:keg_only?)
|
||||||
|
end
|
||||||
|
ENV.setup_build_environment
|
||||||
|
|
||||||
|
# Enable compiler flag filtering
|
||||||
|
ENV.refurbish_args
|
||||||
|
|
||||||
|
# Set up `nodenv`, `pyenv` and `rbenv` if present.
|
||||||
|
env_formulae = %w[nodenv pyenv rbenv]
|
||||||
|
ENV.deps.each do |dep|
|
||||||
|
dep_name = dep.name
|
||||||
|
next unless env_formulae.include?(dep_name)
|
||||||
|
|
||||||
|
dep_root = ENV.fetch("HOMEBREW_#{dep_name.upcase}_ROOT", "#{Dir.home}/.#{dep_name}")
|
||||||
|
ENV.prepend_path "PATH", Pathname.new(dep_root)/"shims"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Setup pkg-config, if present, to help locate packages
|
||||||
|
# Only need this on Linux as Homebrew provides a shim on macOS
|
||||||
|
# TODO: use extend/OS here
|
||||||
|
# rubocop:todo Homebrew/MoveToExtendOS
|
||||||
|
if OS.linux? && (pkgconf = Formulary.factory("pkgconf")) && pkgconf.any_version_installed?
|
||||||
|
ENV.prepend_path "PATH", pkgconf.opt_bin.to_s
|
||||||
|
end
|
||||||
|
# rubocop:enable Homebrew/MoveToExtendOS
|
||||||
|
|
||||||
|
# Ensure the Ruby path we saved goes before anything else, if the command was in the PATH
|
||||||
|
ENV.prepend_path "PATH", command_path if command_path.present?
|
||||||
|
|
||||||
|
# Replace the formula versions from the environment variables
|
||||||
|
formula_versions = {}
|
||||||
|
ENV.each do |key, value|
|
||||||
|
match = key.match(/^HOMEBREW_BUNDLE_EXEC_FORMULA_VERSION_(.+)$/)
|
||||||
|
next if match.blank?
|
||||||
|
|
||||||
|
formula_name = match[1]
|
||||||
|
next if formula_name.blank?
|
||||||
|
|
||||||
|
ENV.delete(key)
|
||||||
|
formula_versions[formula_name.downcase] = value
|
||||||
|
end
|
||||||
|
formula_versions.each do |formula_name, formula_version|
|
||||||
|
ENV.each do |key, value|
|
||||||
|
opt = %r{/opt/#{formula_name}([/:$])}
|
||||||
|
next unless value.match(opt)
|
||||||
|
|
||||||
|
cellar = "/Cellar/#{formula_name}/#{formula_version}\\1"
|
||||||
|
|
||||||
|
# Look for PATH-like environment variables
|
||||||
|
ENV[key] = if key.include?("PATH") && value.match?(PATH_LIKE_ENV_REGEX)
|
||||||
|
rejected_opts = []
|
||||||
|
path = PATH.new(ENV.fetch("PATH"))
|
||||||
|
.reject do |value|
|
||||||
|
rejected_opts << value if value.match?(opt)
|
||||||
|
end
|
||||||
|
rejected_opts.each do |value|
|
||||||
|
path.prepend(value.gsub(opt, cellar))
|
||||||
|
end
|
||||||
|
path.to_s
|
||||||
|
else
|
||||||
|
value.gsub(opt, cellar)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Ensure brew bundle sh/env commands have access to other tools in the PATH
|
||||||
|
if ["sh", "env"].include?(subcommand) && (homebrew_path = ENV.fetch("HOMEBREW_PATH", nil))
|
||||||
|
ENV.append_path "PATH", homebrew_path
|
||||||
|
end
|
||||||
|
|
||||||
|
if subcommand == "env"
|
||||||
|
ENV.each do |key, value|
|
||||||
|
puts "export #{key}=\"#{value}\""
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
exec(*args)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
25
Library/Homebrew/bundle/commands/install.rb
Normal file
25
Library/Homebrew/bundle/commands/install.rb
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module Bundle
|
||||||
|
module Commands
|
||||||
|
module Install
|
||||||
|
module_function
|
||||||
|
|
||||||
|
def run(global: false, file: nil, no_lock: false, no_upgrade: false, verbose: false, force: false,
|
||||||
|
quiet: false)
|
||||||
|
@dsl = Brewfile.read(global:, file:)
|
||||||
|
Homebrew::Bundle::Installer.install(
|
||||||
|
@dsl.entries,
|
||||||
|
global:, file:, no_lock:, no_upgrade:, verbose:, force:, quiet:,
|
||||||
|
) || exit(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
def dsl
|
||||||
|
@dsl
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
20
Library/Homebrew/bundle/commands/list.rb
Normal file
20
Library/Homebrew/bundle/commands/list.rb
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module Bundle
|
||||||
|
module Commands
|
||||||
|
module List
|
||||||
|
module_function
|
||||||
|
|
||||||
|
def run(global:, file:, brews:, casks:, taps:, mas:, whalebrew:, vscode:)
|
||||||
|
parsed_entries = Brewfile.read(global:, file:).entries
|
||||||
|
Homebrew::Bundle::Lister.list(
|
||||||
|
parsed_entries,
|
||||||
|
brews:, casks:, taps:, mas:, whalebrew:, vscode:,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
16
Library/Homebrew/bundle/commands/remove.rb
Normal file
16
Library/Homebrew/bundle/commands/remove.rb
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module Bundle
|
||||||
|
module Commands
|
||||||
|
module Remove
|
||||||
|
module_function
|
||||||
|
|
||||||
|
def run(*args, type:, global:, file:)
|
||||||
|
Homebrew::Bundle::Remover.remove(*args, type:, global:, file:)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
134
Library/Homebrew/bundle/dsl.rb
Normal file
134
Library/Homebrew/bundle/dsl.rb
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module Bundle
|
||||||
|
class Dsl
|
||||||
|
class Entry
|
||||||
|
attr_reader :type, :name, :options
|
||||||
|
|
||||||
|
def initialize(type, name, options = {})
|
||||||
|
@type = type
|
||||||
|
@name = name
|
||||||
|
@options = options
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
attr_reader :entries, :cask_arguments, :input
|
||||||
|
|
||||||
|
def initialize(path)
|
||||||
|
@path = path
|
||||||
|
@input = path.read
|
||||||
|
@entries = []
|
||||||
|
@cask_arguments = {}
|
||||||
|
|
||||||
|
begin
|
||||||
|
process
|
||||||
|
# Want to catch all exceptions for e.g. syntax errors.
|
||||||
|
rescue Exception => e # rubocop:disable Lint/RescueException
|
||||||
|
error_msg = "Invalid Brewfile: #{e.message}"
|
||||||
|
raise RuntimeError, error_msg, e.backtrace
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def process
|
||||||
|
instance_eval(@input, @path.to_s)
|
||||||
|
end
|
||||||
|
|
||||||
|
def cask_args(args)
|
||||||
|
raise "cask_args(#{args.inspect}) should be a Hash object" unless args.is_a? Hash
|
||||||
|
|
||||||
|
@cask_arguments = args
|
||||||
|
end
|
||||||
|
|
||||||
|
def brew(name, options = {})
|
||||||
|
raise "name(#{name.inspect}) should be a String object" unless name.is_a? String
|
||||||
|
raise "options(#{options.inspect}) should be a Hash object" unless options.is_a? Hash
|
||||||
|
|
||||||
|
name = Homebrew::Bundle::Dsl.sanitize_brew_name(name)
|
||||||
|
@entries << Entry.new(:brew, name, options)
|
||||||
|
end
|
||||||
|
|
||||||
|
def cask(name, options = {})
|
||||||
|
raise "name(#{name.inspect}) should be a String object" unless name.is_a? String
|
||||||
|
raise "options(#{options.inspect}) should be a Hash object" unless options.is_a? Hash
|
||||||
|
|
||||||
|
options[:full_name] = name
|
||||||
|
name = Homebrew::Bundle::Dsl.sanitize_cask_name(name)
|
||||||
|
options[:args] = @cask_arguments.merge options.fetch(:args, {})
|
||||||
|
@entries << Entry.new(:cask, name, options)
|
||||||
|
end
|
||||||
|
|
||||||
|
def mas(name, options = {})
|
||||||
|
id = options[:id]
|
||||||
|
raise "name(#{name.inspect}) should be a String object" unless name.is_a? String
|
||||||
|
raise "options[:id](#{id}) should be an Integer object" unless id.is_a? Integer
|
||||||
|
|
||||||
|
@entries << Entry.new(:mas, name, id:)
|
||||||
|
end
|
||||||
|
|
||||||
|
def whalebrew(name)
|
||||||
|
raise "name(#{name.inspect}) should be a String object" unless name.is_a? String
|
||||||
|
|
||||||
|
@entries << Entry.new(:whalebrew, name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def vscode(name)
|
||||||
|
raise "name(#{name.inspect}) should be a String object" unless name.is_a? String
|
||||||
|
|
||||||
|
@entries << Entry.new(:vscode, name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def tap(name, clone_target = nil, options = {})
|
||||||
|
raise "name(#{name.inspect}) should be a String object" unless name.is_a? String
|
||||||
|
if clone_target && !clone_target.is_a?(String)
|
||||||
|
raise "clone_target(#{clone_target.inspect}) should be nil or a String object"
|
||||||
|
end
|
||||||
|
|
||||||
|
options[:clone_target] = clone_target
|
||||||
|
name = Homebrew::Bundle::Dsl.sanitize_tap_name(name)
|
||||||
|
@entries << Entry.new(:tap, name, options)
|
||||||
|
end
|
||||||
|
|
||||||
|
HOMEBREW_TAP_ARGS_REGEX = %r{^([\w-]+)/(homebrew-)?([\w-]+)$}
|
||||||
|
HOMEBREW_CORE_FORMULA_REGEX = %r{^homebrew/homebrew/([\w+-.@]+)$}i
|
||||||
|
HOMEBREW_TAP_FORMULA_REGEX = %r{^([\w-]+)/([\w-]+)/([\w+-.@]+)$}
|
||||||
|
|
||||||
|
def self.sanitize_brew_name(name)
|
||||||
|
name = name.downcase
|
||||||
|
if name =~ HOMEBREW_CORE_FORMULA_REGEX
|
||||||
|
Regexp.last_match(1)
|
||||||
|
elsif name =~ HOMEBREW_TAP_FORMULA_REGEX
|
||||||
|
user = Regexp.last_match(1)
|
||||||
|
repo = T.must(Regexp.last_match(2))
|
||||||
|
name = Regexp.last_match(3)
|
||||||
|
"#{user}/#{repo.sub("homebrew-", "")}/#{name}"
|
||||||
|
else
|
||||||
|
name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.sanitize_tap_name(name)
|
||||||
|
name = name.downcase
|
||||||
|
if name =~ HOMEBREW_TAP_ARGS_REGEX
|
||||||
|
"#{Regexp.last_match(1)}/#{Regexp.last_match(3)}"
|
||||||
|
else
|
||||||
|
name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.sanitize_cask_name(name)
|
||||||
|
name = name.split("/").last if name.include?("/")
|
||||||
|
name.downcase
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.pluralize_dependency(installed_count)
|
||||||
|
(installed_count == 1) ? "dependency" : "dependencies"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
52
Library/Homebrew/bundle/dumper.rb
Normal file
52
Library/Homebrew/bundle/dumper.rb
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "fileutils"
|
||||||
|
require "pathname"
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module Bundle
|
||||||
|
module Dumper
|
||||||
|
module_function
|
||||||
|
|
||||||
|
def can_write_to_brewfile?(brewfile_path, force: false)
|
||||||
|
raise "#{brewfile_path} already exists" if should_not_write_file?(brewfile_path, overwrite: force)
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_brewfile(describe:, no_restart:, brews:, taps:, casks:, mas:, whalebrew:, vscode:)
|
||||||
|
content = []
|
||||||
|
content << TapDumper.dump if taps
|
||||||
|
content << BrewDumper.dump(describe:, no_restart:) if brews
|
||||||
|
content << CaskDumper.dump(describe:) if casks
|
||||||
|
content << MacAppStoreDumper.dump if mas
|
||||||
|
content << WhalebrewDumper.dump if whalebrew
|
||||||
|
content << VscodeExtensionDumper.dump if vscode
|
||||||
|
"#{content.reject(&:empty?).join("\n")}\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
def dump_brewfile(global:, file:, describe:, force:, no_restart:, brews:, taps:, casks:, mas:, whalebrew:,
|
||||||
|
vscode:)
|
||||||
|
path = brewfile_path(global:, file:)
|
||||||
|
can_write_to_brewfile?(path, force:)
|
||||||
|
content = build_brewfile(describe:, no_restart:, taps:, brews:, casks:, mas:, whalebrew:, vscode:)
|
||||||
|
write_file path, content
|
||||||
|
end
|
||||||
|
|
||||||
|
def brewfile_path(global: false, file: nil)
|
||||||
|
Brewfile.path(dash_writes_to_stdout: true, global:, file:)
|
||||||
|
end
|
||||||
|
|
||||||
|
def should_not_write_file?(file, overwrite: false)
|
||||||
|
file.exist? && !overwrite && file.to_s != "/dev/stdout"
|
||||||
|
end
|
||||||
|
|
||||||
|
def write_file(file, content)
|
||||||
|
Bundle.exchange_uid_if_needed! do
|
||||||
|
file.open("w") { |io| io.write content }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
7
Library/Homebrew/bundle/dumper.rbi
Normal file
7
Library/Homebrew/bundle/dumper.rbi
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# typed: strict
|
||||||
|
|
||||||
|
module Homebrew::Bundle
|
||||||
|
module Dumper
|
||||||
|
include Kernel
|
||||||
|
end
|
||||||
|
end
|
77
Library/Homebrew/bundle/installer.rb
Normal file
77
Library/Homebrew/bundle/installer.rb
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module Bundle
|
||||||
|
module Installer
|
||||||
|
module_function
|
||||||
|
|
||||||
|
def install(entries, global: false, file: nil, no_lock: false, no_upgrade: false, verbose: false, force: false,
|
||||||
|
quiet: false)
|
||||||
|
success = 0
|
||||||
|
failure = 0
|
||||||
|
|
||||||
|
entries.each do |entry|
|
||||||
|
name = entry.name
|
||||||
|
args = [name]
|
||||||
|
options = {}
|
||||||
|
verb = "Installing"
|
||||||
|
type = entry.type
|
||||||
|
cls = case type
|
||||||
|
when :brew
|
||||||
|
options = entry.options
|
||||||
|
verb = "Upgrading" if Homebrew::Bundle::BrewInstaller.formula_upgradable?(name)
|
||||||
|
Homebrew::Bundle::BrewInstaller
|
||||||
|
when :cask
|
||||||
|
options = entry.options
|
||||||
|
verb = "Upgrading" if Homebrew::Bundle::CaskInstaller.cask_upgradable?(name)
|
||||||
|
Homebrew::Bundle::CaskInstaller
|
||||||
|
when :mas
|
||||||
|
args << entry.options[:id]
|
||||||
|
Homebrew::Bundle::MacAppStoreInstaller
|
||||||
|
when :whalebrew
|
||||||
|
Homebrew::Bundle::WhalebrewInstaller
|
||||||
|
when :vscode
|
||||||
|
Homebrew::Bundle::VscodeExtensionInstaller
|
||||||
|
when :tap
|
||||||
|
verb = "Tapping"
|
||||||
|
options = entry.options
|
||||||
|
Homebrew::Bundle::TapInstaller
|
||||||
|
end
|
||||||
|
|
||||||
|
next if cls.nil?
|
||||||
|
next if Homebrew::Bundle::Skipper.skip? entry
|
||||||
|
|
||||||
|
preinstall = if cls.preinstall(*args, **options, no_upgrade:, verbose:)
|
||||||
|
puts Formatter.success("#{verb} #{name}")
|
||||||
|
true
|
||||||
|
else
|
||||||
|
puts "Using #{name}" unless quiet
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
if cls.install(*args, **options,
|
||||||
|
preinstall:, no_upgrade:, verbose:, force:)
|
||||||
|
success += 1
|
||||||
|
else
|
||||||
|
$stderr.puts Formatter.error("#{verb} #{name} has failed!")
|
||||||
|
failure += 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
unless failure.zero?
|
||||||
|
dependency = Homebrew::Bundle::Dsl.pluralize_dependency(failure)
|
||||||
|
$stderr.puts Formatter.error "Homebrew Bundle failed! #{failure} Brewfile #{dependency} failed to install"
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
unless quiet
|
||||||
|
dependency = Homebrew::Bundle::Dsl.pluralize_dependency(success)
|
||||||
|
puts Formatter.success "Homebrew Bundle complete! #{success} Brewfile #{dependency} now installed."
|
||||||
|
end
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
7
Library/Homebrew/bundle/installer.rbi
Normal file
7
Library/Homebrew/bundle/installer.rbi
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# typed: strict
|
||||||
|
|
||||||
|
module Homebrew::Bundle
|
||||||
|
module Installer
|
||||||
|
include Kernel
|
||||||
|
end
|
||||||
|
end
|
27
Library/Homebrew/bundle/lister.rb
Normal file
27
Library/Homebrew/bundle/lister.rb
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module Bundle
|
||||||
|
module Lister
|
||||||
|
module_function
|
||||||
|
|
||||||
|
def list(entries, brews:, casks:, taps:, mas:, whalebrew:, vscode:)
|
||||||
|
entries.each do |entry|
|
||||||
|
puts entry.name if show?(entry.type, brews:, casks:, taps:, mas:, whalebrew:, vscode:)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def show?(type, brews:, casks:, taps:, mas:, whalebrew:, vscode:)
|
||||||
|
return true if brews && type == :brew
|
||||||
|
return true if casks && type == :cask
|
||||||
|
return true if taps && type == :tap
|
||||||
|
return true if mas && type == :mas
|
||||||
|
return true if whalebrew && type == :whalebrew
|
||||||
|
return true if vscode && type == :vscode
|
||||||
|
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
7
Library/Homebrew/bundle/lister.rbi
Normal file
7
Library/Homebrew/bundle/lister.rbi
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# typed: strict
|
||||||
|
|
||||||
|
module Homebrew::Bundle
|
||||||
|
module Lister
|
||||||
|
include Kernel
|
||||||
|
end
|
||||||
|
end
|
34
Library/Homebrew/bundle/mac_app_store_checker.rb
Normal file
34
Library/Homebrew/bundle/mac_app_store_checker.rb
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module Bundle
|
||||||
|
module Checker
|
||||||
|
class MacAppStoreChecker < Homebrew::Bundle::Checker::Base
|
||||||
|
PACKAGE_TYPE = :mas
|
||||||
|
PACKAGE_TYPE_NAME = "App"
|
||||||
|
|
||||||
|
def installed_and_up_to_date?(id, no_upgrade: false)
|
||||||
|
Homebrew::Bundle::MacAppStoreInstaller.app_id_installed_and_up_to_date?(id, no_upgrade:)
|
||||||
|
end
|
||||||
|
|
||||||
|
def format_checkable(entries)
|
||||||
|
checkable_entries(entries).to_h { |e| [e.options[:id], e.name] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def exit_early_check(app_ids_with_names, no_upgrade:)
|
||||||
|
work_to_be_done = app_ids_with_names.find do |id, _name|
|
||||||
|
!installed_and_up_to_date?(id, no_upgrade:)
|
||||||
|
end
|
||||||
|
|
||||||
|
Array(work_to_be_done)
|
||||||
|
end
|
||||||
|
|
||||||
|
def full_check(app_ids_with_names, no_upgrade:)
|
||||||
|
app_ids_with_names.reject { |id, _name| installed_and_up_to_date?(id, no_upgrade:) }
|
||||||
|
.map { |_id, name| failure_reason(name, no_upgrade:) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
41
Library/Homebrew/bundle/mac_app_store_dumper.rb
Normal file
41
Library/Homebrew/bundle/mac_app_store_dumper.rb
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "json"
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module Bundle
|
||||||
|
module MacAppStoreDumper
|
||||||
|
module_function
|
||||||
|
|
||||||
|
def reset!
|
||||||
|
@apps = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def apps
|
||||||
|
@apps ||= if Bundle.mas_installed?
|
||||||
|
`mas list 2>/dev/null`.split("\n").map do |app|
|
||||||
|
app_details = app.match(/\A(?<id>\d+)\s+(?<name>.*?)\s+\((?<version>[\d.]*)\)\Z/)
|
||||||
|
|
||||||
|
# Only add the application details should we have a valid match.
|
||||||
|
# Strip unprintable characters
|
||||||
|
if app_details
|
||||||
|
name = T.must(app_details[:name])
|
||||||
|
[app_details[:id], name.gsub(/[[:cntrl:]]|[\p{C}]/, "")]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
[]
|
||||||
|
end.compact
|
||||||
|
end
|
||||||
|
|
||||||
|
def app_ids
|
||||||
|
apps.map { |id, _| id.to_i }
|
||||||
|
end
|
||||||
|
|
||||||
|
def dump
|
||||||
|
apps.sort_by { |_, name| name.downcase }.map { |id, name| "mas \"#{name}\", id: #{id}" }.join("\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
7
Library/Homebrew/bundle/mac_app_store_dumper.rbi
Normal file
7
Library/Homebrew/bundle/mac_app_store_dumper.rbi
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# typed: strict
|
||||||
|
|
||||||
|
module Homebrew::Bundle
|
||||||
|
module MacAppStoreDumper
|
||||||
|
include Kernel
|
||||||
|
end
|
||||||
|
end
|
80
Library/Homebrew/bundle/mac_app_store_installer.rb
Normal file
80
Library/Homebrew/bundle/mac_app_store_installer.rb
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "os"
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module Bundle
|
||||||
|
module MacAppStoreInstaller
|
||||||
|
module_function
|
||||||
|
|
||||||
|
def reset!
|
||||||
|
@installed_app_ids = nil
|
||||||
|
@outdated_app_ids = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def preinstall(name, id, no_upgrade: false, verbose: false)
|
||||||
|
unless Bundle.mas_installed?
|
||||||
|
puts "Installing mas. It is not currently installed." if verbose
|
||||||
|
Bundle.brew("install", "mas", verbose:)
|
||||||
|
raise "Unable to install #{name} app. mas installation failed." unless Bundle.mas_installed?
|
||||||
|
end
|
||||||
|
|
||||||
|
if app_id_installed?(id) &&
|
||||||
|
(no_upgrade || !app_id_upgradable?(id))
|
||||||
|
puts "Skipping install of #{name} app. It is already installed." if verbose
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def install(name, id, preinstall: true, no_upgrade: false, verbose: false, force: false)
|
||||||
|
return true unless preinstall
|
||||||
|
|
||||||
|
if app_id_installed?(id)
|
||||||
|
puts "Upgrading #{name} app. It is installed but not up-to-date." if verbose
|
||||||
|
return false unless Bundle.system "mas", "upgrade", id.to_s, verbose: verbose
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
puts "Installing #{name} app. It is not currently installed." if verbose
|
||||||
|
|
||||||
|
return false unless Bundle.system "mas", "install", id.to_s, verbose: verbose
|
||||||
|
|
||||||
|
installed_app_ids << id
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.app_id_installed_and_up_to_date?(id, no_upgrade: false)
|
||||||
|
return false unless app_id_installed?(id)
|
||||||
|
return true if no_upgrade
|
||||||
|
|
||||||
|
!app_id_upgradable?(id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def app_id_installed?(id)
|
||||||
|
installed_app_ids.include? id
|
||||||
|
end
|
||||||
|
|
||||||
|
def app_id_upgradable?(id)
|
||||||
|
outdated_app_ids.include? id
|
||||||
|
end
|
||||||
|
|
||||||
|
def installed_app_ids
|
||||||
|
@installed_app_ids ||= Homebrew::Bundle::MacAppStoreDumper.app_ids
|
||||||
|
end
|
||||||
|
|
||||||
|
def outdated_app_ids
|
||||||
|
@outdated_app_ids ||= if Bundle.mas_installed?
|
||||||
|
`mas outdated 2>/dev/null`.split("\n").map do |app|
|
||||||
|
app.split(" ", 2).first.to_i
|
||||||
|
end
|
||||||
|
else
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
7
Library/Homebrew/bundle/mac_app_store_installer.rbi
Normal file
7
Library/Homebrew/bundle/mac_app_store_installer.rbi
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# typed: strict
|
||||||
|
|
||||||
|
module Homebrew::Bundle
|
||||||
|
module MacAppStoreInstaller
|
||||||
|
include Kernel
|
||||||
|
end
|
||||||
|
end
|
47
Library/Homebrew/bundle/remover.rb
Normal file
47
Library/Homebrew/bundle/remover.rb
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module Bundle
|
||||||
|
module Remover
|
||||||
|
module_function
|
||||||
|
|
||||||
|
def remove(*args, type:, global:, file:)
|
||||||
|
brewfile = Brewfile.read(global:, file:)
|
||||||
|
content = brewfile.input
|
||||||
|
entry_type = type.to_s if type != :none
|
||||||
|
escaped_args = args.flat_map do |arg|
|
||||||
|
names = if type == :brew
|
||||||
|
possible_names(arg)
|
||||||
|
else
|
||||||
|
[arg]
|
||||||
|
end
|
||||||
|
|
||||||
|
names.uniq.map { |a| Regexp.escape(a) }
|
||||||
|
end
|
||||||
|
|
||||||
|
new_content = content.split("\n")
|
||||||
|
.grep_v(/#{entry_type}(\s+|\(\s*)"(#{escaped_args.join("|")})"/)
|
||||||
|
.join("\n") << "\n"
|
||||||
|
|
||||||
|
if content.chomp == new_content.chomp &&
|
||||||
|
type == :none &&
|
||||||
|
args.any? { |arg| possible_names(arg, raise_error: false).count > 1 }
|
||||||
|
opoo "No matching entries found in Brewfile. Try again with `--formula` to match formula " \
|
||||||
|
"aliases and old formula names."
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
path = Dumper.brewfile_path(global:, file:)
|
||||||
|
Dumper.write_file path, new_content
|
||||||
|
end
|
||||||
|
|
||||||
|
def possible_names(formula_name, raise_error: true)
|
||||||
|
formula = Formulary.factory(formula_name)
|
||||||
|
[formula_name, formula.name, formula.full_name, *formula.aliases, *formula.oldnames].compact.uniq
|
||||||
|
rescue FormulaUnavailableError
|
||||||
|
raise if raise_error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
7
Library/Homebrew/bundle/remover.rbi
Normal file
7
Library/Homebrew/bundle/remover.rbi
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# typed: strict
|
||||||
|
|
||||||
|
module Homebrew::Bundle
|
||||||
|
module Remover
|
||||||
|
include Kernel
|
||||||
|
end
|
||||||
|
end
|
64
Library/Homebrew/bundle/skipper.rb
Normal file
64
Library/Homebrew/bundle/skipper.rb
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "hardware"
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module Bundle
|
||||||
|
module Skipper
|
||||||
|
class << self
|
||||||
|
def skip?(entry, silent: false)
|
||||||
|
# TODO: use extend/OS here
|
||||||
|
# rubocop:todo Homebrew/MoveToExtendOS
|
||||||
|
if (Hardware::CPU.arm? || OS.linux?) &&
|
||||||
|
Homebrew.default_prefix? &&
|
||||||
|
entry.type == :brew && entry.name.exclude?("/") &&
|
||||||
|
(formula = BrewDumper.formulae_by_full_name(entry.name)) &&
|
||||||
|
formula[:official_tap] &&
|
||||||
|
!formula[:bottled]
|
||||||
|
reason = Hardware::CPU.arm? ? "Apple Silicon" : "Linux"
|
||||||
|
puts Formatter.warning "Skipping #{entry.name} (no bottle for #{reason})" unless silent
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
# rubocop:enable Homebrew/MoveToExtendOS
|
||||||
|
return true if @failed_taps&.any? do |tap|
|
||||||
|
prefix = "#{tap}/"
|
||||||
|
entry.name.start_with?(prefix) || entry.options[:full_name]&.start_with?(prefix)
|
||||||
|
end
|
||||||
|
|
||||||
|
entry_type_skips = Array(skipped_entries[entry.type])
|
||||||
|
return false if entry_type_skips.empty?
|
||||||
|
|
||||||
|
# Check the name or ID particularly for Mac App Store entries where they
|
||||||
|
# can have spaces in the names (and the `mas` output format changes on
|
||||||
|
# occasion).
|
||||||
|
entry_ids = [entry.name, entry.options[:id]&.to_s].compact
|
||||||
|
return false unless entry_type_skips.intersect?(entry_ids)
|
||||||
|
|
||||||
|
puts Formatter.warning "Skipping #{entry.name}" unless silent
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def tap_failed!(tap_name)
|
||||||
|
@failed_taps ||= []
|
||||||
|
@failed_taps << tap_name
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def skipped_entries
|
||||||
|
return @skipped_entries if @skipped_entries
|
||||||
|
|
||||||
|
@skipped_entries = {}
|
||||||
|
[:brew, :cask, :mas, :tap, :whalebrew].each do |type|
|
||||||
|
@skipped_entries[type] =
|
||||||
|
ENV["HOMEBREW_BUNDLE_#{type.to_s.upcase}_SKIP"]&.split
|
||||||
|
end
|
||||||
|
@skipped_entries
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
require "extend/os/bundle/skipper"
|
21
Library/Homebrew/bundle/tap_checker.rb
Normal file
21
Library/Homebrew/bundle/tap_checker.rb
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module Bundle
|
||||||
|
module Checker
|
||||||
|
class TapChecker < Homebrew::Bundle::Checker::Base
|
||||||
|
PACKAGE_TYPE = :tap
|
||||||
|
PACKAGE_TYPE_NAME = "Tap"
|
||||||
|
|
||||||
|
def find_actionable(entries, exit_on_first_error: false, no_upgrade: false, verbose: false)
|
||||||
|
requested_taps = format_checkable(entries)
|
||||||
|
return [] if requested_taps.empty?
|
||||||
|
|
||||||
|
current_taps = Homebrew::Bundle::TapDumper.tap_names
|
||||||
|
(requested_taps - current_taps).map { |entry| "Tap #{entry} needs to be tapped." }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
45
Library/Homebrew/bundle/tap_dumper.rb
Normal file
45
Library/Homebrew/bundle/tap_dumper.rb
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "json"
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module Bundle
|
||||||
|
module TapDumper
|
||||||
|
module_function
|
||||||
|
|
||||||
|
def reset!
|
||||||
|
@taps = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
def tap_names
|
||||||
|
taps.map(&:name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def taps
|
||||||
|
@taps ||= begin
|
||||||
|
require "tap"
|
||||||
|
Tap.select(&:installed?).to_a
|
||||||
|
end
|
||||||
|
end
|
||||||
|
private_class_method :taps
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
7
Library/Homebrew/bundle/tap_dumper.rbi
Normal file
7
Library/Homebrew/bundle/tap_dumper.rbi
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# typed: strict
|
||||||
|
|
||||||
|
module Homebrew::Bundle
|
||||||
|
module TapDumper
|
||||||
|
include Kernel
|
||||||
|
end
|
||||||
|
end
|
46
Library/Homebrew/bundle/tap_installer.rb
Normal file
46
Library/Homebrew/bundle/tap_installer.rb
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module Bundle
|
||||||
|
module TapInstaller
|
||||||
|
module_function
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
def install(name, preinstall: true, verbose: false, force: false, **options)
|
||||||
|
return true unless preinstall
|
||||||
|
|
||||||
|
puts "Installing #{name} tap. It is not currently installed." if verbose
|
||||||
|
args = []
|
||||||
|
args << "--force" if force
|
||||||
|
args.append("--force-auto-update") if options[:force_auto_update]
|
||||||
|
|
||||||
|
success = if options[:clone_target]
|
||||||
|
Bundle.brew("tap", name, options[:clone_target], *args, verbose:)
|
||||||
|
else
|
||||||
|
Bundle.brew("tap", name, *args, verbose:)
|
||||||
|
end
|
||||||
|
|
||||||
|
unless success
|
||||||
|
Homebrew::Bundle::Skipper.tap_failed!(name)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
installed_taps << name
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def installed_taps
|
||||||
|
@installed_taps ||= Homebrew::Bundle::TapDumper.tap_names
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
7
Library/Homebrew/bundle/tap_installer.rbi
Normal file
7
Library/Homebrew/bundle/tap_installer.rbi
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# typed: strict
|
||||||
|
|
||||||
|
module Homebrew::Bundle
|
||||||
|
module TapInstaller
|
||||||
|
include Kernel
|
||||||
|
end
|
||||||
|
end
|
21
Library/Homebrew/bundle/vscode_extension_checker.rb
Normal file
21
Library/Homebrew/bundle/vscode_extension_checker.rb
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module Bundle
|
||||||
|
module Checker
|
||||||
|
class VscodeExtensionChecker < Homebrew::Bundle::Checker::Base
|
||||||
|
PACKAGE_TYPE = :vscode
|
||||||
|
PACKAGE_TYPE_NAME = "VSCode Extension"
|
||||||
|
|
||||||
|
def failure_reason(extension, no_upgrade:)
|
||||||
|
"#{PACKAGE_TYPE_NAME} #{extension} needs to be installed."
|
||||||
|
end
|
||||||
|
|
||||||
|
def installed_and_up_to_date?(extension, no_upgrade: false)
|
||||||
|
Homebrew::Bundle::VscodeExtensionInstaller.extension_installed?(extension)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
28
Library/Homebrew/bundle/vscode_extension_dumper.rb
Normal file
28
Library/Homebrew/bundle/vscode_extension_dumper.rb
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module Bundle
|
||||||
|
module VscodeExtensionDumper
|
||||||
|
module_function
|
||||||
|
|
||||||
|
def reset!
|
||||||
|
@extensions = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def extensions
|
||||||
|
@extensions ||= if Bundle.vscode_installed?
|
||||||
|
Bundle.exchange_uid_if_needed! do
|
||||||
|
`code --list-extensions 2>/dev/null`
|
||||||
|
end.split("\n").map(&:downcase)
|
||||||
|
else
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def dump
|
||||||
|
extensions.map { |name| "vscode \"#{name}\"" }.join("\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
7
Library/Homebrew/bundle/vscode_extension_dumper.rbi
Normal file
7
Library/Homebrew/bundle/vscode_extension_dumper.rbi
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# typed: strict
|
||||||
|
|
||||||
|
module Homebrew::Bundle
|
||||||
|
module VscodeExtensionDumper
|
||||||
|
include Kernel
|
||||||
|
end
|
||||||
|
end
|
53
Library/Homebrew/bundle/vscode_extension_installer.rb
Normal file
53
Library/Homebrew/bundle/vscode_extension_installer.rb
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module Bundle
|
||||||
|
module VscodeExtensionInstaller
|
||||||
|
module_function
|
||||||
|
|
||||||
|
def reset!
|
||||||
|
@installed_extensions = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def preinstall(name, no_upgrade: false, verbose: false)
|
||||||
|
if !Bundle.vscode_installed? && Bundle.cask_installed?
|
||||||
|
puts "Installing visual-studio-code. It is not currently installed." if verbose
|
||||||
|
Bundle.brew("install", "--cask", "visual-studio-code", verbose:)
|
||||||
|
end
|
||||||
|
|
||||||
|
if extension_installed?(name)
|
||||||
|
puts "Skipping install of #{name} VSCode extension. It is already installed." if verbose
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
raise "Unable to install #{name} VSCode extension. VSCode is not installed." unless Bundle.vscode_installed?
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def install(name, preinstall: true, no_upgrade: false, verbose: false, force: false)
|
||||||
|
return true unless preinstall
|
||||||
|
return true if extension_installed?(name)
|
||||||
|
|
||||||
|
puts "Installing #{name} VSCode extension. It is not currently installed." if verbose
|
||||||
|
|
||||||
|
return false unless Bundle.exchange_uid_if_needed! do
|
||||||
|
Bundle.system("code", "--install-extension", name, verbose:)
|
||||||
|
end
|
||||||
|
|
||||||
|
installed_extensions << name
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def extension_installed?(name)
|
||||||
|
installed_extensions.include? name.downcase
|
||||||
|
end
|
||||||
|
|
||||||
|
def installed_extensions
|
||||||
|
@installed_extensions ||= Homebrew::Bundle::VscodeExtensionDumper.extensions
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
7
Library/Homebrew/bundle/vscode_extension_installer.rbi
Normal file
7
Library/Homebrew/bundle/vscode_extension_installer.rbi
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# typed: strict
|
||||||
|
|
||||||
|
module Homebrew::Bundle
|
||||||
|
module VscodeExtensionInstaller
|
||||||
|
include Kernel
|
||||||
|
end
|
||||||
|
end
|
27
Library/Homebrew/bundle/whalebrew_dumper.rb
Normal file
27
Library/Homebrew/bundle/whalebrew_dumper.rb
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module Bundle
|
||||||
|
module WhalebrewDumper
|
||||||
|
module_function
|
||||||
|
|
||||||
|
def reset!
|
||||||
|
@images = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def images
|
||||||
|
return [] unless Bundle.whalebrew_installed?
|
||||||
|
|
||||||
|
@images ||= `whalebrew list 2>/dev/null`.split("\n")
|
||||||
|
.reject { |line| line.start_with?("COMMAND ") }
|
||||||
|
.map { |line| line.split(/\s+/).last }
|
||||||
|
.uniq
|
||||||
|
end
|
||||||
|
|
||||||
|
def dump
|
||||||
|
images.map { |image| "whalebrew \"#{image}\"" }.join("\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
7
Library/Homebrew/bundle/whalebrew_dumper.rbi
Normal file
7
Library/Homebrew/bundle/whalebrew_dumper.rbi
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# typed: strict
|
||||||
|
|
||||||
|
module Homebrew::Bundle
|
||||||
|
module WhalebrewDumper
|
||||||
|
include Kernel
|
||||||
|
end
|
||||||
|
end
|
48
Library/Homebrew/bundle/whalebrew_installer.rb
Normal file
48
Library/Homebrew/bundle/whalebrew_installer.rb
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module Bundle
|
||||||
|
module WhalebrewInstaller
|
||||||
|
module_function
|
||||||
|
|
||||||
|
def reset!
|
||||||
|
@installed_images = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def preinstall(name, verbose: false, **_options)
|
||||||
|
unless Bundle.whalebrew_installed?
|
||||||
|
puts "Installing whalebrew. It is not currently installed." if verbose
|
||||||
|
Bundle.brew("install", "--formula", "whalebrew", verbose:)
|
||||||
|
raise "Unable to install #{name} app. Whalebrew installation failed." unless Bundle.whalebrew_installed?
|
||||||
|
end
|
||||||
|
|
||||||
|
if image_installed?(name)
|
||||||
|
puts "Skipping install of #{name} app. It is already installed." if verbose
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def install(name, preinstall: true, verbose: false, force: false, **_options)
|
||||||
|
return true unless preinstall
|
||||||
|
|
||||||
|
puts "Installing #{name} image. It is not currently installed." if verbose
|
||||||
|
|
||||||
|
return false unless Bundle.system "whalebrew", "install", name, verbose: verbose
|
||||||
|
|
||||||
|
installed_images << name
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def image_installed?(image)
|
||||||
|
installed_images.include? image
|
||||||
|
end
|
||||||
|
|
||||||
|
def installed_images
|
||||||
|
@installed_images ||= Homebrew::Bundle::WhalebrewDumper.images
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
7
Library/Homebrew/bundle/whalebrew_installer.rbi
Normal file
7
Library/Homebrew/bundle/whalebrew_installer.rbi
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# typed: strict
|
||||||
|
|
||||||
|
module Homebrew::Bundle
|
||||||
|
module WhalebrewInstaller
|
||||||
|
include Kernel
|
||||||
|
end
|
||||||
|
end
|
272
Library/Homebrew/cmd/bundle.rb
Executable file
272
Library/Homebrew/cmd/bundle.rb
Executable file
@ -0,0 +1,272 @@
|
|||||||
|
# typed: strict
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "abstract_command"
|
||||||
|
|
||||||
|
module Homebrew
|
||||||
|
module Cmd
|
||||||
|
class Bundle < AbstractCommand
|
||||||
|
cmd_args do
|
||||||
|
usage_banner <<~EOS
|
||||||
|
`bundle` [<subcommand>]
|
||||||
|
|
||||||
|
Bundler for non-Ruby dependencies from Homebrew, Homebrew Cask, Mac App Store, Whalebrew and Visual Studio Code.
|
||||||
|
|
||||||
|
`brew bundle` [`install`]:
|
||||||
|
Install and upgrade (by default) all dependencies from the `Brewfile`.
|
||||||
|
|
||||||
|
You can specify the `Brewfile` location using `--file` or by setting the `$HOMEBREW_BUNDLE_FILE` environment variable.
|
||||||
|
|
||||||
|
You can skip the installation of dependencies by adding space-separated values to one or more of the following environment variables: `$HOMEBREW_BUNDLE_BREW_SKIP`, `$HOMEBREW_BUNDLE_CASK_SKIP`, `$HOMEBREW_BUNDLE_MAS_SKIP`, `$HOMEBREW_BUNDLE_WHALEBREW_SKIP`, `$HOMEBREW_BUNDLE_TAP_SKIP`.
|
||||||
|
|
||||||
|
`brew bundle upgrade`:
|
||||||
|
Shorthand for `brew bundle install --upgrade`.
|
||||||
|
|
||||||
|
`brew bundle dump`:
|
||||||
|
Write all installed casks/formulae/images/taps into a `Brewfile` in the current directory or to a custom file specified with the `--file` option.
|
||||||
|
|
||||||
|
`brew bundle cleanup`:
|
||||||
|
Uninstall all dependencies not present in the `Brewfile`.
|
||||||
|
|
||||||
|
This workflow is useful for maintainers or testers who regularly install lots of formulae.
|
||||||
|
|
||||||
|
Unless `--force` is passed, this returns a 1 exit code if anything would be removed.
|
||||||
|
|
||||||
|
`brew bundle check`:
|
||||||
|
Check if all dependencies present in the `Brewfile` are installed.
|
||||||
|
|
||||||
|
This provides a successful exit code if everything is up-to-date, making it useful for scripting.
|
||||||
|
|
||||||
|
`brew bundle list`:
|
||||||
|
List all dependencies present in the `Brewfile`.
|
||||||
|
|
||||||
|
By default, only Homebrew formula dependencies are listed.
|
||||||
|
|
||||||
|
`brew bundle edit`:
|
||||||
|
Edit the `Brewfile` in your editor.
|
||||||
|
|
||||||
|
`brew bundle add` <name> [...]:
|
||||||
|
Add entries to your `Brewfile`. Adds formulae by default. Use `--cask`, `--tap`, `--whalebrew` or `--vscode` to add the corresponding entry instead.
|
||||||
|
|
||||||
|
`brew bundle remove` <name> [...]:
|
||||||
|
Remove entries that match `name` from your `Brewfile`. Use `--formula`, `--cask`, `--tap`, `--mas`, `--whalebrew` or `--vscode` to remove only entries of the corresponding type. Passing `--formula` also removes matches against formula aliases and old formula names.
|
||||||
|
|
||||||
|
`brew bundle exec` <command>:
|
||||||
|
Run an external command in an isolated build environment based on the `Brewfile` dependencies.
|
||||||
|
|
||||||
|
This sanitized build environment ignores unrequested dependencies, which makes sure that things you didn't specify in your `Brewfile` won't get picked up by commands like `bundle install`, `npm install`, etc. It will also add compiler flags which will help with finding keg-only dependencies like `openssl`, `icu4c`, etc.
|
||||||
|
|
||||||
|
`brew bundle sh`:
|
||||||
|
Run your shell in a `brew bundle exec` environment.
|
||||||
|
|
||||||
|
`brew bundle env`:
|
||||||
|
Print the environment variables that would be set in a `brew bundle exec` environment.
|
||||||
|
EOS
|
||||||
|
flag "--file=",
|
||||||
|
description: "Read from or write to the `Brewfile` from this location. " \
|
||||||
|
"Use `--file=-` to pipe to stdin/stdout."
|
||||||
|
switch "--global",
|
||||||
|
description: "Read from or write to the `Brewfile` from `$HOMEBREW_BUNDLE_FILE_GLOBAL` (if set), " \
|
||||||
|
"`${XDG_CONFIG_HOME}/homebrew/Brewfile` (if `$XDG_CONFIG_HOME` is set), " \
|
||||||
|
"`~/.homebrew/Brewfile` or `~/.Brewfile` otherwise."
|
||||||
|
switch "-v", "--verbose",
|
||||||
|
description: "`install` prints output from commands as they are run. " \
|
||||||
|
"`check` lists all missing dependencies."
|
||||||
|
switch "--no-upgrade",
|
||||||
|
env: :bundle_no_upgrade,
|
||||||
|
description: "`install` does not run `brew upgrade` on outdated dependencies. " \
|
||||||
|
"`check` does not check for outdated dependencies. " \
|
||||||
|
"Note they may still be upgraded by `brew install` if needed. " \
|
||||||
|
"This is enabled by default if `$HOMEBREW_BUNDLE_NO_UPGRADE` is set."
|
||||||
|
switch "--upgrade",
|
||||||
|
description: "`install` runs `brew upgrade` on outdated dependencies, " \
|
||||||
|
"even if `$HOMEBREW_BUNDLE_NO_UPGRADE` is set. "
|
||||||
|
switch "--install",
|
||||||
|
description: "Run `install` before continuing to other operations e.g. `exec`."
|
||||||
|
switch "-f", "--force",
|
||||||
|
description: "`install` runs with `--force`/`--overwrite`. " \
|
||||||
|
"`dump` overwrites an existing `Brewfile`. " \
|
||||||
|
"`cleanup` actually performs its cleanup operations."
|
||||||
|
switch "--cleanup",
|
||||||
|
env: :bundle_install_cleanup,
|
||||||
|
description: "`install` performs cleanup operation, same as running `cleanup --force`. " \
|
||||||
|
"This is enabled by default if `$HOMEBREW_BUNDLE_INSTALL_CLEANUP` is set and " \
|
||||||
|
"`--global` is passed."
|
||||||
|
switch "--all",
|
||||||
|
description: "`list` all dependencies."
|
||||||
|
switch "--formula", "--brews",
|
||||||
|
description: "`list` or `dump` Homebrew formula dependencies."
|
||||||
|
switch "--cask", "--casks",
|
||||||
|
description: "`list` or `dump` Homebrew cask dependencies."
|
||||||
|
switch "--tap", "--taps",
|
||||||
|
description: "`list` or `dump` Homebrew tap dependencies."
|
||||||
|
switch "--mas",
|
||||||
|
description: "`list` or `dump` Mac App Store dependencies."
|
||||||
|
switch "--whalebrew",
|
||||||
|
description: "`list` or `dump` Whalebrew dependencies."
|
||||||
|
switch "--vscode",
|
||||||
|
description: "`list` or `dump` VSCode extensions."
|
||||||
|
switch "--no-vscode",
|
||||||
|
env: :bundle_dump_no_vscode,
|
||||||
|
description: "`dump` without VSCode extensions. " \
|
||||||
|
"This is enabled by default if `$HOMEBREW_BUNDLE_DUMP_NO_VSCODE` is set."
|
||||||
|
switch "--describe",
|
||||||
|
env: :bundle_dump_describe,
|
||||||
|
description: "`dump` adds a description comment above each line, unless the " \
|
||||||
|
"dependency does not have a description. " \
|
||||||
|
"This is enabled by default if `$HOMEBREW_BUNDLE_DUMP_DESCRIBE` is set."
|
||||||
|
switch "--no-restart",
|
||||||
|
description: "`dump` does not add `restart_service` to formula lines."
|
||||||
|
switch "--zap",
|
||||||
|
description: "`cleanup` casks using the `zap` command instead of `uninstall`."
|
||||||
|
|
||||||
|
conflicts "--all", "--no-vscode"
|
||||||
|
conflicts "--vscode", "--no-vscode"
|
||||||
|
conflicts "--install", "--upgrade"
|
||||||
|
|
||||||
|
named_args %w[install dump cleanup check exec list sh env edit]
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { override.void }
|
||||||
|
def run
|
||||||
|
# Keep this inside `run` to keep --help fast.
|
||||||
|
require "bundle"
|
||||||
|
|
||||||
|
subcommand = args.named.first.presence
|
||||||
|
if ["exec", "add", "remove"].exclude?(subcommand) && args.named.size > 1
|
||||||
|
raise UsageError, "This command does not take more than 1 subcommand argument."
|
||||||
|
end
|
||||||
|
|
||||||
|
global = args.global?
|
||||||
|
file = args.file
|
||||||
|
args.zap?
|
||||||
|
no_upgrade = if args.upgrade? || subcommand == "upgrade"
|
||||||
|
false
|
||||||
|
else
|
||||||
|
args.no_upgrade?
|
||||||
|
end
|
||||||
|
verbose = args.verbose?
|
||||||
|
force = args.force?
|
||||||
|
zap = args.zap?
|
||||||
|
|
||||||
|
no_type_args = !args.brews? && !args.casks? && !args.taps? && !args.mas? && !args.whalebrew? && !args.vscode?
|
||||||
|
|
||||||
|
if args.install?
|
||||||
|
if [nil, "install", "upgrade"].include?(subcommand)
|
||||||
|
raise UsageError, "`--install` cannot be used with `install`, `upgrade` or no subcommand."
|
||||||
|
end
|
||||||
|
|
||||||
|
redirect_stdout($stderr) do
|
||||||
|
Homebrew::Bundle::Commands::Install.run(global:, file:, no_upgrade:, verbose:, force:, quiet: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
case subcommand
|
||||||
|
when nil, "install", "upgrade"
|
||||||
|
Homebrew::Bundle::Commands::Install.run(global:, file:, no_upgrade:, verbose:, force:, quiet: args.quiet?)
|
||||||
|
|
||||||
|
cleanup = if ENV.fetch("HOMEBREW_BUNDLE_INSTALL_CLEANUP", nil)
|
||||||
|
args.global?
|
||||||
|
else
|
||||||
|
args.cleanup?
|
||||||
|
end
|
||||||
|
|
||||||
|
if cleanup
|
||||||
|
Homebrew::Bundle::Commands::Cleanup.run(
|
||||||
|
global:, file:, zap:,
|
||||||
|
force: true,
|
||||||
|
dsl: Homebrew::Bundle::Commands::Install.dsl
|
||||||
|
)
|
||||||
|
end
|
||||||
|
when "dump"
|
||||||
|
vscode = if args.no_vscode?
|
||||||
|
false
|
||||||
|
elsif args.vscode?
|
||||||
|
true
|
||||||
|
else
|
||||||
|
no_type_args
|
||||||
|
end
|
||||||
|
|
||||||
|
Homebrew::Bundle::Commands::Dump.run(
|
||||||
|
global:, file:, force:,
|
||||||
|
describe: args.describe?,
|
||||||
|
no_restart: args.no_restart?,
|
||||||
|
taps: args.taps? || no_type_args,
|
||||||
|
brews: args.brews? || no_type_args,
|
||||||
|
casks: args.casks? || no_type_args,
|
||||||
|
mas: args.mas? || no_type_args,
|
||||||
|
whalebrew: args.whalebrew? || no_type_args,
|
||||||
|
vscode:
|
||||||
|
)
|
||||||
|
when "edit"
|
||||||
|
exec_editor(Homebrew::Bundle::Brewfile.path(global:, file:))
|
||||||
|
when "cleanup"
|
||||||
|
Homebrew::Bundle::Commands::Cleanup.run(global:, file:, force:, zap:)
|
||||||
|
when "check"
|
||||||
|
Homebrew::Bundle::Commands::Check.run(global:, file:, no_upgrade:, verbose:)
|
||||||
|
when "exec", "sh", "env"
|
||||||
|
named_args = case subcommand
|
||||||
|
when "exec"
|
||||||
|
_subcommand, *named_args = args.named
|
||||||
|
named_args
|
||||||
|
when "sh"
|
||||||
|
preferred_shell = Utils::Shell.preferred_path(default: "/bin/bash")
|
||||||
|
subshell = case Utils::Shell.preferred
|
||||||
|
when :zsh
|
||||||
|
"PS1='brew bundle %B%F{green}%~%f%b$ ' #{preferred_shell} -d -f"
|
||||||
|
when :bash
|
||||||
|
"PS1=\"brew bundle \\[\\033[1;32m\\]\\w\\[\\033[0m\\]$ \" #{preferred_shell} --noprofile --norc"
|
||||||
|
else
|
||||||
|
"PS1=\"brew bundle \\[\\033[1;32m\\]\\w\\[\\033[0m\\]$ \" #{preferred_shell}"
|
||||||
|
end
|
||||||
|
$stdout.flush
|
||||||
|
ENV["HOMEBREW_FORCE_API_AUTO_UPDATE"] = nil
|
||||||
|
[subshell]
|
||||||
|
when "env"
|
||||||
|
["env"]
|
||||||
|
end
|
||||||
|
Homebrew::Bundle::Commands::Exec.run(*named_args, global:, file:, subcommand:)
|
||||||
|
when "list"
|
||||||
|
Homebrew::Bundle::Commands::List.run(
|
||||||
|
global:,
|
||||||
|
file:,
|
||||||
|
brews: args.brews? || args.all? || no_type_args,
|
||||||
|
casks: args.casks? || args.all?,
|
||||||
|
taps: args.taps? || args.all?,
|
||||||
|
mas: args.mas? || args.all?,
|
||||||
|
whalebrew: args.whalebrew? || args.all?,
|
||||||
|
vscode: args.vscode? || args.all?,
|
||||||
|
)
|
||||||
|
when "add", "remove"
|
||||||
|
# We intentionally omit the `s` from `brews`, `casks`, and `taps` for ease of handling later.
|
||||||
|
type_hash = {
|
||||||
|
brew: args.brews?,
|
||||||
|
cask: args.casks?,
|
||||||
|
tap: args.taps?,
|
||||||
|
mas: args.mas?,
|
||||||
|
whalebrew: args.whalebrew?,
|
||||||
|
vscode: args.vscode?,
|
||||||
|
none: no_type_args,
|
||||||
|
}
|
||||||
|
selected_types = type_hash.select { |_, v| v }.keys
|
||||||
|
raise UsageError, "`#{subcommand}` supports only one type of entry at a time." if selected_types.count != 1
|
||||||
|
|
||||||
|
_, *named_args = args.named
|
||||||
|
if subcommand == "add"
|
||||||
|
type = case (t = selected_types.first)
|
||||||
|
when :none then :brew
|
||||||
|
when :mas then raise UsageError, "`add` does not support `--mas`."
|
||||||
|
else t
|
||||||
|
end
|
||||||
|
|
||||||
|
Homebrew::Bundle::Commands::Add.run(*named_args, type:, global:, file:)
|
||||||
|
else
|
||||||
|
Homebrew::Bundle::Commands::Remove.run(*named_args, type: selected_types.first, global:, file:)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
raise UsageError, "unknown subcommand: #{subcommand}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -568,6 +568,10 @@ module Homebrew
|
|||||||
def check_deprecated_official_taps
|
def check_deprecated_official_taps
|
||||||
tapped_deprecated_taps =
|
tapped_deprecated_taps =
|
||||||
Tap.select(&:official?).map(&:repository) & DEPRECATED_OFFICIAL_TAPS
|
Tap.select(&:official?).map(&:repository) & DEPRECATED_OFFICIAL_TAPS
|
||||||
|
|
||||||
|
# TODO: remove this once it's no longer in the default GitHub Actions image
|
||||||
|
tapped_deprecated_taps -= ["bundle"] if GitHub::Actions.env_set?
|
||||||
|
|
||||||
return if tapped_deprecated_taps.empty?
|
return if tapped_deprecated_taps.empty?
|
||||||
|
|
||||||
<<~EOS
|
<<~EOS
|
||||||
|
4
Library/Homebrew/extend/os/bundle/bundle.rb
Normal file
4
Library/Homebrew/extend/os/bundle/bundle.rb
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# typed: strict
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "extend/os/linux/bundle/bundle" if OS.linux?
|
4
Library/Homebrew/extend/os/bundle/skipper.rb
Normal file
4
Library/Homebrew/extend/os/bundle/skipper.rb
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# typed: strict
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "extend/os/linux/bundle/skipper" if OS.linux?
|
17
Library/Homebrew/extend/os/linux/bundle/bundle.rb
Normal file
17
Library/Homebrew/extend/os/linux/bundle/bundle.rb
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# typed: strict
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module OS
|
||||||
|
module Linux
|
||||||
|
module Bundle
|
||||||
|
module ClassMethods
|
||||||
|
sig { returns(T::Boolean) }
|
||||||
|
def mas_installed?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Homebrew::Bundle.singleton_class.prepend(OS::Linux::Bundle::ClassMethods)
|
31
Library/Homebrew/extend/os/linux/bundle/skipper.rb
Normal file
31
Library/Homebrew/extend/os/linux/bundle/skipper.rb
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# typed: true # rubocop:todo Sorbet/StrictSigil
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module OS
|
||||||
|
module Linux
|
||||||
|
module Bundle
|
||||||
|
module Skipper
|
||||||
|
module ClassMethods
|
||||||
|
def macos_only_entry?(entry)
|
||||||
|
[:cask, :mas].include?(entry.type)
|
||||||
|
end
|
||||||
|
|
||||||
|
def macos_only_tap?(entry)
|
||||||
|
entry.type == :tap && entry.name == "homebrew/cask"
|
||||||
|
end
|
||||||
|
|
||||||
|
def skip?(entry, silent: false)
|
||||||
|
if macos_only_entry?(entry) || macos_only_tap?(entry)
|
||||||
|
::Kernel.puts Formatter.warning "Skipping #{entry.type} #{entry.name} (on Linux)" unless silent
|
||||||
|
true
|
||||||
|
else
|
||||||
|
super(entry)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Homebrew::Bundle::Skipper.singleton_class.prepend(OS::Linux::Bundle::Skipper::ClassMethods)
|
@ -6,7 +6,6 @@ OFFICIAL_CASK_TAPS = %w[
|
|||||||
].freeze
|
].freeze
|
||||||
|
|
||||||
OFFICIAL_CMD_TAPS = T.let({
|
OFFICIAL_CMD_TAPS = T.let({
|
||||||
"homebrew/bundle" => ["bundle"],
|
|
||||||
"homebrew/command-not-found" => ["command-not-found-init", "which-formula", "which-update"],
|
"homebrew/command-not-found" => ["command-not-found-init", "which-formula", "which-update"],
|
||||||
"homebrew/test-bot" => ["test-bot"],
|
"homebrew/test-bot" => ["test-bot"],
|
||||||
}.freeze, T::Hash[String, T::Array[String]])
|
}.freeze, T::Hash[String, T::Array[String]])
|
||||||
@ -15,6 +14,7 @@ DEPRECATED_OFFICIAL_TAPS = %w[
|
|||||||
aliases
|
aliases
|
||||||
apache
|
apache
|
||||||
binary
|
binary
|
||||||
|
bundle
|
||||||
cask-drivers
|
cask-drivers
|
||||||
cask-eid
|
cask-eid
|
||||||
cask-fonts
|
cask-fonts
|
||||||
|
79
Library/Homebrew/sorbet/rbi/dsl/homebrew/cmd/bundle.rbi
Normal file
79
Library/Homebrew/sorbet/rbi/dsl/homebrew/cmd/bundle.rbi
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
# typed: true
|
||||||
|
|
||||||
|
# DO NOT EDIT MANUALLY
|
||||||
|
# This is an autogenerated file for dynamic methods in `Homebrew::Cmd::Bundle`.
|
||||||
|
# Please instead update this file by running `bin/tapioca dsl Homebrew::Cmd::Bundle`.
|
||||||
|
|
||||||
|
|
||||||
|
class Homebrew::Cmd::Bundle
|
||||||
|
sig { returns(Homebrew::Cmd::Bundle::Args) }
|
||||||
|
def args; end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Homebrew::Cmd::Bundle::Args < Homebrew::CLI::Args
|
||||||
|
sig { returns(T::Boolean) }
|
||||||
|
def all?; end
|
||||||
|
|
||||||
|
sig { returns(T::Boolean) }
|
||||||
|
def brews?; end
|
||||||
|
|
||||||
|
sig { returns(T::Boolean) }
|
||||||
|
def cask?; end
|
||||||
|
|
||||||
|
sig { returns(T::Boolean) }
|
||||||
|
def casks?; end
|
||||||
|
|
||||||
|
sig { returns(T::Boolean) }
|
||||||
|
def cleanup?; end
|
||||||
|
|
||||||
|
sig { returns(T::Boolean) }
|
||||||
|
def describe?; end
|
||||||
|
|
||||||
|
sig { returns(T::Boolean) }
|
||||||
|
def f?; end
|
||||||
|
|
||||||
|
sig { returns(T.nilable(String)) }
|
||||||
|
def file; end
|
||||||
|
|
||||||
|
sig { returns(T::Boolean) }
|
||||||
|
def force?; end
|
||||||
|
|
||||||
|
sig { returns(T::Boolean) }
|
||||||
|
def formula?; end
|
||||||
|
|
||||||
|
sig { returns(T::Boolean) }
|
||||||
|
def global?; end
|
||||||
|
|
||||||
|
sig { returns(T::Boolean) }
|
||||||
|
def install?; end
|
||||||
|
|
||||||
|
sig { returns(T::Boolean) }
|
||||||
|
def mas?; end
|
||||||
|
|
||||||
|
sig { returns(T::Boolean) }
|
||||||
|
def no_restart?; end
|
||||||
|
|
||||||
|
sig { returns(T::Boolean) }
|
||||||
|
def no_upgrade?; end
|
||||||
|
|
||||||
|
sig { returns(T::Boolean) }
|
||||||
|
def no_vscode?; end
|
||||||
|
|
||||||
|
sig { returns(T::Boolean) }
|
||||||
|
def tap?; end
|
||||||
|
|
||||||
|
sig { returns(T::Boolean) }
|
||||||
|
def taps?; end
|
||||||
|
|
||||||
|
sig { returns(T::Boolean) }
|
||||||
|
def upgrade?; end
|
||||||
|
|
||||||
|
sig { returns(T::Boolean) }
|
||||||
|
def vscode?; end
|
||||||
|
|
||||||
|
sig { returns(T::Boolean) }
|
||||||
|
def whalebrew?; end
|
||||||
|
|
||||||
|
sig { returns(T::Boolean) }
|
||||||
|
def zap?; end
|
||||||
|
end
|
267
Library/Homebrew/test/bundle/brew_dumper_spec.rb
Normal file
267
Library/Homebrew/test/bundle/brew_dumper_spec.rb
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "ostruct"
|
||||||
|
require "bundle"
|
||||||
|
require "tsort"
|
||||||
|
require "formula"
|
||||||
|
require "tab"
|
||||||
|
require "utils/bottles"
|
||||||
|
|
||||||
|
# TODO: remove OpenStruct usage
|
||||||
|
# rubocop:todo Style/OpenStructUse
|
||||||
|
RSpec.describe Homebrew::Bundle::BrewDumper do
|
||||||
|
subject(:dumper) { described_class }
|
||||||
|
|
||||||
|
let(:foo) do
|
||||||
|
instance_double(Formula,
|
||||||
|
name: "foo",
|
||||||
|
desc: "foobar",
|
||||||
|
oldnames: ["oldfoo"],
|
||||||
|
full_name: "qux/quuz/foo",
|
||||||
|
any_version_installed?: true,
|
||||||
|
aliases: ["foobar"],
|
||||||
|
runtime_dependencies: [],
|
||||||
|
deps: [],
|
||||||
|
conflicts: [],
|
||||||
|
any_installed_prefix: nil,
|
||||||
|
linked?: false,
|
||||||
|
keg_only?: true,
|
||||||
|
pinned?: false,
|
||||||
|
outdated?: false,
|
||||||
|
stable: OpenStruct.new(bottle_defined?: false, bottled?: false),
|
||||||
|
tap: OpenStruct.new(official?: false))
|
||||||
|
end
|
||||||
|
let(:foo_hash) do
|
||||||
|
{
|
||||||
|
aliases: ["foobar"],
|
||||||
|
any_version_installed?: true,
|
||||||
|
args: [],
|
||||||
|
bottle: false,
|
||||||
|
bottled: false,
|
||||||
|
build_dependencies: [],
|
||||||
|
conflicts_with: [],
|
||||||
|
dependencies: [],
|
||||||
|
desc: "foobar",
|
||||||
|
full_name: "qux/quuz/foo",
|
||||||
|
installed_as_dependency?: false,
|
||||||
|
installed_on_request?: false,
|
||||||
|
link?: nil,
|
||||||
|
name: "foo",
|
||||||
|
oldnames: ["oldfoo"],
|
||||||
|
outdated?: false,
|
||||||
|
pinned?: false,
|
||||||
|
poured_from_bottle?: false,
|
||||||
|
version: nil,
|
||||||
|
official_tap: false,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
let(:bar) do
|
||||||
|
linked_keg = Pathname("/usr/local").join("var").join("homebrew").join("linked").join("bar")
|
||||||
|
instance_double(Formula,
|
||||||
|
name: "bar",
|
||||||
|
desc: "barfoo",
|
||||||
|
oldnames: [],
|
||||||
|
full_name: "bar",
|
||||||
|
any_version_installed?: true,
|
||||||
|
aliases: [],
|
||||||
|
runtime_dependencies: [],
|
||||||
|
deps: [],
|
||||||
|
conflicts: [],
|
||||||
|
any_installed_prefix: nil,
|
||||||
|
linked?: true,
|
||||||
|
keg_only?: false,
|
||||||
|
pinned?: true,
|
||||||
|
outdated?: true,
|
||||||
|
linked_keg:,
|
||||||
|
stable: OpenStruct.new(bottle_defined?: true, bottled?: true),
|
||||||
|
tap: OpenStruct.new(official?: true),
|
||||||
|
bottle_hash: {
|
||||||
|
cellar: ":any",
|
||||||
|
files: {
|
||||||
|
big_sur: {
|
||||||
|
sha256: "abcdef",
|
||||||
|
url: "https://brew.sh//foo-1.0.big_sur.bottle.tar.gz",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
end
|
||||||
|
let(:bar_hash) do
|
||||||
|
{
|
||||||
|
aliases: [],
|
||||||
|
any_version_installed?: true,
|
||||||
|
args: [],
|
||||||
|
bottle: {
|
||||||
|
cellar: ":any",
|
||||||
|
files: {
|
||||||
|
big_sur: {
|
||||||
|
sha256: "abcdef",
|
||||||
|
url: "https://brew.sh//foo-1.0.big_sur.bottle.tar.gz",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
bottled: true,
|
||||||
|
build_dependencies: [],
|
||||||
|
conflicts_with: [],
|
||||||
|
dependencies: [],
|
||||||
|
desc: "barfoo",
|
||||||
|
full_name: "bar",
|
||||||
|
installed_as_dependency?: false,
|
||||||
|
installed_on_request?: false,
|
||||||
|
link?: nil,
|
||||||
|
name: "bar",
|
||||||
|
oldnames: [],
|
||||||
|
outdated?: true,
|
||||||
|
pinned?: true,
|
||||||
|
poured_from_bottle?: true,
|
||||||
|
version: "1.0",
|
||||||
|
official_tap: true,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
let(:baz) do
|
||||||
|
instance_double(Formula,
|
||||||
|
name: "baz",
|
||||||
|
desc: "",
|
||||||
|
oldnames: [],
|
||||||
|
full_name: "bazzles/bizzles/baz",
|
||||||
|
any_version_installed?: true,
|
||||||
|
aliases: [],
|
||||||
|
runtime_dependencies: [OpenStruct.new(name: "bar")],
|
||||||
|
deps: [OpenStruct.new(name: "bar", build?: true)],
|
||||||
|
conflicts: [],
|
||||||
|
any_installed_prefix: nil,
|
||||||
|
linked?: false,
|
||||||
|
keg_only?: false,
|
||||||
|
pinned?: false,
|
||||||
|
outdated?: false,
|
||||||
|
stable: OpenStruct.new(bottle_defined?: false, bottled?: false),
|
||||||
|
tap: OpenStruct.new(official?: false))
|
||||||
|
end
|
||||||
|
let(:baz_hash) do
|
||||||
|
{
|
||||||
|
aliases: [],
|
||||||
|
any_version_installed?: true,
|
||||||
|
args: [],
|
||||||
|
bottle: false,
|
||||||
|
bottled: false,
|
||||||
|
build_dependencies: ["bar"],
|
||||||
|
conflicts_with: [],
|
||||||
|
dependencies: ["bar"],
|
||||||
|
desc: "",
|
||||||
|
full_name: "bazzles/bizzles/baz",
|
||||||
|
installed_as_dependency?: false,
|
||||||
|
installed_on_request?: false,
|
||||||
|
link?: false,
|
||||||
|
name: "baz",
|
||||||
|
oldnames: [],
|
||||||
|
outdated?: false,
|
||||||
|
pinned?: false,
|
||||||
|
poured_from_bottle?: false,
|
||||||
|
version: nil,
|
||||||
|
official_tap: false,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
described_class.reset!
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#formulae" do
|
||||||
|
it "returns an empty array when no formulae are installed" do
|
||||||
|
expect(dumper.formulae).to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#formulae_by_full_name" do
|
||||||
|
it "returns an empty hash when no formulae are installed" do
|
||||||
|
expect(dumper.formulae_by_full_name).to eql({})
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns an empty hash for an unavailable formula" do
|
||||||
|
expect(Formula).to receive(:[]).with("bar").and_raise(FormulaUnavailableError.new("bar"))
|
||||||
|
expect(dumper.formulae_by_full_name("bar")).to eql({})
|
||||||
|
end
|
||||||
|
|
||||||
|
it "exits on cyclic exceptions" do
|
||||||
|
expect(Formula).to receive(:installed).and_return([foo, bar, baz])
|
||||||
|
expect_any_instance_of(Homebrew::Bundle::BrewDumper::Topo).to receive(:tsort).and_raise(
|
||||||
|
TSort::Cyclic,
|
||||||
|
'topological sort failed: ["foo", "bar"]',
|
||||||
|
)
|
||||||
|
expect { dumper.formulae_by_full_name }.to raise_error(SystemExit)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns a hash for a formula" do
|
||||||
|
expect(Formula).to receive(:[]).with("qux/quuz/foo").and_return(foo)
|
||||||
|
expect(dumper.formulae_by_full_name("qux/quuz/foo")).to eql(foo_hash)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns an array for all formulae" do
|
||||||
|
expect(Formula).to receive(:installed).and_return([foo, bar, baz])
|
||||||
|
expect(bar.linked_keg).to receive(:realpath).and_return(OpenStruct.new(basename: "1.0"))
|
||||||
|
expect(Tab).to receive(:for_keg).with(bar.linked_keg).and_return(
|
||||||
|
instance_double(Tab,
|
||||||
|
installed_as_dependency: false,
|
||||||
|
installed_on_request: false,
|
||||||
|
poured_from_bottle: true,
|
||||||
|
runtime_dependencies: [],
|
||||||
|
used_options: []),
|
||||||
|
)
|
||||||
|
expect(dumper.formulae_by_full_name).to eql({
|
||||||
|
"bar" => bar_hash,
|
||||||
|
"qux/quuz/foo" => foo_hash,
|
||||||
|
"bazzles/bizzles/baz" => baz_hash,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#formulae_by_name" do
|
||||||
|
it "returns a hash for a formula" do
|
||||||
|
expect(Formula).to receive(:[]).with("foo").and_return(foo)
|
||||||
|
expect(dumper.formulae_by_name("foo")).to eql(foo_hash)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#dump" do
|
||||||
|
it "returns a dump string with installed formulae" do
|
||||||
|
expect(Formula).to receive(:installed).and_return([foo, bar, baz])
|
||||||
|
allow(Utils).to receive(:safe_popen_read).and_return("")
|
||||||
|
expected = <<~EOS
|
||||||
|
# barfoo
|
||||||
|
brew "bar"
|
||||||
|
brew "bazzles/bizzles/baz", link: false
|
||||||
|
# foobar
|
||||||
|
brew "qux/quuz/foo"
|
||||||
|
EOS
|
||||||
|
expect(dumper.dump(describe: true)).to eql(expected.chomp)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#formula_aliases" do
|
||||||
|
it "returns an empty string when no formulae are installed" do
|
||||||
|
expect(dumper.formula_aliases).to eql({})
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns a hash with installed formulae aliases" do
|
||||||
|
expect(Formula).to receive(:installed).and_return([foo, bar, baz])
|
||||||
|
expect(dumper.formula_aliases).to eql({
|
||||||
|
"qux/quuz/foobar" => "qux/quuz/foo",
|
||||||
|
"foobar" => "qux/quuz/foo",
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#formula_oldnames" do
|
||||||
|
it "returns an empty string when no formulae are installed" do
|
||||||
|
expect(dumper.formula_oldnames).to eql({})
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns a hash with installed formulae old names" do
|
||||||
|
expect(Formula).to receive(:installed).and_return([foo, bar, baz])
|
||||||
|
expect(dumper.formula_oldnames).to eql({
|
||||||
|
"qux/quuz/oldfoo" => "qux/quuz/foo",
|
||||||
|
"oldfoo" => "qux/quuz/foo",
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
# rubocop:enable Style/OpenStructUse
|
555
Library/Homebrew/test/bundle/brew_installer_spec.rb
Normal file
555
Library/Homebrew/test/bundle/brew_installer_spec.rb
Normal file
@ -0,0 +1,555 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "bundle"
|
||||||
|
require "formula"
|
||||||
|
|
||||||
|
RSpec.describe Homebrew::Bundle::BrewInstaller do
|
||||||
|
let(:formula_name) { "mysql" }
|
||||||
|
let(:options) { { args: ["with-option"] } }
|
||||||
|
let(:installer) { described_class.new(formula_name, options) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
# don't try to load gcc/glibc
|
||||||
|
allow(DevelopmentTools).to receive_messages(needs_libc_formula?: false, needs_compiler_formula?: false)
|
||||||
|
|
||||||
|
stub_formula_loader formula(formula_name) { url "mysql-1.0" }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when the formula is installed" do
|
||||||
|
before do
|
||||||
|
allow_any_instance_of(described_class).to receive(:installed?).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with a true start_service option" do
|
||||||
|
before do
|
||||||
|
allow_any_instance_of(described_class).to receive(:install_change_state!).and_return(true)
|
||||||
|
allow_any_instance_of(described_class).to receive(:installed?).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when service is already running" do
|
||||||
|
before do
|
||||||
|
allow(Homebrew::Bundle::BrewServices).to receive(:started?).with(formula_name).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with a successful installation" do
|
||||||
|
it "start service" do
|
||||||
|
expect(Homebrew::Bundle::BrewServices).not_to receive(:start)
|
||||||
|
described_class.preinstall(formula_name, start_service: true)
|
||||||
|
described_class.install(formula_name, start_service: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with a skipped installation" do
|
||||||
|
it "start service" do
|
||||||
|
expect(Homebrew::Bundle::BrewServices).not_to receive(:start)
|
||||||
|
described_class.install(formula_name, preinstall: false, start_service: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when service is not running" do
|
||||||
|
before do
|
||||||
|
allow(Homebrew::Bundle::BrewServices).to receive(:started?).with(formula_name).and_return(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with a successful installation" do
|
||||||
|
it "start service" do
|
||||||
|
expect(Homebrew::Bundle::BrewServices).to \
|
||||||
|
receive(:start).with(formula_name, verbose: false).and_return(true)
|
||||||
|
described_class.preinstall(formula_name, start_service: true)
|
||||||
|
described_class.install(formula_name, start_service: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with a skipped installation" do
|
||||||
|
it "start service" do
|
||||||
|
expect(Homebrew::Bundle::BrewServices).to \
|
||||||
|
receive(:start).with(formula_name, verbose: false).and_return(true)
|
||||||
|
described_class.install(formula_name, preinstall: false, start_service: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with an always restart_service option" do
|
||||||
|
before do
|
||||||
|
allow_any_instance_of(described_class).to receive(:install_change_state!).and_return(true)
|
||||||
|
allow_any_instance_of(described_class).to receive(:installed?).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with a successful installation" do
|
||||||
|
it "restart service" do
|
||||||
|
expect(Homebrew::Bundle::BrewServices).to \
|
||||||
|
receive(:restart).with(formula_name, verbose: false).and_return(true)
|
||||||
|
described_class.preinstall(formula_name, restart_service: :always)
|
||||||
|
described_class.install(formula_name, restart_service: :always)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with a skipped installation" do
|
||||||
|
it "restart service" do
|
||||||
|
expect(Homebrew::Bundle::BrewServices).to \
|
||||||
|
receive(:restart).with(formula_name, verbose: false).and_return(true)
|
||||||
|
described_class.install(formula_name, preinstall: false, restart_service: :always)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when the link option is true" do
|
||||||
|
before do
|
||||||
|
allow_any_instance_of(described_class).to receive(:install_change_state!).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "links formula" do
|
||||||
|
allow_any_instance_of(described_class).to receive(:linked?).and_return(false)
|
||||||
|
expect(Homebrew::Bundle).to receive(:system).with(HOMEBREW_BREW_FILE, "link", "mysql",
|
||||||
|
verbose: false).and_return(true)
|
||||||
|
described_class.preinstall(formula_name, link: true)
|
||||||
|
described_class.install(formula_name, link: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "force-links keg-only formula" do
|
||||||
|
allow_any_instance_of(described_class).to receive(:linked?).and_return(false)
|
||||||
|
allow_any_instance_of(described_class).to receive(:keg_only?).and_return(true)
|
||||||
|
expect(Homebrew::Bundle).to receive(:system).with(HOMEBREW_BREW_FILE, "link", "--force", "mysql",
|
||||||
|
verbose: false).and_return(true)
|
||||||
|
described_class.preinstall(formula_name, link: true)
|
||||||
|
described_class.install(formula_name, link: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when the link option is :overwrite" do
|
||||||
|
before do
|
||||||
|
allow_any_instance_of(described_class).to receive(:install_change_state!).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "overwrite links formula" do
|
||||||
|
allow_any_instance_of(described_class).to receive(:linked?).and_return(false)
|
||||||
|
expect(Homebrew::Bundle).to receive(:system).with(HOMEBREW_BREW_FILE, "link", "--overwrite", "mysql",
|
||||||
|
verbose: false).and_return(true)
|
||||||
|
described_class.preinstall(formula_name, link: :overwrite)
|
||||||
|
described_class.install(formula_name, link: :overwrite)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when the link option is false" do
|
||||||
|
before do
|
||||||
|
allow_any_instance_of(described_class).to receive(:install_change_state!).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "unlinks formula" do
|
||||||
|
allow_any_instance_of(described_class).to receive(:linked?).and_return(true)
|
||||||
|
expect(Homebrew::Bundle).to receive(:system).with(HOMEBREW_BREW_FILE, "unlink", "mysql",
|
||||||
|
verbose: false).and_return(true)
|
||||||
|
described_class.preinstall(formula_name, link: false)
|
||||||
|
described_class.install(formula_name, link: false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when the link option is nil and formula is unlinked and not keg-only" do
|
||||||
|
before do
|
||||||
|
allow_any_instance_of(described_class).to receive(:install_change_state!).and_return(true)
|
||||||
|
allow_any_instance_of(described_class).to receive(:linked?).and_return(false)
|
||||||
|
allow_any_instance_of(described_class).to receive(:keg_only?).and_return(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "links formula" do
|
||||||
|
expect(Homebrew::Bundle).to receive(:system).with(HOMEBREW_BREW_FILE, "link", "mysql",
|
||||||
|
verbose: false).and_return(true)
|
||||||
|
described_class.preinstall(formula_name, link: nil)
|
||||||
|
described_class.install(formula_name, link: nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when the link option is nil and formula is linked and keg-only" do
|
||||||
|
before do
|
||||||
|
allow_any_instance_of(described_class).to receive(:install_change_state!).and_return(true)
|
||||||
|
allow_any_instance_of(described_class).to receive(:linked?).and_return(true)
|
||||||
|
allow_any_instance_of(described_class).to receive(:keg_only?).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "unlinks formula" do
|
||||||
|
expect(Homebrew::Bundle).to receive(:system).with(HOMEBREW_BREW_FILE, "unlink", "mysql",
|
||||||
|
verbose: false).and_return(true)
|
||||||
|
described_class.preinstall(formula_name, link: nil)
|
||||||
|
|
||||||
|
described_class.install(formula_name, link: nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when the conflicts_with option is provided" do
|
||||||
|
before do
|
||||||
|
allow(Homebrew::Bundle::BrewDumper).to receive(:formulae_by_full_name).and_call_original
|
||||||
|
allow(Homebrew::Bundle::BrewDumper).to receive(:formulae_by_full_name).with("mysql").and_return(
|
||||||
|
name: "mysql",
|
||||||
|
conflicts_with: ["mysql55"],
|
||||||
|
)
|
||||||
|
allow(described_class).to receive(:formula_installed?).and_return(true)
|
||||||
|
allow_any_instance_of(described_class).to receive(:install!).and_return(true)
|
||||||
|
allow_any_instance_of(described_class).to receive(:upgrade!).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "unlinks conflicts and stops their services" do
|
||||||
|
verbose = false
|
||||||
|
allow_any_instance_of(described_class).to receive(:linked?).and_return(true)
|
||||||
|
expect(Homebrew::Bundle).to receive(:system).with(HOMEBREW_BREW_FILE, "unlink", "mysql55",
|
||||||
|
verbose:).and_return(true)
|
||||||
|
expect(Homebrew::Bundle).to receive(:system).with(HOMEBREW_BREW_FILE, "unlink", "mysql56",
|
||||||
|
verbose:).and_return(true)
|
||||||
|
expect(Homebrew::Bundle::BrewServices).to receive(:stop).with("mysql55", verbose:).and_return(true)
|
||||||
|
expect(Homebrew::Bundle::BrewServices).to receive(:stop).with("mysql56", verbose:).and_return(true)
|
||||||
|
expect(Homebrew::Bundle::BrewServices).to receive(:restart).with(formula_name, verbose:).and_return(true)
|
||||||
|
described_class.preinstall(formula_name, restart_service: :always, conflicts_with: ["mysql56"])
|
||||||
|
described_class.install(formula_name, restart_service: :always, conflicts_with: ["mysql56"])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "prints a message" do
|
||||||
|
allow_any_instance_of(described_class).to receive(:linked?).and_return(true)
|
||||||
|
allow_any_instance_of(described_class).to receive(:puts)
|
||||||
|
verbose = true
|
||||||
|
expect(Homebrew::Bundle).to receive(:system).with(HOMEBREW_BREW_FILE, "unlink", "mysql55",
|
||||||
|
verbose:).and_return(true)
|
||||||
|
expect(Homebrew::Bundle).to receive(:system).with(HOMEBREW_BREW_FILE, "unlink", "mysql56",
|
||||||
|
verbose:).and_return(true)
|
||||||
|
expect(Homebrew::Bundle::BrewServices).to receive(:stop).with("mysql55", verbose:).and_return(true)
|
||||||
|
expect(Homebrew::Bundle::BrewServices).to receive(:stop).with("mysql56", verbose:).and_return(true)
|
||||||
|
expect(Homebrew::Bundle::BrewServices).to receive(:restart).with(formula_name, verbose:).and_return(true)
|
||||||
|
described_class.preinstall(formula_name, restart_service: :always, conflicts_with: ["mysql56"], verbose: true)
|
||||||
|
described_class.install(formula_name, restart_service: :always, conflicts_with: ["mysql56"], verbose: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when the postinstall option is provided" do
|
||||||
|
before do
|
||||||
|
allow_any_instance_of(described_class).to receive(:install_change_state!).and_return(true)
|
||||||
|
allow_any_instance_of(described_class).to receive(:installed?).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when formula has changed" do
|
||||||
|
before do
|
||||||
|
allow_any_instance_of(described_class).to receive(:changed?).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "runs the postinstall command" do
|
||||||
|
expect(Kernel).to receive(:system).with("custom command").and_return(true)
|
||||||
|
described_class.preinstall(formula_name, postinstall: "custom command")
|
||||||
|
described_class.install(formula_name, postinstall: "custom command")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "reports a failure" do
|
||||||
|
expect(Kernel).to receive(:system).with("custom command").and_return(false)
|
||||||
|
described_class.preinstall(formula_name, postinstall: "custom command")
|
||||||
|
expect(described_class.install(formula_name, postinstall: "custom command")).to be(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when formula has not changed" do
|
||||||
|
before do
|
||||||
|
allow_any_instance_of(described_class).to receive(:changed?).and_return(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not run the postinstall command" do
|
||||||
|
expect(Kernel).not_to receive(:system)
|
||||||
|
described_class.preinstall(formula_name, postinstall: "custom command")
|
||||||
|
described_class.install(formula_name, postinstall: "custom command")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when a formula isn't installed" do
|
||||||
|
before do
|
||||||
|
allow_any_instance_of(described_class).to receive(:installed?).and_return(false)
|
||||||
|
allow_any_instance_of(described_class).to receive(:install_change_state!).and_return(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "did not call restart service" do
|
||||||
|
expect(Homebrew::Bundle::BrewServices).not_to receive(:restart)
|
||||||
|
described_class.preinstall(formula_name, restart_service: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe ".outdated_formulae" do
|
||||||
|
it "calls Homebrew" do
|
||||||
|
described_class.reset!
|
||||||
|
expect(Homebrew::Bundle::BrewDumper).to receive(:formulae).and_return(
|
||||||
|
[
|
||||||
|
{ name: "a", outdated?: true },
|
||||||
|
{ name: "b", outdated?: true },
|
||||||
|
{ name: "c", outdated?: false },
|
||||||
|
],
|
||||||
|
)
|
||||||
|
expect(described_class.outdated_formulae).to eql(%w[a b])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe ".pinned_formulae" do
|
||||||
|
it "calls Homebrew" do
|
||||||
|
described_class.reset!
|
||||||
|
expect(Homebrew::Bundle::BrewDumper).to receive(:formulae).and_return(
|
||||||
|
[
|
||||||
|
{ name: "a", pinned?: true },
|
||||||
|
{ name: "b", pinned?: true },
|
||||||
|
{ name: "c", pinned?: false },
|
||||||
|
],
|
||||||
|
)
|
||||||
|
expect(described_class.pinned_formulae).to eql(%w[a b])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe ".formula_installed_and_up_to_date?" do
|
||||||
|
before do
|
||||||
|
Homebrew::Bundle::BrewDumper.reset!
|
||||||
|
described_class.reset!
|
||||||
|
allow(described_class).to receive(:outdated_formulae).and_return(%w[bar])
|
||||||
|
allow_any_instance_of(Formula).to receive(:outdated?).and_return(true)
|
||||||
|
allow(Homebrew::Bundle::BrewDumper).to receive(:formulae).and_return [
|
||||||
|
{
|
||||||
|
name: "foo",
|
||||||
|
full_name: "homebrew/tap/foo",
|
||||||
|
aliases: ["foobar"],
|
||||||
|
args: [],
|
||||||
|
version: "1.0",
|
||||||
|
dependencies: [],
|
||||||
|
requirements: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bar",
|
||||||
|
full_name: "bar",
|
||||||
|
aliases: [],
|
||||||
|
args: [],
|
||||||
|
version: "1.0",
|
||||||
|
dependencies: [],
|
||||||
|
requirements: [],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
stub_formula_loader formula("foo") { url "foo-1.0" }
|
||||||
|
stub_formula_loader formula("bar") { url "bar-1.0" }
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns result" do
|
||||||
|
expect(described_class.formula_installed_and_up_to_date?("foo")).to be(true)
|
||||||
|
expect(described_class.formula_installed_and_up_to_date?("foobar")).to be(true)
|
||||||
|
expect(described_class.formula_installed_and_up_to_date?("bar")).to be(false)
|
||||||
|
expect(described_class.formula_installed_and_up_to_date?("baz")).to be(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when brew is installed" do
|
||||||
|
context "when no formula is installed" do
|
||||||
|
before do
|
||||||
|
allow(described_class).to receive(:installed_formulae).and_return([])
|
||||||
|
allow_any_instance_of(described_class).to receive(:conflicts_with).and_return([])
|
||||||
|
allow_any_instance_of(described_class).to receive(:linked?).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "install formula" do
|
||||||
|
expect(Homebrew::Bundle).to receive(:system)
|
||||||
|
.with(HOMEBREW_BREW_FILE, "install", "--formula", formula_name, "--with-option", verbose: false)
|
||||||
|
.and_return(true)
|
||||||
|
expect(installer.preinstall).to be(true)
|
||||||
|
expect(installer.install).to be(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "reports a failure" do
|
||||||
|
expect(Homebrew::Bundle).to receive(:system)
|
||||||
|
.with(HOMEBREW_BREW_FILE, "install", "--formula", formula_name, "--with-option", verbose: false)
|
||||||
|
.and_return(false)
|
||||||
|
expect(installer.preinstall).to be(true)
|
||||||
|
expect(installer.install).to be(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when formula is installed" do
|
||||||
|
before do
|
||||||
|
allow(described_class).to receive(:installed_formulae).and_return([formula_name])
|
||||||
|
allow_any_instance_of(described_class).to receive(:conflicts_with).and_return([])
|
||||||
|
allow_any_instance_of(described_class).to receive(:linked?).and_return(true)
|
||||||
|
allow_any_instance_of(Formula).to receive(:outdated?).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when formula upgradable" do
|
||||||
|
before do
|
||||||
|
allow(described_class).to receive(:outdated_formulae).and_return([formula_name])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "upgrade formula" do
|
||||||
|
expect(Homebrew::Bundle).to \
|
||||||
|
receive(:system).with(HOMEBREW_BREW_FILE, "upgrade", "--formula", formula_name, verbose: false)
|
||||||
|
.and_return(true)
|
||||||
|
expect(installer.preinstall).to be(true)
|
||||||
|
expect(installer.install).to be(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "reports a failure" do
|
||||||
|
expect(Homebrew::Bundle).to \
|
||||||
|
receive(:system).with(HOMEBREW_BREW_FILE, "upgrade", "--formula", formula_name, verbose: false)
|
||||||
|
.and_return(false)
|
||||||
|
expect(installer.preinstall).to be(true)
|
||||||
|
expect(installer.install).to be(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when formula pinned" do
|
||||||
|
before do
|
||||||
|
allow(described_class).to receive(:pinned_formulae).and_return([formula_name])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not upgrade formula" do
|
||||||
|
expect(Homebrew::Bundle).not_to \
|
||||||
|
receive(:system).with(HOMEBREW_BREW_FILE, "upgrade", "--formula", formula_name, verbose: false)
|
||||||
|
expect(installer.preinstall).to be(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when formula not upgraded" do
|
||||||
|
before do
|
||||||
|
allow(described_class).to receive(:outdated_formulae).and_return([])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not upgrade formula" do
|
||||||
|
expect(Homebrew::Bundle).not_to receive(:system)
|
||||||
|
expect(installer.preinstall).to be(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#changed?" do
|
||||||
|
it "is false by default" do
|
||||||
|
expect(described_class.new(formula_name).changed?).to be(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#start_service?" do
|
||||||
|
it "is false by default" do
|
||||||
|
expect(described_class.new(formula_name).start_service?).to be(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when the start_service option is true" do
|
||||||
|
it "is true" do
|
||||||
|
expect(described_class.new(formula_name, start_service: true).start_service?).to be(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#start_service_needed?" do
|
||||||
|
context "when a service is already started" do
|
||||||
|
before do
|
||||||
|
allow(Homebrew::Bundle::BrewServices).to receive(:started?).with(formula_name).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "is false by default" do
|
||||||
|
expect(described_class.new(formula_name).start_service_needed?).to be(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "is false with {start_service: true}" do
|
||||||
|
expect(described_class.new(formula_name, start_service: true).start_service_needed?).to be(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "is false with {restart_service: true}" do
|
||||||
|
expect(described_class.new(formula_name, restart_service: true).start_service_needed?).to be(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "is false with {restart_service: :changed}" do
|
||||||
|
expect(described_class.new(formula_name, restart_service: :changed).start_service_needed?).to be(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "is false with {restart_service: :always}" do
|
||||||
|
expect(described_class.new(formula_name, restart_service: :always).start_service_needed?).to be(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when a service is not started" do
|
||||||
|
before do
|
||||||
|
allow(Homebrew::Bundle::BrewServices).to receive(:started?).with(formula_name).and_return(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "is false by default" do
|
||||||
|
expect(described_class.new(formula_name).start_service_needed?).to be(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "is true if {start_service: true}" do
|
||||||
|
expect(described_class.new(formula_name, start_service: true).start_service_needed?).to be(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "is true if {restart_service: true}" do
|
||||||
|
expect(described_class.new(formula_name, restart_service: true).start_service_needed?).to be(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "is true if {restart_service: :changed}" do
|
||||||
|
expect(described_class.new(formula_name, restart_service: :changed).start_service_needed?).to be(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "is true if {restart_service: :always}" do
|
||||||
|
expect(described_class.new(formula_name, restart_service: :always).start_service_needed?).to be(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#restart_service?" do
|
||||||
|
it "is false by default" do
|
||||||
|
expect(described_class.new(formula_name).restart_service?).to be(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when the restart_service option is true" do
|
||||||
|
it "is true" do
|
||||||
|
expect(described_class.new(formula_name, restart_service: true).restart_service?).to be(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when the restart_service option is always" do
|
||||||
|
it "is true" do
|
||||||
|
expect(described_class.new(formula_name, restart_service: :always).restart_service?).to be(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when the restart_service option is changed" do
|
||||||
|
it "is true" do
|
||||||
|
expect(described_class.new(formula_name, restart_service: :changed).restart_service?).to be(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#restart_service_needed?" do
|
||||||
|
it "is false by default" do
|
||||||
|
expect(described_class.new(formula_name).restart_service_needed?).to be(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when a service is unchanged" do
|
||||||
|
before do
|
||||||
|
allow_any_instance_of(described_class).to receive(:changed?).and_return(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "is false with {restart_service: true}" do
|
||||||
|
expect(described_class.new(formula_name, restart_service: true).restart_service_needed?).to be(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "is true with {restart_service: :always}" do
|
||||||
|
expect(described_class.new(formula_name, restart_service: :always).restart_service_needed?).to be(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "is false if {restart_service: :changed}" do
|
||||||
|
expect(described_class.new(formula_name, restart_service: :changed).restart_service_needed?).to be(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when a service is changed" do
|
||||||
|
before do
|
||||||
|
allow_any_instance_of(described_class).to receive(:changed?).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "is true with {restart_service: true}" do
|
||||||
|
expect(described_class.new(formula_name, restart_service: true).restart_service_needed?).to be(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "is true with {restart_service: :always}" do
|
||||||
|
expect(described_class.new(formula_name, restart_service: :always).restart_service_needed?).to be(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "is true if {restart_service: :changed}" do
|
||||||
|
expect(described_class.new(formula_name, restart_service: :changed).restart_service_needed?).to be(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
62
Library/Homebrew/test/bundle/brew_services_spec.rb
Normal file
62
Library/Homebrew/test/bundle/brew_services_spec.rb
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "bundle"
|
||||||
|
|
||||||
|
RSpec.describe Homebrew::Bundle::BrewServices do
|
||||||
|
describe ".started_services" do
|
||||||
|
before do
|
||||||
|
described_class.reset!
|
||||||
|
end
|
||||||
|
|
||||||
|
it "is empty when brew services not installed" do
|
||||||
|
allow(Homebrew::Bundle).to receive(:services_installed?).and_return(false)
|
||||||
|
expect(described_class.started_services).to be_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns started services" do
|
||||||
|
allow(Homebrew::Bundle).to receive(:services_installed?).and_return(true)
|
||||||
|
allow(Utils).to receive(:safe_popen_read).and_return <<~EOS
|
||||||
|
nginx started homebrew.mxcl.nginx.plist
|
||||||
|
apache stopped homebrew.mxcl.apache.plist
|
||||||
|
mysql started homebrew.mxcl.mysql.plist
|
||||||
|
EOS
|
||||||
|
expect(described_class.started_services).to contain_exactly("nginx", "mysql")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when brew-services is installed" do
|
||||||
|
context "when the service is stopped" do
|
||||||
|
it "when the service is started" do
|
||||||
|
allow(described_class).to receive(:started_services).and_return(%w[nginx])
|
||||||
|
expect(Homebrew::Bundle).to receive(:system).with(HOMEBREW_BREW_FILE, "services", "stop", "nginx",
|
||||||
|
verbose: false).and_return(true)
|
||||||
|
expect(described_class.stop("nginx")).to be(true)
|
||||||
|
expect(described_class.started_services).not_to include("nginx")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "when the service is already stopped" do
|
||||||
|
allow(described_class).to receive(:started_services).and_return(%w[])
|
||||||
|
expect(Homebrew::Bundle).not_to receive(:system).with(HOMEBREW_BREW_FILE, "services", "stop", "nginx",
|
||||||
|
verbose: false)
|
||||||
|
expect(described_class.stop("nginx")).to be(true)
|
||||||
|
expect(described_class.started_services).not_to include("nginx")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "starts the service" do
|
||||||
|
allow(described_class).to receive(:started_services).and_return([])
|
||||||
|
expect(Homebrew::Bundle).to receive(:system).with(HOMEBREW_BREW_FILE, "services", "start", "nginx",
|
||||||
|
verbose: false).and_return(true)
|
||||||
|
expect(described_class.start("nginx")).to be(true)
|
||||||
|
expect(described_class.started_services).to include("nginx")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "restarts the service" do
|
||||||
|
allow(described_class).to receive(:started_services).and_return([])
|
||||||
|
expect(Homebrew::Bundle).to receive(:system).with(HOMEBREW_BREW_FILE, "services", "restart", "nginx",
|
||||||
|
verbose: false).and_return(true)
|
||||||
|
expect(described_class.restart("nginx")).to be(true)
|
||||||
|
expect(described_class.started_services).to include("nginx")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
199
Library/Homebrew/test/bundle/brewfile_spec.rb
Normal file
199
Library/Homebrew/test/bundle/brewfile_spec.rb
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "bundle"
|
||||||
|
|
||||||
|
RSpec.describe Homebrew::Bundle::Brewfile do
|
||||||
|
describe "path" do
|
||||||
|
subject(:path) do
|
||||||
|
described_class.path(dash_writes_to_stdout:, global: has_global, file: file_value)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:dash_writes_to_stdout) { false }
|
||||||
|
let(:env_bundle_file_global_value) { nil }
|
||||||
|
let(:env_bundle_file_value) { nil }
|
||||||
|
let(:env_user_config_home_value) { "/Users/username/.homebrew" }
|
||||||
|
let(:file_value) { nil }
|
||||||
|
let(:has_global) { false }
|
||||||
|
let(:config_dir_brewfile_exist) { false }
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(ENV).to receive(:fetch).and_return(nil)
|
||||||
|
allow(ENV).to receive(:fetch).with("HOMEBREW_BUNDLE_FILE_GLOBAL", any_args)
|
||||||
|
.and_return(env_bundle_file_global_value)
|
||||||
|
allow(ENV).to receive(:fetch).with("HOMEBREW_BUNDLE_FILE", any_args)
|
||||||
|
.and_return(env_bundle_file_value)
|
||||||
|
|
||||||
|
allow(ENV).to receive(:fetch).with("HOMEBREW_USER_CONFIG_HOME", any_args)
|
||||||
|
.and_return(env_user_config_home_value)
|
||||||
|
allow(File).to receive(:exist?).with("/Users/username/.homebrew/Brewfile")
|
||||||
|
.and_return(config_dir_brewfile_exist)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when `file` is specified with a relative path" do
|
||||||
|
let(:file_value) { "path/to/Brewfile" }
|
||||||
|
let(:expected_pathname) { Pathname.new(file_value).expand_path(Dir.pwd) }
|
||||||
|
|
||||||
|
it "returns the expected path" do
|
||||||
|
expect(path).to eq(expected_pathname)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with a configured HOMEBREW_BUNDLE_FILE" do
|
||||||
|
let(:env_bundle_file_value) { "/path/to/Brewfile" }
|
||||||
|
|
||||||
|
it "returns the value specified by `file` path" do
|
||||||
|
expect(path).to eq(expected_pathname)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with an empty HOMEBREW_BUNDLE_FILE" do
|
||||||
|
let(:env_bundle_file_value) { "" }
|
||||||
|
|
||||||
|
it "returns the value specified by `file` path" do
|
||||||
|
expect(path).to eq(expected_pathname)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when `file` is specified with an absolute path" do
|
||||||
|
let(:file_value) { "/tmp/random_file" }
|
||||||
|
let(:expected_pathname) { Pathname.new(file_value) }
|
||||||
|
|
||||||
|
it "returns the expected path" do
|
||||||
|
expect(path).to eq(expected_pathname)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with a configured HOMEBREW_BUNDLE_FILE" do
|
||||||
|
let(:env_bundle_file_value) { "/path/to/Brewfile" }
|
||||||
|
|
||||||
|
it "returns the value specified by `file` path" do
|
||||||
|
expect(path).to eq(expected_pathname)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with an empty HOMEBREW_BUNDLE_FILE" do
|
||||||
|
let(:env_bundle_file_value) { "" }
|
||||||
|
|
||||||
|
it "returns the value specified by `file` path" do
|
||||||
|
expect(path).to eq(expected_pathname)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when `file` is specified with `-`" do
|
||||||
|
let(:file_value) { "-" }
|
||||||
|
let(:expected_pathname) { Pathname.new("/dev/stdin") }
|
||||||
|
|
||||||
|
it "returns stdin by default" do
|
||||||
|
expect(path).to eq(expected_pathname)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with a configured HOMEBREW_BUNDLE_FILE" do
|
||||||
|
let(:env_bundle_file_value) { "/path/to/Brewfile" }
|
||||||
|
|
||||||
|
it "returns the value specified by `file` path" do
|
||||||
|
expect(path).to eq(expected_pathname)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with an empty HOMEBREW_BUNDLE_FILE" do
|
||||||
|
let(:env_bundle_file_value) { "" }
|
||||||
|
|
||||||
|
it "returns the value specified by `file` path" do
|
||||||
|
expect(path).to eq(expected_pathname)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when `dash_writes_to_stdout` is true" do
|
||||||
|
let(:expected_pathname) { Pathname.new("/dev/stdout") }
|
||||||
|
let(:dash_writes_to_stdout) { true }
|
||||||
|
|
||||||
|
it "returns stdout" do
|
||||||
|
expect(path).to eq(expected_pathname)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with a configured HOMEBREW_BUNDLE_FILE" do
|
||||||
|
let(:env_bundle_file_value) { "/path/to/Brewfile" }
|
||||||
|
|
||||||
|
it "returns the value specified by `file` path" do
|
||||||
|
expect(path).to eq(expected_pathname)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with an empty HOMEBREW_BUNDLE_FILE" do
|
||||||
|
let(:env_bundle_file_value) { "" }
|
||||||
|
|
||||||
|
it "returns the value specified by `file` path" do
|
||||||
|
expect(path).to eq(expected_pathname)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when `global` is true" do
|
||||||
|
let(:has_global) { true }
|
||||||
|
let(:expected_pathname) { Pathname.new("#{Dir.home}/.Brewfile") }
|
||||||
|
|
||||||
|
it "returns the expected path" do
|
||||||
|
expect(path).to eq(expected_pathname)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when HOMEBREW_BUNDLE_FILE_GLOBAL is set" do
|
||||||
|
let(:env_bundle_file_global_value) { "/path/to/Brewfile" }
|
||||||
|
let(:expected_pathname) { Pathname.new(env_bundle_file_global_value) }
|
||||||
|
|
||||||
|
it "returns the value specified by the environment variable" do
|
||||||
|
expect(path).to eq(expected_pathname)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when HOMEBREW_BUNDLE_FILE is set" do
|
||||||
|
let(:env_bundle_file_value) { "/path/to/Brewfile" }
|
||||||
|
|
||||||
|
it "returns the value specified by the variable" do
|
||||||
|
expect { path }.to raise_error(RuntimeError)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when HOMEBREW_BUNDLE_FILE is `` (empty)" do
|
||||||
|
let(:env_bundle_file_value) { "" }
|
||||||
|
|
||||||
|
it "returns the value specified by `file` path" do
|
||||||
|
expect(path).to eq(expected_pathname)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when HOMEBREW_USER_CONFIG_HOME/Brewfile exists" do
|
||||||
|
let(:config_dir_brewfile_exist) { true }
|
||||||
|
let(:expected_pathname) { Pathname.new("#{env_user_config_home_value}/Brewfile") }
|
||||||
|
|
||||||
|
it "returns the expected path" do
|
||||||
|
expect(path).to eq(expected_pathname)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when HOMEBREW_BUNDLE_FILE has a value" do
|
||||||
|
let(:env_bundle_file_value) { "/path/to/Brewfile" }
|
||||||
|
|
||||||
|
it "returns the expected path" do
|
||||||
|
expect(path).to eq(Pathname.new(env_bundle_file_value))
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "that is `` (empty)" do
|
||||||
|
let(:env_bundle_file_value) { "" }
|
||||||
|
|
||||||
|
it "defaults to `${PWD}/Brewfile`" do
|
||||||
|
expect(path).to eq(Pathname.new("Brewfile").expand_path(Dir.pwd))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "that is `nil`" do
|
||||||
|
let(:env_bundle_file_value) { nil }
|
||||||
|
|
||||||
|
it "defaults to `${PWD}/Brewfile`" do
|
||||||
|
expect(path).to eq(Pathname.new("Brewfile").expand_path(Dir.pwd))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
56
Library/Homebrew/test/bundle/bundle_spec.rb
Normal file
56
Library/Homebrew/test/bundle/bundle_spec.rb
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "bundle"
|
||||||
|
|
||||||
|
RSpec.describe Homebrew::Bundle do
|
||||||
|
context "when the system call succeeds" do
|
||||||
|
it "omits all stdout output if verbose is false" do
|
||||||
|
expect { described_class.system "echo", "foo", verbose: false }.not_to output.to_stdout_from_any_process
|
||||||
|
end
|
||||||
|
|
||||||
|
it "emits all stdout output if verbose is true" do
|
||||||
|
expect { described_class.system "echo", "foo", verbose: true }.to output("foo\n").to_stdout_from_any_process
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when the system call fails" do
|
||||||
|
it "emits all stdout output even if verbose is false" do
|
||||||
|
expect do
|
||||||
|
described_class.system "/bin/bash", "-c", "echo foo && false",
|
||||||
|
verbose: false
|
||||||
|
end.to output("foo\n").to_stdout_from_any_process
|
||||||
|
end
|
||||||
|
|
||||||
|
it "emits all stdout output only once if verbose is true" do
|
||||||
|
expect do
|
||||||
|
described_class.system "/bin/bash", "-c", "echo foo && true",
|
||||||
|
verbose: true
|
||||||
|
end.to output("foo\n").to_stdout_from_any_process
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when checking for homebrew/cask", :needs_macos do
|
||||||
|
it "finds it when present" do
|
||||||
|
allow(File).to receive(:directory?).with("#{HOMEBREW_PREFIX}/Caskroom").and_return(true)
|
||||||
|
allow(File).to receive(:directory?)
|
||||||
|
.with("#{HOMEBREW_LIBRARY}/Taps/homebrew/homebrew-cask")
|
||||||
|
.and_return(true)
|
||||||
|
expect(described_class.cask_installed?).to be(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when checking for brew services", :needs_macos do
|
||||||
|
it "finds it when present" do
|
||||||
|
allow(described_class).to receive(:which).and_return(true)
|
||||||
|
expect(described_class.services_installed?).to be(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when checking for mas", :needs_macos do
|
||||||
|
it "finds it when present" do
|
||||||
|
stub_formula_loader formula("mas") { url "mas-1.0" }
|
||||||
|
allow(described_class).to receive(:which).and_return(true)
|
||||||
|
expect(described_class.mas_installed?).to be(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
122
Library/Homebrew/test/bundle/cask_dumper_spec.rb
Normal file
122
Library/Homebrew/test/bundle/cask_dumper_spec.rb
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "bundle"
|
||||||
|
require "cask"
|
||||||
|
|
||||||
|
RSpec.describe Homebrew::Bundle::CaskDumper do
|
||||||
|
subject(:dumper) { described_class }
|
||||||
|
|
||||||
|
context "when brew-cask is not installed" do
|
||||||
|
before do
|
||||||
|
described_class.reset!
|
||||||
|
allow(Homebrew::Bundle).to receive(:cask_installed?).and_return(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns empty list" do
|
||||||
|
expect(dumper.cask_names).to be_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
it "dumps as empty string" do
|
||||||
|
expect(dumper.dump).to eql("")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when there is no cask" do
|
||||||
|
before do
|
||||||
|
described_class.reset!
|
||||||
|
allow(Homebrew::Bundle).to receive(:cask_installed?).and_return(true)
|
||||||
|
allow(described_class).to receive(:`).and_return("")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns empty list" do
|
||||||
|
expect(dumper.cask_names).to be_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
it "dumps as empty string" do
|
||||||
|
expect(dumper.dump).to eql("")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn’t want to greedily update a non-installed cask" do
|
||||||
|
expect(dumper.cask_is_outdated_using_greedy?("foo")).to be(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when casks `foo`, `bar` and `baz` are installed, with `baz` being a formula requirement" do
|
||||||
|
let(:foo) { instance_double(Cask::Cask, to_s: "foo", desc: nil, config: nil) }
|
||||||
|
let(:baz) { instance_double(Cask::Cask, to_s: "baz", desc: "Software", config: nil) }
|
||||||
|
let(:bar) do
|
||||||
|
instance_double(
|
||||||
|
Cask::Cask, to_s: "bar",
|
||||||
|
desc: nil,
|
||||||
|
config: instance_double(
|
||||||
|
Cask::Config,
|
||||||
|
explicit: {
|
||||||
|
fontdir: "/Library/Fonts",
|
||||||
|
languages: ["zh-TW"],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
described_class.reset!
|
||||||
|
|
||||||
|
allow(Homebrew::Bundle).to receive(:cask_installed?).and_return(true)
|
||||||
|
allow(Cask::Caskroom).to receive(:casks).and_return([foo, bar, baz])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns list %w[foo bar baz]" do
|
||||||
|
expect(dumper.cask_names).to eql(%w[foo bar baz])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "dumps as `cask 'baz'` and `cask 'foo' cask 'bar'` plus descriptions and config values" do
|
||||||
|
expected = <<~EOS
|
||||||
|
cask "foo"
|
||||||
|
cask "bar", args: { fontdir: "/Library/Fonts", language: "zh-TW" }
|
||||||
|
# Software
|
||||||
|
cask "baz"
|
||||||
|
EOS
|
||||||
|
expect(dumper.dump(describe: true)).to eql(expected.chomp)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn’t want to greedily update a non-installed cask" do
|
||||||
|
expect(dumper.cask_is_outdated_using_greedy?("qux")).to be(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "wants to greedily update foo if there is an update available" do
|
||||||
|
expect(foo).to receive(:outdated?).with(greedy: true).and_return(true)
|
||||||
|
expect(dumper.cask_is_outdated_using_greedy?("foo")).to be(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not want to greedily update bar if there is no update available" do
|
||||||
|
expect(bar).to receive(:outdated?).with(greedy: true).and_return(false)
|
||||||
|
expect(dumper.cask_is_outdated_using_greedy?("bar")).to be(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#formula_dependencies" do
|
||||||
|
context "when the given casks don't have formula dependencies" do
|
||||||
|
before do
|
||||||
|
described_class.reset!
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns an empty array" do
|
||||||
|
expect(dumper.formula_dependencies(["foo"])).to eql([])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when multiple casks have the same dependency" do
|
||||||
|
before do
|
||||||
|
described_class.reset!
|
||||||
|
foo = instance_double(Cask::Cask, to_s: "foo", depends_on: { formula: ["baz", "qux"] })
|
||||||
|
bar = instance_double(Cask::Cask, to_s: "bar", depends_on: {})
|
||||||
|
allow(Cask::Caskroom).to receive(:casks).and_return([foo, bar])
|
||||||
|
allow(Homebrew::Bundle).to receive(:cask_installed?).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns an array of unique formula dependencies" do
|
||||||
|
expect(dumper.formula_dependencies(["foo", "bar"])).to eql(["baz", "qux"])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
162
Library/Homebrew/test/bundle/cask_installer_spec.rb
Normal file
162
Library/Homebrew/test/bundle/cask_installer_spec.rb
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "bundle"
|
||||||
|
|
||||||
|
RSpec.describe Homebrew::Bundle::CaskInstaller do
|
||||||
|
describe ".installed_casks" do
|
||||||
|
before do
|
||||||
|
Homebrew::Bundle::CaskDumper.reset!
|
||||||
|
end
|
||||||
|
|
||||||
|
it "shells out" do
|
||||||
|
expect { described_class.installed_casks }.not_to raise_error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe ".cask_installed_and_up_to_date?" do
|
||||||
|
it "returns result" do
|
||||||
|
described_class.reset!
|
||||||
|
allow(described_class).to receive_messages(installed_casks: ["foo", "baz"],
|
||||||
|
outdated_casks: ["baz"])
|
||||||
|
expect(described_class.cask_installed_and_up_to_date?("foo")).to be(true)
|
||||||
|
expect(described_class.cask_installed_and_up_to_date?("baz")).to be(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when brew-cask is not installed" do
|
||||||
|
describe ".outdated_casks" do
|
||||||
|
it "returns empty array" do
|
||||||
|
described_class.reset!
|
||||||
|
expect(described_class.outdated_casks).to eql([])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when brew-cask is installed" do
|
||||||
|
before do
|
||||||
|
Homebrew::Bundle::CaskDumper.reset!
|
||||||
|
allow(Homebrew::Bundle).to receive(:cask_installed?).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe ".outdated_casks" do
|
||||||
|
it "returns empty array" do
|
||||||
|
described_class.reset!
|
||||||
|
expect(described_class.outdated_casks).to eql([])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when cask is installed" do
|
||||||
|
before do
|
||||||
|
Homebrew::Bundle::CaskDumper.reset!
|
||||||
|
allow(described_class).to receive(:installed_casks).and_return(["google-chrome"])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "skips" do
|
||||||
|
expect(Homebrew::Bundle).not_to receive(:system)
|
||||||
|
expect(described_class.preinstall("google-chrome")).to be(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when cask is outdated" do
|
||||||
|
before do
|
||||||
|
allow(described_class).to receive_messages(installed_casks: ["google-chrome"],
|
||||||
|
outdated_casks: ["google-chrome"])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "upgrades" do
|
||||||
|
expect(Homebrew::Bundle).to \
|
||||||
|
receive(:system).with(HOMEBREW_BREW_FILE, "upgrade", "--cask", "google-chrome", verbose: false)
|
||||||
|
.and_return(true)
|
||||||
|
expect(described_class.preinstall("google-chrome")).to be(true)
|
||||||
|
expect(described_class.install("google-chrome")).to be(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when cask is outdated and uses auto-update" do
|
||||||
|
before do
|
||||||
|
described_class.reset!
|
||||||
|
allow(Homebrew::Bundle::CaskDumper).to receive_messages(cask_names: ["opera"], outdated_cask_names: [])
|
||||||
|
allow(Homebrew::Bundle::CaskDumper).to receive(:cask_is_outdated_using_greedy?).with("opera").and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "upgrades" do
|
||||||
|
expect(Homebrew::Bundle).to \
|
||||||
|
receive(:system).with(HOMEBREW_BREW_FILE, "upgrade", "--cask", "opera", verbose: false)
|
||||||
|
.and_return(true)
|
||||||
|
expect(described_class.preinstall("opera", greedy: true)).to be(true)
|
||||||
|
expect(described_class.install("opera", greedy: true)).to be(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when cask is not installed" do
|
||||||
|
before do
|
||||||
|
allow(described_class).to receive(:installed_casks).and_return([])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "installs cask" do
|
||||||
|
expect(Homebrew::Bundle).to receive(:brew).with("install", "--cask", "google-chrome", "--adopt",
|
||||||
|
verbose: false)
|
||||||
|
.and_return(true)
|
||||||
|
expect(described_class.preinstall("google-chrome")).to be(true)
|
||||||
|
expect(described_class.install("google-chrome")).to be(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "installs cask with arguments" do
|
||||||
|
expect(Homebrew::Bundle).to(
|
||||||
|
receive(:brew).with("install", "--cask", "firefox", "--appdir=/Applications", "--adopt",
|
||||||
|
verbose: false)
|
||||||
|
.and_return(true),
|
||||||
|
)
|
||||||
|
expect(described_class.preinstall("firefox", args: { appdir: "/Applications" })).to be(true)
|
||||||
|
expect(described_class.install("firefox", args: { appdir: "/Applications" })).to be(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "reports a failure" do
|
||||||
|
expect(Homebrew::Bundle).to receive(:brew).with("install", "--cask", "google-chrome", "--adopt",
|
||||||
|
verbose: false)
|
||||||
|
.and_return(false)
|
||||||
|
expect(described_class.preinstall("google-chrome")).to be(true)
|
||||||
|
expect(described_class.install("google-chrome")).to be(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with boolean arguments" do
|
||||||
|
it "includes a flag if true" do
|
||||||
|
expect(Homebrew::Bundle).to receive(:brew).with("install", "--cask", "iterm", "--force",
|
||||||
|
verbose: false)
|
||||||
|
.and_return(true)
|
||||||
|
expect(described_class.preinstall("iterm", args: { force: true })).to be(true)
|
||||||
|
expect(described_class.install("iterm", args: { force: true })).to be(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not include a flag if false" do
|
||||||
|
expect(Homebrew::Bundle).to receive(:brew).with("install", "--cask", "iterm", "--adopt", verbose: false)
|
||||||
|
.and_return(true)
|
||||||
|
expect(described_class.preinstall("iterm", args: { force: false })).to be(true)
|
||||||
|
expect(described_class.install("iterm", args: { force: false })).to be(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when the postinstall option is provided" do
|
||||||
|
before do
|
||||||
|
Homebrew::Bundle::CaskDumper.reset!
|
||||||
|
allow(Homebrew::Bundle::CaskDumper).to receive_messages(cask_names: ["google-chrome"],
|
||||||
|
outdated_cask_names: ["google-chrome"])
|
||||||
|
allow(Homebrew::Bundle).to receive(:brew).and_return(true)
|
||||||
|
allow(described_class).to receive(:upgrading?).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "runs the postinstall command" do
|
||||||
|
expect(Kernel).to receive(:system).with("custom command").and_return(true)
|
||||||
|
expect(described_class.preinstall("google-chrome", postinstall: "custom command")).to be(true)
|
||||||
|
expect(described_class.install("google-chrome", postinstall: "custom command")).to be(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "reports a failure when postinstall fails" do
|
||||||
|
expect(Kernel).to receive(:system).with("custom command").and_return(false)
|
||||||
|
expect(described_class.preinstall("google-chrome", postinstall: "custom command")).to be(true)
|
||||||
|
expect(described_class.install("google-chrome", postinstall: "custom command")).to be(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
49
Library/Homebrew/test/bundle/commands/add_spec.rb
Normal file
49
Library/Homebrew/test/bundle/commands/add_spec.rb
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "bundle"
|
||||||
|
require "cask/cask_loader"
|
||||||
|
|
||||||
|
RSpec.describe Homebrew::Bundle::Commands::Add do
|
||||||
|
subject(:add) do
|
||||||
|
described_class.run(*args, type:, global:, file:)
|
||||||
|
end
|
||||||
|
|
||||||
|
before { FileUtils.touch file }
|
||||||
|
after { FileUtils.rm_f file }
|
||||||
|
|
||||||
|
let(:global) { false }
|
||||||
|
|
||||||
|
context "when called with a valid formula" do
|
||||||
|
let(:args) { ["hello"] }
|
||||||
|
let(:type) { :brew }
|
||||||
|
let(:file) { "/tmp/some_random_brewfile#{Random.rand(2 ** 16)}" }
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_formula_loader formula("hello") { url "hello-1.0" }
|
||||||
|
end
|
||||||
|
|
||||||
|
it "adds entries to the given Brewfile" do
|
||||||
|
expect { add }.not_to raise_error
|
||||||
|
expect(File.read(file)).to include("#{type} \"#{args.first}\"")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when called with a valid cask" do
|
||||||
|
let(:args) { ["alacritty"] }
|
||||||
|
let(:type) { :cask }
|
||||||
|
let(:file) { "/tmp/some_random_brewfile#{Random.rand(2 ** 16)}" }
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_cask_loader Cask::CaskLoader::FromContentLoader.new(+<<~RUBY).load(config: nil)
|
||||||
|
cask "alacritty" do
|
||||||
|
version "1.0"
|
||||||
|
end
|
||||||
|
RUBY
|
||||||
|
end
|
||||||
|
|
||||||
|
it "adds entries to the given Brewfile" do
|
||||||
|
expect { add }.not_to raise_error
|
||||||
|
expect(File.read(file)).to include("#{type} \"#{args.first}\"")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
286
Library/Homebrew/test/bundle/commands/check_spec.rb
Normal file
286
Library/Homebrew/test/bundle/commands/check_spec.rb
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "bundle"
|
||||||
|
|
||||||
|
RSpec.describe Homebrew::Bundle::Commands::Check do
|
||||||
|
let(:do_check) do
|
||||||
|
described_class.run(no_upgrade:, verbose:)
|
||||||
|
end
|
||||||
|
let(:no_upgrade) { false }
|
||||||
|
let(:verbose) { false }
|
||||||
|
|
||||||
|
before do
|
||||||
|
Homebrew::Bundle::Checker.reset!
|
||||||
|
allow_any_instance_of(IO).to receive(:puts)
|
||||||
|
stub_formula_loader formula("mas") { url "mas-1.0" }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when dependencies are satisfied" do
|
||||||
|
it "does not raise an error" do
|
||||||
|
allow_any_instance_of(Pathname).to receive(:read).and_return("")
|
||||||
|
nothing = []
|
||||||
|
allow(Homebrew::Bundle::Checker).to receive_messages(casks_to_install: nothing,
|
||||||
|
formulae_to_install: nothing,
|
||||||
|
apps_to_install: nothing,
|
||||||
|
taps_to_tap: nothing,
|
||||||
|
extensions_to_install: nothing)
|
||||||
|
expect { do_check }.not_to raise_error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when no dependencies are specified" do
|
||||||
|
it "does not raise an error" do
|
||||||
|
allow_any_instance_of(Pathname).to receive(:read).and_return("")
|
||||||
|
allow_any_instance_of(Homebrew::Bundle::Dsl).to receive(:entries).and_return([])
|
||||||
|
expect { do_check }.not_to raise_error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when casks are not installed", :needs_macos do
|
||||||
|
it "raises an error" do
|
||||||
|
allow(Homebrew::Bundle).to receive(:cask_installed?).and_return(true)
|
||||||
|
allow(Homebrew::Bundle::CaskDumper).to receive(:casks).and_return([])
|
||||||
|
allow(Homebrew::Bundle::BrewInstaller).to receive(:upgradable_formulae).and_return([])
|
||||||
|
allow_any_instance_of(Pathname).to receive(:read).and_return("cask 'abc'")
|
||||||
|
expect { do_check }.to raise_error(SystemExit)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when formulae are not installed" do
|
||||||
|
it "raises an error" do
|
||||||
|
allow(Homebrew::Bundle::CaskDumper).to receive(:casks).and_return([])
|
||||||
|
allow(Homebrew::Bundle::BrewInstaller).to receive(:upgradable_formulae).and_return([])
|
||||||
|
allow_any_instance_of(Pathname).to receive(:read).and_return("brew 'abc'")
|
||||||
|
expect { do_check }.to raise_error(SystemExit)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not raise error on skippable formula" do
|
||||||
|
allow(Homebrew::Bundle::CaskDumper).to receive(:casks).and_return([])
|
||||||
|
allow(Homebrew::Bundle::BrewInstaller).to receive(:upgradable_formulae).and_return([])
|
||||||
|
allow(Homebrew::Bundle::Skipper).to receive(:skip?).and_return(true)
|
||||||
|
allow_any_instance_of(Pathname).to receive(:read).and_return("brew 'abc'")
|
||||||
|
expect { do_check }.not_to raise_error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when taps are not tapped" do
|
||||||
|
it "raises an error" do
|
||||||
|
allow(Homebrew::Bundle::CaskDumper).to receive(:casks).and_return([])
|
||||||
|
allow(Homebrew::Bundle::BrewInstaller).to receive(:upgradable_formulae).and_return([])
|
||||||
|
allow_any_instance_of(Pathname).to receive(:read).and_return("tap 'abc/def'")
|
||||||
|
expect { do_check }.to raise_error(SystemExit)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when apps are not installed", :needs_macos do
|
||||||
|
it "raises an error" do
|
||||||
|
allow_any_instance_of(Homebrew::Bundle::MacAppStoreDumper).to receive(:app_ids).and_return([])
|
||||||
|
allow(Homebrew::Bundle::BrewInstaller).to receive(:upgradable_formulae).and_return([])
|
||||||
|
allow_any_instance_of(Pathname).to receive(:read).and_return("mas 'foo', id: 123")
|
||||||
|
expect { do_check }.to raise_error(SystemExit)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when service is not started and app not installed" do
|
||||||
|
let(:verbose) { true }
|
||||||
|
let(:expected_output) do
|
||||||
|
<<~MSG
|
||||||
|
brew bundle can't satisfy your Brewfile's dependencies.
|
||||||
|
→ App foo needs to be installed or updated.
|
||||||
|
→ Service def needs to be started.
|
||||||
|
Satisfy missing dependencies with `brew bundle install`.
|
||||||
|
MSG
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
Homebrew::Bundle::Checker.reset!
|
||||||
|
allow_any_instance_of(Homebrew::Bundle::Checker::MacAppStoreChecker).to \
|
||||||
|
receive(:installed_and_up_to_date?).and_return(false)
|
||||||
|
allow(Homebrew::Bundle::BrewInstaller).to receive_messages(installed_formulae: ["abc", "def"],
|
||||||
|
upgradable_formulae: [])
|
||||||
|
allow(Homebrew::Bundle::BrewServices).to receive(:started?).with("abc").and_return(true)
|
||||||
|
allow(Homebrew::Bundle::BrewServices).to receive(:started?).with("def").and_return(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not raise error when no service needs to be started" do
|
||||||
|
Homebrew::Bundle::Checker.reset!
|
||||||
|
allow_any_instance_of(Pathname).to receive(:read).and_return("brew 'abc'")
|
||||||
|
|
||||||
|
expect(Homebrew::Bundle::BrewInstaller.installed_formulae).to include("abc")
|
||||||
|
expect(Homebrew::Bundle::CaskInstaller.installed_casks).not_to include("abc")
|
||||||
|
expect(Homebrew::Bundle::BrewServices.started?("abc")).to be(true)
|
||||||
|
|
||||||
|
expect { do_check }.not_to raise_error
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when restart_service is true" do
|
||||||
|
it "raises an error" do
|
||||||
|
allow_any_instance_of(Pathname)
|
||||||
|
.to receive(:read).and_return("brew 'abc', restart_service: true\nbrew 'def', restart_service: true")
|
||||||
|
allow_any_instance_of(Homebrew::Bundle::Checker::MacAppStoreChecker)
|
||||||
|
.to receive(:format_checkable).and_return(1 => "foo")
|
||||||
|
expect { do_check }.to raise_error(SystemExit).and output(expected_output).to_stdout
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when start_service is true" do
|
||||||
|
it "raises an error" do
|
||||||
|
allow_any_instance_of(Pathname)
|
||||||
|
.to receive(:read).and_return("brew 'abc', start_service: true\nbrew 'def', start_service: true")
|
||||||
|
allow_any_instance_of(Homebrew::Bundle::Checker::MacAppStoreChecker)
|
||||||
|
.to receive(:format_checkable).and_return(1 => "foo")
|
||||||
|
expect { do_check }.to raise_error(SystemExit).and output(expected_output).to_stdout
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when app not installed and `no_upgrade` is true" do
|
||||||
|
let(:expected_output) do
|
||||||
|
<<~MSG
|
||||||
|
brew bundle can't satisfy your Brewfile's dependencies.
|
||||||
|
→ App foo needs to be installed.
|
||||||
|
Satisfy missing dependencies with `brew bundle install`.
|
||||||
|
MSG
|
||||||
|
end
|
||||||
|
let(:no_upgrade) { true }
|
||||||
|
let(:verbose) { true }
|
||||||
|
|
||||||
|
before do
|
||||||
|
Homebrew::Bundle::Checker.reset!
|
||||||
|
allow_any_instance_of(Homebrew::Bundle::Checker::MacAppStoreChecker).to \
|
||||||
|
receive(:installed_and_up_to_date?).and_return(false)
|
||||||
|
allow(Homebrew::Bundle::BrewInstaller).to receive(:installed_formulae).and_return(["abc", "def"])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raises an error that doesn't mention upgrade" do
|
||||||
|
allow_any_instance_of(Pathname).to receive(:read).and_return("brew 'abc'")
|
||||||
|
allow_any_instance_of(Homebrew::Bundle::Checker::MacAppStoreChecker).to \
|
||||||
|
receive(:format_checkable).and_return(1 => "foo")
|
||||||
|
expect { do_check }.to raise_error(SystemExit).and output(expected_output).to_stdout
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when extension not installed" do
|
||||||
|
let(:expected_output) do
|
||||||
|
<<~MSG
|
||||||
|
brew bundle can't satisfy your Brewfile's dependencies.
|
||||||
|
→ VSCode Extension foo needs to be installed.
|
||||||
|
Satisfy missing dependencies with `brew bundle install`.
|
||||||
|
MSG
|
||||||
|
end
|
||||||
|
let(:verbose) { true }
|
||||||
|
|
||||||
|
before do
|
||||||
|
Homebrew::Bundle::Checker.reset!
|
||||||
|
allow_any_instance_of(Homebrew::Bundle::Checker::VscodeExtensionChecker).to \
|
||||||
|
receive(:installed_and_up_to_date?).and_return(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raises an error that doesn't mention upgrade" do
|
||||||
|
allow_any_instance_of(Pathname).to receive(:read).and_return("vscode 'foo'")
|
||||||
|
expect { do_check }.to raise_error(SystemExit).and output(expected_output).to_stdout
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when there are taps to install" do
|
||||||
|
before do
|
||||||
|
allow_any_instance_of(Pathname).to receive(:read).and_return("")
|
||||||
|
allow(Homebrew::Bundle::Checker).to receive(:taps_to_tap).and_return(["asdf"])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not check for casks" do
|
||||||
|
expect(Homebrew::Bundle::Checker).not_to receive(:casks_to_install)
|
||||||
|
expect { do_check }.to raise_error(SystemExit)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not check for formulae" do
|
||||||
|
expect(Homebrew::Bundle::Checker).not_to receive(:formulae_to_install)
|
||||||
|
expect { do_check }.to raise_error(SystemExit)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not check for apps" do
|
||||||
|
expect(Homebrew::Bundle::Checker).not_to receive(:apps_to_install)
|
||||||
|
expect { do_check }.to raise_error(SystemExit)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when there are VSCode extensions to install" do
|
||||||
|
before do
|
||||||
|
allow_any_instance_of(Pathname).to receive(:read).and_return("")
|
||||||
|
allow(Homebrew::Bundle::Checker).to receive(:extensions_to_install).and_return(["asdf"])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not check for formulae" do
|
||||||
|
expect(Homebrew::Bundle::Checker).not_to receive(:formulae_to_install)
|
||||||
|
expect { do_check }.to raise_error(SystemExit)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not check for apps" do
|
||||||
|
expect(Homebrew::Bundle::Checker).not_to receive(:apps_to_install)
|
||||||
|
expect { do_check }.to raise_error(SystemExit)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when there are formulae to install" do
|
||||||
|
before do
|
||||||
|
allow_any_instance_of(Pathname).to receive(:read).and_return("")
|
||||||
|
allow(Homebrew::Bundle::Checker).to \
|
||||||
|
receive_messages(taps_to_tap: [],
|
||||||
|
casks_to_install: [],
|
||||||
|
apps_to_install: [],
|
||||||
|
formulae_to_install: ["one"])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not start formulae" do
|
||||||
|
expect(Homebrew::Bundle::Checker).not_to receive(:formulae_to_start)
|
||||||
|
expect { do_check }.to raise_error(SystemExit)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when verbose mode is not enabled" do
|
||||||
|
it "stops checking after the first missing formula" do
|
||||||
|
allow(Homebrew::Bundle::CaskDumper).to receive(:casks).and_return([])
|
||||||
|
allow(Homebrew::Bundle::BrewInstaller).to receive(:upgradable_formulae).and_return([])
|
||||||
|
allow_any_instance_of(Pathname).to receive(:read).and_return("brew 'abc'\nbrew 'def'")
|
||||||
|
|
||||||
|
expect_any_instance_of(Homebrew::Bundle::Checker::BrewChecker).to \
|
||||||
|
receive(:exit_early_check).once.and_call_original
|
||||||
|
expect { do_check }.to raise_error(SystemExit)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "stops checking after the first missing cask", :needs_macos do
|
||||||
|
allow_any_instance_of(Pathname).to receive(:read).and_return("cask 'abc'\ncask 'def'")
|
||||||
|
|
||||||
|
expect_any_instance_of(Homebrew::Bundle::Checker::CaskChecker).to \
|
||||||
|
receive(:exit_early_check).once.and_call_original
|
||||||
|
expect { do_check }.to raise_error(SystemExit)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "stops checking after the first missing mac app", :needs_macos do
|
||||||
|
allow_any_instance_of(Pathname).to receive(:read).and_return("mas 'foo', id: 123\nmas 'bar', id: 456")
|
||||||
|
|
||||||
|
expect_any_instance_of(Homebrew::Bundle::Checker::MacAppStoreChecker).to \
|
||||||
|
receive(:exit_early_check).once.and_call_original
|
||||||
|
expect { do_check }.to raise_error(SystemExit)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "stops checking after the first VSCode extension" do
|
||||||
|
allow_any_instance_of(Pathname).to receive(:read).and_return("vscode 'abc'\nvscode 'def'")
|
||||||
|
|
||||||
|
expect_any_instance_of(Homebrew::Bundle::Checker::VscodeExtensionChecker).to \
|
||||||
|
receive(:exit_early_check).once.and_call_original
|
||||||
|
expect { do_check }.to raise_error(SystemExit)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when a new checker fails to implement installed_and_up_to_date" do
|
||||||
|
it "raises an exception" do
|
||||||
|
stub_const("TestChecker", Class.new(Homebrew::Bundle::Checker::Base) do
|
||||||
|
class_eval("PACKAGE_TYPE = :test", __FILE__, __LINE__)
|
||||||
|
end.freeze)
|
||||||
|
|
||||||
|
test_entry = Homebrew::Bundle::Dsl::Entry.new(:test, "test")
|
||||||
|
expect { TestChecker.new.find_actionable([test_entry]) }.to raise_error(NotImplementedError)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
4
Library/Homebrew/test/bundle/commands/check_spec.rbi
Normal file
4
Library/Homebrew/test/bundle/commands/check_spec.rbi
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# typed: strict
|
||||||
|
|
||||||
|
class TestChecker < Homebrew::Bundle::Checker::Base
|
||||||
|
end
|
256
Library/Homebrew/test/bundle/commands/cleanup_spec.rb
Normal file
256
Library/Homebrew/test/bundle/commands/cleanup_spec.rb
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "bundle"
|
||||||
|
|
||||||
|
RSpec.describe Homebrew::Bundle::Commands::Cleanup do
|
||||||
|
describe "read Brewfile and current installation" do
|
||||||
|
before do
|
||||||
|
described_class.reset!
|
||||||
|
|
||||||
|
# don't try to load gcc/glibc
|
||||||
|
allow(DevelopmentTools).to receive_messages(needs_libc_formula?: false, needs_compiler_formula?: false)
|
||||||
|
|
||||||
|
allow_any_instance_of(Pathname).to receive(:read).and_return <<~EOS
|
||||||
|
tap 'x'
|
||||||
|
tap 'y'
|
||||||
|
cask '123'
|
||||||
|
brew 'a'
|
||||||
|
brew 'b'
|
||||||
|
brew 'd2'
|
||||||
|
brew 'homebrew/tap/f'
|
||||||
|
brew 'homebrew/tap/g'
|
||||||
|
brew 'homebrew/tap/h'
|
||||||
|
brew 'homebrew/tap/i2'
|
||||||
|
brew 'homebrew/tap/hasdependency'
|
||||||
|
brew 'hasbuilddependency1'
|
||||||
|
brew 'hasbuilddependency2'
|
||||||
|
mas 'appstoreapp1', id: 1
|
||||||
|
vscode 'VsCodeExtension1'
|
||||||
|
EOS
|
||||||
|
%w[a b d2 homebrew/tap/f homebrew/tap/g homebrew/tap/h homebrew/tap/i2
|
||||||
|
homebrew/tap/hasdependency hasbuilddependency1 hasbuilddependency2].each do |full_name|
|
||||||
|
tap_name, _, name = full_name.rpartition("/")
|
||||||
|
tap = tap_name.present? ? Tap.fetch(tap_name) : nil
|
||||||
|
f = formula(name, tap:) { url "#{name}-1.0" }
|
||||||
|
stub_formula_loader f, full_name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "computes which casks to uninstall" do
|
||||||
|
allow(Homebrew::Bundle::CaskDumper).to receive(:casks).and_return(%w[123 456])
|
||||||
|
expect(described_class.casks_to_uninstall).to eql(%w[456])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "computes which formulae to uninstall" do
|
||||||
|
dependencies_arrays_hash = { dependencies: [], build_dependencies: [] }
|
||||||
|
allow(Homebrew::Bundle::BrewDumper).to receive(:formulae).and_return [
|
||||||
|
{ name: "a2", full_name: "a2", aliases: ["a"], dependencies: ["d"] },
|
||||||
|
{ name: "c", full_name: "c" },
|
||||||
|
{ name: "d", full_name: "homebrew/tap/d", aliases: ["d2"] },
|
||||||
|
{ name: "e", full_name: "homebrew/tap/e" },
|
||||||
|
{ name: "f", full_name: "homebrew/tap/f" },
|
||||||
|
{ name: "h", full_name: "other/tap/h" },
|
||||||
|
{ name: "i", full_name: "homebrew/tap/i", aliases: ["i2"] },
|
||||||
|
{ name: "hasdependency", full_name: "homebrew/tap/hasdependency", dependencies: ["isdependency"] },
|
||||||
|
{ name: "isdependency", full_name: "homebrew/tap/isdependency" },
|
||||||
|
{
|
||||||
|
name: "hasbuilddependency1",
|
||||||
|
full_name: "hasbuilddependency1",
|
||||||
|
poured_from_bottle?: true,
|
||||||
|
build_dependencies: ["builddependency1"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "hasbuilddependency2",
|
||||||
|
full_name: "hasbuilddependency2",
|
||||||
|
poured_from_bottle?: false,
|
||||||
|
build_dependencies: ["builddependency2"],
|
||||||
|
},
|
||||||
|
{ name: "builddependency1", full_name: "builddependency1" },
|
||||||
|
{ name: "builddependency2", full_name: "builddependency2" },
|
||||||
|
{ name: "caskdependency", full_name: "homebrew/tap/caskdependency" },
|
||||||
|
].map { |formula| dependencies_arrays_hash.merge(formula) }
|
||||||
|
allow(Homebrew::Bundle::CaskDumper).to receive(:formula_dependencies).and_return(%w[caskdependency])
|
||||||
|
expect(described_class.formulae_to_uninstall).to eql %w[
|
||||||
|
c
|
||||||
|
homebrew/tap/e
|
||||||
|
other/tap/h
|
||||||
|
builddependency1
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
it "computes which tap to untap" do
|
||||||
|
allow(Homebrew::Bundle::TapDumper).to \
|
||||||
|
receive(:tap_names).and_return(%w[z homebrew/bundle homebrew/core homebrew/tap])
|
||||||
|
expect(described_class.taps_to_untap).to eql(%w[z])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "ignores unavailable formulae when computing which taps to keep" do
|
||||||
|
allow(Formulary).to \
|
||||||
|
receive(:factory).and_raise(TapFormulaUnavailableError.new(Tap.fetch("homebrew/tap"), "foo"))
|
||||||
|
allow(Homebrew::Bundle::TapDumper).to \
|
||||||
|
receive(:tap_names).and_return(%w[z homebrew/bundle homebrew/core homebrew/tap])
|
||||||
|
expect(described_class.taps_to_untap).to eql(%w[z homebrew/tap])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "computes which VSCode extensions to uninstall" do
|
||||||
|
allow(Homebrew::Bundle::VscodeExtensionDumper).to receive(:extensions).and_return(%w[z])
|
||||||
|
expect(described_class.vscode_extensions_to_uninstall).to eql(%w[z])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "computes which VSCode extensions to uninstall irrespective of case of the extension name" do
|
||||||
|
allow(Homebrew::Bundle::VscodeExtensionDumper).to receive(:extensions).and_return(%w[z vscodeextension1])
|
||||||
|
expect(described_class.vscode_extensions_to_uninstall).to eql(%w[z])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when there are no formulae to uninstall and no taps to untap" do
|
||||||
|
before do
|
||||||
|
described_class.reset!
|
||||||
|
allow(described_class).to receive_messages(casks_to_uninstall: [],
|
||||||
|
formulae_to_uninstall: [],
|
||||||
|
taps_to_untap: [],
|
||||||
|
vscode_extensions_to_uninstall: [])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does nothing" do
|
||||||
|
expect(Kernel).not_to receive(:system)
|
||||||
|
expect(described_class).to receive(:system_output_no_stderr).and_return("")
|
||||||
|
described_class.run(force: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when there are casks to uninstall" do
|
||||||
|
before do
|
||||||
|
described_class.reset!
|
||||||
|
allow(described_class).to receive_messages(casks_to_uninstall: %w[a b],
|
||||||
|
formulae_to_uninstall: [],
|
||||||
|
taps_to_untap: [],
|
||||||
|
vscode_extensions_to_uninstall: [])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "uninstalls casks" do
|
||||||
|
expect(Kernel).to receive(:system).with(HOMEBREW_BREW_FILE, "uninstall", "--cask", "--force", "a", "b")
|
||||||
|
expect(described_class).to receive(:system_output_no_stderr).and_return("")
|
||||||
|
expect { described_class.run(force: true) }.to output(/Uninstalled 2 casks/).to_stdout
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when there are casks to zap" do
|
||||||
|
before do
|
||||||
|
described_class.reset!
|
||||||
|
allow(described_class).to receive_messages(casks_to_uninstall: %w[a b],
|
||||||
|
formulae_to_uninstall: [],
|
||||||
|
taps_to_untap: [],
|
||||||
|
vscode_extensions_to_uninstall: [])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "uninstalls casks" do
|
||||||
|
expect(Kernel).to receive(:system).with(HOMEBREW_BREW_FILE, "uninstall", "--cask", "--zap", "--force", "a", "b")
|
||||||
|
expect(described_class).to receive(:system_output_no_stderr).and_return("")
|
||||||
|
expect { described_class.run(force: true, zap: true) }.to output(/Uninstalled 2 casks/).to_stdout
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when there are formulae to uninstall" do
|
||||||
|
before do
|
||||||
|
described_class.reset!
|
||||||
|
allow(described_class).to receive_messages(casks_to_uninstall: [],
|
||||||
|
formulae_to_uninstall: %w[a b],
|
||||||
|
taps_to_untap: [],
|
||||||
|
vscode_extensions_to_uninstall: [])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "uninstalls formulae" do
|
||||||
|
expect(Kernel).to receive(:system).with(HOMEBREW_BREW_FILE, "uninstall", "--formula", "--force", "a", "b")
|
||||||
|
expect(described_class).to receive(:system_output_no_stderr).and_return("")
|
||||||
|
expect { described_class.run(force: true) }.to output(/Uninstalled 2 formulae/).to_stdout
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when there are taps to untap" do
|
||||||
|
before do
|
||||||
|
described_class.reset!
|
||||||
|
allow(described_class).to receive_messages(casks_to_uninstall: [],
|
||||||
|
formulae_to_uninstall: [],
|
||||||
|
taps_to_untap: %w[a b],
|
||||||
|
vscode_extensions_to_uninstall: [])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "untaps taps" do
|
||||||
|
expect(Kernel).to receive(:system).with(HOMEBREW_BREW_FILE, "untap", "a", "b")
|
||||||
|
expect(described_class).to receive(:system_output_no_stderr).and_return("")
|
||||||
|
described_class.run(force: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when there are VSCode extensions to uninstall" do
|
||||||
|
before do
|
||||||
|
described_class.reset!
|
||||||
|
allow(described_class).to receive_messages(casks_to_uninstall: [],
|
||||||
|
formulae_to_uninstall: [],
|
||||||
|
taps_to_untap: [],
|
||||||
|
vscode_extensions_to_uninstall: %w[GitHub.codespaces])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "uninstalls extensions" do
|
||||||
|
expect(Kernel).to receive(:system).with("code", "--uninstall-extension", "GitHub.codespaces")
|
||||||
|
expect(described_class).to receive(:system_output_no_stderr).and_return("")
|
||||||
|
described_class.run(force: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when there are casks and formulae to uninstall and taps to untap but without passing `--force`" do
|
||||||
|
before do
|
||||||
|
described_class.reset!
|
||||||
|
allow(described_class).to receive_messages(casks_to_uninstall: %w[a b],
|
||||||
|
formulae_to_uninstall: %w[a b],
|
||||||
|
taps_to_untap: %w[a b],
|
||||||
|
vscode_extensions_to_uninstall: %w[a b])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "lists casks, formulae and taps" do
|
||||||
|
expect(Formatter).to receive(:columns).with(%w[a b]).exactly(4).times
|
||||||
|
expect(Kernel).not_to receive(:system)
|
||||||
|
expect(described_class).to receive(:system_output_no_stderr).and_return("")
|
||||||
|
expect do
|
||||||
|
described_class.run
|
||||||
|
end.to raise_error(SystemExit)
|
||||||
|
.and output(/Would uninstall formulae:.*Would untap:.*Would uninstall VSCode extensions:/m).to_stdout
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when there is brew cleanup output" do
|
||||||
|
before do
|
||||||
|
described_class.reset!
|
||||||
|
allow(described_class).to receive_messages(casks_to_uninstall: [],
|
||||||
|
formulae_to_uninstall: [],
|
||||||
|
taps_to_untap: [],
|
||||||
|
vscode_extensions_to_uninstall: [])
|
||||||
|
end
|
||||||
|
|
||||||
|
def sane?
|
||||||
|
expect(described_class).to receive(:system_output_no_stderr).and_return("cleaned")
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with --force" do
|
||||||
|
it "prints output" do
|
||||||
|
sane?
|
||||||
|
expect { described_class.run(force: true) }.to output(/cleaned/).to_stdout
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "without --force" do
|
||||||
|
it "prints output" do
|
||||||
|
sane?
|
||||||
|
expect { described_class.run }.to output(/cleaned/).to_stdout
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#system_output_no_stderr" do
|
||||||
|
it "shells out" do
|
||||||
|
expect(IO).to receive(:popen).and_return(StringIO.new("true"))
|
||||||
|
described_class.system_output_no_stderr("true")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
69
Library/Homebrew/test/bundle/commands/dump_spec.rb
Normal file
69
Library/Homebrew/test/bundle/commands/dump_spec.rb
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "bundle"
|
||||||
|
|
||||||
|
RSpec.describe Homebrew::Bundle::Commands::Dump do
|
||||||
|
subject(:dump) do
|
||||||
|
described_class.run(global:, file: nil, describe: false, force:, no_restart: false, taps: true, brews: true,
|
||||||
|
casks: true, mas: true, whalebrew: true, vscode: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:force) { false }
|
||||||
|
let(:global) { false }
|
||||||
|
|
||||||
|
before do
|
||||||
|
Homebrew::Bundle::CaskDumper.reset!
|
||||||
|
Homebrew::Bundle::BrewDumper.reset!
|
||||||
|
Homebrew::Bundle::TapDumper.reset!
|
||||||
|
Homebrew::Bundle::WhalebrewDumper.reset!
|
||||||
|
Homebrew::Bundle::VscodeExtensionDumper.reset!
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when files existed" do
|
||||||
|
before do
|
||||||
|
allow_any_instance_of(Pathname).to receive(:exist?).and_return(true)
|
||||||
|
allow(Homebrew::Bundle).to receive(:cask_installed?).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raises error" do
|
||||||
|
expect do
|
||||||
|
dump
|
||||||
|
end.to raise_error(RuntimeError)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "exits before doing any work" do
|
||||||
|
expect(Homebrew::Bundle::TapDumper).not_to receive(:dump)
|
||||||
|
expect(Homebrew::Bundle::BrewDumper).not_to receive(:dump)
|
||||||
|
expect(Homebrew::Bundle::CaskDumper).not_to receive(:dump)
|
||||||
|
expect(Homebrew::Bundle::WhalebrewDumper).not_to receive(:dump)
|
||||||
|
expect do
|
||||||
|
dump
|
||||||
|
end.to raise_error(RuntimeError)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when files existed and `--force` and `--global` are passed" do
|
||||||
|
let(:force) { true }
|
||||||
|
let(:global) { true }
|
||||||
|
|
||||||
|
before do
|
||||||
|
ENV["HOMEBREW_BUNDLE_FILE"] = ""
|
||||||
|
allow_any_instance_of(Pathname).to receive(:exist?).and_return(true)
|
||||||
|
allow(Homebrew::Bundle).to receive(:cask_installed?).and_return(true)
|
||||||
|
allow(Cask::Caskroom).to receive(:casks).and_return([])
|
||||||
|
|
||||||
|
# don't try to load gcc/glibc
|
||||||
|
allow(DevelopmentTools).to receive_messages(needs_libc_formula?: false, needs_compiler_formula?: false)
|
||||||
|
|
||||||
|
stub_formula_loader formula("mas") { url "mas-1.0" }
|
||||||
|
stub_formula_loader formula("whalebrew") { url "whalebrew-1.0" }
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't raise error" do
|
||||||
|
io = instance_double(File, write: true)
|
||||||
|
expect_any_instance_of(Pathname).to receive(:open).with("w").and_yield(io)
|
||||||
|
expect(io).to receive(:write)
|
||||||
|
expect { dump }.not_to raise_error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
126
Library/Homebrew/test/bundle/commands/exec_spec.rb
Normal file
126
Library/Homebrew/test/bundle/commands/exec_spec.rb
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "bundle"
|
||||||
|
|
||||||
|
RSpec.describe Homebrew::Bundle::Commands::Exec do
|
||||||
|
context "when a Brewfile is not found" do
|
||||||
|
it "raises an error" do
|
||||||
|
expect { described_class.run }.to raise_error(RuntimeError)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when a Brewfile is found" do
|
||||||
|
let(:brewfile_contents) { "brew 'openssl'" }
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow_any_instance_of(Pathname).to receive(:read)
|
||||||
|
.and_return(brewfile_contents)
|
||||||
|
|
||||||
|
# don't try to load gcc/glibc
|
||||||
|
allow(DevelopmentTools).to receive_messages(needs_libc_formula?: false, needs_compiler_formula?: false)
|
||||||
|
|
||||||
|
stub_formula_loader formula("openssl") { url "openssl-1.0" }
|
||||||
|
stub_formula_loader formula("pkgconf") { url "pkgconf-1.0" }
|
||||||
|
ENV.extend(Superenv)
|
||||||
|
allow(ENV).to receive(:setup_build_environment)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with valid command setup" do
|
||||||
|
before do
|
||||||
|
allow(described_class).to receive(:exec).and_return(nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not raise an error" do
|
||||||
|
expect { described_class.run("bundle", "install") }.not_to raise_error
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not raise an error when HOMEBREW_BUNDLE_EXEC_ALL_KEG_ONLY_DEPS is set" do
|
||||||
|
ENV["HOMEBREW_BUNDLE_EXEC_ALL_KEG_ONLY_DEPS"] = "1"
|
||||||
|
expect { described_class.run("bundle", "install") }.not_to raise_error
|
||||||
|
end
|
||||||
|
|
||||||
|
it "uses the formula version from the environment variable" do
|
||||||
|
openssl_version = "1.1.1"
|
||||||
|
ENV["PATH"] = "/opt/homebrew/opt/openssl/bin:/usr/bin:/bin"
|
||||||
|
ENV["MANPATH"] = "/opt/homebrew/opt/openssl/man"
|
||||||
|
ENV["HOMEBREW_BUNDLE_EXEC_FORMULA_VERSION_OPENSSL"] = openssl_version
|
||||||
|
allow(described_class).to receive(:which).and_return(Pathname("/usr/bin/bundle"))
|
||||||
|
described_class.run("bundle", "install")
|
||||||
|
expect(ENV.fetch("PATH")).to include("/Cellar/openssl/1.1.1/bin")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "is able to run without bundle arguments" do
|
||||||
|
allow(described_class).to receive(:exec).with("bundle", "install").and_return(nil)
|
||||||
|
expect { described_class.run("bundle", "install") }.not_to raise_error
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raises an exception if called without a command" do
|
||||||
|
expect { described_class.run }.to raise_error(RuntimeError)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with env command" do
|
||||||
|
it "outputs the environment variables" do
|
||||||
|
ENV["HOMEBREW_PREFIX"] = "/opt/homebrew"
|
||||||
|
ENV["HOMEBREW_PATH"] = "/usr/bin"
|
||||||
|
allow(OS).to receive(:linux?).and_return(true)
|
||||||
|
|
||||||
|
expect { described_class.run("env", subcommand: "env") }.to \
|
||||||
|
output(/HOMEBREW_PREFIX="#{ENV.fetch("HOMEBREW_PREFIX")}"/).to_stdout
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "raises if called with a command that's not on the PATH" do
|
||||||
|
allow(described_class).to receive_messages(exec: nil, which: nil)
|
||||||
|
expect { described_class.run("bundle", "install") }.to raise_error(RuntimeError)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "prepends the path of the requested command to PATH before running" do
|
||||||
|
expect(described_class).to receive(:exec).with("bundle", "install").and_return(nil)
|
||||||
|
expect(described_class).to receive(:which).and_return(Pathname("/usr/local/bin/bundle"))
|
||||||
|
allow(ENV).to receive(:prepend_path).with(any_args).and_call_original
|
||||||
|
expect(ENV).to receive(:prepend_path).with("PATH", "/usr/local/bin").once.and_call_original
|
||||||
|
described_class.run("bundle", "install")
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "when running a command which exists but is not on the PATH" do
|
||||||
|
let(:brewfile_contents) { "brew 'zlib'" }
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_formula_loader formula("zlib") { url "zlib-1.0" }
|
||||||
|
end
|
||||||
|
|
||||||
|
shared_examples "allows command execution" do |command|
|
||||||
|
it "does not raise" do
|
||||||
|
allow(described_class).to receive(:exec).with(command).and_return(nil)
|
||||||
|
expect(described_class).not_to receive(:which)
|
||||||
|
expect { described_class.run(command) }.not_to raise_error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like "allows command execution", "./configure"
|
||||||
|
it_behaves_like "allows command execution", "bin/install"
|
||||||
|
it_behaves_like "allows command execution", "/Users/admin/Downloads/command"
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "when the Brewfile contains rbenv" do
|
||||||
|
let(:rbenv_root) { Pathname.new("/tmp/.rbenv") }
|
||||||
|
let(:brewfile_contents) { "brew 'rbenv'" }
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_formula_loader formula("rbenv") { url "rbenv-1.0" }
|
||||||
|
ENV["HOMEBREW_RBENV_ROOT"] = rbenv_root.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
it "prepends the path of the rbenv shims to PATH before running" do
|
||||||
|
allow(described_class).to receive(:exec).with("/usr/bin/true").and_return(0)
|
||||||
|
allow(ENV).to receive(:fetch).with(any_args).and_call_original
|
||||||
|
allow(ENV).to receive(:prepend_path).with(any_args).once.and_call_original
|
||||||
|
|
||||||
|
expect(ENV).to receive(:fetch).with("HOMEBREW_RBENV_ROOT", "#{Dir.home}/.rbenv").once.and_call_original
|
||||||
|
expect(ENV).to receive(:prepend_path).with("PATH", rbenv_root/"shims").once.and_call_original
|
||||||
|
described_class.run("/usr/bin/true")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
86
Library/Homebrew/test/bundle/commands/install_spec.rb
Normal file
86
Library/Homebrew/test/bundle/commands/install_spec.rb
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "bundle"
|
||||||
|
|
||||||
|
RSpec.describe Homebrew::Bundle::Commands::Install do
|
||||||
|
before do
|
||||||
|
allow_any_instance_of(IO).to receive(:puts)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when a Brewfile is not found" do
|
||||||
|
it "raises an error" do
|
||||||
|
allow_any_instance_of(Pathname).to receive(:read).and_raise(Errno::ENOENT)
|
||||||
|
expect { described_class.run }.to raise_error(RuntimeError)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when a Brewfile is found" do
|
||||||
|
let(:brewfile_contents) do
|
||||||
|
<<~EOS
|
||||||
|
tap 'phinze/cask'
|
||||||
|
brew 'mysql', conflicts_with: ['mysql56']
|
||||||
|
cask 'phinze/cask/google-chrome', greedy: true
|
||||||
|
mas '1Password', id: 443987910
|
||||||
|
whalebrew 'whalebrew/wget'
|
||||||
|
vscode 'GitHub.codespaces'
|
||||||
|
EOS
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not raise an error" do
|
||||||
|
allow(Homebrew::Bundle::TapInstaller).to receive(:preinstall).and_return(false)
|
||||||
|
allow(Homebrew::Bundle::WhalebrewInstaller).to receive(:preinstall).and_return(false)
|
||||||
|
allow(Homebrew::Bundle::VscodeExtensionInstaller).to receive(:preinstall).and_return(false)
|
||||||
|
allow(Homebrew::Bundle::BrewInstaller).to receive_messages(preinstall: true, install: true)
|
||||||
|
allow(Homebrew::Bundle::CaskInstaller).to receive_messages(preinstall: true, install: true)
|
||||||
|
allow(Homebrew::Bundle::MacAppStoreInstaller).to receive_messages(preinstall: true, install: true)
|
||||||
|
allow_any_instance_of(Pathname).to receive(:read).and_return(brewfile_contents)
|
||||||
|
expect { described_class.run }.not_to raise_error
|
||||||
|
end
|
||||||
|
|
||||||
|
it "#dsl returns a valid DSL" do
|
||||||
|
allow(Homebrew::Bundle::TapInstaller).to receive(:preinstall).and_return(false)
|
||||||
|
allow(Homebrew::Bundle::WhalebrewInstaller).to receive(:preinstall).and_return(false)
|
||||||
|
allow(Homebrew::Bundle::VscodeExtensionInstaller).to receive(:preinstall).and_return(false)
|
||||||
|
allow(Homebrew::Bundle::BrewInstaller).to receive_messages(preinstall: true, install: true)
|
||||||
|
allow(Homebrew::Bundle::CaskInstaller).to receive_messages(preinstall: true, install: true)
|
||||||
|
allow(Homebrew::Bundle::MacAppStoreInstaller).to receive_messages(preinstall: true, install: true)
|
||||||
|
allow_any_instance_of(Pathname).to receive(:read).and_return(brewfile_contents)
|
||||||
|
described_class.run
|
||||||
|
expect(described_class.dsl.entries.first.name).to eql("phinze/cask")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not raise an error when skippable" do
|
||||||
|
expect(Homebrew::Bundle::BrewInstaller).not_to receive(:install)
|
||||||
|
|
||||||
|
allow(Homebrew::Bundle::Skipper).to receive(:skip?).and_return(true)
|
||||||
|
allow_any_instance_of(Pathname).to receive(:read)
|
||||||
|
.and_return("brew 'mysql'")
|
||||||
|
expect { described_class.run }.not_to raise_error
|
||||||
|
end
|
||||||
|
|
||||||
|
it "exits on failures" do
|
||||||
|
allow(Homebrew::Bundle::BrewInstaller).to receive_messages(preinstall: true, install: false)
|
||||||
|
allow(Homebrew::Bundle::CaskInstaller).to receive_messages(preinstall: true, install: false)
|
||||||
|
allow(Homebrew::Bundle::MacAppStoreInstaller).to receive_messages(preinstall: true, install: false)
|
||||||
|
allow(Homebrew::Bundle::TapInstaller).to receive_messages(preinstall: true, install: false)
|
||||||
|
allow(Homebrew::Bundle::WhalebrewInstaller).to receive_messages(preinstall: true, install: false)
|
||||||
|
allow(Homebrew::Bundle::VscodeExtensionInstaller).to receive_messages(preinstall: true, install: false)
|
||||||
|
allow_any_instance_of(Pathname).to receive(:read).and_return(brewfile_contents)
|
||||||
|
|
||||||
|
expect { described_class.run }.to raise_error(SystemExit)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "skips installs from failed taps" do
|
||||||
|
allow(Homebrew::Bundle::CaskInstaller).to receive(:preinstall).and_return(false)
|
||||||
|
allow(Homebrew::Bundle::TapInstaller).to receive_messages(preinstall: true, install: false)
|
||||||
|
allow(Homebrew::Bundle::BrewInstaller).to receive_messages(preinstall: true, install: true)
|
||||||
|
allow(Homebrew::Bundle::MacAppStoreInstaller).to receive_messages(preinstall: true, install: true)
|
||||||
|
allow(Homebrew::Bundle::WhalebrewInstaller).to receive_messages(preinstall: true, install: true)
|
||||||
|
allow(Homebrew::Bundle::VscodeExtensionInstaller).to receive_messages(preinstall: true, install: true)
|
||||||
|
allow_any_instance_of(Pathname).to receive(:read).and_return(brewfile_contents)
|
||||||
|
|
||||||
|
expect(Homebrew::Bundle).not_to receive(:system)
|
||||||
|
expect { described_class.run }.to raise_error(SystemExit)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
73
Library/Homebrew/test/bundle/commands/list_spec.rb
Normal file
73
Library/Homebrew/test/bundle/commands/list_spec.rb
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "bundle"
|
||||||
|
|
||||||
|
RSpec.describe Homebrew::Bundle::Commands::List do
|
||||||
|
subject(:list) { described_class.run(global: false, file: nil, brews:, casks:, taps:, mas:, whalebrew:, vscode:) }
|
||||||
|
|
||||||
|
let(:brews) { true }
|
||||||
|
let(:casks) { false }
|
||||||
|
let(:taps) { false }
|
||||||
|
let(:mas) { false }
|
||||||
|
let(:whalebrew) { false }
|
||||||
|
let(:vscode) { false }
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow_any_instance_of(IO).to receive(:puts)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "outputs dependencies to stdout" do
|
||||||
|
before do
|
||||||
|
allow_any_instance_of(Pathname).to receive(:read).and_return(
|
||||||
|
<<~EOS,
|
||||||
|
tap 'phinze/cask'
|
||||||
|
brew 'mysql', conflicts_with: ['mysql56']
|
||||||
|
cask 'google-chrome'
|
||||||
|
mas '1Password', id: 443987910
|
||||||
|
whalebrew 'whalebrew/imagemagick'
|
||||||
|
vscode 'shopify.ruby-lsp'
|
||||||
|
EOS
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "only shows brew deps when no options are passed" do
|
||||||
|
expect { list }.to output("mysql\n").to_stdout
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "limiting when certain options are passed" do
|
||||||
|
types_and_deps = {
|
||||||
|
taps: "phinze/cask",
|
||||||
|
brews: "mysql",
|
||||||
|
casks: "google-chrome",
|
||||||
|
mas: "1Password",
|
||||||
|
whalebrew: "whalebrew/imagemagick",
|
||||||
|
vscode: "shopify.ruby-lsp",
|
||||||
|
}
|
||||||
|
|
||||||
|
combinations = 1.upto(types_and_deps.length).flat_map do |i|
|
||||||
|
types_and_deps.keys.combination(i).take((1..types_and_deps.length).reduce(:*) || 1)
|
||||||
|
end.sort
|
||||||
|
|
||||||
|
combinations.each do |options_list|
|
||||||
|
args_hash = options_list.to_h { |arg| [arg, true] }
|
||||||
|
words = options_list.join(" and ")
|
||||||
|
opts = options_list.map { |o| "`#{o}`" }.join(" and ")
|
||||||
|
verb = (options_list.length == 1 && "is") || "are"
|
||||||
|
|
||||||
|
context "when #{opts} #{verb} passed" do
|
||||||
|
let(:brews) { args_hash[:brews] }
|
||||||
|
let(:casks) { args_hash[:casks] }
|
||||||
|
let(:taps) { args_hash[:taps] }
|
||||||
|
let(:mas) { args_hash[:mas] }
|
||||||
|
let(:whalebrew) { args_hash[:whalebrew] }
|
||||||
|
let(:vscode) { args_hash[:vscode] }
|
||||||
|
|
||||||
|
it "shows only #{words}" do
|
||||||
|
expected = options_list.map { |opt| types_and_deps[opt] }.join("\n")
|
||||||
|
expect { list }.to output("#{expected}\n").to_stdout
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
74
Library/Homebrew/test/bundle/commands/remove_spec.rb
Normal file
74
Library/Homebrew/test/bundle/commands/remove_spec.rb
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "bundle"
|
||||||
|
require "cask/cask_loader"
|
||||||
|
|
||||||
|
RSpec.describe Homebrew::Bundle::Commands::Remove do
|
||||||
|
subject(:remove) do
|
||||||
|
described_class.run(*args, type:, global:, file:)
|
||||||
|
end
|
||||||
|
|
||||||
|
before { File.write(file, content) }
|
||||||
|
after { FileUtils.rm_f file }
|
||||||
|
|
||||||
|
let(:global) { false }
|
||||||
|
|
||||||
|
context "when called with a valid formula" do
|
||||||
|
let(:args) { ["hello"] }
|
||||||
|
let(:type) { :brew }
|
||||||
|
let(:file) { "/tmp/some_random_brewfile#{Random.rand(2 ** 16)}" }
|
||||||
|
let(:content) do
|
||||||
|
<<~BREWFILE
|
||||||
|
brew "hello"
|
||||||
|
BREWFILE
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_formula_loader formula("hello") { url "hello-1.0" }
|
||||||
|
end
|
||||||
|
|
||||||
|
it "removes entries from the given Brewfile" do
|
||||||
|
expect { remove }.not_to raise_error
|
||||||
|
expect(File.read(file)).not_to include("#{type} \"#{args.first}\"")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when called with no type" do
|
||||||
|
let(:args) { ["foo"] }
|
||||||
|
let(:type) { :none }
|
||||||
|
let(:file) { "/tmp/some_random_brewfile#{Random.rand(2 ** 16)}" }
|
||||||
|
let(:content) do
|
||||||
|
<<~BREWFILE
|
||||||
|
tap "someone/tap"
|
||||||
|
brew "foo"
|
||||||
|
cask "foo"
|
||||||
|
BREWFILE
|
||||||
|
end
|
||||||
|
|
||||||
|
it "removes all matching entries from the given Brewfile" do
|
||||||
|
expect { remove }.not_to raise_error
|
||||||
|
expect(File.read(file)).not_to include(args.first)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with arguments that match entries only when considering formula aliases" do
|
||||||
|
let(:foo) do
|
||||||
|
instance_double(
|
||||||
|
Formula,
|
||||||
|
name: "foo",
|
||||||
|
full_name: "qux/quuz/foo",
|
||||||
|
oldnames: ["oldfoo"],
|
||||||
|
aliases: ["foobar"],
|
||||||
|
)
|
||||||
|
end
|
||||||
|
let(:args) { ["foobar"] }
|
||||||
|
|
||||||
|
it "suggests using `--formula` to match against formula aliases" do
|
||||||
|
expect(Formulary).to receive(:factory).with("foobar").and_return(foo)
|
||||||
|
expect { remove }.not_to raise_error
|
||||||
|
expect(File.read(file)).to eq(content)
|
||||||
|
# FIXME: Why doesn't this work?
|
||||||
|
# expect { remove }.to output("--formula").to_stderr
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
104
Library/Homebrew/test/bundle/dsl_spec.rb
Normal file
104
Library/Homebrew/test/bundle/dsl_spec.rb
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "bundle"
|
||||||
|
|
||||||
|
RSpec.describe Homebrew::Bundle::Dsl do
|
||||||
|
def dsl_from_string(string)
|
||||||
|
described_class.new(StringIO.new(string))
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with a DSL example" do
|
||||||
|
subject(:dsl) do
|
||||||
|
dsl_from_string <<~EOS
|
||||||
|
# frozen_string_literal: true
|
||||||
|
cask_args appdir: '/Applications'
|
||||||
|
tap 'homebrew/cask'
|
||||||
|
tap 'telemachus/brew', 'https://telemachus@bitbucket.org/telemachus/brew.git'
|
||||||
|
tap 'auto/update', 'https://bitbucket.org/auto/update.git', force_auto_update: true
|
||||||
|
brew 'imagemagick'
|
||||||
|
brew 'mysql@5.6', restart_service: true, link: true, conflicts_with: ['mysql']
|
||||||
|
brew 'emacs', args: ['with-cocoa', 'with-gnutls'], link: :overwrite
|
||||||
|
cask 'google-chrome'
|
||||||
|
cask 'java' unless system '/usr/libexec/java_home --failfast'
|
||||||
|
cask 'firefox', args: { appdir: '~/my-apps/Applications' }
|
||||||
|
mas '1Password', id: 443987910
|
||||||
|
whalebrew 'whalebrew/wget'
|
||||||
|
vscode 'GitHub.codespaces'
|
||||||
|
EOS
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow_any_instance_of(described_class).to receive(:system)
|
||||||
|
.with("/usr/libexec/java_home --failfast")
|
||||||
|
.and_return(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "processes input" do
|
||||||
|
# Keep in sync with the README
|
||||||
|
expect(dsl.cask_arguments).to eql(appdir: "/Applications")
|
||||||
|
expect(dsl.entries[0].name).to eql("homebrew/cask")
|
||||||
|
expect(dsl.entries[1].name).to eql("telemachus/brew")
|
||||||
|
expect(dsl.entries[1].options).to eql(clone_target: "https://telemachus@bitbucket.org/telemachus/brew.git")
|
||||||
|
expect(dsl.entries[2].options).to eql(
|
||||||
|
clone_target: "https://bitbucket.org/auto/update.git",
|
||||||
|
force_auto_update: true,
|
||||||
|
)
|
||||||
|
expect(dsl.entries[3].name).to eql("imagemagick")
|
||||||
|
expect(dsl.entries[4].name).to eql("mysql@5.6")
|
||||||
|
expect(dsl.entries[4].options).to eql(restart_service: true, link: true, conflicts_with: ["mysql"])
|
||||||
|
expect(dsl.entries[5].name).to eql("emacs")
|
||||||
|
expect(dsl.entries[5].options).to eql(args: ["with-cocoa", "with-gnutls"], link: :overwrite)
|
||||||
|
expect(dsl.entries[6].name).to eql("google-chrome")
|
||||||
|
expect(dsl.entries[7].name).to eql("java")
|
||||||
|
expect(dsl.entries[8].name).to eql("firefox")
|
||||||
|
expect(dsl.entries[8].options).to eql(args: { appdir: "~/my-apps/Applications" }, full_name: "firefox")
|
||||||
|
expect(dsl.entries[9].name).to eql("1Password")
|
||||||
|
expect(dsl.entries[9].options).to eql(id: 443_987_910)
|
||||||
|
expect(dsl.entries[10].name).to eql("whalebrew/wget")
|
||||||
|
expect(dsl.entries[11].name).to eql("GitHub.codespaces")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with invalid input" do
|
||||||
|
it "handles completely invalid code" do
|
||||||
|
expect { dsl_from_string "abcdef" }.to raise_error(RuntimeError)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "handles valid commands but with invalid options" do
|
||||||
|
expect { dsl_from_string "brew 1" }.to raise_error(RuntimeError)
|
||||||
|
expect { dsl_from_string "cask 1" }.to raise_error(RuntimeError)
|
||||||
|
expect { dsl_from_string "tap 1" }.to raise_error(RuntimeError)
|
||||||
|
expect { dsl_from_string "cask_args ''" }.to raise_error(RuntimeError)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "errors on bad options" do
|
||||||
|
expect { dsl_from_string "brew 'foo', ['bad_option']" }.to raise_error(RuntimeError)
|
||||||
|
expect { dsl_from_string "cask 'foo', ['bad_option']" }.to raise_error(RuntimeError)
|
||||||
|
expect { dsl_from_string "tap 'foo', ['bad_clone_target']" }.to raise_error(RuntimeError)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it ".sanitize_brew_name" do
|
||||||
|
expect(described_class.send(:sanitize_brew_name, "homebrew/homebrew/foo")).to eql("foo")
|
||||||
|
expect(described_class.send(:sanitize_brew_name, "homebrew/homebrew-bar/foo")).to eql("homebrew/bar/foo")
|
||||||
|
expect(described_class.send(:sanitize_brew_name, "homebrew/bar/foo")).to eql("homebrew/bar/foo")
|
||||||
|
expect(described_class.send(:sanitize_brew_name, "foo")).to eql("foo")
|
||||||
|
end
|
||||||
|
|
||||||
|
it ".sanitize_tap_name" do
|
||||||
|
expect(described_class.send(:sanitize_tap_name, "homebrew/homebrew-foo")).to eql("homebrew/foo")
|
||||||
|
expect(described_class.send(:sanitize_tap_name, "homebrew/foo")).to eql("homebrew/foo")
|
||||||
|
end
|
||||||
|
|
||||||
|
it ".sanitize_cask_name" do
|
||||||
|
allow_any_instance_of(Object).to receive(:opoo)
|
||||||
|
expect(described_class.send(:sanitize_cask_name, "homebrew/cask-versions/adoptopenjdk8")).to eql("adoptopenjdk8")
|
||||||
|
expect(described_class.send(:sanitize_cask_name, "adoptopenjdk8")).to eql("adoptopenjdk8")
|
||||||
|
end
|
||||||
|
|
||||||
|
it ".pluralize_dependency" do
|
||||||
|
expect(described_class.send(:pluralize_dependency, 0)).to eql("dependencies")
|
||||||
|
expect(described_class.send(:pluralize_dependency, 1)).to eql("dependency")
|
||||||
|
expect(described_class.send(:pluralize_dependency, 5)).to eql("dependencies")
|
||||||
|
end
|
||||||
|
end
|
52
Library/Homebrew/test/bundle/dumper_spec.rb
Normal file
52
Library/Homebrew/test/bundle/dumper_spec.rb
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "bundle"
|
||||||
|
require "cask"
|
||||||
|
|
||||||
|
RSpec.describe Homebrew::Bundle::Dumper do
|
||||||
|
subject(:dumper) { described_class }
|
||||||
|
|
||||||
|
before do
|
||||||
|
ENV["HOMEBREW_BUNDLE_FILE"] = ""
|
||||||
|
|
||||||
|
allow(Homebrew::Bundle).to \
|
||||||
|
receive_messages(
|
||||||
|
cask_installed?: true, mas_installed?: false, whalebrew_installed?: false,
|
||||||
|
vscode_installed?: false
|
||||||
|
)
|
||||||
|
Homebrew::Bundle::BrewDumper.reset!
|
||||||
|
Homebrew::Bundle::TapDumper.reset!
|
||||||
|
Homebrew::Bundle::CaskDumper.reset!
|
||||||
|
Homebrew::Bundle::MacAppStoreDumper.reset!
|
||||||
|
Homebrew::Bundle::WhalebrewDumper.reset!
|
||||||
|
Homebrew::Bundle::VscodeExtensionDumper.reset!
|
||||||
|
Homebrew::Bundle::BrewServices.reset!
|
||||||
|
|
||||||
|
chrome = instance_double(Cask::Cask,
|
||||||
|
full_name: "google-chrome",
|
||||||
|
to_s: "google-chrome",
|
||||||
|
config: nil)
|
||||||
|
java = instance_double(Cask::Cask,
|
||||||
|
full_name: "java",
|
||||||
|
to_s: "java",
|
||||||
|
config: nil)
|
||||||
|
iterm2beta = instance_double(Cask::Cask,
|
||||||
|
full_name: "homebrew/cask-versions/iterm2-beta",
|
||||||
|
to_s: "iterm2-beta",
|
||||||
|
config: nil)
|
||||||
|
|
||||||
|
allow(Cask::Caskroom).to receive(:casks).and_return([chrome, java, iterm2beta])
|
||||||
|
allow(Tap).to receive(:select).and_return([])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "generates output" do
|
||||||
|
expect(dumper.build_brewfile(
|
||||||
|
describe: false, no_restart: false, brews: true, taps: true, casks: true, mas: true,
|
||||||
|
whalebrew: true, vscode: true
|
||||||
|
)).to eql("cask \"google-chrome\"\ncask \"java\"\ncask \"iterm2-beta\"\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "determines the brewfile correctly" do
|
||||||
|
expect(dumper.brewfile_path).to eql(Pathname.new(Dir.pwd).join("Brewfile"))
|
||||||
|
end
|
||||||
|
end
|
167
Library/Homebrew/test/bundle/mac_app_store_dumper_spec.rb
Normal file
167
Library/Homebrew/test/bundle/mac_app_store_dumper_spec.rb
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "bundle"
|
||||||
|
|
||||||
|
RSpec.describe Homebrew::Bundle::MacAppStoreDumper do
|
||||||
|
subject(:dumper) { described_class }
|
||||||
|
|
||||||
|
context "when mas is not installed" do
|
||||||
|
before do
|
||||||
|
described_class.reset!
|
||||||
|
allow(Homebrew::Bundle).to receive(:mas_installed?).and_return(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns empty list" do
|
||||||
|
expect(dumper.apps).to be_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
it "dumps as empty string" do
|
||||||
|
expect(dumper.dump).to eql("")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when there is no apps" do
|
||||||
|
before do
|
||||||
|
described_class.reset!
|
||||||
|
allow(Homebrew::Bundle).to receive(:mas_installed?).and_return(true)
|
||||||
|
allow(described_class).to receive(:`).and_return("")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns empty list" do
|
||||||
|
expect(dumper.apps).to be_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
it "dumps as empty string" do
|
||||||
|
expect(dumper.dump).to eql("")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when apps `foo`, `bar` and `baz` are installed" do
|
||||||
|
before do
|
||||||
|
described_class.reset!
|
||||||
|
allow(Homebrew::Bundle).to receive(:mas_installed?).and_return(true)
|
||||||
|
allow(described_class).to receive(:`).and_return("123 foo (1.0)\n456 bar (2.0)\n789 baz (3.0)")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns list %w[foo bar baz]" do
|
||||||
|
expect(dumper.apps).to eql([["123", "foo"], ["456", "bar"], ["789", "baz"]])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with invalid app details" do
|
||||||
|
let(:invalid_mas_output) do
|
||||||
|
<<~HEREDOC
|
||||||
|
497799835 Xcode (9.2)
|
||||||
|
425424353 The Unarchiver (4.0.0)
|
||||||
|
08981434 iMovie (10.1.8)
|
||||||
|
Install macOS High Sierra (13105)
|
||||||
|
409201541 Pages (7.1)
|
||||||
|
123456789 123AppNameWithNumbers (1.0)
|
||||||
|
409203825 Numbers (5.1)
|
||||||
|
944924917 Pastebin It! (1.0)
|
||||||
|
123456789 My (cool) app (1.0)
|
||||||
|
987654321 an-app-i-use (2.1)
|
||||||
|
123457867 App name with many spaces (1.0)
|
||||||
|
893489734 my,comma,app (2.2)
|
||||||
|
832423434 another_app_name (1.0)
|
||||||
|
543213432 My App? (1.0)
|
||||||
|
688963445 app;with;semicolons (1.0)
|
||||||
|
123345384 my 😊 app (2.0)
|
||||||
|
896732467 你好 (1.1)
|
||||||
|
634324555 مرحبا (1.0)
|
||||||
|
234324325 áéíóú (1.0)
|
||||||
|
310633997 non><printing><characters (1.0)
|
||||||
|
HEREDOC
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:expected_app_details_array) do
|
||||||
|
[
|
||||||
|
["497799835", "Xcode"],
|
||||||
|
["425424353", "The Unarchiver"],
|
||||||
|
["08981434", "iMovie"],
|
||||||
|
["409201541", "Pages"],
|
||||||
|
["123456789", "123AppNameWithNumbers"],
|
||||||
|
["409203825", "Numbers"],
|
||||||
|
["944924917", "Pastebin It!"],
|
||||||
|
["123456789", "My (cool) app"],
|
||||||
|
["987654321", "an-app-i-use"],
|
||||||
|
["123457867", "App name with many spaces"],
|
||||||
|
["893489734", "my,comma,app"],
|
||||||
|
["832423434", "another_app_name"],
|
||||||
|
["543213432", "My App?"],
|
||||||
|
["688963445", "app;with;semicolons"],
|
||||||
|
["123345384", "my 😊 app"],
|
||||||
|
["896732467", "你好"],
|
||||||
|
["634324555", "مرحبا"],
|
||||||
|
["234324325", "áéíóú"],
|
||||||
|
["310633997", "non><printing><characters"],
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:expected_mas_dumped_output) do
|
||||||
|
<<~HEREDOC
|
||||||
|
mas "123AppNameWithNumbers", id: 123456789
|
||||||
|
mas "an-app-i-use", id: 987654321
|
||||||
|
mas "another_app_name", id: 832423434
|
||||||
|
mas "App name with many spaces", id: 123457867
|
||||||
|
mas "app;with;semicolons", id: 688963445
|
||||||
|
mas "iMovie", id: 08981434
|
||||||
|
mas "My (cool) app", id: 123456789
|
||||||
|
mas "My App?", id: 543213432
|
||||||
|
mas "my 😊 app", id: 123345384
|
||||||
|
mas "my,comma,app", id: 893489734
|
||||||
|
mas "non><printing><characters", id: 310633997
|
||||||
|
mas "Numbers", id: 409203825
|
||||||
|
mas "Pages", id: 409201541
|
||||||
|
mas "Pastebin It!", id: 944924917
|
||||||
|
mas "The Unarchiver", id: 425424353
|
||||||
|
mas "Xcode", id: 497799835
|
||||||
|
mas "áéíóú", id: 234324325
|
||||||
|
mas "مرحبا", id: 634324555
|
||||||
|
mas "你好", id: 896732467
|
||||||
|
HEREDOC
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
described_class.reset!
|
||||||
|
allow(Homebrew::Bundle).to receive(:mas_installed?).and_return(true)
|
||||||
|
allow(described_class).to receive(:`).and_return(invalid_mas_output)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns only valid apps" do
|
||||||
|
expect(dumper.apps).to eql(expected_app_details_array)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "dumps excluding invalid apps" do
|
||||||
|
expect(dumper.dump).to eq(expected_mas_dumped_output.strip)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with the new format after mas-cli/mas#339" do
|
||||||
|
let(:new_mas_output) do
|
||||||
|
<<~HEREDOC
|
||||||
|
1440147259 AdGuard for Safari (1.9.13)
|
||||||
|
497799835 Xcode (12.5)
|
||||||
|
425424353 The Unarchiver (4.3.1)
|
||||||
|
HEREDOC
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:expected_app_details_array) do
|
||||||
|
[
|
||||||
|
["1440147259", "AdGuard for Safari"],
|
||||||
|
["497799835", "Xcode"],
|
||||||
|
["425424353", "The Unarchiver"],
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
described_class.reset!
|
||||||
|
allow(Homebrew::Bundle).to receive(:mas_installed?).and_return(true)
|
||||||
|
allow(described_class).to receive(:`).and_return(new_mas_output)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "parses the app names without trailing whitespace" do
|
||||||
|
expect(dumper.apps).to eql(expected_app_details_array)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
92
Library/Homebrew/test/bundle/mac_app_store_installer_spec.rb
Normal file
92
Library/Homebrew/test/bundle/mac_app_store_installer_spec.rb
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "bundle"
|
||||||
|
|
||||||
|
RSpec.describe Homebrew::Bundle::MacAppStoreInstaller do
|
||||||
|
before do
|
||||||
|
stub_formula_loader formula("mas") { url "mas-1.0" }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe ".installed_app_ids" do
|
||||||
|
it "shells out" do
|
||||||
|
expect { described_class.installed_app_ids }.not_to raise_error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe ".app_id_installed_and_up_to_date?" do
|
||||||
|
it "returns result" do
|
||||||
|
allow(described_class).to receive_messages(installed_app_ids: [123, 456], outdated_app_ids: [456])
|
||||||
|
expect(described_class.app_id_installed_and_up_to_date?(123)).to be(true)
|
||||||
|
expect(described_class.app_id_installed_and_up_to_date?(456)).to be(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when mas is not installed" do
|
||||||
|
before do
|
||||||
|
allow(Homebrew::Bundle).to receive(:mas_installed?).and_return(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "tries to install mas" do
|
||||||
|
expect(Homebrew::Bundle).to receive(:system).with(HOMEBREW_BREW_FILE, "install", "mas",
|
||||||
|
verbose: false).and_return(true)
|
||||||
|
expect { described_class.preinstall("foo", 123) }.to raise_error(RuntimeError)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe ".outdated_app_ids" do
|
||||||
|
it "does not shell out" do
|
||||||
|
expect(described_class).not_to receive(:`)
|
||||||
|
described_class.reset!
|
||||||
|
described_class.outdated_app_ids
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when mas is installed" do
|
||||||
|
before do
|
||||||
|
allow(Homebrew::Bundle).to receive(:mas_installed?).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe ".outdated_app_ids" do
|
||||||
|
it "returns app ids" do
|
||||||
|
expect(described_class).to receive(:`).and_return("foo 123")
|
||||||
|
described_class.reset!
|
||||||
|
described_class.outdated_app_ids
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when app is installed" do
|
||||||
|
before do
|
||||||
|
allow(described_class).to receive(:installed_app_ids).and_return([123])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "skips" do
|
||||||
|
expect(Homebrew::Bundle).not_to receive(:system)
|
||||||
|
expect(described_class.preinstall("foo", 123)).to be(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when app is outdated" do
|
||||||
|
before do
|
||||||
|
allow(described_class).to receive_messages(installed_app_ids: [123], outdated_app_ids: [123])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "upgrades" do
|
||||||
|
expect(Homebrew::Bundle).to receive(:system).with("mas", "upgrade", "123", verbose: false).and_return(true)
|
||||||
|
expect(described_class.preinstall("foo", 123)).to be(true)
|
||||||
|
expect(described_class.install("foo", 123)).to be(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when app is not installed" do
|
||||||
|
before do
|
||||||
|
allow(described_class).to receive(:installed_app_ids).and_return([])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "installs app" do
|
||||||
|
expect(Homebrew::Bundle).to receive(:system).with("mas", "install", "123", verbose: false).and_return(true)
|
||||||
|
expect(described_class.preinstall("foo", 123)).to be(true)
|
||||||
|
expect(described_class.install("foo", 123)).to be(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
15
Library/Homebrew/test/bundle/remover_spec.rb
Normal file
15
Library/Homebrew/test/bundle/remover_spec.rb
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "bundle"
|
||||||
|
|
||||||
|
RSpec.describe Homebrew::Bundle::Remover do
|
||||||
|
subject(:remover) { described_class }
|
||||||
|
|
||||||
|
let(:name) { "foo" }
|
||||||
|
|
||||||
|
before { allow(Formulary).to receive(:factory).with(name).and_raise(FormulaUnavailableError.new(name)) }
|
||||||
|
|
||||||
|
it "raises no errors when requested" do
|
||||||
|
expect { remover.possible_names(name, raise_error: false) }.not_to raise_error
|
||||||
|
end
|
||||||
|
end
|
84
Library/Homebrew/test/bundle/skipper_spec.rb
Normal file
84
Library/Homebrew/test/bundle/skipper_spec.rb
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "ostruct"
|
||||||
|
require "bundle"
|
||||||
|
|
||||||
|
RSpec.describe Homebrew::Bundle::Skipper do
|
||||||
|
subject(:skipper) { described_class }
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(ENV).to receive(:[]).and_return(nil)
|
||||||
|
allow(ENV).to receive(:[]).with("HOMEBREW_BUNDLE_BREW_SKIP").and_return("mysql")
|
||||||
|
allow(ENV).to receive(:[]).with("HOMEBREW_BUNDLE_WHALEBREW_SKIP").and_return("whalebrew/imagemagick")
|
||||||
|
allow(ENV).to receive(:[]).with("HOMEBREW_BUNDLE_TAP_SKIP").and_return("org/repo")
|
||||||
|
allow(Formatter).to receive(:warning)
|
||||||
|
skipper.instance_variable_set(:@skipped_entries, nil)
|
||||||
|
skipper.instance_variable_set(:@failed_taps, nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe ".skip?" do
|
||||||
|
context "with a listed formula" do
|
||||||
|
let(:entry) { Homebrew::Bundle::Dsl::Entry.new(:brew, "mysql") }
|
||||||
|
|
||||||
|
it "returns true" do
|
||||||
|
expect(skipper.skip?(entry)).to be true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with an unbottled formula on ARM", :needs_macos do
|
||||||
|
let(:entry) { Homebrew::Bundle::Dsl::Entry.new(:brew, "mysql") }
|
||||||
|
|
||||||
|
# TODO: remove OpenStruct usage
|
||||||
|
# rubocop:todo Style/OpenStructUse
|
||||||
|
it "returns true" do
|
||||||
|
allow(Hardware::CPU).to receive(:arm?).and_return(true)
|
||||||
|
allow_any_instance_of(Formula).to receive(:stable).and_return(OpenStruct.new(bottled?: false,
|
||||||
|
bottle_defined?: true))
|
||||||
|
|
||||||
|
expect(skipper.skip?(entry)).to be true
|
||||||
|
end
|
||||||
|
# rubocop:enable Style/OpenStructUse
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with an unlisted cask", :needs_macos do
|
||||||
|
let(:entry) { Homebrew::Bundle::Dsl::Entry.new(:cask, "java") }
|
||||||
|
|
||||||
|
it "returns false" do
|
||||||
|
expect(skipper.skip?(entry)).to be false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with a listed whalebrew image" do
|
||||||
|
let(:entry) { Homebrew::Bundle::Dsl::Entry.new(:whalebrew, "whalebrew/imagemagick") }
|
||||||
|
|
||||||
|
it "returns true" do
|
||||||
|
expect(skipper.skip?(entry)).to be true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with a listed formula in a failed tap" do
|
||||||
|
let(:entry) { Homebrew::Bundle::Dsl::Entry.new(:brew, "org/repo/formula") }
|
||||||
|
|
||||||
|
it "returns true" do
|
||||||
|
skipper.tap_failed!("org/repo")
|
||||||
|
|
||||||
|
expect(skipper.skip?(entry)).to be true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe ".failed_tap!" do
|
||||||
|
context "with a tap" do
|
||||||
|
let(:tap) { Homebrew::Bundle::Dsl::Entry.new(:tap, "org/repo-b") }
|
||||||
|
let(:entry) { Homebrew::Bundle::Dsl::Entry.new(:brew, "org/repo-b/formula") }
|
||||||
|
|
||||||
|
it "returns false" do
|
||||||
|
expect(skipper.skip?(entry)).to be false
|
||||||
|
|
||||||
|
skipper.tap_failed! tap.name
|
||||||
|
|
||||||
|
expect(skipper.skip?(entry)).to be true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
59
Library/Homebrew/test/bundle/tap_dumper_spec.rb
Normal file
59
Library/Homebrew/test/bundle/tap_dumper_spec.rb
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "bundle"
|
||||||
|
|
||||||
|
RSpec.describe Homebrew::Bundle::TapDumper do
|
||||||
|
subject(:dumper) { described_class }
|
||||||
|
|
||||||
|
context "when there is no tap" do
|
||||||
|
before do
|
||||||
|
described_class.reset!
|
||||||
|
allow(Tap).to receive(:select).and_return []
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns empty list" do
|
||||||
|
expect(dumper.tap_names).to be_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
it "dumps as empty string" do
|
||||||
|
expect(dumper.dump).to eql("")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with taps" do
|
||||||
|
before do
|
||||||
|
described_class.reset!
|
||||||
|
|
||||||
|
bar = instance_double(Tap, name: "bitbucket/bar", custom_remote?: true,
|
||||||
|
remote: "https://bitbucket.org/bitbucket/bar.git")
|
||||||
|
baz = instance_double(Tap, name: "homebrew/baz", custom_remote?: false)
|
||||||
|
foo = instance_double(Tap, name: "homebrew/foo", custom_remote?: false)
|
||||||
|
|
||||||
|
ENV["HOMEBREW_GITHUB_API_TOKEN_BEFORE"] = ENV.fetch("HOMEBREW_GITHUB_API_TOKEN", nil)
|
||||||
|
ENV["HOMEBREW_GITHUB_API_TOKEN"] = "some-token"
|
||||||
|
private_tap = instance_double(Tap, name: "privatebrew/private", custom_remote?: true,
|
||||||
|
remote: "https://#{ENV.fetch("HOMEBREW_GITHUB_API_TOKEN")}@github.com/privatebrew/homebrew-private")
|
||||||
|
|
||||||
|
allow(Tap).to receive(:select).and_return [bar, baz, foo, private_tap]
|
||||||
|
end
|
||||||
|
|
||||||
|
after do
|
||||||
|
ENV["HOMEBREW_GITHUB_API_TOKEN"] = ENV.fetch("HOMEBREW_GITHUB_API_TOKEN_BEFORE", nil)
|
||||||
|
ENV.delete("HOMEBREW_GITHUB_API_TOKEN_BEFORE")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns list of information" do
|
||||||
|
expect(dumper.tap_names).not_to be_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
it "dumps output" do
|
||||||
|
expected_output = <<~EOS
|
||||||
|
tap "bitbucket/bar", "https://bitbucket.org/bitbucket/bar.git"
|
||||||
|
tap "homebrew/baz"
|
||||||
|
tap "homebrew/foo"
|
||||||
|
tap "privatebrew/private", "https://\#{ENV.fetch("HOMEBREW_GITHUB_API_TOKEN")}@github.com/privatebrew/homebrew-private"
|
||||||
|
EOS
|
||||||
|
expect(dumper.dump).to eql(expected_output.chomp)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
77
Library/Homebrew/test/bundle/tap_installer_spec.rb
Normal file
77
Library/Homebrew/test/bundle/tap_installer_spec.rb
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "bundle"
|
||||||
|
|
||||||
|
RSpec.describe Homebrew::Bundle::TapInstaller do
|
||||||
|
describe ".installed_taps" do
|
||||||
|
before do
|
||||||
|
Homebrew::Bundle::TapDumper.reset!
|
||||||
|
end
|
||||||
|
|
||||||
|
it "calls Homebrew" do
|
||||||
|
expect { described_class.installed_taps }.not_to raise_error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when tap is installed" do
|
||||||
|
before do
|
||||||
|
allow(described_class).to receive(:installed_taps).and_return(["homebrew/cask"])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "skips" do
|
||||||
|
expect(Homebrew::Bundle).not_to receive(:system)
|
||||||
|
expect(described_class.preinstall("homebrew/cask")).to be(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when tap is not installed" do
|
||||||
|
before do
|
||||||
|
allow(described_class).to receive(:installed_taps).and_return([])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "taps" do
|
||||||
|
expect(Homebrew::Bundle).to receive(:system).with(HOMEBREW_BREW_FILE, "tap", "homebrew/cask",
|
||||||
|
verbose: false).and_return(true)
|
||||||
|
expect(described_class.preinstall("homebrew/cask")).to be(true)
|
||||||
|
expect(described_class.install("homebrew/cask")).to be(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with clone target" do
|
||||||
|
it "taps" do
|
||||||
|
expect(Homebrew::Bundle).to \
|
||||||
|
receive(:system).with(HOMEBREW_BREW_FILE, "tap", "homebrew/cask", "clone_target_path",
|
||||||
|
verbose: false).and_return(true)
|
||||||
|
expect(described_class.preinstall("homebrew/cask", clone_target: "clone_target_path")).to be(true)
|
||||||
|
expect(described_class.install("homebrew/cask", clone_target: "clone_target_path")).to be(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "fails" do
|
||||||
|
expect(Homebrew::Bundle).to \
|
||||||
|
receive(:system).with(HOMEBREW_BREW_FILE, "tap", "homebrew/cask", "clone_target_path",
|
||||||
|
verbose: false).and_return(false)
|
||||||
|
expect(described_class.preinstall("homebrew/cask", clone_target: "clone_target_path")).to be(true)
|
||||||
|
expect(described_class.install("homebrew/cask", clone_target: "clone_target_path")).to be(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with force_auto_update" do
|
||||||
|
it "taps" do
|
||||||
|
expect(Homebrew::Bundle).to receive(:system).with(HOMEBREW_BREW_FILE, "tap", "homebrew/cask",
|
||||||
|
"--force-auto-update",
|
||||||
|
verbose: false)
|
||||||
|
.and_return(true)
|
||||||
|
expect(described_class.preinstall("homebrew/cask", force_auto_update: true)).to be(true)
|
||||||
|
expect(described_class.install("homebrew/cask", force_auto_update: true)).to be(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "fails" do
|
||||||
|
expect(Homebrew::Bundle).to receive(:system).with(HOMEBREW_BREW_FILE, "tap", "homebrew/cask",
|
||||||
|
"--force-auto-update",
|
||||||
|
verbose: false)
|
||||||
|
.and_return(false)
|
||||||
|
expect(described_class.preinstall("homebrew/cask", force_auto_update: true)).to be(true)
|
||||||
|
expect(described_class.install("homebrew/cask", force_auto_update: true)).to be(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,77 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "bundle"
|
||||||
|
require "extend/kernel"
|
||||||
|
|
||||||
|
RSpec.describe Homebrew::Bundle::VscodeExtensionInstaller do
|
||||||
|
context "when VSCode is not installed" do
|
||||||
|
before do
|
||||||
|
described_class.reset!
|
||||||
|
allow(Homebrew::Bundle).to receive_messages(vscode_installed?: false, cask_installed?: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "tries to install vscode" do
|
||||||
|
expect(Homebrew::Bundle).to \
|
||||||
|
receive(:system).with(HOMEBREW_BREW_FILE, "install", "--cask", "visual-studio-code", verbose: false)
|
||||||
|
.and_return(true)
|
||||||
|
expect { described_class.preinstall("foo") }.to raise_error(RuntimeError)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when VSCode is installed" do
|
||||||
|
before do
|
||||||
|
allow(Homebrew::Bundle).to receive(:vscode_installed?).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when extension is installed" do
|
||||||
|
before do
|
||||||
|
allow(described_class).to receive(:installed_extensions).and_return(["foo"])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "skips" do
|
||||||
|
expect(Homebrew::Bundle).not_to receive(:system)
|
||||||
|
expect(described_class.preinstall("foo")).to be(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "skips ignoring case" do
|
||||||
|
expect(Homebrew::Bundle).not_to receive(:system)
|
||||||
|
expect(described_class.preinstall("Foo")).to be(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when extension is not installed" do
|
||||||
|
before do
|
||||||
|
allow(described_class).to receive(:installed_extensions).and_return([])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "installs extension" do
|
||||||
|
expect(Homebrew::Bundle).to receive(:system).with("code", "--install-extension", "foo",
|
||||||
|
verbose: false).and_return(true)
|
||||||
|
expect(described_class.preinstall("foo")).to be(true)
|
||||||
|
expect(described_class.install("foo")).to be(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "installs extension when euid != uid and Process::UID.re_exchangeable? returns true" do
|
||||||
|
expect(Process).to receive(:euid).and_return(1).once
|
||||||
|
expect(Process::UID).to receive(:re_exchangeable?).and_return(true).once
|
||||||
|
expect(Process::UID).to receive(:re_exchange).twice
|
||||||
|
|
||||||
|
expect(Homebrew::Bundle).to receive(:system).with("code", "--install-extension", "foo",
|
||||||
|
verbose: false).and_return(true)
|
||||||
|
expect(described_class.preinstall("foo")).to be(true)
|
||||||
|
expect(described_class.install("foo")).to be(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "installs extension when euid != uid and Process::UID.re_exchangeable? returns false" do
|
||||||
|
expect(Process).to receive(:euid).and_return(1).once
|
||||||
|
expect(Process::UID).to receive(:re_exchangeable?).and_return(false).once
|
||||||
|
expect(Process::Sys).to receive(:seteuid).twice
|
||||||
|
|
||||||
|
expect(Homebrew::Bundle).to receive(:system).with("code", "--install-extension", "foo",
|
||||||
|
verbose: false).and_return(true)
|
||||||
|
expect(described_class.preinstall("foo")).to be(true)
|
||||||
|
expect(described_class.install("foo")).to be(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
71
Library/Homebrew/test/bundle/whalebrew_dumper_spec.rb
Normal file
71
Library/Homebrew/test/bundle/whalebrew_dumper_spec.rb
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "bundle"
|
||||||
|
|
||||||
|
RSpec.describe Homebrew::Bundle::WhalebrewDumper do
|
||||||
|
subject(:dumper) { described_class }
|
||||||
|
|
||||||
|
describe ".images" do
|
||||||
|
before do
|
||||||
|
dumper.reset!
|
||||||
|
allow(Homebrew::Bundle).to receive(:whalebrew_installed?).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:whalebrew_list_single_output) do
|
||||||
|
"COMMAND IMAGE\nwget whalebrew/wget"
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:whalebrew_list_duplicate_output) do
|
||||||
|
"COMMAND IMAGE\nwget whalebrew/wget\nwget whalebrew/wget"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "removes the header" do
|
||||||
|
allow(dumper).to receive(:`).with("whalebrew list 2>/dev/null")
|
||||||
|
.and_return(whalebrew_list_single_output)
|
||||||
|
expect(dumper.images).not_to include("COMMAND")
|
||||||
|
expect(dumper.images).not_to include("IMAGE")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "dedupes items" do
|
||||||
|
allow(dumper).to receive(:`).with("whalebrew list 2>/dev/null")
|
||||||
|
.and_return(whalebrew_list_duplicate_output)
|
||||||
|
expect(dumper.images).to eq(["whalebrew/wget"])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when whalebrew is not installed" do
|
||||||
|
before do
|
||||||
|
dumper.reset!
|
||||||
|
allow(Homebrew::Bundle).to receive(:whalebrew_installed?).and_return(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns empty list" do
|
||||||
|
expect(dumper.images).to be_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
it "dumps as empty string" do
|
||||||
|
expect(dumper.dump).to eql("")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when whalebrew is installed" do
|
||||||
|
before do
|
||||||
|
allow(Homebrew::Bundle).to receive(:whalebrew_installed?).and_return(true)
|
||||||
|
allow(dumper).to receive(:images).and_return(["whalebrew/wget", "whalebrew/dig"])
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when images are installed" do
|
||||||
|
let(:expected_whalebrew_dump) do
|
||||||
|
%Q(whalebrew "whalebrew/wget"\nwhalebrew "whalebrew/dig")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns correct listing" do
|
||||||
|
expect(dumper.images).to eq(["whalebrew/wget", "whalebrew/dig"])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "dumps usable output for Brewfile" do
|
||||||
|
expect(dumper.dump).to eql(expected_whalebrew_dump)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
76
Library/Homebrew/test/bundle/whalebrew_installer_spec.rb
Normal file
76
Library/Homebrew/test/bundle/whalebrew_installer_spec.rb
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "bundle"
|
||||||
|
|
||||||
|
RSpec.describe Homebrew::Bundle::WhalebrewInstaller do
|
||||||
|
before do
|
||||||
|
stub_formula_loader formula("whalebrew") { url "whalebrew-1.0" }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe ".installed_images" do
|
||||||
|
it "shells out" do
|
||||||
|
expect { described_class.installed_images }.not_to raise_error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe ".image_installed?" do
|
||||||
|
context "when an image is already installed" do
|
||||||
|
before do
|
||||||
|
described_class.reset!
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns true" do
|
||||||
|
allow(Homebrew::Bundle::WhalebrewDumper).to receive(:images).and_return(["whalebrew/wget"])
|
||||||
|
expect(described_class.image_installed?("whalebrew/wget")).to be(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when an image isn't installed" do
|
||||||
|
before do
|
||||||
|
described_class.reset!
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns false" do
|
||||||
|
allow(Homebrew::Bundle::WhalebrewDumper).to receive(:images).and_return([])
|
||||||
|
expect(described_class.image_installed?("test/doesnotexist")).to be(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when whalebrew isn't installed" do
|
||||||
|
before do
|
||||||
|
allow(Homebrew::Bundle).to receive(:whalebrew_installed?).and_return(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "successfully installs whalebrew" do
|
||||||
|
expect(Homebrew::Bundle).to receive(:system).with(HOMEBREW_BREW_FILE, "install", "--formula", "whalebrew",
|
||||||
|
verbose: false)
|
||||||
|
.and_return(true)
|
||||||
|
expect { described_class.preinstall("whalebrew/wget") }.to raise_error(RuntimeError)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when whalebrew is installed" do
|
||||||
|
before do
|
||||||
|
described_class.reset!
|
||||||
|
allow(Homebrew::Bundle).to receive(:whalebrew_installed?).and_return(true)
|
||||||
|
allow(Homebrew::Bundle).to receive(:system).with("whalebrew", "install", "whalebrew/wget", verbose: false)
|
||||||
|
.and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when the requested image is already installed" do
|
||||||
|
before do
|
||||||
|
allow(described_class).to receive(:image_installed?).with("whalebrew/wget").and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "skips" do
|
||||||
|
expect(described_class.preinstall("whalebrew/wget")).to be(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "successfully installs an image" do
|
||||||
|
expect(described_class.preinstall("whalebrew/wget")).to be(true)
|
||||||
|
expect { described_class.install("whalebrew/wget") }.not_to raise_error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -1,13 +1,12 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "cmd/bundle"
|
||||||
require "cmd/shared_examples/args_parse"
|
require "cmd/shared_examples/args_parse"
|
||||||
|
|
||||||
RSpec.describe "Homebrew::Cmd::BundleCmd", :integration_test, :needs_network do
|
RSpec.describe Homebrew::Cmd::Bundle do
|
||||||
before { setup_remote_tap "homebrew/bundle" }
|
it_behaves_like "parseable arguments"
|
||||||
|
|
||||||
it_behaves_like "parseable arguments", command_name: "bundle"
|
it "checks if a Brewfile's dependencies are satisfied", :integration_test do
|
||||||
|
|
||||||
it "checks if a Brewfile's dependencies are satisfied" do
|
|
||||||
HOMEBREW_REPOSITORY.cd do
|
HOMEBREW_REPOSITORY.cd do
|
||||||
system "git", "init"
|
system "git", "init"
|
||||||
system "git", "commit", "--allow-empty", "-m", "This is a test commit"
|
system "git", "commit", "--allow-empty", "-m", "This is a test commit"
|
||||||
|
@ -26,7 +26,7 @@ A *formula* is a package definition written in Ruby. It can be created with `bre
|
|||||||
| **tap** | directory (and usually Git repository) of **formulae**, **casks** and/or **external commands** | `/opt/homebrew/Library/Taps/homebrew/homebrew-core` |
|
| **tap** | directory (and usually Git repository) of **formulae**, **casks** and/or **external commands** | `/opt/homebrew/Library/Taps/homebrew/homebrew-core` |
|
||||||
| **bottle** | pre-built **keg** poured into a **rack** of the **Cellar** instead of building from upstream sources | `qt--6.5.1.ventura.bottle.tar.gz` |
|
| **bottle** | pre-built **keg** poured into a **rack** of the **Cellar** instead of building from upstream sources | `qt--6.5.1.ventura.bottle.tar.gz` |
|
||||||
| **tab** | information about a **keg**, e.g. whether it was poured from a **bottle** or built from source | `/opt/homebrew/Cellar/foo/0.1/INSTALL_RECEIPT.json` |
|
| **tab** | information about a **keg**, e.g. whether it was poured from a **bottle** or built from source | `/opt/homebrew/Cellar/foo/0.1/INSTALL_RECEIPT.json` |
|
||||||
| **Brew Bundle** | an [extension of Homebrew](https://github.com/Homebrew/homebrew-bundle) to describe dependencies | `brew 'myservice', restart_service: true` |
|
| **Brew Bundle** | a declarative interface to Homebrew | `brew 'myservice', restart_service: true` |
|
||||||
| **Brew Services** | the Homebrew command to manage background services | `brew services start myservice` |
|
| **Brew Services** | the Homebrew command to manage background services | `brew services start myservice` |
|
||||||
|
|
||||||
## An introduction
|
## An introduction
|
||||||
@ -152,10 +152,10 @@ A `Hash` (e.g. `=>`) adds information to a dependency. Given a string or symbol,
|
|||||||
* `:optional` (not allowed in `Homebrew/homebrew-core`) generates an implicit `with-foo` option for the formula. This means that, given `depends_on "foo" => :optional`, the user must pass `--with-foo` to use the dependency.
|
* `:optional` (not allowed in `Homebrew/homebrew-core`) generates an implicit `with-foo` option for the formula. This means that, given `depends_on "foo" => :optional`, the user must pass `--with-foo` to use the dependency.
|
||||||
* `:recommended` (not allowed in `Homebrew/homebrew-core`) generates an implicit `without-foo` option, meaning that the dependency is enabled by default and the user must pass `--without-foo` to disable this dependency. The default description can be overridden using the [`option`](https://rubydoc.brew.sh/Formula#option-class_method) syntax (in this case, the [`option` declaration](#adding-optional-steps) must precede the dependency):
|
* `:recommended` (not allowed in `Homebrew/homebrew-core`) generates an implicit `without-foo` option, meaning that the dependency is enabled by default and the user must pass `--without-foo` to disable this dependency. The default description can be overridden using the [`option`](https://rubydoc.brew.sh/Formula#option-class_method) syntax (in this case, the [`option` declaration](#adding-optional-steps) must precede the dependency):
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
option "with-foo", "Compile with foo bindings" # This overrides the generated description if you want to
|
option "with-foo", "Compile with foo bindings" # This overrides the generated description if you want to
|
||||||
depends_on "foo" => :optional # Generated description would otherwise be "Build with foo support"
|
depends_on "foo" => :optional # Generated description would otherwise be "Build with foo support"
|
||||||
```
|
```
|
||||||
|
|
||||||
* `"<option-name>"` (not allowed in `Homebrew/homebrew-core`) requires a dependency to have been built with the specified option.
|
* `"<option-name>"` (not allowed in `Homebrew/homebrew-core`) requires a dependency to have been built with the specified option.
|
||||||
|
|
||||||
@ -950,27 +950,27 @@ Several other utilities for Ruby's [`Pathname`](https://rubydoc.brew.sh/Pathname
|
|||||||
|
|
||||||
* To perform several operations within a directory, enclose them within a [`cd <path> do`](https://rubydoc.brew.sh/Pathname#cd-instance_method) block:
|
* To perform several operations within a directory, enclose them within a [`cd <path> do`](https://rubydoc.brew.sh/Pathname#cd-instance_method) block:
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
cd "src" do
|
cd "src" do
|
||||||
system "./configure", "--disable-debug", "--prefix=#{prefix}"
|
system "./configure", "--disable-debug", "--prefix=#{prefix}"
|
||||||
system "make", "install"
|
system "make", "install"
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
* To surface one or more binaries buried in `libexec` or a macOS `.app` package, use [`write_exec_script`](https://rubydoc.brew.sh/Pathname#write_exec_script-instance_method) or [`write_jar_script`](https://rubydoc.brew.sh/Pathname#write_jar_script-instance_method):
|
* To surface one or more binaries buried in `libexec` or a macOS `.app` package, use [`write_exec_script`](https://rubydoc.brew.sh/Pathname#write_exec_script-instance_method) or [`write_jar_script`](https://rubydoc.brew.sh/Pathname#write_jar_script-instance_method):
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
bin.write_exec_script (libexec/"bin").children
|
bin.write_exec_script (libexec/"bin").children
|
||||||
bin.write_exec_script prefix/"Package.app/Contents/MacOS/package"
|
bin.write_exec_script prefix/"Package.app/Contents/MacOS/package"
|
||||||
bin.write_jar_script libexec/jar_file, "jarfile", java_version: "11"
|
bin.write_jar_script libexec/jar_file, "jarfile", java_version: "11"
|
||||||
```
|
```
|
||||||
|
|
||||||
* For binaries that require setting one or more environment variables to function properly, use [`write_env_script`](https://rubydoc.brew.sh/Pathname#write_env_script-instance_method) or [`env_script_all_files`](https://rubydoc.brew.sh/Pathname#env_script_all_files-instance_method):
|
* For binaries that require setting one or more environment variables to function properly, use [`write_env_script`](https://rubydoc.brew.sh/Pathname#write_env_script-instance_method) or [`env_script_all_files`](https://rubydoc.brew.sh/Pathname#env_script_all_files-instance_method):
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
(bin/"package").write_env_script libexec/"package", PACKAGE_ROOT: libexec
|
(bin/"package").write_env_script libexec/"package", PACKAGE_ROOT: libexec
|
||||||
bin.env_script_all_files(libexec/"bin", PERL5LIB: ENV.fetch("PERL5LIB", nil))
|
bin.env_script_all_files(libexec/"bin", PERL5LIB: ENV.fetch("PERL5LIB", nil))
|
||||||
```
|
```
|
||||||
|
|
||||||
### Rewriting a script shebang
|
### Rewriting a script shebang
|
||||||
|
|
||||||
@ -1046,34 +1046,34 @@ There are two ways to add `launchd` plists and `systemd` services to a formula,
|
|||||||
|
|
||||||
1. If the package already provides a service file the formula can reference it by name:
|
1. If the package already provides a service file the formula can reference it by name:
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
service do
|
service do
|
||||||
name macos: "custom.launchd.name",
|
name macos: "custom.launchd.name",
|
||||||
linux: "custom.systemd.name"
|
linux: "custom.systemd.name"
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
To find the file we append `.plist` to the `launchd` service name and `.service` to the `systemd` service name internally.
|
To find the file we append `.plist` to the `launchd` service name and `.service` to the `systemd` service name internally.
|
||||||
|
|
||||||
2. If the formula does not provide a service file you can generate one using the following stanza:
|
2. If the formula does not provide a service file you can generate one using the following stanza:
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
# 1. An individual command
|
# 1. An individual command
|
||||||
service do
|
service do
|
||||||
run opt_bin/"script"
|
run opt_bin/"script"
|
||||||
end
|
end
|
||||||
|
|
||||||
# 2. A command with arguments
|
# 2. A command with arguments
|
||||||
service do
|
service do
|
||||||
run [opt_bin/"script", "--config", etc/"dir/config.yml"]
|
run [opt_bin/"script", "--config", etc/"dir/config.yml"]
|
||||||
end
|
end
|
||||||
|
|
||||||
# 3. OS specific commands (If you omit one, the service file won't get generated for that OS.)
|
# 3. OS specific commands (If you omit one, the service file won't get generated for that OS.)
|
||||||
service do
|
service do
|
||||||
run macos: [opt_bin/"macos_script", "standalone"],
|
run macos: [opt_bin/"macos_script", "standalone"],
|
||||||
linux: var/"special_linux_script"
|
linux: var/"special_linux_script"
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Service block methods
|
#### Service block methods
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user