Class: Hbc::Installer

Inherits:
Object
  • Object
show all
Extended by:
Predicable
Includes:
Staged, Verify
Defined in:
Library/Homebrew/cask/lib/hbc/installer.rb

Constant Summary collapse

PERSISTENT_METADATA_SUBDIRS =
["gpg"].freeze

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Predicable

attr_predicate

Methods included from Verify

all, for_cask, verifications

Methods included from Staged

#bundle_identifier, #current_user, #info_plist_file, #plist_exec, #plist_set, #set_ownership, #set_permissions

Constructor Details

#initialize(cask, command: SystemCommand, force: false, skip_cask_deps: false, binaries: true, verbose: false, require_sha: false, upgrade: false) ⇒ Installer

Returns a new instance of Installer



22
23
24
25
26
27
28
29
30
31
32
# File 'Library/Homebrew/cask/lib/hbc/installer.rb', line 22

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
  @skip_cask_deps = skip_cask_deps
  @binaries = binaries
  @verbose = verbose
  @require_sha = require_sha
  @reinstall = false
  @upgrade = upgrade
end

Class Method Details



36
37
38
39
40
41
42
43
44
# File 'Library/Homebrew/cask/lib/hbc/installer.rb', line 36

def self.print_caveats(cask)
  odebug "Printing caveats"

  caveats = cask.caveats
  return if caveats.empty?

  ohai "Caveats"
  puts caveats + "\n"
end

Instance Method Details

#arch_dependenciesObject

Raises:



222
223
224
225
226
227
228
229
230
# File 'Library/Homebrew/cask/lib/hbc/installer.rb', line 222

def arch_dependencies
  return if @cask.depends_on.arch.nil?
  @current_arch ||= { type: Hardware::CPU.type, bits: Hardware::CPU.bits }
  return if @cask.depends_on.arch.any? do |arch|
    arch[:type] == @current_arch[:type] &&
    Array(arch[:bits]).include?(@current_arch[:bits])
  end
  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

#backupObject



366
367
368
369
# File 'Library/Homebrew/cask/lib/hbc/installer.rb', line 366

def backup
  @cask.staged_path.rename backup_path
  @cask..rename 
end

#backup_metadata_pathObject



427
428
429
430
# File 'Library/Homebrew/cask/lib/hbc/installer.rb', line 427

def 
  return nil if @cask..nil?
  Pathname.new "#{@cask.}.upgrading"
end

#backup_pathObject



422
423
424
425
# File 'Library/Homebrew/cask/lib/hbc/installer.rb', line 422

def backup_path
  return nil if @cask.staged_path.nil?
  Pathname.new "#{@cask.staged_path}.upgrading"
end

#cask_dependenciesObject



268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'Library/Homebrew/cask/lib/hbc/installer.rb', line 268

def cask_dependencies
  return if @cask.depends_on.cask.empty?
  casks = CaskDependencies.new(@cask)

  if casks.all?(&:installed?)
    puts "All Cask dependencies satisfied."
    return
  end

  not_installed = casks.reject(&:installed?)

  ohai "Installing Cask dependencies: #{not_installed.map(&:to_s).join(", ")}"
  not_installed.each do |cask|
    Installer.new(cask, binaries: binaries?, verbose: verbose?, skip_cask_deps: true, force: false).install
  end
end

#check_conflictsObject



86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'Library/Homebrew/cask/lib/hbc/installer.rb', line 86

def check_conflicts
  return unless @cask.conflicts_with

  @cask.conflicts_with.cask.each do |conflicting_cask|
    begin
      conflicting_cask = CaskLoader.load(conflicting_cask)
      if conflicting_cask.installed?
        raise CaskConflictError.new(@cask, conflicting_cask)
      end
    rescue CaskUnavailableError
      next # Ignore conflicting Casks that do not exist.
    end
  end
end

#disable_accessibility_accessObject



322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
# File 'Library/Homebrew/cask/lib/hbc/installer.rb', line 322

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: [
                    Hbc.tcc_db,
                    "DELETE FROM access WHERE client='#{bundle_identifier}';",
                  ],
                  sudo: true)
  else
    opoo <<~EOS
      Accessibility access cannot be disabled automatically on this version of macOS.
      See System Preferences to disable it manually.
    EOS
  end
end

#downloadObject



124
125
126
127
128
129
# File 'Library/Homebrew/cask/lib/hbc/installer.rb', line 124

def download
  odebug "Downloading"
  @downloaded_path = Download.new(@cask, force: false).perform
  odebug "Downloaded to -> #{@downloaded_path}"
  @downloaded_path
end

#enable_accessibility_accessObject

TODO: logically could be in a separate class



290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
# File 'Library/Homebrew/cask/lib/hbc/installer.rb', line 290

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: [
                    Hbc.tcc_db,
                    "INSERT OR REPLACE INTO access VALUES('kTCCServiceAccessibility','#{bundle_identifier}',0,1,1,NULL);",
                  ],
                  sudo: true)
  elsif MacOS.version <= :el_capitan
    @command.run!("/usr/bin/sqlite3",
                  args: [
                    Hbc.tcc_db,
                    "INSERT OR REPLACE INTO access VALUES('kTCCServiceAccessibility','#{bundle_identifier}',0,1,1,NULL,NULL);",
                  ],
                  sudo: true)
  else
    opoo <<~EOS
      Accessibility access cannot be enabled automatically on this version of macOS.
      See System Preferences to enable it manually.
    EOS
  end
rescue StandardError => e
  purge_versioned_files
  raise e
end

#extract_primary_containerObject



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'Library/Homebrew/cask/lib/hbc/installer.rb', line 141

def extract_primary_container
  odebug "Extracting primary container"

  FileUtils.mkdir_p @cask.staged_path
  container = if @cask.container&.type
    Container.from_type(@cask.container.type)
  else
    Container.for_path(@downloaded_path, @command)
  end

  unless container
    raise CaskError, "Uh oh, could not figure out how to unpack '#{@downloaded_path}'"
  end

  odebug "Using container class #{container} for #{@downloaded_path}"
  container.new(@cask, @downloaded_path, @command, verbose: verbose?).extract
end

#fetchObject



46
47
48
49
50
51
52
53
# File 'Library/Homebrew/cask/lib/hbc/installer.rb', line 46

def fetch
  odebug "Hbc::Installer#fetch"

  satisfy_dependencies
  verify_has_sha if require_sha? && !force?
  download
  verify
end

#finalize_upgradeObject



388
389
390
391
392
# File 'Library/Homebrew/cask/lib/hbc/installer.rb', line 388

def finalize_upgrade
  purge_backed_up_versioned_files

  puts summary
end

#formula_dependenciesObject



237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'Library/Homebrew/cask/lib/hbc/installer.rb', line 237

def formula_dependencies
  formulae = @cask.depends_on.formula.map { |f| Formula[f] }
  return if formulae.empty?

  if formulae.all?(&:any_version_installed?)
    puts "All Formula dependencies satisfied."
    return
  end

  not_installed = formulae.reject(&:any_version_installed?)

  ohai "Installing Formula dependencies: #{not_installed.map(&:to_s).join(", ")}"
  not_installed.each do |formula|
    begin
      old_argv = ARGV.dup
      ARGV.replace([])
      FormulaInstaller.new(formula).tap do |fi|
        fi.installed_as_dependency = true
        fi.installed_on_request = false
        fi.show_header = true
        fi.verbose = verbose?
        fi.prelude
        fi.install
        fi.finish
      end
    ensure
      ARGV.replace(old_argv)
    end
  end
end

#gain_permissions_remove(path) ⇒ Object



432
433
434
# File 'Library/Homebrew/cask/lib/hbc/installer.rb', line 432

def gain_permissions_remove(path)
  Utils.gain_permissions_remove(path, command: @command)
end

#installObject



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'Library/Homebrew/cask/lib/hbc/installer.rb', line 65

def install
  odebug "Hbc::Installer#install"

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

  check_conflicts

  print_caveats
  fetch
  uninstall_existing_cask if @reinstall

  oh1 "Installing Cask #{Formatter.identifier(@cask)}"
  stage
  install_artifacts
  enable_accessibility_access

  puts summary
end

#install_artifactsObject



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
# File 'Library/Homebrew/cask/lib/hbc/installer.rb', line 159

def install_artifacts
  already_installed_artifacts = []

  odebug "Installing artifacts"
  artifacts = @cask.artifacts
  odebug "#{artifacts.length} artifact/s defined", artifacts

  artifacts.each do |artifact|
    next unless artifact.respond_to?(:install_phase)
    odebug "Installing artifact of class #{artifact.class}"

    if artifact.is_a?(Artifact::Binary)
      next unless binaries?
    end

    artifact.install_phase(command: @command, verbose: verbose?, force: force?)
    already_installed_artifacts.unshift(artifact)
  end
rescue StandardError => e
  begin
    already_installed_artifacts.each do |artifact|
      next unless artifact.respond_to?(:uninstall_phase)
      odebug "Reverting installation of artifact of class #{artifact.class}"
      artifact.uninstall_phase(command: @command, verbose: verbose?, force: force?)
    end
  ensure
    purge_versioned_files
    raise e
  end
end

#macos_dependenciesObject



204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'Library/Homebrew/cask/lib/hbc/installer.rb', line 204

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
end


285
286
287
# File 'Library/Homebrew/cask/lib/hbc/installer.rb', line 285

def print_caveats
  self.class.print_caveats(@cask)
end

#purge_backed_up_versioned_filesObject



436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
# File 'Library/Homebrew/cask/lib/hbc/installer.rb', line 436

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

  # versioned staged distribution
  gain_permissions_remove(backup_path) if !backup_path.nil? && backup_path.exist?

  # Homebrew-Cask metadata
  if .directory?
    .children.each do |subdir|
      unless PERSISTENT_METADATA_SUBDIRS.include?(subdir.basename)
        gain_permissions_remove(subdir)
      end
    end
  end
  .rmdir_if_possible
end

#purge_caskroom_pathObject



475
476
477
478
# File 'Library/Homebrew/cask/lib/hbc/installer.rb', line 475

def purge_caskroom_path
  odebug "Purging all staged versions of Cask #{@cask}"
  gain_permissions_remove(@cask.caskroom_path)
end

#purge_versioned_filesObject



453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
# File 'Library/Homebrew/cask/lib/hbc/installer.rb', line 453

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

  # versioned staged distribution
  gain_permissions_remove(@cask.staged_path) if !@cask.staged_path.nil? && @cask.staged_path.exist?

  # Homebrew-Cask metadata
  if @cask..respond_to?(:children) &&
     @cask..exist?
    @cask..children.each do |subdir|
      unless PERSISTENT_METADATA_SUBDIRS.include?(subdir.basename)
        gain_permissions_remove(subdir)
      end
    end
  end
  @cask..rmdir_if_possible
  @cask..rmdir_if_possible unless upgrade?

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

#reinstallObject



101
102
103
104
105
# File 'Library/Homebrew/cask/lib/hbc/installer.rb', line 101

def reinstall
  odebug "Hbc::Installer#reinstall"
  @reinstall = true
  install
end

#restore_backupObject



371
372
373
374
375
376
377
378
379
# File 'Library/Homebrew/cask/lib/hbc/installer.rb', line 371

def restore_backup
  return unless backup_path.directory? && .directory?

  Pathname.new(@cask.staged_path).rmtree if @cask.staged_path.exist?
  Pathname.new(@cask.).rmtree if @cask..exist?

  backup_path.rename @cask.staged_path
  .rename @cask.
end

#revert_upgradeObject



381
382
383
384
385
386
# File 'Library/Homebrew/cask/lib/hbc/installer.rb', line 381

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

#satisfy_dependenciesObject

TODO: move dependencies to a separate class

dependencies should also apply for "brew cask stage"
override dependencies with --force or perhaps --force-deps


193
194
195
196
197
198
199
200
201
202
# File 'Library/Homebrew/cask/lib/hbc/installer.rb', line 193

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?
end

#save_caskfileObject



340
341
342
343
344
345
346
347
348
# File 'Library/Homebrew/cask/lib/hbc/installer.rb', line 340

def save_caskfile
  old_savedir = @cask.

  return unless @cask.sourcefile_path

  savedir = @cask.("Casks", timestamp: :now, create: true)
  FileUtils.copy @cask.sourcefile_path, savedir
  old_savedir&.rmtree
end

#stageObject



55
56
57
58
59
60
61
62
63
# File 'Library/Homebrew/cask/lib/hbc/installer.rb', line 55

def stage
  odebug "Hbc::Installer#stage"

  extract_primary_container
  save_caskfile
rescue StandardError => e
  purge_versioned_files
  raise e
end

#start_upgradeObject



358
359
360
361
362
363
364
# File 'Library/Homebrew/cask/lib/hbc/installer.rb', line 358

def start_upgrade
  oh1 "Starting upgrade for Cask #{Formatter.identifier(@cask)}"

  disable_accessibility_access
  uninstall_artifacts
  backup
end

#summaryObject



118
119
120
121
122
# File 'Library/Homebrew/cask/lib/hbc/installer.rb', line 118

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

#uninstallObject



350
351
352
353
354
355
356
# File 'Library/Homebrew/cask/lib/hbc/installer.rb', line 350

def uninstall
  oh1 "Uninstalling Cask #{Formatter.identifier(@cask)}"
  disable_accessibility_access
  uninstall_artifacts(clear: true)
  purge_versioned_files
  purge_caskroom_path if force?
end

#uninstall_artifacts(clear: false) ⇒ Object



394
395
396
397
398
399
400
401
402
403
404
405
# File 'Library/Homebrew/cask/lib/hbc/installer.rb', line 394

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

  odebug "#{artifacts.length} artifact/s defined", 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?, skip: clear, force: force?)
  end
end

#uninstall_existing_caskObject



107
108
109
110
111
112
113
114
115
116
# File 'Library/Homebrew/cask/lib/hbc/installer.rb', line 107

def uninstall_existing_cask
  return unless @cask.installed?

  # use the same cask file that was used for installation, if possible
  installed_caskfile = @cask.installed_caskfile
  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, upgrade: upgrade?).uninstall
end

#verifyObject



137
138
139
# File 'Library/Homebrew/cask/lib/hbc/installer.rb', line 137

def verify
  Verify.all(@cask, @downloaded_path)
end

#verify_has_shaObject

Raises:



131
132
133
134
135
# File 'Library/Homebrew/cask/lib/hbc/installer.rb', line 131

def verify_has_sha
  odebug "Checking cask has checksum"
  return unless @cask.sha256 == :no_check
  raise CaskNoShasumError, @cask.token
end

#x11_dependenciesObject



232
233
234
235
# File 'Library/Homebrew/cask/lib/hbc/installer.rb', line 232

def x11_dependencies
  return unless @cask.depends_on.x11
  raise CaskX11DependencyError, @cask.token unless MacOS::X11.installed?
end

#zapObject



407
408
409
410
411
412
413
414
415
416
417
418
419
420
# File 'Library/Homebrew/cask/lib/hbc/installer.rb', line 407

def zap
  ohai %Q(Implied "brew cask uninstall #{@cask}")
  uninstall_artifacts
  if (zap_stanzas = @cask.artifacts.select { |a| a.is_a?(Artifact::Zap) }).empty?
    opoo "No zap stanza present for Cask '#{@cask}'"
  else
    ohai "Dispatching zap stanza"
    zap_stanzas.each do |stanza|
      stanza.zap_phase(command: @command, verbose: verbose?, force: force?)
    end
  end
  ohai "Removing all staged versions of Cask '#{@cask}'"
  purge_caskroom_path
end