Treat every Artifact instance as a single artifact.

This commit is contained in:
Markus Reiter 2017-04-06 00:33:31 +02:00
parent 5bcce735dd
commit 53ecdd843f
43 changed files with 560 additions and 540 deletions

View File

@ -33,7 +33,7 @@ module Hbc
# We want to extract nested containers before we # We want to extract nested containers before we
# handle any other artifacts. # handle any other artifacts.
# #
TYPES = [ CLASSES = [
PreflightBlock, PreflightBlock,
Uninstall, Uninstall,
NestedContainer, NestedContainer,
@ -60,12 +60,9 @@ module Hbc
Zap, Zap,
].freeze ].freeze
def self.for_cask(cask, options = {}) def self.for_cask(cask)
odebug "Determining which artifacts are present in Cask #{cask}" odebug "Determining which artifacts are present in Cask #{cask}"
CLASSES.flat_map { |klass| klass.for_cask(cask) }
TYPES
.select { |klass| klass.me?(cask) }
.map { |klass| klass.new(cask, options) }
end end
end end
end end

View File

@ -1,34 +1,28 @@
module Hbc module Hbc
module Artifact module Artifact
class Base class AbstractArtifact
extend Predicable extend Predicable
def self.artifact_name def self.english_name
@artifact_name ||= name.sub(/^.*:/, "").gsub(/(.)([A-Z])/, '\1_\2').downcase @english_name ||= name.sub(/^.*:/, "").gsub(/(.)([A-Z])/, '\1 \2')
end end
def self.artifact_english_name def self.english_article
@artifact_english_name ||= name.sub(/^.*:/, "").gsub(/(.)([A-Z])/, '\1 \2') @english_article ||= (english_name =~ /^[aeiou]/i) ? "an" : "a"
end end
def self.artifact_english_article def self.dsl_key
@artifact_english_article ||= (artifact_english_name =~ /^[aeiou]/i) ? "an" : "a" @dsl_key ||= name.sub(/^.*:/, "").gsub(/(.)([A-Z])/, '\1_\2').downcase.to_sym
end end
def self.artifact_dsl_key def self.dirmethod
@artifact_dsl_key ||= artifact_name.to_sym @dirmethod ||= "#{dsl_key}dir".to_sym
end end
def self.artifact_dirmethod def self.for_cask(cask)
@artifact_dirmethod ||= "#{artifact_name}dir".to_sym cask.artifacts[dsl_key].to_a
end end
def self.me?(cask)
cask.artifacts[artifact_dsl_key].any?
end
attr_reader :force
# TODO: this sort of logic would make more sense in dsl.rb, or a # TODO: this sort of logic would make more sense in dsl.rb, or a
# constructor called from dsl.rb, so long as that isn't slow. # constructor called from dsl.rb, so long as that isn't slow.
def self.read_script_arguments(arguments, stanza, default_arguments = {}, override_arguments = {}, key = nil) def self.read_script_arguments(arguments, stanza, default_arguments = {}, override_arguments = {}, key = nil)
@ -63,17 +57,14 @@ module Hbc
[executable, arguments] [executable, arguments]
end end
def summary attr_reader :cask
{}
def initialize(cask)
@cask = cask
end end
attr_predicate :force?, :verbose? def to_s
"#{summarize} (#{self.class.english_name})"
def initialize(cask, command: SystemCommand, force: false, verbose: false)
@cask = cask
@command = command
@force = force
@verbose = verbose
end end
end end
end end

View File

@ -1,39 +1,47 @@
require "hbc/artifact/base" require "hbc/artifact/abstract_artifact"
module Hbc module Hbc
module Artifact module Artifact
class AbstractFlightBlock < Base class AbstractFlightBlock < AbstractArtifact
def self.artifact_dsl_key def self.dsl_key
super.to_s.sub(/_block$/, "").to_sym super.to_s.sub(/_block$/, "").to_sym
end end
def self.uninstall_artifact_dsl_key def self.uninstall_dsl_key
artifact_dsl_key.to_s.prepend("uninstall_").to_sym dsl_key.to_s.prepend("uninstall_").to_sym
end end
def self.class_for_dsl_key(dsl_key) def self.for_cask(cask)
Object.const_get("Hbc::DSL::#{dsl_key.to_s.split("_").collect(&:capitalize).join}") [dsl_key, uninstall_dsl_key].flat_map do |key|
[*cask.artifacts[key]].map { |block| new(cask, key => block) }
end
end end
def self.me?(cask) attr_reader :directives
cask.artifacts[artifact_dsl_key].any? ||
cask.artifacts[uninstall_artifact_dsl_key].any? def initialize(cask, **directives)
super(cask)
@directives = directives
end end
def install_phase def install_phase(**)
abstract_phase(self.class.artifact_dsl_key) abstract_phase(self.class.dsl_key)
end end
def uninstall_phase def uninstall_phase(**)
abstract_phase(self.class.uninstall_artifact_dsl_key) abstract_phase(self.class.uninstall_dsl_key)
end end
private private
def class_for_dsl_key(dsl_key)
namespace = self.class.name.to_s.sub(/::.*::.*$/, "")
self.class.const_get("#{namespace}::DSL::#{dsl_key.to_s.split("_").collect(&:capitalize).join}")
end
def abstract_phase(dsl_key) def abstract_phase(dsl_key)
@cask.artifacts[dsl_key].each do |block| return if (block = directives[dsl_key]).nil?
self.class.class_for_dsl_key(dsl_key).new(@cask).instance_eval(&block) class_for_dsl_key(dsl_key).new(cask).instance_eval(&block)
end
end end
end end
end end

View File

@ -1,11 +1,11 @@
require "pathname" require "pathname"
require "timeout" require "timeout"
require "hbc/artifact/base" require "hbc/artifact/abstract_artifact"
module Hbc module Hbc
module Artifact module Artifact
class UninstallBase < Base class AbstractUninstall < AbstractArtifact
ORDERED_DIRECTIVES = [ ORDERED_DIRECTIVES = [
:early_script, :early_script,
:launchctl, :launchctl,
@ -20,26 +20,33 @@ module Hbc
:rmdir, :rmdir,
].freeze ].freeze
def dispatch_uninstall_directives def self.from_args(cask, **directives)
directives_set = @cask.artifacts[stanza] new(cask, directives)
ohai "Running #{stanza} process for #{@cask}; your password may be necessary" end
directives_set.each do |directives| attr_reader :directives
warn_for_unknown_directives(directives)
end
ORDERED_DIRECTIVES.each do |directive_sym| def initialize(cask, directives)
directives_set.select { |h| h.key?(directive_sym) }.each do |directives| super(cask)
args = directives[directive_sym] @directives = directives
send("uninstall_#{directive_sym}", *(args.is_a?(Hash) ? [args] : args))
end
end
end end
private private
def dispatch_uninstall_directives(**options)
ohai "Running #{stanza} process for #{@cask}; your password may be necessary"
warn_for_unknown_directives(directives)
ORDERED_DIRECTIVES.each do |directive_sym|
next unless directives.key?(directive_sym)
args = directives[directive_sym]
send("uninstall_#{directive_sym}", *(args.is_a?(Hash) ? [args] : args), **options)
end
end
def stanza def stanza
self.class.artifact_dsl_key self.class.dsl_key
end end
def warn_for_unknown_directives(directives) def warn_for_unknown_directives(directives)
@ -51,18 +58,18 @@ module Hbc
# Preserve prior functionality of script which runs first. Should rarely be needed. # Preserve prior functionality of script which runs first. Should rarely be needed.
# :early_script should not delete files, better defer that to :script. # :early_script should not delete files, better defer that to :script.
# If Cask writers never need :early_script it may be removed in the future. # If Cask writers never need :early_script it may be removed in the future.
def uninstall_early_script(directives) def uninstall_early_script(directives, **options)
uninstall_script(directives, directive_name: :early_script) uninstall_script(directives, directive_name: :early_script, **options)
end end
# :launchctl must come before :quit/:signal for cases where app would instantly re-launch # :launchctl must come before :quit/:signal for cases where app would instantly re-launch
def uninstall_launchctl(*services) def uninstall_launchctl(*services, command: nil, **_)
services.each do |service| services.each do |service|
ohai "Removing launchctl service #{service}" ohai "Removing launchctl service #{service}"
[false, true].each do |with_sudo| [false, true].each do |with_sudo|
plist_status = @command.run("/bin/launchctl", args: ["list", service], sudo: with_sudo, print_stderr: false).stdout plist_status = command.run("/bin/launchctl", args: ["list", service], sudo: with_sudo, print_stderr: false).stdout
if plist_status =~ /^\{/ if plist_status =~ /^\{/
@command.run!("/bin/launchctl", args: ["remove", service], sudo: with_sudo) command.run!("/bin/launchctl", args: ["remove", service], sudo: with_sudo)
sleep 1 sleep 1
end end
paths = ["/Library/LaunchAgents/#{service}.plist", paths = ["/Library/LaunchAgents/#{service}.plist",
@ -70,38 +77,38 @@ module Hbc
paths.each { |elt| elt.prepend(ENV["HOME"]) } unless with_sudo paths.each { |elt| elt.prepend(ENV["HOME"]) } unless with_sudo
paths = paths.map { |elt| Pathname(elt) }.select(&:exist?) paths = paths.map { |elt| Pathname(elt) }.select(&:exist?)
paths.each do |path| paths.each do |path|
@command.run!("/bin/rm", args: ["-f", "--", path], sudo: with_sudo) command.run!("/bin/rm", args: ["-f", "--", path], sudo: with_sudo)
end end
# undocumented and untested: pass a path to uninstall :launchctl # undocumented and untested: pass a path to uninstall :launchctl
next unless Pathname(service).exist? next unless Pathname(service).exist?
@command.run!("/bin/launchctl", args: ["unload", "-w", "--", service], sudo: with_sudo) command.run!("/bin/launchctl", args: ["unload", "-w", "--", service], sudo: with_sudo)
@command.run!("/bin/rm", args: ["-f", "--", service], sudo: with_sudo) command.run!("/bin/rm", args: ["-f", "--", service], sudo: with_sudo)
sleep 1 sleep 1
end end
end end
end end
def running_processes(bundle_id) def running_processes(bundle_id, command: nil)
@command.run!("/bin/launchctl", args: ["list"]).stdout.lines command.run!("/bin/launchctl", args: ["list"]).stdout.lines
.map { |line| line.chomp.split("\t") } .map { |line| line.chomp.split("\t") }
.map { |pid, state, id| [pid.to_i, state.to_i, id] } .map { |pid, state, id| [pid.to_i, state.to_i, id] }
.select do |fields| .select do |fields|
next if fields[0].zero? next if fields[0].zero?
fields[2] =~ /^#{Regexp.escape(bundle_id)}($|\.\d+)/ fields[2] =~ /^#{Regexp.escape(bundle_id)}($|\.\d+)/
end end
end end
# :quit/:signal must come before :kext so the kext will not be in use by a running process # :quit/:signal must come before :kext so the kext will not be in use by a running process
def uninstall_quit(*bundle_ids) def uninstall_quit(*bundle_ids, command: nil, **_)
bundle_ids.each do |bundle_id| bundle_ids.each do |bundle_id|
ohai "Quitting application ID #{bundle_id}" ohai "Quitting application ID #{bundle_id}"
next if running_processes(bundle_id).empty? next if running_processes(bundle_id, command: command).empty?
@command.run!("/usr/bin/osascript", args: ["-e", %Q(tell application id "#{bundle_id}" to quit)], sudo: true) command.run!("/usr/bin/osascript", args: ["-e", %Q(tell application id "#{bundle_id}" to quit)], sudo: true)
begin begin
Timeout.timeout(3) do Timeout.timeout(3) do
Kernel.loop do Kernel.loop do
break if running_processes(bundle_id).empty? break if running_processes(bundle_id, command: command).empty?
end end
end end
rescue Timeout::Error rescue Timeout::Error
@ -111,15 +118,15 @@ module Hbc
end end
# :signal should come after :quit so it can be used as a backup when :quit fails # :signal should come after :quit so it can be used as a backup when :quit fails
def uninstall_signal(*signals) def uninstall_signal(*signals, **options)
signals.flatten.each_slice(2) do |pair| signals.flatten.each_slice(2) do |pair|
unless pair.size == 2 unless pair.size == 2
raise CaskInvalidError.new(@cask, "Each #{stanza} :signal must consist of 2 elements.") raise CaskInvalidError.new(cask, "Each #{stanza} :signal must consist of 2 elements.")
end end
signal, bundle_id = pair signal, bundle_id = pair
ohai "Signalling '#{signal}' to application ID '#{bundle_id}'" ohai "Signalling '#{signal}' to application ID '#{bundle_id}'"
pids = running_processes(bundle_id).map(&:first) pids = running_processes(bundle_id, **options).map(&:first)
next unless pids.any? next unless pids.any?
# Note that unlike :quit, signals are sent from the current user (not # Note that unlike :quit, signals are sent from the current user (not
# upgraded to the superuser). This is a todo item for the future, but # upgraded to the superuser). This is a todo item for the future, but
@ -133,10 +140,10 @@ module Hbc
end end
end end
def uninstall_login_item(*login_items) def uninstall_login_item(*login_items, command: nil, **_)
login_items.each do |name| login_items.each do |name|
ohai "Removing login item #{name}" ohai "Removing login item #{name}"
@command.run!("/usr/bin/osascript", command.run!("/usr/bin/osascript",
args: ["-e", %Q(tell application "System Events" to delete every login item whose name is "#{name}")], args: ["-e", %Q(tell application "System Events" to delete every login item whose name is "#{name}")],
sudo: false) sudo: false)
sleep 1 sleep 1
@ -144,23 +151,24 @@ module Hbc
end end
# :kext should be unloaded before attempting to delete the relevant file # :kext should be unloaded before attempting to delete the relevant file
def uninstall_kext(*kexts) def uninstall_kext(*kexts, command: nil, **_)
kexts.each do |kext| kexts.each do |kext|
ohai "Unloading kernel extension #{kext}" ohai "Unloading kernel extension #{kext}"
is_loaded = @command.run!("/usr/sbin/kextstat", args: ["-l", "-b", kext], sudo: true).stdout is_loaded = command.run!("/usr/sbin/kextstat", args: ["-l", "-b", kext], sudo: true).stdout
if is_loaded.length > 1 if is_loaded.length > 1
@command.run!("/sbin/kextunload", args: ["-b", kext], sudo: true) command.run!("/sbin/kextunload", args: ["-b", kext], sudo: true)
sleep 1 sleep 1
end end
@command.run!("/usr/sbin/kextfind", args: ["-b", kext], sudo: true).stdout.chomp.lines.each do |kext_path| command.run!("/usr/sbin/kextfind", args: ["-b", kext], sudo: true).stdout.chomp.lines.each do |kext_path|
ohai "Removing kernel extension #{kext_path}" ohai "Removing kernel extension #{kext_path}"
@command.run!("/bin/rm", args: ["-rf", kext_path], sudo: true) command.run!("/bin/rm", args: ["-rf", kext_path], sudo: true)
end end
end end
end end
# :script must come before :pkgutil, :delete, or :trash so that the script file is not already deleted # :script must come before :pkgutil, :delete, or :trash so that the script file is not already deleted
def uninstall_script(directives, directive_name: :script) def uninstall_script(directives, directive_name: :script, force: false, command: nil, **_)
# TODO: Create a common `Script` class to run this and Artifact::Installer.
executable, script_arguments = self.class.read_script_arguments(directives, executable, script_arguments = self.class.read_script_arguments(directives,
"uninstall", "uninstall",
{ must_succeed: true, sudo: false }, { must_succeed: true, sudo: false },
@ -168,25 +176,25 @@ module Hbc
directive_name) directive_name)
ohai "Running uninstall script #{executable}" ohai "Running uninstall script #{executable}"
raise CaskInvalidError.new(@cask, "#{stanza} :#{directive_name} without :executable.") if executable.nil? raise CaskInvalidError.new(cask, "#{stanza} :#{directive_name} without :executable.") if executable.nil?
executable_path = @cask.staged_path.join(executable) executable_path = cask.staged_path.join(executable)
unless executable_path.exist? unless executable_path.exist?
message = "uninstall script #{executable} does not exist" message = "uninstall script #{executable} does not exist"
raise CaskError, "#{message}." unless force? raise CaskError, "#{message}." unless force
opoo "#{message}, skipping." opoo "#{message}, skipping."
return return
end end
@command.run("/bin/chmod", args: ["--", "+x", executable_path]) command.run("/bin/chmod", args: ["--", "+x", executable_path])
@command.run(executable_path, script_arguments) command.run(executable_path, script_arguments)
sleep 1 sleep 1
end end
def uninstall_pkgutil(*pkgs) def uninstall_pkgutil(*pkgs, command: nil, **_)
ohai "Uninstalling packages:" ohai "Uninstalling packages:"
pkgs.each do |regex| pkgs.each do |regex|
Hbc::Pkg.all_matching(regex, @command).each do |pkg| Hbc::Pkg.all_matching(regex, command).each do |pkg|
puts pkg.package_id puts pkg.package_id
pkg.uninstall pkg.uninstall
end end
@ -215,28 +223,28 @@ module Hbc
end end
end end
def uninstall_delete(*paths) def uninstall_delete(*paths, command: nil, **_)
return if paths.empty? return if paths.empty?
ohai "Removing files:" ohai "Removing files:"
each_resolved_path(:delete, paths) do |path, resolved_paths| each_resolved_path(:delete, paths) do |path, resolved_paths|
puts path puts path
@command.run!("/usr/bin/xargs", args: ["-0", "--", "/bin/rm", "-r", "-f", "--"], input: resolved_paths.join("\0"), sudo: true) command.run!("/usr/bin/xargs", args: ["-0", "--", "/bin/rm", "-r", "-f", "--"], input: resolved_paths.join("\0"), sudo: true)
end end
end end
def uninstall_trash(*paths) def uninstall_trash(*paths, **options)
return if paths.empty? return if paths.empty?
resolved_paths = each_resolved_path(:trash, paths).to_a resolved_paths = each_resolved_path(:trash, paths).to_a
ohai "Trashing files:" ohai "Trashing files:"
puts resolved_paths.map(&:first) puts resolved_paths.map(&:first)
trash_paths(*resolved_paths.flat_map(&:last)) trash_paths(*resolved_paths.flat_map(&:last), **options)
end end
def trash_paths(*paths) def trash_paths(*paths, command: nil, **_)
@command.run!("/usr/bin/osascript", args: ["-e", <<-'EOS'.undent, *paths]) command.run!("/usr/bin/osascript", args: ["-e", <<-'EOS'.undent, *paths])
on run argv on run argv
repeat with i from 1 to (count argv) repeat with i from 1 to (count argv)
set item i of argv to (item i of argv as POSIX file) set item i of argv to (item i of argv as POSIX file)
@ -260,7 +268,7 @@ module Hbc
EOS EOS
end end
def uninstall_rmdir(*directories) def uninstall_rmdir(*directories, command: nil, **_)
return if directories.empty? return if directories.empty?
ohai "Removing directories if empty:" ohai "Removing directories if empty:"
@ -268,10 +276,10 @@ module Hbc
puts path puts path
resolved_paths.select(&:directory?).each do |resolved_path| resolved_paths.select(&:directory?).each do |resolved_path|
if (ds_store = resolved_path.join(".DS_Store")).exist? if (ds_store = resolved_path.join(".DS_Store")).exist?
@command.run!("/bin/rm", args: ["-f", "--", ds_store], sudo: true, print_stderr: false) command.run!("/bin/rm", args: ["-f", "--", ds_store], sudo: true, print_stderr: false)
end end
@command.run("/bin/rmdir", args: ["--", resolved_path], sudo: true, print_stderr: false) command.run("/bin/rmdir", args: ["--", resolved_path], sudo: true, print_stderr: false)
end end
end end
end end

View File

@ -5,21 +5,32 @@ require "hbc/utils/hash_validator"
module Hbc module Hbc
module Artifact module Artifact
class Artifact < Moved class Artifact < Moved
def self.artifact_english_name def self.english_name
"Generic Artifact" "Generic Artifact"
end end
def self.artifact_dirmethod def self.from_args(cask, *args)
:appdir source_string, target_hash = args
if source_string.nil?
raise CaskInvalidError.new(cask.token, "no source given for #{english_name}")
end
unless target_hash.is_a?(Hash)
raise CaskInvalidError.new(cask.token, "target required for #{english_name} '#{source_string}'")
end
target_hash.extend(HashValidator).assert_valid_keys(:target)
new(cask, source_string, **target_hash)
end end
def load_specification(artifact_spec) def self.resolve_target(target)
source_string, target_hash = artifact_spec Pathname(target)
raise CaskInvalidError.new(@cask.token, "no source given for artifact") if source_string.nil? end
@source = @cask.staged_path.join(source_string)
raise CaskInvalidError.new(@cask.token, "target required for generic artifact #{source_string}") unless target_hash.is_a?(Hash) def initialize(cask, source, target: nil)
target_hash.extend(HashValidator).assert_valid_keys(:target) super(cask, source, target: target)
@target = Pathname.new(target_hash[:target])
end end
end end
end end

View File

@ -3,13 +3,13 @@ require "hbc/artifact/symlinked"
module Hbc module Hbc
module Artifact module Artifact
class Binary < Symlinked class Binary < Symlinked
def link def link(command: nil, **options)
super super(command: command, **options)
return if source.executable? return if source.executable?
if source.writable? if source.writable?
FileUtils.chmod "+x", source FileUtils.chmod "+x", source
else else
@command.run!("/bin/chmod", args: ["+x", source], sudo: true) command.run!("/bin/chmod", args: ["+x", source], sudo: true)
end end
end end
end end

View File

@ -1,31 +1,78 @@
require "hbc/artifact/base" require "hbc/artifact/abstract_artifact"
module Hbc module Hbc
module Artifact module Artifact
class Installer < Base class Installer < AbstractArtifact
def install_phase VALID_KEYS = Set.new [
@cask.artifacts[self.class.artifact_dsl_key].each do |artifact| :manual,
if artifact.manual :script,
puts <<-EOS.undent ]
To complete the installation of Cask #{@cask}, you must also
run the installer at
'#{@cask.staged_path.join(artifact.manual)}' module ManualInstaller
def install_phase(**)
puts <<-EOS.undent
To complete the installation of Cask #{cask}, you must also
run the installer at
EOS '#{path}'
else EOS
executable, script_arguments = self.class.read_script_arguments(artifact.script,
self.class.artifact_dsl_key.to_s,
{ must_succeed: true, sudo: false },
print_stdout: true)
ohai "Running #{self.class.artifact_dsl_key} script #{executable}"
raise CaskInvalidError.new(@cask, "#{self.class.artifact_dsl_key} missing executable") if executable.nil?
executable_path = @cask.staged_path.join(executable)
@command.run("/bin/chmod", args: ["--", "+x", executable_path]) if File.exist?(executable_path)
@command.run(executable_path, script_arguments)
end
end end
end end
module ScriptInstaller
def install_phase(command: nil, **_)
ohai "Running #{self.class.dsl_key} script '#{path.relative_path_from(cask.staged_path)}'"
FileUtils.chmod "+x", path unless path.executable?
command.run(path, **args)
end
end
def self.from_args(cask, **args)
raise CaskInvalidError.new(cask, "'installer' stanza requires an argument.") if args.empty?
if args.key?(:script) && !args[:script].respond_to?(:key?)
if args.key?(:executable)
raise CaskInvalidError.new(cask, "'installer' stanza gave arguments for both :script and :executable.")
end
args[:executable] = args[:script]
args.delete(:script)
args = { script: args }
end
unless args.keys.count == 1
raise CaskInvalidError.new(cask, "invalid 'installer' stanza: Only one of #{VALID_KEYS.inspect} is permitted.")
end
args.extend(HashValidator).assert_valid_keys(*VALID_KEYS)
new(cask, **args)
end
attr_reader :path, :args
def initialize(cask, **args)
super(cask)
if args.key?(:manual)
@path = cask.staged_path.join(args[:manual])
@args = []
extend(ManualInstaller)
return
end
path, @args = self.class.read_script_arguments(
args[:script], self.class.dsl_key.to_s, { must_succeed: true, sudo: false }, print_stdout: true
)
raise CaskInvalidError.new(cask, "#{self.class.dsl_key} missing executable") if path.nil?
path = Pathname(path)
@path = path.absolute? ? path : cask.staged_path.join(path)
extend(ScriptInstaller)
end
def summarize
path.relative_path_from(cask.staged_path).to_s
end
end end
end end
end end

View File

@ -4,63 +4,61 @@ module Hbc
module Artifact module Artifact
class Moved < Relocated class Moved < Relocated
def self.english_description def self.english_description
"#{artifact_english_name}s" "#{english_name}s"
end end
def install_phase def install_phase(**options)
each_artifact(&method(:move)) move(**options)
end end
def uninstall_phase def uninstall_phase(**options)
each_artifact(&method(:delete)) delete(**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 end
private private
def move def move(force: false, command: nil, **options)
if Utils.path_occupied?(target) if Utils.path_occupied?(target)
message = "It seems there is already #{self.class.artifact_english_article} #{self.class.artifact_english_name} at '#{target}'" message = "It seems there is already #{self.class.english_article} #{self.class.english_name} at '#{target}'"
raise CaskError, "#{message}." unless force? raise CaskError, "#{message}." unless force
opoo "#{message}; overwriting." opoo "#{message}; overwriting."
delete delete(force: force, command: command, **options)
end end
unless source.exist? unless source.exist?
raise CaskError, "It seems the #{self.class.artifact_english_name} source '#{source}' is not there." raise CaskError, "It seems the #{self.class.english_name} source '#{source}' is not there."
end end
ohai "Moving #{self.class.artifact_english_name} '#{source.basename}' to '#{target}'." ohai "Moving #{self.class.english_name} '#{source.basename}' to '#{target}'."
target.dirname.mkpath target.dirname.mkpath
if target.parent.writable? if target.parent.writable?
FileUtils.move(source, target) FileUtils.move(source, target)
else else
SystemCommand.run("/bin/mv", args: [source, target], sudo: true) command.run("/bin/mv", args: [source, target], sudo: true)
end end
add_altname_metadata target, source.basename.to_s add_altname_metadata(target, source.basename, command: command)
end end
def delete def delete(force: false, command: nil, **_)
ohai "Removing #{self.class.artifact_english_name} '#{target}'." ohai "Removing #{self.class.english_name} '#{target}'."
raise CaskError, "Cannot remove undeletable #{self.class.artifact_english_name}." if MacOS.undeletable?(target) raise CaskError, "Cannot remove undeletable #{self.class.english_name}." if MacOS.undeletable?(target)
return unless Utils.path_occupied?(target) return unless Utils.path_occupied?(target)
if target.parent.writable? && !force if target.parent.writable? && !force
target.rmtree target.rmtree
else else
Utils.gain_permissions_remove(target, command: @command) Utils.gain_permissions_remove(target, command: command)
end
end
def summarize_artifact(artifact_spec)
load_specification artifact_spec
if target.exist?
"#{printable_target} (#{target.abv})"
else
Formatter.error(printable_target, label: "Missing #{self.class.artifact_english_name}")
end end
end end
end end

View File

@ -1,23 +1,31 @@
require "hbc/artifact/base" require "hbc/artifact/abstract_artifact"
module Hbc module Hbc
module Artifact module Artifact
class NestedContainer < Base class NestedContainer < AbstractArtifact
def install_phase attr_reader :path
@cask.artifacts[:nested_container].each { |container| extract(container) }
def initialize(cask, path)
super(cask)
@path = cask.staged_path.join(path)
end end
def extract(container_relative_path) def install_phase(**options)
source = @cask.staged_path.join(container_relative_path) extract(**options)
container = Container.for_path(source, @command) end
private
def extract(command: nil, verbose: nil, **_)
container = Container.for_path(path, command)
unless container unless container
raise CaskError, "Aw dang, could not identify nested container at '#{source}'" raise CaskError, "Aw dang, could not identify nested container at '#{source}'"
end end
ohai "Extracting nested container #{source.basename}" ohai "Extracting nested container #{path.relative_path_from(cask.staged_path)}"
container.new(@cask, source, @command, verbose: verbose?).extract container.new(cask, path, command, verbose: verbose).extract
FileUtils.remove_entry_secure(source) FileUtils.remove_entry_secure(path)
end end
end end
end end

View File

@ -1,4 +1,4 @@
require "hbc/artifact/base" require "hbc/artifact/abstract_artifact"
require "hbc/utils/hash_validator" require "hbc/utils/hash_validator"
@ -6,62 +6,57 @@ require "vendor/plist/plist"
module Hbc module Hbc
module Artifact module Artifact
class Pkg < Base class Pkg < AbstractArtifact
attr_reader :pkg_relative_path attr_reader :pkg_relative_path
def self.artifact_dsl_key def self.from_args(cask, path, **options)
:pkg options.extend(HashValidator).assert_valid_keys(:allow_untrusted, :choices)
new(cask, path, **options)
end end
def load_pkg_description(pkg_description) attr_reader :path, :options
@pkg_relative_path = pkg_description.shift
@pkg_install_opts = pkg_description.shift def initialize(cask, path, **options)
begin super(cask)
if @pkg_install_opts.respond_to?(:keys) @path = cask.staged_path.join(path)
@pkg_install_opts.extend(HashValidator).assert_valid_keys(:allow_untrusted, :choices) @options = options
elsif @pkg_install_opts
raise
end
raise if pkg_description.nil?
rescue StandardError
raise CaskInvalidError.new(@cask, "Bad pkg stanza")
end
end end
def pkg_install_opts(opt) def summarize
@pkg_install_opts[opt] if @pkg_install_opts.respond_to?(:keys) path.relative_path_from(cask.staged_path).to_s
end end
def install_phase def install_phase(**options)
@cask.artifacts[:pkg].each { |pkg_description| run_installer(pkg_description) } run_installer(**options)
end end
def run_installer(pkg_description) private
load_pkg_description pkg_description
ohai "Running installer for #{@cask}; your password may be necessary." def run_installer(command: nil, verbose: false, **options)
ohai "Running installer for #{cask}; your password may be necessary."
ohai "Package installers may write to any location; options such as --appdir are ignored." ohai "Package installers may write to any location; options such as --appdir are ignored."
source = @cask.staged_path.join(pkg_relative_path) unless path.exist?
unless source.exist? raise CaskError, "pkg source file not found: '#{path.relative_path_from(cask.staged_path)}'"
raise CaskError, "pkg source file not found: '#{source}'"
end end
args = [ args = [
"-pkg", source, "-pkg", path,
"-target", "/" "-target", "/"
] ]
args << "-verboseR" if verbose? args << "-verboseR" if verbose
args << "-allowUntrusted" if pkg_install_opts :allow_untrusted args << "-allowUntrusted" if options.fetch(:allow_untrusted, false)
with_choices_file do |choices_path| with_choices_file do |choices_path|
args << "-applyChoiceChangesXML" << choices_path if choices_path args << "-applyChoiceChangesXML" << choices_path if choices_path
@command.run!("/usr/sbin/installer", sudo: true, args: args, print_stdout: true) command.run!("/usr/sbin/installer", sudo: true, args: args, print_stdout: true)
end end
end end
def with_choices_file def with_choices_file
return yield nil unless pkg_install_opts(:choices) choices = options.fetch(:choices, {})
return yield nil if choices.empty?
Tempfile.open(["choices", ".xml"]) do |file| Tempfile.open(["choices", ".xml"]) do |file|
begin begin
file.write Plist::Emit.dump(pkg_install_opts(:choices)) file.write Plist::Emit.dump(choices)
file.close file.close
yield file.path yield file.path
ensure ensure

View File

@ -3,7 +3,7 @@ require "hbc/artifact/moved"
module Hbc module Hbc
module Artifact module Artifact
class Prefpane < Moved class Prefpane < Moved
def self.artifact_english_name def self.english_name
"Preference Pane" "Preference Pane"
end end
end end

View File

@ -3,22 +3,24 @@ require "hbc/artifact/moved"
module Hbc module Hbc
module Artifact module Artifact
class Qlplugin < Moved class Qlplugin < Moved
def self.artifact_english_name def self.english_name
"QuickLook Plugin" "QuickLook Plugin"
end end
def install_phase def install_phase(**options)
super super(**options)
reload_quicklook reload_quicklook(**options)
end end
def uninstall_phase def uninstall_phase(**options)
super super(**options)
reload_quicklook reload_quicklook(**options)
end end
def reload_quicklook private
@command.run!("/usr/bin/qlmanage", args: ["-r"])
def reload_quicklook(command: nil, **_)
command.run!("/usr/bin/qlmanage", args: ["-r"])
end end
end end
end end

View File

@ -1,33 +1,57 @@
require "hbc/artifact/base" require "hbc/artifact/abstract_artifact"
require "hbc/utils/hash_validator" require "hbc/utils/hash_validator"
module Hbc module Hbc
module Artifact module Artifact
class Relocated < Base class Relocated < AbstractArtifact
def summary def self.from_args(cask, *args)
{ source_string, target_hash = args
english_description: self.class.english_description,
contents: @cask.artifacts[self.class.artifact_dsl_key].map(&method(:summarize_artifact)).compact, if target_hash
} raise CaskInvalidError unless target_hash.respond_to?(:keys)
target_hash.extend(HashValidator).assert_valid_keys(:target)
end
target_hash ||= {}
new(cask, source_string, **target_hash)
end
def self.resolve_target(target)
Hbc.public_send(dirmethod).join(target)
end end
attr_reader :source, :target attr_reader :source, :target
def printable_target def initialize(cask, source, target: nil)
target.to_s.sub(/^#{ENV['HOME']}(#{File::SEPARATOR}|$)/, "~/") super(cask)
@source_string = source.to_s
@target_string = target.to_s
source = cask.staged_path.join(source)
@source = source
target ||= source.basename
@target = self.class.resolve_target(target)
end end
def summarize
target_string = @target_string.empty? ? "" : " -> #{@target_string}"
"#{@source_string}#{target_string}"
end
private
ALT_NAME_ATTRIBUTE = "com.apple.metadata:kMDItemAlternateNames".freeze ALT_NAME_ATTRIBUTE = "com.apple.metadata:kMDItemAlternateNames".freeze
# Try to make the asset searchable under the target name. Spotlight # Try to make the asset searchable under the target name. Spotlight
# respects this attribute for many filetypes, but ignores it for App # respects this attribute for many filetypes, but ignores it for App
# bundles. Alfred 2.2 respects it even for App bundles. # bundles. Alfred 2.2 respects it even for App bundles.
def add_altname_metadata(file, altname) def add_altname_metadata(file, altname, command: nil)
return if altname.casecmp(file.basename).zero? return if altname.to_s.casecmp(file.basename.to_s).zero?
odebug "Adding #{ALT_NAME_ATTRIBUTE} metadata" odebug "Adding #{ALT_NAME_ATTRIBUTE} metadata"
altnames = @command.run("/usr/bin/xattr", altnames = command.run("/usr/bin/xattr",
args: ["-p", ALT_NAME_ATTRIBUTE, file.to_s], args: ["-p", ALT_NAME_ATTRIBUTE, file],
print_stderr: false).stdout.sub(/\A\((.*)\)\Z/, '\1') print_stderr: false).stdout.sub(/\A\((.*)\)\Z/, '\1')
odebug "Existing metadata is: '#{altnames}'" odebug "Existing metadata is: '#{altnames}'"
altnames.concat(", ") unless altnames.empty? altnames.concat(", ") unless altnames.empty?
@ -35,31 +59,15 @@ module Hbc
altnames = "(#{altnames})" altnames = "(#{altnames})"
# Some packages are shipped as u=rx (e.g. Bitcoin Core) # Some packages are shipped as u=rx (e.g. Bitcoin Core)
@command.run!("/bin/chmod", args: ["--", "u+rw", file, file.realpath]) command.run!("/bin/chmod", args: ["--", "u+rw", file, file.realpath])
@command.run!("/usr/bin/xattr", command.run!("/usr/bin/xattr",
args: ["-w", ALT_NAME_ATTRIBUTE, altnames, file], args: ["-w", ALT_NAME_ATTRIBUTE, altnames, file],
print_stderr: false) print_stderr: false)
end end
def each_artifact def printable_target
@cask.artifacts[self.class.artifact_dsl_key].each do |artifact| target.to_s.sub(/^#{ENV['HOME']}(#{File::SEPARATOR}|$)/, "~/")
load_specification(artifact)
yield
end
end
def load_specification(artifact_spec)
source_string, target_hash = artifact_spec
raise CaskInvalidError if source_string.nil?
@source = @cask.staged_path.join(source_string)
if target_hash
raise CaskInvalidError unless target_hash.respond_to?(:keys)
target_hash.extend(HashValidator).assert_valid_keys(:target)
@target = Hbc.send(self.class.artifact_dirmethod).join(target_hash[:target])
else
@target = Hbc.send(self.class.artifact_dirmethod).join(source.basename)
end
end end
end end
end end

View File

@ -1,10 +1,18 @@
require "hbc/artifact/base" require "hbc/artifact/abstract_artifact"
module Hbc module Hbc
module Artifact module Artifact
class StageOnly < Base class StageOnly < AbstractArtifact
def self.artifact_dsl_key def self.from_args(cask, *args)
:stage_only if args != [true]
raise CaskInvalidError.new(cask.token, "'stage_only' takes only a single argument: true")
end
new(cask)
end
def initialize(cask)
super(cask)
end end
end end
end end

View File

@ -3,11 +3,11 @@ require "hbc/artifact/moved"
module Hbc module Hbc
module Artifact module Artifact
class Suite < Moved class Suite < Moved
def self.artifact_english_name def self.english_name
"App Suite" "App Suite"
end end
def self.artifact_dirmethod def self.dirmethod
:appdir :appdir
end end
end end

View File

@ -8,47 +8,18 @@ module Hbc
end end
def self.english_description def self.english_description
"#{artifact_english_name} #{link_type_english_name}s" "#{english_name} #{link_type_english_name}s"
end end
def install_phase def install_phase(**options)
each_artifact(&method(:link)) link(**options)
end end
def uninstall_phase def uninstall_phase(**options)
each_artifact(&method(:unlink)) unlink(**options)
end end
private def summarize_installed
def link
unless source.exist?
raise CaskError, "It seems the #{self.class.link_type_english_name.downcase} source '#{source}' is not there."
end
if target.exist? && !target.symlink?
raise CaskError, "It seems there is already #{self.class.artifact_english_article} #{self.class.artifact_english_name} at '#{target}'; not linking."
end
ohai "Linking #{self.class.artifact_english_name} '#{source.basename}' to '#{target}'."
create_filesystem_link(source, target)
end
def unlink
return unless target.symlink?
ohai "Unlinking #{self.class.artifact_english_name} '#{target}'."
target.delete
end
def create_filesystem_link(source, target)
target.dirname.mkpath
@command.run!("/bin/ln", args: ["-h", "-f", "-s", "--", source, target])
add_altname_metadata source, target.basename.to_s
end
def summarize_artifact(artifact_spec)
load_specification artifact_spec
if target.symlink? && target.exist? && target.readlink.exist? if target.symlink? && target.exist? && target.readlink.exist?
"#{printable_target} -> #{target.readlink} (#{target.readlink.abv})" "#{printable_target} -> #{target.readlink} (#{target.readlink.abv})"
else else
@ -61,6 +32,33 @@ module Hbc
Formatter.error(string, label: "Broken Link") Formatter.error(string, label: "Broken Link")
end end
end end
private
def link(**options)
unless source.exist?
raise CaskError, "It seems the #{self.class.link_type_english_name.downcase} source '#{source}' is not there."
end
if target.exist? && !target.symlink?
raise CaskError, "It seems there is already #{self.class.english_article} #{self.class.english_name} at '#{target}'; not linking."
end
ohai "Linking #{self.class.english_name} '#{source.basename}' to '#{target}'."
create_filesystem_link(**options)
end
def unlink(**)
return unless target.symlink?
ohai "Unlinking #{self.class.english_name} '#{target}'."
target.delete
end
def create_filesystem_link(command: nil, **_)
target.dirname.mkpath
command.run!("/bin/ln", args: ["-h", "-f", "-s", "--", source, target])
add_altname_metadata(source, target.basename, command: command)
end
end end
end end
end end

View File

@ -1,10 +1,10 @@
require "hbc/artifact/uninstall_base" require "hbc/artifact/abstract_uninstall"
module Hbc module Hbc
module Artifact module Artifact
class Uninstall < UninstallBase class Uninstall < AbstractUninstall
def uninstall_phase def uninstall_phase(**options)
dispatch_uninstall_directives dispatch_uninstall_directives(**options)
end end
end end
end end

View File

@ -1,10 +1,10 @@
require "hbc/artifact/uninstall_base" require "hbc/artifact/abstract_uninstall"
module Hbc module Hbc
module Artifact module Artifact
class Zap < UninstallBase class Zap < AbstractUninstall
def zap_phase def zap_phase(**options)
dispatch_uninstall_directives dispatch_uninstall_directives(**options)
end end
end end
end end

View File

@ -214,12 +214,10 @@ module Hbc
end end
def check_generic_artifacts def check_generic_artifacts
cask.artifacts[:artifact].each do |source, target_hash| cask.artifacts[:artifact].each do |artifact|
unless target_hash.is_a?(Hash) && target_hash[:target] unless artifact.target.absolute?
add_error "target required for generic artifact #{source}" add_error "target must be absolute path for #{artifact.class.english_name} #{artifact.source}"
next
end end
add_error "target must be absolute path for generic artifact #{source}" unless Pathname.new(target_hash[:target]).absolute?
end end
end end

View File

@ -17,7 +17,7 @@ module Hbc
@token = token @token = token
@sourcefile_path = sourcefile_path @sourcefile_path = sourcefile_path
@tap = tap @tap = tap
@dsl = DSL.new(@token) @dsl = DSL.new(self)
return unless block_given? return unless block_given?
@dsl.instance_eval(&block) @dsl.instance_eval(&block)
@dsl.language_eval @dsl.language_eval

View File

@ -69,13 +69,11 @@ module Hbc
def self.artifact_info(cask) def self.artifact_info(cask)
ohai "Artifacts" ohai "Artifacts"
DSL::ORDINARY_ARTIFACT_TYPES.each do |type| DSL::ORDINARY_ARTIFACT_CLASSES.flat_map { |klass| klass.for_cask(cask) }
next if cask.artifacts[type].empty? .select { |artifact| artifact.respond_to?(:install_phase) }
cask.artifacts[type].each do |artifact| .each do |artifact|
activatable_item = (type == :stage_only) ? "<none>" : artifact.first puts artifact.to_s
puts "#{activatable_item} (#{type})" end
end
end
end end
end end
end end

View File

@ -21,34 +21,9 @@ module Hbc
# brew cask _stanza artifacts --table --yaml alfred google-chrome adium voicemac logisim vagrant # brew cask _stanza artifacts --table --yaml alfred google-chrome adium voicemac logisim vagrant
# #
# TODO: this should be retrievable from Hbc::DSL ARTIFACTS =
ARTIFACTS = Set.new [ DSL::ORDINARY_ARTIFACT_CLASSES.map(&:dsl_key) +
:app, DSL::ARTIFACT_BLOCK_CLASSES.map(&:dsl_key)
:suite,
:artifact,
:prefpane,
:qlplugin,
:dictionary,
:font,
:service,
:colorpicker,
:binary,
:input_method,
:internet_plugin,
:audio_unit_plugin,
:vst_plugin,
:vst3_plugin,
:screen_saver,
:pkg,
:installer,
:stage_only,
:nested_container,
:uninstall,
:preflight,
:postflight,
:uninstall_preflight,
:uninstall_postflight,
]
option "--table", :table, false option "--table", :table, false
option "--quiet", :quiet, false option "--quiet", :quiet, false
@ -93,7 +68,7 @@ module Hbc
end end
begin begin
value = cask.send(@stanza) value = cask.send(stanza)
rescue StandardError rescue StandardError
opoo "failure calling '#{stanza}' on Cask '#{cask}'" unless quiet? opoo "failure calling '#{stanza}' on Cask '#{cask}'" unless quiet?
puts "" puts ""
@ -108,8 +83,8 @@ module Hbc
value = value.fetch(artifact_name).to_a.flatten if artifact_name value = value.fetch(artifact_name).to_a.flatten if artifact_name
if @format if format
puts value.send(@format) puts value.send(format)
elsif artifact_name || value.is_a?(Symbol) elsif artifact_name || value.is_a?(Symbol)
puts value.inspect puts value.inspect
else else

View File

@ -30,9 +30,9 @@ module Hbc
end end
def self.list_artifacts(cask) def self.list_artifacts(cask)
Artifact.for_cask(cask).each do |artifact| Artifact.for_cask(cask).group_by(&:class).each do |klass, artifacts|
summary = artifact.summary next unless klass.respond_to?(:english_description)
ohai summary[:english_description], summary[:contents] unless summary.empty? ohai klass.english_description, artifacts.map(&:summarize_installed)
end end
end end

View File

@ -1,6 +1,8 @@
require "set" require "set"
require "locale" require "locale"
require "hbc/artifact"
require "hbc/dsl/appcast" require "hbc/dsl/appcast"
require "hbc/dsl/base" require "hbc/dsl/base"
require "hbc/dsl/caveats" require "hbc/dsl/caveats"
@ -8,7 +10,6 @@ require "hbc/dsl/conflicts_with"
require "hbc/dsl/container" require "hbc/dsl/container"
require "hbc/dsl/depends_on" require "hbc/dsl/depends_on"
require "hbc/dsl/gpg" require "hbc/dsl/gpg"
require "hbc/dsl/installer"
require "hbc/dsl/postflight" require "hbc/dsl/postflight"
require "hbc/dsl/preflight" require "hbc/dsl/preflight"
require "hbc/dsl/stanza_proxy" require "hbc/dsl/stanza_proxy"
@ -18,39 +19,35 @@ require "hbc/dsl/version"
module Hbc module Hbc
class DSL class DSL
ORDINARY_ARTIFACT_TYPES = [ ORDINARY_ARTIFACT_CLASSES = [
:app, Artifact::Installer,
:artifact, Artifact::App,
:audio_unit_plugin, Artifact::Artifact,
:binary, Artifact::AudioUnitPlugin,
:colorpicker, Artifact::Binary,
:dictionary, Artifact::Colorpicker,
:font, Artifact::Dictionary,
:input_method, Artifact::Font,
:internet_plugin, Artifact::InputMethod,
:pkg, Artifact::InternetPlugin,
:prefpane, Artifact::Pkg,
:qlplugin, Artifact::Prefpane,
:screen_saver, Artifact::Qlplugin,
:service, Artifact::ScreenSaver,
:stage_only, Artifact::Service,
:suite, Artifact::StageOnly,
:vst_plugin, Artifact::Suite,
:vst3_plugin, Artifact::VstPlugin,
Artifact::Vst3Plugin,
Artifact::Uninstall,
Artifact::Zap,
].freeze ].freeze
ACTIVATABLE_ARTIFACT_TYPES = ([:installer, *ORDINARY_ARTIFACT_TYPES] - [:stage_only]).freeze ACTIVATABLE_ARTIFACT_TYPES = (ORDINARY_ARTIFACT_CLASSES.map(&:dsl_key) - [:stage_only]).freeze
SPECIAL_ARTIFACT_TYPES = [ ARTIFACT_BLOCK_CLASSES = [
:uninstall, Artifact::PreflightBlock,
:zap, Artifact::PostflightBlock,
].freeze
ARTIFACT_BLOCK_TYPES = [
:preflight,
:postflight,
:uninstall_preflight,
:uninstall_postflight,
].freeze ].freeze
DSL_METHODS = Set.new [ DSL_METHODS = Set.new [
@ -72,15 +69,15 @@ module Hbc
:url, :url,
:version, :version,
:appdir, :appdir,
*ORDINARY_ARTIFACT_TYPES, *ORDINARY_ARTIFACT_CLASSES.map(&:dsl_key),
*ACTIVATABLE_ARTIFACT_TYPES, *ACTIVATABLE_ARTIFACT_TYPES,
*SPECIAL_ARTIFACT_TYPES, *ARTIFACT_BLOCK_CLASSES.flat_map { |klass| [klass.dsl_key, klass.uninstall_dsl_key] },
*ARTIFACT_BLOCK_TYPES,
].freeze ].freeze
attr_reader :token attr_reader :token, :cask
def initialize(token) def initialize(cask)
@token = token @cask = cask
@token = cask.token
end end
def name(*args) def name(*args)
@ -93,12 +90,14 @@ module Hbc
return instance_variable_get("@#{stanza}") if should_return return instance_variable_get("@#{stanza}") if should_return
if instance_variable_defined?("@#{stanza}") if instance_variable_defined?("@#{stanza}")
raise CaskInvalidError.new(token, "'#{stanza}' stanza may only appear once") raise CaskInvalidError.new(cask, "'#{stanza}' stanza may only appear once.")
end end
instance_variable_set("@#{stanza}", yield) instance_variable_set("@#{stanza}", yield)
rescue CaskInvalidError
raise
rescue StandardError => e rescue StandardError => e
raise CaskInvalidError.new(token, "'#{stanza}' stanza failed with: #{e}") raise CaskInvalidError.new(cask, "'#{stanza}' stanza failed with: #{e}")
end end
def homepage(homepage = nil) def homepage(homepage = nil)
@ -113,7 +112,7 @@ module Hbc
return unless default return unless default
unless @language_blocks.default.nil? unless @language_blocks.default.nil?
raise CaskInvalidError.new(token, "Only one default language may be defined") raise CaskInvalidError.new(cask, "Only one default language may be defined.")
end end
@language_blocks.default = block @language_blocks.default = block
@ -163,7 +162,7 @@ module Hbc
DSL::Container.new(*args).tap do |container| DSL::Container.new(*args).tap do |container|
# TODO: remove this backward-compatibility section after removing nested_container # TODO: remove this backward-compatibility section after removing nested_container
if container && container.nested if container && container.nested
artifacts[:nested_container] << container.nested artifacts[:nested_container] << Artifact::NestedContainer.new(cask, container.nested)
end end
end end
end end
@ -173,7 +172,7 @@ module Hbc
def version(arg = nil) def version(arg = nil)
set_unique_stanza(:version, arg.nil?) do set_unique_stanza(:version, arg.nil?) do
if !arg.is_a?(String) && arg != :latest if !arg.is_a?(String) && arg != :latest
raise CaskInvalidError.new(token, "invalid 'version' value: '#{arg.inspect}'") raise CaskInvalidError.new(cask, "invalid 'version' value: '#{arg.inspect}'")
end end
DSL::Version.new(arg) DSL::Version.new(arg)
end end
@ -182,7 +181,7 @@ module Hbc
def sha256(arg = nil) def sha256(arg = nil)
set_unique_stanza(:sha256, arg.nil?) do set_unique_stanza(:sha256, arg.nil?) do
if !arg.is_a?(String) && arg != :no_check if !arg.is_a?(String) && arg != :no_check
raise CaskInvalidError.new(token, "invalid 'sha256' value: '#{arg.inspect}'") raise CaskInvalidError.new(cask, "invalid 'sha256' value: '#{arg.inspect}'")
end end
arg arg
end end
@ -195,7 +194,7 @@ module Hbc
begin begin
@depends_on.load(*args) @depends_on.load(*args)
rescue RuntimeError => e rescue RuntimeError => e
raise CaskInvalidError.new(token, e) raise CaskInvalidError.new(cask, e)
end end
@depends_on @depends_on
end end
@ -237,39 +236,29 @@ module Hbc
set_unique_stanza(:auto_updates, auto_updates.nil?) { auto_updates } set_unique_stanza(:auto_updates, auto_updates.nil?) { auto_updates }
end end
ORDINARY_ARTIFACT_TYPES.each do |type| ORDINARY_ARTIFACT_CLASSES.each do |klass|
type = klass.dsl_key
define_method(type) do |*args| define_method(type) do |*args|
if type == :stage_only begin
if args != [true] if [*artifacts.keys, type].include?(:stage_only) && (artifacts.keys & ACTIVATABLE_ARTIFACT_TYPES).any?
raise CaskInvalidError.new(token, "'stage_only' takes a single argument: true") raise CaskInvalidError.new(cask, "'stage_only' must be the only activatable artifact.")
end end
unless (artifacts.keys & ACTIVATABLE_ARTIFACT_TYPES).empty? artifacts[type].add(klass.from_args(cask, *args))
raise CaskInvalidError.new(token, "'stage_only' must be the only activatable artifact") rescue CaskInvalidError
end raise
rescue StandardError => e
raise CaskInvalidError.new(cask, "invalid '#{klass.dsl_key}' stanza: #{e}")
end end
artifacts[type].add(args)
end end
end end
def installer(*args) ARTIFACT_BLOCK_CLASSES.each do |klass|
return artifacts[:installer] if args.empty? [klass.dsl_key, klass.uninstall_dsl_key].each do |dsl_key|
artifacts[:installer] << DSL::Installer.new(*args) define_method(dsl_key) do |&block|
raise "'stage_only' must be the only activatable artifact" if artifacts.key?(:stage_only) artifacts[dsl_key] << block
rescue StandardError => e end
raise CaskInvalidError.new(token, e)
end
SPECIAL_ARTIFACT_TYPES.each do |type|
define_method(type) do |*args|
artifacts[type].merge(args)
end
end
ARTIFACT_BLOCK_TYPES.each do |type|
define_method(type) do |&block|
artifacts[type] << block
end end
end end

View File

@ -1,32 +0,0 @@
module Hbc
class DSL
class Installer
VALID_KEYS = Set.new [
:manual,
:script,
]
attr_accessor(*VALID_KEYS)
def initialize(*parameters)
raise CaskInvalidError.new(token, "'installer' stanza requires an argument") if parameters.empty?
parameters = {}.merge(*parameters)
if parameters.key?(:script) && !parameters[:script].respond_to?(:key?)
if parameters.key?(:executable)
raise CaskInvalidError.new(token, "'installer' stanza gave arguments for both :script and :executable")
end
parameters[:executable] = parameters[:script]
parameters.delete(:script)
parameters = { script: parameters }
end
unless parameters.keys.length == 1
raise "invalid 'installer' stanza: only one of #{VALID_KEYS.inspect} is permitted"
end
key = parameters.keys.first
raise "invalid 'installer' stanza key: '#{key.inspect}'" unless VALID_KEYS.include?(key)
writer_method = "#{key}=".to_sym
send(writer_method, parameters[key])
end
end
end
end

View File

@ -177,7 +177,7 @@ module Hbc
already_installed_artifacts = [] already_installed_artifacts = []
odebug "Installing artifacts" odebug "Installing artifacts"
artifacts = Artifact.for_cask(@cask, command: @command, verbose: verbose?, force: force?) artifacts = Artifact.for_cask(@cask)
odebug "#{artifacts.length} artifact/s defined", artifacts odebug "#{artifacts.length} artifact/s defined", artifacts
artifacts.each do |artifact| artifacts.each do |artifact|
@ -188,7 +188,7 @@ module Hbc
next unless binaries? next unless binaries?
end end
artifact.install_phase artifact.install_phase(command: @command, verbose: verbose?, force: force?)
already_installed_artifacts.unshift(artifact) already_installed_artifacts.unshift(artifact)
end end
rescue StandardError => e rescue StandardError => e
@ -196,7 +196,7 @@ module Hbc
already_installed_artifacts.each do |artifact| already_installed_artifacts.each do |artifact|
next unless artifact.respond_to?(:uninstall_phase) next unless artifact.respond_to?(:uninstall_phase)
odebug "Reverting installation of artifact of class #{artifact.class}" odebug "Reverting installation of artifact of class #{artifact.class}"
artifact.uninstall_phase artifact.uninstall_phase(command: @command, verbose: verbose?, force: force?)
end end
ensure ensure
purge_versioned_files purge_versioned_files
@ -374,25 +374,27 @@ module Hbc
def uninstall_artifacts def uninstall_artifacts
odebug "Un-installing artifacts" odebug "Un-installing artifacts"
artifacts = Artifact.for_cask(@cask, command: @command, verbose: verbose?, force: force?) artifacts = Artifact.for_cask(@cask)
odebug "#{artifacts.length} artifact/s defined", artifacts odebug "#{artifacts.length} artifact/s defined", artifacts
artifacts.each do |artifact| artifacts.each do |artifact|
next unless artifact.respond_to?(:uninstall_phase) next unless artifact.respond_to?(:uninstall_phase)
odebug "Un-installing artifact of class #{artifact.class}" odebug "Un-installing artifact of class #{artifact.class}"
artifact.uninstall_phase artifact.uninstall_phase(command: @command, verbose: verbose?, force: force?)
end end
end end
def zap def zap
ohai %Q(Implied "brew cask uninstall #{@cask}") ohai %Q(Implied "brew cask uninstall #{@cask}")
uninstall_artifacts uninstall_artifacts
if Artifact::Zap.me?(@cask) if (zap_stanzas = Artifact::Zap.for_cask(@cask)).empty?
ohai "Dispatching zap stanza"
Artifact::Zap.new(@cask, command: @command).zap_phase
else
opoo "No zap stanza present for Cask '#{@cask}'" 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 end
ohai "Removing all staged versions of Cask '#{@cask}'" ohai "Removing all staged versions of Cask '#{@cask}'"
purge_caskroom_path purge_caskroom_path

View File

@ -4,7 +4,7 @@ module Hbc
index = 0 if index == :first index = 0 if index == :first
index = 1 if index == :second index = 1 if index == :second
index = -1 if index == :last index = -1 if index == :last
Hbc.appdir.join(@cask.artifacts[:app].to_a.at(index).first, "Contents", "Info.plist") @cask.artifacts[:app].to_a.at(index).target.join("Contents", "Info.plist")
end end
def plist_exec(cmd) def plist_exec(cmd)

View File

@ -3,7 +3,7 @@ describe Hbc::Artifact::App, :cask do
let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-alt-target.rb") } let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-alt-target.rb") }
let(:install_phase) { let(:install_phase) {
-> { Hbc::Artifact::App.new(cask).install_phase } -> { described_class.for_cask(cask).each { |artifact| artifact.install_phase(command: Hbc::NeverSudoSystemCommand, force: false) } }
} }
let(:source_path) { cask.staged_path.join("Caffeine.app") } let(:source_path) { cask.staged_path.join("Caffeine.app") }

View File

@ -2,13 +2,13 @@ describe Hbc::Artifact::App, :cask do
let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/local-caffeine.rb") } let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/local-caffeine.rb") }
let(:command) { Hbc::SystemCommand } let(:command) { Hbc::SystemCommand }
let(:force) { false } let(:force) { false }
let(:app) { Hbc::Artifact::App.new(cask, command: command, force: force) } let(:app) { described_class.for_cask(cask).first }
let(:source_path) { cask.staged_path.join("Caffeine.app") } let(:source_path) { cask.staged_path.join("Caffeine.app") }
let(:target_path) { Hbc.appdir.join("Caffeine.app") } let(:target_path) { Hbc.appdir.join("Caffeine.app") }
let(:install_phase) { app.install_phase } let(:install_phase) { app.install_phase(command: command, force: force) }
let(:uninstall_phase) { app.uninstall_phase } let(:uninstall_phase) { app.uninstall_phase(command: command, force: force) }
before(:each) do before(:each) do
InstallHelper.install_without_artifacts(cask) InstallHelper.install_without_artifacts(cask)
@ -105,8 +105,8 @@ describe Hbc::Artifact::App, :cask do
describe "target is user-owned but contains read-only files" do describe "target is user-owned but contains read-only files" do
before(:each) do before(:each) do
system "/usr/bin/touch", "--", "#{target_path}/foo" FileUtils.touch "#{target_path}/foo"
system "/bin/chmod", "--", "0555", target_path FileUtils.chmod 0555, target_path
end end
it "overwrites the existing app" do it "overwrites the existing app" do
@ -138,7 +138,7 @@ describe Hbc::Artifact::App, :cask do
end end
after(:each) do after(:each) do
system "/bin/chmod", "--", "0755", target_path FileUtils.chmod 0755, target_path
end end
end end
end end
@ -206,8 +206,8 @@ describe Hbc::Artifact::App, :cask do
end end
describe "summary" do describe "summary" do
let(:description) { app.summary[:english_description] } let(:description) { app.class.english_description }
let(:contents) { app.summary[:contents] } let(:contents) { app.summarize_installed }
it "returns the correct english_description" do it "returns the correct english_description" do
expect(description).to eq("Apps") expect(description).to eq("Apps")
@ -217,14 +217,13 @@ describe Hbc::Artifact::App, :cask do
it "returns the path to the app" do it "returns the path to the app" do
install_phase install_phase
expect(contents).to eq(["#{target_path} (#{target_path.abv})"]) expect(contents).to eq("#{target_path} (#{target_path.abv})")
end end
end end
describe "app is missing" do describe "app is missing" do
it "returns a warning and the supposed path to the app" do it "returns a warning and the supposed path to the app" do
expect(contents.size).to eq(1) expect(contents).to match(/.*Missing App.*: #{target_path}/)
expect(contents[0]).to match(/.*Missing App.*: #{target_path}/)
end end
end end
end end

View File

@ -26,7 +26,8 @@ describe Hbc::Artifact::Binary, :cask do
end end
it "links the binary to the proper directory" do it "links the binary to the proper directory" do
Hbc::Artifact::Binary.new(cask).install_phase described_class.for_cask(cask)
.each { |artifact| artifact.install_phase(command: Hbc::NeverSudoSystemCommand, force: false) }
expect(expected_path).to be_a_symlink expect(expected_path).to be_a_symlink
expect(expected_path.readlink).to exist expect(expected_path.readlink).to exist
@ -45,7 +46,8 @@ describe Hbc::Artifact::Binary, :cask do
expect(FileUtils).to receive(:chmod) expect(FileUtils).to receive(:chmod)
.with("+x", cask.staged_path.join("naked_non_executable")).and_call_original .with("+x", cask.staged_path.join("naked_non_executable")).and_call_original
Hbc::Artifact::Binary.new(cask).install_phase described_class.for_cask(cask)
.each { |artifact| artifact.install_phase(command: Hbc::NeverSudoSystemCommand, force: false) }
expect(expected_path).to be_a_symlink expect(expected_path).to be_a_symlink
expect(expected_path.readlink).to be_executable expect(expected_path.readlink).to be_executable
@ -56,7 +58,8 @@ describe Hbc::Artifact::Binary, :cask do
FileUtils.touch expected_path FileUtils.touch expected_path
expect { expect {
Hbc::Artifact::Binary.new(cask).install_phase described_class.for_cask(cask)
.each { |artifact| artifact.install_phase(command: Hbc::NeverSudoSystemCommand, force: false) }
}.to raise_error(Hbc::CaskError) }.to raise_error(Hbc::CaskError)
expect(expected_path).not_to be :symlink? expect(expected_path).not_to be :symlink?
@ -65,7 +68,8 @@ describe Hbc::Artifact::Binary, :cask do
it "clobbers an existing symlink" do it "clobbers an existing symlink" do
expected_path.make_symlink("/tmp") expected_path.make_symlink("/tmp")
Hbc::Artifact::Binary.new(cask).install_phase described_class.for_cask(cask)
.each { |artifact| artifact.install_phase(command: Hbc::NeverSudoSystemCommand, force: false) }
expect(File.readlink(expected_path)).not_to eq("/tmp") expect(File.readlink(expected_path)).not_to eq("/tmp")
end end
@ -73,7 +77,8 @@ describe Hbc::Artifact::Binary, :cask do
it "creates parent directory if it doesn't exist" do it "creates parent directory if it doesn't exist" do
FileUtils.rmdir Hbc.binarydir FileUtils.rmdir Hbc.binarydir
Hbc::Artifact::Binary.new(cask).install_phase described_class.for_cask(cask)
.each { |artifact| artifact.install_phase(command: Hbc::NeverSudoSystemCommand, force: false) }
expect(expected_path.exist?).to be true expect(expected_path.exist?).to be true
end end
@ -86,8 +91,10 @@ describe Hbc::Artifact::Binary, :cask do
} }
it "links the binary to the proper directory" do it "links the binary to the proper directory" do
Hbc::Artifact::App.new(cask).install_phase Hbc::Artifact::App.for_cask(cask)
Hbc::Artifact::Binary.new(cask).install_phase .each { |artifact| artifact.install_phase(command: Hbc::NeverSudoSystemCommand, force: false) }
described_class.for_cask(cask)
.each { |artifact| artifact.install_phase(command: Hbc::NeverSudoSystemCommand, force: false) }
expect(expected_path).to be_a_symlink expect(expected_path).to be_a_symlink
expect(expected_path.readlink).to exist expect(expected_path.readlink).to exist

View File

@ -2,7 +2,7 @@ describe Hbc::Artifact::Artifact, :cask do
let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-generic-artifact.rb") } let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-generic-artifact.rb") }
let(:install_phase) { let(:install_phase) {
-> { Hbc::Artifact::Artifact.new(cask).install_phase } -> { described_class.for_cask(cask).each { |artifact| artifact.install_phase(command: Hbc::NeverSudoSystemCommand, force: false) } }
} }
let(:source_path) { cask.staged_path.join("Caffeine.app") } let(:source_path) { cask.staged_path.join("Caffeine.app") }
@ -12,11 +12,11 @@ describe Hbc::Artifact::Artifact, :cask do
InstallHelper.install_without_artifacts(cask) InstallHelper.install_without_artifacts(cask)
end end
describe "with no target" do context "without target" do
let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-generic-artifact-no-target.rb") } it "fails to load" do
expect {
it "fails to install with no target" do Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-generic-artifact-no-target.rb")
expect(install_phase).to raise_error(Hbc::CaskInvalidError) }.to raise_error(Hbc::CaskInvalidError, /target required for Generic Artifact/)
end end
end end

View File

@ -5,7 +5,8 @@ describe Hbc::Artifact::NestedContainer, :cask do
InstallHelper.install_without_artifacts(c) InstallHelper.install_without_artifacts(c)
end end
Hbc::Artifact::NestedContainer.new(cask).install_phase described_class.for_cask(cask)
.each { |artifact| artifact.install_phase(command: Hbc::NeverSudoSystemCommand, force: false) }
expect(cask.staged_path.join("MyNestedApp.app")).to be_a_directory expect(cask.staged_path.join("MyNestedApp.app")).to be_a_directory
end end

View File

@ -8,7 +8,7 @@ describe Hbc::Artifact::Pkg, :cask do
describe "install_phase" do describe "install_phase" do
it "runs the system installer on the specified pkgs" do it "runs the system installer on the specified pkgs" do
pkg = Hbc::Artifact::Pkg.new(cask, command: fake_system_command) pkg = described_class.for_cask(cask).first
expect(fake_system_command).to receive(:run!).with( expect(fake_system_command).to receive(:run!).with(
"/usr/sbin/installer", "/usr/sbin/installer",
@ -17,7 +17,7 @@ describe Hbc::Artifact::Pkg, :cask do
print_stdout: true, print_stdout: true,
) )
pkg.install_phase pkg.install_phase(command: fake_system_command)
end end
end end
@ -25,7 +25,7 @@ describe Hbc::Artifact::Pkg, :cask do
let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-choices.rb") } let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-choices.rb") }
it "passes the choice changes xml to the system installer" do it "passes the choice changes xml to the system installer" do
pkg = Hbc::Artifact::Pkg.new(cask, command: fake_system_command) pkg = described_class.for_cask(cask).first
file = double(path: Pathname.new("/tmp/choices.xml")) file = double(path: Pathname.new("/tmp/choices.xml"))
@ -57,7 +57,7 @@ describe Hbc::Artifact::Pkg, :cask do
print_stdout: true, print_stdout: true,
) )
pkg.install_phase pkg.install_phase(command: fake_system_command)
end end
end end
end end

View File

@ -11,7 +11,8 @@ describe Hbc::Artifact::PostflightBlock, :cask do
end end
end end
described_class.new(cask).install_phase described_class.for_cask(cask)
.each { |artifact| artifact.install_phase(command: Hbc::NeverSudoSystemCommand, force: false) }
expect(called).to be true expect(called).to be true
expect(yielded_arg).to be_kind_of(Hbc::DSL::Postflight) expect(yielded_arg).to be_kind_of(Hbc::DSL::Postflight)
@ -30,7 +31,8 @@ describe Hbc::Artifact::PostflightBlock, :cask do
end end
end end
described_class.new(cask).uninstall_phase described_class.for_cask(cask)
.each { |artifact| artifact.uninstall_phase(command: Hbc::NeverSudoSystemCommand, force: false) }
expect(called).to be true expect(called).to be true
expect(yielded_arg).to be_kind_of(Hbc::DSL::UninstallPostflight) expect(yielded_arg).to be_kind_of(Hbc::DSL::UninstallPostflight)

View File

@ -11,7 +11,8 @@ describe Hbc::Artifact::PreflightBlock, :cask do
end end
end end
described_class.new(cask).install_phase described_class.for_cask(cask)
.each { |artifact| artifact.install_phase(command: Hbc::NeverSudoSystemCommand, force: false) }
expect(called).to be true expect(called).to be true
expect(yielded_arg).to be_kind_of Hbc::DSL::Preflight expect(yielded_arg).to be_kind_of Hbc::DSL::Preflight
@ -30,7 +31,8 @@ describe Hbc::Artifact::PreflightBlock, :cask do
end end
end end
described_class.new(cask).uninstall_phase described_class.for_cask(cask)
.each { |artifact| artifact.uninstall_phase(command: Hbc::NeverSudoSystemCommand, force: false) }
expect(called).to be true expect(called).to be true
expect(yielded_arg).to be_kind_of Hbc::DSL::UninstallPreflight expect(yielded_arg).to be_kind_of Hbc::DSL::UninstallPreflight

View File

@ -1,7 +1,9 @@
describe Hbc::Artifact::Suite, :cask do describe Hbc::Artifact::Suite, :cask do
let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-suite.rb") } let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-suite.rb") }
let(:install_phase) { -> { Hbc::Artifact::Suite.new(cask).install_phase } } let(:install_phase) {
-> { described_class.for_cask(cask).each { |artifact| artifact.install_phase(command: Hbc::NeverSudoSystemCommand, force: false) } }
}
let(:target_path) { Hbc.appdir.join("Caffeine") } let(:target_path) { Hbc.appdir.join("Caffeine") }
let(:source_path) { cask.staged_path.join("Caffeine") } let(:source_path) { cask.staged_path.join("Caffeine") }

View File

@ -3,7 +3,7 @@ describe Hbc::Artifact::App, :cask do
let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-two-apps-correct.rb") } let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-two-apps-correct.rb") }
let(:install_phase) { let(:install_phase) {
-> { Hbc::Artifact::App.new(cask).install_phase } -> { described_class.for_cask(cask).each { |artifact| artifact.install_phase(command: Hbc::NeverSudoSystemCommand, force: false) } }
} }
let(:source_path_mini) { cask.staged_path.join("Caffeine Mini.app") } let(:source_path_mini) { cask.staged_path.join("Caffeine Mini.app") }

View File

@ -2,7 +2,7 @@ describe Hbc::Artifact::Zap, :cask do
let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-installable.rb") } let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-installable.rb") }
let(:zap_artifact) { let(:zap_artifact) {
Hbc::Artifact::Zap.new(cask) described_class.for_cask(cask).first
} }
before(:each) do before(:each) do

View File

@ -1,12 +1,12 @@
shared_examples "#uninstall_phase or #zap_phase" do shared_examples "#uninstall_phase or #zap_phase" do
let(:artifact_name) { described_class.artifact_name } let(:artifact_dsl_key) { described_class.dsl_key }
let(:artifact) { described_class.new(cask, command: fake_system_command) } let(:artifact) { described_class.for_cask(cask).first }
let(:fake_system_command) { Hbc::FakeSystemCommand } let(:fake_system_command) { Hbc::FakeSystemCommand }
subject { artifact.public_send(:"#{artifact_name}_phase") } subject { artifact.public_send(:"#{artifact_dsl_key}_phase", command: fake_system_command) }
context "using :launchctl" do context "using :launchctl" do
let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-#{artifact_name}-launchctl.rb") } let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-#{artifact_dsl_key}-launchctl.rb") }
let(:launchctl_list_cmd) { %w[/bin/launchctl list my.fancy.package.service] } let(:launchctl_list_cmd) { %w[/bin/launchctl list my.fancy.package.service] }
let(:launchctl_remove_cmd) { %w[/bin/launchctl remove my.fancy.package.service] } let(:launchctl_remove_cmd) { %w[/bin/launchctl remove my.fancy.package.service] }
let(:unknown_response) { "launchctl list returned unknown response\n" } let(:unknown_response) { "launchctl list returned unknown response\n" }
@ -61,7 +61,7 @@ shared_examples "#uninstall_phase or #zap_phase" do
context "using :pkgutil" do context "using :pkgutil" do
let(:fake_system_command) { class_double(Hbc::SystemCommand) } let(:fake_system_command) { class_double(Hbc::SystemCommand) }
let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-#{artifact_name}-pkgutil.rb") } let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-#{artifact_dsl_key}-pkgutil.rb") }
let(:main_pkg_id) { "my.fancy.package.main" } let(:main_pkg_id) { "my.fancy.package.main" }
let(:agent_pkg_id) { "my.fancy.package.agent" } let(:agent_pkg_id) { "my.fancy.package.agent" }
@ -85,7 +85,7 @@ shared_examples "#uninstall_phase or #zap_phase" do
end end
context "using :kext" do context "using :kext" do
let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-#{artifact_name}-kext.rb") } let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-#{artifact_dsl_key}-kext.rb") }
let(:kext_id) { "my.fancy.package.kernelextension" } let(:kext_id) { "my.fancy.package.kernelextension" }
it "is supported" do it "is supported" do
@ -110,7 +110,7 @@ shared_examples "#uninstall_phase or #zap_phase" do
end end
context "using :quit" do context "using :quit" do
let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-#{artifact_name}-quit.rb") } let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-#{artifact_dsl_key}-quit.rb") }
let(:bundle_id) { "my.fancy.package.app" } let(:bundle_id) { "my.fancy.package.app" }
let(:quit_application_script) do let(:quit_application_script) do
%Q(tell application id "#{bundle_id}" to quit) %Q(tell application id "#{bundle_id}" to quit)
@ -130,7 +130,7 @@ shared_examples "#uninstall_phase or #zap_phase" do
end end
context "using :signal" do context "using :signal" do
let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-#{artifact_name}-signal.rb") } let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-#{artifact_dsl_key}-signal.rb") }
let(:bundle_id) { "my.fancy.package.app" } let(:bundle_id) { "my.fancy.package.app" }
let(:signals) { %w[TERM KILL] } let(:signals) { %w[TERM KILL] }
let(:unix_pids) { [12_345, 67_890] } let(:unix_pids) { [12_345, 67_890] }
@ -170,10 +170,10 @@ shared_examples "#uninstall_phase or #zap_phase" do
end end
let(:fake_system_command) { Hbc::NeverSudoSystemCommand } let(:fake_system_command) { Hbc::NeverSudoSystemCommand }
let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-#{artifact_name}-#{directive}.rb") } let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-#{artifact_dsl_key}-#{directive}.rb") }
before(:each) do before(:each) do
allow_any_instance_of(Hbc::Artifact::UninstallBase).to receive(:trash_paths) allow_any_instance_of(Hbc::Artifact::AbstractUninstall).to receive(:trash_paths)
.and_wrap_original do |method, *args| .and_wrap_original do |method, *args|
result = method.call(*args) result = method.call(*args)
FileUtils.rm_rf result.stdout.split("\0") FileUtils.rm_rf result.stdout.split("\0")
@ -196,7 +196,7 @@ shared_examples "#uninstall_phase or #zap_phase" do
context "using :rmdir" do context "using :rmdir" do
let(:fake_system_command) { Hbc::NeverSudoSystemCommand } let(:fake_system_command) { Hbc::NeverSudoSystemCommand }
let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-#{artifact_name}-rmdir.rb") } let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-#{artifact_dsl_key}-rmdir.rb") }
let(:empty_directory) { Pathname.new("#{TEST_TMPDIR}/empty_directory_path") } let(:empty_directory) { Pathname.new("#{TEST_TMPDIR}/empty_directory_path") }
let(:ds_store) { empty_directory.join(".DS_Store") } let(:ds_store) { empty_directory.join(".DS_Store") }
@ -223,7 +223,7 @@ shared_examples "#uninstall_phase or #zap_phase" do
[:script, :early_script].each do |script_type| [:script, :early_script].each do |script_type|
context "using #{script_type.inspect}" do context "using #{script_type.inspect}" do
let(:fake_system_command) { Hbc::NeverSudoSystemCommand } let(:fake_system_command) { Hbc::NeverSudoSystemCommand }
let(:token) { "with-#{artifact_name}-#{script_type}".tr("_", "-") } let(:token) { "with-#{artifact_dsl_key}-#{script_type}".tr("_", "-") }
let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/#{token}.rb") } let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/#{token}.rb") }
let(:script_pathname) { cask.staged_path.join("MyFancyPkg", "FancyUninstaller.tool") } let(:script_pathname) { cask.staged_path.join("MyFancyPkg", "FancyUninstaller.tool") }
@ -250,7 +250,7 @@ shared_examples "#uninstall_phase or #zap_phase" do
end end
context "using :login_item" do context "using :login_item" do
let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-#{artifact_name}-login-item.rb") } let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-#{artifact_dsl_key}-login-item.rb") }
it "is supported" do it "is supported" do
Hbc::FakeSystemCommand.expects_command( Hbc::FakeSystemCommand.expects_command(

View File

@ -265,19 +265,14 @@ describe Hbc::Audit, :cask do
end end
describe "generic artifact checks" do describe "generic artifact checks" do
context "with no target" do
let(:cask_token) { "generic-artifact-no-target" }
it { is_expected.to fail_with(/target required for generic artifact/) }
end
context "with relative target" do context "with relative target" do
let(:cask_token) { "generic-artifact-relative-target" } let(:cask_token) { "generic-artifact-relative-target" }
it { is_expected.to fail_with(/target must be absolute path for generic artifact/) } it { is_expected.to fail_with(/target must be absolute path for Generic Artifact/) }
end end
context "with absolute target" do context "with absolute target" do
let(:cask_token) { "generic-artifact-absolute-target" } let(:cask_token) { "generic-artifact-absolute-target" }
it { should_not fail_with(/target required for generic artifact/) } it { should_not fail_with(/target required for Generic Artifact/) }
end end
end end

View File

@ -10,7 +10,7 @@ describe Hbc::CLI::Info, :cask do
==> Name ==> Name
None None
==> Artifacts ==> Artifacts
Caffeine.app (app) Caffeine.app (App)
EOS EOS
end end
@ -24,7 +24,7 @@ describe Hbc::CLI::Info, :cask do
==> Name ==> Name
None None
==> Artifacts ==> Artifacts
Caffeine.app (app) Caffeine.app (App)
local-transmission: 2.61 local-transmission: 2.61
http://example.com/local-transmission http://example.com/local-transmission
Not installed Not installed
@ -32,7 +32,7 @@ describe Hbc::CLI::Info, :cask do
==> Name ==> Name
None None
==> Artifacts ==> Artifacts
Transmission.app (app) Transmission.app (App)
EOS EOS
} }
@ -60,7 +60,7 @@ describe Hbc::CLI::Info, :cask do
==> Name ==> Name
None None
==> Artifacts ==> Artifacts
Caffeine.app (app) Caffeine.app (App)
==> Caveats ==> Caveats
Here are some things you might want to know. Here are some things you might want to know.
@ -86,7 +86,7 @@ describe Hbc::CLI::Info, :cask do
==> Name ==> Name
None None
==> Artifacts ==> Artifacts
Caffeine.app (app) Caffeine.app (App)
EOS EOS
end end

View File

@ -48,7 +48,8 @@ describe Hbc::CLI::List, :cask do
it "lists the installed files for those Casks" do it "lists the installed files for those Casks" do
casks.each(&InstallHelper.method(:install_without_artifacts_with_caskfile)) casks.each(&InstallHelper.method(:install_without_artifacts_with_caskfile))
Hbc::Artifact::App.new(transmission).install_phase Hbc::Artifact::App.for_cask(transmission)
.each { |artifact| artifact.install_phase(command: Hbc::NeverSudoSystemCommand, force: false) }
expect { expect {
Hbc::CLI::List.run("local-transmission", "local-caffeine") Hbc::CLI::List.run("local-transmission", "local-caffeine")

View File

@ -186,12 +186,12 @@ describe Hbc::DSL, :cask do
app "Bar.app" app "Bar.app"
end end
expect(Array(cask.artifacts[:app])).to eq([["Foo.app"], ["Bar.app"]]) expect(cask.artifacts[:app].map(&:to_s)).to eq(["Foo.app (App)", "Bar.app (App)"])
end end
it "allow app stanzas to be empty" do it "allow app stanzas to be empty" do
cask = Hbc::Cask.new("cask-with-no-apps") cask = Hbc::Cask.new("cask-with-no-apps")
expect(Array(cask.artifacts[:app])).to eq([]) expect(cask.artifacts[:app]).to be_empty
end end
end end
@ -219,7 +219,7 @@ describe Hbc::DSL, :cask do
pkg "Bar.pkg" pkg "Bar.pkg"
end end
expect(Array(cask.artifacts[:pkg])).to eq([["Foo.pkg"], ["Bar.pkg"]]) expect(cask.artifacts[:pkg].map(&:to_s)).to eq(["Foo.pkg (Pkg)", "Bar.pkg (Pkg)"])
end end
end end
@ -471,10 +471,10 @@ describe Hbc::DSL, :cask do
let(:token) { "with-installer-script" } let(:token) { "with-installer-script" }
it "allows installer script to be specified" do it "allows installer script to be specified" do
expect(cask.artifacts[:installer].first.script[:executable]).to eq("/usr/bin/true") expect(cask.artifacts[:installer].first.path).to eq(Pathname("/usr/bin/true"))
expect(cask.artifacts[:installer].first.script[:args]).to eq(["--flag"]) expect(cask.artifacts[:installer].first.args[:args]).to eq(["--flag"])
expect(cask.artifacts[:installer].to_a[1].script[:executable]).to eq("/usr/bin/false") expect(cask.artifacts[:installer].to_a[1].path).to eq(Pathname("/usr/bin/false"))
expect(cask.artifacts[:installer].to_a[1].script[:args]).to eq(["--flag"]) expect(cask.artifacts[:installer].to_a[1].args[:args]).to eq(["--flag"])
end end
end end
@ -482,7 +482,9 @@ describe Hbc::DSL, :cask do
let(:token) { "with-installer-manual" } let(:token) { "with-installer-manual" }
it "allows installer manual to be specified" do it "allows installer manual to be specified" do
expect(cask.artifacts[:installer].first.manual).to eq("Caffeine.app") installer = cask.artifacts[:installer].first
expect(installer).to be_a(Hbc::Artifact::Installer::ManualInstaller)
expect(installer.path).to eq(cask.staged_path.join("Caffeine.app"))
end end
end end
end end
@ -492,7 +494,7 @@ describe Hbc::DSL, :cask do
let(:token) { "stage-only" } let(:token) { "stage-only" }
it "allows stage_only stanza to be specified" do it "allows stage_only stanza to be specified" do
expect(cask.artifacts[:stage_only].first).to eq([true]) expect(cask.artifacts[:stage_only]).not_to be_empty
end end
end end
@ -518,7 +520,7 @@ describe Hbc::DSL, :cask do
let(:token) { "appdir-interpolation" } let(:token) { "appdir-interpolation" }
it "is allowed" do it "is allowed" do
expect(cask.artifacts[:binary].first).to eq(["#{Hbc.appdir}/some/path"]) expect(cask.artifacts[:binary].first.source).to eq(Hbc.appdir/"some/path")
end end
end end
@ -531,7 +533,7 @@ describe Hbc::DSL, :cask do
binary "#{appdir}/some/path" binary "#{appdir}/some/path"
end end
expect(cask.artifacts[:binary].first).to eq(["#{original_appdir}/some/path"]) expect(cask.artifacts[:binary].first.source).to eq(original_appdir/"some/path")
ensure ensure
Hbc.appdir = original_appdir Hbc.appdir = original_appdir
end end