Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement brew cask upgrade #3396

Merged
merged 15 commits into from
Nov 27, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 28 additions & 3 deletions Library/Homebrew/cask/lib/hbc/artifact/moved.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def install_phase(**options)
end

def uninstall_phase(**options)
delete(**options)
move_back(**options)
end

def summarize_installed
Expand All @@ -30,7 +30,7 @@ def move(force: false, command: nil, **options)
message = "It seems there is already #{self.class.english_article} #{self.class.english_name} at '#{target}'"
raise CaskError, "#{message}." unless force
opoo "#{message}; overwriting."
delete(force: force, command: command, **options)
delete(target, force: force, command: command, **options)
end

unless source.exist?
Expand All @@ -49,7 +49,32 @@ def move(force: false, command: nil, **options)
add_altname_metadata(target, source.basename, command: command)
end

def delete(force: false, command: nil, **_)
def move_back(skip: false, force: false, command: nil, **options)
if Utils.path_occupied?(source)
message = "It seems there is already #{self.class.english_article} #{self.class.english_name} at '#{source}'"
raise CaskError, "#{message}." unless force
opoo "#{message}; overwriting."
delete(source, force: force, command: command, **options)
end

unless target.exist?
return if skip
raise CaskError, "It seems the #{self.class.english_name} source '#{target}' is not there."
end

ohai "Moving #{self.class.english_name} '#{target.basename}' back to '#{source}'."
source.dirname.mkpath

if source.parent.writable?
FileUtils.move(target, source)
else
command.run("/bin/mv", args: [target, source], sudo: true)
end

add_altname_metadata(target, source.basename, command: command)
end

def delete(target, force: false, command: nil, **_)
ohai "Removing #{self.class.english_name} '#{target}'."
raise CaskError, "Cannot remove undeletable #{self.class.english_name}." if MacOS.undeletable?(target)

Expand Down
1 change: 1 addition & 0 deletions Library/Homebrew/cask/lib/hbc/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
require "hbc/cli/search"
require "hbc/cli/style"
require "hbc/cli/uninstall"
require "hbc/cli/upgrade"
require "hbc/cli/--version"
require "hbc/cli/zap"

Expand Down
87 changes: 87 additions & 0 deletions Library/Homebrew/cask/lib/hbc/cli/upgrade.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
module Hbc
class CLI
class Upgrade < AbstractCommand
option "--greedy", :greedy, false
option "--quiet", :quiet, false
option "--force", :force, false
option "--skip-cask-deps", :skip_cask_deps, false

def initialize(*)
super
self.verbose = ($stdout.tty? || verbose?) && !quiet?
end

def run
outdated_casks = casks(alternative: lambda {
Hbc.installed.select do |cask|
cask.outdated?(greedy?)
end
}).select { |cask| cask.outdated?(true) }

if outdated_casks.empty?
oh1 "No Casks to upgrade"
return
end

oh1 "Upgrading #{Formatter.pluralize(outdated_casks.length, "outdated package")}, with result:"
puts outdated_casks.map { |f| "#{f.full_name} #{f.version}" } * ", "

outdated_casks.each do |old_cask|
odebug "Started upgrade process for Cask #{old_cask}"
raise CaskNotInstalledError, old_cask unless old_cask.installed? || force?

raise CaskUnavailableError.new(old_cask, "The Caskfile is missing!") if old_cask.installed_caskfile.nil?

old_cask = CaskLoader.load(old_cask.installed_caskfile)

old_cask_installer = Installer.new(old_cask, binaries: binaries?, verbose: verbose?, force: force?, upgrade: true)

new_cask = CaskLoader.load(old_cask.to_s)

new_cask_installer =
Installer.new(new_cask, binaries: binaries?,
verbose: verbose?,
force: force?,
skip_cask_deps: skip_cask_deps?,
require_sha: require_sha?,
upgrade: true)

started_upgrade = false
new_artifacts_installed = false

begin
# Start new Cask's installation steps
new_cask_installer.check_conflicts

new_cask_installer.fetch

new_cask_installer.stage

# Move the old Cask's artifacts back to staging
old_cask_installer.start_upgrade
# And flag it so in case of error
started_upgrade = true

# Install the new Cask
new_cask_installer.install_artifacts
new_artifacts_installed = true

new_cask_installer.enable_accessibility_access

# If successful, wipe the old Cask from staging
old_cask_installer.finalize_upgrade
rescue CaskError => e
new_cask_installer.uninstall_artifacts if new_artifacts_installed
new_cask_installer.purge_versioned_files
old_cask_installer.revert_upgrade if started_upgrade
raise e
end
end
end

def self.help
"upgrades all outdated casks"
end
end
end
end
42 changes: 31 additions & 11 deletions Library/Homebrew/cask/lib/hbc/installer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class Installer

PERSISTENT_METADATA_SUBDIRS = ["gpg"].freeze

def initialize(cask, command: SystemCommand, force: false, skip_cask_deps: false, binaries: true, verbose: false, require_sha: false)
def initialize(cask, command: SystemCommand, force: false, skip_cask_deps: false, binaries: true, verbose: false, require_sha: false, upgrade: false)
@cask = cask
@command = command
@force = force
Expand All @@ -28,9 +28,10 @@ def initialize(cask, command: SystemCommand, force: false, skip_cask_deps: false
@verbose = verbose
@require_sha = require_sha
@reinstall = false
@upgrade = upgrade
end

attr_predicate :binaries?, :force?, :skip_cask_deps?, :require_sha?, :verbose?
attr_predicate :binaries?, :force?, :skip_cask_deps?, :require_sha?, :upgrade?, :verbose?

def self.print_caveats(cask)
odebug "Printing caveats"
Expand Down Expand Up @@ -82,7 +83,7 @@ def stage
def install
odebug "Hbc::Installer#install"

if @cask.installed? && !force? && !@reinstall
if @cask.installed? && !force? && !@reinstall && !upgrade?
raise CaskAlreadyInstalledError, @cask
end

Expand Down Expand Up @@ -129,13 +130,13 @@ def uninstall_existing_cask
installed_cask = installed_caskfile.exist? ? CaskLoader.load(installed_caskfile) : @cask

# Always force uninstallation, ignore method parameter
Installer.new(installed_cask, binaries: binaries?, verbose: verbose?, force: true).uninstall
Installer.new(installed_cask, binaries: binaries?, verbose: verbose?, force: true, upgrade: upgrade?).uninstall
end

def summary
s = ""
s << "#{Emoji.install_badge} " if Emoji.enabled?
s << "#{@cask} was successfully installed!"
s << "#{@cask} was successfully #{upgrade? ? "upgraded" : "installed"}!"
end

def download
Expand Down Expand Up @@ -367,12 +368,31 @@ def save_caskfile
def uninstall
oh1 "Uninstalling Cask #{@cask}"
disable_accessibility_access
uninstall_artifacts
uninstall_artifacts(clear: true)
purge_versioned_files
purge_caskroom_path if force?
end

def uninstall_artifacts
def start_upgrade
oh1 "Starting upgrade for Cask #{@cask}"

disable_accessibility_access
uninstall_artifacts
end

def revert_upgrade
opoo "Reverting upgrade for Cask #{@cask}"
install_artifacts
enable_accessibility_access
end

def finalize_upgrade
purge_versioned_files

puts summary
end

def uninstall_artifacts(clear: false)
odebug "Un-installing artifacts"
artifacts = @cask.artifacts

Expand All @@ -381,7 +401,7 @@ def uninstall_artifacts
artifacts.each do |artifact|
next unless artifact.respond_to?(:uninstall_phase)
odebug "Un-installing artifact of class #{artifact.class}"
artifact.uninstall_phase(command: @command, verbose: verbose?, force: force?)
artifact.uninstall_phase(command: @command, verbose: verbose?, skip: clear, force: force?)
end
end

Expand All @@ -405,7 +425,7 @@ def gain_permissions_remove(path)
end

def purge_versioned_files
odebug "Purging files for version #{@cask.version} of Cask #{@cask}"
ohai "Purging files for version #{@cask.version} of Cask #{@cask}"

# versioned staged distribution
gain_permissions_remove(@cask.staged_path) if [email protected]_path.nil? && @cask.staged_path.exist?
Expand All @@ -420,10 +440,10 @@ def purge_versioned_files
end
end
@cask.metadata_versioned_path.rmdir_if_possible
@cask.metadata_master_container_path.rmdir_if_possible
@cask.metadata_master_container_path.rmdir_if_possible unless upgrade?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this check here necessary, since this is not rm_rf but only rmdir?

Copy link
Contributor Author

@amyspark amyspark Nov 16, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Line 445 clears the Cask's versioned metadata (which is what we need for finalize_upgrade), but the lines below delete the whole metadata folder (which we need for uninstall), and then purges the whole Cask's folder (which will break everything).

(edited for clarity and completeness)


# toplevel staged distribution
@cask.caskroom_path.rmdir_if_possible
@cask.caskroom_path.rmdir_if_possible unless upgrade?
end

def purge_caskroom_path
Expand Down
3 changes: 2 additions & 1 deletion Library/Homebrew/test/cask/cli/reinstall_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
Already downloaded: .*local-caffeine--1.2.3.zip
==> Verifying checksum for Cask local-caffeine
==> Uninstalling Cask local-caffeine
==> Removing App '.*Caffeine.app'.
==> Moving App 'Caffeine.app' back to '.*Caffeine.app'.
==> Purging files for version 1.2.3 of Cask local-caffeine
==> Installing Cask local-caffeine
==> Moving App 'Caffeine.app' to '.*Caffeine.app'.
.*local-caffeine was successfully installed!
Expand Down
3 changes: 2 additions & 1 deletion Library/Homebrew/test/cask/cli/uninstall_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@

output = Regexp.new <<~EOS
==> Uninstalling Cask local-caffeine
==> Removing App '.*Caffeine.app'.
==> Moving App 'Caffeine.app' back to '.*Caffeine.app'.
==> Purging files for version 1.2.3 of Cask local-caffeine
EOS

expect {
Expand Down
Loading