mirror of
https://github.com/Homebrew/brew.git
synced 2025-07-14 16:09:03 +08:00

We already do this for deprecations but these may make warnings and errors from Homebrew easier to spot in GitHub Actions logs. While we're here, cleanup other cases that should have used `GitHub::Actions::Annotation` but didn't and provide some helpers and tweaks there necessary for our use case here.
697 lines
22 KiB
Ruby
697 lines
22 KiB
Ruby
# typed: true
|
|
# frozen_string_literal: true
|
|
|
|
require "attrable"
|
|
require "formula_installer"
|
|
require "unpack_strategy"
|
|
require "utils/topological_hash"
|
|
|
|
require "cask/config"
|
|
require "cask/download"
|
|
require "cask/migrator"
|
|
require "cask/quarantine"
|
|
|
|
require "cgi"
|
|
|
|
module Cask
|
|
# Installer for a {Cask}.
|
|
class Installer
|
|
extend Attrable
|
|
|
|
def initialize(cask, command: SystemCommand, force: false, adopt: false,
|
|
skip_cask_deps: false, binaries: true, verbose: false,
|
|
zap: false, require_sha: false, upgrade: false, reinstall: false,
|
|
installed_as_dependency: false, quarantine: true,
|
|
verify_download_integrity: true, quiet: false)
|
|
@cask = cask
|
|
@command = command
|
|
@force = force
|
|
@adopt = adopt
|
|
@skip_cask_deps = skip_cask_deps
|
|
@binaries = binaries
|
|
@verbose = verbose
|
|
@zap = zap
|
|
@require_sha = require_sha
|
|
@reinstall = reinstall
|
|
@upgrade = upgrade
|
|
@installed_as_dependency = installed_as_dependency
|
|
@quarantine = quarantine
|
|
@verify_download_integrity = verify_download_integrity
|
|
@quiet = quiet
|
|
end
|
|
|
|
attr_predicate :binaries?, :force?, :adopt?, :skip_cask_deps?, :require_sha?,
|
|
:reinstall?, :upgrade?, :verbose?, :zap?, :installed_as_dependency?,
|
|
:quarantine?, :quiet?
|
|
|
|
def self.caveats(cask)
|
|
odebug "Printing caveats"
|
|
|
|
caveats = cask.caveats
|
|
return if caveats.empty?
|
|
|
|
Homebrew.messages.record_caveats(cask.token, caveats)
|
|
|
|
<<~EOS
|
|
#{ohai_title "Caveats"}
|
|
#{caveats}
|
|
EOS
|
|
end
|
|
|
|
sig { params(quiet: T.nilable(T::Boolean), timeout: T.nilable(T.any(Integer, Float))).void }
|
|
def fetch(quiet: nil, timeout: nil)
|
|
odebug "Cask::Installer#fetch"
|
|
|
|
load_cask_from_source_api! if @cask.loaded_from_api? && @cask.caskfile_only?
|
|
verify_has_sha if require_sha? && !force?
|
|
check_requirements
|
|
|
|
download(quiet:, timeout:)
|
|
|
|
satisfy_cask_and_formula_dependencies
|
|
|
|
forbidden_tap_check
|
|
forbidden_cask_and_formula_check
|
|
end
|
|
|
|
def stage
|
|
odebug "Cask::Installer#stage"
|
|
|
|
Caskroom.ensure_caskroom_exists
|
|
|
|
extract_primary_container
|
|
save_caskfile
|
|
rescue => e
|
|
purge_versioned_files
|
|
raise e
|
|
end
|
|
|
|
def install
|
|
start_time = Time.now
|
|
odebug "Cask::Installer#install"
|
|
|
|
Migrator.migrate_if_needed(@cask)
|
|
|
|
old_config = @cask.config
|
|
predecessor = @cask if reinstall? && @cask.installed?
|
|
|
|
check_deprecate_disable
|
|
check_conflicts
|
|
|
|
print caveats
|
|
fetch
|
|
uninstall_existing_cask if reinstall?
|
|
|
|
backup if force? && @cask.staged_path.exist? && @cask.metadata_versioned_path.exist?
|
|
|
|
oh1 "Installing Cask #{Formatter.identifier(@cask)}"
|
|
opoo "macOS's Gatekeeper has been disabled for this Cask" unless quarantine?
|
|
stage
|
|
|
|
@cask.config = @cask.default_config.merge(old_config)
|
|
|
|
install_artifacts(predecessor:)
|
|
|
|
if (tap = @cask.tap) && tap.should_report_analytics?
|
|
::Utils::Analytics.report_package_event(:cask_install, package_name: @cask.token, tap_name: tap.name,
|
|
on_request: true)
|
|
end
|
|
|
|
purge_backed_up_versioned_files
|
|
|
|
puts summary
|
|
end_time = Time.now
|
|
Homebrew.messages.package_installed(@cask.token, end_time - start_time)
|
|
rescue
|
|
restore_backup
|
|
raise
|
|
end
|
|
|
|
def check_deprecate_disable
|
|
deprecate_disable_type = DeprecateDisable.type(@cask)
|
|
return if deprecate_disable_type.nil?
|
|
|
|
message = DeprecateDisable.message(@cask)
|
|
message_full = "#{@cask.token} has been #{message}"
|
|
|
|
case deprecate_disable_type
|
|
when :deprecated
|
|
opoo message_full
|
|
when :disabled
|
|
GitHub::Actions.puts_annotation_if_env_set(:error, message)
|
|
raise CaskCannotBeInstalledError.new(@cask, message)
|
|
end
|
|
end
|
|
|
|
def check_conflicts
|
|
return unless @cask.conflicts_with
|
|
|
|
@cask.conflicts_with[:cask].each do |conflicting_cask|
|
|
if (conflicting_cask_tap_with_token = Tap.with_cask_token(conflicting_cask))
|
|
conflicting_cask_tap, = conflicting_cask_tap_with_token
|
|
next unless conflicting_cask_tap.installed?
|
|
end
|
|
|
|
conflicting_cask = CaskLoader.load(conflicting_cask)
|
|
raise CaskConflictError.new(@cask, conflicting_cask) if conflicting_cask.installed?
|
|
rescue CaskUnavailableError
|
|
next # Ignore conflicting Casks that do not exist.
|
|
end
|
|
end
|
|
|
|
def uninstall_existing_cask
|
|
return unless @cask.installed?
|
|
|
|
# Always force uninstallation, ignore method parameter
|
|
cask_installer = Installer.new(@cask, verbose: verbose?, force: true, upgrade: upgrade?, reinstall: true)
|
|
zap? ? cask_installer.zap : cask_installer.uninstall(successor: @cask)
|
|
end
|
|
|
|
sig { returns(String) }
|
|
def summary
|
|
s = +""
|
|
s << "#{Homebrew::EnvConfig.install_badge} " unless Homebrew::EnvConfig.no_emoji?
|
|
s << "#{@cask} was successfully #{upgrade? ? "upgraded" : "installed"}!"
|
|
s.freeze
|
|
end
|
|
|
|
sig { returns(Download) }
|
|
def downloader
|
|
@downloader ||= Download.new(@cask, quarantine: quarantine?)
|
|
end
|
|
|
|
sig { params(quiet: T.nilable(T::Boolean), timeout: T.nilable(T.any(Integer, Float))).returns(Pathname) }
|
|
def download(quiet: nil, timeout: nil)
|
|
# Store cask download path in cask to prevent multiple downloads in a row when checking if it's outdated
|
|
@cask.download ||= downloader.fetch(quiet:, verify_download_integrity: @verify_download_integrity,
|
|
timeout:)
|
|
end
|
|
|
|
def verify_has_sha
|
|
odebug "Checking cask has checksum"
|
|
return if @cask.sha256 != :no_check
|
|
|
|
raise CaskError, <<~EOS
|
|
Cask '#{@cask}' does not have a sha256 checksum defined and was not installed.
|
|
This means you have the #{Formatter.identifier("--require-sha")} option set, perhaps in your HOMEBREW_CASK_OPTS.
|
|
EOS
|
|
end
|
|
|
|
def primary_container
|
|
@primary_container ||= begin
|
|
downloaded_path = download(quiet: true)
|
|
UnpackStrategy.detect(downloaded_path, type: @cask.container&.type, merge_xattrs: true)
|
|
end
|
|
end
|
|
|
|
def extract_primary_container(to: @cask.staged_path)
|
|
odebug "Extracting primary container"
|
|
|
|
odebug "Using container class #{primary_container.class} for #{primary_container.path}"
|
|
|
|
basename = downloader.basename
|
|
|
|
if (nested_container = @cask.container&.nested)
|
|
Dir.mktmpdir("cask-installer", HOMEBREW_TEMP) do |tmpdir|
|
|
tmpdir = Pathname(tmpdir)
|
|
primary_container.extract(to: tmpdir, basename:, verbose: verbose?)
|
|
|
|
FileUtils.chmod_R "+rw", tmpdir/nested_container, force: true, verbose: verbose?
|
|
|
|
UnpackStrategy.detect(tmpdir/nested_container, merge_xattrs: true)
|
|
.extract_nestedly(to:, verbose: verbose?)
|
|
end
|
|
else
|
|
primary_container.extract_nestedly(to:, basename:, verbose: verbose?)
|
|
end
|
|
|
|
return unless quarantine?
|
|
return unless Quarantine.available?
|
|
|
|
Quarantine.propagate(from: primary_container.path, to:)
|
|
end
|
|
|
|
sig { params(predecessor: T.nilable(Cask)).void }
|
|
def install_artifacts(predecessor: nil)
|
|
artifacts = @cask.artifacts
|
|
already_installed_artifacts = []
|
|
|
|
odebug "Installing artifacts"
|
|
|
|
artifacts.each do |artifact|
|
|
next unless artifact.respond_to?(:install_phase)
|
|
|
|
odebug "Installing artifact of class #{artifact.class}"
|
|
|
|
next if artifact.is_a?(Artifact::Binary) && !binaries?
|
|
|
|
artifact.install_phase(
|
|
command: @command, verbose: verbose?, adopt: adopt?, force: force?, predecessor:,
|
|
)
|
|
already_installed_artifacts.unshift(artifact)
|
|
end
|
|
|
|
save_config_file
|
|
save_download_sha if @cask.version.latest?
|
|
rescue => e
|
|
begin
|
|
already_installed_artifacts&.each do |artifact|
|
|
if artifact.respond_to?(:uninstall_phase)
|
|
odebug "Reverting installation of artifact of class #{artifact.class}"
|
|
artifact.uninstall_phase(command: @command, verbose: verbose?, force: force?)
|
|
end
|
|
|
|
next unless artifact.respond_to?(:post_uninstall_phase)
|
|
|
|
odebug "Reverting installation of artifact of class #{artifact.class}"
|
|
artifact.post_uninstall_phase(command: @command, verbose: verbose?, force: force?)
|
|
end
|
|
ensure
|
|
purge_versioned_files
|
|
raise e
|
|
end
|
|
end
|
|
|
|
def check_requirements
|
|
check_macos_requirements
|
|
check_arch_requirements
|
|
end
|
|
|
|
def check_macos_requirements
|
|
return unless @cask.depends_on.macos
|
|
return if @cask.depends_on.macos.satisfied?
|
|
|
|
raise CaskError, @cask.depends_on.macos.message(type: :cask)
|
|
end
|
|
|
|
def check_arch_requirements
|
|
return if @cask.depends_on.arch.nil?
|
|
|
|
@current_arch ||= { type: Hardware::CPU.type, bits: Hardware::CPU.bits }
|
|
return if @cask.depends_on.arch.any? do |arch|
|
|
arch[:type] == @current_arch[:type] &&
|
|
Array(arch[:bits]).include?(@current_arch[:bits])
|
|
end
|
|
|
|
raise CaskError,
|
|
"Cask #{@cask} depends on hardware architecture being one of " \
|
|
"[#{@cask.depends_on.arch.map(&:to_s).join(", ")}], " \
|
|
"but you are running #{@current_arch}."
|
|
end
|
|
|
|
def cask_and_formula_dependencies
|
|
return @cask_and_formula_dependencies if @cask_and_formula_dependencies
|
|
|
|
graph = ::Utils::TopologicalHash.graph_package_dependencies(@cask)
|
|
|
|
raise CaskSelfReferencingDependencyError, @cask.token if graph[@cask].include?(@cask)
|
|
|
|
::Utils::TopologicalHash.graph_package_dependencies(primary_container.dependencies, graph)
|
|
|
|
begin
|
|
@cask_and_formula_dependencies = graph.tsort - [@cask]
|
|
rescue TSort::Cyclic
|
|
strongly_connected_components = graph.strongly_connected_components.sort_by(&:count)
|
|
cyclic_dependencies = strongly_connected_components.last - [@cask]
|
|
raise CaskCyclicDependencyError.new(@cask.token, cyclic_dependencies.to_sentence)
|
|
end
|
|
end
|
|
|
|
def missing_cask_and_formula_dependencies
|
|
cask_and_formula_dependencies.reject do |cask_or_formula|
|
|
case cask_or_formula
|
|
when Formula
|
|
cask_or_formula.any_version_installed? && cask_or_formula.optlinked?
|
|
when Cask
|
|
cask_or_formula.installed?
|
|
end
|
|
end
|
|
end
|
|
|
|
def satisfy_cask_and_formula_dependencies
|
|
return if installed_as_dependency?
|
|
|
|
formulae_and_casks = cask_and_formula_dependencies
|
|
|
|
return if formulae_and_casks.empty?
|
|
|
|
missing_formulae_and_casks = missing_cask_and_formula_dependencies
|
|
|
|
if missing_formulae_and_casks.empty?
|
|
puts "All dependencies satisfied."
|
|
return
|
|
end
|
|
|
|
ohai "Installing dependencies: #{missing_formulae_and_casks.map(&:to_s).join(", ")}"
|
|
missing_formulae_and_casks.each do |cask_or_formula|
|
|
if cask_or_formula.is_a?(Cask)
|
|
if skip_cask_deps?
|
|
opoo "`--skip-cask-deps` is set; skipping installation of #{cask_or_formula}."
|
|
next
|
|
end
|
|
|
|
Installer.new(
|
|
cask_or_formula,
|
|
adopt: adopt?,
|
|
binaries: binaries?,
|
|
verbose: verbose?,
|
|
installed_as_dependency: true,
|
|
force: false,
|
|
).install
|
|
else
|
|
fi = FormulaInstaller.new(
|
|
cask_or_formula,
|
|
**{
|
|
show_header: true,
|
|
installed_as_dependency: true,
|
|
installed_on_request: false,
|
|
verbose: verbose?,
|
|
}.compact,
|
|
)
|
|
fi.prelude
|
|
fi.fetch
|
|
fi.install
|
|
fi.finish
|
|
end
|
|
end
|
|
end
|
|
|
|
def caveats
|
|
self.class.caveats(@cask)
|
|
end
|
|
|
|
def metadata_subdir
|
|
@metadata_subdir ||= @cask.metadata_subdir("Casks", timestamp: :now, create: true)
|
|
end
|
|
|
|
def save_caskfile
|
|
old_savedir = @cask.metadata_timestamped_path
|
|
|
|
return if @cask.source.blank?
|
|
|
|
extension = @cask.loaded_from_api? ? "json" : "rb"
|
|
(metadata_subdir/"#{@cask.token}.#{extension}").write @cask.source
|
|
old_savedir&.rmtree
|
|
end
|
|
|
|
def save_config_file
|
|
@cask.config_path.atomic_write(@cask.config.to_json)
|
|
end
|
|
|
|
def save_download_sha
|
|
@cask.download_sha_path.atomic_write(@cask.new_download_sha) if @cask.checksumable?
|
|
end
|
|
|
|
sig { params(successor: T.nilable(Cask)).void }
|
|
def uninstall(successor: nil)
|
|
load_installed_caskfile!
|
|
oh1 "Uninstalling Cask #{Formatter.identifier(@cask)}"
|
|
uninstall_artifacts(clear: true, successor:)
|
|
if !reinstall? && !upgrade?
|
|
remove_download_sha
|
|
remove_config_file
|
|
end
|
|
purge_versioned_files
|
|
purge_caskroom_path if force?
|
|
end
|
|
|
|
def remove_config_file
|
|
FileUtils.rm_f @cask.config_path
|
|
@cask.config_path.parent.rmdir_if_possible
|
|
end
|
|
|
|
def remove_download_sha
|
|
FileUtils.rm_f @cask.download_sha_path
|
|
@cask.download_sha_path.parent.rmdir_if_possible
|
|
end
|
|
|
|
sig { params(successor: T.nilable(Cask)).void }
|
|
def start_upgrade(successor:)
|
|
uninstall_artifacts(successor:)
|
|
backup
|
|
end
|
|
|
|
def backup
|
|
@cask.staged_path.rename backup_path
|
|
@cask.metadata_versioned_path.rename backup_metadata_path
|
|
end
|
|
|
|
def restore_backup
|
|
return if !backup_path.directory? || !backup_metadata_path.directory?
|
|
|
|
@cask.staged_path.rmtree if @cask.staged_path.exist?
|
|
@cask.metadata_versioned_path.rmtree if @cask.metadata_versioned_path.exist?
|
|
|
|
backup_path.rename @cask.staged_path
|
|
backup_metadata_path.rename @cask.metadata_versioned_path
|
|
end
|
|
|
|
sig { params(predecessor: Cask).void }
|
|
def revert_upgrade(predecessor:)
|
|
opoo "Reverting upgrade for Cask #{@cask}"
|
|
restore_backup
|
|
install_artifacts(predecessor:)
|
|
end
|
|
|
|
def finalize_upgrade
|
|
ohai "Purging files for version #{@cask.version} of Cask #{@cask}"
|
|
|
|
purge_backed_up_versioned_files
|
|
|
|
puts summary
|
|
end
|
|
|
|
sig { params(clear: T::Boolean, successor: T.nilable(Cask)).void }
|
|
def uninstall_artifacts(clear: false, successor: nil)
|
|
artifacts = @cask.artifacts
|
|
|
|
odebug "Uninstalling artifacts"
|
|
odebug "#{::Utils.pluralize("artifact", artifacts.length, include_count: true)} defined", artifacts
|
|
|
|
artifacts.each do |artifact|
|
|
if artifact.respond_to?(:uninstall_phase)
|
|
odebug "Uninstalling artifact of class #{artifact.class}"
|
|
artifact.uninstall_phase(
|
|
command: @command,
|
|
verbose: verbose?,
|
|
skip: clear,
|
|
force: force?,
|
|
successor:,
|
|
upgrade: upgrade?,
|
|
reinstall: reinstall?,
|
|
)
|
|
end
|
|
|
|
next unless artifact.respond_to?(:post_uninstall_phase)
|
|
|
|
odebug "Post-uninstalling artifact of class #{artifact.class}"
|
|
artifact.post_uninstall_phase(
|
|
command: @command,
|
|
verbose: verbose?,
|
|
skip: clear,
|
|
force: force?,
|
|
successor:,
|
|
)
|
|
end
|
|
end
|
|
|
|
def zap
|
|
load_installed_caskfile!
|
|
ohai "Implied `brew uninstall --cask #{@cask}`"
|
|
uninstall_artifacts
|
|
if (zap_stanzas = @cask.artifacts.select { |a| a.is_a?(Artifact::Zap) }).empty?
|
|
opoo "No zap stanza present for Cask '#{@cask}'"
|
|
else
|
|
ohai "Dispatching zap stanza"
|
|
zap_stanzas.each do |stanza|
|
|
stanza.zap_phase(command: @command, verbose: verbose?, force: force?)
|
|
end
|
|
end
|
|
ohai "Removing all staged versions of Cask '#{@cask}'"
|
|
purge_caskroom_path
|
|
end
|
|
|
|
def backup_path
|
|
return if @cask.staged_path.nil?
|
|
|
|
Pathname("#{@cask.staged_path}.upgrading")
|
|
end
|
|
|
|
def backup_metadata_path
|
|
return if @cask.metadata_versioned_path.nil?
|
|
|
|
Pathname("#{@cask.metadata_versioned_path}.upgrading")
|
|
end
|
|
|
|
def gain_permissions_remove(path)
|
|
Utils.gain_permissions_remove(path, command: @command)
|
|
end
|
|
|
|
def purge_backed_up_versioned_files
|
|
# versioned staged distribution
|
|
gain_permissions_remove(backup_path) if backup_path&.exist?
|
|
|
|
# Homebrew Cask metadata
|
|
return unless backup_metadata_path.directory?
|
|
|
|
backup_metadata_path.children.each do |subdir|
|
|
gain_permissions_remove(subdir)
|
|
end
|
|
backup_metadata_path.rmdir_if_possible
|
|
end
|
|
|
|
def purge_versioned_files
|
|
ohai "Purging files for version #{@cask.version} of Cask #{@cask}"
|
|
|
|
# versioned staged distribution
|
|
gain_permissions_remove(@cask.staged_path) if @cask.staged_path&.exist?
|
|
|
|
# Homebrew Cask metadata
|
|
if @cask.metadata_versioned_path.directory?
|
|
@cask.metadata_versioned_path.children.each do |subdir|
|
|
gain_permissions_remove(subdir)
|
|
end
|
|
|
|
@cask.metadata_versioned_path.rmdir_if_possible
|
|
end
|
|
@cask.metadata_main_container_path.rmdir_if_possible unless upgrade?
|
|
|
|
# toplevel staged distribution
|
|
@cask.caskroom_path.rmdir_if_possible unless upgrade?
|
|
|
|
# Remove symlinks for renamed casks if they are now broken.
|
|
@cask.old_tokens.each do |old_token|
|
|
old_caskroom_path = Caskroom.path/old_token
|
|
FileUtils.rm old_caskroom_path if old_caskroom_path.symlink? && !old_caskroom_path.exist?
|
|
end
|
|
end
|
|
|
|
def purge_caskroom_path
|
|
odebug "Purging all staged versions of Cask #{@cask}"
|
|
gain_permissions_remove(@cask.caskroom_path)
|
|
end
|
|
|
|
sig { void }
|
|
def forbidden_tap_check
|
|
return if Tap.allowed_taps.blank? && Tap.forbidden_taps.blank?
|
|
|
|
owner = Homebrew::EnvConfig.forbidden_owner
|
|
owner_contact = if (contact = Homebrew::EnvConfig.forbidden_owner_contact.presence)
|
|
"\n#{contact}"
|
|
end
|
|
|
|
unless skip_cask_deps?
|
|
cask_and_formula_dependencies.each do |cask_or_formula|
|
|
dep_tap = cask_or_formula.tap
|
|
next if dep_tap.blank? || (dep_tap.allowed_by_env? && !dep_tap.forbidden_by_env?)
|
|
|
|
dep_full_name = cask_or_formula.full_name
|
|
error_message = +"The installation of #{@cask} has a dependency #{dep_full_name}\n" \
|
|
"from the #{dep_tap} tap but #{owner} "
|
|
error_message << "has not allowed this tap in `HOMEBREW_ALLOWED_TAPS`" unless dep_tap.allowed_by_env?
|
|
error_message << " and\n" if !dep_tap.allowed_by_env? && dep_tap.forbidden_by_env?
|
|
error_message << "has forbidden this tap in `HOMEBREW_FORBIDDEN_TAPS`" if dep_tap.forbidden_by_env?
|
|
error_message << ".#{owner_contact}"
|
|
|
|
raise CaskCannotBeInstalledError.new(@cask, error_message)
|
|
end
|
|
end
|
|
|
|
cask_tap = @cask.tap
|
|
return if cask_tap.blank? || (cask_tap.allowed_by_env? && !cask_tap.forbidden_by_env?)
|
|
|
|
error_message = +"The installation of #{@cask.full_name} has the tap #{cask_tap}\n" \
|
|
"but #{owner} "
|
|
error_message << "has not allowed this tap in `HOMEBREW_ALLOWED_TAPS`" unless cask_tap.allowed_by_env?
|
|
error_message << " and\n" if !cask_tap.allowed_by_env? && cask_tap.forbidden_by_env?
|
|
error_message << "has forbidden this tap in `HOMEBREW_FORBIDDEN_TAPS`" if cask_tap.forbidden_by_env?
|
|
error_message << ".#{owner_contact}"
|
|
|
|
raise CaskCannotBeInstalledError.new(@cask, error_message)
|
|
end
|
|
|
|
sig { void }
|
|
def forbidden_cask_and_formula_check
|
|
forbidden_formulae = Set.new(Homebrew::EnvConfig.forbidden_formulae.to_s.split)
|
|
forbidden_casks = Set.new(Homebrew::EnvConfig.forbidden_casks.to_s.split)
|
|
return if forbidden_formulae.blank? && forbidden_casks.blank?
|
|
|
|
owner = Homebrew::EnvConfig.forbidden_owner
|
|
owner_contact = if (contact = Homebrew::EnvConfig.forbidden_owner_contact.presence)
|
|
"\n#{contact}"
|
|
end
|
|
|
|
unless skip_cask_deps?
|
|
cask_and_formula_dependencies.each do |dep_cask_or_formula|
|
|
dep_name, dep_type, variable = if dep_cask_or_formula.is_a?(Cask) && forbidden_casks.present?
|
|
dep_cask = dep_cask_or_formula
|
|
dep_cask_name = if forbidden_casks.include?(dep_cask.token)
|
|
dep_cask.token
|
|
elsif dep_cask.tap.present? &&
|
|
forbidden_casks.include?(dep_cask.full_name)
|
|
dep_cask.full_name
|
|
end
|
|
[dep_cask_name, "cask", "HOMEBREW_FORBIDDEN_CASKS"]
|
|
elsif dep_cask_or_formula.is_a?(Formula) && forbidden_formulae.present?
|
|
dep_formula = dep_cask_or_formula
|
|
formula_name = if forbidden_formulae.include?(dep_formula.name)
|
|
dep_formula.name
|
|
elsif dep_formula.tap.present? &&
|
|
forbidden_formulae.include?(dep_formula.full_name)
|
|
dep_formula.full_name
|
|
end
|
|
[formula_name, "formula", "HOMEBREW_FORBIDDEN_FORMULAE"]
|
|
end
|
|
next if dep_name.blank?
|
|
|
|
raise CaskCannotBeInstalledError.new(@cask, <<~EOS
|
|
The installation of #{@cask} has a dependency #{dep_name}
|
|
but the #{dep_name} #{dep_type} was forbidden by #{owner} in `#{variable}`.#{owner_contact}
|
|
EOS
|
|
)
|
|
end
|
|
end
|
|
return if forbidden_casks.blank?
|
|
|
|
cask_name = if forbidden_casks.include?(@cask.token)
|
|
@cask.token
|
|
elsif forbidden_casks.include?(@cask.full_name)
|
|
@cask.full_name
|
|
else
|
|
return
|
|
end
|
|
|
|
raise CaskCannotBeInstalledError.new(@cask, <<~EOS
|
|
The installation of #{cask_name} was forbidden by #{owner}
|
|
in `HOMEBREW_FORBIDDEN_CASKS`.#{owner_contact}
|
|
EOS
|
|
)
|
|
end
|
|
|
|
private
|
|
|
|
# load the same cask file that was used for installation, if possible
|
|
def load_installed_caskfile!
|
|
Migrator.migrate_if_needed(@cask)
|
|
|
|
installed_caskfile = @cask.installed_caskfile
|
|
|
|
if installed_caskfile&.exist?
|
|
begin
|
|
@cask = CaskLoader.load(installed_caskfile)
|
|
return
|
|
rescue CaskInvalidError
|
|
# could be caused by trying to load outdated caskfile
|
|
end
|
|
end
|
|
|
|
load_cask_from_source_api! if @cask.loaded_from_api? && @cask.caskfile_only?
|
|
# otherwise we default to the current cask
|
|
end
|
|
|
|
def load_cask_from_source_api!
|
|
@cask = Homebrew::API::Cask.source_download(@cask)
|
|
end
|
|
end
|
|
end
|