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
def initialize ( cask , command : SystemCommand , force : false , skip_cask_deps : false , require_sha : false )
@cask = cask
@command = command
@force = force
@skip_cask_deps = skip_cask_deps
@require_sha = require_sha
end
2016-08-18 22:11:42 +03: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
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
2016-10-01 23:18:13 +02:00
if @cask . installed? && ! force
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
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
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 = [ ]
options = { command : @command , force : force }
2016-09-24 13:52:43 +02:00
odebug " Installing artifacts "
artifacts = Artifact . for_cask ( @cask )
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 |
odebug " Installing artifact of class #{ artifact } "
artifact . new ( @cask , options ) . 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 |
odebug " Reverting installation of artifact of class #{ artifact } "
artifact . new ( @cask , options ) . uninstall_phase
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 } ... "
dep = Hbc . load ( dep_token )
if dep . installed?
puts " already installed "
else
Installer . new ( dep , force : false , skip_cask_deps : true ) . install
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
timestamp = :now
create = true
savedir = @cask . metadata_subdir ( " Casks " , timestamp , create )
if Dir . entries ( savedir ) . size > 2
# should not happen
raise CaskAlreadyInstalledError , @cask unless force
savedir . rmtree
FileUtils . mkdir_p savedir
end
FileUtils . copy ( @cask . sourcefile_path , savedir ) if @cask . sourcefile_path
2016-08-18 22:11:42 +03:00
end
2016-09-24 13:52:43 +02:00
def uninstall
2016-12-04 23:13:39 +01:00
odebug " Hbc::Installer # uninstall "
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 "
artifacts = Artifact . for_cask ( @cask )
odebug " #{ artifacts . length } artifact/s defined " , artifacts
artifacts . each do | artifact |
odebug " Un-installing artifact of class #{ artifact } "
options = { command : @command , force : force }
artifact . new ( @cask , options ) . uninstall_phase
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 "
Artifact :: Zap . new ( @cask , command : @command ) . zap_phase
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
if @cask . metadata_versioned_container_path . respond_to? ( :children ) &&
@cask . metadata_versioned_container_path . exist?
@cask . metadata_versioned_container_path . children . each do | subdir |
unless PERSISTENT_METADATA_SUBDIRS . include? ( subdir . basename )
gain_permissions_remove ( subdir )
end
2016-08-18 22:11:42 +03:00
end
end
2016-09-24 13:52:43 +02:00
@cask . metadata_versioned_container_path . rmdir_if_possible
@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