diff --git a/Library/Homebrew/cask/lib/hbc/artifact.rb b/Library/Homebrew/cask/lib/hbc/artifact.rb index 074d15017a..cb15ec0513 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact.rb @@ -33,7 +33,7 @@ module Hbc # We want to extract nested containers before we # handle any other artifacts. # - TYPES = [ + CLASSES = [ PreflightBlock, Uninstall, NestedContainer, @@ -60,12 +60,9 @@ module Hbc Zap, ].freeze - def self.for_cask(cask, options = {}) + def self.for_cask(cask) odebug "Determining which artifacts are present in Cask #{cask}" - - TYPES - .select { |klass| klass.me?(cask) } - .map { |klass| klass.new(cask, options) } + CLASSES.flat_map { |klass| klass.for_cask(cask) } end end end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/base.rb b/Library/Homebrew/cask/lib/hbc/artifact/abstract_artifact.rb similarity index 66% rename from Library/Homebrew/cask/lib/hbc/artifact/base.rb rename to Library/Homebrew/cask/lib/hbc/artifact/abstract_artifact.rb index ae15552a4e..1b18cc6e7d 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/base.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/abstract_artifact.rb @@ -1,34 +1,28 @@ module Hbc module Artifact - class Base + class AbstractArtifact extend Predicable - def self.artifact_name - @artifact_name ||= name.sub(/^.*:/, "").gsub(/(.)([A-Z])/, '\1_\2').downcase + def self.english_name + @english_name ||= name.sub(/^.*:/, "").gsub(/(.)([A-Z])/, '\1 \2') end - def self.artifact_english_name - @artifact_english_name ||= name.sub(/^.*:/, "").gsub(/(.)([A-Z])/, '\1 \2') + def self.english_article + @english_article ||= (english_name =~ /^[aeiou]/i) ? "an" : "a" end - def self.artifact_english_article - @artifact_english_article ||= (artifact_english_name =~ /^[aeiou]/i) ? "an" : "a" + def self.dsl_key + @dsl_key ||= name.sub(/^.*:/, "").gsub(/(.)([A-Z])/, '\1_\2').downcase.to_sym end - def self.artifact_dsl_key - @artifact_dsl_key ||= artifact_name.to_sym + def self.dirmethod + @dirmethod ||= "#{dsl_key}dir".to_sym end - def self.artifact_dirmethod - @artifact_dirmethod ||= "#{artifact_name}dir".to_sym + def self.for_cask(cask) + cask.artifacts[dsl_key].to_a 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 # 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) @@ -63,17 +57,14 @@ module Hbc [executable, arguments] end - def summary - {} + attr_reader :cask + + def initialize(cask) + @cask = cask end - attr_predicate :force?, :verbose? - - def initialize(cask, command: SystemCommand, force: false, verbose: false) - @cask = cask - @command = command - @force = force - @verbose = verbose + def to_s + "#{summarize} (#{self.class.english_name})" end end end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/abstract_flight_block.rb b/Library/Homebrew/cask/lib/hbc/artifact/abstract_flight_block.rb index be3050acb7..6670d42499 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/abstract_flight_block.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/abstract_flight_block.rb @@ -1,39 +1,47 @@ -require "hbc/artifact/base" +require "hbc/artifact/abstract_artifact" module Hbc module Artifact - class AbstractFlightBlock < Base - def self.artifact_dsl_key + class AbstractFlightBlock < AbstractArtifact + def self.dsl_key super.to_s.sub(/_block$/, "").to_sym end - def self.uninstall_artifact_dsl_key - artifact_dsl_key.to_s.prepend("uninstall_").to_sym + def self.uninstall_dsl_key + dsl_key.to_s.prepend("uninstall_").to_sym end - def self.class_for_dsl_key(dsl_key) - Object.const_get("Hbc::DSL::#{dsl_key.to_s.split("_").collect(&:capitalize).join}") + def self.for_cask(cask) + [dsl_key, uninstall_dsl_key].flat_map do |key| + [*cask.artifacts[key]].map { |block| new(cask, key => block) } + end end - def self.me?(cask) - cask.artifacts[artifact_dsl_key].any? || - cask.artifacts[uninstall_artifact_dsl_key].any? + attr_reader :directives + + def initialize(cask, **directives) + super(cask) + @directives = directives end - def install_phase - abstract_phase(self.class.artifact_dsl_key) + def install_phase(**) + abstract_phase(self.class.dsl_key) end - def uninstall_phase - abstract_phase(self.class.uninstall_artifact_dsl_key) + def uninstall_phase(**) + abstract_phase(self.class.uninstall_dsl_key) end 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) - @cask.artifacts[dsl_key].each do |block| - self.class.class_for_dsl_key(dsl_key).new(@cask).instance_eval(&block) - end + return if (block = directives[dsl_key]).nil? + class_for_dsl_key(dsl_key).new(cask).instance_eval(&block) end end end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/uninstall_base.rb b/Library/Homebrew/cask/lib/hbc/artifact/abstract_uninstall.rb similarity index 63% rename from Library/Homebrew/cask/lib/hbc/artifact/uninstall_base.rb rename to Library/Homebrew/cask/lib/hbc/artifact/abstract_uninstall.rb index d926441502..9499d5c03f 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/uninstall_base.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/abstract_uninstall.rb @@ -1,11 +1,11 @@ require "pathname" require "timeout" -require "hbc/artifact/base" +require "hbc/artifact/abstract_artifact" module Hbc module Artifact - class UninstallBase < Base + class AbstractUninstall < AbstractArtifact ORDERED_DIRECTIVES = [ :early_script, :launchctl, @@ -20,26 +20,33 @@ module Hbc :rmdir, ].freeze - def dispatch_uninstall_directives - directives_set = @cask.artifacts[stanza] - ohai "Running #{stanza} process for #{@cask}; your password may be necessary" + def self.from_args(cask, **directives) + new(cask, directives) + end - directives_set.each do |directives| - warn_for_unknown_directives(directives) - end + attr_reader :directives - ORDERED_DIRECTIVES.each do |directive_sym| - directives_set.select { |h| h.key?(directive_sym) }.each do |directives| - args = directives[directive_sym] - send("uninstall_#{directive_sym}", *(args.is_a?(Hash) ? [args] : args)) - end - end + def initialize(cask, directives) + super(cask) + @directives = directives end 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 - self.class.artifact_dsl_key + self.class.dsl_key end def warn_for_unknown_directives(directives) @@ -51,18 +58,18 @@ module Hbc # Preserve prior functionality of script which runs first. Should rarely be needed. # :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. - def uninstall_early_script(directives) - uninstall_script(directives, directive_name: :early_script) + def uninstall_early_script(directives, **options) + uninstall_script(directives, directive_name: :early_script, **options) end # :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| ohai "Removing launchctl service #{service}" [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 =~ /^\{/ - @command.run!("/bin/launchctl", args: ["remove", service], sudo: with_sudo) + command.run!("/bin/launchctl", args: ["remove", service], sudo: with_sudo) sleep 1 end paths = ["/Library/LaunchAgents/#{service}.plist", @@ -70,38 +77,38 @@ module Hbc paths.each { |elt| elt.prepend(ENV["HOME"]) } unless with_sudo paths = paths.map { |elt| Pathname(elt) }.select(&:exist?) 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 # undocumented and untested: pass a path to uninstall :launchctl next unless Pathname(service).exist? - @command.run!("/bin/launchctl", args: ["unload", "-w", "--", service], sudo: with_sudo) - @command.run!("/bin/rm", args: ["-f", "--", 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) sleep 1 end end end - def running_processes(bundle_id) - @command.run!("/bin/launchctl", args: ["list"]).stdout.lines - .map { |line| line.chomp.split("\t") } - .map { |pid, state, id| [pid.to_i, state.to_i, id] } - .select do |fields| - next if fields[0].zero? - fields[2] =~ /^#{Regexp.escape(bundle_id)}($|\.\d+)/ - end + def running_processes(bundle_id, command: nil) + command.run!("/bin/launchctl", args: ["list"]).stdout.lines + .map { |line| line.chomp.split("\t") } + .map { |pid, state, id| [pid.to_i, state.to_i, id] } + .select do |fields| + next if fields[0].zero? + fields[2] =~ /^#{Regexp.escape(bundle_id)}($|\.\d+)/ + end end # :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| ohai "Quitting application ID #{bundle_id}" - next if running_processes(bundle_id).empty? - @command.run!("/usr/bin/osascript", args: ["-e", %Q(tell application id "#{bundle_id}" to quit)], sudo: true) + 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) begin Timeout.timeout(3) do Kernel.loop do - break if running_processes(bundle_id).empty? + break if running_processes(bundle_id, command: command).empty? end end rescue Timeout::Error @@ -111,15 +118,15 @@ module Hbc end # :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| 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 signal, bundle_id = pair 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? # 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 @@ -133,10 +140,10 @@ module Hbc end end - def uninstall_login_item(*login_items) + def uninstall_login_item(*login_items, command: nil, **_) login_items.each do |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}")], sudo: false) sleep 1 @@ -144,23 +151,24 @@ module Hbc end # :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| 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 - @command.run!("/sbin/kextunload", args: ["-b", kext], sudo: true) + command.run!("/sbin/kextunload", args: ["-b", kext], sudo: true) sleep 1 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}" - @command.run!("/bin/rm", args: ["-rf", kext_path], sudo: true) + command.run!("/bin/rm", args: ["-rf", kext_path], sudo: true) end end end # :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, "uninstall", { must_succeed: true, sudo: false }, @@ -168,25 +176,25 @@ module Hbc directive_name) ohai "Running uninstall script #{executable}" - raise CaskInvalidError.new(@cask, "#{stanza} :#{directive_name} without :executable.") if executable.nil? - executable_path = @cask.staged_path.join(executable) + raise CaskInvalidError.new(cask, "#{stanza} :#{directive_name} without :executable.") if executable.nil? + executable_path = cask.staged_path.join(executable) unless executable_path.exist? message = "uninstall script #{executable} does not exist" - raise CaskError, "#{message}." unless force? + raise CaskError, "#{message}." unless force opoo "#{message}, skipping." return end - @command.run("/bin/chmod", args: ["--", "+x", executable_path]) - @command.run(executable_path, script_arguments) + command.run("/bin/chmod", args: ["--", "+x", executable_path]) + command.run(executable_path, script_arguments) sleep 1 end - def uninstall_pkgutil(*pkgs) + def uninstall_pkgutil(*pkgs, command: nil, **_) ohai "Uninstalling packages:" 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 pkg.uninstall end @@ -215,28 +223,28 @@ module Hbc end end - def uninstall_delete(*paths) + def uninstall_delete(*paths, command: nil, **_) return if paths.empty? ohai "Removing files:" each_resolved_path(:delete, paths) do |path, resolved_paths| 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 - def uninstall_trash(*paths) + def uninstall_trash(*paths, **options) return if paths.empty? resolved_paths = each_resolved_path(:trash, paths).to_a ohai "Trashing files:" puts resolved_paths.map(&:first) - trash_paths(*resolved_paths.flat_map(&:last)) + trash_paths(*resolved_paths.flat_map(&:last), **options) end - def trash_paths(*paths) - @command.run!("/usr/bin/osascript", args: ["-e", <<-'EOS'.undent, *paths]) + def trash_paths(*paths, command: nil, **_) + command.run!("/usr/bin/osascript", args: ["-e", <<-'EOS'.undent, *paths]) on run argv repeat with i from 1 to (count argv) set item i of argv to (item i of argv as POSIX file) @@ -260,7 +268,7 @@ module Hbc EOS end - def uninstall_rmdir(*directories) + def uninstall_rmdir(*directories, command: nil, **_) return if directories.empty? ohai "Removing directories if empty:" @@ -268,10 +276,10 @@ module Hbc puts path resolved_paths.select(&:directory?).each do |resolved_path| 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 - @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 diff --git a/Library/Homebrew/cask/lib/hbc/artifact/artifact.rb b/Library/Homebrew/cask/lib/hbc/artifact/artifact.rb index b42db877d0..0f37afaded 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/artifact.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/artifact.rb @@ -5,21 +5,32 @@ require "hbc/utils/hash_validator" module Hbc module Artifact class Artifact < Moved - def self.artifact_english_name + def self.english_name "Generic Artifact" end - def self.artifact_dirmethod - :appdir + def self.from_args(cask, *args) + 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 - def load_specification(artifact_spec) - source_string, target_hash = artifact_spec - raise CaskInvalidError.new(@cask.token, "no source given for artifact") if source_string.nil? - @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) - target_hash.extend(HashValidator).assert_valid_keys(:target) - @target = Pathname.new(target_hash[:target]) + def self.resolve_target(target) + Pathname(target) + end + + def initialize(cask, source, target: nil) + super(cask, source, target: target) end end end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/binary.rb b/Library/Homebrew/cask/lib/hbc/artifact/binary.rb index 7178c2af69..68f4b074da 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/binary.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/binary.rb @@ -3,13 +3,13 @@ require "hbc/artifact/symlinked" module Hbc module Artifact class Binary < Symlinked - def link - super + def link(command: nil, **options) + super(command: command, **options) return if source.executable? if source.writable? FileUtils.chmod "+x", source else - @command.run!("/bin/chmod", args: ["+x", source], sudo: true) + command.run!("/bin/chmod", args: ["+x", source], sudo: true) end end end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/installer.rb b/Library/Homebrew/cask/lib/hbc/artifact/installer.rb index be857696ec..64ce2d4bc5 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/installer.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/installer.rb @@ -1,31 +1,78 @@ -require "hbc/artifact/base" +require "hbc/artifact/abstract_artifact" module Hbc module Artifact - class Installer < Base - def install_phase - @cask.artifacts[self.class.artifact_dsl_key].each do |artifact| - if artifact.manual - puts <<-EOS.undent - To complete the installation of Cask #{@cask}, you must also - run the installer at + class Installer < AbstractArtifact + VALID_KEYS = Set.new [ + :manual, + :script, + ] - '#{@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 - else - 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 + '#{path}' + EOS 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 diff --git a/Library/Homebrew/cask/lib/hbc/artifact/moved.rb b/Library/Homebrew/cask/lib/hbc/artifact/moved.rb index 3fe969c0c2..ba1c8e9072 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/moved.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/moved.rb @@ -4,63 +4,61 @@ module Hbc module Artifact class Moved < Relocated def self.english_description - "#{artifact_english_name}s" + "#{english_name}s" end - def install_phase - each_artifact(&method(:move)) + def install_phase(**options) + move(**options) end - def uninstall_phase - each_artifact(&method(:delete)) + def uninstall_phase(**options) + 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 private - def move + def move(force: false, command: nil, **options) if Utils.path_occupied?(target) - message = "It seems there is already #{self.class.artifact_english_article} #{self.class.artifact_english_name} at '#{target}'" - raise CaskError, "#{message}." unless force? + message = "It seems there is already #{self.class.english_article} #{self.class.english_name} at '#{target}'" + raise CaskError, "#{message}." unless force opoo "#{message}; overwriting." - delete + delete(force: force, command: command, **options) end 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 - ohai "Moving #{self.class.artifact_english_name} '#{source.basename}' to '#{target}'." + ohai "Moving #{self.class.english_name} '#{source.basename}' to '#{target}'." target.dirname.mkpath if target.parent.writable? FileUtils.move(source, target) else - SystemCommand.run("/bin/mv", args: [source, target], sudo: true) + command.run("/bin/mv", args: [source, target], sudo: true) end - add_altname_metadata target, source.basename.to_s + add_altname_metadata(target, source.basename, command: command) end - def delete - ohai "Removing #{self.class.artifact_english_name} '#{target}'." - raise CaskError, "Cannot remove undeletable #{self.class.artifact_english_name}." if MacOS.undeletable?(target) + def delete(force: false, command: nil, **_) + ohai "Removing #{self.class.english_name} '#{target}'." + raise CaskError, "Cannot remove undeletable #{self.class.english_name}." if MacOS.undeletable?(target) return unless Utils.path_occupied?(target) if target.parent.writable? && !force target.rmtree else - 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}") + Utils.gain_permissions_remove(target, command: command) end end end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/nested_container.rb b/Library/Homebrew/cask/lib/hbc/artifact/nested_container.rb index 84253ea308..81adf90290 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/nested_container.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/nested_container.rb @@ -1,23 +1,31 @@ -require "hbc/artifact/base" +require "hbc/artifact/abstract_artifact" module Hbc module Artifact - class NestedContainer < Base - def install_phase - @cask.artifacts[:nested_container].each { |container| extract(container) } + class NestedContainer < AbstractArtifact + attr_reader :path + + def initialize(cask, path) + super(cask) + @path = cask.staged_path.join(path) end - def extract(container_relative_path) - source = @cask.staged_path.join(container_relative_path) - container = Container.for_path(source, @command) + def install_phase(**options) + extract(**options) + end + + private + + def extract(command: nil, verbose: nil, **_) + container = Container.for_path(path, command) unless container raise CaskError, "Aw dang, could not identify nested container at '#{source}'" end - ohai "Extracting nested container #{source.basename}" - container.new(@cask, source, @command, verbose: verbose?).extract - FileUtils.remove_entry_secure(source) + ohai "Extracting nested container #{path.relative_path_from(cask.staged_path)}" + container.new(cask, path, command, verbose: verbose).extract + FileUtils.remove_entry_secure(path) end end end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/pkg.rb b/Library/Homebrew/cask/lib/hbc/artifact/pkg.rb index be0a6be717..0967fd99d8 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/pkg.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/pkg.rb @@ -1,4 +1,4 @@ -require "hbc/artifact/base" +require "hbc/artifact/abstract_artifact" require "hbc/utils/hash_validator" @@ -6,62 +6,57 @@ require "vendor/plist/plist" module Hbc module Artifact - class Pkg < Base + class Pkg < AbstractArtifact attr_reader :pkg_relative_path - def self.artifact_dsl_key - :pkg + def self.from_args(cask, path, **options) + options.extend(HashValidator).assert_valid_keys(:allow_untrusted, :choices) + new(cask, path, **options) end - def load_pkg_description(pkg_description) - @pkg_relative_path = pkg_description.shift - @pkg_install_opts = pkg_description.shift - begin - if @pkg_install_opts.respond_to?(:keys) - @pkg_install_opts.extend(HashValidator).assert_valid_keys(:allow_untrusted, :choices) - elsif @pkg_install_opts - raise - end - raise if pkg_description.nil? - rescue StandardError - raise CaskInvalidError.new(@cask, "Bad pkg stanza") - end + attr_reader :path, :options + + def initialize(cask, path, **options) + super(cask) + @path = cask.staged_path.join(path) + @options = options end - def pkg_install_opts(opt) - @pkg_install_opts[opt] if @pkg_install_opts.respond_to?(:keys) + def summarize + path.relative_path_from(cask.staged_path).to_s end - def install_phase - @cask.artifacts[:pkg].each { |pkg_description| run_installer(pkg_description) } + def install_phase(**options) + run_installer(**options) end - def run_installer(pkg_description) - load_pkg_description pkg_description - ohai "Running installer for #{@cask}; your password may be necessary." + private + + 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." - source = @cask.staged_path.join(pkg_relative_path) - unless source.exist? - raise CaskError, "pkg source file not found: '#{source}'" + unless path.exist? + raise CaskError, "pkg source file not found: '#{path.relative_path_from(cask.staged_path)}'" end args = [ - "-pkg", source, + "-pkg", path, "-target", "/" ] - args << "-verboseR" if verbose? - args << "-allowUntrusted" if pkg_install_opts :allow_untrusted + args << "-verboseR" if verbose + args << "-allowUntrusted" if options.fetch(:allow_untrusted, false) with_choices_file do |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 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| begin - file.write Plist::Emit.dump(pkg_install_opts(:choices)) + file.write Plist::Emit.dump(choices) file.close yield file.path ensure diff --git a/Library/Homebrew/cask/lib/hbc/artifact/prefpane.rb b/Library/Homebrew/cask/lib/hbc/artifact/prefpane.rb index a44f8ae3a3..87f120934d 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/prefpane.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/prefpane.rb @@ -3,7 +3,7 @@ require "hbc/artifact/moved" module Hbc module Artifact class Prefpane < Moved - def self.artifact_english_name + def self.english_name "Preference Pane" end end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/qlplugin.rb b/Library/Homebrew/cask/lib/hbc/artifact/qlplugin.rb index ee41de2fe2..298714d894 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/qlplugin.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/qlplugin.rb @@ -3,22 +3,24 @@ require "hbc/artifact/moved" module Hbc module Artifact class Qlplugin < Moved - def self.artifact_english_name + def self.english_name "QuickLook Plugin" end - def install_phase - super - reload_quicklook + def install_phase(**options) + super(**options) + reload_quicklook(**options) end - def uninstall_phase - super - reload_quicklook + def uninstall_phase(**options) + super(**options) + reload_quicklook(**options) end - def reload_quicklook - @command.run!("/usr/bin/qlmanage", args: ["-r"]) + private + + def reload_quicklook(command: nil, **_) + command.run!("/usr/bin/qlmanage", args: ["-r"]) end end end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/relocated.rb b/Library/Homebrew/cask/lib/hbc/artifact/relocated.rb index 0e8e42c9be..72a6f6bb56 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/relocated.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/relocated.rb @@ -1,33 +1,57 @@ -require "hbc/artifact/base" +require "hbc/artifact/abstract_artifact" require "hbc/utils/hash_validator" module Hbc module Artifact - class Relocated < Base - def summary - { - english_description: self.class.english_description, - contents: @cask.artifacts[self.class.artifact_dsl_key].map(&method(:summarize_artifact)).compact, - } + class Relocated < AbstractArtifact + def self.from_args(cask, *args) + source_string, target_hash = args + + 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 attr_reader :source, :target - def printable_target - target.to_s.sub(/^#{ENV['HOME']}(#{File::SEPARATOR}|$)/, "~/") + def initialize(cask, source, target: nil) + 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 + def summarize + target_string = @target_string.empty? ? "" : " -> #{@target_string}" + "#{@source_string}#{target_string}" + end + + private + ALT_NAME_ATTRIBUTE = "com.apple.metadata:kMDItemAlternateNames".freeze # Try to make the asset searchable under the target name. Spotlight # respects this attribute for many filetypes, but ignores it for App # bundles. Alfred 2.2 respects it even for App bundles. - def add_altname_metadata(file, altname) - return if altname.casecmp(file.basename).zero? + def add_altname_metadata(file, altname, command: nil) + return if altname.to_s.casecmp(file.basename.to_s).zero? odebug "Adding #{ALT_NAME_ATTRIBUTE} metadata" - altnames = @command.run("/usr/bin/xattr", - args: ["-p", ALT_NAME_ATTRIBUTE, file.to_s], + altnames = command.run("/usr/bin/xattr", + args: ["-p", ALT_NAME_ATTRIBUTE, file], print_stderr: false).stdout.sub(/\A\((.*)\)\Z/, '\1') odebug "Existing metadata is: '#{altnames}'" altnames.concat(", ") unless altnames.empty? @@ -35,31 +59,15 @@ module Hbc altnames = "(#{altnames})" # 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], print_stderr: false) end - def each_artifact - @cask.artifacts[self.class.artifact_dsl_key].each do |artifact| - 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 + def printable_target + target.to_s.sub(/^#{ENV['HOME']}(#{File::SEPARATOR}|$)/, "~/") end end end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/stage_only.rb b/Library/Homebrew/cask/lib/hbc/artifact/stage_only.rb index 1122c1d02d..7aef664690 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/stage_only.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/stage_only.rb @@ -1,10 +1,18 @@ -require "hbc/artifact/base" +require "hbc/artifact/abstract_artifact" module Hbc module Artifact - class StageOnly < Base - def self.artifact_dsl_key - :stage_only + class StageOnly < AbstractArtifact + def self.from_args(cask, *args) + 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 diff --git a/Library/Homebrew/cask/lib/hbc/artifact/suite.rb b/Library/Homebrew/cask/lib/hbc/artifact/suite.rb index 35251f70c1..59ae58cf13 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/suite.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/suite.rb @@ -3,11 +3,11 @@ require "hbc/artifact/moved" module Hbc module Artifact class Suite < Moved - def self.artifact_english_name + def self.english_name "App Suite" end - def self.artifact_dirmethod + def self.dirmethod :appdir end end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/symlinked.rb b/Library/Homebrew/cask/lib/hbc/artifact/symlinked.rb index 16715fe81a..3726ebb5c5 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/symlinked.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/symlinked.rb @@ -8,47 +8,18 @@ module Hbc end def self.english_description - "#{artifact_english_name} #{link_type_english_name}s" + "#{english_name} #{link_type_english_name}s" end - def install_phase - each_artifact(&method(:link)) + def install_phase(**options) + link(**options) end - def uninstall_phase - each_artifact(&method(:unlink)) + def uninstall_phase(**options) + unlink(**options) end - private - - 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 - + def summarize_installed if target.symlink? && target.exist? && target.readlink.exist? "#{printable_target} -> #{target.readlink} (#{target.readlink.abv})" else @@ -61,6 +32,33 @@ module Hbc Formatter.error(string, label: "Broken Link") 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 diff --git a/Library/Homebrew/cask/lib/hbc/artifact/uninstall.rb b/Library/Homebrew/cask/lib/hbc/artifact/uninstall.rb index 5a3dc098d9..2bbf82862a 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/uninstall.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/uninstall.rb @@ -1,10 +1,10 @@ -require "hbc/artifact/uninstall_base" +require "hbc/artifact/abstract_uninstall" module Hbc module Artifact - class Uninstall < UninstallBase - def uninstall_phase - dispatch_uninstall_directives + class Uninstall < AbstractUninstall + def uninstall_phase(**options) + dispatch_uninstall_directives(**options) end end end diff --git a/Library/Homebrew/cask/lib/hbc/artifact/zap.rb b/Library/Homebrew/cask/lib/hbc/artifact/zap.rb index cdfe2531da..31ff54d201 100644 --- a/Library/Homebrew/cask/lib/hbc/artifact/zap.rb +++ b/Library/Homebrew/cask/lib/hbc/artifact/zap.rb @@ -1,10 +1,10 @@ -require "hbc/artifact/uninstall_base" +require "hbc/artifact/abstract_uninstall" module Hbc module Artifact - class Zap < UninstallBase - def zap_phase - dispatch_uninstall_directives + class Zap < AbstractUninstall + def zap_phase(**options) + dispatch_uninstall_directives(**options) end end end diff --git a/Library/Homebrew/cask/lib/hbc/audit.rb b/Library/Homebrew/cask/lib/hbc/audit.rb index b8bb6ab814..03d8cce826 100644 --- a/Library/Homebrew/cask/lib/hbc/audit.rb +++ b/Library/Homebrew/cask/lib/hbc/audit.rb @@ -214,12 +214,10 @@ module Hbc end def check_generic_artifacts - cask.artifacts[:artifact].each do |source, target_hash| - unless target_hash.is_a?(Hash) && target_hash[:target] - add_error "target required for generic artifact #{source}" - next + cask.artifacts[:artifact].each do |artifact| + unless artifact.target.absolute? + add_error "target must be absolute path for #{artifact.class.english_name} #{artifact.source}" end - add_error "target must be absolute path for generic artifact #{source}" unless Pathname.new(target_hash[:target]).absolute? end end diff --git a/Library/Homebrew/cask/lib/hbc/cask.rb b/Library/Homebrew/cask/lib/hbc/cask.rb index 6d89a997cc..27f8ae791f 100644 --- a/Library/Homebrew/cask/lib/hbc/cask.rb +++ b/Library/Homebrew/cask/lib/hbc/cask.rb @@ -17,7 +17,7 @@ module Hbc @token = token @sourcefile_path = sourcefile_path @tap = tap - @dsl = DSL.new(@token) + @dsl = DSL.new(self) return unless block_given? @dsl.instance_eval(&block) @dsl.language_eval diff --git a/Library/Homebrew/cask/lib/hbc/cli/info.rb b/Library/Homebrew/cask/lib/hbc/cli/info.rb index d26747e176..9cdada62e8 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/info.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/info.rb @@ -69,13 +69,11 @@ module Hbc def self.artifact_info(cask) ohai "Artifacts" - DSL::ORDINARY_ARTIFACT_TYPES.each do |type| - next if cask.artifacts[type].empty? - cask.artifacts[type].each do |artifact| - activatable_item = (type == :stage_only) ? "" : artifact.first - puts "#{activatable_item} (#{type})" - end - end + DSL::ORDINARY_ARTIFACT_CLASSES.flat_map { |klass| klass.for_cask(cask) } + .select { |artifact| artifact.respond_to?(:install_phase) } + .each do |artifact| + puts artifact.to_s + end end end end diff --git a/Library/Homebrew/cask/lib/hbc/cli/internal_stanza.rb b/Library/Homebrew/cask/lib/hbc/cli/internal_stanza.rb index 4515fe9319..2727f95b70 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/internal_stanza.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/internal_stanza.rb @@ -21,34 +21,9 @@ module Hbc # brew cask _stanza artifacts --table --yaml alfred google-chrome adium voicemac logisim vagrant # - # TODO: this should be retrievable from Hbc::DSL - ARTIFACTS = Set.new [ - :app, - :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, - ] + ARTIFACTS = + DSL::ORDINARY_ARTIFACT_CLASSES.map(&:dsl_key) + + DSL::ARTIFACT_BLOCK_CLASSES.map(&:dsl_key) option "--table", :table, false option "--quiet", :quiet, false @@ -93,7 +68,7 @@ module Hbc end begin - value = cask.send(@stanza) + value = cask.send(stanza) rescue StandardError opoo "failure calling '#{stanza}' on Cask '#{cask}'" unless quiet? puts "" @@ -108,8 +83,8 @@ module Hbc value = value.fetch(artifact_name).to_a.flatten if artifact_name - if @format - puts value.send(@format) + if format + puts value.send(format) elsif artifact_name || value.is_a?(Symbol) puts value.inspect else diff --git a/Library/Homebrew/cask/lib/hbc/cli/list.rb b/Library/Homebrew/cask/lib/hbc/cli/list.rb index 9d978360eb..4b5fcd8736 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/list.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/list.rb @@ -30,9 +30,9 @@ module Hbc end def self.list_artifacts(cask) - Artifact.for_cask(cask).each do |artifact| - summary = artifact.summary - ohai summary[:english_description], summary[:contents] unless summary.empty? + Artifact.for_cask(cask).group_by(&:class).each do |klass, artifacts| + next unless klass.respond_to?(:english_description) + ohai klass.english_description, artifacts.map(&:summarize_installed) end end diff --git a/Library/Homebrew/cask/lib/hbc/dsl.rb b/Library/Homebrew/cask/lib/hbc/dsl.rb index 8ad206c2f2..2dda476277 100644 --- a/Library/Homebrew/cask/lib/hbc/dsl.rb +++ b/Library/Homebrew/cask/lib/hbc/dsl.rb @@ -1,6 +1,8 @@ require "set" require "locale" +require "hbc/artifact" + require "hbc/dsl/appcast" require "hbc/dsl/base" require "hbc/dsl/caveats" @@ -8,7 +10,6 @@ require "hbc/dsl/conflicts_with" require "hbc/dsl/container" require "hbc/dsl/depends_on" require "hbc/dsl/gpg" -require "hbc/dsl/installer" require "hbc/dsl/postflight" require "hbc/dsl/preflight" require "hbc/dsl/stanza_proxy" @@ -18,39 +19,35 @@ require "hbc/dsl/version" module Hbc class DSL - ORDINARY_ARTIFACT_TYPES = [ - :app, - :artifact, - :audio_unit_plugin, - :binary, - :colorpicker, - :dictionary, - :font, - :input_method, - :internet_plugin, - :pkg, - :prefpane, - :qlplugin, - :screen_saver, - :service, - :stage_only, - :suite, - :vst_plugin, - :vst3_plugin, + ORDINARY_ARTIFACT_CLASSES = [ + Artifact::Installer, + Artifact::App, + Artifact::Artifact, + Artifact::AudioUnitPlugin, + Artifact::Binary, + Artifact::Colorpicker, + Artifact::Dictionary, + Artifact::Font, + Artifact::InputMethod, + Artifact::InternetPlugin, + Artifact::Pkg, + Artifact::Prefpane, + Artifact::Qlplugin, + Artifact::ScreenSaver, + Artifact::Service, + Artifact::StageOnly, + Artifact::Suite, + Artifact::VstPlugin, + Artifact::Vst3Plugin, + Artifact::Uninstall, + Artifact::Zap, ].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 = [ - :uninstall, - :zap, - ].freeze - - ARTIFACT_BLOCK_TYPES = [ - :preflight, - :postflight, - :uninstall_preflight, - :uninstall_postflight, + ARTIFACT_BLOCK_CLASSES = [ + Artifact::PreflightBlock, + Artifact::PostflightBlock, ].freeze DSL_METHODS = Set.new [ @@ -72,15 +69,15 @@ module Hbc :url, :version, :appdir, - *ORDINARY_ARTIFACT_TYPES, + *ORDINARY_ARTIFACT_CLASSES.map(&:dsl_key), *ACTIVATABLE_ARTIFACT_TYPES, - *SPECIAL_ARTIFACT_TYPES, - *ARTIFACT_BLOCK_TYPES, + *ARTIFACT_BLOCK_CLASSES.flat_map { |klass| [klass.dsl_key, klass.uninstall_dsl_key] }, ].freeze - attr_reader :token - def initialize(token) - @token = token + attr_reader :token, :cask + def initialize(cask) + @cask = cask + @token = cask.token end def name(*args) @@ -93,12 +90,14 @@ module Hbc return instance_variable_get("@#{stanza}") if should_return 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 instance_variable_set("@#{stanza}", yield) + rescue CaskInvalidError + raise rescue StandardError => e - raise CaskInvalidError.new(token, "'#{stanza}' stanza failed with: #{e}") + raise CaskInvalidError.new(cask, "'#{stanza}' stanza failed with: #{e}") end def homepage(homepage = nil) @@ -113,7 +112,7 @@ module Hbc return unless default 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 @language_blocks.default = block @@ -163,7 +162,7 @@ module Hbc DSL::Container.new(*args).tap do |container| # TODO: remove this backward-compatibility section after removing nested_container if container && container.nested - artifacts[:nested_container] << container.nested + artifacts[:nested_container] << Artifact::NestedContainer.new(cask, container.nested) end end end @@ -173,7 +172,7 @@ module Hbc def version(arg = nil) set_unique_stanza(:version, arg.nil?) do 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 DSL::Version.new(arg) end @@ -182,7 +181,7 @@ module Hbc def sha256(arg = nil) set_unique_stanza(:sha256, arg.nil?) do 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 arg end @@ -195,7 +194,7 @@ module Hbc begin @depends_on.load(*args) rescue RuntimeError => e - raise CaskInvalidError.new(token, e) + raise CaskInvalidError.new(cask, e) end @depends_on end @@ -237,39 +236,29 @@ module Hbc set_unique_stanza(:auto_updates, auto_updates.nil?) { auto_updates } end - ORDINARY_ARTIFACT_TYPES.each do |type| + ORDINARY_ARTIFACT_CLASSES.each do |klass| + type = klass.dsl_key + define_method(type) do |*args| - if type == :stage_only - if args != [true] - raise CaskInvalidError.new(token, "'stage_only' takes a single argument: true") + begin + if [*artifacts.keys, type].include?(:stage_only) && (artifacts.keys & ACTIVATABLE_ARTIFACT_TYPES).any? + raise CaskInvalidError.new(cask, "'stage_only' must be the only activatable artifact.") end - unless (artifacts.keys & ACTIVATABLE_ARTIFACT_TYPES).empty? - raise CaskInvalidError.new(token, "'stage_only' must be the only activatable artifact") - end + artifacts[type].add(klass.from_args(cask, *args)) + rescue CaskInvalidError + raise + rescue StandardError => e + raise CaskInvalidError.new(cask, "invalid '#{klass.dsl_key}' stanza: #{e}") end - - artifacts[type].add(args) end end - def installer(*args) - return artifacts[:installer] if args.empty? - artifacts[:installer] << DSL::Installer.new(*args) - raise "'stage_only' must be the only activatable artifact" if artifacts.key?(:stage_only) - rescue StandardError => e - 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 + ARTIFACT_BLOCK_CLASSES.each do |klass| + [klass.dsl_key, klass.uninstall_dsl_key].each do |dsl_key| + define_method(dsl_key) do |&block| + artifacts[dsl_key] << block + end end end diff --git a/Library/Homebrew/cask/lib/hbc/dsl/installer.rb b/Library/Homebrew/cask/lib/hbc/dsl/installer.rb deleted file mode 100644 index b01b28d762..0000000000 --- a/Library/Homebrew/cask/lib/hbc/dsl/installer.rb +++ /dev/null @@ -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 diff --git a/Library/Homebrew/cask/lib/hbc/installer.rb b/Library/Homebrew/cask/lib/hbc/installer.rb index 37cc4e5612..b9c34e3a1d 100644 --- a/Library/Homebrew/cask/lib/hbc/installer.rb +++ b/Library/Homebrew/cask/lib/hbc/installer.rb @@ -177,7 +177,7 @@ module Hbc already_installed_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 artifacts.each do |artifact| @@ -188,7 +188,7 @@ module Hbc next unless binaries? end - artifact.install_phase + artifact.install_phase(command: @command, verbose: verbose?, force: force?) already_installed_artifacts.unshift(artifact) end rescue StandardError => e @@ -196,7 +196,7 @@ module Hbc already_installed_artifacts.each do |artifact| next unless artifact.respond_to?(:uninstall_phase) odebug "Reverting installation of artifact of class #{artifact.class}" - artifact.uninstall_phase + artifact.uninstall_phase(command: @command, verbose: verbose?, force: force?) end ensure purge_versioned_files @@ -374,25 +374,27 @@ module Hbc def uninstall_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 artifacts.each do |artifact| next unless artifact.respond_to?(:uninstall_phase) odebug "Un-installing artifact of class #{artifact.class}" - artifact.uninstall_phase + artifact.uninstall_phase(command: @command, verbose: verbose?, force: force?) end end def zap ohai %Q(Implied "brew cask uninstall #{@cask}") uninstall_artifacts - if Artifact::Zap.me?(@cask) - ohai "Dispatching zap stanza" - Artifact::Zap.new(@cask, command: @command).zap_phase - else + if (zap_stanzas = Artifact::Zap.for_cask(@cask)).empty? opoo "No zap stanza present for Cask '#{@cask}'" + else + ohai "Dispatching zap stanza" + zap_stanzas.each do |stanza| + stanza.zap_phase(command: @command, verbose: verbose?, force: force?) + end end ohai "Removing all staged versions of Cask '#{@cask}'" purge_caskroom_path diff --git a/Library/Homebrew/cask/lib/hbc/staged.rb b/Library/Homebrew/cask/lib/hbc/staged.rb index c1aa01b295..dc21279dee 100644 --- a/Library/Homebrew/cask/lib/hbc/staged.rb +++ b/Library/Homebrew/cask/lib/hbc/staged.rb @@ -4,7 +4,7 @@ module Hbc index = 0 if index == :first index = 1 if index == :second 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 def plist_exec(cmd) diff --git a/Library/Homebrew/test/cask/artifact/alt_target_spec.rb b/Library/Homebrew/test/cask/artifact/alt_target_spec.rb index 9e8d83bb4a..02be796edc 100644 --- a/Library/Homebrew/test/cask/artifact/alt_target_spec.rb +++ b/Library/Homebrew/test/cask/artifact/alt_target_spec.rb @@ -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(: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") } diff --git a/Library/Homebrew/test/cask/artifact/app_spec.rb b/Library/Homebrew/test/cask/artifact/app_spec.rb index 0add472e21..f67ffd31b5 100644 --- a/Library/Homebrew/test/cask/artifact/app_spec.rb +++ b/Library/Homebrew/test/cask/artifact/app_spec.rb @@ -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(:command) { Hbc::SystemCommand } 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(:target_path) { Hbc.appdir.join("Caffeine.app") } - let(:install_phase) { app.install_phase } - let(:uninstall_phase) { app.uninstall_phase } + let(:install_phase) { app.install_phase(command: command, force: force) } + let(:uninstall_phase) { app.uninstall_phase(command: command, force: force) } before(:each) do 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 before(:each) do - system "/usr/bin/touch", "--", "#{target_path}/foo" - system "/bin/chmod", "--", "0555", target_path + FileUtils.touch "#{target_path}/foo" + FileUtils.chmod 0555, target_path end it "overwrites the existing app" do @@ -138,7 +138,7 @@ describe Hbc::Artifact::App, :cask do end after(:each) do - system "/bin/chmod", "--", "0755", target_path + FileUtils.chmod 0755, target_path end end end @@ -206,8 +206,8 @@ describe Hbc::Artifact::App, :cask do end describe "summary" do - let(:description) { app.summary[:english_description] } - let(:contents) { app.summary[:contents] } + let(:description) { app.class.english_description } + let(:contents) { app.summarize_installed } it "returns the correct english_description" do expect(description).to eq("Apps") @@ -217,14 +217,13 @@ describe Hbc::Artifact::App, :cask do it "returns the path to the app" do install_phase - expect(contents).to eq(["#{target_path} (#{target_path.abv})"]) + expect(contents).to eq("#{target_path} (#{target_path.abv})") end end describe "app is missing" do it "returns a warning and the supposed path to the app" do - expect(contents.size).to eq(1) - expect(contents[0]).to match(/.*Missing App.*: #{target_path}/) + expect(contents).to match(/.*Missing App.*: #{target_path}/) end end end diff --git a/Library/Homebrew/test/cask/artifact/binary_spec.rb b/Library/Homebrew/test/cask/artifact/binary_spec.rb index ce00e39357..5ffaca861f 100644 --- a/Library/Homebrew/test/cask/artifact/binary_spec.rb +++ b/Library/Homebrew/test/cask/artifact/binary_spec.rb @@ -26,7 +26,8 @@ describe Hbc::Artifact::Binary, :cask do end 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.readlink).to exist @@ -45,7 +46,8 @@ describe Hbc::Artifact::Binary, :cask do expect(FileUtils).to receive(:chmod) .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.readlink).to be_executable @@ -56,7 +58,8 @@ describe Hbc::Artifact::Binary, :cask do FileUtils.touch expected_path 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) expect(expected_path).not_to be :symlink? @@ -65,7 +68,8 @@ describe Hbc::Artifact::Binary, :cask do it "clobbers an existing symlink" do 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") end @@ -73,7 +77,8 @@ describe Hbc::Artifact::Binary, :cask do it "creates parent directory if it doesn't exist" do 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 end @@ -86,8 +91,10 @@ describe Hbc::Artifact::Binary, :cask do } it "links the binary to the proper directory" do - Hbc::Artifact::App.new(cask).install_phase - Hbc::Artifact::Binary.new(cask).install_phase + Hbc::Artifact::App.for_cask(cask) + .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.readlink).to exist diff --git a/Library/Homebrew/test/cask/artifact/generic_artifact_spec.rb b/Library/Homebrew/test/cask/artifact/generic_artifact_spec.rb index cb2ef9850d..bec8c27428 100644 --- a/Library/Homebrew/test/cask/artifact/generic_artifact_spec.rb +++ b/Library/Homebrew/test/cask/artifact/generic_artifact_spec.rb @@ -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(: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") } @@ -12,11 +12,11 @@ describe Hbc::Artifact::Artifact, :cask do InstallHelper.install_without_artifacts(cask) end - describe "with no target" do - let(:cask) { Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-generic-artifact-no-target.rb") } - - it "fails to install with no target" do - expect(install_phase).to raise_error(Hbc::CaskInvalidError) + context "without target" do + it "fails to load" do + expect { + Hbc::CaskLoader.load_from_file(TEST_FIXTURE_DIR/"cask/Casks/with-generic-artifact-no-target.rb") + }.to raise_error(Hbc::CaskInvalidError, /target required for Generic Artifact/) end end diff --git a/Library/Homebrew/test/cask/artifact/nested_container_spec.rb b/Library/Homebrew/test/cask/artifact/nested_container_spec.rb index be7ba5ff87..41d143764b 100644 --- a/Library/Homebrew/test/cask/artifact/nested_container_spec.rb +++ b/Library/Homebrew/test/cask/artifact/nested_container_spec.rb @@ -5,7 +5,8 @@ describe Hbc::Artifact::NestedContainer, :cask do InstallHelper.install_without_artifacts(c) 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 end diff --git a/Library/Homebrew/test/cask/artifact/pkg_spec.rb b/Library/Homebrew/test/cask/artifact/pkg_spec.rb index 3e62616ea2..c6a45c49ab 100644 --- a/Library/Homebrew/test/cask/artifact/pkg_spec.rb +++ b/Library/Homebrew/test/cask/artifact/pkg_spec.rb @@ -8,7 +8,7 @@ describe Hbc::Artifact::Pkg, :cask do describe "install_phase" 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( "/usr/sbin/installer", @@ -17,7 +17,7 @@ describe Hbc::Artifact::Pkg, :cask do print_stdout: true, ) - pkg.install_phase + pkg.install_phase(command: fake_system_command) 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") } 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")) @@ -57,7 +57,7 @@ describe Hbc::Artifact::Pkg, :cask do print_stdout: true, ) - pkg.install_phase + pkg.install_phase(command: fake_system_command) end end end diff --git a/Library/Homebrew/test/cask/artifact/postflight_block_spec.rb b/Library/Homebrew/test/cask/artifact/postflight_block_spec.rb index 51b1431f03..4a44bb59be 100644 --- a/Library/Homebrew/test/cask/artifact/postflight_block_spec.rb +++ b/Library/Homebrew/test/cask/artifact/postflight_block_spec.rb @@ -11,7 +11,8 @@ describe Hbc::Artifact::PostflightBlock, :cask do 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(yielded_arg).to be_kind_of(Hbc::DSL::Postflight) @@ -30,7 +31,8 @@ describe Hbc::Artifact::PostflightBlock, :cask do 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(yielded_arg).to be_kind_of(Hbc::DSL::UninstallPostflight) diff --git a/Library/Homebrew/test/cask/artifact/preflight_block_spec.rb b/Library/Homebrew/test/cask/artifact/preflight_block_spec.rb index b13c4ab9d3..d7d4e72d93 100644 --- a/Library/Homebrew/test/cask/artifact/preflight_block_spec.rb +++ b/Library/Homebrew/test/cask/artifact/preflight_block_spec.rb @@ -11,7 +11,8 @@ describe Hbc::Artifact::PreflightBlock, :cask do 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(yielded_arg).to be_kind_of Hbc::DSL::Preflight @@ -30,7 +31,8 @@ describe Hbc::Artifact::PreflightBlock, :cask do 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(yielded_arg).to be_kind_of Hbc::DSL::UninstallPreflight diff --git a/Library/Homebrew/test/cask/artifact/suite_spec.rb b/Library/Homebrew/test/cask/artifact/suite_spec.rb index 8c217a9e0c..2f913fecc7 100644 --- a/Library/Homebrew/test/cask/artifact/suite_spec.rb +++ b/Library/Homebrew/test/cask/artifact/suite_spec.rb @@ -1,7 +1,9 @@ describe Hbc::Artifact::Suite, :cask do 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(:source_path) { cask.staged_path.join("Caffeine") } diff --git a/Library/Homebrew/test/cask/artifact/two_apps_correct_spec.rb b/Library/Homebrew/test/cask/artifact/two_apps_correct_spec.rb index a1fdd3b74c..f6e0d3c97b 100644 --- a/Library/Homebrew/test/cask/artifact/two_apps_correct_spec.rb +++ b/Library/Homebrew/test/cask/artifact/two_apps_correct_spec.rb @@ -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(: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") } diff --git a/Library/Homebrew/test/cask/artifact/uninstall_no_zap_spec.rb b/Library/Homebrew/test/cask/artifact/uninstall_no_zap_spec.rb index 8cd0b1e41e..d6a8393dac 100644 --- a/Library/Homebrew/test/cask/artifact/uninstall_no_zap_spec.rb +++ b/Library/Homebrew/test/cask/artifact/uninstall_no_zap_spec.rb @@ -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(:zap_artifact) { - Hbc::Artifact::Zap.new(cask) + described_class.for_cask(cask).first } before(:each) do diff --git a/Library/Homebrew/test/cask/artifact/uninstall_zap_shared_examples.rb b/Library/Homebrew/test/cask/artifact/uninstall_zap_shared_examples.rb index 0e522bc210..06eec4a011 100644 --- a/Library/Homebrew/test/cask/artifact/uninstall_zap_shared_examples.rb +++ b/Library/Homebrew/test/cask/artifact/uninstall_zap_shared_examples.rb @@ -1,12 +1,12 @@ shared_examples "#uninstall_phase or #zap_phase" do - let(:artifact_name) { described_class.artifact_name } - let(:artifact) { described_class.new(cask, command: fake_system_command) } + let(:artifact_dsl_key) { described_class.dsl_key } + let(:artifact) { described_class.for_cask(cask).first } 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 - 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_remove_cmd) { %w[/bin/launchctl remove my.fancy.package.service] } 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 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(:agent_pkg_id) { "my.fancy.package.agent" } @@ -85,7 +85,7 @@ shared_examples "#uninstall_phase or #zap_phase" do end 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" } it "is supported" do @@ -110,7 +110,7 @@ shared_examples "#uninstall_phase or #zap_phase" do end 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(:quit_application_script) do %Q(tell application id "#{bundle_id}" to quit) @@ -130,7 +130,7 @@ shared_examples "#uninstall_phase or #zap_phase" do end 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(:signals) { %w[TERM KILL] } let(:unix_pids) { [12_345, 67_890] } @@ -170,10 +170,10 @@ shared_examples "#uninstall_phase or #zap_phase" do end 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 - 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| result = method.call(*args) FileUtils.rm_rf result.stdout.split("\0") @@ -196,7 +196,7 @@ shared_examples "#uninstall_phase or #zap_phase" do context "using :rmdir" do 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(: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| context "using #{script_type.inspect}" do 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(:script_pathname) { cask.staged_path.join("MyFancyPkg", "FancyUninstaller.tool") } @@ -250,7 +250,7 @@ shared_examples "#uninstall_phase or #zap_phase" do end 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 Hbc::FakeSystemCommand.expects_command( diff --git a/Library/Homebrew/test/cask/audit_spec.rb b/Library/Homebrew/test/cask/audit_spec.rb index ddc773e3e2..7e140acb24 100644 --- a/Library/Homebrew/test/cask/audit_spec.rb +++ b/Library/Homebrew/test/cask/audit_spec.rb @@ -265,19 +265,14 @@ describe Hbc::Audit, :cask do end 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 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 context "with absolute target" do 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 diff --git a/Library/Homebrew/test/cask/cli/info_spec.rb b/Library/Homebrew/test/cask/cli/info_spec.rb index bffe900ec4..aec7080dea 100644 --- a/Library/Homebrew/test/cask/cli/info_spec.rb +++ b/Library/Homebrew/test/cask/cli/info_spec.rb @@ -10,7 +10,7 @@ describe Hbc::CLI::Info, :cask do ==> Name None ==> Artifacts - Caffeine.app (app) + Caffeine.app (App) EOS end @@ -24,7 +24,7 @@ describe Hbc::CLI::Info, :cask do ==> Name None ==> Artifacts - Caffeine.app (app) + Caffeine.app (App) local-transmission: 2.61 http://example.com/local-transmission Not installed @@ -32,7 +32,7 @@ describe Hbc::CLI::Info, :cask do ==> Name None ==> Artifacts - Transmission.app (app) + Transmission.app (App) EOS } @@ -60,7 +60,7 @@ describe Hbc::CLI::Info, :cask do ==> Name None ==> Artifacts - Caffeine.app (app) + Caffeine.app (App) ==> Caveats Here are some things you might want to know. @@ -86,7 +86,7 @@ describe Hbc::CLI::Info, :cask do ==> Name None ==> Artifacts - Caffeine.app (app) + Caffeine.app (App) EOS end diff --git a/Library/Homebrew/test/cask/cli/list_spec.rb b/Library/Homebrew/test/cask/cli/list_spec.rb index ecca3035f5..fd6997f412 100644 --- a/Library/Homebrew/test/cask/cli/list_spec.rb +++ b/Library/Homebrew/test/cask/cli/list_spec.rb @@ -48,7 +48,8 @@ describe Hbc::CLI::List, :cask do it "lists the installed files for those Casks" do 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 { Hbc::CLI::List.run("local-transmission", "local-caffeine") diff --git a/Library/Homebrew/test/cask/dsl_spec.rb b/Library/Homebrew/test/cask/dsl_spec.rb index 7f2207a877..aec1e917f3 100644 --- a/Library/Homebrew/test/cask/dsl_spec.rb +++ b/Library/Homebrew/test/cask/dsl_spec.rb @@ -186,12 +186,12 @@ describe Hbc::DSL, :cask do app "Bar.app" 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 it "allow app stanzas to be empty" do cask = Hbc::Cask.new("cask-with-no-apps") - expect(Array(cask.artifacts[:app])).to eq([]) + expect(cask.artifacts[:app]).to be_empty end end @@ -219,7 +219,7 @@ describe Hbc::DSL, :cask do pkg "Bar.pkg" 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 @@ -471,10 +471,10 @@ describe Hbc::DSL, :cask do let(:token) { "with-installer-script" } 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.script[: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].script[:args]).to eq(["--flag"]) + expect(cask.artifacts[:installer].first.path).to eq(Pathname("/usr/bin/true")) + expect(cask.artifacts[:installer].first.args[:args]).to eq(["--flag"]) + expect(cask.artifacts[:installer].to_a[1].path).to eq(Pathname("/usr/bin/false")) + expect(cask.artifacts[:installer].to_a[1].args[:args]).to eq(["--flag"]) end end @@ -482,7 +482,9 @@ describe Hbc::DSL, :cask do let(:token) { "with-installer-manual" } 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 @@ -492,7 +494,7 @@ describe Hbc::DSL, :cask do let(:token) { "stage-only" } 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 @@ -518,7 +520,7 @@ describe Hbc::DSL, :cask do let(:token) { "appdir-interpolation" } 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 @@ -531,7 +533,7 @@ describe Hbc::DSL, :cask do binary "#{appdir}/some/path" 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 Hbc.appdir = original_appdir end