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

Add cask install receipts #17554

Merged
merged 11 commits into from
Jul 13, 2024
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
Rylan12 marked this conversation as resolved.
Show resolved Hide resolved

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
Rylan12 marked this conversation as resolved.
Show resolved Hide resolved
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
11 changes: 8 additions & 3 deletions Library/Homebrew/cask/info.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def self.get_info(cask)
output << "#{Formatter.url(cask.homepage)}\n" if cask.homepage
deprecate_disable = DeprecateDisable.message(cask)
output << "#{deprecate_disable.capitalize}\n" if deprecate_disable
output << installation_info(cask)
output << "#{installation_info(cask)}\n"
repo = repo_info(cask)
output << "#{repo}\n" if repo
output << name_info(cask)
Expand All @@ -37,7 +37,7 @@ def self.title_info(cask)
end

def self.installation_info(cask)
return "Not installed\n" unless cask.installed?
return "Not installed" unless cask.installed?

versioned_staged_path = cask.caskroom_path.join(cask.installed_version)
path_details = if versioned_staged_path.exist?
Expand All @@ -46,7 +46,12 @@ def self.installation_info(cask)
Formatter.error("does not exist")
end

"Installed\n#{versioned_staged_path} (#{path_details})\n"
tab = Tab.for_cask(cask)
Rylan12 marked this conversation as resolved.
Show resolved Hide resolved

info = ["Installed"]
info << "#{versioned_staged_path} (#{path_details})"
info << " #{tab}" if tab.tabfile&.exist?
info.join("\n")
end

def self.name_info(cask)
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?
Rylan12 marked this conversation as resolved.
Show resolved Hide resolved
@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
112 changes: 112 additions & 0 deletions Library/Homebrew/cask/tab.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# 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)
tab = super

tab.tabfile = cask.metadata_main_container_path/FILENAME
tab.uninstall_flight_blocks = cask.uninstall_flight_blocks?
tab.runtime_dependencies = Tab.runtime_deps_hash(cask, cask.depends_on)
tab.source["version"] = cask.version.to_s
tab.source["path"] = cask.sourcefile_path.to_s
tab.uninstall_artifacts = cask.artifacts_list(uninstall_only: true)

tab
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" => tap_git_head(cask),
"version" => cask.version.to_s,
}
tab.uninstall_artifacts = cask.artifacts_list(uninstall_only: true)

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
apainintheneck marked this conversation as resolved.
Show resolved Hide resolved
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"]
Rylan12 marked this conversation as resolved.
Show resolved Hide resolved
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
Loading