/
moved.rb
198 lines (165 loc) 路 7.19 KB
/
moved.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# typed: true
# frozen_string_literal: true
require "cask/artifact/relocated"
require "cask/quarantine"
module Cask
module Artifact
# Superclass for all artifacts that are installed by moving them to the target location.
#
# @api private
class Moved < Relocated
sig { returns(String) }
def self.english_description
"#{english_name}s"
end
def install_phase(**options)
move(**options)
end
def uninstall_phase(**options)
move_back(**options)
end
def summarize_installed
if target.exist?
"#{printable_target} (#{target.abv})"
else
Formatter.error(printable_target, label: "Missing #{self.class.english_name}")
end
end
private
def move(adopt: false, force: false, verbose: false, predecessor: nil, reinstall: false,
command: nil, **options)
unless source.exist?
raise CaskError, "It seems the #{self.class.english_name} source '#{source}' is not there."
end
if Utils.path_occupied?(target)
if target.directory? && target.children.empty? && matching_artifact?(predecessor)
# An upgrade removed the directory contents but left the directory itself (see below).
unless source.directory?
if target.parent.writable? && !force
target.rmdir
else
Utils.gain_permissions_remove(target, command:)
end
end
else
if adopt
ohai "Adopting existing #{self.class.english_name} at '#{target}'"
source_plist = Pathname("#{source}/Contents/Info.plist")
target_plist = Pathname("#{target}/Contents/Info.plist")
same = if source_plist.size? &&
(source_bundle_version = Homebrew::BundleVersion.from_info_plist(source_plist)) &&
target_plist.size? &&
(target_bundle_version = Homebrew::BundleVersion.from_info_plist(target_plist))
if source_bundle_version.short_version == target_bundle_version.short_version
if source_bundle_version.version == target_bundle_version.version
true
else
onoe "The bundle version of #{source} is #{source_bundle_version.version} but " \
"is #{target_bundle_version.version} for #{target}!"
false
end
else
onoe "The bundle short version of #{source} is #{source_bundle_version.short_version} but " \
"is #{target_bundle_version.short_version} for #{target}!"
false
end
else
command.run(
"/usr/bin/diff",
args: ["--recursive", "--brief", source, target],
verbose:,
print_stdout: verbose,
).success?
end
unless same
raise CaskError,
"It seems the existing #{self.class.english_name} is different from " \
"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
message = "It seems there is already #{self.class.english_article} " \
"#{self.class.english_name} at '#{target}'"
raise CaskError, "#{message}." if !force && !adopt
opoo "#{message}; overwriting."
delete(target, force:, command:, **options)
end
end
ohai "Moving #{self.class.english_name} '#{source.basename}' to '#{target}'"
Utils.gain_permissions_mkpath(target.dirname, command:) unless target.dirname.exist?
if target.directory? && Quarantine.app_management_permissions_granted?(app: target, command:)
if target.writable?
source.children.each { |child| FileUtils.move(child, target/child.basename) }
else
command.run!("/bin/cp", args: ["-pR", *source.children, target],
sudo: true)
end
Quarantine.copy_xattrs(source, target, command:)
source.rmtree
elsif target.dirname.writable?
FileUtils.move(source, target)
else
# default sudo user isn't necessarily able to write to Homebrew's locations
# e.g. with runas_default set in the sudoers (5) file.
command.run!("/bin/cp", args: ["-pR", source, target], sudo: true)
source.rmtree
end
post_move(command)
end
# Performs any actions necessary after the source has been moved to the target location.
def post_move(command)
FileUtils.ln_sf target, source
add_altname_metadata(target, source.basename, command:)
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, adopt: false, command: nil, **options)
FileUtils.rm source if source.symlink? && source.dirname.join(source.readlink) == target
if Utils.path_occupied?(source)
message = "It seems there is already #{self.class.english_article} " \
"#{self.class.english_name} at '#{source}'"
raise CaskError, "#{message}." if !force && !adopt
opoo "#{message}; overwriting."
delete(source, force:, command:, **options)
end
unless target.exist?
return if skip || force
raise CaskError, "It seems the #{self.class.english_name} source '#{target}' is not there."
end
ohai "Backing #{self.class.english_name} '#{target.basename}' up to '#{source}'"
source.dirname.mkpath
# We need to preserve extended attributes between copies.
command.run!("/bin/cp", args: ["-pR", target, source], sudo: !source.parent.writable?)
delete(target, force:, command:, **options)
end
def delete(target, force: false, successor: nil, command: nil, **_)
ohai "Removing #{self.class.english_name} '#{target}'"
raise CaskError, "Cannot remove undeletable #{self.class.english_name}." if MacOS.undeletable?(target)
return unless Utils.path_occupied?(target)
if target.directory? && matching_artifact?(successor) && Quarantine.app_management_permissions_granted?(
app: target, command:,
)
# 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:)
end
end
elsif target.parent.writable? && !force
target.rmtree
else
Utils.gain_permissions_remove(target, command:)
end
end
end
end
end