Merge pull request #15138 from JBYoshi/cask-move-contents
Don't remove cask directories when upgrading.
This commit is contained in:
commit
0e387fee8c
@ -285,8 +285,8 @@ module Cask
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def uninstall_login_item(*login_items, command: nil, upgrade: false, **_)
|
def uninstall_login_item(*login_items, command: nil, successor: nil, **_)
|
||||||
return if upgrade
|
return if successor
|
||||||
|
|
||||||
apps = cask.artifacts.select { |a| a.class.dsl_key == :app }
|
apps = cask.artifacts.select { |a| a.class.dsl_key == :app }
|
||||||
derived_login_items = apps.map { |a| { path: a.target } }
|
derived_login_items = apps.map { |a| { path: a.target } }
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require "cask/artifact/relocated"
|
require "cask/artifact/relocated"
|
||||||
|
require "cask/quarantine"
|
||||||
|
|
||||||
module Cask
|
module Cask
|
||||||
module Artifact
|
module Artifact
|
||||||
@ -32,39 +33,51 @@ module Cask
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def move(adopt: false, force: false, verbose: false, command: nil, **options)
|
def move(adopt: false, force: false, verbose: false, predecessor: nil, reinstall: false,
|
||||||
|
command: nil, **options)
|
||||||
unless source.exist?
|
unless source.exist?
|
||||||
raise CaskError, "It seems the #{self.class.english_name} source '#{source}' is not there."
|
raise CaskError, "It seems the #{self.class.english_name} source '#{source}' is not there."
|
||||||
end
|
end
|
||||||
|
|
||||||
if Utils.path_occupied?(target)
|
if Utils.path_occupied?(target)
|
||||||
if adopt
|
if target.directory? && target.children.empty? && matching_artifact?(predecessor)
|
||||||
ohai "Adopting existing #{self.class.english_name} at '#{target}'"
|
# An upgrade removed the directory contents but left the directory itself (see below).
|
||||||
same = command.run(
|
unless source.directory?
|
||||||
"/usr/bin/diff",
|
if target.parent.writable? && !force
|
||||||
args: ["--recursive", "--brief", source, target],
|
target.rmdir
|
||||||
verbose: verbose,
|
else
|
||||||
print_stdout: verbose,
|
Utils.gain_permissions_remove(target, command: command)
|
||||||
).success?
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if adopt
|
||||||
|
ohai "Adopting existing #{self.class.english_name} at '#{target}'"
|
||||||
|
same = command.run(
|
||||||
|
"/usr/bin/diff",
|
||||||
|
args: ["--recursive", "--brief", source, target],
|
||||||
|
verbose: verbose,
|
||||||
|
print_stdout: verbose,
|
||||||
|
).success?
|
||||||
|
|
||||||
unless same
|
unless same
|
||||||
raise CaskError,
|
raise CaskError,
|
||||||
"It seems the existing #{self.class.english_name} is different from " \
|
"It seems the existing #{self.class.english_name} is different from " \
|
||||||
"the one being installed."
|
"the one being installed."
|
||||||
|
end
|
||||||
|
|
||||||
|
# Remove the source as we don't need to move it to the target location
|
||||||
|
source.rmtree
|
||||||
|
|
||||||
|
return post_move(command)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Remove the source as we don't need to move it to the target location
|
message = "It seems there is already #{self.class.english_article} " \
|
||||||
source.rmtree
|
"#{self.class.english_name} at '#{target}'"
|
||||||
|
raise CaskError, "#{message}." unless force
|
||||||
|
|
||||||
return post_move(command)
|
opoo "#{message}; overwriting."
|
||||||
|
delete(target, force: force, command: command, **options)
|
||||||
end
|
end
|
||||||
|
|
||||||
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(target, force: force, command: command, **options)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
ohai "Moving #{self.class.english_name} '#{source.basename}' to '#{target}'"
|
ohai "Moving #{self.class.english_name} '#{source.basename}' to '#{target}'"
|
||||||
@ -77,7 +90,16 @@ module Cask
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if target.dirname.writable?
|
if target.directory?
|
||||||
|
if target.writable?
|
||||||
|
source.children.each { |child| FileUtils.move(child, target + child.basename) }
|
||||||
|
else
|
||||||
|
command.run!("/bin/cp", args: ["-pR", "#{source}/*", "#{source}/.*", "#{target}/"],
|
||||||
|
sudo: true)
|
||||||
|
end
|
||||||
|
Quarantine.copy_xattrs(source, target)
|
||||||
|
source.rmtree
|
||||||
|
elsif target.dirname.writable?
|
||||||
FileUtils.move(source, target)
|
FileUtils.move(source, target)
|
||||||
else
|
else
|
||||||
# default sudo user isn't necessarily able to write to Homebrew's locations
|
# default sudo user isn't necessarily able to write to Homebrew's locations
|
||||||
@ -96,6 +118,14 @@ module Cask
|
|||||||
add_altname_metadata(target, source.basename, command: command)
|
add_altname_metadata(target, source.basename, command: command)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def matching_artifact?(cask)
|
||||||
|
return false unless cask
|
||||||
|
|
||||||
|
cask.artifacts.any? do |a|
|
||||||
|
a.instance_of?(self.class) && instance_of?(a.class) && a.target == target
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def move_back(skip: false, force: false, command: nil, **options)
|
def move_back(skip: false, force: false, command: nil, **options)
|
||||||
FileUtils.rm source if source.symlink? && source.dirname.join(source.readlink) == target
|
FileUtils.rm source if source.symlink? && source.dirname.join(source.readlink) == target
|
||||||
|
|
||||||
@ -123,13 +153,23 @@ module Cask
|
|||||||
delete(target, force: force, command: command, **options)
|
delete(target, force: force, command: command, **options)
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete(target, force: false, command: nil, **_)
|
def delete(target, force: false, successor: nil, command: nil, **_)
|
||||||
ohai "Removing #{self.class.english_name} '#{target}'"
|
ohai "Removing #{self.class.english_name} '#{target}'"
|
||||||
raise CaskError, "Cannot remove undeletable #{self.class.english_name}." if MacOS.undeletable?(target)
|
raise CaskError, "Cannot remove undeletable #{self.class.english_name}." if MacOS.undeletable?(target)
|
||||||
|
|
||||||
return unless Utils.path_occupied?(target)
|
return unless Utils.path_occupied?(target)
|
||||||
|
|
||||||
if target.parent.writable? && !force
|
if target.directory? && matching_artifact?(successor)
|
||||||
|
# If an app folder is deleted, macOS considers the app uninstalled and removes some data.
|
||||||
|
# Remove only the contents to handle this case.
|
||||||
|
target.children.each do |child|
|
||||||
|
if target.writable? && !force
|
||||||
|
child.rmtree
|
||||||
|
else
|
||||||
|
Utils.gain_permissions_remove(child, command: command)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elsif target.parent.writable? && !force
|
||||||
target.rmtree
|
target.rmtree
|
||||||
else
|
else
|
||||||
Utils.gain_permissions_remove(target, command: command)
|
Utils.gain_permissions_remove(target, command: command)
|
||||||
|
@ -20,7 +20,7 @@ module Cask
|
|||||||
|
|
||||||
def initialize(cask, command: SystemCommand, force: false, adopt: false,
|
def initialize(cask, command: SystemCommand, force: false, adopt: false,
|
||||||
skip_cask_deps: false, binaries: true, verbose: false,
|
skip_cask_deps: false, binaries: true, verbose: false,
|
||||||
zap: false, require_sha: false, upgrade: false,
|
zap: false, require_sha: false, upgrade: false, reinstall: false,
|
||||||
installed_as_dependency: false, quarantine: true,
|
installed_as_dependency: false, quarantine: true,
|
||||||
verify_download_integrity: true, quiet: false)
|
verify_download_integrity: true, quiet: false)
|
||||||
@cask = cask
|
@cask = cask
|
||||||
@ -32,7 +32,7 @@ module Cask
|
|||||||
@verbose = verbose
|
@verbose = verbose
|
||||||
@zap = zap
|
@zap = zap
|
||||||
@require_sha = require_sha
|
@require_sha = require_sha
|
||||||
@reinstall = false
|
@reinstall = reinstall
|
||||||
@upgrade = upgrade
|
@upgrade = upgrade
|
||||||
@installed_as_dependency = installed_as_dependency
|
@installed_as_dependency = installed_as_dependency
|
||||||
@quarantine = quarantine
|
@quarantine = quarantine
|
||||||
@ -93,6 +93,7 @@ module Cask
|
|||||||
|
|
||||||
raise CaskAlreadyInstalledError, @cask
|
raise CaskAlreadyInstalledError, @cask
|
||||||
end
|
end
|
||||||
|
predecessor = @cask if reinstall? && @cask.installed?
|
||||||
|
|
||||||
check_conflicts
|
check_conflicts
|
||||||
|
|
||||||
@ -108,7 +109,7 @@ module Cask
|
|||||||
|
|
||||||
@cask.config = @cask.default_config.merge(old_config)
|
@cask.config = @cask.default_config.merge(old_config)
|
||||||
|
|
||||||
install_artifacts
|
install_artifacts(predecessor: predecessor)
|
||||||
|
|
||||||
if (tap = @cask.tap) && tap.should_report_analytics?
|
if (tap = @cask.tap) && tap.should_report_analytics?
|
||||||
::Utils::Analytics.report_event(:cask_install, package_name: @cask.token, tap_name: tap.name,
|
::Utils::Analytics.report_event(:cask_install, package_name: @cask.token, tap_name: tap.name,
|
||||||
@ -141,18 +142,12 @@ on_request: true)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def reinstall
|
|
||||||
odebug "Cask::Installer#reinstall"
|
|
||||||
@reinstall = true
|
|
||||||
install
|
|
||||||
end
|
|
||||||
|
|
||||||
def uninstall_existing_cask
|
def uninstall_existing_cask
|
||||||
return unless @cask.installed?
|
return unless @cask.installed?
|
||||||
|
|
||||||
# Always force uninstallation, ignore method parameter
|
# Always force uninstallation, ignore method parameter
|
||||||
cask_installer = Installer.new(@cask, verbose: verbose?, force: true, upgrade: upgrade?)
|
cask_installer = Installer.new(@cask, verbose: verbose?, force: true, upgrade: upgrade?, reinstall: true)
|
||||||
zap? ? cask_installer.zap : cask_installer.uninstall
|
zap? ? cask_installer.zap : cask_installer.uninstall(successor: @cask)
|
||||||
end
|
end
|
||||||
|
|
||||||
sig { returns(String) }
|
sig { returns(String) }
|
||||||
@ -219,7 +214,7 @@ on_request: true)
|
|||||||
Quarantine.propagate(from: primary_container.path, to: to)
|
Quarantine.propagate(from: primary_container.path, to: to)
|
||||||
end
|
end
|
||||||
|
|
||||||
def install_artifacts
|
def install_artifacts(predecessor: nil)
|
||||||
artifacts = @cask.artifacts
|
artifacts = @cask.artifacts
|
||||||
already_installed_artifacts = []
|
already_installed_artifacts = []
|
||||||
|
|
||||||
@ -232,7 +227,8 @@ on_request: true)
|
|||||||
|
|
||||||
next if artifact.is_a?(Artifact::Binary) && !binaries?
|
next if artifact.is_a?(Artifact::Binary) && !binaries?
|
||||||
|
|
||||||
artifact.install_phase(command: @command, verbose: verbose?, adopt: adopt?, force: force?)
|
artifact.install_phase(command: @command, verbose: verbose?, adopt: adopt?, force: force?,
|
||||||
|
predecessor: predecessor)
|
||||||
already_installed_artifacts.unshift(artifact)
|
already_installed_artifacts.unshift(artifact)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -394,10 +390,10 @@ on_request: true)
|
|||||||
@cask.download_sha_path.atomic_write(@cask.new_download_sha) if @cask.checksumable?
|
@cask.download_sha_path.atomic_write(@cask.new_download_sha) if @cask.checksumable?
|
||||||
end
|
end
|
||||||
|
|
||||||
def uninstall
|
def uninstall(successor: nil)
|
||||||
load_installed_caskfile!
|
load_installed_caskfile!
|
||||||
oh1 "Uninstalling Cask #{Formatter.identifier(@cask)}"
|
oh1 "Uninstalling Cask #{Formatter.identifier(@cask)}"
|
||||||
uninstall_artifacts(clear: true)
|
uninstall_artifacts(clear: true, successor: successor)
|
||||||
if !reinstall? && !upgrade?
|
if !reinstall? && !upgrade?
|
||||||
remove_download_sha
|
remove_download_sha
|
||||||
remove_config_file
|
remove_config_file
|
||||||
@ -415,8 +411,8 @@ on_request: true)
|
|||||||
FileUtils.rm_f @cask.download_sha_path if @cask.download_sha_path.exist?
|
FileUtils.rm_f @cask.download_sha_path if @cask.download_sha_path.exist?
|
||||||
end
|
end
|
||||||
|
|
||||||
def start_upgrade
|
def start_upgrade(successor:)
|
||||||
uninstall_artifacts
|
uninstall_artifacts(successor: successor)
|
||||||
backup
|
backup
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -435,10 +431,10 @@ on_request: true)
|
|||||||
backup_metadata_path.rename @cask.metadata_versioned_path
|
backup_metadata_path.rename @cask.metadata_versioned_path
|
||||||
end
|
end
|
||||||
|
|
||||||
def revert_upgrade
|
def revert_upgrade(predecessor)
|
||||||
opoo "Reverting upgrade for Cask #{@cask}"
|
opoo "Reverting upgrade for Cask #{@cask}"
|
||||||
restore_backup
|
restore_backup
|
||||||
install_artifacts
|
install_artifacts(predecessor: predecessor)
|
||||||
end
|
end
|
||||||
|
|
||||||
def finalize_upgrade
|
def finalize_upgrade
|
||||||
@ -449,7 +445,7 @@ on_request: true)
|
|||||||
puts summary
|
puts summary
|
||||||
end
|
end
|
||||||
|
|
||||||
def uninstall_artifacts(clear: false)
|
def uninstall_artifacts(clear: false, successor: nil)
|
||||||
artifacts = @cask.artifacts
|
artifacts = @cask.artifacts
|
||||||
|
|
||||||
odebug "Uninstalling artifacts"
|
odebug "Uninstalling artifacts"
|
||||||
@ -459,7 +455,11 @@ on_request: true)
|
|||||||
if artifact.respond_to?(:uninstall_phase)
|
if artifact.respond_to?(:uninstall_phase)
|
||||||
odebug "Uninstalling artifact of class #{artifact.class}"
|
odebug "Uninstalling artifact of class #{artifact.class}"
|
||||||
artifact.uninstall_phase(
|
artifact.uninstall_phase(
|
||||||
command: @command, verbose: verbose?, skip: clear, force: force?, upgrade: upgrade?,
|
command: @command,
|
||||||
|
verbose: verbose?,
|
||||||
|
skip: clear,
|
||||||
|
force: force?,
|
||||||
|
successor: successor,
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -467,7 +467,11 @@ on_request: true)
|
|||||||
|
|
||||||
odebug "Post-uninstalling artifact of class #{artifact.class}"
|
odebug "Post-uninstalling artifact of class #{artifact.class}"
|
||||||
artifact.post_uninstall_phase(
|
artifact.post_uninstall_phase(
|
||||||
command: @command, verbose: verbose?, skip: clear, force: force?, upgrade: upgrade?,
|
command: @command,
|
||||||
|
verbose: verbose?,
|
||||||
|
skip: clear,
|
||||||
|
force: force?,
|
||||||
|
successor: successor,
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -12,6 +12,7 @@ module Cask
|
|||||||
QUARANTINE_ATTRIBUTE = "com.apple.quarantine"
|
QUARANTINE_ATTRIBUTE = "com.apple.quarantine"
|
||||||
|
|
||||||
QUARANTINE_SCRIPT = (HOMEBREW_LIBRARY_PATH/"cask/utils/quarantine.swift").freeze
|
QUARANTINE_SCRIPT = (HOMEBREW_LIBRARY_PATH/"cask/utils/quarantine.swift").freeze
|
||||||
|
COPY_XATTRS_SCRIPT = (HOMEBREW_LIBRARY_PATH/"cask/utils/copy-xattrs.swift").freeze
|
||||||
|
|
||||||
def self.swift
|
def self.swift
|
||||||
@swift ||= DevelopmentTools.locate("swift")
|
@swift ||= DevelopmentTools.locate("swift")
|
||||||
@ -172,5 +173,19 @@ module Cask
|
|||||||
|
|
||||||
raise CaskQuarantinePropagationError.new(to, quarantiner.stderr)
|
raise CaskQuarantinePropagationError.new(to, quarantiner.stderr)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.copy_xattrs(from, to)
|
||||||
|
odebug "Copying xattrs from #{from} to #{to}"
|
||||||
|
|
||||||
|
system_command!(
|
||||||
|
swift,
|
||||||
|
args: [
|
||||||
|
*swift_target_args,
|
||||||
|
COPY_XATTRS_SCRIPT,
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -26,8 +26,9 @@ module Cask
|
|||||||
force: force,
|
force: force,
|
||||||
skip_cask_deps: skip_cask_deps,
|
skip_cask_deps: skip_cask_deps,
|
||||||
require_sha: require_sha,
|
require_sha: require_sha,
|
||||||
|
reinstall: true,
|
||||||
quarantine: quarantine,
|
quarantine: quarantine,
|
||||||
zap: zap).reinstall
|
zap: zap).install
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -180,22 +180,22 @@ module Cask
|
|||||||
new_cask_installer.fetch
|
new_cask_installer.fetch
|
||||||
|
|
||||||
# Move the old cask's artifacts back to staging
|
# Move the old cask's artifacts back to staging
|
||||||
old_cask_installer.start_upgrade
|
old_cask_installer.start_upgrade(successor: new_cask)
|
||||||
# And flag it so in case of error
|
# And flag it so in case of error
|
||||||
started_upgrade = true
|
started_upgrade = true
|
||||||
|
|
||||||
# Install the new cask
|
# Install the new cask
|
||||||
new_cask_installer.stage
|
new_cask_installer.stage
|
||||||
|
|
||||||
new_cask_installer.install_artifacts
|
new_cask_installer.install_artifacts(predecessor: old_cask)
|
||||||
new_artifacts_installed = true
|
new_artifacts_installed = true
|
||||||
|
|
||||||
# If successful, wipe the old cask from staging
|
# If successful, wipe the old cask from staging
|
||||||
old_cask_installer.finalize_upgrade
|
old_cask_installer.finalize_upgrade
|
||||||
rescue => e
|
rescue => e
|
||||||
new_cask_installer.uninstall_artifacts if new_artifacts_installed
|
new_cask_installer.uninstall_artifacts(successor: old_cask) if new_artifacts_installed
|
||||||
new_cask_installer.purge_versioned_files
|
new_cask_installer.purge_versioned_files
|
||||||
old_cask_installer.revert_upgrade if started_upgrade
|
old_cask_installer.revert_upgrade(predecessor: new_cask) if started_upgrade
|
||||||
raise e
|
raise e
|
||||||
end
|
end
|
||||||
|
|
||||||
|
80
Library/Homebrew/cask/utils/copy-xattrs.swift
Executable file
80
Library/Homebrew/cask/utils/copy-xattrs.swift
Executable file
@ -0,0 +1,80 @@
|
|||||||
|
#!/usr/bin/swift
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct SwiftErr: TextOutputStream {
|
||||||
|
public static var stream = SwiftErr()
|
||||||
|
|
||||||
|
mutating func write(_ string: String) {
|
||||||
|
fputs(string, stderr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
guard CommandLine.arguments.count >= 3 else {
|
||||||
|
print("Usage: swift copy-xattrs.swift <source> <dest>")
|
||||||
|
exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
CommandLine.arguments[2].withCString { destinationPath in
|
||||||
|
let destinationNamesLength = listxattr(destinationPath, nil, 0, 0)
|
||||||
|
if destinationNamesLength == -1 {
|
||||||
|
print("listxattr for destination failed: \(errno)", to: &SwiftErr.stream)
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
let destinationNamesBuffer = UnsafeMutablePointer<Int8>.allocate(capacity: destinationNamesLength)
|
||||||
|
if listxattr(destinationPath, destinationNamesBuffer, destinationNamesLength, 0) != destinationNamesLength {
|
||||||
|
print("Attributes changed during system call", to: &SwiftErr.stream)
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var destinationNamesIndex = 0
|
||||||
|
while destinationNamesIndex < destinationNamesLength {
|
||||||
|
let attribute = destinationNamesBuffer + destinationNamesIndex
|
||||||
|
|
||||||
|
if removexattr(destinationPath, attribute, 0) != 0 {
|
||||||
|
print("removexattr for \(String(cString: attribute)) failed: \(errno)", to: &SwiftErr.stream)
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
destinationNamesIndex += strlen(attribute) + 1
|
||||||
|
}
|
||||||
|
destinationNamesBuffer.deallocate()
|
||||||
|
|
||||||
|
CommandLine.arguments[1].withCString { sourcePath in
|
||||||
|
let sourceNamesLength = listxattr(sourcePath, nil, 0, 0)
|
||||||
|
if sourceNamesLength == -1 {
|
||||||
|
print("listxattr for source failed: \(errno)", to: &SwiftErr.stream)
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
let sourceNamesBuffer = UnsafeMutablePointer<Int8>.allocate(capacity: sourceNamesLength)
|
||||||
|
if listxattr(sourcePath, sourceNamesBuffer, sourceNamesLength, 0) != sourceNamesLength {
|
||||||
|
print("Attributes changed during system call", to: &SwiftErr.stream)
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var sourceNamesIndex = 0
|
||||||
|
while sourceNamesIndex < sourceNamesLength {
|
||||||
|
let attribute = sourceNamesBuffer + sourceNamesIndex
|
||||||
|
|
||||||
|
let valueLength = getxattr(sourcePath, attribute, nil, 0, 0, 0)
|
||||||
|
if valueLength == -1 {
|
||||||
|
print("getxattr for \(String(cString: attribute)) failed: \(errno)", to: &SwiftErr.stream)
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
let valueBuffer = UnsafeMutablePointer<Int8>.allocate(capacity: valueLength)
|
||||||
|
if getxattr(sourcePath, attribute, valueBuffer, valueLength, 0, 0) != valueLength {
|
||||||
|
print("Attributes changed during system call", to: &SwiftErr.stream)
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if setxattr(destinationPath, attribute, valueBuffer, valueLength, 0, 0) != 0 {
|
||||||
|
print("setxattr for \(String(cString: attribute)) failed: \(errno)", to: &SwiftErr.stream)
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
valueBuffer.deallocate()
|
||||||
|
sourceNamesIndex += strlen(attribute) + 1
|
||||||
|
}
|
||||||
|
sourceNamesBuffer.deallocate()
|
||||||
|
}
|
||||||
|
}
|
@ -310,4 +310,29 @@ describe Cask::Artifact::App, :cask do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "upgrade" do
|
||||||
|
# Fix for https://github.com/Homebrew/homebrew-cask/issues/102721
|
||||||
|
it "reuses the same directory" do
|
||||||
|
install_phase
|
||||||
|
|
||||||
|
contents_path = target_path.join("Contents/Info.plist")
|
||||||
|
|
||||||
|
expect(target_path).to exist
|
||||||
|
inode = target_path.stat.ino
|
||||||
|
expect(contents_path).to exist
|
||||||
|
|
||||||
|
app.uninstall_phase(command: command, force: force, successor: cask)
|
||||||
|
|
||||||
|
expect(target_path).to exist
|
||||||
|
expect(target_path.children).to be_empty
|
||||||
|
expect(contents_path).not_to exist
|
||||||
|
|
||||||
|
app.install_phase(command: command, adopt: adopt, force: force, predecessor: cask)
|
||||||
|
expect(target_path).to exist
|
||||||
|
expect(target_path.stat.ino).to eq(inode)
|
||||||
|
|
||||||
|
expect(contents_path).to exist
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -12,6 +12,9 @@ describe Cask::Upgrade, :cask do
|
|||||||
let(:local_transmission) { Cask::CaskLoader.load("local-transmission") }
|
let(:local_transmission) { Cask::CaskLoader.load("local-transmission") }
|
||||||
let(:local_caffeine_path) { local_caffeine.config.appdir.join("Caffeine.app") }
|
let(:local_caffeine_path) { local_caffeine.config.appdir.join("Caffeine.app") }
|
||||||
let(:local_caffeine) { Cask::CaskLoader.load("local-caffeine") }
|
let(:local_caffeine) { Cask::CaskLoader.load("local-caffeine") }
|
||||||
|
let(:renamed_app) { Cask::CaskLoader.load("renamed-app") }
|
||||||
|
let(:renamed_app_old_path) { renamed_app.config.appdir.join("OldApp.app") }
|
||||||
|
let(:renamed_app_new_path) { renamed_app.config.appdir.join("NewApp.app") }
|
||||||
let(:args) { Homebrew::CLI::Args.new }
|
let(:args) { Homebrew::CLI::Args.new }
|
||||||
|
|
||||||
context "when the upgrade is successful" do
|
context "when the upgrade is successful" do
|
||||||
@ -21,6 +24,7 @@ describe Cask::Upgrade, :cask do
|
|||||||
"outdated/local-transmission",
|
"outdated/local-transmission",
|
||||||
"outdated/auto-updates",
|
"outdated/auto-updates",
|
||||||
"outdated/version-latest",
|
"outdated/version-latest",
|
||||||
|
"outdated/renamed-app",
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -40,6 +44,11 @@ describe Cask::Upgrade, :cask do
|
|||||||
expect(local_transmission_path).to be_a_directory
|
expect(local_transmission_path).to be_a_directory
|
||||||
expect(local_transmission.versions).to include("2.60")
|
expect(local_transmission.versions).to include("2.60")
|
||||||
|
|
||||||
|
expect(renamed_app).to be_installed
|
||||||
|
expect(renamed_app_old_path).to be_a_directory
|
||||||
|
expect(renamed_app_new_path).not_to be_a_directory
|
||||||
|
expect(renamed_app.versions).to include("1.0.0")
|
||||||
|
|
||||||
described_class.upgrade_casks(args: args)
|
described_class.upgrade_casks(args: args)
|
||||||
|
|
||||||
expect(local_caffeine).to be_installed
|
expect(local_caffeine).to be_installed
|
||||||
@ -49,6 +58,11 @@ describe Cask::Upgrade, :cask do
|
|||||||
expect(local_transmission).to be_installed
|
expect(local_transmission).to be_installed
|
||||||
expect(local_transmission_path).to be_a_directory
|
expect(local_transmission_path).to be_a_directory
|
||||||
expect(local_transmission.versions).to include("2.61")
|
expect(local_transmission.versions).to include("2.61")
|
||||||
|
|
||||||
|
expect(renamed_app).to be_installed
|
||||||
|
expect(renamed_app_old_path).not_to be_a_directory
|
||||||
|
expect(renamed_app_new_path).to be_a_directory
|
||||||
|
expect(renamed_app.versions).to include("2.0.0")
|
||||||
end
|
end
|
||||||
|
|
||||||
it "updates only the Casks specified in the command line" do
|
it "updates only the Casks specified in the command line" do
|
||||||
@ -60,6 +74,11 @@ describe Cask::Upgrade, :cask do
|
|||||||
expect(local_transmission_path).to be_a_directory
|
expect(local_transmission_path).to be_a_directory
|
||||||
expect(local_transmission.versions).to include("2.60")
|
expect(local_transmission.versions).to include("2.60")
|
||||||
|
|
||||||
|
expect(renamed_app).to be_installed
|
||||||
|
expect(renamed_app_old_path).to be_a_directory
|
||||||
|
expect(renamed_app_new_path).not_to be_a_directory
|
||||||
|
expect(renamed_app.versions).to include("1.0.0")
|
||||||
|
|
||||||
described_class.upgrade_casks(local_caffeine, args: args)
|
described_class.upgrade_casks(local_caffeine, args: args)
|
||||||
|
|
||||||
expect(local_caffeine).to be_installed
|
expect(local_caffeine).to be_installed
|
||||||
@ -69,6 +88,11 @@ describe Cask::Upgrade, :cask do
|
|||||||
expect(local_transmission).to be_installed
|
expect(local_transmission).to be_installed
|
||||||
expect(local_transmission_path).to be_a_directory
|
expect(local_transmission_path).to be_a_directory
|
||||||
expect(local_transmission.versions).to include("2.60")
|
expect(local_transmission.versions).to include("2.60")
|
||||||
|
|
||||||
|
expect(renamed_app).to be_installed
|
||||||
|
expect(renamed_app_old_path).to be_a_directory
|
||||||
|
expect(renamed_app_new_path).not_to be_a_directory
|
||||||
|
expect(renamed_app.versions).to include("1.0.0")
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'updates "auto_updates" and "latest" Casks when their tokens are provided in the command line' do
|
it 'updates "auto_updates" and "latest" Casks when their tokens are provided in the command line' do
|
||||||
@ -106,6 +130,11 @@ describe Cask::Upgrade, :cask do
|
|||||||
expect(local_transmission_path).to be_a_directory
|
expect(local_transmission_path).to be_a_directory
|
||||||
expect(local_transmission.versions).to include("2.60")
|
expect(local_transmission.versions).to include("2.60")
|
||||||
|
|
||||||
|
expect(renamed_app).to be_installed
|
||||||
|
expect(renamed_app_old_path).to be_a_directory
|
||||||
|
expect(renamed_app_new_path).not_to be_a_directory
|
||||||
|
expect(renamed_app.versions).to include("1.0.0")
|
||||||
|
|
||||||
expect(version_latest).to be_installed
|
expect(version_latest).to be_installed
|
||||||
expect(version_latest_path_first).to be_a_directory
|
expect(version_latest_path_first).to be_a_directory
|
||||||
expect(version_latest.versions).to include("latest")
|
expect(version_latest.versions).to include("latest")
|
||||||
@ -127,6 +156,11 @@ describe Cask::Upgrade, :cask do
|
|||||||
expect(local_transmission_path).to be_a_directory
|
expect(local_transmission_path).to be_a_directory
|
||||||
expect(local_transmission.versions).to include("2.61")
|
expect(local_transmission.versions).to include("2.61")
|
||||||
|
|
||||||
|
expect(renamed_app).to be_installed
|
||||||
|
expect(renamed_app_old_path).not_to be_a_directory
|
||||||
|
expect(renamed_app_new_path).to be_a_directory
|
||||||
|
expect(renamed_app.versions).to include("2.0.0")
|
||||||
|
|
||||||
expect(version_latest).to be_installed
|
expect(version_latest).to be_installed
|
||||||
expect(version_latest_path_second).to be_a_directory
|
expect(version_latest_path_second).to be_a_directory
|
||||||
expect(version_latest.versions).to include("latest")
|
expect(version_latest.versions).to include("latest")
|
||||||
@ -186,6 +220,7 @@ describe Cask::Upgrade, :cask do
|
|||||||
"outdated/local-transmission",
|
"outdated/local-transmission",
|
||||||
"outdated/auto-updates",
|
"outdated/auto-updates",
|
||||||
"outdated/version-latest",
|
"outdated/version-latest",
|
||||||
|
"outdated/renamed-app",
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -207,6 +242,11 @@ describe Cask::Upgrade, :cask do
|
|||||||
expect(local_transmission_path).to be_a_directory
|
expect(local_transmission_path).to be_a_directory
|
||||||
expect(local_transmission.versions).to include("2.60")
|
expect(local_transmission.versions).to include("2.60")
|
||||||
|
|
||||||
|
expect(renamed_app).to be_installed
|
||||||
|
expect(renamed_app_old_path).to be_a_directory
|
||||||
|
expect(renamed_app_new_path).not_to be_a_directory
|
||||||
|
expect(renamed_app.versions).to include("1.0.0")
|
||||||
|
|
||||||
described_class.upgrade_casks(dry_run: true, args: args)
|
described_class.upgrade_casks(dry_run: true, args: args)
|
||||||
|
|
||||||
expect(local_caffeine).to be_installed
|
expect(local_caffeine).to be_installed
|
||||||
@ -218,6 +258,12 @@ describe Cask::Upgrade, :cask do
|
|||||||
expect(local_transmission_path).to be_a_directory
|
expect(local_transmission_path).to be_a_directory
|
||||||
expect(local_transmission.versions).to include("2.60")
|
expect(local_transmission.versions).to include("2.60")
|
||||||
expect(local_transmission.versions).not_to include("2.61")
|
expect(local_transmission.versions).not_to include("2.61")
|
||||||
|
|
||||||
|
expect(renamed_app).to be_installed
|
||||||
|
expect(renamed_app_old_path).to be_a_directory
|
||||||
|
expect(renamed_app_new_path).not_to be_a_directory
|
||||||
|
expect(renamed_app.versions).to include("1.0.0")
|
||||||
|
expect(renamed_app.versions).not_to include("2.0.0")
|
||||||
end
|
end
|
||||||
|
|
||||||
it "would update only the Casks specified in the command line" do
|
it "would update only the Casks specified in the command line" do
|
||||||
@ -255,6 +301,11 @@ describe Cask::Upgrade, :cask do
|
|||||||
expect(auto_updates_path).to be_a_directory
|
expect(auto_updates_path).to be_a_directory
|
||||||
expect(auto_updates.versions).to include("2.57")
|
expect(auto_updates.versions).to include("2.57")
|
||||||
|
|
||||||
|
expect(renamed_app).to be_installed
|
||||||
|
expect(renamed_app_old_path).to be_a_directory
|
||||||
|
expect(renamed_app_new_path).not_to be_a_directory
|
||||||
|
expect(renamed_app.versions).to include("1.0.0")
|
||||||
|
|
||||||
described_class.upgrade_casks(local_caffeine, auto_updates, dry_run: true, args: args)
|
described_class.upgrade_casks(local_caffeine, auto_updates, dry_run: true, args: args)
|
||||||
|
|
||||||
expect(local_caffeine).to be_installed
|
expect(local_caffeine).to be_installed
|
||||||
@ -266,6 +317,12 @@ describe Cask::Upgrade, :cask do
|
|||||||
expect(auto_updates_path).to be_a_directory
|
expect(auto_updates_path).to be_a_directory
|
||||||
expect(auto_updates.versions).to include("2.57")
|
expect(auto_updates.versions).to include("2.57")
|
||||||
expect(auto_updates.versions).not_to include("2.61")
|
expect(auto_updates.versions).not_to include("2.61")
|
||||||
|
|
||||||
|
expect(renamed_app).to be_installed
|
||||||
|
expect(renamed_app_old_path).to be_a_directory
|
||||||
|
expect(renamed_app_new_path).not_to be_a_directory
|
||||||
|
expect(renamed_app.versions).to include("1.0.0")
|
||||||
|
expect(renamed_app.versions).not_to include("2.0.0")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -285,6 +342,11 @@ describe Cask::Upgrade, :cask do
|
|||||||
expect(local_transmission_path).to be_a_directory
|
expect(local_transmission_path).to be_a_directory
|
||||||
expect(local_transmission.versions).to include("2.60")
|
expect(local_transmission.versions).to include("2.60")
|
||||||
|
|
||||||
|
expect(renamed_app).to be_installed
|
||||||
|
expect(renamed_app_old_path).to be_a_directory
|
||||||
|
expect(renamed_app_new_path).not_to be_a_directory
|
||||||
|
expect(renamed_app.versions).to include("1.0.0")
|
||||||
|
|
||||||
expect(version_latest).to be_installed
|
expect(version_latest).to be_installed
|
||||||
# Change download sha so that :latest cask decides to update itself
|
# Change download sha so that :latest cask decides to update itself
|
||||||
version_latest.download_sha_path.write("fake download sha")
|
version_latest.download_sha_path.write("fake download sha")
|
||||||
@ -307,6 +369,12 @@ describe Cask::Upgrade, :cask do
|
|||||||
expect(local_transmission.versions).to include("2.60")
|
expect(local_transmission.versions).to include("2.60")
|
||||||
expect(local_transmission.versions).not_to include("2.61")
|
expect(local_transmission.versions).not_to include("2.61")
|
||||||
|
|
||||||
|
expect(renamed_app).to be_installed
|
||||||
|
expect(renamed_app_old_path).to be_a_directory
|
||||||
|
expect(renamed_app_new_path).not_to be_a_directory
|
||||||
|
expect(renamed_app.versions).to include("1.0.0")
|
||||||
|
expect(renamed_app.versions).not_to include("2.0.0")
|
||||||
|
|
||||||
expect(version_latest).to be_installed
|
expect(version_latest).to be_installed
|
||||||
expect(version_latest.outdated_download_sha?).to be(true)
|
expect(version_latest.outdated_download_sha?).to be(true)
|
||||||
end
|
end
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
cask "renamed-app" do
|
||||||
|
version "1.0.0"
|
||||||
|
sha256 "cf001ed6c81820e049dc7a353957dab8936b91f1956ee74ff0b3eb59791f1ad9"
|
||||||
|
|
||||||
|
url "file://#{TEST_FIXTURE_DIR}/cask/old-app.tar.gz"
|
||||||
|
homepage "https://brew.sh/"
|
||||||
|
|
||||||
|
app "OldApp.app"
|
||||||
|
end
|
@ -0,0 +1,9 @@
|
|||||||
|
cask "renamed-app" do
|
||||||
|
version "2.0.0"
|
||||||
|
sha256 "9f88a6f3d8a7977cd3c116c56ee7a20a3c69e838a1d4946f815a926a57883299"
|
||||||
|
|
||||||
|
url "file://#{TEST_FIXTURE_DIR}/cask/new-app.tar.gz"
|
||||||
|
homepage "https://brew.sh/"
|
||||||
|
|
||||||
|
app "NewApp.app"
|
||||||
|
end
|
BIN
Library/Homebrew/test/support/fixtures/cask/new-app.tar.gz
Normal file
BIN
Library/Homebrew/test/support/fixtures/cask/new-app.tar.gz
Normal file
Binary file not shown.
BIN
Library/Homebrew/test/support/fixtures/cask/old-app.tar.gz
Normal file
BIN
Library/Homebrew/test/support/fixtures/cask/old-app.tar.gz
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user