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 Jun 22, 2024
1 parent 2974be8 commit 93f8bf2
Show file tree
Hide file tree
Showing 6 changed files with 216 additions and 4 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"
5 changes: 5 additions & 0 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 @@ -209,6 +210,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
5 changes: 4 additions & 1 deletion Library/Homebrew/cask/info.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ def self.installation_info(cask)
Formatter.error("does not exist")
end

"Installed\n#{versioned_staged_path} (#{path_details})\n"
tab = Tab.for_cask(cask)
tab_details = "\n #{tab}" if tab.tabfile&.exist?

"Installed\n#{versioned_staged_path} (#{path_details})#{tab_details}\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?
@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
185 changes: 185 additions & 0 deletions Library/Homebrew/cask/tab.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
# typed: true
# frozen_string_literal: true

module Cask
class Tab
extend Cachable

FILENAME = "INSTALL_RECEIPT.json"

attr_accessor :homebrew_version, :tabfile, :loaded_from_api, :installed_as_dependency, :installed_on_request,
:time, :dependencies, :arch, :source, :installed_on, :artifacts

# Instantiates a {Tab} for a new installation of a cask.
sig { params(cask: Cask).returns(Tab) }
def self.create(cask)
attributes = {
"homebrew_version" => HOMEBREW_VERSION,
"tabfile" => cask.metadata_main_container_path/FILENAME,
"loaded_from_api" => cask.loaded_from_api?,
"installed_as_dependency" => false,
"installed_on_request" => false,
"time" => Time.now.to_i,
"dependencies" => Tab.runtime_deps_hash(cask, cask.depends_on),
"arch" => Hardware::CPU.arch,
"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,
},
"installed_on" => DevelopmentTools.build_system_info,
"artifacts" => cask.to_h["artifacts"],
}

# 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 the {Tab} for an install receipt at `path`.
#
# NOTE: Results are cached.
sig { params(path: Pathname).returns(Tab) }
def self.from_file(path)
cache.fetch(path) do |p|
content = File.read(p)
return empty if content.blank?

cache[p] = from_file_content(content, p)
end
end

# Like {from_file}, but bypass the cache.
sig { params(content: String, path: Pathname).returns(Tab) }
def self.from_file_content(content, path)
attributes = begin
JSON.parse(content)
rescue JSON::ParserError => e
raise e, "Cannot parse #{path}: #{e}", e.backtrace
end
attributes["tabfile"] = path

new(attributes)
end

sig { params(cask: Cask).returns(Tab) }
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.artifacts = cask.to_h["artifacts"]
tab.source["tap_git_head"] = cask.tap.git_head if cask.tap&.installed?

tab
end

sig { returns(Tab) }
def self.empty
attributes = {
"homebrew_version" => HOMEBREW_VERSION,
"loaded_from_api" => false,
"installed_as_dependency" => false,
"installed_on_request" => false,
"time" => nil,
"dependencies" => nil,
"arch" => nil,
"source" => {
"path" => nil,
"tap" => nil,
"tap_git_head" => nil,
"version" => nil,
},
"installed_on" => DevelopmentTools.generic_build_system_info,
"artifacts" => [],
}

new(attributes)
end

sig { params(cask: Cask, depends_on: DSL::DependsOn).returns(T::Hash[Symbol, T.untyped]) }
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 initialize(attributes = {})
attributes.each { |key, value| instance_variable_set(:"@#{key}", value) }
end

sig { returns(T.nilable(Tap)) }
def tap
tap_name = source["tap"]
Tap.fetch(tap_name) if tap_name
end

sig { params(_args: T::Array[T.untyped]).returns(String) }
def to_json(*_args)
attributes = {
"homebrew_version" => homebrew_version,
"loaded_from_api" => loaded_from_api,
"installed_as_dependency" => installed_as_dependency,
"installed_on_request" => installed_on_request,
"time" => time,
"dependencies" => dependencies,
"arch" => arch,
"source" => source,
"installed_on" => installed_on,
"artifacts" => artifacts,
}

JSON.pretty_generate(attributes)
end

sig { void }
def write
self.class.cache[tabfile] = self
tabfile.atomic_write(to_json)
end

sig { returns(String) }
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
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

0 comments on commit 93f8bf2

Please sign in to comment.