Skip to content

Commit

Permalink
Add cask install receipts
Browse files Browse the repository at this point in the history
  • Loading branch information
Rylan12 committed Jul 4, 2024
1 parent 5e7765c commit acd6018
Show file tree
Hide file tree
Showing 10 changed files with 316 additions and 134 deletions.
1 change: 1 addition & 0 deletions Library/Homebrew/cask.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@
require "cask/pkg"
require "cask/quarantine"
require "cask/staged"
require "cask/tab"
require "cask/url"
require "cask/utils"
6 changes: 6 additions & 0 deletions Library/Homebrew/cask/artifact/abstract_flight_block.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ def summarize
directives.keys.map(&:to_s).join(", ")
end

def uninstall?
directives.keys.any? do |key|
key.to_s.start_with?("uninstall_")
end
end

private

def class_for_dsl_key(dsl_key)
Expand Down
43 changes: 30 additions & 13 deletions Library/Homebrew/cask/cask.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
require "cask/config"
require "cask/dsl"
require "cask/metadata"
require "cask/tab"
require "utils/bottles"
require "extend/api_hashable"

Expand Down Expand Up @@ -158,6 +159,12 @@ def caskfile_only?
languages.any? || artifacts.any?(Artifact::AbstractFlightBlock)
end

def uninstall_flight_blocks?
artifacts.any? do |artifact|
artifact.is_a?(Artifact::AbstractFlightBlock) && artifact.uninstall?
end
end

sig { returns(T.nilable(Time)) }
def install_time
# <caskroom_path>/.metadata/<version>/<timestamp>/Casks/<token>.{rb,json} -> <timestamp>
Expand Down Expand Up @@ -209,6 +216,10 @@ def bundle_long_version
bundle_version&.version
end

def tab
Tab.for_cask(self)
end

def config_path
metadata_main_container_path/"config.json"
end
Expand Down Expand Up @@ -465,6 +476,25 @@ def to_hash_with_variations(hash_method: :to_h)
hash
end

def artifacts_list(compact: false, uninstall_only: false)
artifacts.filter_map do |artifact|
case artifact
when Artifact::AbstractFlightBlock
next if uninstall_only && !artifact.uninstall?

# Only indicate whether this block is used as we don't load it from the API
# We can skip this entirely once we move to internal JSON v3.
{ artifact.summarize => nil } unless compact
else
zap_artifact = artifact.is_a?(Artifact::Zap)
uninstall_artifact = artifact.respond_to?(:uninstall_phase) || artifact.respond_to?(:post_uninstall_phase)
next if uninstall_only && !zap_artifact && !uninstall_artifact

{ artifact.class.dsl_key => artifact.to_args }
end
end
end

private

sig { returns(T.nilable(Homebrew::BundleVersion)) }
Expand All @@ -482,19 +512,6 @@ def api_to_local_hash(hash)
hash
end

def artifacts_list(compact: false)
artifacts.filter_map do |artifact|
case artifact
when Artifact::AbstractFlightBlock
# Only indicate whether this block is used as we don't load it from the API
# We can skip this entirely once we move to internal JSON v3.
{ artifact.summarize => nil } unless compact
else
{ artifact.class.dsl_key => artifact.to_args }
end
end
end

def url_specs
url&.specs.dup.tap do |url_specs|
case url_specs&.dig(:user_agent)
Expand Down
21 changes: 18 additions & 3 deletions Library/Homebrew/cask/installer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
require "cask/download"
require "cask/migrator"
require "cask/quarantine"
require "cask/tab"

require "cgi"

Expand All @@ -21,8 +22,8 @@ class Installer
def initialize(cask, command: SystemCommand, force: false, adopt: false,
skip_cask_deps: false, binaries: true, verbose: false,
zap: false, require_sha: false, upgrade: false, reinstall: false,
installed_as_dependency: false, quarantine: true,
verify_download_integrity: true, quiet: false)
installed_as_dependency: false, installed_on_request: true,
quarantine: true, verify_download_integrity: true, quiet: false)
@cask = cask
@command = command
@force = force
Expand All @@ -35,13 +36,14 @@ def initialize(cask, command: SystemCommand, force: false, adopt: false,
@reinstall = reinstall
@upgrade = upgrade
@installed_as_dependency = installed_as_dependency
@installed_on_request = installed_on_request
@quarantine = quarantine
@verify_download_integrity = verify_download_integrity
@quiet = quiet
end

attr_predicate :binaries?, :force?, :adopt?, :skip_cask_deps?, :require_sha?,
:reinstall?, :upgrade?, :verbose?, :zap?, :installed_as_dependency?,
:reinstall?, :upgrade?, :verbose?, :zap?, :installed_as_dependency?, :installed_on_request?,
:quarantine?, :quiet?

def self.caveats(cask)
Expand Down Expand Up @@ -112,6 +114,11 @@ def install

install_artifacts(predecessor:)

tab = Tab.create(@cask)
tab.installed_as_dependency = installed_as_dependency?
tab.installed_on_request = installed_on_request?
tab.write

if (tap = @cask.tap) && tap.should_report_analytics?
::Utils::Analytics.report_package_event(:cask_install, package_name: @cask.token, tap_name: tap.name,
on_request: true)
Expand Down Expand Up @@ -356,6 +363,7 @@ def satisfy_cask_and_formula_dependencies
binaries: binaries?,
verbose: verbose?,
installed_as_dependency: true,
installed_on_request: false,
force: false,
).install
else
Expand Down Expand Up @@ -408,13 +416,20 @@ def uninstall(successor: nil)
oh1 "Uninstalling Cask #{Formatter.identifier(@cask)}"
uninstall_artifacts(clear: true, successor:)
if !reinstall? && !upgrade?
remove_tabfile
remove_download_sha
remove_config_file
end
purge_versioned_files
purge_caskroom_path if force?
end

def remove_tabfile
tabfile = @cask.tab.tabfile
FileUtils.rm_f tabfile if tabfile.present? && tabfile.exist?
@cask.config_path.parent.rmdir_if_possible
end

def remove_config_file
FileUtils.rm_f @cask.config_path
@cask.config_path.parent.rmdir_if_possible
Expand Down
120 changes: 120 additions & 0 deletions Library/Homebrew/cask/tab.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# typed: true
# frozen_string_literal: true

require "tab"

module Cask
class Tab < ::AbstractTab
attr_accessor :uninstall_flight_blocks, :uninstall_artifacts

# Instantiates a {Tab} for a new installation of a cask.
def self.create(cask)
attributes = generic_attributes(cask).merge({
"tabfile" => cask.metadata_main_container_path/FILENAME,
"uninstall_flight_blocks" => cask.uninstall_flight_blocks?,
"runtime_dependencies" => Tab.runtime_deps_hash(cask, cask.depends_on),
"source" => {
"path" => cask.sourcefile_path.to_s,
"tap" => cask.tap&.name,
"tap_git_head" => nil, # Filled in later if possible
"version" => cask.version.to_s,
},
"uninstall_artifacts" => cask.artifacts_list(uninstall_only: true),
})

# We can only get `tap_git_head` if the tap is installed locally
attributes["source"]["tap_git_head"] = cask.tap.git_head if cask.tap&.installed?

new(attributes)
end

# Returns a {Tab} for an already installed cask,
# or a fake one if the cask is not installed.
def self.for_cask(cask)
path = cask.metadata_main_container_path/FILENAME

return from_file(path) if path.exist?

tab = empty
tab.source = {
"path" => cask.sourcefile_path.to_s,
"tap" => cask.tap&.name,
"tap_git_head" => nil,
"version" => cask.version.to_s,
}
tab.uninstall_artifacts = cask.artifacts_list(uninstall_only: true)
tab.source["tap_git_head"] = cask.tap.git_head if cask.tap&.installed?

tab
end

def self.empty
tab = super
tab.uninstall_flight_blocks = false
tab.uninstall_artifacts = []
tab.source["version"] = nil

tab
end

def self.runtime_deps_hash(cask, depends_on)
mappable_types = [:cask, :formula]
depends_on.to_h do |type, deps|
next [type, deps] unless mappable_types.include? type

deps = deps.map do |dep|
if type == :cask
c = CaskLoader.load(dep)
{
"full_name" => c.full_name,
"version" => c.version.to_s,
"declared_directly" => cask.depends_on.cask.include?(dep),
}
elsif type == :formula
f = Formulary.factory(dep, warn: false)
{
"full_name" => f.full_name,
"version" => f.version.to_s,
"revision" => f.revision,
"pkg_version" => f.pkg_version.to_s,
"declared_directly" => cask.depends_on.formula.include?(dep),
}
else
dep
end
end

[type, deps]
end
end

def version
source["version"]
end

def to_json(*_args)
attributes = {
"homebrew_version" => homebrew_version,
"loaded_from_api" => loaded_from_api,
"uninstall_flight_blocks" => uninstall_flight_blocks,
"installed_as_dependency" => installed_as_dependency,
"installed_on_request" => installed_on_request,
"time" => time,
"runtime_dependencies" => runtime_dependencies,
"source" => source,
"arch" => arch,
"uninstall_artifacts" => uninstall_artifacts,
"built_on" => built_on,
}

JSON.pretty_generate(attributes)
end

def to_s
s = ["Installed"]
s << "using the formulae.brew.sh API" if loaded_from_api
s << Time.at(time).strftime("on %Y-%m-%d at %H:%M:%S") if time
s.join(" ")
end
end
end
2 changes: 1 addition & 1 deletion Library/Homebrew/dev-cmd/bottle.rb
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,7 @@ def bottle_formula(formula)
tab.time = nil
tab.changed_files = changed_files.dup
if args.only_json_tab?
tab.changed_files.delete(Pathname.new(Tab::FILENAME))
tab.changed_files.delete(Pathname.new(AbstractTab::FILENAME))
tab.tabfile.unlink
else
tab.write
Expand Down
2 changes: 1 addition & 1 deletion Library/Homebrew/formula.rb
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,7 @@ def latest_version_installed?
# If at least one version of {Formula} is installed.
sig { returns(T::Boolean) }
def any_version_installed?
installed_prefixes.any? { |keg| (keg/Tab::FILENAME).file? }
installed_prefixes.any? { |keg| (keg/AbstractTab::FILENAME).file? }
end

# The link status symlink directory for this {Formula}.
Expand Down
3 changes: 3 additions & 0 deletions Library/Homebrew/sorbet/rbi/parlour.rbi
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,9 @@ module Cask
sig { returns(T::Boolean) }
def installed_as_dependency?; end

sig { returns(T::Boolean) }
def installed_on_request?; end

sig { returns(T::Boolean) }
def quarantine?; end

Expand Down
Loading

0 comments on commit acd6018

Please sign in to comment.