2016-08-18 22:11:42 +03:00
require " rubygems "
require " hbc/cask_dependencies "
require " hbc/staged "
require " hbc/verify "
2016-09-24 13:52:43 +02:00
module Hbc
class Installer
# 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
attr_reader :force , :skip_cask_deps
PERSISTENT_METADATA_SUBDIRS = [ " gpg " ] . freeze
2017-05-19 19:13:23 +02:00
def initialize ( cask , command : SystemCommand , force : false , skip_cask_deps : false , binaries : true , require_sha : 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
2016-09-24 13:52:43 +02:00
@require_sha = require_sha
2017-03-27 01:31:29 -05:00
@reinstall = false
2016-09-24 13:52:43 +02:00
end
2016-08-18 22:11:42 +03:00
2017-05-19 19:13:23 +02:00
def binaries?
@binaries
end
2016-09-24 13:52:43 +02:00
def self . print_caveats ( cask )
odebug " Printing caveats "
2016-10-24 17:07:57 +02:00
return if cask . caveats . empty?
output = capture_output do
cask . caveats . each do | caveat |
if caveat . respond_to? ( :eval_and_print )
caveat . eval_and_print ( cask )
else
puts caveat
2016-08-18 22:11:42 +03:00
end
end
end
2016-10-24 17:07:57 +02:00
return if output . empty?
ohai " Caveats "
puts output
2016-08-18 22:11:42 +03:00
end
2016-09-24 13:52:43 +02:00
def self . capture_output ( & block )
old_stdout = $stdout
$stdout = Buffer . new ( $stdout . tty? )
block . call
output = $stdout . string
$stdout = old_stdout
output
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
verify_has_sha if @require_sha && ! @force
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-03-18 17:48:20 -05:00
if @cask . installed? && ! force && ! @reinstall
2016-10-01 23:18:13 +02:00
raise CaskAlreadyInstalledAutoUpdatesError , @cask if @cask . auto_updates
raise CaskAlreadyInstalledError , @cask
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
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-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
installed_cask = installed_caskfile . exist? ? CaskLoader . load_from_file ( installed_caskfile ) : @cask
2017-03-18 17:48:20 -05:00
# Always force uninstallation, ignore method parameter
2017-05-19 19:13:23 +02:00
Installer . new ( installed_cask , binaries : binaries? , force : true ) . 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?
2016-09-24 13:52:43 +02:00
s << " #{ @cask } was successfully 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
raise CaskNoShasumError , @cask
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
container = if @cask . container && @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 } "
container . new ( @cask , @downloaded_path , @command ) . extract
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-02-05 23:27:35 +01:00
artifacts = Artifact . for_cask ( @cask , command : @command , force : force )
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-02-05 07:47:54 +01:00
artifact . install_phase
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-02-05 07:47:54 +01:00
artifact . uninstall_phase
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
cask_dependencies unless skip_cask_deps
puts " complete "
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
return unless @cask . depends_on . formula && ! @cask . depends_on . formula . empty?
ohai " Installing Formula dependencies from Homebrew "
@cask . depends_on . formula . each do | dep_name |
print " #{ dep_name } ... "
2016-11-10 14:48:53 +01:00
installed = @command . run ( HOMEBREW_BREW_FILE ,
2016-09-24 13:52:43 +02:00
args : [ " list " , " --versions " , dep_name ] ,
print_stderr : false ) . stdout . include? ( dep_name )
if installed
puts " already installed "
else
2016-11-10 14:48:53 +01:00
@command . run! ( HOMEBREW_BREW_FILE ,
2016-09-24 13:52:43 +02:00
args : [ " install " , dep_name ] )
puts " done "
end
2016-08-18 22:11:42 +03:00
end
end
2016-09-24 13:52:43 +02:00
def cask_dependencies
return unless @cask . depends_on . cask && ! @cask . depends_on . cask . empty?
ohai " Installing Cask dependencies: #{ @cask . depends_on . cask . join ( " , " ) } "
deps = CaskDependencies . new ( @cask )
deps . sorted . each do | dep_token |
puts " #{ dep_token } ... "
2017-03-13 01:09:36 +01:00
dep = CaskLoader . load ( dep_token )
2016-09-24 13:52:43 +02:00
if dep . installed?
puts " already installed "
else
2017-05-19 19:13:23 +02:00
Installer . new ( dep , force : false , binaries : binaries? , skip_cask_deps : true ) . install
2016-09-24 13:52:43 +02:00
puts " done "
end
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
2016-09-24 13:52:43 +02:00
opoo <<-EOS.undent
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
2016-09-24 13:52:43 +02:00
opoo <<-EOS.undent
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-04-20 10:48:32 +02:00
old_savedir . rmtree unless old_savedir . nil?
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
uninstall_artifacts
purge_versioned_files
purge_caskroom_path if force
2016-08-18 22:11:42 +03:00
end
2016-09-24 13:52:43 +02:00
def uninstall_artifacts
odebug " Un-installing artifacts "
2017-02-05 23:27:35 +01:00
artifacts = Artifact . for_cask ( @cask , command : @command , force : force )
2017-02-16 17:10:40 +01:00
# Make sure the `uninstall` stanza is run first, as it
# may depend on other artifacts still being installed.
artifacts = artifacts . sort_by { | a | a . is_a? ( Artifact :: Uninstall ) ? - 1 : 1 }
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-02-05 07:47:54 +01:00
artifact . uninstall_phase
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
if Artifact :: Zap . me? ( @cask )
ohai " Dispatching zap stanza "
2017-02-10 07:49:21 +01:00
Artifact :: Zap . new ( @cask , command : @command ) . zap_phase
2016-09-24 13:52:43 +02:00
else
opoo " No zap stanza present for Cask ' #{ @cask } ' "
end
ohai " Removing all staged versions of Cask ' #{ @cask } ' "
purge_caskroom_path
2016-08-18 22:11:42 +03:00
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
2016-09-24 13:52:43 +02:00
def purge_versioned_files
odebug " 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
2016-09-24 13:52:43 +02:00
@cask . metadata_master_container_path . rmdir_if_possible
2016-08-18 22:11:42 +03:00
2016-09-24 13:52:43 +02:00
# toplevel staged distribution
@cask . caskroom_path . rmdir_if_possible
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