2016-08-18 22:11:42 +03:00
require " rubygems "
2017-06-28 17:53:59 +02:00
require " formula_installer "
2016-08-18 22:11:42 +03:00
require " hbc/cask_dependencies "
require " hbc/staged "
require " hbc/verify "
2016-09-24 13:52:43 +02:00
module Hbc
class Installer
2017-06-26 07:30:28 +02:00
extend Predicable
2016-09-24 13:52:43 +02:00
# TODO: it is unwise for Hbc::Staged to be a module, when we are
# dealing with both staged and unstaged Casks here. This should
# either be a class which is only sometimes instantiated, or there
# should be explicit checks on whether staged state is valid in
# every method.
include Staged
include Verify
PERSISTENT_METADATA_SUBDIRS = [ " gpg " ] . freeze
2017-10-29 17:31:07 -03:00
def initialize ( cask , command : SystemCommand , force : false , skip_cask_deps : false , binaries : true , verbose : false , require_sha : false , upgrade : false )
2016-09-24 13:52:43 +02:00
@cask = cask
@command = command
@force = force
@skip_cask_deps = skip_cask_deps
2017-05-19 19:13:23 +02:00
@binaries = binaries
2017-05-21 00:15:56 +02:00
@verbose = verbose
2016-09-24 13:52:43 +02:00
@require_sha = require_sha
2017-03-27 01:31:29 -05:00
@reinstall = false
2017-10-29 17:31:07 -03:00
@upgrade = upgrade
2016-09-24 13:52:43 +02:00
end
2016-08-18 22:11:42 +03:00
2017-11-06 18:33:29 -03:00
attr_predicate :binaries? , :force? , :skip_cask_deps? , :require_sha? , :upgrade? , :verbose?
2017-05-21 00:15:56 +02:00
2016-09-24 13:52:43 +02:00
def self . print_caveats ( cask )
odebug " Printing caveats "
2016-10-24 17:07:57 +02:00
2017-11-24 17:44:01 +01:00
caveats = cask . caveats
return if caveats . empty?
2016-10-24 17:07:57 +02:00
ohai " Caveats "
2017-11-24 17:44:01 +01:00
puts caveats + " \n "
2016-09-24 13:52:43 +02:00
end
2016-08-18 22:11:42 +03:00
2016-12-04 23:13:39 +01:00
def fetch
odebug " Hbc::Installer # fetch "
satisfy_dependencies
2017-06-26 07:30:28 +02:00
verify_has_sha if require_sha? && ! force?
2016-12-04 23:13:39 +01:00
download
verify
end
def stage
odebug " Hbc::Installer # stage "
extract_primary_container
save_caskfile
rescue StandardError = > e
purge_versioned_files
raise e
end
2016-09-24 13:52:43 +02:00
def install
2016-12-04 23:13:39 +01:00
odebug " Hbc::Installer # install "
2016-08-18 22:11:42 +03:00
2017-11-16 10:40:32 -03:00
if @cask . installed? && ! force? && ! @reinstall && ! upgrade?
raise CaskAlreadyInstalledError , @cask
2016-09-24 13:52:43 +02:00
end
2016-08-18 22:11:42 +03:00
2017-08-05 15:56:34 +02:00
check_conflicts
2016-09-24 13:52:43 +02:00
print_caveats
2016-12-04 23:13:39 +01:00
fetch
2017-04-17 17:21:02 -07:00
uninstall_existing_cask if @reinstall
2017-03-18 18:57:04 -05:00
oh1 " Installing Cask #{ @cask } "
2016-12-04 23:13:39 +01:00
stage
install_artifacts
enable_accessibility_access
2016-08-18 22:11:42 +03:00
2016-09-24 13:52:43 +02:00
puts summary
end
2016-08-18 22:11:42 +03:00
2017-08-05 15:56:34 +02:00
def check_conflicts
return unless @cask . conflicts_with
@cask . conflicts_with . cask . each do | conflicting_cask |
begin
conflicting_cask = CaskLoader . load ( conflicting_cask )
if conflicting_cask . installed?
raise CaskConflictError . new ( @cask , conflicting_cask )
end
rescue CaskUnavailableError
next # Ignore conflicting Casks that do not exist.
end
end
end
2017-03-27 01:31:29 -05:00
def reinstall
odebug " Hbc::Installer # reinstall "
@reinstall = true
install
end
2017-04-17 17:21:02 -07:00
def uninstall_existing_cask
return unless @cask . installed?
2017-03-18 17:48:20 -05:00
# use the same cask file that was used for installation, if possible
2017-04-17 17:21:02 -07:00
installed_caskfile = @cask . installed_caskfile
2017-10-07 15:58:49 +02:00
installed_cask = installed_caskfile . exist? ? CaskLoader . load ( installed_caskfile ) : @cask
2017-03-18 17:48:20 -05:00
# Always force uninstallation, ignore method parameter
2017-11-06 18:33:29 -03:00
Installer . new ( installed_cask , binaries : binaries? , verbose : verbose? , force : true , upgrade : upgrade? ) . uninstall
2017-03-18 17:48:20 -05:00
end
2016-09-24 13:52:43 +02:00
def summary
2016-12-04 21:52:30 +01:00
s = " "
s << " #{ Emoji . install_badge } " if Emoji . enabled?
2017-11-06 21:27:02 -03:00
s << " #{ @cask } was successfully #{ upgrade? ? " upgraded " : " installed " } ! "
2016-08-18 22:11:42 +03:00
end
2016-09-24 13:52:43 +02:00
def download
odebug " Downloading "
2016-12-04 23:13:39 +01:00
@downloaded_path = Download . new ( @cask , force : false ) . perform
2016-09-24 13:52:43 +02:00
odebug " Downloaded to -> #{ @downloaded_path } "
@downloaded_path
end
2016-08-18 22:11:42 +03:00
2016-09-24 13:52:43 +02:00
def verify_has_sha
odebug " Checking cask has checksum "
return unless @cask . sha256 == :no_check
2017-06-11 02:00:59 +02:00
raise CaskNoShasumError , @cask . token
2016-09-24 13:52:43 +02:00
end
2016-08-18 22:11:42 +03:00
2016-09-24 13:52:43 +02:00
def verify
Verify . all ( @cask , @downloaded_path )
end
2016-08-18 22:11:42 +03:00
2016-09-24 13:52:43 +02:00
def extract_primary_container
odebug " Extracting primary container "
2016-12-04 23:13:39 +01:00
2016-09-24 13:52:43 +02:00
FileUtils . mkdir_p @cask . staged_path
2017-09-24 19:24:46 +01:00
container = if @cask . container & . type
2016-10-14 20:11:33 +02:00
Container . from_type ( @cask . container . type )
else
Container . for_path ( @downloaded_path , @command )
end
2016-12-04 23:13:39 +01:00
2016-09-24 13:52:43 +02:00
unless container
raise CaskError , " Uh oh, could not figure out how to unpack ' #{ @downloaded_path } ' "
end
2016-12-04 23:13:39 +01:00
2016-09-24 13:52:43 +02:00
odebug " Using container class #{ container } for #{ @downloaded_path } "
2017-06-11 04:41:43 +02:00
container . new ( @cask , @downloaded_path , @command , verbose : verbose? ) . extract
2016-09-24 13:52:43 +02:00
end
2016-08-18 22:11:42 +03:00
2016-09-24 13:52:43 +02:00
def install_artifacts
2016-12-04 23:14:35 +01:00
already_installed_artifacts = [ ]
2016-09-24 13:52:43 +02:00
odebug " Installing artifacts "
2017-10-04 17:08:35 +02:00
artifacts = @cask . artifacts
2016-09-24 13:52:43 +02:00
odebug " #{ artifacts . length } artifact/s defined " , artifacts
2016-12-04 23:14:35 +01:00
2016-09-24 13:52:43 +02:00
artifacts . each do | artifact |
2017-02-05 07:47:54 +01:00
next unless artifact . respond_to? ( :install_phase )
2017-02-05 23:27:35 +01:00
odebug " Installing artifact of class #{ artifact . class } "
2017-05-19 19:13:23 +02:00
if artifact . is_a? ( Artifact :: Binary )
next unless binaries?
end
2017-04-06 00:33:31 +02:00
artifact . install_phase ( command : @command , verbose : verbose? , force : force? )
2017-01-23 14:19:14 +01:00
already_installed_artifacts . unshift ( artifact )
2016-09-24 13:52:43 +02:00
end
2016-12-04 23:14:35 +01:00
rescue StandardError = > e
begin
already_installed_artifacts . each do | artifact |
2017-02-05 07:47:54 +01:00
next unless artifact . respond_to? ( :uninstall_phase )
2017-02-05 23:27:35 +01:00
odebug " Reverting installation of artifact of class #{ artifact . class } "
2017-04-06 00:33:31 +02:00
artifact . uninstall_phase ( command : @command , verbose : verbose? , force : force? )
2016-12-04 23:14:35 +01:00
end
ensure
purge_versioned_files
2016-12-16 18:22:04 +01:00
raise e
2016-12-04 23:14:35 +01:00
end
2016-09-24 13:52:43 +02:00
end
2016-08-18 22:11:42 +03:00
2016-09-24 13:52:43 +02:00
# TODO: move dependencies to a separate class
# dependencies should also apply for "brew cask stage"
# override dependencies with --force or perhaps --force-deps
def satisfy_dependencies
return unless @cask . depends_on
ohai " Satisfying dependencies "
macos_dependencies
arch_dependencies
x11_dependencies
formula_dependencies
2017-05-21 00:15:56 +02:00
cask_dependencies unless skip_cask_deps?
2016-08-18 22:11:42 +03:00
end
2016-09-24 13:52:43 +02:00
def macos_dependencies
return unless @cask . depends_on . macos
if @cask . depends_on . macos . first . is_a? ( Array )
operator , release = @cask . depends_on . macos . first
unless MacOS . version . send ( operator , release )
raise CaskError , " Cask #{ @cask } depends on macOS release #{ operator } #{ release } , but you are running release #{ MacOS . version } . "
end
elsif @cask . depends_on . macos . length > 1
unless @cask . depends_on . macos . include? ( Gem :: Version . new ( MacOS . version . to_s ) )
raise CaskError , " Cask #{ @cask } depends on macOS release being one of [ #{ @cask . depends_on . macos . map ( & :to_s ) . join ( " , " ) } ], but you are running release #{ MacOS . version } . "
end
else
unless MacOS . version == @cask . depends_on . macos . first
raise CaskError , " Cask #{ @cask } depends on macOS release #{ @cask . depends_on . macos . first } , but you are running release #{ MacOS . version } . "
end
end
2016-08-18 22:11:42 +03:00
end
2016-09-24 13:52:43 +02:00
def arch_dependencies
return if @cask . depends_on . arch . nil?
@current_arch || = { type : Hardware :: CPU . type , bits : Hardware :: CPU . bits }
2016-10-23 14:44:14 +02:00
return if @cask . depends_on . arch . any? do | arch |
2016-09-24 13:52:43 +02:00
arch [ :type ] == @current_arch [ :type ] &&
Array ( arch [ :bits ] ) . include? ( @current_arch [ :bits ] )
2016-10-23 14:44:14 +02:00
end
2016-09-24 13:52:43 +02:00
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
2016-08-18 22:11:42 +03:00
2016-09-24 13:52:43 +02:00
def x11_dependencies
return unless @cask . depends_on . x11
2016-12-04 21:35:43 +01:00
raise CaskX11DependencyError , @cask . token unless MacOS :: X11 . installed?
2016-09-24 13:52:43 +02:00
end
def formula_dependencies
2017-06-28 17:53:59 +02:00
formulae = @cask . depends_on . formula . map { | f | Formula [ f ] }
return if formulae . empty?
if formulae . all? ( & :any_version_installed? )
puts " All Formula dependencies satisfied. "
return
end
not_installed = formulae . reject ( & :any_version_installed? )
ohai " Installing Formula dependencies: #{ not_installed . map ( & :to_s ) . join ( " , " ) } "
not_installed . each do | formula |
begin
old_argv = ARGV . dup
ARGV . replace ( [ ] )
FormulaInstaller . new ( formula ) . tap do | fi |
fi . installed_as_dependency = true
fi . installed_on_request = false
fi . show_header = true
fi . verbose = verbose?
fi . prelude
fi . install
fi . finish
end
ensure
ARGV . replace ( old_argv )
2016-09-24 13:52:43 +02:00
end
2016-08-18 22:11:42 +03:00
end
end
2016-09-24 13:52:43 +02:00
def cask_dependencies
2017-06-28 17:53:59 +02:00
return if @cask . depends_on . cask . empty?
casks = CaskDependencies . new ( @cask )
if casks . all? ( & :installed? )
puts " All Cask dependencies satisfied. "
return
end
not_installed = casks . reject ( & :installed? )
ohai " Installing Cask dependencies: #{ not_installed . map ( & :to_s ) . join ( " , " ) } "
not_installed . each do | cask |
Installer . new ( cask , binaries : binaries? , verbose : verbose? , skip_cask_deps : true , force : false ) . install
2016-09-24 13:52:43 +02:00
end
end
2016-08-18 22:11:42 +03:00
2016-09-24 13:52:43 +02:00
def print_caveats
self . class . print_caveats ( @cask )
end
2016-08-18 22:11:42 +03:00
2016-09-24 13:52:43 +02:00
# TODO: logically could be in a separate class
def enable_accessibility_access
return unless @cask . accessibility_access
ohai " Enabling accessibility access "
if MacOS . version < = :mountain_lion
@command . run! ( " /usr/bin/touch " ,
args : [ Hbc . pre_mavericks_accessibility_dotfile ] ,
sudo : true )
elsif MacOS . version < = :yosemite
@command . run! ( " /usr/bin/sqlite3 " ,
args : [
2016-10-14 20:33:16 +02:00
Hbc . tcc_db ,
" INSERT OR REPLACE INTO access VALUES('kTCCServiceAccessibility',' #{ bundle_identifier } ',0,1,1,NULL); " ,
] ,
2016-09-24 13:52:43 +02:00
sudo : true )
elsif MacOS . version < = :el_capitan
@command . run! ( " /usr/bin/sqlite3 " ,
args : [
2016-10-14 20:33:16 +02:00
Hbc . tcc_db ,
" INSERT OR REPLACE INTO access VALUES('kTCCServiceAccessibility',' #{ bundle_identifier } ',0,1,1,NULL,NULL); " ,
] ,
2016-09-24 13:52:43 +02:00
sudo : true )
2016-08-18 22:11:42 +03:00
else
2017-10-15 02:28:32 +02:00
opoo << ~ EOS
2016-09-24 13:52:43 +02:00
Accessibility access cannot be enabled automatically on this version of macOS .
See System Preferences to enable it manually .
EOS
2016-08-18 22:11:42 +03:00
end
2016-12-04 23:13:39 +01:00
rescue StandardError = > e
purge_versioned_files
raise e
2016-08-18 22:11:42 +03:00
end
2016-09-24 13:52:43 +02:00
def disable_accessibility_access
return unless @cask . accessibility_access
if MacOS . version > = :mavericks && MacOS . version < = :el_capitan
ohai " Disabling accessibility access "
@command . run! ( " /usr/bin/sqlite3 " ,
args : [
2016-10-14 20:33:16 +02:00
Hbc . tcc_db ,
" DELETE FROM access WHERE client=' #{ bundle_identifier } '; " ,
] ,
2016-09-24 13:52:43 +02:00
sudo : true )
2016-08-18 22:11:42 +03:00
else
2017-10-15 02:28:32 +02:00
opoo << ~ EOS
2016-09-24 13:52:43 +02:00
Accessibility access cannot be disabled automatically on this version of macOS .
See System Preferences to disable it manually .
EOS
2016-08-18 22:11:42 +03:00
end
end
2016-09-24 13:52:43 +02:00
def save_caskfile
2017-04-20 10:48:32 +02:00
old_savedir = @cask . metadata_timestamped_path
2017-03-11 18:55:16 +01:00
return unless @cask . sourcefile_path
2017-04-20 10:48:32 +02:00
savedir = @cask . metadata_subdir ( " Casks " , timestamp : :now , create : true )
2017-03-11 18:55:16 +01:00
FileUtils . copy @cask . sourcefile_path , savedir
2017-09-24 19:24:46 +01:00
old_savedir & . rmtree
2016-08-18 22:11:42 +03:00
end
2016-09-24 13:52:43 +02:00
def uninstall
2017-03-18 18:57:04 -05:00
oh1 " Uninstalling Cask #{ @cask } "
2016-09-24 13:52:43 +02:00
disable_accessibility_access
2017-11-10 10:05:18 -03:00
uninstall_artifacts ( clear : true )
2017-10-29 17:31:07 -03:00
purge_versioned_files
purge_caskroom_path if force?
end
2017-10-30 23:29:00 -03:00
def start_upgrade
oh1 " Starting upgrade for Cask #{ @cask } "
2017-10-29 17:31:07 -03:00
2017-10-30 23:29:00 -03:00
disable_accessibility_access
uninstall_artifacts
2017-11-28 17:37:36 +00:00
backup
2017-11-28 13:00:08 +00:00
end
def backup
2017-11-28 17:37:36 +00:00
@cask . staged_path . rename backup_path
2017-11-28 21:00:16 +00:00
@cask . metadata_versioned_path . rename backup_metadata_path
2017-11-28 13:00:08 +00:00
end
def restore_backup
2017-11-28 21:00:16 +00:00
return unless backup_path . directory? && backup_metadata_path . directory?
2017-11-28 13:00:08 +00:00
Pathname . new ( @cask . staged_path ) . rmtree if @cask . staged_path . exist?
2017-11-28 21:00:16 +00:00
Pathname . new ( @cask . metadata_versioned_path ) . rmtree if @cask . metadata_versioned_path . exist?
2017-11-28 13:00:08 +00:00
2017-11-28 17:37:36 +00:00
backup_path . rename @cask . staged_path
2017-11-28 21:00:16 +00:00
backup_metadata_path . rename @cask . metadata_versioned_path
2017-10-30 23:29:00 -03:00
end
def revert_upgrade
opoo " Reverting upgrade for Cask #{ @cask } "
2017-11-28 17:37:36 +00:00
restore_backup
2017-11-06 21:27:02 -03:00
install_artifacts
enable_accessibility_access
2017-10-30 23:29:00 -03:00
end
def finalize_upgrade
2017-11-28 20:36:36 +00:00
purge_backed_up_versioned_files
2017-11-11 17:21:13 -03:00
puts summary
2016-08-18 22:11:42 +03:00
end
2017-11-10 10:05:18 -03:00
def uninstall_artifacts ( clear : false )
2016-09-24 13:52:43 +02:00
odebug " Un-installing artifacts "
2017-10-04 17:08:35 +02:00
artifacts = @cask . artifacts
2017-02-16 17:10:40 +01:00
2016-09-24 13:52:43 +02:00
odebug " #{ artifacts . length } artifact/s defined " , artifacts
2017-02-16 17:10:40 +01:00
2016-09-24 13:52:43 +02:00
artifacts . each do | artifact |
2017-02-05 07:47:54 +01:00
next unless artifact . respond_to? ( :uninstall_phase )
2017-02-05 23:27:35 +01:00
odebug " Un-installing artifact of class #{ artifact . class } "
2017-11-10 10:05:18 -03:00
artifact . uninstall_phase ( command : @command , verbose : verbose? , skip : clear , force : force? )
2016-09-24 13:52:43 +02:00
end
2016-08-18 22:11:42 +03:00
end
2016-09-24 13:52:43 +02:00
def zap
2016-10-14 20:08:05 +02:00
ohai %Q( Implied "brew cask uninstall #{ @cask } " )
2016-09-24 13:52:43 +02:00
uninstall_artifacts
2017-10-04 17:08:35 +02:00
if ( zap_stanzas = @cask . artifacts . select { | a | a . is_a? ( Artifact :: Zap ) } ) . empty?
2016-09-24 13:52:43 +02:00
opoo " No zap stanza present for Cask ' #{ @cask } ' "
2017-04-06 00:33:31 +02:00
else
ohai " Dispatching zap stanza "
zap_stanzas . each do | stanza |
stanza . zap_phase ( command : @command , verbose : verbose? , force : force? )
end
2016-09-24 13:52:43 +02:00
end
ohai " Removing all staged versions of Cask ' #{ @cask } ' "
purge_caskroom_path
2016-08-18 22:11:42 +03:00
end
2017-11-28 17:37:36 +00:00
def backup_path
return nil if @cask . staged_path . nil?
2017-11-28 18:03:57 +00:00
Pathname . new " #{ @cask . staged_path } .upgrading "
2017-11-28 13:00:08 +00:00
end
2017-11-28 21:00:16 +00:00
def backup_metadata_path
return nil if @cask . metadata_versioned_path . nil?
Pathname . new " #{ @cask . metadata_versioned_path } .upgrading "
end
2016-09-24 13:52:43 +02:00
def gain_permissions_remove ( path )
Utils . gain_permissions_remove ( path , command : @command )
2016-08-18 22:11:42 +03:00
end
2017-11-28 20:36:36 +00:00
def purge_backed_up_versioned_files
2017-11-10 10:05:18 -03:00
ohai " Purging files for version #{ @cask . version } of Cask #{ @cask } "
2016-08-18 22:11:42 +03:00
2016-09-24 13:52:43 +02:00
# versioned staged distribution
2017-11-28 18:03:57 +00:00
gain_permissions_remove ( backup_path ) if ! backup_path . nil? && backup_path . exist?
2017-11-28 13:00:08 +00:00
2017-11-28 18:03:57 +00:00
# Homebrew-Cask metadata
2017-11-29 01:11:30 +00:00
if backup_metadata_path . directory?
2017-11-29 01:02:50 +00:00
backup_metadata_path . children . each do | subdir |
unless PERSISTENT_METADATA_SUBDIRS . include? ( subdir . basename )
gain_permissions_remove ( subdir )
end
end
2017-11-28 21:00:16 +00:00
end
backup_metadata_path . rmdir_if_possible
2017-11-28 18:03:57 +00:00
end
2017-11-10 10:05:18 -03:00
def purge_versioned_files
ohai " Purging files for version #{ @cask . version } of Cask #{ @cask } "
2016-08-18 22:11:42 +03:00
2016-09-24 13:52:43 +02:00
# versioned staged distribution
gain_permissions_remove ( @cask . staged_path ) if ! @cask . staged_path . nil? && @cask . staged_path . exist?
2016-08-18 22:11:42 +03:00
2016-09-24 13:52:43 +02:00
# Homebrew-Cask metadata
2017-04-20 10:48:32 +02:00
if @cask . metadata_versioned_path . respond_to? ( :children ) &&
@cask . metadata_versioned_path . exist?
@cask . metadata_versioned_path . children . each do | subdir |
2016-09-24 13:52:43 +02:00
unless PERSISTENT_METADATA_SUBDIRS . include? ( subdir . basename )
gain_permissions_remove ( subdir )
end
2016-08-18 22:11:42 +03:00
end
end
2017-04-20 10:48:32 +02:00
@cask . metadata_versioned_path . rmdir_if_possible
2017-11-07 22:27:04 -03:00
@cask . metadata_master_container_path . rmdir_if_possible unless upgrade?
2016-08-18 22:11:42 +03:00
2016-09-24 13:52:43 +02:00
# toplevel staged distribution
2017-11-07 22:27:04 -03:00
@cask . caskroom_path . rmdir_if_possible unless upgrade?
2016-09-24 13:52:43 +02:00
end
2016-08-18 22:11:42 +03:00
2016-09-24 13:52:43 +02:00
def purge_caskroom_path
odebug " Purging all staged versions of Cask #{ @cask } "
gain_permissions_remove ( @cask . caskroom_path )
end
2016-08-18 22:11:42 +03:00
end
end